集合框架学习(一):List&&Map

1,061 阅读3分钟

7c2dbf05d1b89657682a1e70b8ca361.jpg

集合框架

  1. 集合实现了动态保存
  2. 集合提供了方便的操作方法

Collection:单列集合

List 单列集合 有序集合 可重复集合

ArrayList

  1. ArrayList可以加入NULL值,且可加入多个

  2. ArrayList是数组实现存储的

  3. ArrayList是线程不安全的

源码:

  1. ArrayList中维护了一个Object[]类型的elementData

  2. 扩容机制:

    1. 使用无参构造器构造时,初始化长度为0,插入数据后,扩容elementData长度到10,再次扩容时,扩容至原来的1.5倍

    2. 使用有参构造器时,elementData的长度为初始的指定大小,需要扩容时,扩容至elementData长度的1.5倍

  3. 扩容机制的原因:

    
    	// 以无参构造ArrayList的扩容为例
    
    
    	// 调用add方法后,会调用方法:ensureCapacityInternal
    	public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    	// 先调用calculateCapacity再调用ensureExplicitCapacity
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    	// 在方法中calculateCapacity,判断了初始化的elementData是否为空,若为空则返回大小为DEFAULT_CAPACITY, minCapacity中的最大值,此时minCapacity = 0,而DEFAULT_CAPACITY 默认值为10
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    	// 判断最小需要空间是否大于目前空间的长度,若大于则需要扩容,调用grow方法
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    	// 在grow方法中,首先把elementData的长度赋值给了oldCapacity,再用oldCapacity计算新的空间,此时:oldCapacity = 0;newCapacity = 0+0/2=0,minCapacity = 10;通过if判断得到最后newCapacity = 10,即未初始化长度时,第一次扩容会扩增到长度为10。
    	// 注:oldCapacity >> 1 即 oldCapacity/2 
    	// 所以 newCapacity = oldCapacity + oldCapacity/2  即扩容1.5倍
        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);
        }
    

LinkedList

  1. 底层的数据结构是双向链表和双端队列

  2. 可以添加任何元素、可以重复、可以为null

  3. 线程不安全,没有实现同步、互斥

源码:

  1. LinkedList维护了size(元素个数)first(指向首节点)last(指向末节点)

  2. 每个节点(Node对象)维护了prev(指向前一个节点) next(指向下一个节点) item(数据)

  3. 增、删数据的时候是通过改变该节点前后节点的指向关系实现的,不牵扯数组,不扩容,相对效率更高

image.png

// add方法调用linkLast
void linkLast(E e) {
    	// 创建一个Node对象l,此时l=last为null
        final Node<E> l = last;
    	// 创建一个Node对象newNode且自定参数,则newNode数据结构为: prev:null item:e next:null
        final Node<E> newNode = new Node<>(l, e, null);
    	// last 指向newNode节点
        last = newNode;
    	// 此时为ture
        if (l == null)
            // first 指向newNode
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
// 此时数据已经插入,总体结构
// 链表:first指向newNode,last指向newNode,size=1
// Node节点:prev:null item:e next:null

Set 单列集合 无序集合 不可重复集合

  1. Set是无序的,这里是指添加和取出的顺序不一样,但是取出时,每次顺序都是固定的
  2. Set有且仅有一个null

HashSet

  1. 底层是实现了HashMap,key为存储的数据,value为PRESENT,占位作用

    private static final Object PRESENT = new Object();
    
  2. 不保证存放的顺序和取出的顺序一致,取决于hash后,确定索引的结果

  3. 数据不可重复,可以存放null

  4. 在add时,添加一个元素,会先得到它的hash值,再转为索引值,确定放在那个索引下,找到存储的数据表table,再进行添加的逻辑校验

// 得到的hash值并不是key的hashCode 是向右移动了16位,减少碰撞,降低hash冲突的几率
	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

##Map 双列集合 key-value结构

HashMap(JDK 1.8)

  1. 数组+链表+红黑树

  2. 当链表到达一定量且数组在一个范围的时候 链表树化 转为红黑树。默认情况下,是链表长度到8,数组大小不小于64时,转换为红黑树

  3. HashMap默认数组长度为16,装载因子为0.75,当前数组长度达16(默认总长度)*0.75=12时,进行扩容,扩容为原来的2倍

  4. 线程非安全

  5. 选取数据类型作为key时,最好采用包装类,内部重写了hashCode()和equals()方法,避免HashMap内部的hash()方法出现计算错误;如果使用自己的Object作为key的话,一定要重写hashCode()和equals()方法