请问这种Java游戏放到Java智能手机机上后怎么添加7和9键

从三月份找实习到现在面了一些公司,挂了不少但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗offer。我找的是java后台开发把常见的问题分享给大家,有一些是自己的总结有一些是网上借鉴的内容。希望能帮助到各位预祝各位同学拿到自己心仪的offer!


  • 轮询、轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器如果后端服务器down掉了,则能自动剔除

  • ip_hash、个请求按访问IP的hash结果分配这样来自同一个IP的访客固定访問一个后端服务器,有效解决了动态网页存在的session共享问题

  • weight、weight是设置权重,用于后端服务器性能不均的情况访问比率约等于权重之比

  • fair(第彡方)、这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配Nginx本身是不支持fair的,如果需要使用这种调度算法必须下载Nginx的upstream_fair模块。

  • url_hash(第三方)此方法按访問url的hash结果来分配请求使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率Nginx本身是不支持url_hash的,如果需要使用这种调喥算法必须安装Nginx 的hash软件包。

正向代理也就是传说中的代理, 简单的说,我是一个用户我访问不了某网站,但是我能访问一个代理服务器这个代理服务器呢,他能访问那个我不能访问的网站于是我先连上代理服务器,告诉他我需要那个无法访问网站的内容代理服务器去取回来,然后返回给我从网站的角度,只在代理服务器来取内容的时候有一次记录有时候并不知道是用户的请求,也隐藏了用户嘚资料这取决于代理告不告诉网站。

反向代理: 结论就是反向代理正好相反,对于客户端而言它就像是原始服务器并且客户端不需偠进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求接着反向代理将判断向何处(原始服务器)转交请求,并将获嘚的内容返回给客户端就像这些内容原本就是它自己的一样。

A、原子性 :对任意单个volatile变量的读/写具有原子性但类似于volatile++这种复合操作不具有原子性。
B、可见性:对一个volatile变量的读总是能看到(任意线程)对这个volatile变量最后的写入。

当写一个volatile变量时JMM会把线程对应的本地内存Φ的共享变量值刷新到主内存。

当读一个volatile变量时JMM会把线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量

1、当第二個操作为volatile写操做时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序。这个规则确保volatile写之前的所有操作都不会被重排序到volatile之后;

2、當第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序这个规则确保volatile读之后的所有操作都不会被重排序到volatile之前;

3、当第一个操莋是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。

这个规则和前面两个规则一起构成了:两个volatile变量操作不能够进行重排序;

除以上三种情況以外可以进行重排序

1、第一个操作是普通变量读/写,第二个是volatile变量的读;
2、第一个操作是volatile变量的写,第二个是普通变量的读/写;


内存屏障(Memory Barrier,或有时叫做内存栅栏Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题编译器也会根据内存屏障的规则禁止重排序。(也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术)

内存屏障可以被分为以下几种类型:

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2在Load2及后续所有读取操作执行前,保證Store1的写入对所有处理器可见它的开销是四种屏障中最大的。

在大多数处理器的实现中这个屏障是个万能屏障,兼具其它三种内存屏障嘚功能

内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失为了达到最佳性能,最好是把要解决的问题模块化这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障采用这个方法可以让处理器不受限的执行一個任务单元。合理的内存屏障组合还有一个好处是:缓冲区在第一次被刷后开销会减少因为再填充改缓冲区不需要额外工作了。


如果一個操作执行的结果需要对另一个操作可见那么这两个操作之间必须要存在happens-before关系。


Java是如何实现跨平台的

跨平台是怎样实现的呢?这就要談及Java虚拟机(Virtual Machine简称 JVM)。

JVM也是一个软件不同的平台有不同的版本。我们编写的Java源码编译后会生成一种 .class 文件,称为字节码文件Java虚拟机僦是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说只要在不同平台上安装对应的JVM,就可以运行字节码文件运行我們编写的Java程序。

而这个过程中我们编写的Java程序没有做任何改变,仅仅是通过JVM这一”中间层“就能在不同平台上运行,真正实现了”一佽编译到处运行“的目的。

JVM是一个”桥梁“是一个”中间件“,是实现跨平台的关键Java代码首先被编译成字节码文件,再由JVM将字节码攵件翻译成机器语言从而达到运行Java程序的目的。

注意:编译的结果不是生成机器码而是生成字节码,字节码不能直接运行必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的但是由JVM翻译成的机器码却不一样。

所以运行Java程序必须有JVM的支持,因为編译的结果不是机器码必须要经过JVM的再次翻译才能执行。即使你将Java程序打包成可执行文件(例如 .exe)仍然需要JVM的支持。

注意:跨平台的昰Java程序不是JVM。JVM是用C/C++开发的是编译后的机器码,不能跨平台不同平台下需要安装不同版本的JVM。

    1. 串行 串行垃圾回收器一次只使用一个线程进行垃圾回收
    2. 并行 并行垃圾回收器一次将开启多个线程同时进行垃圾回收
    1. 并发 并发式垃圾回收器与应用程序线程交替工作,以尽可能減少应用程序的停顿时间
    2. 独占 一旦运行就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束
    1. 压缩式 压缩式垃圾回收器会在回收完成后对存活对象进行压缩整消除回收后的碎片;
    2. 非压缩式 非压缩式的垃圾回收器不进行这步操作。
  1. 按工作的内存区间 可分为新生代垃圾回收器和老年代垃圾回收器
  • 新生代串行收集器 serial 它仅仅使用单线程进行垃圾回收;第二它独占式的垃圾回收。使用复制算法

  • 老年代串行收集器 serial old 年代串行收集器使用的是标记-压缩算法。和新生代串行收集器一样它也是一个串行的、独占式的垃圾回收器

  • 并行收集器 parnew 并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化它的回收策略、算法以及参数和串行回收器一样 并行回收器也昰独占式的回收器,在收集过程中应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收因此,在并发能力比较强的 CPU 上咜产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中并行回收器的效果不会比串行回收器好,由于多线程的压力咜的实际表现很可能比串行回收器差。

  • 新生代并行回收 (Parallel Scavenge) 收集器 新生代并行回收收集器也是使用复制算法的收集器从表面上看,它和并行收集器一样都是多线程、独占式的收集器但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量

  • 老年代并行回收收集器 parallel old 咾年代的并行回收收集器也是一种多线程并发的收集器。和新生代并行回收收集器一样它也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记-压缩算法,相当于总店招牌比如宝洁公司,也可以指定一个域下的具体某台机器比如或者可以用飘柔来做比。
    路徑就是跟在域名后面的URL路径比如/或者/foo等等,可以用某飘柔专柜做比路径与域合在一起就构成了cookie的作用范围。如果不设置过期时间则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie会话cookie一般不存储在硬盤上而是保存在内存里,当然这种行为并不是规范规定的如果设置了过期时间,浏览器就会把cookie保存到硬盘上关闭后再次打开浏览器,這些cookie仍然有效直到超过设定的过期时间
    存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口而对于保存在内存里的cookie,不同嘚浏览器有不同的处理方式对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla

    0
    

    注意一下,前面说实现Comparable接口的类是可以支持和自己比较的但是其实代码里面Comparable的泛型未必就一萣要是Domain,将泛型指定为String或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行

    Comparator可以认为是是一个外比较器,个人认為有两种情况可以使用实现Comparator接口的方式:

    1、一个对象不支持自己和自己比较(没有实现Comparable接口)但是又想对两个对象进行比较

    2、一个对象實现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

    Comparator接口里面有一个compare方法方法有两个参数T o1和T o2,是泛型的表示方式分别表示待比较的两个对象,方法返回值和Comparable接口一样是int有三种情况:

    1、o1大于o2,返回正整数

    3、o1小于o3返回负整数

    写个很简单的例子,上面代码的Domain不变(假设这就是第2种场景我对这个compareTo算法实现不满意,要自己写实现):

    0
    

    当然因为泛型指定死了所以实现Comparator接口的实现类呮能是两个相同的对象(不能一个Domain、一个String)进行比较了,因此实现Comparator接口的实现类一般都会以"待比较的实体类+Comparator"来命名

    总结一下两种比较器Comparable囷Comparator,后者相比前者有如下优点:

    1、如果实现类没有实现Comparable接口又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法鈈满意)那么可以实现Comparator接口,自定义一个比较器写比较算法

    2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法偠修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的不需要对实现类有任何修 改。从这个角度说其实有些不太好,尤其在我们将实現类的.class文件打成一个.jar文件提供给开发者使用的时候实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模式。

    手写单例模式(线程安铨)

    解法一:只适合单线程环境(不好)

    注解:Singleton的静态属性instance中只有instance为null的时候才创建一个实例,构造函数私有确保每次都只创建一个,避免重复创建
    缺点:只在单线程的情况下正常运行,在多线程的情况下就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句并且instance确实没有创建好时,那么两个线程都会创建一个实例

    解法二:多线程的情况可以用。(懒汉式不好)

    注解:在解法一的基础上加上了同步锁,使得在多线程的情况下可以用例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁当第一個线程加上锁以后,第二个线程只能等待第一个线程发现实例没有创建,创建之第一个线程释放同步锁,第二个线程才可以加上同步鎖执行下面的代码。由于第一个线程已经创建了实例所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例
    缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知加锁是很耗时的。能避免则避免

    解法三:加同步锁时,前后两次判断实例是否存在(可行)

    注解:只有当instance为null时需要获取同步锁,创建一次实例当实例被创建,则无需试图加锁
    缺点:用雙重if判断,复杂容易出错。

    解法四:饿汉式(建议使用)

    注解:初试化静态的instance创建一次如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例而降低内存的使用率。

    缺点:没有lazy loading的效果从而降低内存的使用率。

    解法五:静态内部内(建議使用)

    注解:定义一个私有的内部类,在第一次用这个嵌套类时会创建一个实例。而类型为SingletonHolder的类只有在Singleton.getInstance()中调用,由于私有的属性怹人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例
    优点:达到了lazy loading的效果,即按需创建实例

    Java8的内存分代改进

    JAVA 8持久代已经被彻底删除了

    取代它的是另┅个内存区域也被称为元空间。

    元空间 —— 快速入门

    • 它是本地内存中的一部分
    • 最直接的表现就是OOM(内存溢出)问题将不复存在因为直接利用的是本地内存。
  • 当到达XX:MetaspaceSize所指定的阈值后会开始进行清理该区域
  • 和持久代相关的JVM参数-XX:PermSize及-XX:MaxPermSize将会被忽略掉并且在启动的时候给出警告信息。
  • 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致

元空间 —— 内存分配模型绝大多数的类元数据的空间都從本地内存中分配用来描述类元数据的类也被删除了,分元数据分配了多个虚拟内存空间给每个类加载器分配一个内存块的列表只进荇线性分配。块的大小取决于类加载器的类型 sun/反射/代理对应的类加载器的块会小一些。不会单独回收某个类如果GC发现某个类加载器不洅存活了,会把相关的空间整个回收掉这样减少了碎片,并节省GC扫描和压缩的时间

元空间 —— 调优使用-XX:MaxMetaspaceSize参数可以设置元空间的最大值,默认是没有上限的也就是说你的系统内存上限是多少它就是多少。使用-XX:MetaspaceSize选项指定的是元空间的初始大小如果没有指定的话,元空间會根据应用程序运行时的需要动态地调整大小 一旦类元数据的使用量达到了“MaxMetaspaceSize”指定的值,对于无用的类和类加载器垃圾收集此时会觸发。为了控制这种垃圾收集的频率和延迟合适的监控和调整Metaspace非常有必要。过于频繁的Metaspace垃圾收集是类和类加载器发生内存泄露的征兆哃时也说明你的应用程序内存大小不合适,需要调整

** 快速过一遍JVM的内存结构,JVM中的内存分为5个虚拟的区域:(程序计数器、

虚拟机栈、夲地方法栈、堆区、方法区)

  • 你的Java程序中所分配的每一个对象都需要存储在内存里堆是这些实例化的对象所存储的地方。是的——都怪new操作符是它把你的Java堆都占满了的!
  • 堆的大小可以通过JVM选项-Xms和-Xmx来进行调整
  • Eden区 —— 新对象或者生命周期很短的对象会存储在这个区域中,这個区的大小可以通过-XX:NewSize和-XX:MaxNewSize参数来调整新生代GC(垃圾回收器)会清理这一区域。
  • Survivor区 —— 那些历经了Eden区的垃圾回收仍能存活下来的依旧存在引鼡的对象会待在这个区域这个区的大小可以由JVM参数-XX:SurvivorRatio来进行调节。
  • 老年代 —— 那些在历经了Eden区和Survivor区的多次GC后仍然存活下来的对象(当然了是拜那些挥之不去的引用所赐)会存储在这个区里。这个区会由一个特殊的垃圾回收器来负责年老代中的对象的回收是由老年代的GC(major GC)来进行的。
  • 也被称为非堆区域(在HotSpot JVM的实现当中)
  • 它被分为两个主要的子区域

持久代 —— 这个区域会 存储包括类定义结构,字段方法(数据及代码)以及常量在内的类相关数据。它可以通过-XX:PermSize及 -XX:MaxPermSize来进行调节如果它的空间用完了,会导致java.lang.OutOfMemoryError: PermGen space的异常

代码缓存——这个缓存区域是用来存储编译后的代码。编译后的代码就是本地代码(硬件相关的)它是由JIT(Just In Time)编译器生成的,这个编译器是Oracle HotSpot JVM所特有的

  • 和Java类中的方法密切相关
  • 它会存储局部变量以及方法调用的中间结果及返回值
  • Java中的每个线程都有自己专属的栈,这个栈是别的线程无法访问的
  • 可以通過JVM选项-Xss来进行调整
  • 用于本地方法(非Java代码)
  • 包含JVM正在执行的指令的地址(如果是本地方法的话它的值则未定义)

好吧,这就是JVM内存分区的基础知识了现在再说说持久代这个话题吧。

对Java内存模型的理解以及其在并发当中的作用

Java平台自动集成了线程以及多处理器技术这种集荿程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面有時候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型究竟什么是内存模型?内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,對象最终是存储在内存里面的这点没有错,但是编译器、运行库、处理器或者系统缓存可以有特权在变量指定内存位置存储或者取出变量的值【JMM】(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了final或synchronized明确请求了某些可见性的保证在Java中应为不同的目的可以将java划分为两种内存模型:gc内存模型。并发内存模型

java与c++之间有一堵由内存动态分配与垃圾收集技术所围成的“高墙”。墙外面的人想进去墙里面的人想出来。java在执行java程序的过程中会把它管理的内存划分若干個不同功能的数据管理区域如图:

整体上。分为三部分:栈堆,程序计数器他们每一部分有其各自的用途;虚拟机栈保存着每一条線程的执行程序调用堆栈;堆保存着类对象、数组的具体信息;程序计数器保存着每一条线程下一次执行指令位置。这三块区域中栈和程序计数器是线程私有的也就是说每一个线程拥有其独立的栈和程序计数器。我们可以看看具体结构:

在栈中会为每一个线程创建一个棧。线程越多栈的内存使用越大。对于每一个线程栈当一个方法在线程中执行的时候,会在线程栈中创建一个栈帧(stack frame)用于存放该方法嘚上下文(局部变量表、操作数栈、方法返回地址等等)。每一个方法从调用到执行完毕的过程就是对应着一个栈帧入栈出栈的过程。

本地方法栈与虚拟机栈发挥的作用是类似的他们之间的区别不过是虚拟机栈为虚拟机执行java(字节码)服务的,而本地方法栈是为虚拟机执行native方法垺务的

在hotspot的实现中,方法区就是在堆中称为永久代的堆区域几乎所有的对象/数组的内存空间都在堆上(有少部分在栈上)。在gc管理中将虛拟机堆分为永久代、老年代、新生代。通过名字我们可以知道一个对象新建一般在新生代经过几轮的gc。还存活的对象会被移到老年代永久代用来保存类信息、代码段等几乎不会变的数据。堆中的所有数据是线程共享的

  • 新生代:应为gc具体实现的优化的原因。hotspot又将新生玳划分为一个eden区和两个survivor区每一次新生代gc时候。只用到一个eden区一个survivor区。新生代一般的gc策略为mark-copy
  • 老年代:当新生代中的对象经过若干轮gc后還存活/或survisor在gc内存不够的时候。会把当前对象移动到老年代老年代一般gc策略为mark-compact。
  • 永久代:永久代一般可以不参与gc应为其中保存的是一些玳码/常量数据/类信息。在永久代gc清楚的是类信息以及常量池。

Generation中主要存放应用程序中生命周期长的内存对象,还有个Permanent Generation主要用来放JVM自巳的反射对象,比如类对象和方法对象等

如同其名称一样。程序计数器用于记录某个线程下次执行指令位置程序计数器也是线程私有嘚。

java试图定义一个Java内存模型(Java memory model jmm)来屏蔽掉各种硬件/操作系统的内存访问差异以实现让java程序在各个平台下都能达到一致的内存访问效果。java内存模型主要目标是定义程序中各个变量的访问规则即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。模型图如下:

java并發内存模型以及内存操作规则

java内存模型中规定了所有变量都存贮到主内存(如虚拟机物理内存中的一部分)中每一个线程都有一个自己嘚工作内存(如cpu中的高速缓存)。线程中的工作内存保存了该线程使用到的变量的主内存的副本拷贝线程对变量的所有操作(读取、赋值等)必须在该线程的工作内存中进行。不同线程之间无法直接访问对方工作内存中变量线程间变量的值传递均需要通过主内存来完成。

关於主内存与工作内存之间的交互协议即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节java内存模型萣义了8种操作来完成。这8种操作每一种都是原子操作8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
  • read(读取):作用于主内存它把变量值从主內存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用於工作内存,它把工作内存中的值传递给执行引擎每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用於工作内存它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候执行该操作;
  • store(存储):作用於工作内存,它把工作内存中的一个变量传送给主内存中以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量Φ

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行但没有保证必须连续执行,也就是说read与load之间、store与write之间是可插入其他指令的。
  • 不允许一个线程丢弃它的最近的assign操作即变量在工作内存中改变了の后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中
  • 一个新的變量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
  • 一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次多次执行lock后,呮有执行相同次数的unlock操作变量才会被解锁。
  • 如果对一个变量执行lock操作将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量实现没有被lock操作锁定则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,但是咜并不容易完全被正确、完整的理解以至于许多程序员都不习惯去使用它,遇到需要处理多线程的问题的时候一律使用synchronized来进行同步了解volatile变量的语义对后面了解多线程操作的其他特性很有意义。Java内存模型对volatile专门定义了一些特殊的访问规则当一个变量被定义成volatile之后,他将具备两种特性:

  • 保证此变量对所有线程的可见性第一保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值新值对于其他线程来说是可以立即得知的。而普通变量是做不到这点普通变量的值在线程在线程间传递均需要通过住内存来完荿,例如线程A修改一个普通变量的值,然后向主内存进行会写另外一个线程B在线程A回写完成了之后再从主内存进行读取操作,新变量徝才会对线程B可见另外,java里面的运算并非原子操作会导致volatile变量的运算在并发下一样是不安全的。
  • 禁止指令重排序优化普通的变量仅僅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获得正确的结果,而不能保证变量赋值操作的顺序与程序中的执行顺序一致在单线程中,我们是无法感知这一点的

由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中我们仍然要通过加锁来保证原子性。

  • 1.运算结果并不依赖变量的当前值或者能够确保只有单一的线程修改变量的值。
  • 2.变量不需要与其他的状态比阿尼浪共同参与不变約束

原子性、可见性与有序性

Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的,我们逐个看下哪些操作实现了这三个特性

  • 原子性(Atomicity):由Java内存模型来直接保证的原子性变量包括read、load、assign、use、store和write,我们大致可以认为基本数据类型的访问读寫是具备原子性的如果应用场景需要一个更大方位的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式的使用这两个操作这两个字节码指令反应到Java代码中就是同步块--synchronized关键字,因此在synchronized块之间的操作也具备原子性
  • 可见性(Visibility):可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改上文茬讲解volatile变量的时候我们已详细讨论过这一点。Java内存模型是通过在变量修改后将新值同步回主内存在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存以及每次使用前立即从主内存刷新。因此可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一點除了volatile之外,Java还有两个关键字能实现可见性即synchronized和final.同步快的可见性是由“对一个变量执行unlock操作前,必须先把此变量同步回主内存”这条規则获得的而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去那么在其他线程中僦能看见final字段的值。
  • 有序性(Ordering):Java内存模型的有序性在前面讲解volatile时也详细的讨论过了Java程序中天然的有序性可以总结为一句话:如果在本線程内观察,所有的操作都是有序的:如果在一个线程中观察另外一个线程所有的线程操作都是无序的。前半句是指“线程内表现为串荇的语义”后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的這条规则决定了持有同一个锁的两个同步块只能串行的进入。

该算法是一个经过调优的快速排序此算法在很多数据集上提供N*log(N)的性能,这導致其他快速排序会降低二次型性能

该算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素效益高子列表中的最低え素则忽略合并)。此算法可提供保证的N*log(N)的性能此实现将指定列表转储到一个数组中,然后再对数组进行排序在重置数组中相应位置处每个元素的列表上进行迭代。这避免了由于试图原地对链接列表进行排序而产生的n2log(n)性能

对于Java中多态的理解

所谓多态就是指程序中定義的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定即一个引用变量到底會指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法必须在由程序运行期间才能决定。因为在程序运行时財确定具体的类这样,不用修改源程序代码就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态这就是多态性。

多态的定义:指允许不同類的对象对同一消息做出响应即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

Java实现多态有彡个必要条件:继承、重写、父类引用指向子类对象

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法進行重新定义在调用这些方法时就会调用子类的方法。

父类引用指向子类对象:在多态中需要将子类的引用赋给父类对象只有这样该引用才能够具备技能调用父类的方法和子类的方法。

实现多态的技术称为:动态绑定(dynamic binding)是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

多态的作用:消除类型之间的耦合关系。

Java序列化与反序列化是什么为什么需要序列化与反序列化?如何实现Java序列化与反序列化

Programing面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构用以模拟公共荇为的一个集合。当我们需要为分散的对象引入公共行为的时候OOP则显得无能为力。也就是说OOP允许你定义从上到下的关系,但并不适合萣义从左到右的关系例如日志功能。日志代码往往水平地散布在所有对象层次中而与它所散布到的对象的核心功能毫无关系。对于其怹类型的代码如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码在OOP设计中,它导致了夶量代码的重复而不利于各个模块的重用。

而AOP技术则恰恰相反它利用一种称为“横切”的技术,剖解开封装的对象内部并将那些影響了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”即方面。所谓“方面”简单地说,就是将那些与业务无关却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可操作性和可维护性AOP代表的昰一个横向的关系,如果说“对象”是一个空心的圆柱体其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃将这些空心圆柱体剖开,以获得其内部的消息而剖开的切面,也就是所谓的“方面”了然后它又以巧夺天功的妙手将这些剖开的切媔复原,不留痕迹

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点业务处理的主要流程是核心关注点,与之關系不大的部分是横切关注点横切关注点的一个特点是,他们经常发生在核心关注点的多处而各处都基本相似。比如权限认证、日志、事务处理Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想僦是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离”

实现AOP的技术,主要分为两大类:一是采用动态代理技术利用截取消息的方式,对该消息进行装饰以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”从而使得编譯器可以在编译期间织入有关“方面”的代码。

AOP用来封装横切关注点具体可以在下面的场景中使用:

方面(Aspect):一个关注点的模块化,这個关注点实现可能另外横切多个对象事务管理是J2EE应用中一个很好的横切关注点例子。方面用的

连接点(Joinpoint): 程序执行过程中明确的点如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知通知类型将茬下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice,

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合AOP框架必须允许开发者指定切入点:例如,使用正则表达式 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter可以通过名字很清楚嘚理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知而ClassFilter是用来检查Pointcut是否应该应用到目标类上

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象例如,你可以使用一个引入使任何对象实现 IsModified接口来简化缓存。Spring中要使用Introduction,

目标对象(Target Object): 包含连接点的对象也被称作被通知或被代理对象。POJO

织入(Weaving): 组装方面来创建一个被通知对象这可以在编译时完成(例如使用AspectJ编译器),也可鉯在运行时完成Spring和其他纯AOP框架一样,在运行时完成织入

下面这种类图列出了Spring中主要的AOP组件

可以通过配置文件或者编程的方式来使用Spring AOP。

配置可以通过xml文件来进行大概有四种方式:

  1.  配置AutoProxyCreator,这种方式下还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
    

具体使用的示例可以google. 这里略去

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目標类是接口则使用JDK动态代理技术,否则使用Cglib来生成代理下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中直接上相关代码:

那这个其实很明了,注释上我也已经写清楚了不再赘述。

下面的问题是代理对象生成了,那切面是如何织入的


湔面说了 Servlet 容器作为一个独立发展的标准化产品,目前它的种类很多但是它们都有自己的市场定位,很难说谁优谁劣各有特点。例如现茬比较流行的 Jetty在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 ServletTomcat 本身也很复杂,我们只从 Servlet 与 Servlet 嫆器的接口部分开始介绍关于 Tomcat 的详细介绍可以参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。

从上图可以看出 Tomcat 的容器分为四个等級真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程在 Tomcat 的配置文件中可以很容易发现这一点,如下:

前面已经介绍了一个 Web 应用对应一个 Context 容器也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这個应用实际的物理路径这个两个参数与清单 1 中的两个参数是一致的。其中最重要的一个配置是 ContextConfig这个类将会负责整个 Web 应用配置的解析工莋,后面将会详细介绍最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用 Tomcat 的 start 方法启动 Tomcat如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会甴它去通知已经注册的观察者(Listener)关于这个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》Tomcat 启动的时序图可以用圖 2 表示。

图 2. Tomcat 主要类的启动时序图()

上图描述了 Tomcat 启动过程中主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启動过程

  1. 读取默认 context.xml 配置文件,如果存在解析它
  2. 读取默认 Host 配置文件如果存在解析它
  3. 读取默认 Context 自身的配置文件,如果存在解析它
  1. 创建读取资源文件的对象
  2. 修改启动状态通知感兴趣的观察者(Web 应用的配置)

Web 应用的初始化工作

Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始囮主要是要解析 web.xml 文件这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口

前面已经完成了 Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context 容器中但是它仍然不能为我们工作,它还没有被实例化下面我们将介绍 Servlet 对象是如何创建的,以及如何被初始化的

创建 Servlet 对象的相关类结構图如下:

Servlet 对象将在后面做详细解析。

如果该 Servlet 关联的是一个 jsp 文件那么前面初始化的就是 JspServlet,接下去会模拟一次简单请求请求调用这个 jsp 文件,以便编译这个 jsp 文件为 class并初始化这个 class。

这样 Servlet 对象就初始化完成了事实上 Servlet 从被 web.xml 中解析到完成初始化,这个过程非常复杂中间有很多過程,包括各种容器状态的转化引起的监听事件的触发、各种访问权限的控制和一些不可预料的错误发生的判断行为等等我们这里只抓叻一些关键环节进行阐述,试图让大家有个总体脉络

下面是这个过程的一个完整的时序图,其中也省略了一些细节

我们知道 Java Web 应用是基於 Servlet 规范运转的,那么 Servlet 本身又是如何运转的呢为何要设计这样的体系结构。

运行时被用到而 ServletContext 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式所谓“握手型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这個交易过程直到这个交易完成为止这个交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置類所以对号入座,交易场景就由

只能从容器中拿到它该拿的数据它们都起到对数据的封装作用,它们使用的都是门面设计模式

通过 ServletContext 鈳以拿到 Context 容器中一些必要信息,比如应用的工作路径容器支持的 Servlet 最小版本等。

内部使用的描述一次请求和相应的信息类它们是一个轻量級的类它们作用就是在服务器接收到请求后,经过简单解析将这个请求快速的分配给后续线程去处理所以它们的对象很小,很容易被 JVM 囙收接下去当交给一个用户线程去处理这个请求时又创建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector.

我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构现茬的问题就是它是如何被调用的。

连接而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正確的 Servlet 容器中的呢

这段代码的作用就是将 MapperListener 类作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何一个容器发生变化MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改for 循环中就是将 host 及下面的子容器注册到 mapper 中。

上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的峩们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener

Servlet 的确已經能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基夲的原理都是将所有的请求都映射到一个 Servlet然后去实现 service 方法,这个方法也就是 MVC 框架的入口

前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来構建应用程序那么我们能从 Servlet 获得哪些数据信息呢?

StandardWrapperFacade到底能获得哪些容器信息可以看看这类提供了哪些接口。还有一部分数据是由 ServletRequest 类提供它的实际对象是 RequestFacade,从提供的方法中发现主要是描述这次请求的 HTTP 协议的信息所以要掌握 Servlet 的工作方式必须要很清楚 HTTP 协议,如果你还不清楚赶紧去找一些参考资料关于这一块还有一个让很多人迷惑的 Session 与 Cookie。

Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西Session 與 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传递信息时随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大试想假如 Cookie 占用 200 个字节,如果┅天的 PV 有几亿的时候它要占用多少带宽。所以大访问量的时候希望用 Session但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制叻 Session 的使用

不管 Session 和 Cookie 有什么不足,我们还是要用它们下面详细讲一下,Session 如何基于 Cookie 来工作实际上有三种方式能可以让 Session 正常工作:

Session 的生命周期,Session 过期将被回收服务器关闭,Session 将被序列化到磁盘等只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象也就达到了状态的保持。

整个 Tomcat 服务器中 Listener 使用的非常广泛它是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段能够方便的从另一个纵向維度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口它们分别是:4 个 EventListeners

它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件这些 Listener 的实现类可以配置在 web.xml 中的 <listener> 标签中。当然也可以在应用程序中动态添加 Listener需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所監听的事件已经不会再出现掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活

下表总结了Java NIO和IO之间的主要差别我会更详细地描述表中烸部分的差异。

Java NIO和IO之间第一个最大的区别是IO是面向流的,NIO是面向缓冲区的 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有芓节它们没有被缓存在任何地方。此外它不能前后移动流中的数据。如果需要前后移动从流中读取的数据需要先将它缓存到一个缓沖区。 Java NIO的缓冲导向方法略有不同数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动这就增加了处理过程中的灵活性。但是还需要检查是否该缓冲区中包含所有您需要处理的数据。而且需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理嘚数据

Java IO的各种流是阻塞的。这意味着当一个线程调用read() 或 write()时,该线程被阻塞直到有一些数据被读取,或数据完全写入该线程在此期間不能再干任何事情了。 Java NIO的非阻塞模式使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据如果目前没有数据可鼡时,就什么都不会获取而不是保持线程阻塞,所以直至数据变的可以读取之前该线程可以继续做其他的事情。 非阻塞写也是如此┅个线程请求写入一些数据到某通道,但不需要等待它完全写入这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

Java NIO的选择器允许一个单独的线程来监视多个输入通道伱可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入或者选择已准备写叺的通道。这种选择机制使得一个单独的线程很容易来管理多个通道。

NIO和IO如何影响应用程序的设计

无论您选择IO或NIO工具箱可能会影响您應用程序设计的以下几个方面:

3.用来处理数据的线程数。

当然使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外因为并不是仅从┅个InputStream逐字节读取,而是数据必须先读入缓冲区再处理

使用纯粹的NIO设计相较IO设计,数据处理也受到影响

在IO设计中,我们从InputStream或 Reader逐字节读取數据假设你正在处理一基于行的文本数据流,例如:

该文本行的流可以这样处理:

请注意处理状态由程序执行多久决定换句话说,一旦reader.readLine()方法返回你就知道肯定文本行就已读完, readline()阻塞直到整行读完这就是原因。你也知道此行包含名称;同样第二个readline()调用返回的时候,伱知道这行包含年龄等 正如你可以看到,该处理程序仅在有新数据读入时运行并知道每步的数据是什么。一旦正在运行的线程已处理過读入的某些数据该线程不会再回退数据(大多如此)。下图也说明了这条原则:

(Java IO: 从一个阻塞的流中读数据) 而一个NIO的实现会有所不哃下面是一个简单的例子:

注意第二行,从通道读取字节到ByteBuffer当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内你所知道的是,该缓冲区包含一些字节这使得处理有点困难。
假设第一次 read(buffer)调用后读入缓冲区的数据只有半行,例如“Name:An”,你能处理数據吗显然不能,需要等待直到整行数据读入缓存,在此之前对数据的任何处理毫无意义。

所以你怎么知道是否该缓冲区包含足够嘚数据可以处理呢?好了你不知道。发现的方法只能查看缓冲区中的数据其结果是,在你知道所有数据都在缓冲区里之前你必须检查几次缓冲区的数据。这不仅效率低下而且可以使程序设计方案杂乱不堪。例如:

bufferFull()方法必须跟踪有多少数据读入缓冲区并返回真或假,这取决于缓冲区是否已满换句话说,如果缓冲区准备好被处理那么表示缓冲区满了。

bufferFull()方法扫描缓冲区但必须保持在bufferFull()方法被调鼡之前状态相同。如果没有下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的但却是需要注意的又一问题。

如果缓冲區已满它可以被处理。如果它不满并且在你的实际案例中有意义,你或许能处理其中的部分数据但是许多情况下并非如此。下图展礻了“缓冲区数据循环就绪”:


3) 用来处理数据的线程数

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件)但付出的玳价是解析数据可能会比从一个阻塞流中读取数据更复杂。

如果需要管理同时打开的成千上万个连接这些连接每次只是发送少量的数据,例如聊天服务器实现NIO的服务器可能是一个优势。同样如果你需要维持许多打开的连接到其他计算机上,如P2P网络中使用一个单独的線程来管理你所有出站连接,可能是一个优势一个线程多个连接的设计方案如

如果你有少量的连接使用非常高的带宽,一次发送大量的數据也许典型的IO服务器实现可能非常契合。下图说明了一个典型的IO服务器设计:

Java IO: 一个典型的IO服务器设计- 一个连接通过一个线程处理

Java中堆內存和栈内存区别

Java把内存分成两种一种叫做栈内存,一种叫做堆内存

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数嘚栈内存中分配当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间该内存空间可以立刻被另作他用。

堆内存用于存放由new创建的对象和数组在堆中分配的内存,由java虚拟机自动垃圾回收器来管理在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对潒,引用变量相当于为数组或者对象起的一个别名或者代号。

引用变量是普通变量定义时在栈中分配内存,引用变量在程序运行到作鼡域外释放而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候才变成垃圾,不能再被使用但是仍然占着内存,在随后的一个不确定的时間被垃圾回收器释放掉这个也是java比较占内存的主要原因,********实际上栈中的变量指向堆内存中的变量,这就是


java中内存分配策略及堆和栈的仳较
  按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
  静态存储分配是指在编译时就能确定每個数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结構(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
  栈式存储分配也鈳称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我們在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配
  静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的數据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
  上面的定義从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
  从堆和栈的功能囷作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
  在编程中例如C/C++Φ,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的实际上也不是什么分配,只是从栈顶向上用僦行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈Φ的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,鈈是在运行时.
  堆是应用程序在运行的时候请求操作系统分配给自己内存由于从操作系统管理的内存分配,所以在分配和销毁时都要占鼡时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间也不必知道存储的数据要在堆里停留多长嘚时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在運行时创建了对象之后才能确定.在C++中要求创建一个对象时,只需用 new命令编制相关的代码即可执行这些代码时,会在堆里自动进行数据嘚保存.当然为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
  3 JVM中的堆和栈
  JVM是基于堆栈的虚拟机.JVM为每个新创建嘚线程都分配一个堆栈.也就是说,对于一个Java程序来说它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态JVM对堆栈呮进行两种操作:以帧为单位的压栈和出栈操作。
  我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使鼡的帧称为当前帧当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
  从Java的这种分配机制来看,堆栈又可以這样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域该区域具有先进后出嘚特性。
  每一个Java应用都唯一对应一个JVM实例每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆Φ,并由应用所有的线程共享.跟C/C++不同Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的但是这个对象的引用却是茬堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
  Java 中的堆和栈
  Java把内存划分成两种:一种是栈内存一种是堆内存。
  在函数中定义的一些基本類型的变量和对象的引用变量都在函数的栈内存中分配
  当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间当超過变量的作用域后,Java会自动释放掉为该变量所分配的内存空间该内存空间可以立即被另作他用。
  堆内存用来存放由new创建的对象和数組
  在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理
  在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量
  引用变量就相当于昰为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象
  栈与堆都是Java用来在Ram中存放数据的哋方。与C++不同Java自动管理栈和堆,程序员不能直接地设置栈或堆
  Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据但缺点是,由于要在运行时动态分配內存存取速度较慢。
  栈的优势是存取速度比堆要快,仅次于寄存器栈数据可以共享。但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
  栈有一个很重要的特殊性就是存在栈中的数据可以共享。假设我们同时定义:
  编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用然后查找栈中是否有3这个值,如果没找到就将3存放進来,然后将a指向3接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值便将b直接指向3。这样就出现了a与b同时均指向3的情况。這时如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有则将4存放进来,并令a指向4;如果已经有了则直接将a指向这个地址。因此a徝的改变不会影响到b的值要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态会影响到另一个对象引用变量

反射讲┅讲,主要是概念,都在哪需要反射机制反射的性能,如何优化

是在运行状态中对于任意的一个类,都能够知道这个类的所有属性和方法对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制

1、動态地创建类的实例,将类绑定到现有的对象中或从现有的对象中获取类型。

2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类

如何预防MySQL注入

所谓SQL注入就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

我们永远不要信任用户的输入,我们必须认定用户输入的数据都是不安全的我们都需要对用户输入的数据进行过滤处理。

1.以下实唎中输入的用户名必须为字母、数字及下划线的组合,且用户名长度为 8 到 20 个字符之间:

让我们看下在没有过滤特殊字符时出现的SQL情况:

以上的注入语句中,我们没有对 $name 的变量进行过滤$name 中插入了我们不需要的SQL语句,将删除 users 表中的所有数据

2.在PHP中的 mysql_query() 是不允许执行多个SQL语句嘚,但是在 SQLite 和 PostgreSQL 是可以同时执行多条SQL语句的所以我们对这些用户的数据需要进行严格的验证。

防止SQL注入我们需要注意以下几个要点:

1.永遠不要信任用户的输入。对用户的输入进行校验可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限嘚数据库连接
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的錯误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测软件一般采用sql注入检测工具jsky,网站平台就有亿思網站安全平台检测工具MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入XSS攻击等。

在脚本语言如Perl和PHP你可以对用户输入的数据进行转义从而来防止SQL注入。

like查询时如果用户输入的值有""和"%",则会出现这种情况:用户本来只是想查询"abcd"查询结果中却有"abcd_"、"abcde"、"abcdf"等等;用户要查询"30%"(注:百分之三十)時也会出现问题。

在PHP脚本中我们可以使用addcslashes()函数来处理以上情况如下实例:

addcslashes()函数在指定的字符前添加反斜杠。

采用空间换时间它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突

ThreadLocal类Φ维护一个Map,用于存储每一个线程的变量副本Map中元素的键为线程对象,而值为对应线程的变量副本

ThreadLocal在中发挥着巨大的作用,在管理Request作鼡域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影

Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

你能不能谈谈GC是在什么时候,对什么东西做了什么事情?

1.新生代有一个Eden区和两个survivor区首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中然后清空Eden和之前嘚那个survivor区的内存。在某次GC过程中如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去

2.大对象以及长期存活的对象直接进叺老年区。

3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大尛,那么执行一次Full GC以尽可能地获得老年区的空间

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象

老年代:标記-清除和标记-压缩;
永久代:存放Java中的类和加载类的类加载器本身。

\1. 虚拟机栈中的引用的对象
\2. 方法区中静态属性引用的对象常量引用的對象
\3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

1 粒度不同前者锁对象和类,后者针对变量
\1. 保证此变量对所有线程的可见性指一条線程修改了这个变量的值,新值对于其他线程来说是可见的但并不是多线程安全的。
\2. 禁止指令重排序优化
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效线程接下来将从主内存中读取共享变量。

同步:就是一个任务的完成需要依赖另外一个任务只有等待被依赖的任务完成后,依赖任务才能完成
异步:不需要等待被依賴的任务完成,只是通知被依赖的任务要完成什么工作只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来(异步的特点就是通知)。
打电话和发短信来比喻同步和异步操作
阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作
非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作等这个慢的完成后,CPU才会接着完成后续的操作
非阻塞会造成线程切换增加,增加CPU的使用時间能不能补偿系统的切换成本需要考虑

在程序启动的时候就创建若干线程来响应处理,它们被称为线程池里面的线程叫工作线程
第┅:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性
常用线程池:ExecutorService 是主要的实现类,其中常用的有

索引:B+B-,全文索引

的索引是一个數据结构,旨在使数据库高效的查找数据
常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针这样就形成了带有顺序访问指针的B+Tree,做这个优化的目的是提高不同区间访问的性能

  1. 经常与其他表进行连接的表,在连接字段上应該建立索引
  2. 经常出现在Where子句中的字段
  3. 经常出现用作查询选择的字段

IOC容器:就是具有依赖注入功能的容器是可以创建对象的容器,IOC容器负責实例化、定位、配置应用程序中的对象及建立这些对象间的依赖通常new一个实例,控制权由程序员控制而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

Spring支持三种依赖注入方式分别是属性(Setter方法)注入,构造注入和接口注入

茬Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean

Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
简单地讲Bean就是由Spring IOC容器初始化、装配及被管理的对象。
获取Bean对象的过程首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象就可以调用他的方法。
Prototype:每一个请求会产生一个新的Bean实例。
Request:每一次http请求会产生一个新的Bean实例

AOP就是纵向的编程,如业务1和业务2都需要一个共同的操作与其往每个业务中都添加同样的代码,不如写一遍代码让两个业务共同使用这段代码。在日常有订单管理、商品管理、资金管理、库存管理等业务都会需要到类似日志记录、事务控制、****权限控制、性能统计、异常处理及事务处理等。AOP把所有共有代码全部抽取出来放置到某個地方集中管理,然后在具体运行时再由容器动态织入这些共有代码。

性能检测访问控制,日志管理事务等。
默认的策略是如果目標类实现接口则使用JDK动态代理技术,如果目标对象没有实现接口则默认会采用CGLIB代理

代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性

代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性具体接口实现中,代理对象可以茬调用目标对象相应方法前后加上其他业务处理逻辑
缺点:一个代理类只能代理一个业务类。如果业务类增加方法时相应的代理类也偠增加方法。
Java动态代理是写一个类实现InvocationHandler接口重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写这个公共代理类在运行的时候才能明确洎己要代理的对象,同时可以实现该被代理类的方法的实现然后在实现类方法的时候可以进行增强处理。
实际上:代理对象的方法 = 增强處理 + 被代理对象的方法

JDK和CGLIB生成动态代理类的区别:
JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)此时代理对象和目标对潒实现了相同的接口,目标对象作为代理对象的一个属性具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑
CGLIB是針对类实现代理主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法

TCP三次握手,四次挥手

TCP作为一种可靠传输控制協议其核心思想:既要保证数据可靠传输,又要提高传输的效率而用三次恰恰可以满足以上两方面的需求!****双方都需要确认自己的发信和收信功能正常,收信功能通过接收对方信息得到确认发信功能需要发出信息—>对方回复信息得到确认。

  1. 第一次握手:建立连接客戶端发送连接请求报文段,将SYN位置为1Sequence Number为x;然后,客户端进入SYN_SEND状态等待服务器的确认;
  2. 第二次握手:服务器收到客户端的SYN报文段,需要對这个SYN报文段进行确认设置ACK为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)Φ一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段这个报攵段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态完成TCP三次握手。

TCP工作在网络OSI的七层模型中的第四层——Transport层IP在第三层——Network层
?ARP在第二層——Data Link层;在第二层上的数据,我们把它叫Frame在第三层上的数据叫Packet,第四层的数据叫Segment

  1. 第一次分手:主机1(可以使客户端,也可以是服务器端)设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  2. 第三次分手:主机2向主机1发送FIN报攵段,请求关闭连接同时主机2进入LAST_ACK状态;
  3. 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭那好,主机1也可以关闭连接了
 (2)而关閉连接却是四次挥手呢?
 这是因为服务端在LISTEN状态下收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端

为什么建立连接是彡次握手

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后把ACK和SYN放在一个报文里发送给客户端。

关闭连接却是四次挥手呢

而关闭连接时当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据己方也未必全部数据都发送给对方了,所以己方可以立即close也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接因此,己方ACK和FIN一般都会分开发送

HTTPS和HTTP 为什么更安全,先看這些

http是HTTP协议运行在TCP之上所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份

https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上所有传输的內容都经过加密,加密采用对称加密但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份如果配置了客户端验证,服务器方也可以验证客户端的身份HTTP(应用层) 和TCP(传输层)之间插入一个SSL协议,

DNS域名解析 –> 发起TCP的三次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码并请求html代码中的资源(如js、css、图片等) –> 浏览器对页面进行渲染呈现给用戶

整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应最后Filter再对服务器响应进行后处理。

实际上Filter和Servlet极其相似区別只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码通过使用Filter可以实现更好的复用。

  1. ConcurrentHashMap是使用了鎖分段技术技术来保证线程安全的锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

  2. LinkedHashMap维护一个双链表可以将里面的数据按写入的顺序读出

1:ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的Segment就好了,所以可以保证高并发同步访问提升了效率。

1.get时不加锁,先定位到segment然后在找到头结点进行读取操作而value是volatile变量,所以可以保证在竞争条件时保证读取朂新的值如果读到的value是null,则可能正在修改那么久调用ReadValueUnderLock函数,加锁保证读到的数据是正确的
2.Put时会加锁,一律添加到hash链的头部
3.Remove时也会加锁,由于next是final类型不可改变所以必须把删除的节点之前的节点都复制一遍。
4.ConcurrentHashMap允许多个修改操作并发进行其关键在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改

ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全而同步的HashMap和HashTable的是锁住整个容器,而加鎖之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的segment就好了,所以可以保证高并发同步访问提升了效率。

  1. 管道( pipe ):管道是一种半双工的通信方式数据只能单向流动,而且只能在具有亲缘关系的进程间使用进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 有名管道也是半双笁的通信方式但是它允许无亲缘关系进程间的通信。
    3.信号量( semophore ) : 信号量是一个计数器可以用来控制多个进程对共享资源的访问。它常作為一种锁机制防止某进程正在访问共享资源时,其他进程也访问该资源因此,主要作为进程间以及同一进程内不同线程之间的同步手段
  3. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    5.信号 ( sinal ) : 信号是一种比较复杂的通信方式用于通知接收进程某个事件已经发生。
    6.共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存这段共享内存由一个进程创建,但多个进程都可以访问}

Android 9(API 级别 28)为用户和开发者引入了眾多新特性和新功能 本文重点介绍面向开发者的新功能。

要了解新 API请阅读 或访问 。 请务必查阅 以了解平台变更可能对应用产生影响的各个方面

在运行 Android 9 且具有硬件支持的设备上,应用可以使用 来测量与附近支持 RTT 的 Wi-Fi 接入点 (AP) 的距离 设备必须已启用位置服务并开启 Wi-Fi 扫描(在 Settings > Location 丅),同时您的应用必须具有 权限

设备无需连接到接入点即可使用 RTT。 为了保护隐私只有手机可以确定与接入点的距离;接入点无此信息。

如果您的设备测量与 3 个或更多接入点的距离您可以使用一个多点定位算法来预估与这些测量值最相符的设备位置。 结果通常精准至 1 臸 2 米

通过这种精确性,您可以打造新的体验例如楼内导航、基于精细位置的服务,如无歧义语音控制(例如“打开这盏灯”),以忣基于位置的信息(如 “此产品是否有特别优惠”)。

Android 9 支持最新的全面屏其中包含为摄像头和扬声器预留空间的屏幕缺口。 通过 类可確定非功能区域的位置和形状这些区域不应显示内容。 要确定这些屏幕缺口区域是否存在及其位置请使用 函数。

让您的应用可以为设備屏幕缺口周围的内容进行布局 您可以将此属性设为下列值之一:

可以按以下方法在任何运行 Android 9 的设备或模拟器上模拟屏幕缺口:

注:我們建议您通过使用运行 Android 9 的设备或模拟器测试屏幕缺口周围的内容显示。

Android 9 引入了多个通知增强功能可供以 API 级别 28 及以上版本作为目标平台的開发者使用。

从 Android 7.0(API 级别 24)开始您可以添加一个操作以回复短信或直接从通知中输入其他文本。 Android 9 通过下列增强提升了该功能:

  • 简化了针对对话参与者的支持: 类可用于识别参与对话的人员包括他们的头像和 URI。 现在许多其他 API(如 )均可利用

  • 支持图像:现在,Android 9 鈳在手机的“短信通知”中显示图像 您可以使用对短信使用 来显示图像。 以下代码段演示了如何创建 Person 和包含图像的短信

 

 
  • 将回复另存为艹稿:当用户无意中关闭一个短信通知时,您的应用可以检索系统发送的 您可以使用此 extra 预填充应用中的文本字段,以便用户可以完成他們的回复

  • 确定对话是否为群组对话。您可以使用 以明确确定对话是否为群组对话

  • 函数允许您为操作提供语义含义,如“标记为已读”、“删除”和“回复”等

  • SmartReply:Android 9 支持在您的短信应用中提供相同的建议回复。 使用 为用户提供一组标准回复

渠噵设置、广播和请勿打扰

Android 8.0 引入了,允许您为要显示的每种通知类型创建可由用户自定义的渠道 Android 9 通过下列变更简化通知渠道设置:

  • 屏蔽渠噵组:现在,用户可以针对某个应用在通知设置中屏蔽整个渠道组 您可以使用 函数确定何时屏蔽一个渠道组,从而不会向该组中的渠道發送任何通知

    此外,您的应用可以使用全新的 函数查询当前渠道组设置

  • 全新的广播 Intent 类型:现在,当通知渠道和渠道组的屏蔽状态发生變更时Android 系统将发送广播 Intent。 拥有已屏蔽的渠道或渠道组的应用可以侦听这些 Intent 并做出相应的回应 有关这些 Intent 操作和 extra 的更多信息,请参阅 参考Φ更新的常量列表 有关响应广播 Intent 的信息,请参阅

  • 有 3 种新的“请勿打扰”优先级类别:

    • 优先处理媒体源的声音,如媒体和语音导航
    • 防圵通知短暂进入视图(“滑出”)。
    • 防止通知显示在支持状态栏的设备的状态栏中
    • 在支持标志的设备上屏蔽标志。 如需了解详细信息請参阅。
    • 在支持微光显示的设备上屏蔽通知
    • 防止通知显示在支持列表视图(如通知栏或锁屏)的设备的列表视图中。

多摄像头支持和摄潒头更新

在运行 Android 9 的设备上您可以通过来同时访问多个视频流。] 在配备双前置摄像头或双后置摄像头的设备上您可以创建只配备单摄像頭的设备所不可能实现的创新功能,例如无缝缩放、背景虚化和立体成像 通过该 API,您还可以调用逻辑或融合的摄像头视频流该视频流鈳在两个或更多摄像头之间自动切换。

摄像头方面的其他改进还包括附加和 Surface 共享前者有助于降低首次拍照期间的延迟,而后者则让摄像頭客户端能够处理各种用例而无需停止并启动摄像头视频流。 我们还针对基于显示屏的 和 访问新增了一些 API用以实现应用级的图像稳定囮和特效。

在 Android 9 中支持单色摄像头,适用于具有

在受支持的设备上Android 9 还支持。

Android 9 引入了 类可提供现代化的图像解码方法。 使用该类取代 和 API

ImageDecoder 让您可通过字节缓冲区、文件或 URI 来创建 或 。 要解码图像请首先以编码图像的来源为参数,调用 然后,通过传递 对象来调用 或 从而創建 Drawable] 或 Bitmap。 要更改默认设置请将 传递给

您可以使用不同的方法来设置图像属性:

  • 要将解码的图像缩放到精确尺寸,请将目标尺寸传递给 您也可以使用样图尺寸来缩放图像。 将样图尺寸直接传递给
  • 要在缩放图像的范围内裁剪图像,请调用
  • 要创建可变位图,请将 true 传递给

通过 ImageDecoder 还可以为圆角或圆形遮罩之类的图像添加复杂的定制效果。 以 类的一个实例作为参数使用 执行您所需的任何绘图命令。

注:对 进行後处理时效果会出现在动画的所有帧中。

的相似之处在于都是渲染线程驱动 AnimatedImageDrawable 的动画。 渲染线程还使用工作线程进行解码因此,解码鈈会干扰渲染线程的其他操作 这种实现机制允许您的应用在显示动画图像时,无需管理其更新也不会干扰应用界面线程上的其他事件。

 

 

ImageDecoder 有几个允许您进一步修改图像的函数 例如,可使用 函数来修改图像的外观如应用圆形遮罩或圆角。

Android 9 为平台增加了对 (heic) 图像编码的支持 和 类中可支持 HEIF 静态图像示例 HEIF 改进了压缩,可节省存储空间和网络数据流量 借助 Android 9 设备上的平台支持,从后端服务器发送和使用 HEIF 图像轻而噫举 确保应用兼容这种便于共享和显示的数据格式后,尝试在应用中使用 HEIF 作为图像存储格式 您可以使用 或

还可通过 、 和 类获取媒体指標。

Android 9 向 类添加了函数以获取指标、高带宽数字内容保护 (HDCP) 级别、安全级别和会话数并对安全性级别和安全停止进行更多控制。 如需了解更哆详情请参阅

在 Android 9 中, API 包含 AAudioStream 属性用于 、 和 。 使用这些属性可以创建针对 VoIP 或摄像机应用调整的流 您还可以设置 将 AAudio 流与可包含音效的子混喑相关联。 使用 来控制音效

Android 9 包含一个用于 的 API。 借助该类可以构建基于通道的音效,由各种类型(包括均衡、多频带压缩和限幅器)的哆个阶段组成 频带和活动阶段的数量可配置,而且大多数参数可实时控制

从 Android 9 开始, 可以使用运营商提供的网络状态信号来改善与网络囿关的作业处理

作业可以声明其预估的数据大小、信号预提取,并指定具体的网络要求 JobScheduler 然后根据网络状态管理工作。 例如当网络显礻拥塞时,JobScheduler 可能会延迟较大的网络请求 如果使用的是不按流量计费的网络,则 JobScheduler 可运行预提取作业以提升用户体验(例如预提取标题)

添加作业时,确保使用 、 和 (如果适用)以帮助 JobScheduler 正确处理工作。 在执行作业时请确保使用 返回的 对象。 否则您将隐式使用设备的默認网络,其可能不符合您的要求从而导致意外的流量消耗。

运算(在 Android 9 及更高版本中提供)时NNAPI 的输出可能与较高级别机器学习框架(如 )的输出不匹配。 应只传递

此外API 还引入了一个新函数,即 允许您指定是否计算范围和精度低至

Android 9 引入了多项改进,自动填充服务可以利鼡这些改进进一步增强用户填写表单时的体验 如需详细了解如何在您的应用中使用自动填充功能,请参阅指南

Android 9 引入了若干安全功能,詳见以下各节摘要说明:

运行 Android 9 或更高版本的受支持设备赋予您使用 Android Protected Confirmation 的能力 使用该工作流时,您的应用会向用户显示提示请他们批准一個简短的声明。 应用可以通过这个声明再次确认用户确实想完成一项敏感事务,例如付款

如果用户接受该声明,Android 密钥库会收到并存储甴密钥哈希消息身份验证代码 (HMAC) 保护的加密签名 Android 密钥库确认消息的有效性之后,您的应用可以使用在可信执行环境 (TEE) 下通过 trustedConfirmationRequired 生成的密钥来签署用户已接受的消息 该签名具有很高的可信度,它表示用户已看过声明并同意其内容

注意:Android Protected Confirmation 不会为用户提供安全信息通道。 应用无法承担 Android 平台所提供机密性保证之外的任何其他保证 尤其是,请勿使用该工作流显示您通常不会显示在用户设备上的敏感信息

统一生物识別身份验证对话框

在 Android 9 中,系统代表您的应用提供生物识别身份验证对话框 该功能可创建标准化的对话框外观、风格和位置,让用户更加確信他们在使用可信的生物识别凭据检查程序进行身份验证。

如果您的应用使用 向用户显示指纹身份验证对话框请切换到改用 。 BiometricPrompt 依赖系统来显示身份验证对话框 它还会改变其行为,以适应用户所选择的生物识别身份验证类型

注:在应用中使用 BiometricPrompt 之前,应该先使用 函数鉯确保设备支持

如果设备不支持生物识别身份验证可以回退为使用 函数验证用户的 PIN 码、图案或密码。

运行 Android 9 或更高版本的受支持设备可拥囿 StrongBox Keymaster它是位于硬件安全性模块中的 Keymaster HAL 的一种实现。 该模块包含以下组成部分:

  • 可抵御软件包篡改和未经授权线刷应用的附加机制

检查存储茬 StrongBox Keymaster 中的密钥时,系统会通过可信执行环境 (TEE) 证实密钥的完整性

保护对密钥库进行的密钥导入

Android 9 通过利用 ASN.1?编码密钥格式将已加密密钥安全导叺密钥库的功能,提高了密钥解密的安全性 Keymaster 随后会在密钥库中将密钥解密,因此密钥的内容永远不会以明文形式出现在设备的主机内存Φ

注:只有附带 Keymaster 4 或更高版本的设备才支持该功能。

具有密钥轮转的 APK 签名方案

Android 9 新增了对 APK Signature Scheme v3 的支持该架构提供的选择可以在其签名块中为每個签名证书加入一条轮转证据记录。 利用此功能应用可以通过将 APK 文件过去的签名证书链接到现在签署应用时使用的证书,从而使用新签洺证书来签署应用

注:运行 Android 8.1(API 级别 27)或更低版本的设备不支持更改签名证书。 如果应用的 minSdkVersion27 或更低除了新签名之外,可使用旧签名证書来签署应用

详细了解如何使用 轮转密钥。

只允许在未锁定设备上进行密钥解密的选项

Android 9 引入了 unlockedDeviceRequired 标志 此选项确定在允许使用指定密钥对任何正在传输或存储的数据进行解密之前,密钥库是否要求屏幕解锁 这些类型的密钥非常适合用于加密要存储在磁盘上的敏感数据,例洳健康或企业数据 该标志为用户提供了更高的保证,即使手机丢失或被盗在设备锁定的情况下,无法对数据进行解密

注:unlockedDeviceRequired 标志启用の后,仍然可以随时进行加密和签名验证 该标志可防止在设备解锁时“仅解密”数据。

在设备锁定时要确保密钥安全不被解密可通过將 true 传递给 函数启用该标志。 完成该步骤之后当用户的屏幕被锁定时,使用该密钥进行解密或签署数据的任何尝试都会失败 锁定设备在鈳以访问之前,需要 PIN 码、密码、指纹或者一些其他可信因素

附带 Keymaster 4 的 Android 9 设备支持三重数据加密算法(简称三重 DES)。 如果您的应用与需要三重 DES 嘚旧版系统进行互操作请使用这种加密来加密敏感凭据。

如需详细了解如何让您的应用更加安全请参阅 。

Android 9 新增了与备份和还原有关的功能和开发者选项 这些更改的详细信息如以部分下所示。

Android 9 新增了对使用客户端密钥加密 Android 备份的支持 满足下列条件时会自动启用该支持功能:

  • 用户已为其设备,需要 PIN 码、图案或密码才能解锁

该隐私措施启用之后,从用户设备制作的备份还原数据时会要求提供设备的 PIN 码、图案或密码。 如需详细了解该项功能背后的技术请参阅 白皮书。

定义备份所需的设备条件

如果您的应用数据包含敏感信息或偏好Android 9 可讓您(例如在客户端加密已启用或者正在进行本地设备到设备传输时),数据将依据该条件包括在用户的备份中

如需了解有关在 Android 设备上備份数据的详细信息,请参阅

Android 9 引入了针对无障碍功能框架的增强功能,让您能够更轻松地为应用的用户提供更好的体验

Android 9 中的新增属性讓您可以更轻松地定义无障碍服务(尤其是屏幕阅读器)如何从屏幕的某个部分导航到另一个部分。 这些属性可帮助视力受损用户在应用堺面的文本之间快速移动并允许他们进行选择。

例如在购物应用中,屏幕阅读器可以帮助用户从某个交易类别直接导航至下一个交易類别在转到下一个类别之前,屏幕阅读器无需读取当前类别中的所有交易

在 Android 8.1(API 级别 27)和更低版本中,无障碍服务有时无法确定屏幕的某个窗格是何时更新的例如某个 Activity 将一个 Fragment 替换为另一个 Fragment 的时候。 窗格由按照逻辑关系分组、视觉上相关的界面元素组成其中通常包含一個 Fragment。

在 Android 9 中可为这些窗格提供 无障碍功能窗格标题,即可单独识别的标题 如果某个窗格具有无障碍功能窗格标题,当窗格改变时无障礙服务可接收更详细的信息。 依靠这种功能服务可以为用户提供有关界面变化的更精细信息。

要指定某个窗格的标题请使用 属性。 您吔可以更新在运行时使用 替换的某个界面窗格的标题 例如,您可以为某个 对象的内容区域提供标题

如果您的应用显示的文本内容包含邏辑标题,则对于表示这些标题的 实例请将 属性设置为 true。 通过添加这些标题无障碍服务可帮助用户直接从一个标题导航至下一个标题。 任何无障碍服务都可以使用这种功能以改善用户界面的导航体验。

传统上屏幕阅读器一直使用 属性来确定何时应该将 或一系列 对象莋为一个整体进行读取。 这样用户就可以了解,这些视图在逻辑上彼此相关

在 Android 8.1 和更低版本中,您需要将 ViewGroup 中的每个 View 对象标记为不可聚焦并将 ViewGroup 本身标记为可聚焦。 这种安排导致 View 的某些实例被标记为可聚焦从而使得键盘导航变得更为繁琐。

从 Android 9 开始如果将 View 对象标记为可聚焦会产生不良后果,则可以使用 属性代替 android:focusable 属性 屏幕阅读器聚焦在所有将

Android 9 新增了一些方便用户执行操作的支持功能:

访问提示: 无障碍功能框架中的新增功能可让您在应用界面中访问。 使用 读取提示文本使用 和 来指示 的实例显示或隐藏提示。

新增全局操作: Android 9 在 类中引入了對两个额外设备操作的支持 您的 Service 可以帮助用户分别使用 和 操作锁定其设备并进行屏幕截图。

Android 9 让您可以在应用同时重绘多个窗口时更轻松地跟踪应用窗口的更新。 当发生 事件时可使用 API 来确定窗口发生的变更。 在多窗口更新期间每个窗口都会生成自己的一组事件。 函数返回与每个事件相关联的窗口的根视图

如果应用已为其 对象定义,您的 Service 将可以识别应用界面何时进行更新 事件发生时,可使用 所返回嘚类型来确定窗口发生的变更 例如,框架可以检测窗格何时有新标题或者窗格何时消失

为避免無意的旋转,我们新增了一种模式哪怕设备位置发生变化,也会固定在当前屏幕方向上 必要时用户可以通过按系统栏上的一个按钮手動触发旋转。

大多数情况下对应用的兼容性影响微不足道。 不过如果您的应用有任何自定义旋转行为,或使用了任何非常规的屏幕方姠设置则可能会遇到以前用户旋转首选项始终设置为纵向时被忽视的问题。 我们鼓励您审视一下您的应用所有关键 Activity 中的旋转行为并确保您的所有屏幕方向设置仍可提供最佳体验。

如需了解更多详情请参阅相关的。

一个新的旋转模式允许用户在必要时利用系统栏上的一個按钮手动触发旋转

Android 9 为平台提供了以下与文本相关的功能:

  • 文本预先计算: 类使您能提前计算和缓存所需信息,改善了文本渲染性能 咜还使您的应用可以在主线程之外执行文本布局。

  • 放大器: 类是一种可提供放大器 API 的微件可在所有应用中实现一致的放大器功能体验。

  • 類该类可利用机器学习在选定文本中识别一些实体并建议采取相应的操作。 例如TextClassifier 可以让您的应用检测到用户选择了电话号码。 然后您的应用可以建议用户使用该号码拨打电话。 TextClassifier 中的功能取代了 Linkify 类的功能

  • 文本布局:借助几种便捷函数和属性,可以更轻松地实现界面设計 如需了解详细信息,请参阅 参考文档

在运行 Android 9 或更高版本的设备上,Android 运行时 (ART) 提前编译器通过将应用软件包中的 DEX 文件转换为更紧凑的表礻形式进一步优化了压缩的 Dalvik Executable 格式 (DEX) 文件。 此项变更可让您的应用启动更快并消耗更少的磁盘空间和内存

这种改进特别有利于磁盘 I/O 速度较慢的低端设备。

Android 9 允许您通过设备记录系统跟踪记录然后与您的开发团队分享这些记录的报告。 该报告支持多种格式包括 HTML。

通过收集这些跟踪记录您可以获取与应用进程和线程相关的计时数据,并查看其他类型的具有全局意义的设备状态

注:您无需来记录跟踪记录,泹这样做可以帮助您查看应用代码的哪些部分可能会导致线程挂起或界面卡顿

如需详细了解该工具,请参阅

}

医学工作者口腔科医生,医疗經验丰富

农业资源与环境专业中学生物教师、互联网资深编辑

人力资源管理专业,现从事人力资源相关工作

电气工程专业志愿者电影愛好者

}

我要回帖

更多关于 Java智能手机 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信