Java的集合容器

Java的集合容器

整体集合架构

返回目录

  • 面向接口编程

  • Collection

    • List
      • ArrayList
      • LinkedList
    • Set
      • HashSet
      • TreeSet
  • Map

    • HashMap
    • HashTable
    • LinkedHashMap
    • TreeMap

ArrayList

返回目录

API分类

    • add
    • addAll
    • remove
    • removeAll
    • clear
    • removeIf
    • set
    • sort
    • replace
    • replaceAll
    • retainAll
    • listIterator
    • iterator
    • subList
    • lastIndexOf
    • isEmpty
    • size
    • indexOf
    • get

要点

  • 默认初始化长度是 10
  • ArrayList和Vector集合之间的区别
    • ArrayList是线程不安全的,Vector是线程安全的
    • ArrayList扩容默认扩容1.5倍,Vector扩容的时候是2倍
  • Iterator接口
    • 所有集合都实现了这个接口,实现了了这个接口都有着增强for的能力
    • 增强for的底层实现还是通过iterator
  • Iterator使用的时出现的并发异常ConcurrentModificationException
    • 异常产生原因和迭代器里面的游标属性相关,以remove举例
      • 如果在使用迭代器的时候调用了外部的remove方法,那么不会更新迭代器中的游标属性值,就会抛出异常
      • 如果迭代器使用了内部的remove方法,那么会对游标进行更新,那么就不会抛出异常

源码解读

默认容器初始容量

注释里面就说ArrayList的默认初始化长度就是 10 。

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
并发容器异常

ConcurrentModificationException,产生异常的原因就是外面的list中的数组的结构发生了改变,但是迭代器里面的指针没有改变和更新,导致最终错误的发生。

public class CollectionDemo {
    public static void main(String[] args) {

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

        list.add(1);
        list.add(2);
        list.add(3);

        Iterator<Object> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
            if (next.equals(1)) {
                list.remove(next);
            }
        }
    }
}

解决方案

迭代器使用 listIterator

public class CollectionDemo {
    public static void main(String[] args) {

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

        list.add(1);
        list.add(2);
        list.add(3);

        Iterator<Object> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
            if (next.equals(1)) {
                iterator.remove(next);
            }
        }
    }
}

迭代器 remove 源码讲解

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

总结:如果使用iterator进行迭代的过程中如果删除其中的某个元素会报错,并发操作异常,因此如果遍历的同事需要修改元素,建议使用listIterator()。

HashSet

返回目录

API分类

    • add
    • remove
    • clear
    • set
    • sort
    • replace
    • replaceAll
    • iterator
    • size
    • isEmpty
    • contains

要点

  • HashSet的底层还是通过HashMap实现的,具体可以看其中的add方法的源码,里面添加元素的容器就是一个Map

源码解读

HashSet的底层容器实现

HashSet的本质还是HashMap

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashMap

返回目录

API分类

    • put
    • putAll
    • putIfAbsent
    • remove
    • removeAll
    • clear
    • replace
    • merge
    • replaceAll
    • size
    • isEmpty
    • containsKey
    • containsValue
    • get
    • getOrDefault
    • keySet
    • entrySet

要点

  • HashMap和HashTable之间的区别

    • HashMap
      • 效率高,但是线程不安全
      • 其中的Value允许为空
    • HashTable
      • 线程安全,但是效率低
      • HashTable中的value不允许为空
  • HashMap在JDK版本1.7和1.8的实现有一些改进

    • 1.7
      • 数据结构实现:数组+链表
    • 1.8
      • 数据结构实现:数组+链表+红黑树
      • 增加了链表转红黑树的属性值字段(值为8)
  • HashMap的初始容量是2的N次幂

    • 方便进行&操作,提高效率,&要比取模运算效率要高
    • 在扩容之后涉及到元素的迁移过程,迁移的时候只需要判断二进制的前一位是0或者是1即可
      • 如果是 0 ,表示数组和旧数组的下标的位置不变
    • 如果是 1 ,只需要将索引位置加上旧的数组的长度值即可为新数组的下标
  • HashMap扩容的倍数默认是2倍

    • 方便进行位运算,效率比较高,比较细节的操作

源码解读

JDK1.7源码
  • DEFAULT_INITIAL_CAPACITY,默认初始化大小: 16
  • DEFAULT_LOAD_FACTOR,扩容的因子: 0.75 当前容量到达容器最大容量的一定比例的时候,就进行扩容
put方法流程图

HashMap的初始容量是2的N次幂
- 方便进行&操作,提高效率,&要比取模运算效率要高
- 在扩容之后涉及到元素的迁移过程,迁移的时候只需要判断二进制的前一位是0或者是1即可
- 如果是 0 ,表示数组和旧数组的下标的位置不变
- 如果是 1 ,只需要将索引位置加上旧的数组的长度值即可为新数组的下标

HashMap扩容的倍数默认是2倍
- 方便进行位运算,效率比较高,比较细节的操作

put()方法

判断是否需要进行扩容
让后放入新的值

resize()方法

进行扩容和数据的移动

indexFor()方法

判断新插入的值应该插入Hash表的什么位置

JDK1.8源码
put方法流程图

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)
        // 为空,resize创建数组并且设置阈值
        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))))
            // 如果目标位置和table[i]指向的是同一个节点,并且hash和key值相同,那么修改原值,返回原值
            e = p;
        else if (p instanceof TreeNode)
            // 如果table[i]是红黑树,遍历树
                // 树里已经有了目标值,修改原值,返回原值
                // 树里没有原值,那么红黑树插入新的值
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // table[i]是链表
            // 遍历链表
            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
                        // 如果链表长度大于8,将链表转成红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                // 链表里面有相同的key,那么替换原值,返回原值
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            // 如果已经存在之前的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;
}

相对于1.7来说新增了红黑树

TREEIFY_THRESHOLD = 8
- 表示HashMap中的数组+链表中的链表的长度如果超过 8 的话,就将链表转换为红黑树的数据结构

  • Entry的名称改为了Node
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值