目录
概述
所有集合的顶级接口Collection
子接口:List接口 set接口 queue 接口
List接口:实现类有ArrayList linkedList stack vector
set接口:
- HashSet 底层是使用HashMap(底层是数组加链表)
- LinkedHashSet:保证数据有序
- TreeSet:能排序
Queue接口:先进先出
list和set是实现了collection接口的。Map不是collection的子接口或者实现类。Map是一个接口
jdk1.8图
collection接口:
Map:
List:
- 可以允许重复的对象。
- 可以插入多个null元素。
- 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
- List中提供索引的方式来添加元素和获取元素,由此可见List集合可是达到精确的存储和获取,
- 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
Set:
- 不允许重复对象
- 只允许一个 null 元素
- 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
- 传入的参数都是对象,而Set只能一个一个的比较,显然效率和实用性是比不上List集合的
- Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
Map
- Map不是collection的子接口或者实现类。Map是一个接口。
- Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
- Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
- Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap.(HashMap、TreeMap最常用)
2.面试题:什么场景下使用list,set,map呢?优缺点
-
如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList。
-
如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
-
如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。LinkedHashSet 也按照元素的插入顺序对它们进行存储。
-
如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。你可以根据你的后续需要从 Hashtable、HashMap、TreeMap 中进行选择。
细说:
List 的实现类有 ArrayList,Vector 和 LinkedList:
- ArrayList: 底层使用数组来实现,可以自动扩容,初始容量默认是10,每一增加原有长度的一半,增删比较慢 查询速度快,线程不安全的一个列表
- LinkedList:增删快 查询慢 线程不安全,内存不连续
-
Vector是JDK1.0出现。底层使用数组实现,向量 :默认容量是10,默认每次扩容是原来容量的一倍如果是写了增量,那么是原来容量加增量。直接子类是Stact,增删慢 查询快,Vector的方法都是同步方法,线程安全的集合。 .Enumeration 是JDK1.0出现。现在不常用。被iterator替代。Collention接口的父接口是Iterable,增强for循环本质上就是迭代器.
-
stack栈是vector的子类,先进后出。入栈/压栈:存数据,将数据从栈顶压到栈底。出栈/弹栈:取数据,将数据从栈底传到栈顶。
-
Queue(队列):先进先出, 接口,父接口Collection
- 数组可以储存对象,也可以存储基本数据类型 Person[]
集合中不可以存放基本数据类型:Collection<E> :泛型,泛型的类型必须是引用数据类型 -
如果数据决定了查询和增删效率差不多,那么优先选择ArrayList还是LinkedList??
选择LinkedList。因为LinkedList是内存不连续的。ArrayList是内存连续的。
Set 散列集合的实现类有 HashSet 和 TreeSet;
- HashSet:底层是由哈希表,线程不安全,默认的初始容量是16,HashSet指定初始容量,底层会保证结果一定是2的n次方的形式:10-->16 17 -->32
- LinkedHashSet:是有序的集合(每一个桶之间的链表也有联系),不会扩容
- TreeSet:TreeSet 使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序。
Map 接口有三个实现类:Hashtable,HashMap,TreeMap,LinkedHashMap;
- HashMap实现不同步,线程不安全。 HashTable线程安全 , HashMap中的key-value都是存储在Entry中的。 HashMap可以存null键和null值,不保证元素的顺序恒久不变,通过hashCode()方法和equals方法保证键的唯一性
- Hashtable:内部存储的键值对是无序的是按照哈希算法进行排序,与 HashMap 最大的区别就是线程安全。键或者值不能为 null,为 null 就会抛出空指针异常。
- LinkedHashMap:有序的 Map 集合实现类,相当于一个栈,先 put 进去的最后出来,先进后出。
- TreeMap:基于红黑树 (red-black tree) 数据结构实现,按 key 排序,默认的排序方式是升序。
补充:HashMap实际上是一个 HashMap 实例,HashMap底层是数组加链表,数组的每一个位置称之为桶,每一个桶上都是链表,存数据的时候,先计算是0-15哪个值,寻找对应的桶,在存放进桶的时候,要先和桶里面的数据进行对比,如果相等就舍弃,存放在链表的最前面,扩容后,会将原来的所有数据重新计算,重新存放的过程叫rehash,扩容的加载因子的0.75(太小频繁扩容,太大链表内容会过多,影响效率),初始容量是16(太小:链表内容会过多,影响效率太大:浪费内存),每次扩容都会增加原来的一倍的数量,注意:,一个桶中存放10000个数据,其他桶中没有数据,那么会不会扩容,不会JDK1.8之后(链表长度超过8之后)会将桶中的链式栈结构扭转为二叉树结构,从而提升效率,)支持的;是无序的,不保证 set 元素的迭代顺序,不保证数据的位置(数据位置是可能发生改变的)
解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap是采用拉链法解决哈希冲突的。
- 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位;
- 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
- 拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小。
2. Hashtable和HashMap的区别:
a) 继承不同。 public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
b) HashMap线程不安全。 HashTable线程安全 。Hashtable中的方法是同步的,而HashMap中的方法是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
c) Hashtable 中, key 和 value 都不允许出现 null 值。 在 HashMap 中, null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。
d) 两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
e) 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
f) Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。
- HashTable中hash数组默认大小是11,增加的方式是old*2+1。
- HashMap中hash数组的默认大小是16,而且一定是2的指数。
- 注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。
- HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。
ConcurrentHashMap
- ConcurrentMap 并发映射:继承了Map接口,支持并发操作,并且保证并发操作过程中的安全性
- ConcurrentHashMap 并发哈希映射:实现了Map接口,底层是基于数组+链表结构来存储,默认初始容量是16,默认加载因子是0.75,每次扩容默认增加一倍
- 异步式线程安全:采取的分桶/段锁机制 - 当有线程操作某个键值对的时候,会将这个键值对所在的桶整体锁起来,此时不影响其他桶的操作
- 在后续版本中,为了提高效率,在分桶锁的基础上,引入了读写锁:读锁:允许多个线程同时读,但是不允许线程写。写锁:只允许一个线程写, 但是不允许线程读
- 在CPU中,一旦引入了线程锁的问题,就会导致CPU的资源耗费比较严重,也因此在JDK1.8中,引入了无锁算法CAS(和内核相关的,必须计算机内核支持才能使用CAS) - Compare And Swap - 我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少 - 避免锁机制带来的CPU开销。先并发进程,在抢占进程,提高并发。
- 从JDK1.8开始,在桶的基础上引入了红黑树机制。当桶中的元素超过8个的时候,会将桶中的链表扭转成一棵红黑树;如果桶中的元素不足7个,则将红黑树再扭转回链表,树化的最小容量是64桶数量,>=64,才会启动红黑树,