Android技术知识Android开发Android开发

送给Android程序员2021全新整理的面试合集,只为助力年后

2021-01-02  本文已影响0人  Z_萧晓

垃圾回收算法

标记算法:
  1. 引用计数法
  2. 可达性分析法(注意GC root的类型,虚拟机栈和本地方法栈引用的对象、静态对象、字节码对象)
回收算法(复制算法、标记清除、标记整理)

java类加载机制

java匿名内部类

匿名内部类就是没有名字的内部类,(其实是有名字的,虚拟机定位这个类,编译之后会使用 外部类名$1这样的名字,数字按顺序生成)。

匿名内部类的构造方法由编译器生成,参数列表包括:

注意

拓展:lambda表达式可以替代部分匿名内部类,父类必须是接口,且只有一个方法。

java泛型擦除

使用泛型可以声明集合存储的元素类型,取出元素时避免强转的操作。在java中,编译完成后泛型参数会被擦除,例如List<String>List<Integer>编译完成后都是List类型。

java泛型为什么会被擦除:
存在的问题:
  1. 基本类型无法用于泛型,只能用装箱类型,例如List,装箱操作有额外的开销。
  2. 泛型参数不能用于方法重载,因为编译完成后泛型被擦除,参数都是一样的。
  3. 泛型类型不能当做真实的类型来使用,例如方法参数中有一个泛型T,方法中不能直接new T(),因为编译之后就是Object,不知道真实的类型。
  4. 静态方法无法引用类的泛型,因为类的泛型在实例化的时候才知道。
  5. 类型强转的额外开销。

泛型在特定场景可以通过反射获取,例如父类有一个泛型参数已经被确定,子类继承之后可以获取。例如gson中,解析带泛型的List,要传入一个TypeToken,实际上是new了一个子类,通过反射获取泛型类型。

如何写出线程安全的程序?

线程安全的本质,可变资源在线程间共享的问题。关键:可变资源线程共享

线程安全三要素:原子性可见性有序性

所以要保证线程安全:

  1. 共享不可变资源,final关键字的使用。
  2. 使用纯函数(不访问外部资源),使用ThreadLocal,不共享资源。
  3. 使用volatile关键字保证共享资源的可见性,并禁止指令重排序。
  4. 操作原子性(加锁保证操作的互斥性,原子类AtomicXXX的使用,CAS指令如Unsafe.compareAndSwap

Synchronized原理

底层通过一个监视器monitor实现,monitor对象包含一个count计数字段和owner字段指向获取锁的线程,当线程获取monitor后,count+1owner指向线程,监视器处于锁定状态,其他线程不能获取monitor会进入阻塞状态,当前线程释放monitor后,其他线程可以继续竞争该锁。

Java1.6之后对Synchronized进行了一些优化:

Synchronized可以修饰静态方法(锁对象为字节码对象)、实例方法(锁为实例对象)和代码块,无论是否发生异常虚拟机都会正常释放锁 ReentrantLock发生异常时不能释放锁,所以一般需要在finaly代码块中释放锁,它包含公平锁和读写锁等用法,使用更灵活

java虚拟机内存模型

class加载过程

  1. 装载,将class文件加载进内存,在堆中生成class对象
  2. 链接,验证二进制数据流(类结构是否正确),分配静态变量设置默认值(初始化时才真正赋值),将符号引用转换为直接引用
  3. 初始化,初始化静态变量,静态代码块

java内存模型、volatile的作用

内存模型

Java内存模型规定了所有对共享变量的读写操作都必须在本地内存中进行,需要先从主内存中拿到数据,复制到本地内存,然后在本地内存中对数据进行修改,再刷新回主内存,这就导致了多线程情况下数据的可见性问题,可以使用volatile关键字来修饰

如何安全停止一个线程

stop方法,被废弃。强行停止一个线程,没有资源的机会,如果正在处理任务,会留下一堆异常的数据。另一个线程再访问时就会发生错误。那么如何安全的结束呢: 1、设置volatile的boolean标志位,修改标志位来判断是否继续执行还是清理现场。 2、Interrupt方法:线程内部也需要做支持,判断是否被中断,和标志位类似的处理。支持sleep等系统方法(sleep过程中中断)。 判断是否中断的两个方法的区别: interrupted,静态方法,获取当前正在执行的线程是否被中断,中断之后会清空状态,重复获取就返回false isInterrupted,线程的方法,获取当前线程的中断状态,不会被清空状态

HashMap原理

底层是数组+链表的结构,默认数组长度16,加载因子0.75,在put时,(如果第一次put,会创建数组)如果元素个数大于数组长度*加载因子时,将触发扩容操作,数组长度翻倍,并重新计算hash将元素放入数组;

Java1.8中,如果元素过多,数组长64,链表长度超过8,将进行树化操作,将链表转为红黑树,红黑树的节点是链表节点占用空间的两倍,提高查询效率;

如何计算元素存储的位置,如何解决hash冲突,为何数组长度必须为2的整数幂:

先把key取hash值,然后进行一个二次hash,方式为(n-1)&hash,这个二次hash是因为如果n正好等于2的幂,(n-1)&hash相当于对n取模,这样位运算效率很高,这样就相当于把元素均匀分布到了数组中,如果数组的位置没有元素,直接保存元素,如果已经有元素了,表示发生了hash冲突,将改为链表的存储方式,把新元素放在头部(1.8中是尾插法)

为什么加载因子为0.75?设为1和0.5有什么问题?

loadFactor太大,比如等于1,那么就会有很高的哈希冲突的概率,会大大降低查询速度。 loadFactor太小,比如等于0.5,那么频繁扩容没,就会大大浪费空间。

Hashtable

初始化容量不一样(11),线程安全对整个数组加锁,不允许null值,数据结构一直是数组+链表,不会转换为红黑树;

ConcurrentHashMap:

1.5-1.7采用分段锁segment机制,不再是整个数组加锁,而是对单条或者几条链表和红黑树进行加锁。内部结构如图:segment数组,segment中类似HashMap的数组+链表。要通过hash让元素尽可能的均匀分布到不同的segment和数组中,所以对key取hash,用高位确定segment的位置,然后用低位确定数组的位置。

1.5的hash算法不好,元素多的时候会造成新加的节点分布在最后的几个桶,分布不均匀,

1.6就改善了hash算法。

1.7的优化是采用segment的懒加载机制,并用volatile的方式访问数组,保证数组的线程可见性,结合CAS指令来避免加锁。 1.8中则基于hashmap做优化,不再采用分段锁,而是对桶节点加锁,使用volatile和CAS乐观锁来实现读和写,再次提高了效率。

通过对Hashtable和ConcurrentHashMap的比较,得出一些锁优化的方法论,比如大锁不如小锁,长锁不如短锁,读写锁的分离等等

线程池原理

线程池的参数
  1. corePoolSize:线程池大小,当向线程池提交任务时,如果线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会创建一个新的线程来执行任务,直到线程数大于或等于corePoolSize。(除了提交新任务来创建线程,也可以通过prestartCoreThreadprestartAllCoreThreads来提前创建核心线程)
  2. maximumPoolSize:线程池最大大小,当任务队列满了,且已创建的线程数小于最大线程数,则创建新线程来执行任务,如果线程池任务队列为无界队列可以忽略该参数
  3. keepAliveTime:线程存活时间,当线程数大于核心线程数时,线程空闲时间大于存活时间,那么这个线程将被销毁,如果线程池任务队列为无界队列可以忽略该参数
  4. workQueue:任务队列,用于保存等待执行任务的阻塞队列
  5. threadFactory:线程工厂,用于创建新线程,可以设置统一风格的线程名
  6. handler:线程饱和策略,当任务队列和线程池都满了,继续提交任务将执行此策略
如何配置线程池?需要看任务的类型
java自带的线程池
线程池 核心线程 最大线程 存活时间 任务队列
CachedThreadPool 0 Integer.MAX_VALUE 60S SynchronousQueue
FixedThreadPool n n 0 LinkedBlockingQueue
SingleThreadExecutor 1 1 0 LinkedBlockingQueue
ScheduledThreadPool n Integer.MAX_VALUE 0 DelayWorkQueue

SynchronousQueue:只能有一个元素的队列,插入和获取元素都会阻塞线程

java方法分派(多态)

子类复写父类方法,调用方法调用子类还是父类? 取决于运行时具体的调用者类型,实例是子类就调用子类的方法。

HTTPS

对称加密和非对称加密
数字签名技术

​ 非对称加密在实际使用中,公钥会公开出来,私钥保存在自己手中不公开。由于私钥加密的密文只有公钥可解,那么如果有一个密文用你的公钥可以解开,那么可以说明这个密文肯定是你的私钥加密的,这就诞生了数字签名技术。

​ 只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明

https的本质

https的本质就是:用非对称加密的方式协商出一个对称加密的会话秘钥来进行会话

客户端如何校验CA证书
  1. 客户端收到证书后,用证书中的公钥去解密该Hash值,得到hash-a
  2. 客户端用证书中指定的签名算法,计算出一个hash-b,比较hash-a和hash-b
  3. 除了校验hash值,还会校验CA证书有效期和域名等
SSL握手过程
  1. 客户端A访问服务端B,客户端生成一个随机数1、将自己支持的SSL版本号、加密套件(包括哈希算法和加密算法)等信息发送给服务端
  2. 服务端B收到请求,选择一个加密套件,也生成一个随机数2,将随机数和自己的证书一同返回给客户端
  3. 客户端收到证书,校验证书是否有效(方法之前说过了),通过校验后,生成一个随机数3,用证书中的公钥加密随机数3,发送给服务端B
  4. 服务端收到加密的随机数,用私钥解密
  5. 服务端和客户端都有了随机数1、2、3,通过这三个随机数,生成一个对称加密的会话密钥
  6. 服务端和客户端分别通知对方之后的会话用会话秘钥来完成,握手结束
为什么要用非对称加密来握手,而用对称加密来会话

对称加密握手的话,由于双方的秘钥是一样的,相当于秘钥公开了,和没加密没有区别

而会话阶段,对称加密效率较非对称高

TCP为什么要三次握手和四次挥手

为什么TCP是可靠的?

TCP基于连接,具有以下机制:

UDP是无连接、不安全的,每个数据包都包含接收的ip等信息,客户端只管发送,没有确认重传机制,所以速度更快,但是可能会丢包。

HTTP1.0、1.1、2.0的区别

1.1和1.0:
SPDY:

SPDY是Http1.x版本的优化方案,包括多路复用技术、请求优先级(多路复用时,多个请求并行于共用的TCP连接,可以设置请求的优先级防止关键请求被阻塞)、header压缩和服务端推送功能;SPDY的特性并入了Http2.0中;

1.1和2.0:

三方授权方式

bearer token 的获取⽅式( OAuth2 的授权流程):
  1. 第三⽅⽹站向授权⽅⽹站申请第三⽅授权合作,拿到 client id 和 client secret

  2. ⽤户在使⽤第三⽅⽹站时,点击「通过 XX (如 GitHub) 授权」按钮,第三⽅⽹站将⻚⾯跳转到授权⽅⽹站,并传⼊ client id 作为⾃⼰的身份标识

  3. 授权⽅⽹站根据 client id ,将第三⽅⽹站的信息和需要的⽤户权限展示给⽤户,询问⽤户是否同意授权

  4. ⽤户点击「同意授权」按钮后,授权⽅⽹站将⻚⾯跳转回第三⽅⽹站,并传⼊ Authorization code 作为⽤户认可的凭证。

  5. 第三⽅⽹站将 Authorization code 发送回⾃⼰的服务器

  6. 服务器将 Authorization code 和 client secret ⼀并发送给授权⽅的服务器,授权⽅返回 access token。

WebSocket和Socket的区别

多线程下载和断点续传

两个核心Header,Content-Length表示文件的总字节数,RANGE表示从某一个位置开始传输。

首先,获取到文件大小后,通过线程数来计算每个线程下载的开始位置。

然后,通过range来设置从哪个位置传输。

当暂停或者退出时,记录已下载的位置,下次恢复后从记录的位置下载。

使用RandAccessFile来保存文件,这个类的特点是可以通过移动文件指针来设置写入的位置。

面试大厂复习路线

多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!

这里给大家提供一个方向,进行体系化的学习:

1、看视频进行系统学习

前几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频,有需要的我也可以分享给你。

2、进行系统梳理知识,提升储备

客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

系统学习方向:

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

3、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

主要内含微信 MMKV 源码、AsyncTask 源码、Volley 源码、Retrofit源码、OkHttp 源码等等。

4、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

总结

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

以上内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者关注主页扫描加 微信 获取。

上一篇下一篇

猜你喜欢

热点阅读