1.ArrayList源码分析
线程非安全性
对ArrayList的操作一般分为两个步骤,改变位置(size)和操作元素(e)。
transient关键字解析
Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化。
transient Object[] elementData;
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
//克隆数据低层调用
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
ArrayList
在序列化的时候会调用writeObject()
方法,将size
和element
写入ObjectOutputStream
;反序列化时调用readObject()
,从ObjectInputStream
获取size
和element
,再恢复到elementData
。
原因在于
elementData
是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
ArrayList优缺点
优点:
-
因为其底层是数组,所以修改和查询效率高。
-
可自动扩容(1.5倍)。
缺点:
-
插入和删除效率不高。
-
线程不安全。
2.HashMap源码分析
数据结构
插入效率比平衡二叉树高,查询效率比普通二叉树高。所以选择性能相对折中的红黑树。
备注:为什么是8,6?个人认为7是临界点 3层深度,在临界点加减1.
//检查链表长度是否达到阈值,达到将该槽位节点组织形式转为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//构造函数
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
}
//通过移位和异或运算,可以让 hash 变得更复杂,进而影响 hash 的分布性。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
备注:tab[index = (n - 1) & hash]----解释了为什么需要2的次幂作为数组的容量。由于n是2的次幂,因此,n-1类似于一个低位掩码。通过与操作,高位的hash值全部归零,保证低位才有效 从而保证获得的值都小于n。(还有就是扩容不是原索引就是原索引+原容量)
但是,使用了该功能之后,由于只取了低位,因此 hash 碰撞会也会相应的变得很严重。这时候就需要使用「扰动函数」^ (h >>> 16)该函数通过将哈希码的高16位的右移后与原哈希码进行异或而得到。此方法保证了高16位不变, 低16位根据异或后的结果改变。
01010011 00100101 01010100 00100101 & 00000000 00000000 00000000 00001111 --------------------------------------- 00000000 00000000 00000000 00000101 //高位全部归零,只保留末四位 5 // 保证了计算出的值小于数组的长度 n 01010011 00100101 01010100 00100101 ^ 00000000 00000000 01010011 00100101 --------------------------------------- 01010011 00100101 00000111 00000000 //高位不变,低位改变 5->0
工作原理
注意事项
虽然 HashMap
设计的非常优秀, 但是应该尽可能少的避免 resize()
, 该过程会很耗费时间。
同时, 由于 hashmap
不能自动的缩小容量 因此,如果你的 hashmap
容量很大,但执行了很多 remove
操作时,容量并不会减少。如果你觉得需要减少容量,请重新创建一个 hashmap。