NightPxy 个人技术博客

java集合

Posted on By NightPxy

集合框架

Collection

Collection 就是最常规意义的集合.是List,Set和Queue的父接口

List

List元素有序并且允许重复
List可以精确的控制插入位置或直接删除某个位置的元素

ArrayList
  • 排列有序,允许元素重复
  • 底层使用数组,遍历速度快,随机读取快,增删节点慢
  • 线程不安全集合
  • 扩容策略为每次50%,相对节约内存
Vector
  • 排列有序,允许元素重复
  • 底层使用数组,遍历速度块,随机读取快,增删节点慢
  • 线程安全集合,这是Vector与ArrayList最大区别
  • 扩容策略为每次扩容1倍,相对ArrayList会更加浪费内存
LinkedList
  • 排列有序,允许元素重复
  • 底层使用双向链表.遍历速度块,增删节点也快,但随机读取慢
  • 线程不安全
  • 没有扩容策略,链表本身属于无界集合

Set

HashSet
  • 排列无序,不可重复
  • 底层使用Hash表实现
  • 内部本质是HashMap,随机读取快,增删节点快,遍历速度慢(Map的通病)
  • 线程不安全
TreeSet
  • 有序存储
LinkedHashSet
  • 采用Hash表存储,并于双向链表记录插入顺讯
  • 内部本质是LinkedHashMap

Queue

本质是在两端出入的list,一般就是数组或链表List的特殊进出规则封装

Map

Map提供一种基于映射关系的键值对存储,它的元素实际以Entry形式存储
对Map来说键不可以重复,但值可以重复

HashMap

  • 键不可重复,值允许重复
  • 底层哈希表
  • 线程不安全
  • 允许Key为null,允许值为null.但基于键不重复,null只会存在一个

HashTable

  • 键不可重复,值允许重复
  • 底层哈希表
  • 线程安全
  • Key,Value都不允许为null

TreeMap

  • 底层二叉树
  • 键不可重复.值允许重复

Concurrent

ConcurrentHashMap

  • 线程安全
  • 更加高效的线程安全模式(锁分段)

重要知识点

集合类型的选择

  • 首先是根据自己的实际场景需要选择是否需要线程安全集合
    普遍来说,线程安全因为同步块的存在其效率会明显不如非线程安全集合
    注意线程安全集合仅仅只能保证自身提供操作的线程安全,不能保证组合操作的线程安全
  • 总体来说
    数组系的优势在于遍历快随机读取快,缺点是增删慢,扩容策略有内存浪费.
    链表系的优势在于遍历快增删快,无界优势表示没有扩容带来的内存浪费,缺点是随机读取慢
    哈希系的优势在于随机读取快增删快,缺点是遍历慢.

equals&hashCode

原则上一个对象如果重写了hashCode方法,必须同时重写equals.这是因为难以预料该对象是否会用于哈希键中.反过来说,如果使用一个非基本类型的对象作为键,也需要检查其对象hashCode和equals是否匹配
保证两个对象如果equals相同,则hashCode必须相同

根本原因在于,基于哈希散列不允许键重复.其判定规则如下

  • 首先比较两个对象的hashCode是否一致,如果不一致将直接视为不重复
  • 但hashCode冲突(两个不同对象的哈希值相同)是非常普遍的,所以哈希比较之后会进一步调用equals方法比对是否是相同对象,equals的判定结果将作为最终结果

  • 一个更好的hashCode,可以加快存取速度
  • 而一个不匹配的hashCode,会造成无法判定唯一而不断写入对象,造成内存泄露

哈希冲突

哈希冲突是基于哈希表的集合都必须解决的问题
哈希冲突是指两个不同对象有一样的hash值.也就是说通过无法一个哈希值判定两个对象唯一
解决哈希冲突的常见思路有

  • 再哈希
    再哈希是一种最简单的解决方案,并且性能也相当不错.但问题是在于不能保证第二次哈希唯一,所以极端情况下,需要N次哈希才能解决
  • 链表法
    链表法也是一种相当简单的思路.优势在于一次链表必定解决哈希冲突,劣势在于哈希冲突激烈时性能骤降.极端情况下可能会有彻底失去哈希优势蜕变了链表的可能

java.util.ConcurrentModificationException

快速失败(fail-fast)机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的数量被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件

Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    // 抛出 java.util.ConcurrentModificationException 
	list.remove(integer);
}

要避免这个Exception,可以使用递归器的remove()方法

Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    //这种在没有线程安全的情况下没有问题的
    //因为递归器remove方法会同步一次集合数量
    //但因为这只是一次非线程安全的同步集合数量,所以多线程下依然会有问题
    //此时还必须使用同步块才能真正解决
	iterator.remove(integer);
}

常见集合对比

HashMap & HashTable & ConcurrentHashMap

  • HashMap不是线程安全,允许K,V为null(K为null时固定写在0处)
  • HashTable是线程安全的,但因为锁全段所以线程安全性能较低,K,V都不允许为null
  • ConcurrentHashMap是高效的线程安全(1.8前锁分段机制,以及final,volatile修饰带来的无锁读取)
    注意ConcurrentHashMap在诸如size,contains时会锁全段
    JDK1.8后锁Node,并且加入了CAS以提高性能,不过依然采用synchronized而没有使用ReentrantLock