Java8容器源码-目录
ArrayList
- 底层数据结构是数组。线程不安全
- 它是动态数组,可以扩容(int[] aa = new int[5],是静态数组不可扩容) ,add(E e),调用copyOf()
// 扩容为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 拷贝一个新的数组放进去
elementData = Arrays.copyOf(elementData, newCapacity);
-
add(int index, E element)
System.arraycopy() 是个 native 方法, 而 JVM 调用 native 方法有一定开销的; 在拷贝小数组的时候, 使用 System.arraycopy() 和使用 for 循环的方式哪种更高效?
-
ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制。
-
ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
-
删除元素时不会减少容量,若希望减少容量则调用trimToSize()
-
它不是线程安全的。它能存放null值。
-
如果想要ArrayList实现同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了~,LinkedList也是
还有另一个区别:
ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
LinkedList
- 底层数据结构是链表。线程不安全
LinkedList
实现了Deque接口, Deque
是双向队列的声明接口,而LinkedList
内部实现是链表,所以说LinkedList
是一个双向链表
而LinkedList
的类图相对于ArrayList少了RandomAccess接口,因此LinkedList
是不支持随机访问的,因此LinkedList
根据索引位置查找元素的速度比ArrayList
慢
add
方法调用的是linkLast
也就是在链表末尾添加元素。之前咱们再介绍ArrayList
新增元素的时候,每次都需要调用ensureCapacityInternal(int minCapacity)
方法,确保数组空间足够,不够的话要进行扩容。
而LinkedList
就不需要,这也是LinkedList
的主要优点:它不需要初始化容器容量大小,也就无需动态扩容。也就是说LinkedList
比ArrayList
更省内存空间,插入速度更快。
但是如果需要根据索引位置插入元素,效率就比较低,因为需要先逐个查找元素,这边看下 add(int index, E element)
的实现
1、按需分配空间,不需要预先分配很多空间,更加节省内存空间
2、不支持随机访问,按照索引位置访问效率比较低,必须从头或尾顺着链表找 效率为O(N/2)
3、不管列表是否已经排序,只要按照内容查找元素,效率都比较低,必须逐个比较,效率为O(N)
4、根据索引位置进行插入和删除元素,要先定位,效率比较低,为O(N/2),但修改本身效率很高为O(1)
Vector
- 底层数据结构是数组。线程安全,不能存放null
- Vector是扩展1倍。
https://github.com/ZhongFuCheng3y/3y
String,StringBuffer,StringBuilder的区别
- String value前面有final修饰,是不可变的,每次拼接都会生成新的对象
- StringBuffer和StringBuilder都继承了AbstractStringBuilder,功能是一样的
- StringBuffer是线程安全的,所有的public方法都加了synchronized 修饰
看Map底层原理
- 桶排序 int [length] [10] ,先找出最大值,确定循环几次,然后%10,循环排序,先排个位,十位,百位…
- 散列表是链表数组实现的,每个列表称为桶. 散列冲突,JDK1.8中,桶满时会从链表变成平衡二叉树,装填因子默认为0.75,如果表中超过了75%的位置已经填入了元素,那么这个表就会用双倍的桶数自动进行再散列
- 红黑树
- 堆排序
HashMap
-
位置计算公式是hash(key)&(length-1)
(length-1): hash(key)是一个很长的数,当length是2的整数次幂时,length-1是1111,当它们做与运算之后,相当于取了hash(key)的低位数,这样不会超出散列表长度. 如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,在于h与操作,最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,同时加大了碰撞
hash(key): 设计者将key的哈希值的高位也做了运算(与高16位做异或运算,使得在做&运算时,此时的低位实际上是高位与低位的结合),这就增加了随机性,减少了碰撞冲突的可能性
-
HashMap允许键值为null. 初始容量是16,最大容量2^31. 默认装载因子0.75f(存储量达到75%时进行扩容,扩容到大于所需长度且最接近2的整数次幂).
为什么时默认装载因子时0.75f ? 为了达到空间与时间的平衡,散列表越满,碰撞概率越大,这是设计者经过测试得出的. 根据统计学的结果, hash冲突是符合泊松分布的, 而冲突概率最小的是在7-8之间, 都小于百万分之一了; 所以HashMap.loadFactor选取只要在7-8之间的任意值即可, 但是为什么就选了3/4这个值, 我们看了HashMap的扩容机制也就知道了;
-
put()
判断Map为空则进行扩容resize()
如果没有发生碰撞,则直接存入
如果发生碰撞,且新key已存在,则覆盖旧值并返回旧值
------>新key不存在
------------->如果是TreeNode 调用二叉树添加元素((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
------------->如果是链表,先添加,然后判断长度是否大于8,若大于8且散列表.length<64,为HashMap扩容
-------------------------------------------------------------------------->若大于8且散列表.length>64,转为红黑树结构
如果当前散列表存储超过阈值(0.75f),则进行扩容
-
1 如果table == null, 则为HashMap的初始化, 生成空table返回即可;
2 如果table不为空, 需要重新计算table的长度, newCap= oldCap << 1(注, 如果原oldLength已经到了上限, 则newCap = oldCap); 计算出新的阈值 newThr = oldThr << 1 (或者newCap * loadFactor)
3 遍历oldTable:
3.1 首节点为空, 本次循环结束;
3.2 无后续节点, 重新计算hash位, 本次循环结束;
3.3 当前是红黑树, 走红黑树的重定位;
3.4 当前是链表, JAVA7时还需要重新计算hash位, 但是JAVA8做了优化, 通过(e.hash & oldCap) == 0来判断是否需要移位; 如果为真则在原位不动, 否则则需要移动到当前hash槽位 + oldCap的位置;
LinkedHashMap
-
put()调用的HashMap的put方法,newNode是自己重写的
-
在传入AccessOrder的情况下,使用get方法也是结构性的修改. 最常用的将其放在链表的最后,不常用的放在链表的最前
LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);
-
因为最常被使用的元素再遍历的时候却放在了最后边,在LinkedHashMap中我也没找到对应的方法来进行调用~
-
一个
removeEldestEntry(Map.Entry<K,V> eldest)
方法,重写它可以删除最久未被使用的元素 -
还有一个是
afterNodeInsertion(boolean evict)
方法,新增时判断是否需要删除最久未被使用的元素
连环问
StringBuffer,StringBuilder区别是啥?
什么是线程安全?
如何保证线程安全?
什么是锁?
死锁?
synchronized的实现原理是什么?
有了synchronized,还要volatile干什么?
synchronized的锁优化是怎么回事?
(锁粗化?锁消除?自旋锁?偏向锁?轻量级锁?)
知道JMM吗?(原子性?可见性?有序性?)
Java并发包了解吗?
那什么是fail-fast?
什么是fail-safe?
什么是CopyOnWrite?
那AQS呢?
那CAS呢?
CAS都知道,那乐观锁一定知道了?
乐观锁悲观锁区别是什么?
数据库如何实现悲观锁和乐观锁?
数据库锁有了解么?
行级锁?
表级锁?
共享锁?
排他锁?
gap锁?
next-key lock?
数据库锁和隔离级别有什么关系?
数据库锁和索引有什么关系?
什么是聚簇索引?
非聚簇索引?
最左前缀是什么?
B+树索引?
联合索引?
回表?
分布式锁有了解吗?
Redis怎么实现分布式锁?
为什么要用Redis?
Redis和memcache区别是什么?
Zookeeper怎么实现分布式锁?
什么是Zookeeper?
什么是CAP?
什么是BASE?和CAP什么区别?
CAP怎么推导?
如何取舍?
分布式系统怎么保证数据一致性?
啥是分布式事务?
分布式事务方案?
那么,最后了,来手写一个线程安全的单例吧?
不用synchronized和lock能实现线程安全的单例吗?
这你都能答上?那好吧,你给我解释下什么是Paxos算法吧?