集合 集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue)和 map(映射)。
Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
Iterator:迭代器,可以通过迭代器遍历集合中的数据
Map:是映射表的基础接口
list ArrayList 排列有序,可重复
底层使用数组
查询速度快,增删慢,getter()和setter()方法快
线程不安全
当容量不够时,ArrayList是当前容量 * 1.5 + 1
Vector 排列有序,可重复
底层使用数组
查询速度快,增删慢
线程安全,效率低
当容量不够时,Vector默认扩展一倍容量
LinkedList 排列有序,可重复
底层使用双向循环链表数据结构
查询速度慢,增删快,add() 和 remove()方法快
线程不安全
Set HashSet 排序无序,不可重复
底层使用Hash表实现
存取速度快
内部是HashMap
TreeSet 排序无序,不可重复
底层使用二叉树实现
排序存储
内部是TreeMap的SortedSet
LinkedHashSet 采用hash表存储,并用双向链表记录插入顺序
内部是LinkedHashMap
Queue 在两端出入的List,所以也可以用数组或链表来实现
Map HashMap 键不可重复,值可重复
底层哈希表
线程不安全
允许Key值为null,value也可以为null
HashTable 键不可重复,值可重复
底层哈希表
线程安全
Key、value都不允许为null
TreeMap 键不可重复,值可重复
底层二叉树
HashMap源码分析 Jdk 7 使用头插法 数据结构使用 数组 + 链表
Jdk 8 使用尾插法 数据结构使用 数组 + 链表 + 红黑树
1 2 3 1. initialCapacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。 2. loadFactor:负载因子,默认为 0.75。 3. threshold:扩容的阈值,等于 capacity * loadFactor
构造方法 1 2 3 4 5 6 7 8 9 10 11 12 public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException ("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException ("Illegal load factor: " + loadFactor); this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); }
1 2 3 4 5 6 7 8 9 static final int tableSizeFor (int cap) { int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; return (n < 0 ) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ; }
把tableSizeFor 复制到你的类中,执行
1 2 3 4 5 6 7 8 9 10 static final int tableSizeFor (int cap) { int MAXIMUM_CAPACITY = 1 << 30 ; int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; return (n < 0 ) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ; }
发现这个方法会返回 1 2 4 8 16 32,结合注释,输入一个数,返回一个能包含输入数的2次方的一个数。
int 32位4个字节
>>>右移
最高位无论是0还是1都补0,空位补0
|按位或
两个位置上任一位置上是1,结果就是1,两个位置上都是0,结果才是0
1 2 3 4 5 6 7 8 System.out.println(tableSizeFor(0 )); System.out.println(tableSizeFor(1 )); System.out.println(tableSizeFor(1 << 30 )); System.out.println(tableSizeFor(Integer.MAX_VALUE));
输入一个非2的次方的数,如33
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; >>> 取高位的值 补 低位 如果 高位值没有了就为0 了 | 是增大数 0000 0000 0 0000 0001 1 0000 0011 3 0000 0111 7 0000 1111 15 0001 1111 31 0011 1111 63 0111 1111 127
PUT方法 1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
^按位异或
两个位置上值相同,结果就是0,两个位置上值不相同,结果才是1
当key == null 时 hash值为0;
当key不为空时,hash值为 对象的hashcode 异或 对象的hashcode的高16位。
hashMap的存储 节点定义
1 2 3 4 5 6 static class Node <K,V> implements Map .Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
数组
1 transient Node<K,V>[] table;
节点定义中Node<K,V> next;表示为单向链表。
put方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 onlyIfAbsent 不存在时设置valuefinal V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0 ) n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; }
& 按位与
两个位置上任一位置上是0,结果就是0,两个位置上都是1,结果才是1
1 2 红黑树的put e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final void treeifyBin (Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1 ) & hash]) != null ) { TreeNode<K,V> hd = null , tl = null ; do { TreeNode<K,V> p = replacementTreeNode(e, null ); if (tl == null ) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null ); if ((tab[index] = hd) != null ) hd.treeify(tab); } }
头插和尾插: hashmap是线程非安全的,jdk1.7多线程情况下,会出现get()方法死循环,是因为扩容时会调整节点的位置,以前是数组的元素,现在变成链表中的一个元素,节点A与节点B,以前AB都是数组索引位置上,A的下一个节点指向B,别一个线程进行扩容后,B经过哈希函数算出位置在A之后,这时A的下一个节点指向B,B的下一个节点是A,这时就造成了死循环。
jdk1.7与1.8中hashmap的区别 1 2 3 4 5 6 7 1. 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构; 2. jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容; 3. 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插; 4. jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀; 5. 扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前; 6. jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的; 7. 扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。
总结 获取hash值:
当key == null 时 hash值为0;
当key不为空时,hash值为 对象的hashcode 异或 对象的hashcode的高16位。
如果数组为空,调用扩容方法初始化。
算出存储数组位置 由(数组长度-1)& hash值
如果数组位置上是空才放上
如果数组位置上不为空
数组上的节点就是要修改的节点,走后续的修改
如果数组上的节点是树节点,走树的查找或插入或修改
如果不是树节点,就循环,到最后一个节点插入
如果循环次数超过8位,走树化,如果数组容量不足64,走扩容,如果满足则把链表转为红黑树
如果是修改节点的value,则修改并返回。
如果容量大于阈值则扩容。
扩容(resize)方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1 ; } else if (oldThr > 0 ) newCap = oldThr; else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0 ) { float ft = (float )newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ? (int )ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node [newCap]; table = newTab; if (oldTab != null ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) newTab[e.hash & (newCap - 1 )] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this , newTab, j, oldCap); else { Node<K,V> loHead = null , loTail = null ; Node<K,V> hiHead = null , hiTail = null ; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0 ) { if (loTail == null ) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null ) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
GET方法 1 2 3 4 public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final Node<K,V> getNode (int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1 ) & hash]) != null ) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null ) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null ); } } return null ; }