Java集合 Collection和Map

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWW91bmdfWl9DaGFu,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWW91bmdfWl9DaGFu,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWW91bmdfWl9DaGFu,size_10,color_FFFFFF,t_70,g_se,x_16

 首先说一下Collection,它继承了Iterable,说明它的可实例化的子类都可以使用Iterater来做遍历

下面开始讲一下List

        List的所有实现类都是可以重复添加元素的是可以通过索引获取元素的是保证插入和取出顺序的

List的实现类:

一、ArrayList:

        ArrayList的底层是一个可变数组,先看他的重要成员和构造方法。

private static final int DEFAULT_CAPACITY = 10;

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData;

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    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);
        }
    }

ArrayList的构造方法:

        1:无参构造器:它会将一个 成员Object[ ]的空数组(Object[ ]==null在内存里是没有开辟空间的) 赋值给它的另一个成员 elementData[ ]。要注意的是,这里的赋值只是简单的引用而已。

        2:有参构造器:它会接收一个 初始容量的值 创建一个初始容量大小的Object[ ]数组 (在内存里开辟了空间)。

ArrayList的add( )方法与扩容机制:

        size  是 ArrayList的长度(大小)成员。初始默认值是0。

private int size;

        进入add( )方法后 传入一个 最小容量(minCapacity)值为size+1。

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

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

 calculateCapacity( )方法非常关键:它将会给无参构造的ArrayList 定义一个最小容量( 10 )

private static final int DEFAULT_CAPACITY = 10;
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

        如果  add( )方法需要的最小的容量  比现在的  elemenData[ ]   的空间要大  则增长扩容。

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

        grow( )方法就是扩容机制:

其中有连个关键的变量 newCapacity-----oldCapacity

扩容机制:

        newCapacity = oldCapacity +(oldCapacity >> 1)    :    将旧的容量 乘以 1.5 赋值给新的容量。

        然后将新的容量传入copyOf方法--->构造一个 以新容量为大小的数组,将原来elementData[ ]里面的元素拷贝到新的数组 并将引用指向 新的数组 。

    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);
    }
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

总结ArrayList:

底层数据结构:

        数组  ----->> 有索引,插入与取出有序

ArrayList的扩容机制:

        如果用无参构造创建ArrayList对象,    则在第一次添加元素的时候会    分配10 个内存空间 , 直到添加超出容量的第11个元素的时候,才会把容量增加到1.5倍,往后每次超出都扩容1.5倍。

(重点:无参构造是在添加元素的时候才会分配空间 )

        而如果是有参构造的话   创建对象的时候就会 按照传入的 最小容量 创建指定大小的 数组 ,直到超出 该容量 才会 扩容至 1.5 倍。

(重点:有参构造是在创建对象的时候就会分配空间 )


二、Vector

        Vector 的底层也是数组,扩容机制与ArrayList是一模一样的,只不过在CRUD方法中都加了

synchronized 关键字------>>>>加了锁

        也就是说 Vector与ArrayList的不同之处就是 Vector是线程安全的,而ArrayList不是线程安全的,正因为这个原因,Vector的效率 比 ArrayList的效率要差。

(  get方法加了锁  其他的CRUD方法也是如此就不一一列举了)

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }


三、LinkedList

        LinkedList底层的数据结构是双向链表

首先看一下LinkedList里面的一个私密内部类

        Node------>>>结点

此内部类有三个成员:

        item next prev  这三个都是引用类型  (可以理解为指针)其中有 指向前后节点的引用(指针)所以:LinkedList的底层原理是双向链表。

    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( )方法:

        创建第一个Node对象      LinkdeList  的  first(头指针) last(尾指针都指向该节点)

        item(指针)会指向它引用的对象 由于是第一个Node 其prev 和 next 都是null。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWW91bmdfWl9DaGFu,size_19,color_FFFFFF,t_70,g_se,x_16

 第二次 add( )后:

        两个节点形成双向链表。但第一个结点的prev和最后一个结点的next都是null。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWW91bmdfWl9DaGFu,size_19,color_FFFFFF,t_70,g_se,x_16

        由于底层的结构是双向链表,所以增删操作效率相对数组比较高,其复杂度是O(n),但由于双向链表没有索引,所以查询,修改操作是需要遍历链表的,其复杂度为O(n)。

扩容机制:

        无参构造之后,其first和last都是指向空,并不会开辟空间,而每次插入一个元素的时候才会在链表的最后一位添加一个结点。所以,可以理解为LinkedList没有扩容机制。


下面讲一下Set,Set是不能有多个重复元素的,且插入和查询都是不能保证顺序的。(但插入完成后每次查询的顺序都是相同的。)

Set的实现类:

一、HashSet:

HashSet的底层是一个HashMap,我们可以直接从构造器中看出来

    public HashSet() {
        map = new HashMap<>();
    }

        Set是单列的,而Map是双列的,但Set的底层是Map,这要怎么解释呢?这里就需要看一下HashSet的两个重要的成员和add( )方法了。

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

        可以看见add( )方法其实是往HashSet的一个成员 中放入一个 传来的参数做键(Key) 将一个Object类的PRESENT作为值(Value)做占位。

        所以说想要知道HashSet的数据结构和扩容机制就要看HashMap的。接下来我们看看HashSet的add( )方法所调用的HashMap.put( )方法。

    public V put(K key, V value) {
        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;
        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;
    }

        解析一下putVal( )方法 每次put的时候都会检查 一个名为table的Node[ ]数组是否为空或者长度为0,如果是,则会调用resize( ),这个方法非常重要解释了Set 乃至 Map的扩容机制。这个将会在后面讲到。

        经过了table的扩容或者不扩容之后,传进来的K-V会被封装成一个HashMap$Node对象然后经行Hash运算找到它要插入的table的索引位置

        如果该索引上已经有结点,则会经行equals( )比较,如果equals( )==true 则说明两个Node指向的对象相同,则不添加该Node,  如果equals( )==false,则会向后一位 比较hash值(hash值不等于hashCode)和 equals( )方法 ,一直这样循环下去,直到找到一个相同就放弃添加,或者找到最后都没找到相同的,则添加到最后。Node会形成一条链表,在特殊情况下会变成一颗树。

        由此可以看出HashSet和HashMap底层都是 (数组+链表+树)

下面讲解一下扩容机制和resize( )方法

        由于resize( )方法太长了,所以这里将会一个一个代码块来讲解。

resize( )与扩容机制

        先看三个常量:

分别是默认初始化容量==16,最小的树化长度==64,临界因子==0.75

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    static final int MIN_TREEIFY_CAPACITY = 64;

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

         下面这段是关于初始化HashMap的代码块:定义了table的长度为16,还有临界值为12。

(当table的长度达到邻接值,且某一链表的长度大于8,则该链表会树化)

        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

        当达到table阈值的时候就会把table的长度增加到 原来的长度*loadFactor(增长因子,默认为2,可以有参构造的时候覆盖)

        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }

         当链表长度 大于等于7 且 table的 长度大于64 则树化。(如下)

    static final int TREEIFY_THRESHOLD = 8;

    static final int MIN_TREEIFY_CAPACITY = 64;
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
        treeifyBin(tab, hash);
    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();

         treeifyBin的后面的else代码块就是树化的过程,篇幅较长这里就不展示了。

           Set的其他子类晚点再补充,

先写一下HashMap

Map的实现类:

一、HashMap:

先来看看HashMap的一些结构:

        一对K-V===>>>作为一个Entry===>>>所有的entry组成===>>>entrySet

        所有的key===>>>组成keySet

        所有的value===>>>组成values

 

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
    public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }
    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值