Android面试终篇- Java基础和网络

394 阅读27分钟

update time 2021年7月14日11点57分 ,阅读时间30分钟, 文章版本:V 1.5。主要收集在面试过程中普遍问到的基础知识(面试收集 主要来自于bilibili 嵩恒 蚂蚁金服等互联网公司) 由于总结的东西很多很乱,所以知识点并没有深入探讨,很多小标题的东西都可以写成一篇单独的总结,这里偷懒直接放在一起汇总了。

中级Android面试总结之Android篇 链接

参考文章

网络

网络模块 面试主要涉及到 应用层(HTTP DNS等) 和 传输层 (TCP UDP)的东西,

网络基础

网络协议的体系结构

物理层 数据链路层:逻辑链路控制LLC、媒体接入控制MAC 网络层:IP协议、地址解析协议ARP、逆地址解析协议RARP、因特网控制报文协议ICMP 传输层:传输控制协议TCP、用户数据报协议UDP 应用层:文件传输协议FTP、远程登录协议TELNET、超文本传输协议HTTP、域名系统DNS、简单邮件协议SMTP、简单网络管理协议SNMP

11.webp

拥塞控制和流量控制

  • 拥塞控制:对网络中的路由和链路传输进行速度限制,避免网络过载;包含四个过程:慢启动、拥塞避免、快重传和快恢复
  • 流量控制 :对点和点/发送方和接收方之间进行速度匹配,由于接收方的应用程序读取速度不一定很迅速,加上缓存有限,因此需要避免发送速度过快;相关技术:TCP滑动窗口、回退N针协议

TCP UDP

TCP 连接 :传输可靠;有序;面向字节流;速度慢;较重量;全双工; 适用于文件传输、浏览器等

全双工:A 给 B 发消息的同时,B 也能给 A 发 半双工:A 给 B 发消息的同时,B 不能给 A 发

UDP 无连接 :传输不可靠;无序;面向报文;速度快;轻量; 适用于即时通讯、视频通话等

三次握手四次挥手

参考文章

  • 握手:
  1. 建立连接,客户端发送syn包(syn=x)到服务器,客户端进入SYN_SENT状态,等待服务器确认;
  2. 服务器收到syn包,首先确认客户的SYN(ack=x+1),自己向客户端发送一个SYN包(syn=y),此时服务器进入SYN_RECV状态;
  3. 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),发送完毕后,客户端和服务器双方进入ESTABLISHED(TCP连接成功)状态,握手完成!

SYN:同步序列编号(Synchronize Sequence Numbers)。

2.webp

  • 挥手:
  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。所以服务器结束TCP连接的时间要比客户端早一些。

访问URL 过程

在浏览器地址栏键入URL后:

  1. 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址
  2. 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
  3. 浏览器发出读取文件的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
  4. 服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器
  5. 释放TCP连接,若connection模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
  6. 客户端将服务器响应的html文本解析并显示

Http 缓存

  1. Cache-Control : 在 HTTP 响应头中,用于指示代理和 UA 使用何种缓存策略(no-cache 为本次响应不可直接用于后续请求 、no-store 为禁止缓存、private为仅 UA 可缓存、public都可以缓存)
  2. Etag : Etag 响应头字段表示资源的版本,浏览器在发送请求时会带 If-None-Match 头字段, 来询问服务器该版本是否仍然可用
  3. Last-Modified : 与 Etag 类似,Last-Modified HTTP 响应头也用来标识资源的有效性

HTTP HTTPS 区别

HTTP 是超文本传输协议,明文传输;HTTPS 使用 SSL 协议对 HTTP 传输数据进行了加密 HTTP 默认 80 端口;HTTPS 默认 443 端口

优点:安全 缺点:费时、SSL 证书收费,加密能力还是有限的,但是比 HTTP 更加安全,也是大势所趋。

Get 参数放在 url 中;Post 参数放在 request Body 中 Get 可能不安全,因为参数放在 url 中,并且对于传送的数据长度有限制。

HTTPS

3.webp

HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。

原因:非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密

Java基础

内存模型

  1. 栈:储存局部变量 (线程私有 使用完毕就会释放)
  2. 堆:储存 new 出来的东西、static类型变量 成员方法等 (线程共享 使用完毕 等待gc清理)
  3. 方法区: 对象的运行过程 (线程共享)
  4. 本地方法区:为系统方法使用 (线程私有)
  5. 寄存器:为CPU提供
  6. 程序计数器:指向当前线程正在执行的指令的地址,确保多线程下正常运行(线程私有)

参考文章 全面理解java内存模型

类加载过程

  1. 加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象
  2. 验证:确保该 Class 字节流符合虚拟机要求
  3. 准备:初始化静态变量(基本类型 、引用类型初始值null、final static值)
  4. 解析:将常量池的符号引用替换为直接引用 (类方法、变量直接指向内存引用地址或偏移量)
  5. 初始化:执行静态块代码、类变量赋值 (new 类型)
  6. 使用
  7. 卸载

四种引用方式

  这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

  1. 强引用
Object obj =new Object();

  上述Object这类对象就具有强引用,属于不可回收的资源,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠回收具有强引用的对象,来解决内存不足的问题。

  值得注意的是:如果想中断或者回收强引用对象,可以显式地将引用赋值为null,这样的话JVM就会在合适的时间,进行垃圾回收。

  1. 软引用 (SoftReference)

  如果一个对象只具有软引用,那么它的性质属于可有可无的那种。如果此时内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

  软引用可用来实现内存敏感的告诉缓存。软引用可以和一个引用队列联合使用,如果软件用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

Object obj = new Object(); 
ReferenceQueue queue = new ReferenceQueue(); 
SoftReference reference = new SoftReference(obj, queue); 
//强引用对象滞空,保留软引用 
obj = null;

  当内存不足时,软引用对象被回收时,reference.get()为null,此时软引用对象的作用已经发挥完毕,这时将其添加进ReferenceQueue 队列中

  如果要判断哪些软引用对象已经被清理:

SoftReference ref = null; 
while ((ref = (SoftReference) queue.poll()) != null) {
//清除软引用对象 
}
  1. 弱引用(WeakReference)

  如果一个对象具有弱引用,那其的性质也是可有可无的状态。

  而弱引用和软引用的区别在于:弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间充足与否,都会回收它的内存。

  同样的弱引用也可以和引用队列一起使用。

Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
WeakReference reference = new WeakReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;
  1. 虚引用

PhantomReference) 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

注意:虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference reference = new PhantomReference(obj, queue);
//强引用对象滞空,保留软引用
obj = null;

线程/进程

进程:进程是系统进行资源分配和调度的一个独立单位 (拥有独立内存空间),一个app就是一个进程,进程包含线程。

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

sleep wait 等 区别

  • wait(): 当一个线程执行到wait()方法时,它就进入到一个等待池中,同时释放对象锁,使得其他线程可以访问。用户可以使用notify,notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。

  • sleep():该函数是Thread的静态函数,作用是使调用线程进入阻塞状态(blocke)。因为sleep()是Thread类的Static方法,因为它不能改变对象的机制。所以,调用sleep方法时,线程虽然休眠了,但是对象的机制并没有被释放,其他线程无法访问这个对象

注意:wait方法,notify方法,notifyAll方法必须放在synchronized block中,否则会抛出异常。

  • join():等待目标线程执行完成之后再继续执行

  • yield():线程礼让,线程进入就绪状态(ready)。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的。

比如 T1和 T2线程先执行, T3最后执行

T1.start(); //join方法可以使线程阻塞,等调用join的线程执行完才可以继续执行 T1.join(); T2.start(); T2.join(); T3.start();

多线程

synchronized 参考文章

ThreadLocal

ThreadLocal提供了线程本地变量存储集合 Map 内部Entry使用弱引用持有。它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。 ThreadLocal 参考文章

多线程安全

多线程安全 参考文章

线程池

线程池参考文章

线程池拒绝策略

线程池拒绝策略 参考文章

synchronized 原理

Synchronized可以把任何一个非null对象作为"锁",其中都有一个监视器锁:monitor。Synchronized的语义底层是通过一个monitor的对象来完成。

  1. 同步代码块(synchronize(this)的方式)会执行 monitorenter 开始,motnitorexit 结束,当线程进入monitor,如果为0 则改为1 并分配使用,如果为1 则需要挂起等待。其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
  2. 同步代码块采用( synchronized void method()的方式)调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

synchronized 在JDK 1.6以后的优化

  1. 自适应自旋锁 :指一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放(自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定),而不是进入线程挂起或睡眠状态(因为为了很短的等待时间就去挂起唤醒会 更低效)。

  2. 锁消除:但是在有些情况下,JVM检测到不存在共享数据竞争,JVM会对这些同步锁进行锁消除。

  3. 锁粗化 :就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

  4. 偏向锁、轻量级锁:轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时通过标识,避免再走各种加锁/解锁流程,达到进一步提高性能。

synchronized 添加在非静态方法上为 对象锁,如果在静态方法上为类锁。

锁问题

可重入锁 :已经获取到锁后,再次调用同步代码块/尝试获取锁时不必重新去申请锁,可以直接执行相关代码。 ReentrantLock 和 synchronized 都是可重入锁 公平锁 : 等待时间最久的线程会优先获得锁 , synchronized lock 默认都为非公平锁

公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。

锁主要分为:悲观锁 (线程一旦得到锁,其他线程就挂起等待。用于写入频繁的 synchronized)和 乐观锁(假设没有冲突,不加锁,更新数据时判断该数据是否过期,过期的话则不进行数据更新,适用于读取操作频繁的场景,比如 AtomicInteger、AtomicLong、AtomicBoolean)

锁的状态依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

  • volatile :只能用来修饰变量,适用修饰可能被多线程同时访问的变量,相当于轻量级的synchronized,保证有序性和 可见性(变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝被 volatile 修饰的变量改变后会立即同步到主内存)。

  • synchronized :是java 的关键字(悲观锁),自动释放锁,并且无法中断锁。保证 原子性、可见性 和 有序性。

  • Lock : java 中的一个接口,lock 需要手动释放,所以需要写到 try catch 块中并在 finally 中释放锁,可以手动中断锁。

双重检查单例加volatile的原因 : 将instance =newInstance(); 创建实例分为三个过程 ,1.分配内存 2.初始化 3.将instance指向分配的内存空。但是如果 在另一个线程中想要使用instance,发现instance!=null,但是实际上instance还未初始化完毕这个问题。

JVM内存管理

JVM执行Java程序的过程:Java源代码文件(.java)会被Java编译器编译为字节码文件(.class),然后JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。

1.webp

运行时数据区被分为 线程私有数据区线程共享数据区 两大类:

线程私有数据区包含:程序计数器、虚拟机栈、本地方法栈 线程共享数据区包含:Java堆、方法区(内部包含常量池)

线程私有数据区包含:

  • 程序计数器:是当前线程所执行的字节码的行号指示器
  • 虚拟机栈:是Java方法执行的内存模型
  • 本地方法栈:是虚拟机使用到的Native方法服务

线程共享数据区包含:

  • Java堆:用于存放几乎所有的对象实例和数组;是垃圾收集器管理的主要区域,也被称做“GC堆”;是Java虚拟机所管理的内存中最大的一块
  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

Java堆和栈的区别

  • 堆内存 用于存储Java中的对象和数组,当我们new一个对象或者创建一个数组的时候,就会在堆内存中开辟一段空间给它,用于存放。特点: 先进先出,后进后出。堆可以动态地分配内存大小,由于要在运行时动态分配内存,存取速度较慢。
  • 栈内存 主要是用来执行程序用的,比如:基本类型的变量和对象的引用变量。特点:先进后出,后进先出,存取速度比堆要快,仅次于寄存器,栈数据可以共享,但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性

垃圾回收机制/ 回收算法

判定对象可回收有两种方法:

  • 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。
  • 可达性分析法:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。其中可作为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象

回收算法

分代收集算法:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。

  • 新生代:多数对象死去,少量存活。使用『复制算法』,只需复制少量存活对象即可。

    • 复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
  • 老年代:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。

    • 标记-清除算法:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的对象。
    • 标记-整理算法:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。

参考文章

JAVA反射

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

反射过程:获取反射的 Class 对象、通过反射创建类对象、通过反射获取类属性方法及构造器。

获取类的 Class 对象实例

Class clz = Class.forName("com.Apple");

根据 Class 对象实例获取 Constructor 对象

Constructor appleConstructor = clz.getConstructor();

使用 Constructor 对象的 newInstance 方法获取反射类对象

Object appleObj = appleConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

获取方法的 Method 对象

Method setPriceMethod = clz.getMethod("setPrice", int.class);

利用 invoke 方法调用方法

setPriceMethod.invoke(appleObj, 14);

反射 参考文章

JAVA泛型

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

  1. 泛型类: 如List、Set、Map。
  2. 泛型接口:如interface Generator,主要用于生产器
  3. 泛型方法:如public T showKeyName(Generic container)

数据结构

ArrayList 、LinkedList

  • ArrayList :基于动态数组实现,所以查找快(O1) 增删慢 (On),如果创建方法无参数,默认数组长度为10,添加元素如果需要扩容则是新创建一个 数组,进行数据的转移拷贝。删除的时候 如果删除成功,后续元素需要通过System.arraycopy 进行元素移动(相当的低效)。改查 则比较迅速。

  • LinkedList : 底层基于双向链表,维持了 last 和 next 两个节点,所以 查找慢 (On) 增删快(O1),链表的查找是循环的(不过查找的过程是 先判断index是靠近哪一段 然后再进行查找 可以理解为 O(n/2)),但是速度还是慢。在添加和删除中,因为添加是直接放到链表尾部 但是删除存在 先循环一遍,然后删除的情况,不过 相对于ArrayList的复制要好的很多了。

HashMap SparseArray HashTable TreeMap LinkedHashMap ArrayMap

  • HashMap : jdk8后由数组、链表和红黑树组成,基于散列表实现。当数组中的数据出现 hash冲突的时候启动链表,当链表中的 个数超过8个 将链表转为红黑树。允许 key val 为NULL,key和 val存储数据可以为任何类型(非基本类型 会自动装箱)。HashMap并不是线程安全的,可以通过 Collections.synchronizedMap(new HashMap)的方式获得线程的Hahsmap,或者使用 下边的ConcurrentHashMap。

由于HashCode冲突自动装箱Entry类型多余元素问题,数据量小的时候推荐使用 SparseArray 和 ArrayMap HashMap 源码参考文章

  • SparseArray : key 只能为整数型,内部也是两个数组,一个存key的hashcode,一个存 key和val 交叉保存, 添加的时候 key 不用装箱,val则需要如果是基本类型,查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)

  • ArrayMap :key val 可以为 任意类型(非基本类型,会自动装箱),中有两个数组,一个存储key 的hash值,另外一个交叉存储 key val 数据(key val key val .... 形式存储)

  • TreeMap : 相比较于HashMap,TreeMap实现SortedMap接口,所以TreeMap是有序的!HashMap是无序的。

  • HashTable : 其实主体和 HashMap类似,但是写入和 读取方法 添加了 synchronize 可以做到 线程安全,key 和val 不能为null,但是效率没有HashMap高。

  • LinkedHashMap: LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

  • ConcurrentHashMap :ConcurrentHashMap由多个segment 组成,每个segment 包含一个Entity 的数组。这里比HashMap 多了一个segment 类。该类继承了ReentrantLock 类,所以本身是一个锁。当多线程对ConcurrentHashMap 操作时,不是完全锁住map, 而是锁住相应的segment 。这样提高了并发效率。缺点:当遍历ConcurrentMap中的元素时,需要获取所有的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操作的一些方法

ConcurrentHashMap 和 HashMap不多的博客推荐 传送门

常用设计模式及源码使用

  • 单例模式
    初始化比较复杂,并且程序中只需要一个。避免重复创建消耗内存 Android中 获取WindowManager服务引用 WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE); l另外一种不错实现单例的方式 使用 eunm,
public class Singleton {
    private static volatile Singleton s;
    private Singleton(){};
    public static Singleton getInstance() {  
        if(s == null) {
            synchronized (Singleton.class) {
                if(s == null) {
                    s = new Singleton(); 
                }
            }
        }
        return s; 
    }
}
  • 创建者模式
    创建某对象时,需要设定很多的参数(通过setter方法),但是这些参数必须按照某个顺序设定 Android 中 创建所有的 Dialog 中使用的
public class TestClient {
    
    private int index;
    private String name;
    public TestClient() {
        this(new Builder());
    }
    public TestClient(Builder builder){
        this.index = builder.index;
        this.name = builder.name;
    }

    public static final class Builder {
        private int index;
        private String name;
        public Builder() {
            this.index = 1;
            this.name = "xxx";
        }
        public Builder(TestClient testClient){
            this.index = testClient.index;
            this.name = testClient.name;
        }
        public Builder setIndex(int index) {
            this.index = index;
            return this;
        }
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
        public TestClient build(){
            return new TestClient(this);
        }
    }
}
  • 原型模式
  • 工厂模式 定义一个创建对象的工厂,根据不同传参 创建不同的对象。 Android 中 BitmapFactory 和 Iterator 根据循环对象不同返回不同的对象
  • 策略模式 有一系列的算法,将算法封装起来(每个算法可以封装到不同的类中),各个算法之间可以替换,策略模式让算法独立于使用它的客户而独立变化 Android 中的 时间插值器,可以使用不同的 加速 减速 或者自定义加速器 展示不同的动画效果
  • 责任链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系,将这些对象连成一条链,并沿这条链传递该请求,直到有对象处理它为止。 Android 中有 View 点击事件分发 或者 第三方库 OKHttp 中的拦截器
  • 命令模式 命令模式将每个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;将请求进行排队或者记录请求日志,以及支持可撤销操作。 Android 事件机制中,底层逻辑对事件的转发处理。每次的按键事件会被封装成NotifyKeyArgs对象,通过InputDispatcher封装具体的事件操作 / Runable实现中封装我们需要的实现
  • 观察者模式 Java的Observable类和Observer接口就是实现了观察者模式。一个Observer对象监视着一个Observable对象的变化,当Observable对象发生变化时,Observer得到通知,就可以进行相应的工作。
  • 中介者模式 在Binder机制中,即ServiceManager持有各种系统服务的引用 ,当我们需要获取系统的Service时,首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者。
  • 代理模式 给某一个对象提供一个代理,并由代理对象控制对原对象的引用 (,静态代理 和 动态代理)
  • 适配器模式 把一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

参考文章