1.参考
jdk8中文文档:https://www.matools.com/api/java8
jdk8英文文档:https://docs.oracle.com/javase/8/docs/api/
https://snailclimb.gitee.io/javaguide
2.List
2.1Arraylist
Arrays.copyOf()方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
基本参数
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
// RandomAccess表明实现这个这个接口的 List 集合是支持快速随机访问的。在 `ArrayList` 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认大小时的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 保存数据的数组
transient Object[] elementData; // non-private to simplify nested class access
// 当前数组中元素的个数,默认0
private int size;
// 最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法
// 无参构造方法,赋值默认大小时的空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定初始大小initialCapacity
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 初始化大小为0时,赋值空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 包含指定集合的元素
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
// 如果是ArrayList类型的
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 不是的话,拷贝
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
elementData = EMPTY_ELEMENTDATA;
}
}
修改数组容量
// 修改数组容量大小为size,扩容之后没有使用的空间会被浪费
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
外部扩容
// 外部扩容,在add大量元素之前用ensureCapatity可以减少重新分配的次数
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
扩容
// 在添加前都会调用此方法确保容量足够
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 如果初始化的时候使用的是无参构造函数
// 第一次添加的时候需要设置初始化容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果添加元素后的容量比当前数组的容量大,就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容
private void grow(int minCapacity) {
// 旧容量
int oldCapacity = elementData.length;
// 新容量为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果容量扩为1.5倍后比所需的容量还小
if (newCapacity - minCapacity < 0)
// 则新容量为minCapacity
newCapacity = minCapacity;
// 如果新容量超出规定的最大容量大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 则限制最大的容量大小
newCapacity = hugeCapacity(minCapacity);
// 扩大数组容量并拷贝原数组内容
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 限制最大的容量大小
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
add
// 添加元素之前都会调用ensureCapacityInternal方法判断是否需要扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
get
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
小总结
-
源码中大量使用Arrays.copyOf()与System.arraycopy()方法来扩容和修改删除元素;
-
从源码中大量操作中判断了元素是否为null,可以知道,我们在使用的过程中可以设值为null;
-
虽然我们可以赋值元素为null,但在remove(),clear()源码中可以看到,删除元素也是将该元素设置null,所以在获取值的时候用了边界判断rangeCheck来取保获取的是未删除的元素;
-
扩容大小一定是1.5倍吗?如果原容量长度是单数的话,没有1.5倍,采用向下取整的方式计算;
-
为什么不是线程安全的?
-
在add操作中,会先进行扩容判断。场景:初始容量为10,size为9,即elementData有9个元素了,现在有2个线程都进行add操作,线程A进行扩容判断,发现9+1-10 = 0不需要扩容,同时线程B也发现不需要扩容,这时,线程A进行赋值然后size++,此时size为10了,线程B也来赋值,但是size=10会发生数组越界。
-
在赋值过程中,线程A和线程B读到的size都是一样的,赋值的时候都赋值在size位置上,但后续都进行size++操作,出现的问题,size位置的值错误,size+1位置没有值。
final List<Integer> list = new ArrayList<>() ; new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { list.add(i) ; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 1000; i < 2000; i++) { list.add(i) ; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); Thread.sleep(1000); for (int i = 0; i < list.size(); i++){ if ( i != list.get(i) ) { System.out.println(i + ":" + list.get(i)); } } 1:1000 2:1 3:1001 4:2 5:1002 6:3 7:1003 8:4 9:1004 Exception in thread "main" java.lang.NullPointerException at Java.Basis.CollectionDemo.ArrayListDemo.test(ArrayListDemo.java:85) at Java.Basis.CollectionDemo.ArrayListDemo.main(ArrayListDemo.java:14)
-
2.2LinkedList
基本参数
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, 7<E>, Cloneable, java.io.Serializable
// 链表长度
transient int size = 0;
// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// 节点类
private static class Node<E> {
// 元素
E item;
// 指向下一个节点
Node<E> next;
// 指向上一个节点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// 获取指定位置的节点
Node<E> node(int index) {
// 采用的是折半查找法
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
get
// 获取第一个元素
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
// 获取最后一个元素
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
// 获取指定位置的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
set
// 设置指定位置的内容
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
remove
// 删除第一个节点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 删除最后一个节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
// 删除指定元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
// 删除第一个节点
private E unlinkFirst(Node<E> f) {
// 获取第一个节点的元素
final E element = f.item;
// 获取下一个节点
final Node<E> next = f.next;
// 设置第一个节点的值和指向都为空
f.item = null;
f.next = null; // help GC
// 删除第一个节点后,first就为下一个节点
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
// 删除最后一个节点
private E unlinkLast(Node<E> l) {
// 获取最后一个节点的元素
final E element = l.item;
// 获取最后一个节点的前一个节点
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
// 删除最后一个节点后,last就为前一个节点
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
add
// 默认添加在最后
public boolean add(E e) {
linkLast(e);
return true;
}
// 添加元素为第一个
public void addFirst(E e) {
linkFirst(e);
}
// 添加元素为最后一个
public void addLast(E e) {
linkLast(e);
}
// 在指定的位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 头节点添加
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
// 尾节点添加
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
// 指定位置添加,succ为指定位置的节点,e为待添加的
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
peek
// 获取头元素
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
// 获取首个元素
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
// 获取最后一个元素
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
poll
// 获取并删除
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
小总结
-
LinkedList在添加删除操作上比ArrayList快,因为ArrayList添加删除操作需要用到扩容,Arrays.copyOf()和System.arraycopy方法,而LinkedList是链表操作,在查找方面比ArrayList更慢;
-
为什么不是线程安全的?
// 当多个线程同时进行时,会导致modCount != expectedModCount // modCount记录了LinkedList被修改的次数,当他与预期的modCount不一致会抛异常 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
2.3Vector
Vector是非常古老的类,是线程安全的。
为什么不使用了?虽然线程安全的,但效率低;
3.Map
3.1HashMap
基本参数
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// 默认初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 扩容因子
// 因子越大越紧密,越小越稀疏
// 太大导致查找元素效率低,太小导致数组的利用率低,0.75是比较好的一个临界值
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 桶中结点数超过这个数转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
// 桶中结点数小于这个数转换为链表
static final int UNTREEIFY_THRESHOLD = 6;
// 转换为红黑树对应的table最小长度即table.length,而不是size
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,容量总是2的幂
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
// 当前有多少个元素
transient int size;
// 计数器
transient int modCount;
// 临界值,当size超过临界值时,会进行扩容
int threshold;
// 加载因子
final float loadFactor;
static class Node<K,V> implements Map.Entry<K,V> {
// hash值,存放元素时确定存放的位置
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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
hash
// 获取key的hash,hash为key的高16位与低16位与的结果
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
计算扩容临界值
// 返回大于cap的2的整次幂
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;
}
构造方法
// 初始容量大小和装载因子
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);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final 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;
// 如果桶中没有存在的hash,就放在第一个
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果有存在的hash
else {
Node<K,V> e; K k;
// 如果桶中第一个hash相同,key也相同,也放第一个
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);
// 链表的结点超过8个就进行转换红黑树的操作
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// treeifyBin还要判断是否tab长度超过MIN_TREEIFY_CAPACITY,超过才转换
treeifyBin(tab, hash);
break;
}
// 如果有找到相同的key,就覆盖值
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果有相同的key,覆盖value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
// 如果是有相同的key,就没有扩容的操作判断,返回oldValue
return oldValue;
}
}
++modCount;
// 如果size达到了扩容的临界值
if (++size > threshold)
// 扩容
resize();
afterNodeInsertion(evict);
return null;
}
get
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 如果存在table中, (n-1) & hash 相当于 hash % (n-1)
// first为桶中第一个元素
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
// 如果桶中第一个元素对应key,就不用往链表或者红黑树中查了,就是第一个
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 如果第一个不是,是否存在hash相同,是否还有链表或者红黑树
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;
}
resize
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
// old容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// old扩容临界值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// old容量超出最大容量限制了,不再进行扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 否则扩容1倍 newCap = oldCap<<1
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 扩容临界值也为一倍
newThr = oldThr << 1; // double threshold
}
// 如果old容量<=0,就看old扩容临界值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 否则就全部设置默认值,当使用无参构造函数时才会出现
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"})
// 初始化新的tab
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 重新分配位置
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
// todo
}
}
return newTab;
}
小总结
-
在put的时候,key和value都可以为null,但不建议null,因为get的时候
return (e = getNode(hash(key), key)) == null ? null : e.value;
,如果返回的是null,就无法判断是put进去的还是本来就不存在; -
DEFAULT_LOAD_FACTOR扩容因子,扩容因子越接近1,存放就越密,查找也比较耗时,相当于空间快用完了才扩容,越接近0,存放的就比较稀疏,浪费空间,相当于还没存几个数就要扩容,所以0.75是比较合适的一个数;
-
转换为红黑树的临界值:桶中数量大于TREEIFY_THRESHOLD 8个,且table的长度不是size大于MIN_TREEIFY_CAPACITY 64才转换红黑树;
-
为什么table的容量总是2的幂?因为临界值的计算tableSizeFor()返回的总是2的整次幂,而且在resize()中扩容也是扩为原来的1倍,所以table的容量总是2的幂;
-
threshold扩容临界值=容量cap*扩容因子loadFactor;
-
(n - 1) & hash:就是计算在tab中的下标位置,相当于hash %(n - 1),与运算会更快;
-
为什么线程不安全?
//与Arraylist类似,在put过程中,如果桶中元素个数为0,线程A与线程B同时put操作,进行hash碰撞的时候(n - 1) & hash, //结果都为null,线程A和线程B都会把元素放在tab[i],出现覆盖值的问题; // 如果桶中没有存在的hash,就放在第一个 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
3.2LinkedHashMap
LinkedHashMap保存插入的顺序
3.3TreeMap
// 实现了Navigable
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
// Navigable 继承了 SortedMap
public interface NavigableMap<K,V> extends SortedMap<K,V> {
3.4HashTable
HashTable在每个操作上加了synchronized来保证线程安全,每次的操作都是锁了整个Table,效率非常低。
与HashMap的不同
// 初始容量为11,HashMap为2的幂
public Hashtable() {
this(11, 0.75f);
}
// 底层结构
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
// 从这里可以看到HashTab的底层结构就是数组+链表
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
// 扩容大小
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// 扩容大小为2n+1,n为原来的大小,第一次扩容的话即你初始化的大小,没有2的幂的概念
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
// ......
}
// key value不能为空
public synchronized V put(K key, V value) {
// value不能为空
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
// 获取key的hashcode是Object的方法,为空会空指针异常
// HashMap获取key的hash为 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
小疑问
为什么在jdk8中put操作没有看到添加在链表尾的操作?
3.5ConcurrentHashMap
https://www.cnblogs.com/zhaojj/p/8942647.html
/*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
*当为负数时:-1代表正在初始化;
-N代表有N-1个线程正在进行扩容;
*当为0时:代表当时的table还没有被初始化;
*当为正数时:表示初始化或者下一次进行扩容的大小
*/
private transient volatile int sizeCtl;
// forwarding nodes的hash值
static final int MOVED = -1;
// 树根节点的hash值
static final int TREEBIN = -2 ;
// ReservationNode的hash值
static final int RESERVED = -3 ;
initTable
// 初始化table
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 死循环
while ((tab = table) == null || tab.length == 0) {
// 当小于0时,表示正在初始化或正在扩容,退让,依赖死循环继续初始化
if ((sc = sizeCtl) < 0)
Thread.yield();
// CAS:www.cnblogs.com/fengzheng/p/9018152.html
// CAS:https://www.jianshu.com/p/ab2c8fce878b
// CAS比较替换,乐观锁,如果内存值和旧值相同,则将内存值替换为待修改的值
// SIZECTL(Unsafe.objectFieldOffset获得,指内存偏移值)内存值与sc旧的预期值比较 -1为待修改的值
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// Thread.yield();的线程还会进来,所以要继续判断一次
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 扩容临界值,结果是n*0.75的最近的2次幂
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
put
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key和value都不能为空
if (key == null || value == null) throw new NullPointerException();
// 获取hash
// spread:return (h ^ (h >>> 16)) & HASH_BITS;
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// tab为空,初始化tab
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果当前索引值的位置即首节点是空,就插入
// tabAt:return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
// 其他线程对tab的改变在这里可见
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// casTabAt:return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
// cas放入
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
// 表示正在扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 说明不是首节点,锁f
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
get
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// hash为负表示在扩容或者是树节点
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
小总结
-
hash计算方式:
(h ^ (h >>> 16)) & 0x7fffffff:与hashmap计算hash基本一样,但多了一步& HASH_BITS,HASH_BITS是
0x7fffffff
,该步是为了消除最高位上的负符号 hash的负在ConcurrentHashMap中有特殊意义表示在扩容或者是树节点 -
jdk8中ConcurrentHahsMap的结构是node数组+链表/红黑树和HashMap结构一样,并发控制采用synchronized和CAS操作;
4.Set
Set与List的区别,Set是不可重复且无序的,List是可重复且有序的。
4.1HashSet
// HashSet的底层是基于HashMap实现的 ,同样是线程不安全的;
private transient HashMap<E,Object> map;
// map中的value值
private static final Object PRESENT = new Object();
// 添加的时候map的中value为统一的空Object
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
4.2LinkedHashSet
// 是HashSet的子类
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// 为什么是有序的?
// 见LinkedHashMap
4.3TreeSet
// TreeSet是实现NavigableSet
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
// NavigableSet是继承SortedSet
public interface NavigableSet<E> extends SortedSet<E> {
// TreeSet的用的Map是NavigableMap
private transient NavigableMap<E,Object> m;
// NavigableMap是继承SortedMap
public interface NavigableMap<K,V> extends SortedMap<K,V> {
5.关于NavigableSet与NavigableMap接口
6.自定义排序规则Comparable与Comparator
List<Integer> list = new ArrayList<>() ;
Random random = new Random();
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(10));
}
list.stream().forEach((a) -> System.out.print(a + " "));
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println();
list.stream().forEach((a) -> System.out.print(a + " "));
实现Comparable接口重写compareTo方法