java复习系列[6] - Java集合

Java集合

ArrayList的扩容

  1. 在add()、addAll()方法中判断是否需要扩容
  2. 使用 grow() 函数以 1.5 倍的方式进行扩容

HashMap

HashMap扩容流程(1.7 与 1.8 )

	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;
            // 使用红黑树作为数据结构,调用其 putTreeVal()
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    // 找到最后一个 next 不会 null 的位置,插入元素
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 如果树的深度大于阀值-1, 则重新调整,平衡二叉树
                        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;
                // LinkedHashMap 预留
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 修改+1
        ++modCount;
        // 容量超过阀值,扩容
        if (++size > threshold)
            resize();
        // LinkedHashMap 预留
        afterNodeInsertion(evict);
        return null;
    }

HashMap扩容流程(1.8)

  1. 检查是否满足阈值(0.75*N)
  2. 调用resize()方法
  3. 检查当前Node[]大小是否大于 最大容量MAXIMUM_CAPACITY
  4. 2 倍扩展,开辟新空间
  5. 判断当前Hash位置节点是否为空
  6. (不为空)是否存在链
  7. (没有链)是否是树节点
  8. (树节点)使用split方法
  9. (存在链)高低分离
// (1)调用 resize() 之前,检查是否满足阈值(0.75*N)
final Node<K,V>[] resize() {
	...
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
             threshold = Integer.MAX_VALUE;
             return oldTab;
        }
        //(2) 2 倍扩容
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
             oldCap >= DEFAULT_INITIAL_CAPACITY)
             newThr = oldThr << 1; // double threshold
        }
	}
    ...
	if (oldTab != null) {
        // (3)遍历原始节点数组
        for (int j = 0; j < oldCap; ++j) { 
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;	// 原始位置,设置为 null
                    if (e.next == null)
                        // (4)当前 hash 位置不存在冲突时候进行处理
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        // (5)红黑树的时候进行处理
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        // (6)链表的时候进行处理
                        ... 
                    }
                }
            }
        }
}

HashMap为什么要使用红黑树

防止自定义Hash效果不好,出现太多重复Hash,所以使用红黑树,提高查询速度

HashMap 1.7 扩容流程

HashMap 1.7 扩容流程

JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

image-20210829202511295
void transfer(Entry[] newTable, boolean rehash){
    int newCapacity = newTable.length;
    /**
    * 循环数组,放入到新的数组中
    */
    for(Entry<K, V> e : table){ // 外循环 遍历数组
        while(null != e){		// 内循环 遍历链表
            Entry<K, V> next = e.next;
            // -------------  是否是 rehash
            if(rehash){
                e.hash = null == e.key ?0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            // -------------  头插法
            e.next = newTable[i];
            newTable[i] = e;
            // -------------  处理下一个链表节点
            e = next;
        }
    }
}

假设有如下一个HasMap,就两个下标0和1,table[0]为null,table[1]存key=3的entry对象,因为entry对象有next标记,所以可以通过next标记找到下一个key=7的entry对象。也就是常说的每一个数组下标都对应一个链表,数组里存的是链表头结点

image-20210829203144176
单线程扩容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1mDhltNx-1638366604141)(Java集合.assets/image-20210829203412189.png)]

在扩容时,使用头插法进行链的移动;

多线程情况下:

除了其他线程安全问题(数据覆盖、丢失),而且会导致死循环问题,令程序不可用。

多线程扩容:
image-20210829212704330

如上图所示:1.7的头插法会导致死循环的产生!!!

在JDK1.8之后,HashMap底层的数组扩容时候使用了尾插法代替头插法。具体流程为

  1. 处理当前节点之前,会执行e = oldTab[j] 并且 oldTab[j]= null;
  2. 使用高低位分离的方式构建两个链表(高维和低位)
  3. 构建的时候使用尾插法
  4. 新数组中不同位置分别指向 高低链表的头结点

1.8 之后避免了死循环的产生,但是仍然存在线程安全问题!!!

迭代器与遍历的区别

遍历

ArrayList:ArrayList是采用数组的形式保存对象的,这种方式将对象放在连续的内存块中,所以插入和删除时比较麻烦(时间复杂度为O(n)),查询比较方便(时间复杂度为O(1))。

LinkedList:LinkedList是将对象放在独立的空间中,而且每个空间中还保存下一个空间的索引,也就是数据结构中的链表结构,插入和删除比较方便(时间复杂度为O(1)),但是查找很麻烦,要从第一个开始遍历(时间复杂度为O(n))。

for

使用下标遍历,对于底层是数组的集合类更加友好,get(i)时间复杂度:O(1)

        for (int i = 0; i < arrayList.size(); i++) {
            array = arrayList.get(i); // 等同于 array[i]
        }		

当底层是链表时,每次get(i)时间复杂度为:O(n)

        for (int i = 0; i < linkedList.size(); i++) {
            link = linkedList.get(i); 
            /* linkedList.get(i) 等同于如下操作 
					Node<E> x = first;
                    for (int i = 0; i < index; i++)
                        x = x.next;
                    return x;
            */
        }
foreach

底层通过迭代器实现,针对特定的集合类,会有具体的迭代方法。

foreach过程中,不能增加,删除元素。

代码示例:for与forEach的区别
package com.company.iter;


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * for和foreach测试,验证一下分别在List中操作性能
 *
 */
public class TestForAndForeach {
    public static void main(String[] args) {
        // ArrayList是采用数组的形式保存对象的,这种方式将对象放在连续的内存块中,所以插入和删除时比较麻烦(时间复杂度为O(n)),查询比较方便(时间复杂度为O(1))。
        List<Integer> arrayList = new ArrayList<>();
        // LinkedList是将对象放在独立的空间中,而且每个空间中还保存下一个空间的索引,也就是数据结构中的链表结构,插入和删除比较方便(时间复杂度为O(1)),但是查找很麻烦,要从第一个开始遍历(时间复杂度为O(n))。
        List<Integer> linkedList = new LinkedList<>();

        for (int i = 0; i < 200000; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }

        int array = 0;
        //用for循环arrayList
        long arrayForStartTime = System.currentTimeMillis();
        for (int i = 0; i < arrayList.size(); i++) {
            array = arrayList.get(i);
        }
        long arrayForEndTime = System.currentTimeMillis();
        System.out.println("用for循环arrayList 20万次花费时间:" + (arrayForEndTime - arrayForStartTime) + "毫秒");

        //用foreach循环arrayList
        long arrayForeachStartTime = System.currentTimeMillis();
        for(Integer in : arrayList){
            array = in;
        }
        long arrayForeachEndTime = System.currentTimeMillis();
        System.out.println("用foreach循环arrayList 20万次花费时间:" + (arrayForeachEndTime - arrayForeachStartTime ) + "毫秒");

        //用for循环linkList
        long linkForStartTime = System.currentTimeMillis();
        int link = 0;
        for (int i = 0; i < linkedList.size(); i++) {
            link = linkedList.get(i);
        }
        long linkForEndTime = System.currentTimeMillis();
        System.out.println("用for循环linkList 20万次花费时间:" + (linkForEndTime - linkForStartTime) + "毫秒");

        //用froeach循环linkList
        long linkForeachStartTime = System.currentTimeMillis();
        for(Integer in : linkedList){
            link = in;
        }
        long linkForeachEndTime = System.currentTimeMillis();
        System.out.println("用foreach循环linkList 20万次花费时间:" + (linkForeachEndTime - linkForeachStartTime ) + "毫秒");
    }
}

用for循环arrayList 20万次花费时间:3毫秒
用foreach循环arrayList 20万次花费时间:3毫秒
用for循环linkList 20万次花费时间:15856毫秒
用foreach循环linkList 20万次花费时间:4毫秒

在循环操作ArrayList的时候,使用for和foreach速度差不多,for稍微快一点。实际操作ArrayList使用过程中建议使用for循环,因为for循环采用下标访问,对于数组结构的数据来说,采用下标访问比较好。

在循环操作LinkedList的时候,使用for和foreach速度差非常多。所以在操作LinkedList的时候,一定要使用foreach循环。如果使用for循环,数据量大的时候有可能会导致系统崩溃。

迭代器

迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部.

简而言之,迭代器将遍历行为与被遍历的对象分离,这样我们无需了解序列底层结构

迭代器特点
  1. 将遍历行为与遍历对象分离,无需关心底层结构,
  2. 根据底层数据结构,构建最适合的迭代方式,
  3. 使用迭代器过程中,不能使用集合的add, remove等修改集合结构的方法,否则会抛出ConcurrentModificationException 异常
package com.company.iter;
import java.util.*;

public class test {
    public static void main(String[] args)
    {
        HashSet set=new HashSet(10);
        set.add("1");
        set.add("2");
        set.add("3");
        Iterator it=set.iterator();
        while (it.hasNext())
        {
            /*
            set.add("5");
            // Exception in thread "main" java.util.ConcurrentModificationException
            * */
            /*
            it.remove();
            // 正常执行
            * */
            System.out.println(it.next());
        }
        System.out.println("size : " +set.size());
    }
}
从源码中看区别
ArrayList

Java中Iterator(迭代器)实现原理

    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // 要返回的下一个元素下标
        int lastRet = -1; // 最近一次已经访问过的下标,最近未访问则为 -1
        int expectedModCount = modCount; 
        /*
		迭代部分的代码很简单,唯一难懂的是remove操作里涉及到的expectedModCount = modCount;
		在网上查到说这是集合迭代中的一种“快速失败”机制,这种机制提供迭代过程中集合的安全性。
		从源代码里可以看到增删操作都会使 modCount++,通过和expectedModCount的对比,迭代器可以快速的知道迭代过程中是否存在list.add()类似的操作,存在的话快速失败!
		
		就是说在迭代的过程中不允许进行add()类似的操作,如果有就会抛出异常。
        */

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }


    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

也就是说,modCount记录修改此列表的次数,当在迭代过程中出现对被操作集合的如下行为:包括改变列表的结构,改变列表的大小(增删元素),打乱列表的顺序等操作,会抛出 ConcurrentModificationException 异常

BUT:使用迭代器提供的方法(remove, add等)可以进行增删。

LinkedList

使用链表的形式进行迭代,而不是使用下标。

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next; // 获取下一个元素
        nextIndex++;
        return lastReturned.item;
    }
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        return nextIndex > 0;
    }

    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }

    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }

    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (modCount == expectedModCount && nextIndex < size) {
            action.accept(next.item);
            lastReturned = next;
            next = next.next;
            nextIndex++;
        }
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
HashMap

HashMap中有三种遍历方式,分别是:

  1. KeySet:通过keySet()方法获取一个KeySet集合,这个类里封装的是map的key。
  2. Values:通过values()方法获取Values集合,这个类里封装的是map的值。
  3. EntrySet():通过entrySet()获取EnterSet集合,这个类里封装的是map的键值对。

HashMap中的迭代器是内部类EntryIterator,它继承自另外一个内部类HashIterator,并且实现了Iterator接口(部分方法是在父类中实现的),迭代的主要代码存在于HashIterator这个类中。

    abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            // *************************************************************
            // 获取下一个节点(可能是解决Hash冲突的链、红黑树, 也可能是不同hash的节点)
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

    final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }

    final class ValueIterator extends HashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    final class EntryIterator extends HashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }
HashSet

用的是HashMap中的 KeyIterator

    // HashSet 的迭代器
	public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
    final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值