Java集合(底层设计与实现)

Java集合(底层设计与实现)

集合体系

Java集合(底层设计与实现)_第1张图片
Java集合(底层设计与实现)_第2张图片

List接口

基本介绍

  • 元素有序(即添加顺序和取出顺序一致)、且可重复
  • 支持索引
  • 有下标,下标对应元素在容器中的位置

ArrayList

基本介绍:底层由数组实现;在多线程环境下不建议使用,由于没有加synchronized线程不安全,建议使用Vector。

  • 底层是维护一个Object类型的elementData数组
  • 当创建ArrayList对象时,如果使用的是一个无参构造器,则初始elementData数组容量为0;第一次添加元素,数组容量扩充为10;如需再次扩容,则扩容elementData数组容量1.5倍
  • 如果使用的是指定大小的构造器,则创建后的大小即为输入值,后续扩容是直接扩容1.5倍

核心代码:

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

Vector

基本介绍:底层由数组实现

  • 底层是维护一个Object类型的elementData数组
  • Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  • 构建时,如果是无参,默认容量是10,之后扩容,就按2倍扩容
  • 有参构建时,容量为输入的数,之后扩容,直接2倍扩容

核心代码:

/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

LinkedList

基本介绍:LinkedList底层实现了一个双向链表和双端队列;线程不安全,没有实现线程同步

  • LinkedList底层维护一个双向链表
  • LinkedList中维护了两个属性,first和last,分别指向首节点和尾节点

ArrayList与LinkedList的比较

Java集合(底层设计与实现)_第3张图片

如何选择:

  • 查找操作多时,建议使用ArrayList
  • 修改操作多时,建有使用LinkedList

Set接口

基本介绍

  • 无序(添加顺序与取出顺序不一样),没有索引
  • 不允许有重复元素,所以最多包含一个null

HashSet

  • 底层为HashMap(HashMap的底层为 链表+数组+红黑树)
  • add方法返回一个boolean类型的结果,添加成功ture,失败(重复添加)为false

面试题

  • 同时添加两个new String("123"),一个添加成功,一个失败

扩容机制

  • 底层是HashMap
  • 添加元素时,先得到hash值,然后传换成索引值
  • 找到存储数据表table,看这个索引位置是否存在元素
  • 如果没有则加入
  • 如果有,调用equals比较,如果相同,就放弃添加,如果不同则添加到最后
  • 如果一条链表的元素数到达默认值,并且table的大小大于等于默认值,就会发生树化(红黑树)

核心代码

/**
     * 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;								//定义辅助变量
        //table 就是 HashMap 的一个数组,类型是 Node[]
        //如果当前 table 是 null,或者大小 = 0
        //就扩容,到16个空间
        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;
            // 判断 p 是否是一棵红黑树
            // 如果是一颗红黑树,就调用 putTreeVul, 来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 如果table对应索引位置,已经是一个链表,就使用for循环比较
            // (1)依次和该链表的每一个节点上的元素比较,都不相同,则加入到该链表的最后
            //    注意,在将元素添加到链表后,立即判断 该链表是否已经达到8个结点,达到
           	//    则调用 treeifyBin() 对当前这个链表进行树化(红黑树)。
            //    注意,转换成红黑树以后,还要进行判断,当table表与链表的大小达到64时,
            //    才会树化,其余先将table进行扩容
            // (2)依次和该链表的每一个节点上的元素比较,如果有相同情况,就直接break
            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;
    }

TreeSet

  • 构造方法中使用比较器,可以实现排序

Map接口

基本介绍

  • 底层是将k-v保存到一个 HashMap$Node node = new Node(hash, key, value, null)
  • k-v为了遍历方便,还会创建EntrySet集合,其中元素的类型对象为EntrySet>,其中,k,v都是指向Node中的k,v
  • EntrySet保存的key是存在Set中的,value是存在collection中的

阿里开发规范要求使用entrySet的方式遍历map,因为entrySet只需要访问一次,而keySet需要访问map两次

HashMap

  • 是以key-value对的方式来存储数据(HashMap$Node类型)
  • key不可重复,但是value可以重复,运行使用null来充当键或者值
  • 如果添加相同的key,后一个value会覆盖之前的value
  • 与HashSet一样,不保存存储顺序
  • HashMap没有实现同步,所以是线程不安全的

核心代码:

与HashSet核心代码相同

扩容树化触发

当table的容量到达64时,且当个链表上面的数量大于8时,就会发生树化,其余是扩容。

当table内存的数量达到容量乘以扩容因子是,就会发生扩容

HashTable

  • 存放元素的方式是k-v
  • 底层维护数组 Hashtable$Entry[], 初始值为 11
  • 元素的键值不能为null,否则会直接抛异常
  • 方法基本和HashTable一样
  • HashTable是线程安全的

扩容机制:

核心代码:

/**
     * Increases the capacity of and internally reorganizes this
     * hashtable, in order to accommodate and access its entries more
     * efficiently.  This method is called automatically when the
     * number of keys in the hashtable exceeds this hashtable's capacity
     * and load factor.
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        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;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

按照原先容量两倍加一的方式扩容

Properties

  • 使用键值对的形式保存数据
  • 使用特点与Hashtable类似
  • Properties 还可用于从xxx.properties 文件中,加载数据到Properties类对象,并进行读取和修改

TreeMap

  • 构造方法中使用比较器,可以实现排序

你可能感兴趣的:(Java基础,java,开发语言)