-
什么是集合类
简单来讲:集合就是一个放数据的容器,准确的说是放数据对象引用的容器
- 集合类存放于java.util包中。
- 集合类型主要有3种:set(集)、list(列表)和map(映射)。
- 集合存放的都是对象的引用,而非对象本身。所以我们称集合中的对象就是集合中对象的引用。
-
集合类有哪些
-
Set
-
List和Set都是集合,一般来说:如果我们需要保证集合的元素是唯一的,就应该想到用Set集合,比如说:现在要发送一批消息给用户,我们为了减少「一次发送重复的内容给用户」这样的错误,我们就用Set集合来保存用户的信息
一般我们在开发中最多用到的也就是HashSet。TreeSet是可以排序的Set,一般我们需要有序,从数据库拉出来的数据就是有序的,可能往往写order by id desc
比较多。而在开发中也很少管元素插入有序的问题,所以LinkedHashSet一般也用不上。
如果考虑线程安全的问题,可以考虑CopyOnWriteArraySet,用得就更少了(这是一个线程安全的Set,底层实际上就是CopyWriteOnArrayList);TreeSet和LinkedHashSet更多的可能用在刷算法的时候。
1) Set 集合属于单列集合,不允许包含重复元素;
2) 判断元素是否重复的标准为对象的 equals 方法,存在时返回 false,不存在返回 true;
3) 元素的排序规则,由相应的实现类决定,分为无序、元素大小排序、写入顺序排序;
4) 初始化大小,扩容参考 HashMap。
名称 | 类型 | 线程同步 | 描述 |
---|---|---|---|
Set | 接口 | 继承了Collection接口 | |
SortedSet | 接口 | 继承了Set接口 | |
HashSet | 实现类 | 不同步 | 继承了AbstractSet类,实现了Set、Cloneable、Serializable接口 |
TreeSet | 实现类 | 不同步 | 继承了AbstractSet类,实现了NavigableSet(继承了SortedSet)、Cloneable、Serializable接口 |
LinkedHashSet | 实现类 | 不同步 | 继承了HashSet类,实现了Set、Cloneable、Serializable接口 |
-
HsahSet
1) HashSet 实现了 Set 接口,继承了 AbstractSet 类,由哈希表支持,看源码可以发现是一个 HashMap 实例。
2) HashSet 不保证集合内元素的迭代顺序,特别是不保证迭代顺序永久不变,该集合运行 null 元素存在。
3) HashSet 中的元素,作为 HashMap 键值对的 Key 存储,而 Value 由一个统一的值保存。
4) HashSet 默认初始化大小为 16,扩容加载因子为 0.75,扩容大小为原来的一倍。即一个初始化size为16的 HashSet,元素添加到12个的时候会进行扩容,扩容后的大小为32。
-
TreeSet
1) TreeSet 实现了 NavigableSet 接口,继承了AbstractSet类,由哈希表支持,看源码可以发现是一个 TreeMap 实例。
2) TreeSet 中的元素有序的,排序规则遵照元素本身的大小进行排序,元素不能重复。
3) TreeSet 中的元素,作为 TreeMap 键值对的 Key 存储,而 Value 由一个统一的值保存。
-
LinkedHashSet
1) LinkedHashSet 实现了 Set 接口,继承了HashSet类,由哈希表支持,看源码可以发现是一个 LinkedHashMap 实例。 2) LinkedHashSet 中的元素有序的,排序规则遵照元素写入顺序进行排序,元素不能重复。 3) LinkedHashSet 中的元素,作为 LinkedHashMap 键值对的 Key 存储,而 Value 由一个统一的值保存。
-
List
List集合下最常见的集合类有两个:ArrayList和LinkedList。在工作中,我都是无脑用ArrayList。我问了两个同事:“你们在项目中用过LinkedList吗?”他们都表示没有。
众所周知,ArrayList底层是数组,LinkedList底层是链表。数组遍历速度快,LinkedList增删元素快。
为什么在工作中一般就用ArrayList,而不用LinkedList呢?原因也很简单:
- 在工作中,遍历的需求比增删多,即便是增加元素往往也只是从尾部插入元素,而ArrayList在尾部插入元素也是O(1)
- ArrayList增删没有想象中慢,ArrayList的增删底层调用的
copyOf()
被优化过,加上现代CPU对内存可以块操作,普通大小的ArrayList增删比LinkedList更快。
所以,在开发中,想到要用集合来装载元素,第一个想到的就是ArrayList。LinkedList用在什么地方呢?我们一般用在刷算法题上。把LinkedList当做一个先进先出的队列,LinkedList本身就实现了Queue接口,如果考虑线程安全的问题,可以看看CopyWriteOnArrayList,实际开发用得不多,但我觉得可以了解一下它的思想(CopyWriteOn),这个思想在Linux/文件系统都有用到。
1) List 集合属于单列、有序的、允许元素重复、可以为 null 的集合;
2) List 接口的实现类主要有三种:ArrayList、LinkedList、Vector。
名称 | 类型 | 线程同步 | 描述 |
---|---|---|---|
List | 接口 | 继承了Collection接口 | |
ArrayList | 实现类 | 不同步 | 继承了AbstractList类,实现了List、RandomAccess、Cloneable、Serializable接口 |
LinkedList | 实现类 | 不同步 | 继承了AbstractSequentialList类,实现了List、Deque、Cloneable、Serializable接口 |
Vector | 实现类 | 同步 | 继承了AbstractList类,实现了List、RandomAccess、Cloneable、Serializable接口 |
-
Arraylist
1) ArrayList 实现了 List 接口,继承了 AbstractList 类,由一个 Object[] 实例实现,即底层为数组结构;
2) 默认初始化长度为 10,扩容规则为 0.5倍的原容量加1,即一次扩容后的长度为 16;
3) 特点:查询速度快,添加、删除相对于LinkedList较慢、线程不同步(不安全)。
-
LinkedList
1) LinkedList 实现了 List 接口,继承了 AbstractSequentialList 类,由一个 Node 节点链表实现,即底层为链表结构;
2) 由于LinkedList 数据结构为链表,无预扩容机制;
3) 特点:添加、删除速度快,查询相对于ArrayList较慢、线程不同步(不安全)。
-
Vector
1) Vector实现了 List 接口,继承了 AbstractList 类,由一个 Object[] 实例实现,即底层为数组结构;
2) 默认初始化长度为 10,扩容加载因子为 1,当元素长度大于原容量时进行扩容,默认为 10,一次扩容后容量为 20;
3) 特点:线程安全,但是速度慢;在实现的方法上,用 synchronized 关键字进行了修饰,即在方法上实现了同步锁。
-
Map
Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap
如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。
HashMap在实际开发中用得也非常多,只要是key-value
结构的,一般我们就用HashMap
。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。
ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了
名称 | 类型 | 线程同步 | 描述 |
---|---|---|---|
Map | 接口 | ||
HashMap | 实现类 | 不同步 | 继承了AbstractMap类,实现了Map、Cloneable、Serializable接口 |
LinkedHashMap | 实现类 | 不同步 | 继承了HashMap类,实现了Map接口 |
TreeMap | 实现类 | 不同步 | 继承了AbstractMap类,实现了NavigableMap(继承了SortedMap)、Cloneable、 Serializable接口 |
Hashtable | 实现类 | 同步 | 继承了Dictionary类,实现了Map、Cloneable、Serializable接口 |
ConcurrentHashMap | 实现类 | 同步 | 继承了AbstractMap类,实现了ConcurrentMap(继承了Map)、Serializable接口 |
-
HashMap
1) HashMap实现了 Map接口,继承了 AbstractMap类,数据结构采用的位桶数组,底层采用链表或红黑树进行存储;
2) 默认初始化长度为 16,扩容加载因子为 0.75,一旦大于 0.75*16之后,就会调用resize()进行扩容,扩容2倍,即32;
3) JDK1.7中,数据结构采用:位桶数组+链表结构; JDK1.8中,数据结构采用:位桶数组+(链表/红黑树);
4) 支持克隆,无序,线程不同步,非安全。
-
LinkedHashMap
1) LinkedHashMap 实现了 Map 接口,继承了 HashMap 类; 引用实现;
2) 迭代顺序由 accessOrder 属性的决定,默认为 false,以插入顺序访问;设置为 true 则按上次读取顺序访问(每次访问 元素时会把元素移动到链表末尾方便下次访问,结构会时刻变化)。
3) 默认初始化长度为 16,扩容加载因子为 0.75,一旦>0.75*16之后,就会调用resize()进行扩容,与HashMap一致;
4) 支持克隆,有序,线程不同步,非安全。
-
TreeMap
1) TreeMap实现了 NavigableMap接口,继承了 AbstractMap 类;
2) 数据结构基于红黑树实现;
3) 该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法;
4) 无初始化长度;
5) 支持克隆,有序,线程不同步,非安全。
-
HashTable
1) Hashtable实现了 Map 接口,继承了 Dictionary类;
2) 数据结构:也是一个散列表,数据结构与HashMap相同,key、value都不可以为 null;
3) 该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法;
4) 默认初始化长度为 11,扩容加载因子为 0.75,一旦>0.75*11之后,就会进行扩容,扩容到2n+1,即23;
5) 支持克隆,无序,线程同步,安全。在实现的方法上,用 synchronized 关键字进行了修饰,即在方法上实现了同步锁。
6) 支持 Enumeration 遍历方式。
-
ConcurrentHashMap
1) ConcurrentHashMap实现了 ConcurrentMap接口,继承了 AbstractMap类;
2) 默认初始化长度为 16,扩容加载因子为 0.75,一旦大于 0.75*16之后,就会调用resize()进行扩容,扩容2倍,即32;
3) 数据结构:由Segment数组结构和HashEntry数组结构组成,一个ConcurrentHashMap中包含一个Segment数组, Segment的结构和HashMap类似,是一种数组和链表结构。
4) 使用了锁分段技术,Segment是一种可重入锁ReentrantLock,每个Segment拥有一个锁,当对HashEntry数组的 数据进行修改时,必须先获得对应的Segment锁
5) 不支持克隆,无序,线程同步,安全。
-
什么时候考虑线程安全
由于 HashSet、TreeSet、LinkedHashSet的底层实现为HashMap、TreeMap、LinkedHashMap,所以Set集合是非线程安全的。 如果要实现 Set 集合的线程安全,可以使用 ConcurrentHashMap 实现一个Set集合。
什么时候考虑线程安全的集合类,那当然是线程不安全的时候咯。那什么时候线程不安全?最常见的是:操作的对象是有状态的
虽然说,我们经常会听到线程不安全,但在业务开发中要我们程序员处理线程不安全的地方少之又少。比如说:你在写Servlet的时候,加过syn/lock
锁吗?应该没有吧?
因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了。
一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类。
总结
List和Set总结:
(1)List,Set都是继承自Collection接口,Map则不是
(2)List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
(3)Set和List对比:Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
ArrayList与LinkedList的区别和适用场景
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
ArrayList与Vector的区别和适用场景
ArrayList有三个构造方法:
public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。
public ArrayList() //默认构造一个初始容量为10的空列表。
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表
Vector有四个构造方法:
public Vector()//使用指定的初始容量和等于0的容量增量构造一个空向量。
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量
ArrayList和Vector都是用数组实现的,主要有这么三个区别:
(1).Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;
(2)两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。
*(3)*Vector可以设置增长因子,而ArrayList不可以。
*(4)*Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。
适用场景分析:
1.Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。
2.如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。
Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。请注意!!!, Map 没有继承 Collection 接口, Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。一个 Map 中不能包含相同的 key ,每个 key 只能映射一个 value 。 Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。
HashMap 非线程安全
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
适用场景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。
HashMap:适用于Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
线程安全集合类与非线程安全集合类
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的。
数据结构
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序