JAVA集合
JAVA集合接口继承关系和实现
- 集合类存放在java.util包中,主要有set(集)、list(列表)、map(映射)。
- Collection:是集合List、Set、Queue的基本接口
- Iterator:迭代器,可以通过迭代器遍历集合中的数据
- Map:映射表的基本接口
List
- List是有序、可重复的Collection,java List有三个实现类:ArrayList、Vector、LinkedList。
ArrayList
- 内部通过
数组
实现,允许对元素进行随机访问。 - 缺点:每个元素之间
不能有间隔
,当数组大小不满足时,需要增加存储能力,要将已经有数组的数据复制到新的存储空间中。 适合随机查找和遍历,不适合插入和删除
。
Vector
- 通过
数组
实现 - 支持
线程同步
,某一时刻只有一个线程能写Vector,避免多线程同时写引起的不一致问题,但实现同步需要很高的花费 查询快,增删慢,效率低
。
LinkList
链表
结构存储数据- 适合
动态插入和删除
- 随机访问和遍历速度较慢
- 提供操作表头和表尾元素,
可以当做堆栈、队列和双向队列使用
set
- Set用于存储无序、唯一的元素
- 对象的相等性本质是对象
hashCode
值判断的 - 两个不同的对象相等,必须
覆盖Object的hashCode方法和equals方法
HashSet
- 哈希表存放的是
哈希值
- 存储元素的顺序不是按照存入的顺序,而是
按照哈希值存
的,取数据也是按照哈希值取数。 - 元素的哈希值是通过元素的hashCode方法获取
- HashSet首先判断两个元素的哈希值,如果哈希值一样,接着比较equals方法,如果equals结果为true,HashSet视为同一个元素。
哈希值相同,equals为false的元素存储:在同一的哈希值下顺延(哈希值相同的元素放在一个哈希桶中),哈希一样的存在一列
。- HashSet通过hashCode值来确定原则在内存中的位置,一个hashCode位置上可以存放多个元素
LinkedHashSet
- LinkedHashSet是一个
哈希表和链表
的结合. - LinkedHashSet而言,它继承与HashSet、又基于LinkedHashMap来实现的。
- LinkedHashSet
底层使用LinkedHashMap来保存所有元素
。 - LinkedHashSet继承于HashSet,其所有的方法操作上又与HashSet相同,因此LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个LinkedHashMap来实现,在相关操作上与父类HashSet的操作相同,直接调用父类HashSet的方法即可。
TreeSet
- 使用二叉树的原理对新add()的对象按照指定的顺序排序,每增加一个对象都会进行排序,将对象插入的二叉树指定位置。
Interger和String对象都可以通过默认的TreeSet排序,自定义的类的对象不可用
,自定义的类必须实现Comparable接口,并且覆写相应的compareTo()方法,才可以正常使用。在覆写compare()函数时,要返回相应的值才能使TreeSet按照一定的规则来排序
。- 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
Map
HashMap
- HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
HashMap最多只允许一条记录的键为null,允许多条记录的值为null
。- HashMap
非线程安全
,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。 - 如果需要满足线程安全,
可以用 Collections的synchronizedMap方法
使HashMap具有线程安全的能力,或者使用ConcurrentHashMap
。 - JAVA7实现的HashMap
- HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。
- Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
- 参数:
- capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
- loadFactor:负载因子,默认为 0.75。
- threshold:扩容的阈值,等于 capacity * loadFactor。
- JAVA8实现的HashMap
- Java8实现的HashMap最大的不同就是利用了
红黑树
,所以其由 数组+链表+红黑树 组成。 当链表中的元素超过了8个以后,会将链表转换为红黑树
,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
- Java8实现的HashMap最大的不同就是利用了
HashTable
- 它承自Dictionary类。
线程安全
,任一时间只有一个线程能写Hashtable,不允许使用null值和null键。- 它
使用synchronized来锁住整张Hash表来实现线程安全
,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。 - Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
TreeMap
TreeMap实现SortedMap接口
,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap
。- 在使用TreeMap时,
key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator
,否则会在运行时抛出java.lang.ClassCastException类型的异常。
LinkedHashMap
- HashMap的一个子类,是
基于HashMap和双向链表
来实现的。 - 保存了记录的插入顺序,
LinkedHashMap有序,可分为插入顺序和访问顺序两种
。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。 - 在用Iterator遍历时,先得到的记录时限插入的,也可以在构造时带参数,按照访问次序排序。
- LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序
- LinkedHashMap是线程不安全的。
ConcurrentHashMap
- Segment段
- ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“或“槽”,也可以称为“分段锁”。
一个 Segment 其实就是一个类 Hash Table 的结构
,Segment 内部维护了一个链表数组。
- 线程安全(Segment 继承 ReentrantLock 加锁)
Segment 通过继承 ReentrantLock 来进行加锁
,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
- 锁分离技术
- ConcurrentHashMap采用了
二次hash
的方式,第一次hash将key映射到对应的segment,而第二次hash则是映射到segment的不同桶(bucket)中
。 - 二次hash的原因:为了
构造分离锁
,使得对于map的修改不会锁住整个容器,提高并发能力。
- ConcurrentHashMap采用了
- JDK1.7的ConcurrentHashMap
- ConcurrentHashMap是由
Segment 数组、HashEntry 组成
,和 HashMap 一样,仍然是数组加链表。 - 和 HashMap 非常类似,唯一的区别就是其中的
核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性
。 - ConcurrentHashMap 支持
CurrencyLevel (Segment 数组数量)的线程并发
。CurrencyLevel 默认是 16,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。concurrencyLevel 一经指定,不可改变
。 - 如果ConrruentHashMap需要扩容,ConcurrentHashMap不会增加Segment的数量,而
只会增加Segment中链表数组的容量大小
,而只需要对Segment里面的元素做一次rehash
就可以了。
- ConcurrentHashMap是由
- JDK1.8中的ConcurrentHashMap
利用 CAS 算法,底层依然由“数组”+链表+红黑树的方式思想
,但是为了做到并发,又增加了很多辅助的类。- ConcurrentHashMap 使用了
happens-before 规则
来实现。 - 采用
红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized
。