1.Collection接口:单列数据,定义了存取一组对象的方法的集合
            1.List:元素有序,可重复,Vector,ArrayList,LinkedList
            2.Set:元素无序,不可重复,HashSet,LinkedHashSet,TreeSet
2.Map接口:双列数据,保存具有映射关系“key-value”的集合
Hashmap,LinkedHashMap,TreeMap,Hashtable,Properties

一、测试Collection中的方法

public class CollectionTest {
    @Test
    public void test1(){
        ArrayList<Object> coll = new ArrayList<>();
        coll.add("AA");
        coll.add(123);
        coll.add(new Date());

        System.out.println(coll.size());//3
        System.out.println(coll.isEmpty());//false
        System.out.println(coll.contains(123));//true,调用equals方法,类需重写该方法
        System.out.println(coll.hashCode());//1463284720

        //集合到数组
        Object[] arr = coll.toArray();
        for (int i = 0; i <arr.length ; i++) {
            System.out.println(arr[i]);
        }

        //数组到集合
        List<Object> list = Arrays.asList(arr);
        System.out.println(list.toString());  //[AA, 123, Fri Sep 04 11:11:26 CST 2020]
    }
}

      向Collection接口的实现类的对象中添加数据时,要求数据所在类重写equals方法,自定义类一定要重写equals方法。


二、使用Iterator遍历Collection集合

public class IteratorTest {
    @Test
    public void test1(){
        ArrayList<Object> coll = new ArrayList<>();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        System.out.println(coll.toString());
        Iterator<Object> iterator = coll.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

      如上代码所示,创建一个集合叫coll,里面有四个元素,然后利用iterator()方法创建了一个迭代器,可以遍历集合中的元素。遍历原理是:iterator对象的指针初始在集合的第一个元素的上方,然后调用hasNext()方法判断指针的下一个地址是否存在集合元素,存在的话,调用Next()方法将指针下移,并返回该地址的元素,因此完成集合元素的遍历。
      注意:每次集合对象调用iterator()方法得到一个全新的迭代器对象。


三、使用foreach遍历Collection集合

public class foreachTest {
    @Test
    public void test2(){
        ArrayList<Object> coll = new ArrayList<>();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        for(Object obj : coll){
            System.out.println(obj);
        }
    }
}


List接口——有序的、可重复的


四、List接口常用实现类的对比

      ArrayList、LinkedList、Vector三者的异同?
      同:三个类都是实现了List接口,存储有序、可重复的数据。
      异:ArrayList线程不安全,但是效率高;Vector线程安全,但是效率低;二者底层都是使用Object[ ] elementData存储。LinkedList底层使用双向链表存储,对于频繁的插入和删除操作,使用此类效率较高。


五、ArrayList源码分析

A、JDK7情况下:

1.ArrayList空参构造器

public ArrayList() {
        this(10);
    }

      new ArrayList()空参构造器在底层创建了长度为10的object[ ]数组 elementData。下面是add函数的源码:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

      首先判断集合的空间是否够(初始为10),如果够,就执行elementData[size++] = e操作,如果不够,执行grow(minCapacity)进行扩容,下面看grow函数源码:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //扩容为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

      一般情况下,如果超出集合的空间大小,将扩容为原大小的1.5倍,但是如果扩大1.5倍后还是不够,就把待存储数据加上原数据的总大小赋值给新的空间大小;如果新空间大小超过规定的MAX_ARRAY_SIZE,就把更大的一个值Integer.MAX_VALUE赋值给新空间大小。新空间大小确定后,再把原来的数据传到新空间,从而完成扩容操作。

2.ArrayList带参构造器

public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

      带参构造器直接把表示空间大小的数据传给构造器,然后底层创建一个相应大小的object[ ]数组。

B、JDK8情况下:

1.ArrayList空参构造器

1.private static final int DEFAULT_CAPACITY = 10;

2.private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

3. public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

      如上所示,默认容量大小DEFAULT_CAPACITY = 10,调用ArrayList空参构造器时,底层新建一个长度为0的object[]数组elementData,下面看add方法:

1. public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

2. private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

3.private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
4.  private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

      如上所示,当调用add方法时,首先跟jdk7一样判断空间是否够用,不同点是如果是首次添加元素,jdk8会先判断待添加的元素是否超过默认容量10,把较大的值赋给minCapacity。然后执行扩容操作,对于首次添加元素时,直接把newCapacity = minCapacity即可;如果不是首次添加元素,就把空间扩大为原来的1.5倍,这块与jdk7一致。总之jdk7与jdk8不同处在于首次添加元素初始化空间大小时,jdk8考虑了首次添加的元素可能超过默认容量大小10的情况。
      jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

2.ArrayList带参构造器

1.  private static final Object[] EMPTY_ELEMENTDATA = {};

2.  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


六、LinkedList源码分析

jdk8下:

      每个元素的内部结构:

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

      下面看add方法:

1. public boolean add(E e) {
        linkLast(e);
        return true;
    }
    
	2.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++;
    }

      如上代码所示,调用add方法后会调用linkLast方法,然后返回是否添加成功的布尔类型数据。在linkLast里,last表示目前链表结构里的最后一个数据。当添加一个元素时,将之前的最后一个数据作为该元素的prev属性,同时添加的数据的next属性设置为null,并将添加的元素设置为last。如果待添加的元素是首个元素时,就将该元素声明为first,如果不是,就把原最后一个数据(现倒数第二个数据)的next属性设置为刚添加的元素。


七、Vector源码分析

1.public Vector() {
        this(10);
    }
    
 public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
2.public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

      如上所示是Vector的空参构造器和带参构造器,空参构造器默认情况下底层创建一个容量大小为10的Object[]数组,带参构造器底层创建一个容量大小为参数大小的Object[]数组。

      下面看如何添加元素:

1.public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    
   2. private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

   3.private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

      注意:一般情况下Vector容量不够时扩容为原来的两倍


八、List,Set遍历方法总结

1.迭代器Iterator()遍历
2.foreach循环
3.普通的循环——get( int index)


九、区分remove(index)和remove(Object obj)

     remove(2)表示删除list集合里索引为2即第三个元素,如果想删除内容为2的元素,需要remove(new Integer(2))来实现。


Set接口——无序的、不可重复的


十、Set接口实现类的对比——HashSet,LinkedHashSet,TreeSet

1.HashSet:线程不安全,可以存储null值
     2.LinkedHashSet:底层使用链表存储,作为HashSet子类,遍历内部元素,可以按照添加的顺序遍历。
3.TreeSet:底层使用红黑树存储,可以按照添加对象的指定属性进行排序。


十一、分析Set如何实现无序性,不可重复性

     举例HashSet,存储的每个数据有它的哈希值。由hashcode()产生,哈希值决定了数据的存储位置,因此Set是无序的。基本数据类型(包装类)都重写了equals()和 hashcode()方法,如果自己写的类需要自己重写equals()和 hashcode()方法,这样如果在Set里添加这些类时,就不能够重复,如果该类未重写上述两个方法,添加时就不存在不可重复性。下面分析其原理:

     1.我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
     2.此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断数组此位置上是否已经元素:
          1.如果此位置上没其他元素,则元素a添加成功。 —>情况1
          2.如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值:
                    1.如果hash值不相同,则元素a添加成功。—>情况2
                    2.如果hash值相同,进而需要调用元素a所在类的equals()方法:
                          1. equals()返回true,元素a添加失败
                          2.equals()返回false,则元素a添加成功。—>情况3


注意点:
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下

HashSet底层:数组(初始容量为16)+链表的结构。(前提:jdk7)


十二、LinkedHashSet的无序性以及输出有序的分析

      LinkedHashSet作为HashSet的子类,添加元素时,元素的存放位置跟HashSet一样的规则,但是遍历输出时却是按照添加的先后顺序输出的,这与LinkedHashSet存储的每个数据都是一个链表结构有关。


十三、TreeSet

      1.向TreeSet中添加数据时,必须是相同的类的对象。

      2.输出TreeSet中数据默认按照从小到大输出,如果是自己定义的类,跟类中的equals方法有关。
      3.注意:TreeSet中不可重复性根据类的compareTo进行比较,而不是重写的equals方法,因此例如我们有个person类,一个是Tom,22岁,另一个是Tom,34岁,而compareTo比较的是二者的name,那么只能添加成功一个对象。因此解决方案要不是在compareTo方法中考虑到所有属性的比较,要不调用TreeSet带参构造器,参数是new comparator类型的,称为定制排序。


Map接口——双列数据


十四、Map接口的对比

1.HashMap:线程不安全,效率高,存储null的key和value值
      2.LinkedHashMap:按照添加的顺序遍历,每个元素有一对指针,便于遍历。
3.TreeMap(红黑树):根据key的自然排序或者自己定制排序来实现排序遍历
4.Hashtable:线程安全,效率低,不能存储null的key和value值

注意:Map中的key使用Set存储,保证不可重复性。value使用Collection存储,实现可重复性,但是不具有有序性。每个key-value使用一个Entry对象存储,Entry具有无序的,不可重复的特点。key所在的类需要重写equals和hashcode方法,value所在的类需要重写equals方法。如果是TreeMap,则需要重写compareTo方法或者定制排序。


十五、HashMap的底层实现原理

      HashMap底层实现与HashSet类似,首先底层创建一个长度为16的Entry[ ]数组,然后执行put(key1,value1)往里面存放数据。过程如下:
      调用key1(无序的,不可重复的)所在类的hashcode()方法,计算key1的哈希值,此哈希值经过某种算法(散列函数)计算得到在entry[ ]数组中的位置。
            1.如果此位置无数据,此时key1-value1添加成功。
            2.如果此位置上有数据,一个或者多个,就比较它们的哈希值。
                        1.如果哈希值全不相同,则key1-value1添加成功。
                        2.如果哈希值和其中某个数据key2哈希值相同,则调用key1所在类的equals方法与key2比较.
                              1.如果equals返回false.则key1-value1添加成功。
                              1.如果equals返回true.则使用value1替换value2。

注意:HashMap与HashSet添加数据不同点是HashMap当执行到最后发现存储地址相同,哈希值相同,内容也相同时,HashSet就会添加失败,而HashMap则会执行替换操作。


十六、HashMap在JDK7中的源码分析


JDK7下源码:


A.构造器


1. public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    
2.  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);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }

      如上代码所示,先看HashMap构造器,空参构造器默认情况下初始容量为16的Entry[ ]数组,默认加载因子为0.75。

while (capacity < initialCapacity)
            capacity <<= 1;

      注意看下这两行代码,说明一个问题:我们创建HashMap时,容量只能是2的倍数。例如new HashMap(15),底层的capacity 会被设置为16。说明带参的构造器底层创建的容量只会是比参数大的最小的2的幂数。

threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

      threshold代表扩容的临界值,默认情况下扩容的临界值=容量*填充因子=16 * 0.75 => 12,因此当存储的数据满12时,就进行扩容。




      

B.下面看如何增加元素:


public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

       int hash = hash(key);该代码得到key的哈希值,然后执行 int i = indexFor(hash, table.length);得到key的存放位置。下面是HashMap计算哈希值和通过哈希值计算存储位置的源码:

1.   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();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

  2.static int indexFor(int h, int length) {
        return h & (length-1);
    }

       求得数据的存放位置后,先看该位置是否有数据,无的话直接addEntry(hash,key,value,i)添加元素,有的话接着判断hash值是否相同。如果for循环里所有的哈希值都不等,执行addEntry(hash,key,value,i)添加元素。如果和某个元素的哈希值相同,则比较value ,不等的话添加成功;相等的话进入if语句,执行新value替换原value操作。


C .扩容问题

1.  void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

2. void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
在这里插入代码片

       如上代码所示,首先判断是否超过临界值并且待存放的位置不为空,则扩容为原来的两倍大小。并重新计算key的哈希值和存放位置。在createEntry函数里,首先把原先位置上的数据取出来,传给e,然后将新存放的数据放在该位置上,并把该数据的next设置为e。这就说明了jdk7中新存放的数据放在旧数据的上面,然后指向旧数据。


JDK8下源码:


A.构造器

1. public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

2.    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);
    }

      如上代码所示,jdk8下的HashMap空参与带参构造器都没有新建一个长度为16的Entry[ ]数组,只是将加载因子和扩容的临界值分别赋值。在添加元素时,空参构造器新建一个长度为16的数组,带参构造器的底层创建大于等于参数的2的幂次数的值大小的容量。

B.如何添加元素

1. public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
2.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;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    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;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        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"})
            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 { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

      首次添加元素时,jdk8会在resize函数里新建长度为16的Node[ ] 数组。三种添加成功的情况与jdk7类似。

注意:
A、HashMap底层典型属性的属性的说明:
      1.DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
      2.DEFAULT_LOAD_FACTOR:      3.HashMap的默认加载因子:0.75
      4.threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
      5.TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
      6.MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64


B、HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
      4.1.形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)

      4.2.当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。当前数组的长度<64时,进行扩容。


十七、LinkedHashMap的底层实现

      LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
      区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.

JDK 8下:

      HashMap中的内部类:Node

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

      LinkedHashMap中的内部类:Entry

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


十八、TreeMap

public class TreeMapTest {
    @Test
    public void test1(){
        TreeMap map = new TreeMap();

        String u1 = new String("Tom ");
        String u2 = new String("Jerry ");
        String u3 = new String("Jack ");
        String u4 = new String("Rose ");

        map.put(u1,98);
        map.put(u2,95);
        map.put(u3,93);
        map.put(u4,50);

        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while(iterator1.hasNext()){
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry.getKey()+"****"+entry.getValue());
        }
    }
}

      TreeMap里输出时按照排序后的输出,需要添加的key所在类重写了compareTo排序方法,可以选择自然排序或者定制排序方式。


十九、Collections工具类的使用

reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值

      说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程的。
      使用synchronizedList(List list) 和 synchronizedMap(Map map)