JAVA 集合

121 阅读3分钟

可以将JAVA 中的集合分成两派分别是 Collection 派别 和 Map 派别

Collection

image.png

image.png

List接口

List接口的特点:存放的元素是有序的,而且可以重复

ArrayList

  • ArrayList 继承关系图

image.png

ArrayList 常用API TODO

ArrayList 底层数据结构

ArrayList底层数据结构是数组: 数组的特点:它是一块连续的存储空间,使用下标索引进行访问,所以说他的访问速度是比较块的

源码分析:


/**
 * 默认容量大小
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 共享一个空的数组实例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};


 /*  只是创建了一个空的ArrayList对象的时候它的默认大小是一个空的对象,当添加第一个元素的时候默认的初   *始化大小是10*/

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


transient Object[] elementData; // non-private to simplify nested class access

/**
 * The size of the ArrayList (the number of elements it contains).
 * 元素的个数
 * @serial
 */
private int size;

/**
 * 初始化的时候指定容量大小
 */
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);
    }
}

/**
 * 空参构造器,初始化一个空的对象数组
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

1.在创建一个ArrayList的时候没有添加元素,他会创建长度空的对象数组

ArrayList<Integer> list = new ArrayList<>();

image.png

image.png

2.添加对象的时候,数组长度的情况

添加一个对象的默认长度,赋值为10

image.png

image.png

image.png

3.触发第一次扩容

通过上面的源码可以知道,当添加了第一个元素以后默认的容量大小是10,那么在添加第11个元素的时候会触发扩容的机制。

image.png

image.png

image.png

image.png

LinkedList

  • LinkedList 继承关系图

image.png

LinkedList 常用API TODO

底层数据结构

LinkedList底层的数据结构是双向链表:链表的特点,他的next指针会执向内存的随机的一块区域, 所以他在查找的时候效率会低一点,但是在插入的时候和删除的时候效率是比较好的

底层数据结构:

可以看的到他是一个双向链表

image.png

  1. add方法:添加一个元素

image.png

Vector

  • Vector 继承关系图

image.png

常用API

底层数据结构

从下面的这个图中可以知道Vector底层的数据结构也是采用了一个对象数组的结构,而且他在JDK1.0版本的时候就已经存在了

image.png

  1. 创建的Vector的时候的默认大小是10

image.png

  1. 它的操作方法是线程安全的

image.png

  1. Vector它的扩容机制 image.png image.png 指定 capacityIncrement image.png

不指定 capacityIncrement 就是扩容 2倍

image.png

总结对比ArrayList、LinkedList、Vector他们之间的相同点和不同点

先说一说 ArrayList 和 Vertory

相同:首先他们两个底层都是使用的数组,也都是在List体系下所以他们都是有序可以重复的数据

不同:他俩的不同点在于 ArrayList他的操作方法是线程不安全的,而Vertory的操作方法是线程安全的。其次就是他们的扩容机制不同,ArrayList的扩容它扩容的大小是原来的0.5倍,Vertory扩容的大小是可以指定的,如果不指定扩容大小,那么扩容的大小翻倍,如果指定了则扩容 原来的容量 + 指定大小

LinkedList 它的底层数据结构就是链表了,底层数据结构是链表,不需要连续的内存,随机访问的慢,但是增删块头部,尾部插入的速度块,但是如果想要往中间插入并不快,内存容量占用比较大

Set接口

Set接口特点 元素无序、不可重复的集合

HashSet 继承图

image.png

SortedSet 继承图

image.png

Queue接口

Map

Map 集合是和Collection并列的存在,用来保存具有映射关系的数据Key-Value

Map中的Key和Value可以是任何应用类型的数据,

Map中的key不允许重复,但是Value可以重复

Map中的key可以为Null 但是只能有一个,Value可以为null但是可以有多个

image.png

Map集合的遍历方式

    @Test
    public void mapBianLi(){
        Map map = new HashMap();
        map.put("1","牛小牛");
        map.put("2","牛小牛2");
        map.put("3","牛小牛3");
        map.put("4","牛小牛4");

        System.out.println("**********第一种遍历方式***********");
        Set keySet = map.keySet();
        for (Object key:keySet){
            System.out.println(key + "-" + map.get(key));
        }

        System.out.println("***********第二种遍历方式***********");
        Iterator iterator = keySet.iterator();
        while(iterator.hasNext()){
            /*获取到的是Map的Key*/
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        System.out.println("***********第三种遍历方式***********");
        //取出来所有的values
        Collection values = map.values();
        for(Object value : values){
            System.out.println(value);
        }

        System.out.println("***********迭代器方式获取值***********");
        Iterator iterator2 = values.iterator();
        while(iterator2.hasNext()){
            Object value = iterator2.next();
            System.out.println(value);
        }

        System.out.println("**************第三组遍历方式***********");
        Set entrySet = map.entrySet();
        for (Object entry:entrySet){
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }

        System.out.println("****************迭代器****************");
        Iterator iterator3 = entrySet.iterator();
        while(iterator3.hasNext()){
            Map.Entry next = (Map.Entry) iterator3.next();
            System.out.println(next.getKey() + "-" + next.getValue());
        }
    }
}

HashMap源码分析

1、执行内部的构造器

初始化加载因子loadfactor 默认值0.75

image.png

2、执行put调用Hash方法,计算key的hash值

image.png

image.png

3、执行putVal

/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //1、如果table为空,或者lenth == 0 执行resize方法,扩容到16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //2、通过hash计算索引的位置
    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);
                    // 树化操作,链表的节点数量大于8个并且table的长度大于64才会树化
                    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 void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

扩容方法:


/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field threshold.
 * Otherwise, because we are using power-of-two expansion, the
 * elements from each bin must either stay at same index, or move
 * with a power of two offset in the new table.
 *
 * @return the table
 */
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
        //如果Table为空赋值初始容量 16
        newCap = DEFAULT_INITIAL_CAPACITY;
        
        //计算他的一个加载因子 加载因子(0.75)* 数组的容量
        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;
}

HashTable 源码分析

1、存放的也是键值对

2、HashTable的键值不能为null

3、HashTable是线程安全的,HashMap是线程不安全的

image.png

HashTable 构造方法:

public Hashtable() {
    this(11, 0.75f);
}

public Hashtable(int initialCapacity, float loadFactor) {

    //如果容量小于 0 抛出非法参数异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    // 如果 loadFactor 传过来的不是一个浮点数,或者是说传过来的值小于0,则抛出异常                                          
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);

    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry<?,?>[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

可以看到他的默认初始容量是11

image.png

临界值的计算规则

image.png

Put方法:

public synchronized V put(K key, V value) {
    // 判断value是否为null,如果为null,则抛出异常
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    // 计算索引下标
    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;
}

--- TODO