- Java异常体系
Throwable是Java语言中所有错误或异常的超类。下一层分为Error 和Exception。
Error:
是指java运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
Exception包含:RuntimeException、CheckedException;
编程错误可以分成三类:语法错误、逻辑错误和运行错误。
语法错误(也称编译错误)是在编译过程中出现的错误,由编译器检查发现语法错误。
逻辑错误指程序的执行结果与预期不符,可以通过调试定位并发现错误的原因。
运行错误是引起程序非正常终端的错误,需要通过异常处理的方式处理运行错误。
RuntimeException:运行时异常,程序应该从逻辑角度尽可能避免这类异常的发生。
如NullPointerException、ClassCastException ;
CheckedException:受检异常,程序使用trycatch进行捕捉处理;
如IOException、SQLException、NotFoundException;
▐ 数据结构
- ArrayList和LinkedList
ArrayList:
底层基于数组实现,支持对元素进行快速随机访问,适合随机查找和遍历,不适合插入和删除。(提一句实际上)
默认初始大小为10,当数组容量不够时,会触发扩容机制(扩大到当前的1.5倍),需要将原来数组的数据复制到新的数组中;当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。
LinkedList:
底层基于双向链表实现,适合数据的动态插入和删除;
内部提供了List接口中没有定义的方法,用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。(比如jdk官方推荐使用基于linkedList的Deque进行堆栈操作)
ArrayList与LinkedList区别:
都是线程不安全的,ArrayList适用于查找的场景,LinkedList适用于增加、删除多的场景。
实现线程安全:
可以使用原生的Vector,或者是Collections.synchronizedList(List list)函数返回一个线程安全的ArrayList集合。
建议使用concurrent并发包下的CopyOnWriteArrayList的。
①Vector:底层通过synchronize修饰保证线程安全,效率较差。
②CopyOnWriteArrayList:写时加锁,使用了一种叫写时复制的方法;读操作是可以不用加锁的。
- List遍历快速和安全失败
普通for循环遍历List删除指定元素
for(int i=0; i < list.size(); i++){ if(list.get(i) == 5) list.remove(i);}
迭代遍历,用list.remove(i)方法删除元素
Iterator<Integer> it = list.iterator();while(it.hasNext()){ Integer value = it.next(); if(value == 5){ list.remove(value); }}
foreach遍历List删除元素
for(Integer i:list){ if(i==3) list.remove(i);}
fail—fast:快速失败
当异常产生时,直接抛出异常,程序终止。
fail-fast主要是体现在当我们在遍历集合元素的时候,经常会使用迭代器,但在迭代器遍历元素的过程中,如果集合的结构(modCount)被改变的话,就会抛出异常ConcurrentModificationException,防止继续遍历。这就是所谓的快速失败机制。
fail—safe:安全失败
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
缺点:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
- 详细介绍HashMap
角度:数据结构+扩容情况+put查找的详细过程+哈希函数+容量为什么始终都是2^N,JDK1.7与1.8的区别。
参考:https://www.jianshu.com/p/9fe4cb316c05
数据结构:
HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据。
扩容情况:
默认的负载因子是0.75,如果数组中已经存储的元素个数大于数组长度的75%,将会引发扩容操作。
【1】创建一个长度为原来数组长度两倍的新数组。
【2】1.7采用Entry的重新hash运算,1.8采用高于运算。
put操作步骤:
1、判断数组是否为空,为空进行初始化;2、不为空,则计算 key的hash值,通过(n - 1) & hash计算应当存放在数组中的下标 index;3、查看table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;4、存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据;5、若不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;6、若不是红黑树,创建普通Node加入链表中;判断链表长度是否大于8,大于则将链表转换为红黑树;7、插入完成之后判断当前节点数是否大于阈值,若大于,则扩容为原数组的二倍;哈希函数:通过hash函数(优质因子31循环累加)先拿到key的hashcode,是一个32位的值,然后让hashcode的高16位和低16位进行异或操作。该函数也称为扰动函数,做到尽可能降低hash碰撞,通过尾插法进行插入。容量为什么始终都是2^N:先做对数组的⻓度取模运算,得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。(n代表数组⻓度)。方便数组的扩容和增删改时的取模。JDK1.7与1.8的区别:JDK1.7 HashMap:底层是 数组和链表 结合在⼀起使⽤也就是链表散列。如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。扩容翻转时顺序不一致使用头插法会产生死循环,导致cpu100%JDK1.8 HashMap:底层数据结构上采用了数组+链表+红黑树;当链表⻓度⼤于阈值(默认为 8-泊松分布),数组的⻓度大于 64时,链表将转化为红⿊树,以减少搜索时间。(解决了tomcat臭名昭著的url参数dos攻击问题)
- ConcurrentHashMap
可以通过ConcurrentHashMap和Hashtable来实现线程安全;Hashtable 是原始API类,通过synchronize同步修饰,效率低下;ConcurrentHashMap通过分段锁实现,效率较比Hashtable要好。ConcurrentHashMap的底层实现:JDK1.7的ConcurrentHashMap底层采⽤ 分段的数组+链表 实现;采用 分段锁(Sagment) 对整个桶数组进⾏了分割分段(Segment默认16个),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。
JDK1.8的 ConcurrentHashMap采⽤的数据结构跟HashMap1.8的结构⼀样,数组+链表/红⿊树;摒弃了Segment的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,通过并发控制synchronized和CAS来操作保证线程的安全。
- 序列化和反序列化
序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象。对象序列化是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输。反序列化就是根据这些保存的信息重建对象的过程。
序列化:将java对象转化为字节序列的过程。反序列化:将字节序列转化为java对象的过程。 优点:
- 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)Redis的RDB
- 利用序列化实现远程通信,即在网络上传送对象的字节序列。Google的protoBuf。
反序列化失败的场景:序列化ID:serialVersionUID不一致的时候,导致反序列化失败。
- String
String使用数组存储内容,数组使用final修饰,因此String定义的字符串的值也是不可变的。
StringBuffer对方法加了同步锁,线程安全,效率略低于StringBuilder。
▐ 设计模式与原则
- 单例模式
某个类只能生成一个实例,该实例全局访问,例如Spring容器里一级缓存里的单例池。优点:唯一访问:如生成唯一序列化的场景、或者spring默认的bean类型。提高性能:频繁实例化创建销毁或者耗时耗资源的场景,如连接池、线程池。缺点:不适合有状态且需变更的;实现方式:饿汉式:线程安全速度快;懒汉式:双重检测锁,第一次减少锁的开销、第二次防止重复、volatile防止重排序导致实例化未完成;静态内部类:线程安全利用率高;枚举:effictiveJAVA推荐,反射也无法破坏;
- 工厂模式
定义一个用于创建产品的接口,由子类决定生产何种产品。优点:解耦:提供参数即可获取产品,通过配置文件可以不修改代码增加具体产品。缺点:每增加一个产品就得新增一个产品类。
- 抽象工厂模式
提供一个接口,用于创建相关或者依赖对象的家族,并由此进行约束。
优点:可以在类的内部对产品族进行约束。缺点:假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。