集合详解
List集合:
Map集合:
- 集合只能存放对象。比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。
- 集合可以存放不同类型,不限数量的数据类型。
- 集合存放的是多个对象的引用,对象本身还是放在堆内存中。
- 剩下的点击链接吧,毕竟人家已经特别用心准备了,当然写的也特别好。
推荐链接1:集合讲解
推荐链接2:重在集合特点及集合区别
推荐链接3:集合API
推荐链接4:集合与泛型
集合与泛型:
集合:
推荐链接5:Map集合遍历及常用API等
推荐链接6:集合详解
queue集合示例
数组是很常用的一种的数据结构,我们用它可以满足很多的功能,但是,有时我们会遇到如下这样的问题:
1、我们需要该容器的长度是不确定的。
2、我们需要它能自动排序。
3、我们需要存储以键值对方式存在的数据。
如果遇到上述的情况,数组是很难满足需求的,接下来本章将介绍另一种与数组类似的数据结构——集合类,集合类在Java中有很重要的意义,保存临时数据,管理对象,泛型,Web框架等,很多都大量用到了集合类。
下面的表格也许可以更直接的表现出他们之间的区别和联系:
接口 | 简述 | 实现 | 操作特性 | 成员要求 |
Set | 成员不能重复 | HashSet | 外部无序地遍历成员 | 成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 |
TreeSet | 外部有序地遍历成员;附加实现了SortedSet, 支持子集等要求顺序的操作 | 成员要求实现caparable接口,或者使用 Comparator构造TreeSet。成员一般为同一类型。 | ||
LinkedHashSet | 外部按成员的插入顺序遍历成员 | 成员与HashSet成员类似 | ||
List | 提供基于索引的对成员的随机访问 | ArrayList | 提供快速的基于索引的成员访问,对尾部成员的增加和删除支持较好 | 成员可为任意Object子类的对象 |
LinkedList | 对列表中任何位置的成员的增加和删除支持较好,但对基于索引的成员访问支持性能较差 | 成员可为任意Object子类的对象 | ||
Map | 保存键值对成员,基于键找值操作,compareTo或compare方法对键排序 | HashMap | 能满足用户对Map的通用需求 | 键成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 |
TreeMap | 支持对键有序地遍历,使用时建议先用HashMap增加和删除成员,最后从HashMap生成TreeMap;附加实现了SortedMap接口,支持子Map等要求顺序的操作 | 键成员要求实现caparable接口,或者使用Comparator构造TreeMap。键成员一般为同一类型。 | ||
LinkedHashMap | 保留键的插入顺序,用equals 方法检查键和值的相等性 | 成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 | ||
IdentityHashMap | 使用== 来检查键和值的相等性。 | 成员使用的是严格相等 | ||
WeakHashMap | 其行为依赖于垃圾回收线程,没有绝对理由则少用 |
|
(上图来源于网友的总结,已不知是哪位的原创,恕不贴出地址,如原作者看到请联系我,必将贴出链接!)
实现Collection接口的类,如Set和List,他们都是单值元素(其实Set内部也是采用的是Map来实现的,只是键值一样,从表面理解,就是单值),不像实现Map接口的类一样,里面存放的是key-value(键值对)形式的数据。这方面就造成他们很多的不同点,如遍历方式,前者只能采用迭代或者循环来取出值,但是后者可以使用键来获得值得值。
我们知道List是可以自动扩容的,这个功能就取决于如下的方法: ensureCapacity(int paramInt)用来初始化或者扩大ArrayList的空间。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。
ArrayList基于数组实现,所以它具备数组的特点,即查询速度较快,但是修改、插入的速度却有点儿慢,但是,下面将要介绍的LinkedList就是来解决这个问题的,LinkedList基于链表,与ArrayList互补,所以实际开发中我们应该按照自己的需求来定到底用哪一个。
LinkedList底层采用双向循环列表实现,进行插入和删除操作时具有较高的速度,我们还可以使用LinkedList来实现队列和栈。
这是LinkedList的原始存储模型,因为是双向循环列表,我们可以回忆一下数据结构中双向列表是什么情况:一个数据data,两个指针,一个指向前一个节点,名为previous,一个指向下一个节点,名为next
头尾相等,就是说初始化的时候就已经设置成了循环的。仔细观察源码,不难理解,如果熟悉数据结构的读者,一定很快就能掌握她的原理。
我们先来观察下Entity类,构造方法有三个参数,第二个是她的next域,第三个是她的previous域,所以上述代码1行处将传进来的entry实体,即header对象作为newEntry的next域,而将entry.previous即header.previous作为previous域。也就是说在header节点和header的前置节点之间插入新的节点。看下面的图:
一目了然。
实现Map接口的
HashMap
关于HashMap的详细介绍,请看深入解析HashMap章节
WeakHashMap
理解该集合类之前,建议先去了解Java的垃圾回收机制,WeakHashMap多用于缓存系统,就是说在系统内存紧张的时候可随时进行GC,但是如果内存不紧张则可以用来存放一些缓存数据。因为如果使用HashMap的话,它里面的值基本都是强引用,即使内存不足,它也不会进行GC,这样系统就会报异常。
Entry继承了WeakReference类,且在构造函数中构造了Key的弱引用,当进行put或者get操作时,都会调用一个函数叫expungeStaleEntries()
就是用来判断,如果key存在弱引用,则进行垃圾回收,所以这个就是WeakHashMap的工作原理。它与HashMap的区别就是:数据量大的时候,它可根据内存的情况,自动进行垃圾回收。如果手动将key置为强引用,那么它将和HashMap变得一样,失去其功能。
三、比较(性能,功能方面)
这一块主要就是对我们平时接触的这些集合类做一个简单的总结,一方面有助于自己整理思路,再者面试的时候,面试官总喜欢问一些他们之间的区别,凡是Java面试,几乎都要问到集合类的东西,问的形式有两种:一、总体介绍下集合类有哪些。这个问题只要把我上文中的图介绍一下就行了。二、比较一下XXX和XXXX。当然了,肯定包括相同点和不同的地方。这个稍微麻烦一点,需要我们彻底理解了,才能回答的比较准确。以下是我对常被比较的一些类的分析:
1、HashMap和HashTable
相同点:二者都实现了Map接口,因此具有一系列Map接口提供的方法。
不同点:
1、HashMap继承了AbstractMap,而HashTable继承了Dictionary。
2、HashMap非线程安全,HashTable线程安全,到处都是synchronized关键字。
3、因为HashMap没有同步,所以处理起来效率较高。
4、HashMap键、值都允许为null,HashTable键、值都不允许有null。
5、HashTable使用Enumeration,HashMap使用Iterator。
这些就是一些比较突出的不同点,实际上他们在实现的过程中会有很多的不同,如初始化的大小、计算hash值的方式等等。毕竟这两个类包含了很多方法,有很重要的功能,所以其他不同点,请感兴趣的读者自己去看源码,去研究。笔者推荐使用HashMap,因为她提供了比HashTable更多的方法,以及较高的效率,如果大家需要在多线程环境中使用,那么用Collections类来做一下同步即可。
2、Set接口和List接口
相同点:都实现了Collection接口
不同点:
1、Set接口不保证维护元素的顺序,而且元素不能重复。List接口维护元素的顺序,而且元素可以重复。
2、关于Set元素如何保证元素不重复,我将在下面的博文中给出。
3、ArrayList和LinkList
相同点:都实现了Collection接口
不同点:ArrayList基于数组,具有较高的查询速度,而LinkedList基于双向循环列表,具有较快的添加或者删除的速度,二者的区别,其实就是数组和列表的区别。上文有详细的分析。
4、SortedSet和SortedMap
二者都提供了排序的功能。
SortedMap具有自动排序功能
5、TreeMap和HashMap
HashMap具有较高的速度(查询),TreeMap则提供了按照键进行排序的功能。
6、HashSet和LinkedHashSet
HashSet,为快速查找而设计的Set。存入HashSet的对象必须实现hashCode()和equals()。
LinkedHashSet,具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序),于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
7、TreeSet和HashSet
TreeSet: 提供排序功能的Set,底层为树结构 。相比较HashSet其查询速度低,如果只是进行元素的查询,我们一般使用HashSet。
8、ArrayList和Vector
同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的。
数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
9、Collection和Collections
Collection是一系列单值集合类的父接口,提供了基本的一些方法,而Collections则是一系列算法的集合。里面的属性和方法基本都是static的,也就是说我们不需要实例化,直接可以使用类名来调用。下面是Collections类的一些功能列表:
生成单元素集合
Collections中的单元素集合指的是集合中只有一个元素而且集合只读。
Collections.singletonList——用来生成只读的单一元素的List
Collections.singletonMap——用来生成只读的单Key和Value组成的Map
Collections.singleton——用来生成只读的单一元素的Set
Checked集合
Checked集合具有检查插入集合元素类型的特性,例如当我们设定checkedList中元素的类型是String的时候,如果插入其他类型的元素就会抛出ClassCastExceptions异常,Collections中提供了以下生成Checked集合的方法checkedCollection,checkedList,checkedMap,checkedSet,checkedSortedMap,checkedSortedSet
同步集合
Collections类提供一系列同步方法,为一些非线程安全的集合类提供同步机制。
查找替换
fill——使用指定元素替换指定列表中的所有元素。
frequency——返回指定 collection 中等于指定对象的元素数。
indexOfSubList—— 返回指定源列表中第一次出现指定目标列表的起始位置,如果没有出现这样的列表,则返回 -1。
lastIndexOfSubList——返回指定源列表中最后一次出现指定目标列表的起始位置,如果没有出现这样的列表,则返回-1。
max—— 根据元素的自然顺序,返回给定 collection 的最大元素。
min——根据元素的自然顺序 返回给定 collection 的最小元素。
replaceAll——使用另一个值替换列表中出现的所有某一指定值。
Collections还提供了集中对集合进行排序的方法。
reverse——对List中的元素进行转置
shuffle——对List中的元素随即排列
sort——对List中的元素排序
swap——交换List中某两个指定下标位元素在集合中的位置。
rotate——循环移动
总结一下,带Tree的集合类,底层一般是基于二叉树的,所以具有自动排序功能。有些功能方面差别不大,具体开发的时候需根据实际情况选择使用哪个类。
四、常见问题这块内容,我讲就一些常见的问题做一下分析,欢迎广大读者提出更多的问题,我们一起讨论,解决!
1、Set集合如何保证对象不重复
这儿我采用HashSet来实现Set接口
说明,Set集合不允许有重复出现的对象,且最终的判断是根据equals()的。其实原理是这样的:HashSet的底层采用HashMap来存放数据,HashMap的put()方法是这样的:
当向HashMap中添加元素的时候,首先计算元素的hashcode值,然后根据1处的代码计算出Hashcode的值,再根据2处的代码计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则看3-4的代码,遍历索引为i的链上的元素,如果key重复,则替换并返回oldValue值。<关于这部分内容,请看另一篇文章:《深入解读HashMap》有详细介绍>
2、集合类排序问题
一种情况是集合类本身自带排序功能,如前面说过的TreeSet、SortedSet、SortedMap等,另一种就是本身不带排序功能,我们通过为需要排序的类实现Comparable或者Comparator接口来实现。
一个是实现Comparable的,一个是实现Comparator的。
Comparator和Comparable用于不同的场景,实现对对象的比较从而进行排序。总结为:
相同点:
1、二者都可以实现对象的排序,不论用Arrays的方法还是用Collections的sort()方法。
不同点:
1、实现Comparable接口的类,似乎是预先知道该类将要进行排序,需要排序的类实现Comparable接口,是一种“静态绑定排序”。
2、实现Comparator的类不需要,设计者无需事先为需要排序的类实现任何接口。
3、Comparator接口里有两个抽象方法compare()和equals(),而Comparable接口里只有一个方法:compareTo()。
4、Comparator接口无需改变排序类的内部,也就是说实现算法和数据分离,是一个良好的设计,是一种“动态绑定排序”。
5、Comparator接口可以使用多种排序标准,比如升序、降序等。
3、使用for循环删除元素陷阱
先来看看下面这个程序:
-
public
class Test {
-
-
public static void main(String[] args) {
-
List<String> list =
new LinkedList<String>();
-
list.add(
"A");
-
list.add(
"B");
-
list.add(
"C");
-
-
for(
int i=
0; i<list.size(); i++){
-
list.remove(i);
-
}
-
-
for(String item:list){
-
System.out.println(item);
-
}
-
}
-
}
读者朋友们可以先猜猜这个程序输出什么?按我们的思路,应该是输不出什么,但是执行它,输出的却是:B。这是为什么呢?我们分部分析下这个程序,当地一步remove完后,集合内还剩2个元素,此时i为1,而list.size()的值为2,从0开始的话,i为1时,正好指向第二个元素,也就是说当remove完A后,直接就跳到C,将B漏了。
解决办法:
-
public
class Test {
-
-
public static void main(String[] args) {
-
List<String> list =
new LinkedList<String>();
-
list.add(
"A");
-
list.add(
"B");
-
list.add(
"C");
-
-
for(
int i=
0; i<list.size(); i++){
-
list.remove(i);
-
i -=
1;
//每次删除完后,i减少1
-
}
-
-
for(String item:list){
-
System.out.println(item);
-
}
-
}
-
}