一、collection

  • Cloneable 可克隆
  • Serializable 可序列化
  • RandomAccess 为 List 提供快速访问功能,为空接口,只是一个可以快速访问的标识,即通过序号获取元素

容器 - 图1

1. List

基本介绍

ArrayList、Vector、LinkedList继承AbstractList(实现List接口)

  • ArrayList和Vector使用数组实现,区别是前者不是线程同步,线程不安全,后者线程安全,且Vector扩容时翻倍size/创建对象时设置扩容量,ArrayList则扩容一半
  • LinkedList使用循环双向链表数据结构

ArrayList

特点

  • 维护一个数组,数组是拥有连续存储地址的存储块
  • 查询修改效率高,插入删除较低
  • 遍历ArrayList删除元素:使用iterator迭代器
  • 线程不安全

1) 成员变量

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  3. {
  4. private static final long serialVersionUID = 8683452581122892189L;
  5. /**
  6. * 默认初始化容量
  7. */
  8. private static final int DEFAULT_CAPACITY = 10;
  9. /**
  10. * 初始化时若容量为0,先不创建数组,第一次进行add操作才创建数组
  11. */
  12. private static final Object[] EMPTY_ELEMENTDATA = {};
  13. /**
  14. * 主要用于使用无参构造器创建的对象(没有指定初始容量的对象),在第一次添加元素时,扩容到初始变量10
  15. */
  16. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  17. /**
  18. * 用于存储元素的数组,数组的长度就是容器的容量
  19. * 当第一次添加元素并且满足elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,数组会扩容到 * 长度为DEFAULT_CAPACITY(10)
  20. * 非私有便于嵌套类访问
  21. */
  22. transient Object[] elementData;
  23. /**
  24. * 数组储存的元素的个数,不等于容器容量
  25. */
  26. private int size;
  27. /**
  28. * Integer的最大值减去8 = 2147483647 - 8 = 2147483639
  29. * 这是数组能够申请最大的大小,如果试图申请比这个还要大的大小,
  30. * 就会超过VM虚拟机的限制而抛出内存溢出的错误OutOfMemoryError
  31. */
  32. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

transient:

2) 构造方法

  • 传入初始容量
    • 容量为0:elementData = EMPTY_ELEMENTDATA;
    • 容量大于0:elementData = new Object[initialCapacity];
    • 容量参数不合法,抛出异常
  1. /**
  2. * 初始化容量
  3. *
  4. * @param initialCapacity the initial capacity of the list
  5. * @throws IllegalArgumentException if the specified initial capacity
  6. * is negative
  7. */
  8. public ArrayList(int initialCapacity) {
  9. if (initialCapacity > 0) {
  10. this.elementData = new Object[initialCapacity];
  11. } else if (initialCapacity == 0) {
  12. this.elementData = EMPTY_ELEMENTDATA;
  13. } else {
  14. throw new IllegalArgumentException("Illegal Capacity: "+
  15. initialCapacity);
  16. }
  17. }
  • 无参: `elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;````java /**

    • Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } ```
  • 传入容器

    1. 将容器转化为数组,赋值给elementData 传入容器前做非空判断
    2. 获取数组elementData长度
      • 长度为0: elementData = EMPTY_ELEMENTDATA;
      • 长度不为0,确保elementData是储存object的数组
        1. public ArrayList(Collection<? extends E> c) {
        2. //确保容器c不能为null,否则会抛出异常
        3. elementData = c.toArray();
        4. if ((size = elementData.length) != 0) {
        5. //c.toArray()可能出错返回的不是Object,当返回的不是object时,就将elementData转化为储存object的数组
        6. if (elementData.getClass() != Object[].class)
        7. elementData = Arrays.copyOf(elementData, size, Object[].class);
        8. } else {
        9. // replace with empty array.
        10. this.elementData = EMPTY_ELEMENTDATA;
        11. }
        12. }
  1. // Collection.toArray调用的方法
  2. public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
  3. @SuppressWarnings("unchecked")
  4. //根据class的类型来决定是new 还是反射去构造一个泛型数组
  5. T[] copy = ((Object)newType == (Object)Object[].class)
  6. ? (T[]) new Object[newLength]
  7. : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  8. //利用native函数,批量赋值元素至新数组中。
  9. System.arraycopy(original, 0, copy, 0,
  10. Math.min(original.length, newLength));
  11. return copy;
  12. }
  1. // 第一个参数:源数组
  2. // 第二个参数:开始复制的起始位置
  3. // 第三个参数:目标数组
  4. // 第四个参数:复制到的目标数组的起始位置
  5. // 第五个参数:复制的元素的长度
  6. System.arraycopy(original, 0, copy, 0,
  7. Math.min(original.length, newLength));
  • new ArrayList()时,底层创建空数组,只有第一次add的时候才重新初始化数组,容量默认为10```java private void grow(int minCapacity) {
    1. // overflow-conscious code
    2. int oldCapacity = elementData.length;
    //扩容1.5倍 等同于newCapacity = oldCapacity + (oldCapacity / 2)
    1. int newCapacity = oldCapacity + (oldCapacity >> 1);
    2. if (newCapacity - minCapacity < 0)
    3. newCapacity = minCapacity;
    4. if (newCapacity - MAX_ARRAY_SIZE > 0)
    5. newCapacity = hugeCapacity(minCapacity);
    6. elementData = Arrays.copyOf(elementData, newCapacity);
    } ```

3) 增加

  • 添加单个
  1. 确定底层elementData的容量
    1. 计算容器需要的容量
    2. 比较需要的容量和当前容量,看是否需要扩容(如果需要就进行扩容),并将操作数加一

扩容:

  • 计算新容量: 1.5倍—> 不够,则为需要的容量 —> 新容量超出虚拟机允许值,重新计算
  • 复制原来的数组到新的数组
    1. 元素插入结尾,element存储个数加一
  1. public boolean add(E e) {
  2. //确定内部容量
  3. ensureCapacityInternal(size + 1);
  4. //元素插入尾端,再将size加一
  5. //没有判断是否为null,所以可以存储null
  6. elementData[size++] = e;
  7. return true;
  8. }
  9. private void ensureCapacityInternal(int minCapacity) {
  10. //先调用calculateCapacity计算容器需要的容量,再调用ensureExplicitCapacity看是否需要扩容,并执行操作数加一
  11. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  12. }
  13. private static int calculateCapacity(Object[] elementData, int minCapacity) {
  14. //如果对象调用无参构造器创建,并且是第一次执行add操作,就有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  15. //此时容器需要的容量是默认的10
  16. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  17. return Math.max(DEFAULT_CAPACITY, minCapacity);
  18. }
  19. //否则,容器需要的容量是已经储存的元素个数加一 因为minCapacity= size + 1;
  20. return minCapacity;
  21. }
  22. private void ensureExplicitCapacity(int minCapacity) {
  23. //操作数加一
  24. modCount++;
  25. // 如果当前数组的容量小于需要的容量,就对数组进行扩容
  26. if (minCapacity - elementData.length > 0)
  27. grow(minCapacity);
  28. }
  29. private void grow(int minCapacity) {
  30. int oldCapacity = elementData.length;
  31. //先设置新容量为原来容量的1.5倍
  32. int newCapacity = oldCapacity + (oldCapacity >> 1);
  33. //如果1.5倍的容量仍然小于需要的容量,设置新容量为需要的容量
  34. if (newCapacity - minCapacity < 0)
  35. newCapacity = minCapacity;
  36. //当新容量超过虚拟机允许的大小,会重新计算合适的新容量
  37. if (newCapacity - MAX_ARRAY_SIZE > 0)
  38. newCapacity = hugeCapacity(minCapacity);
  39. // 将原来的数组复制到新的数组,并赋值给elementData
  40. elementData = Arrays.copyOf(elementData, newCapacity);
  41. }
  42. private static int hugeCapacity(int minCapacity) {
  43. //如果所需容量小于0,抛异常
  44. if (minCapacity < 0)
  45. throw new OutOfMemoryError();
  46. //如果所需容量大于虚拟机允许值,返回整数最大值
  47. return (minCapacity > MAX_ARRAY_SIZE) ?
  48. Integer.MAX_VALUE :
  49. MAX_ARRAY_SIZE;
  50. }
  • 批量添加```java public boolean addAll(Collection<? extends E> c) {
    1. //需要确保传入的集合不能为null
    2. Object[] a = c.toArray();
    3. int numNew = a.length;
    4. //确认新的容量
    5. ensureCapacityInternal(size + numNew);
    6. System.arraycopy(a, 0, elementData, size, numNew);
    7. size += numNew;
    8. return numNew != 0;
    }

public boolean addAll(int index, Collection<? extends E> c) { //检查是否越界 rangeCheckForAdd(index);

  1. Object[] a = c.toArray();
  2. int numNew = a.length;
  3. //确认新容量
  4. ensureCapacityInternal(size + numNew);
  5. int numMoved = size - index;
  6. //如果移动数大于0,复制需要移动的元素到尾端
  7. if (numMoved > 0)
  8. System.arraycopy(elementData, index, elementData, index + numNew,
  9. numMoved);

//将添加的元素复制到数组相应的位置 System.arraycopy(a, 0, elementData, index, numNew); //储存元素数目增加 size += numNew; //传入的集合长度为0,则添加失败 return numNew != 0; }

  1. <a name="qL9HW"></a>
  2. #### 4) 删除
  3. - 删除特定对象
  4. ```java
  5. public boolean remove(Object o) {
  6. if (o == null) {
  7. for (int index = 0; index < size; index++)
  8. if (elementData[index] == null) {
  9. fastRemove(index);
  10. return true;
  11. }
  12. } else {
  13. for (int index = 0; index < size; index++)
  14. if (o.equals(elementData[index])) {
  15. fastRemove(index);
  16. return true;
  17. }
  18. }
  19. return false;
  20. }
  • 删除指定位置的元素```java public E remove(int index) { //位置大于size(不是容量),抛异常

    1. rangeCheck(index);
    2. modCount++;

    //获取删除的元素,用于返回

    1. E oldValue = elementData(index);

    //计算需要移动的个数

    1. int numMoved = size - index - 1;
    2. if (numMoved > 0)
    3. //复制删除位置后面的元素,从删除的位置开始覆盖,此时 最后一个元素和倒数第二个元素相同,即elementData[size-1] = elementDAta[size - 2]
    4. System.arraycopy(elementData, index+1, elementData, index,
    5. numMoved);

    //将尾端元素置为null,给gc处理

    1. elementData[--size] = null;

    //返回删除的元素

    1. return oldValue;

    } ```

  • 删除所有```java public void clear() {

    1. modCount++;
    2. // clear to let GC do its work
    3. for (int i = 0; i < size; i++)
    4. elementData[i] = null;
    5. size = 0;

    } ```

  • 批量删除

    • 会删除原集合和传入的集合相同的元素 ```java public boolean removeAll(Collection<?> c) { //判空 Objects.requireNonNull(c); return batchRemove(c, false); }

private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; //r表示遍历原数组的当前下标, w表示批量删除后,数组剩下的元素个数 int r = 0, w = 0; boolean modified = false; try { //遍历数组,比较原数组和c的元素,删去所有相同的元素,将原数组有的而c中没有的元素放回原来的数组 for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { //如果遍历出错,则r != size为true // 此时elementData[r]以及之后的元素都没有遍历,没有处理,而elementData[0] ~ elementData[w-1] 则是目前为止已经处理过了的,不需要删除的元素 // 所以将elementData[r]以及之后的元素复制移动到elementData[w] if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } // 如果原数组有元素删除,则w != size为true // 此时 elementData[0] ~ elementData[w-1]是处理后最终的无需删除的元素 if (w != size) { // 将elementData[w]以及之后的元素置为null,交给gc处理 for (int i = w; i < size; i++) elementData[i] = null; //操作数加上 删除的元素个数 modCount += size - w; size = w; modified = true; } } return modified; }

  1. <a name="MvCuS"></a>
  2. #### 5) 改
  3. - 不修改modCount
  4. - 相对增删更为高效
  5. ```java
  6. public E set(int index, E element) {
  7. rangeCheck(index);
  8. E oldValue = elementData(index);
  9. elementData[index] = element;
  10. return oldValue;
  11. }

6) 查

  • 不修改modCount
  • 相对增删高效
  1. public E get(int index) {
  2. rangeCheck(index);
  3. return elementData(index);
  4. }
  5. @SuppressWarnings("unchecked")
  6. E elementData(int index) {
  7. return (E) elementData[index];
  8. }

LinkedList

基本介绍

  • 实现接口: List、Deque可以作为双端队列、Cloneable、Serializeble
  • 未实现RandomAcess,随机访问元素较慢
  • 底层是链表,增删移动指针,效率较高,无需扩容,空间效率高
  • 时间效率低,会根据index判断目标Node在前半段还是后半段,然后决定是顺序还是逆序查询以提升时间效率。不过随着n的增大,总体时间效率依然很低。
  • 增删修改modCount

1) 成员变量

  1. transient int size = 0;
  2. //头节点
  3. //(first == null && last == null) ||(first.prev == null && first.item != null) 始终是正确的
  4. //也就是,头节点有两种情况
  5. //第一,链表未储存元素,头节点为null
  6. //第二,链表有储存元素,头节点储存的内容不为null,其指向的前结点一定为null
  7. transient Node<E> first;
  8. //尾结点
  9. //(first == null && last == null) || (last.next == null && last.item != null)始终是正确的
  10. //第一,链表未储存元素,尾节点为null
  11. //第二,链表有储存元素,尾节点储存的内容不为null,其指向的后结点一定为null
  12. transient Node<E> last;

2)构造方法

  • 无参java public LinkedList() { }

  • 有参

    1. 确认插入结点的前一个结点和后一个结点
    2. 新建结点(此时已经建立了新结点单方面对prev的联系)
    3. 建立prev与新结点的关系,并使prev指向新结点
    4. 所有结点创建后,再建立最后一个插入的结点与succ双方的关系 ```java public LinkedList(Collection<? extends E> c) { this(); addAll(c); }

public boolean addAll(Collection<? extends E> c) { //从尾端开始插入元素 return addAll(size, c); }

/*

  • index为插入的起始位置 */ public boolean addAll(int index, Collection<? extends E> c) { //检查越界

    1. checkPositionIndex(index);

    //与ArrayList一样,在传入集合进行初始化之前,要判断是否为null

    1. Object[] a = c.toArray();

    //添加的元素个数

    1. int numNew = a.length;
    2. if (numNew == 0)
    3. return false;

    //初始的pred为插入的第一个结点的first结点,会随着元素的插入不断后移,即 index.pre = pred //succ为插入的最后一个结点指向的next结点,即 lastIndex.next = succ

    1. Node<E> pred, succ;

    //如果从尾端插入 //插入的最后一个的结点是链表的尾结点,而链表尾结点必有 last.next= null,则succ = null //第一个插入的结点的前一个结点是尾结点,即 pred = last

    1. if (index == size) {
    2. succ = null;
    3. pred = last;
    4. } else {
    5. //succ为插入位置的next结点
    6. succ = node(index);
    7. pred = succ.prev;
    8. }

    //遍历集合,将其插入链表相应的位置

    1. for (Object o : a) {
    2. @SuppressWarnings("unchecked") E e = (E) o;
    3. //新建结点储存元素, 前面指向pred结点,后面指向null
    4. Node<E> newNode = new Node<>(pred, e, null);
    5. if (pred == null)
    6. //从头开始插入结点时,新建的第一个结点就是头结点
    7. first = newNode;
    8. else
    9. //使 pred后面指向新结点
    10. pred.next = newNode;
    11. // 插入完成后,pred总是指向最后一个插入的结点
    12. pred = newNode;
    13. }

    //succ == null 即尾端插入,此时链表的尾结点是最后一个新结点

    1. if (succ == null) {
    2. last = pred;
    3. } else {
    4. pred.next = succ;
    5. succ.prev = pred;
    6. }
    7. size += numNew;
    8. modCount++;
    9. return true;

    }

    1. ```java
    2. //不属于尾端插入结点时,返回index指向的结点
    3. Node<E> node(int index) {
    4. //优化查询,如果插入位置在链表的前半部分,从前往后找
    5. //插入位置在链表的后半部分,从后往前找
    6. if (index < (size >> 1)) {
    7. Node<E> x = first;
    8. for (int i = 0; i < index; i++)
    9. x = x.next;
    10. return x;
    11. } else {
    12. Node<E> x = last;
    13. for (int i = size - 1; i > index; i--)
    14. x = x.prev;
    15. return x;
    16. }
    17. }

3) 增

  1. void linkLast(E e) {
  2. final Node<E> l = last;
  3. final Node<E> newNode = new Node<>(l, e, null);
  4. last = newNode;
  5. //尾结点为空,表示原链表无储存元素
  6. if (l == null)
  7. first = newNode;
  8. else
  9. l.next = newNode;
  10. size++;
  11. modCount++;
  12. }
  1. public void add(int index, E element) {
  2. checkPositionIndex(index);
  3. //尾端插入
  4. if (index == size)
  5. linkLast(element);
  6. else
  7. linkBefore(element, node(index));
  8. }
  9. void linkBefore(E e, Node<E> succ) {
  10. //不是尾端插入, succ一定不为null
  11. final Node<E> pred = succ.prev;
  12. //新建结点已经单方面建立起与pred、succ的关系
  13. final Node<E> newNode = new Node<>(pred, e, succ);
  14. //建立succ与newNode的关系
  15. succ.prev = newNode;
  16. //建立pred与newNode的关系
  17. if (pred == null)
  18. first = newNode;
  19. else
  20. pred.next = newNode;
  21. size++;
  22. modCount++;
  23. }

4) 删

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (Node<E> x = first; x != null; x = x.next) {
  4. if (x.item == null) {
  5. unlink(x);
  6. return true;
  7. }
  8. }
  9. } else {
  10. for (Node<E> x = first; x != null; x = x.next) {
  11. if (o.equals(x.item)) {
  12. unlink(x);
  13. return true;
  14. }
  15. }
  16. }
  17. return false;
  18. }
  19. /**
  20. *传入要删除的结点
  21. */
  22. E unlink(Node<E> x) {
  23. // assert x != null;
  24. final E element = x.item;
  25. final Node<E> next = x.next;
  26. final Node<E> prev = x.prev;
  27. if (prev == null) {
  28. first = next;
  29. } else {
  30. prev.next = next;
  31. x.prev = null;
  32. }
  33. if (next == null) {
  34. last = prev;
  35. } else {
  36. next.prev = prev;
  37. x.next = null;
  38. }
  39. x.item = null;
  40. size--;
  41. modCount++;
  42. return element;
  43. }
public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
//清空所有    
public void clear() {
        // Clearing all of the links between nodes is "unnecessary", but:
        // - helps a generational GC if the discarded nodes inhabit
        //   more than one generation
        // - is sure to free memory even if there is a reachable Iterator
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }

ArrayList与LinkedList区别

1) 增加元素到尾端

  • ArrayList:
    • 容量够大时,直接使用add()操作,效率高
    • 容量需求超过当前数组大小时,会调用System.arraycopy()方法扩容进行数组复制,效率也是挺高的> System.arraycopy 为 Native 层的方法,可以高效复制数组元素。
  • LinkedList:不需要维护容量大小,但每次增加元素都需要新建结点,性能较低

2)插入元素到列表任意位置

  • ArrayList:数组是一块连续的内存空间,每次插入元素都会进行一次数组复制,大量复制导致系统性能下降
  • LinkedList:此时任意位置插入与在尾端增加是一样的
  • 这种情况下选择LinkedList更好

3)删除任意位置元素

  • ArrayList:每次remove()都需要进行数组重组,删除的元素位置越靠前开销越大
  • LinkedList:首先通过循环找到要删除的元素。效率很低
    • 删除的元素位置处于List前半段就从前往后找;
    • 处于后半段,从后往前找
    • 处于中间,会遍历完半个List

4)容量参数

  • 是ArrayList和Vector等基于数组的List的特有性能参数,表示初始数组的大小ArrayList默认初始大小10
  • 合理设置容量参数,能减少数组扩容,提升系统性能

5)遍历数组

使用for循环随机遍历列表,ArrayList速度很快,但LinkedList则表现不佳,对LinkedList随机访问,总会进行一次列表的遍历操作

2. Map

容器 - 图2

HashMap

1.基本介绍

实现java.util.Map接口的实现类主要有4个

  • HashMap:具有很快的访问速度,遍历顺序不确定,线程不安全
  • LinkedHashMap:是HashMap的一个子类,保存了记录的插入顺序
  • HashTable:遗留类,功能与HahMap相似,但是是线程安全的,不推荐使用。

    ConcurrentHashMap使用分段锁,并发性能大于HashTable,需要线程安全就使用ConcurrentHashMap

  • TreeMap:实现SortedMap接口,可以根据key值排序(默认升序),可以指定排序的比较器。

    key必须实现Comparable接口或者在构造TreeMap时传入自定义Comparator

注意:key最好是不可变对象,确保hashCode的值不会被改变

2.存储结构

数组+链表+红黑树(jdk 1.8新增)
微信图片_20191128132552.png

3.成员变量

//哈希表,存储键值对
transient Node<K,V>[] table;
//集合
transient Set<Map.Entry<K,V>> entrySet;
//当前实际存储容量
transient int size;
//操作数
transient int modCount;
//扩容阈值,超过这个数就扩容
int threshold;
//负载因子,默认0.75
final float loadFactor;
  • 容量length:即哈希表table的长度length,默认16

    这里要求桶的容量必须是2^n(是合数),是非常规设计,是为了取模及扩容时候优化 常规设计:桶的大小设计为素数(素数引起冲突的概率要小于合数引起的)

  • 负载因子loadFactor:决定扩容阈值

    0.75默认值是对时间和空间效率的平衡选择,无必要不修改

    • 内存空间很多,对时间效率要求高,可以降低负载因子的值
    • 内存空间紧张,对时间效率要求不高,可以增加负载因子的值,可以大于1
  • 扩容阈值:表示当前表的阈值,当所需容量超过阈值时,进行2倍扩容(保证2的次幂) threshold = loadFactor * length

4. 构造方法

  • 无参构造器

    public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
      }
    
  • 有参构造器

 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;
     //在这里并没有根据用户输入的初始容量来初始化table,只是先用阈值记录
     //第一次执行put操作时再初始化数组
        this.threshold = tableSizeFor(initialCapacity);
    }

static final int tableSizeFor(int cap) {
       //用于找出大于等于cap的最小2的幂来作为容量
       //优化了jdk1.7通过循环位移的算法,使用了很巧妙的位运算减少了大容量时的很多次循环次数
        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;
    }

5. 确定哈希数组索引位置

//jdk1.8
static final int hash(Object key) {
        int h;
    //让高位也参与运算,减少冲突,
    //这里运算结束后还没有得到数组索引位置,还要在put方法中执行与运算获得下标
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//jdk1.7
final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

//该方法是jdk1.7才有的,jdk1.8中没有单独写成一个方法,而是放在put方法中
static int indexFor(int h, int length) {
     //取模操作做了优化
        return h & (length-1); 
    }
  • 获取数组下标三步:取key的hashCode值—>高位参与运算(一次与+一次异或)—>取模运算

微信图片_20191129093906.png

由于哈希表的容量length必须是2的n次幂,故,length-1的二进制必是000……111…,这样和哈希值进行与运算时,就能得到哈希值的后n位的值,也是h%length的值

6.增

微信图片_20191128171721.png

 public V put(K key, V value) {
      // 对key的hashCode()做hash
      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;
      // 步骤①:tab为空则创建
     if ((tab = table) == null || (n = tab.length) == 0)
         n = (tab = resize()).length;
     // 步骤②:计算index,并对null做处理 
     if ((p = tab[i = (n - 1) & hash]) == null) 
         tab[i] = newNode(hash, key, value, null);
     else {
         Node<K,V> e; K k;
         // 步骤③:节点key存在,直接覆盖value
         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, hash);
                     break;
                 }
                    // key已经存在直接覆盖value
                 if (e.hash == hash &&
                     ((k = e.key) == key || (key != null && key.equals(k))))                                       break;
                 p = e;
             }
         }         
         if (e != null) { // existing mapping for key
             V oldValue = e.value;
             if (!onlyIfAbsent || oldValue == null)
                 e.value = value;
             afterNodeAccess(e);
             return oldValue;
         }
     }

     ++modCount;
     // 步骤⑥:超过最大容量 就扩容
     if (++size > threshold)
         resize();
     afterNodeInsertion(evict);
     return null;
 }
  • 扩容
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
    //第一次执行时,旧容量为0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
    //用来记录新容量,新阈值
        int newCap, newThr = 0;
    //不是第一次执行,table已经初始化
        if (oldCap > 0) {
            //当旧容量超过最大值1<<30,直接将 Integer.MAX_VALUE 作为阈值,这样以后都不会扩容
            //旧容量小于最大值时,直接将旧容量翻倍获得新容量newCap
            //如果newCap<最大容量并且,oldCap大于等于默认容量,旧阈值翻倍获得新阈值
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; 
        }
    //第一次执行put操作时
    //如果阈值大于0(意味着使用有参构造器传入的初始容量大于0),则新容量为当前阈值,即传入的初始化容量
    //否则,新容量为默认容量,阈值为 加载因子*容量
        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 { 
                        //loHead指向最终放在原索引位置的链表的表头,loTail指向表尾
                        Node<K,V> loHead = null, loTail = null;
                        //hiHead指向最终放在原索引+oldCap位置的链表的表头,hiTail指向表尾
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //判断之前的哈希值的高一位是0还是1
                            //是0则放在原索引位置的链表表尾
                            //是1则放在原索引+oldCap位置的链表表尾
                            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;
    }
  • resize方法中,jdk1.8进行了优化
    • 关于扩容后hash值的计算

      1.7中,不断rehash计算新的位置 1.8中只需要判断高一位来确定是原索引还是原索引旧容量 省去重新计算hash值时间,并且高一位是0还是1是随机的,由本身的hash值决定,也就使得之前冲突的值尽可能分散均匀

1574932023508.png

  • 关于链表插入方式:

    1.7中采用头插入,容易出现倒序,且并发时出现循环链表 1.8中使用尾插入法

image-20200815222333796.png

LinkedHashMap

  • HashMap + LinkedList
  • map的非同步实现,同时可以用于实现LRU

1. 存储结构

static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
 }

JDK1.7使用环形双向链表,只有头结点

2. 成员变量

    transient LinkedHashMap.Entry<K,V> head;
    transient LinkedHashMap.Entry<K,V> tail;
    //true表示按照访问顺序迭代,false按插入顺内迭代
    final boolean accessOrder;

3. 构造方法

public LinkedHashMap() {
        super();
    //是否基于访问顺序排序  有两种,插入排序和访问排序
        accessOrder = false;
    }

image.png

4. put方法

  • HashMap类似,重写newNode方法 ```java Node newNode(int hash, K key, V value, Node e) { //转换节点
      LinkedHashMap.Entry<K,V> p =
          new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    
    //添加到链表尾部
      linkNodeLast(p);
      return p;
    
    }

private void linkNodeLast(LinkedHashMap.Entry p) { LinkedHashMap.Entry last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }

<a name="3JNzj"></a>
#### 5.访问顺序

- HashMap定义三个回调方法,由LinkedHashMap重写实现,在调用父类的put、remove方法时。会调用LinkedHashMap的这些方法
```java
//处理元素被访问后的情况    
void afterNodeAccess(Node<K,V> p) { }
//处理元素插入后的情况
void afterNodeInsertion(boolean evict) { }
//处理元素被删除后的情况
void afterNodeRemoval(Node<K,V> p) { }
  • afterNodeRemoval:删除链表中的节点

    void afterNodeRemoval(Node<K,V> e) { // unlink
          LinkedHashMap.Entry<K,V> p =
              (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
          p.before = p.after = null;
          if (b == null)
              head = a;
          else
              b.after = a;
          if (a == null)
              tail = b;
          else
              a.before = b;
      }
    
  • afterNodeInsertion:插入节点后,判断是否需要删除头结点,即最久未访问元素 ```java void afterNodeInsertion(boolean evict) { // possibly remove eldest

      LinkedHashMap.Entry<K,V> first;
    

    //判断是否要删除头结点,默认返回false //如果要实现LRU,就需要重写removeEldestEntry,使得在链表超过某个长度时,返回true

      if (evict && (first = head) != null && removeEldestEntry(first)) {
          K key = first.key;
          removeNode(hash(key), key, null, false, true);
      }
    

    }

//默认返回false protected boolean removeEldestEntry(Map.Entry eldest) { return false; }


- afterNodeAccess:访问节点后,如果是基于访问顺序迭代的,就将该节点移到链表的尾端
```java
void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

6. 遍历

abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            //从头结点开始
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            //并发不安全
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

7. 实现LUR

class LRUCache extends LinkedHashMap<Integer, Integer>{
    private int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }


    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; 
    }
}

容器排序

  • 调用Collentions.sort,实际调用Arrays.sort方法

image.png

  • Arrays.sort

    public static <T> void sort(T[] a, Comparator<? super T> c) {
          if (c == null) {
              sort(a);
          } else {
              //请求传统归并排序,jdk5就是调用该方法实现
              if (LegacyMergeSort.userRequested)
                  legacyMergeSort(a, c);
              else
                  //改进后的归并排序,对归并排序在已经反向排好序的输入O(n^2)的问题做了优化
                  //能够对已经正向排好序的输入减少回溯,对一会升序一会降序的输入处理较好
                  TimSort.sort(a, 0, a.length, c, null, 0, 0);
          }
      }
    
  • TimSort.sort

    • 排序长度小于32,进行不必合并的mini-TimeSort
    • 否则,执行以下

      • 将待排序数组分为minRun大小的子数组
        • 如果数组大小为2的N次幂,则返回16(MIN_MERGE / 2)
        • 其他情况下,逐位向右位移(即除以2),直到找到介于16和32间的一个数
      • 对各子数组进行min-Timesort并记录对应的信息(下标+长度)
      • 检查是否需要合并,是就进行合并
      • 最后,对还未合并的数组进行合并 ```java static void sort(T[] a, int lo, int hi, Comparator<? super T> c,

                     T[] work, int workBase, int workLen) {
        

        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining = hi - lo; //排序长度小于2,不必排序 if (nRemaining < 2)

        return;  
        

        // 排序长度小于MIN_MERGE(32)时,无需合并,执行mini-TimeSort即可 if (nRemaining < MIN_MERGE) {

        int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
        binarySort(a, lo, hi, lo + initRunLen, c);
        return;
        

        }

        TimSort ts = new TimSort<>(a, c, work, workBase, workLen); //子数组的长度 int minRun = minRunLength(nRemaining); do { //找到初始的一组升序数列(降序的进行翻转)

        int runLen = countRunAndMakeAscending(a, lo, hi, c);
        
        // 如果该序列长度小于minRun,将后续补足,对[lo:lo+force]进行mini-Timesort
        //得到[lo:lo+force]是有序的
        if (runLen < minRun) {
            //可能最后未排序的个数不足minRun
            int force = nRemaining <= minRun ? nRemaining : minRun;
            binarySort(a, lo, lo + force, lo + runLen, c);
            runLen = force;
        }
        
        //入栈,记录排好序各个子数组的起始以及大小,为后续merge准备
        ts.pushRun(lo, runLen);
        //合并检查,相邻区间能合并的先合并
        ts.mergeCollapse();
        
        // 下一个子数组的起始下标
        lo += runLen;
        //还未排好序的个数
        nRemaining -= runLen;
        

        } while (nRemaining != 0);

        assert lo == hi; //还有未meage的区块,就进行合并 ts.mergeForceCollapse(); assert ts.stackSize == 1; }

//记录排序好的子数组块的起始下标及长度 private void pushRun(int runBase, int runLen) { this.runBase[stackSize] = runBase; this.runLen[stackSize] = runLen; stackSize++; }

<a name="8uepP"></a>
### `mini-TimeSort`过程
```java
if (nRemaining < MIN_MERGE) {
    //从数组开始找到连续升序或降序(进行翻转)的序列长度
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
    //二分查找将后续的数插入已经排序好的数组
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                    Comparator<? super T> c) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        //降序
        if (c.compare(a[runHi++], a[lo]) < 0) { 
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            //翻转
            reverseRange(a, lo, runHi);
        } else { 
            //升序
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }

//对a[start:hi]中的每个元素调用二分查找在[lo:start]中找到位置并插入
private static <T> void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator<? super T> c) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            T pivot = a[start];

            int left = lo;
            int right = start;
            assert left <= right;
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            int n = start - left;  
            // Switch is just an optimization for arraycopy in default case
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

合并检查过程

//假设X Y Z为3个相邻的区块
//若区块数为2:当x长度 <= y长度,合并x y
//区块数>=3, 当x长度 <= y + z,合并x,y,直到x > y + z以及 y > z
private void mergeCollapse() {
        while (stackSize > 1) {
            int n = stackSize - 2;
            if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
                if (runLen[n - 1] < runLen[n + 1])
                    n--;
                mergeAt(n);
            } else if (runLen[n] <= runLen[n + 1]) {
                //如果前一个区间的长度小于当前区间长度,就进行merge
                mergeAt(n);
            } else {
                break; // Invariant is established
            }
        }
    }
  • 合并过程mergeAt(i):合并i和i+1的数组

    • 前提:i的长度小于i+1的长度

      private void mergeAt(int i) {
         assert stackSize >= 2;
         assert i >= 0;
         assert i == stackSize - 2 || i == stackSize - 3;
      
         int base1 = runBase[i];
         int len1 = runLen[i];
         int base2 = runBase[i + 1];
         int len2 = runLen[i + 1];
         assert len1 > 0 && len2 > 0;
         assert base1 + len1 == base2;
      
        //更新合并后区间大小
         runLen[i] = len1 + len2;
         if (i == stackSize - 3) {
             runBase[i + 1] = runBase[i + 2];
             runLen[i + 1] = runLen[i + 2];
         }
         stackSize--;
      
        //找到区间2首(最小)元素在区间1的插入位置k,此时区间1[base1:k]就是合并后的最小元素数组,无需排序
         int k = gallopRight(a[base2], a, base1, len1, 0, c);
         assert k >= 0;
      //更新区间1未排序首元素下标
         base1 += k;
      //更新区间1未排序长度
         len1 -= k;
         if (len1 == 0)
             return;
      
         //找到区间1尾(最大)元素在区间2的插入位置k,此时区间2[k:结尾]就是合并后的最大元素数组,无需排序,len2表示base2-k
         len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c);
         assert len2 >= 0;
         if (len2 == 0)
             return;
      
      //到这,有a[base1]>a[base2]. 且a[base1+len1]<a[base2+len2]   
         if (len1 <= len2)
             mergeLo(base1, len1, base2, len2);
         else
             mergeHi(base1, len1, base2, len2);
      }
      

      image.png

迭代器

  • 迭代器提供了同一个接口遍历元素
  • Java中,Iterator为一个接口,提供迭代的基本规则;

    package java.util;
    public interface Iterator<E> {
      boolean hasNext();//判断是否存在下一个对象元素
    
      E next();//获取下一个元素
    
      void remove();//移除元素
    }
    
  • Java还提供一个Iterable接口,能返回一个迭代器。实现这个接口允许对象成为foreach语句的目标,就能通过foreach遍历底层序列

  • 快速失败机制:当遍历集合元素时,使用迭代器,在迭代遍历元素的过程中,如果集合的结构被改变,就会抛出异常ConcurrentModificationException防止继续遍历

    • 迭代器在执行操作之前都会判断expectedModCount和modCount是否相等,不等就会抛出异常
    • 只有调用Iterator的remove方法才不会造成快速失败
    • java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改 ```java //以ArrayList的迭代器为例 private class Itr implements Iterator { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such //expectedModCount在初始化前就是确定的 //当其他线程或自身执行了list.remove(),modcount就会改变,而expectedModCount不会变,就会造成错误 int expectedModCount = modCount;

      Itr() {}

      final void checkForComodification() {

         if (modCount != expectedModCount)
             throw new ConcurrentModificationException();
      

      }

      public boolean hasNext() {

         return cursor != size;
      

      }

      @SuppressWarnings(“unchecked”) public E next() {

         checkForComodification();
         int i = cursor;
         if (i >= size)
             throw new NoSuchElementException();
         Object[] elementData = ArrayList.this.elementData;
         if (i >= elementData.length)
             throw new ConcurrentModificationException();
         cursor = i + 1;
         return (E) elementData[lastRet = i];
      

      }

      public void remove() {

         if (lastRet < 0)
             throw new IllegalStateException();
         checkForComodification();
      
         try {
             ArrayList.this.remove(lastRet);
             cursor = lastRet;
             lastRet = -1;
             //更新expectedModCount的值
             expectedModCount = modCount;
         } catch (IndexOutOfBoundsException ex) {
             throw new ConcurrentModificationException();
         }
      

      }

```

  • 安全失败机制:遍历时不直接在集合内容上范文,而是先复制原有集合内容,在拷贝的集合上进行遍历
    • 缺点:无法访问到修改后的内容
    • java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改