java集合笔记

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;
}

小总结

  1. 源码中大量使用Arrays.copyOf()与System.arraycopy()方法来扩容和修改删除元素;

  2. 从源码中大量操作中判断了元素是否为null,可以知道,我们在使用的过程中可以设值为null;

  3. 虽然我们可以赋值元素为null,但在remove(),clear()源码中可以看到,删除元素也是将该元素设置null,所以在获取值的时候用了边界判断rangeCheck来取保获取的是未删除的元素;

  4. 扩容大小一定是1.5倍吗?如果原容量长度是单数的话,没有1.5倍,采用向下取整的方式计算;

  5. 为什么不是线程安全的?

    1. 在add操作中,会先进行扩容判断。场景:初始容量为10,size为9,即elementData有9个元素了,现在有2个线程都进行add操作,线程A进行扩容判断,发现9+1-10 = 0不需要扩容,同时线程B也发现不需要扩容,这时,线程A进行赋值然后size++,此时size为10了,线程B也来赋值,但是size=10会发生数组越界。

    2. 在赋值过程中,线程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);
}

小总结

  1. LinkedList在添加删除操作上比ArrayList快,因为ArrayList添加删除操作需要用到扩容,Arrays.copyOf()和System.arraycopy方法,而LinkedList是链表操作,在查找方面比ArrayList更慢;

  2. 为什么不是线程安全的?

    // 当多个线程同时进行时,会导致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;
}

小总结

  1. 在put的时候,key和value都可以为null,但不建议null,因为get的时候return (e = getNode(hash(key), key)) == null ? null : e.value;,如果返回的是null,就无法判断是put进去的还是本来就不存在;

  2. DEFAULT_LOAD_FACTOR扩容因子,扩容因子越接近1,存放就越密,查找也比较耗时,相当于空间快用完了才扩容,越接近0,存放的就比较稀疏,浪费空间,相当于还没存几个数就要扩容,所以0.75是比较合适的一个数;

  3. 转换为红黑树的临界值:桶中数量大于TREEIFY_THRESHOLD 8个,且table的长度不是size大于MIN_TREEIFY_CAPACITY 64才转换红黑树;

  4. 为什么table的容量总是2的幂?因为临界值的计算tableSizeFor()返回的总是2的整次幂,而且在resize()中扩容也是扩为原来的1倍,所以table的容量总是2的幂;

  5. threshold扩容临界值=容量cap*扩容因子loadFactor;

  6. (n - 1) & hash:就是计算在tab中的下标位置,相当于hash %(n - 1),与运算会更快;

  7. 为什么线程不安全?

    //与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;
}

小总结

  1. hash计算方式:

    (h ^ (h >>> 16)) & 0x7fffffff:与hashmap计算hash基本一样,但多了一步& HASH_BITS,HASH_BITS是0x7fffffff,该步是为了消除最高位上的负符号 hash的负在ConcurrentHashMap中有特殊意义表示在扩容或者是树节点

  2. 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方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值