一、集合概述
Java
集合为了保存数量不确定的数据,以及保存具有映射关系的数据。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类,集合类位于java.util
包下。下面是集合类的两大框架:
Collection
接口里定义了如下操作集合元素的方法:
boolean add(Object o)
:用于向集合里添加一个元素boolean addAll(Collection c)
:该方法把集合c
里面的所有的元素添加到指定集合里void clear()
:清除集合里的所有元素,将集合长度变为0boolean contains(Object o)
:返回集合里是否包含指定元素boolean containsAll(Collection c)
:返回集合是否包含集合c
里的所有元素boolean isEmpty()
:返回集合是否为空Interator iterator()
:返回一个Interator
对象,用于遍历集合里的元素boolean remove(Object o)
:删除集合中指定元素boolean removeAll(Collection c)
:从集合中删除集合c
里的包含的所有元素boolean retainAll(Collections c)
:从集合中删除集合c
里不包含的元素(取并集)int size()
:该方法返回集合里元素的个数Object[] toArray()
:该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素Collection
接口已重写toString()
方法
二、Set 集合
2.1 HashSet
当向HashSet
集合中存入一个元素时,HashSet
会调用该对象的HashCode()
方法来得到该对象的hashCode
值,然后根据该hashCode
值决定该对象在HashSet
中的存储位置。如果有两个元素通过equals()
返回比较返回true
,但它们的hashCode()
方法返回值不相等,HashSet
将会把它们存储在不同的位置,依然可以添加成功。
hash
算法的功能:它能快速查找被检查的对象,hash
算法的价值在于速度。当需要查询集合中某个元素时,hash
算法可以直接根据该元素的hashCode
值计算出该元素的存储位置。
HashSet
中每个能存储元素的“槽位”通常称为"桶"(bucket
),如果有多个元素的hashCode
值相同,但它们通过equals()
方法比较返回false
,就需要在一个“桶”里放多个元素,这样会导致性能下降。
HashSet的存储过程
当我们向HashSet
中添加一个数据时。会先调用该数据的hashCode
方法来决定该数据在数组中所存储的位置。
如果该位置上没有其他的元素,则将该数据直接存放即可。如果该位置已经有了其他的元素,调用该元素所在类的equals
方法进行比较。如果返回值是个true
则认为两个数据相同则不能存放。如果返回值是个false
则以链表的形式将该数存在该位置上。
2.2 LinkHashSet
LinkHashSet
是HashSet的子类,LinkHashSet
集合也是根据元素的hashCode
值来决定元素的存储位置,但它同时使用链表维护元素的次序。LinkHashSet
按元素的添加顺序来访问集合里的元素。
LinkHashSet
需要维护元素的插入顺序,因此性能略低于HashSet
性能,但在迭代访问Set
里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
2.3 TreeSet
TreeSet
还提供了额外的方法Comparator compatator
:如果TreeSet
采用了定制排序,则该方法返回定制排序所使用的Comparator
;如果TreeSet
采用了自然排序,则返回null。
与HashSet
集合采用hash
算法来决定元素的存储位置不同,TreeSet
采用红黑树的数据结构来存储集合元素。
TreeSet
用到如下两种排序算法:
① 自然排序
实现Comparable
接口,重写compareTo(Object obj)
@Override
//当该类有多个属性需要比较时,多层嵌套if-else比较属性
public int compareTo(Object o) {
if(o instanceof Person){
Person p = (Person) o;
int tem = this.age.compareTo(p.age);
if(tem ==0){
return this.name.compareTo(p.name);
}else {
return tem;
}
}
return 0;
}
② 定制排序
实现Comparator
接口匿名实现类
Comparator com = new Comparator() {
@Override
//当该类有多个属性需要比较时,多层嵌套if-else比较属性
public int compare(Object o1, Object o2) {
Person p1 =(Person) o1;
Person p2 =(Person) o2;
int i =- (p1.getAge().compareTo(p2.getAge()));
if(i==0){
return - (p1.getName().compareTo(p2.getName()));
}else{
return i;
}
}
};
Map map = new TreeSet(com);
2.4 EnumSet
EnumSet
类是一个专门为枚举类设计的集合类,EnumSet
中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet
时,显示或隐式的指定。EnumSet
的集合元素也是有序,EnumSet
以枚举值在Enum
类内的定义顺序来决定集合元素的顺序。EnumSet
集合中不允许插入null
。
Enum
在内部以位向量的形式进行存储,这种存储形式非常紧凑、高效,因此EnumSet
对象占用内存很小,而且运行效率很好。尤其是进行批量操作(如调用containsAll()
、retainAll()
)时,如果其其参数也是EnumSet
集合,则该批量操作的执行效率也非常快。
EnumSet
类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet
对象。
enum Season{
SPRING,SUMMER,FALL,WINTER;
}
public class EnumSetTest {
@Test
public void test(){
//创建EnumSet集合的方法,集合元素就是枚举类的所有元素
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);//[SPRING, SUMMER, FALL, WINTER]
//创建一个EnumSet空集合,指定其集合元素是该类的枚举值
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2);//[]
//手动添加两个元素(只能添加上枚举类的值)
es2.add(Season.SPRING);
es2.add(Season.SUMMER);
System.out.println(es2);//[SPRING, SUMMER]
//已指定枚举创建EnumSet集合
EnumSet es3 = EnumSet.of(Season.SPRING,Season.FALL);
System.out.println(es3);//[SPRING, FALL]
//创建一个包含从from到to枚举值范围内所有枚举值的EnumSet集合。
EnumSet es4 = EnumSet.range(Season.SPRING,Season.FALL);
System.out.println(es4);//[SPRING, SUMMER, FALL]
//es5中的枚举值=全部枚举值-es4
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5);//[WINTER]
//copy
EnumSet es6 = EnumSet.copyOf(es5);
System.out.println(es6);//[WINTER]
}
}
2.5 Set实现类性能分析
HashSet
和TreeSet
是Set
的两个典型。HashSet
的性能总是比TreeSet
好(特别是常用的添加、查询元素等操作),因为TreeSet
需要额外的红黑树算法来维护集合元素的次数。只有需要一个保持次序的Set
时,才应该使用TreeSet
,否则都应该使用HashSet
。
HashSet
还有一个子类:LinkHashSet
,对于普通的插入、删除操作,LinkHashSet
比HashSet
要略慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkHashSet
会更块。
EnumSet
是所有Set
实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
必须指出的是,Set
的三个实现类HashSet
、TreeSet
和EnumSet
都是线程不安全的。如果有多个线程同时访问一个Set
集合,并且有一个线程修改了该Set
集合,则必须手动该Set
集合的同步性。通常可以通过Collections
工具类的synchronizedSortedSet
方法来“包装”该集合。此操作最好再创建时进行,以防止对Set
集合的意外非同步访问
三、List 集合
3.1 List 解析
List
作为Collection
接口的子接口,当然可以使用Collection
接口里的全部方法。而且由于List
是有序集合,因此List
集合里增加了一些根据索引来操作集合元素的方法:
void add(int index,Object element)
:将元素element
插入到List
集合的index
处boolean addAll(int index,Collection c)
:将集合c
所包含的所有的元素都插入到List
集合的Index
处Object get(int index)
:返回集合index
索引出的元素int indexOf(Object o)
:返回对象o
在List
集合中第一次出现的位置索引int lastIndexOf(Object o)
:返回对象o
在List
集合中最后一次出现的位置索引Object remove(int index)
:删除并返回index
索引处的元素Object set(int index,Object element)
:将index
索引处的元素替换成element
对象,返回被替代的旧元素List subList(int fromIndex,int toIndex)
:返回从索引fromIndex
(包含)到索引toIndex
(不包含)处所有集合元素组成的子集合void replaceAll(UnaryOperator operatao)
:根据operator
指定的计算规则重新设置List
集合的所有元素void sort(Comparator c)
:根据Compatator
参数对List
集合的元素排序
与Set
只提供了一个interator()
方法不同,List
还额外提供了一个listIterator()
方法,该方法返回一个ListIterator
对象,ListIterator
接口继承了Iterator
接口,提供了专门操作List
的方法。ListIterator
接口在Iterator
接口的基础上增加了如下方法:
boolean hasPrevious()
:返回迭代器关联集合是否还有上一个元素Object previous()
:返回该迭代器的上一个元素void add(Object o)
:在指定位置插入一个元素
ListIterator
增加了向前迭代的功能
while(listIterator.hasPrevious()){
System.out.println(listIterator.previous());
}
3.2 ArrayList 和 Vector 实现类
ArrayList
和Vector
类都是基于数组实现的List
类,所以ArrayList
和Vector
类封装了一个动态的、允许在分配的Object[]
数组。ArrayList
或Vector
对象使用initialCapacity
参数来设置该数组的长度。当向ArrayList
或Vector
中添加元素超过了该数组的长度时,他们的initialCapacity
会自动增加。
若已知该数组的大小可以手动设置initialCapacity
的值。ArrayList
和Vector
提供了如下两个方法啦重新分配Object[]
数组。
void ensureCapacity(int minCapacity)
:将ArrayList
和Vector
集合中的Object[]
数组长度增加大于或等于minCapacity
值void trimToSize()
:调整ArrayList
和Vector
集合的Object[]
数组长度为当前元素的个数。调用该方法可以减少集合对象占用的存储空间
ArrayList
是线程不安全的,Vector
是线程安全的,但是ArrayList
的性能比Vector
要好的多,所以ArrayList
被大部分使用。
Vector
提供一个Stack
子类,它用于模拟“栈”这种数据接口,“栈”通常是指“后进先出(LIFO
)”的容器。最先push
进栈的元素,将最先被pop
出栈。Stack
提供了一下几个方法:
Object peak()
:返回“栈”的第一个元素,但并将该元素pop
出栈Object pop()
:返回“栈”的第一个元素,并将该元素pop
出栈void push(Object item)
:将一个元素push
进栈,最后一个进“栈”的元素总是位于"栈"顶
需要指出的是,由于Stack
继承了Vector
,因此他也是一个非常古老的Java
集合类,它同时是线程安全、性能较差的,因此要尽量少用Stack
类。如果程序需要使用“栈”这种数据结构,则可以考虑后面介绍的ArrayDeque
。ArrayDeque
也是List
的实现类,ArrayDeque
即实现了List
接口,也实现了Deque
接口。
3.3 固定长度的 List
Arrays
工具类里提供了asLIst(Object…a)
方法,该方法可以把一个数组或指定个数的对象转换成一个List
集合,这个List
集合既不是ArrayList
实现类的实例,也不是Vector
实现类的实例,而是Arrays
的内部类ArrayList
的实例。
Arrays.ArrayList
是一个固定的长度的List
集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。
@Test
public void test(){
List fixedList = Arrays.asList("AA","BB",123);
//获取fixedList的实现类,将输出Arrays$ArrayList
System.out.println("fixedList.getClass()");
//使用方法遍历集合元素
fixedList.forEach(System.out::println);
//试图增加、删除元素都会引发异常,该ArrayList不可被操作
}
3.4 ArrayList 扩容
通过ArrayList
空参的构造器创建对象。底层会创建一个长度为10的数组。当我们向数组中添加第11个元素时底层会进行扩容,扩容为原来的1.5倍(创建一个新的数组长度为原来的1.5倍并将原数组中的内容添加到新的数组中)
3.5 各线性表的性能分析
Java
提供的List
就是一个线性表接口,而ArrayList
、LinkedList
又是线性表的两种典型表现;基于数组的线性表和基于链的线性表。Queue
代表了队列,Deque
代表了双端队列。
关于使用List
集合有如下的建议:
- 如果需要遍历
List
集合元素,对于ArrayList
、Vector
集合,应该使用随机访问方法(get
)来遍历集合元素,这样性能更好;对于LinkedList
集合,则应该采用迭代器来遍历集合元素 - 如果需要经常执行插入、删除操作来改变包含大量数据的
List
集合的大小,可考虑使用LinkedList
集合,使用ArrayList
、Vector
集合可能需要经常重新分配内部数组大小,效果肯能较差 - 如果有多个线程要同时访问
List
集合中的元素,开发者可考虑使用Collections
将集合包装成线程安全的集合
四、Queue 集合
4.1 Queue 解析
Queue
用于模拟队列这种数据结构,队列通常是指先进先出(FIFO
)的容器。队列的头部保存在队列中存放时间最长的元素,队列的尾部保存在队列中存放时间最短的元素。新元素插入(offer
)到队列的尾部,访问元素(poll
)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。
Queue
接口定义了如下几个方法:
void add(Object e)
:将指定元素加入此队列的尾部Object element()
:获取队列头部的元素,但是不删除该元素boolean offer(Object e)
:将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)
方法更好Object peak()
:获取队列头部的元素,但是不删除该元素。若队列为空,则返回null
Object pull()
:获取队列头部的元素,并删除该元素。若队列为空,则返回null
Object remove()
:获取队列头部的元素,并删除该元素
4.2 PriotityQueue
PriotityQueue
保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行排序。因此当调用peek()
或者poll()
方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。其违反了队列的最基本的规则:先进先出(FIFO
)。
@Test
public void test(){
PriorityQueue pq = new PriotityQueue();
pq.offer(6);
pq.offer(-3);
pq.offer(20);
pq.offer(18);
//输出pq队列,并不是按元素的加入顺序排列
System.out.println(pq); //输出[-3,6,20,18]
//访问队列的第一个元素,其实就是队列中最小的元素:-3
System.out.println(pq.poll);
}
运行上面程序直接输出PriotityQueue
集合时,可能看到该队列中的元素并没有很好地按大小进行排序,但这只是受到PriotityQueue
的toString()
方法的返回值的影响。实际上,程序多次调用poll
方法,即可看到元素按小到大的顺序移除队列。
同时PriotityQueue
也有两种排序:自然排序和定制排序。
4.3 Deque 和 ArrayDeque
Deque
接口是Queue
接口的子接口,它表示一个双端队列,Deque
接口里定义了 一些双端队列的方法,这些方法允许从两端来操作队列的元素(操作last
方法省略,将first
改为last
即可)
void addFirst(Object e)
:将指定元素插入到该双端队列的开头void addLast(Object e)
:将指定元素插入该双端队列的末尾Iterator descendingIterator()
:返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素Object getFirst(Object e)
:获取但不删除双端队列的第一个元素boolean offerFirst(Object e)
:将指定元素插入到该双端队列的开头Object peakFirst()
:获取双端队列头部的元素,但是不删除该元素。若队列为空,则返回null
Object pullFirst()
:获取双端队列头部的元素,并删除该元素。若队列为空,则返回null
Object pop()
(栈方法):pop
出该双端队列所表示的栈顶元素。相当于removeFirst()
方法Object push()
(栈方法):将一个元素push
进该双端队列所表示的栈的栈顶。相当于addFirst()
方法Object removeFirst()
:获取并删除该双端队列的第一个元素Object removeFirstOccurrence(Object o)
:删除该双端队列的第一个出现的元素
ArrayDeque
Deque
接口提供了一个典型的实现类:ArrayDeque
。它是一个基于数组实现的双端队列。创建Deque
时可以指定numElements
参数,若不指定,Deque
底层数组的长度为16。
ArrayDeque
即可以实现栈,也可以实现队列两种数据结构:
ArrayDeque
:栈(LIFO
)–使用push()
入栈,使用pop()
出栈ArrayDeque
:队列(FIFO
)—使用offer()
入队,使用poll()
出队
4.4 LInkedList
LinkedList
类是List
接口的实现类,同时实现了Deque
接口,可以被当做双端队列来实现,因此既可以被可以被当成“栈”来使用,还可以被当成队列使用。
LinkedList
与ArrayList
、ArrayDeque
的实现机制完全不同,ArrayList
、ArrayDeque
内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList
内部以链表的形式来保存集合中的元素,因此随机访问集合时性能较差,但是在插入、删除元素时性能比较出色。
五、Map 集合
5.1 Map 解析
Map
用于保存具有映射关系的数据,因此Map
集合里保存这两组值,一组值用于保存Map
里的Key
,一组值用于保存Map
里的Value
。Map
里的Key
不允许重复,Key
与Value
之间存在单向一对一关系。Map
包含一个keySet()
方法,用于返回Map
里所有Key
组成的Set
集合。Map
提供了一个Entry
内部类来封装KV
对,而计算Entry
存储时则只考虑Entry
封装的Key
。从Java
源码来看,Java
是先实现了Map
,然后通过包装一个所有Value
为null
的Map
就实现了Set
集合。
Map
接口中定义了常用的接口:
-
void clear()
:删除该Map
对象中的所有KV
对 -
boolean containsKey(Object key)
:查询Map
中是否包含指定的Key
-
boolean containsValue(Object value)
:查询Map
中是否包含一个或多个Value
-
Set entrySet()
:返回Map
中包含的KV
对所组成的Set
集合,每个集合元素都是Map.Entry
对象 -
Set keySet()
:返回该Map
中所有Key
组成的Set
集合 -
Collection values()
:返回该Map
里所有Value
组成的Collection
-
Object put(Object key,Object value)
:添加一个KV
,若添加的Key
已存在,那么会覆盖之前的KV
-
Object get(Object key)
:返回指定Key
所对应的Value
-
boolean isEmpty()
:查询该Map
是否为空 -
void putAll(Map m)
:将指定Map
中的所有KV
对赋值到该Map中 -
Object remove(Object key)
:删除指定Key
所对应的KV
队,返回被删除所Key
所对应的Value
-
int size()
:返回Map
里的KV
对的个数 -
Map
中包含一个内部类Entry
,该类封装了一个KV
对。Entry
包含3个方法:Object getKey()
:返回该Entry
里包含的Key
值Object getValue()
:返回该Entry
里包含的Value
值Object setValue(value)
:设置该Entry
里包含的Value
值,并返回新设置的Value
值
遍历Map
//遍历keySet
for(Object key:map.keySet()){
//map.get(key)方法获取指定key对应的value
System.out.println(key+"-->"+map.get(key));
}
//遍历entrySet
for(Object o :map.entrySet()){
Map.Entry entry = (Map.Entry)o;
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
5.2 HashMap 和 HashTable
HashMap
和HashTable
的关系就像ArrayList
和Vector
,后者差不多被淘汰了,很少被使用。后者线性安全不能插入null
作为元素,通Vector
一样。
HashMap
的Key
为Set
,Set
集合中的对象必须实现equals()
和hashCode()
方法。为了成功在HashMap
中存储、获取对象,用做key
的对象必须实现equals()
和hashCode()
方法。
5.3 LinkedHashMap 实现类
LinkedHashMap
是HashMap
的子类;LinkedHashMap
也使用双向链表来维护KV
对的次序,该链表负责维护Map
的迭代顺序,迭代顺序与KV
对的插入顺序保持一致。
LinkedHashMap
可以避免对HashMap
、Hashable
里的KV
对进行排序(只要插入KV
对时保持顺序即可),同时又避免使用TreeMap
所增加的成本
LinkedHashMap
需要维护元素的插入顺序,因此性能略低于HashMap
的性能。但因为它以链表来维护内部顺序,所以在迭代访问Map
里的全部元素时将有较好的性能。
5.4 Properties
Properties
类是Hashtable
类的子类,正如它的名字所暗示,该对象在处理属性文件时特别方法(Windows
操作平台上的ini
文件就是一种属性文件)。Properties
类可以把Map
对象和属性文件关联起来,从而可以把Map
对象中的KV
对写入属性文件中,也可以把属性文件中“属性名=属性值”加载到Map
对象。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties
里的key
、value
都是字符串类型。该类提供了如下三个方法来修改Properties
里的key
、value
。
Properties
相当于一个key
、value
都是String
类型的Map
String getProperty(String key)
:获取Properties
中指定属性名,类似于Map
的get(Object key)
方法String getProperty(String key,String defaultValue)
:该方法与前一个方法基本类似。该方法多一个功能,如果Properties
中不存在指定的key
时,则该方法指定默认值Object setProperty(String key,String value)
:设置属性值,类似于Hashtable
的put()
方法。除此之外它还提供了两个读写属性文件的方法void load(InputStream inStream)
:从属性文件(以输入流表示)中加载KV
对,把加载到的KV
对追加到Properties
里(Properties
是Hashtable
的子类,它不保证KV
对之间的次序)void store(OutputStream out,String comments)
:将Properties
中的KV
对输出到指定的属性文件中(以输出流表示)中
@Test
public void test(){
Properties props = new Properties();
//向Properties中添加属性
props.setProperty("username","yeeku");
props.SetProperty("password","123456");
//将Properties中的key-value对保存到a.ini文件中
pops.store(new FileOutputStream("a.ini"),"comment line");//-----------1
//新建一个Properties对象
Properties props2 =new Properties();
//向Properties中添加属性
props.setProperty("gender","male");
///将a.ini文件中的key-value对追加到props2中
props2.load(new FileInputStream("a.ini"));//-----------2
System.out.println(props2);
}
上面程序示范了Properties
类的用法 ,其中1代码处将Properties
对象中的key-value
对写入a.ini
文件中。2代码处则从a.ini
文件中读取key-value对,并添加到props2
对象中。编译、运行上面程序,输出结果为:[password=123456,gender=male,username=yeeku]
上面程序还在当前路径下生成了一个a.ini
文件,该文件的内容如下:
#comment line
#Thu Apr 17 00:40:22 CST 2014
password=123456
username=yeeku
5.5 SortedMap 和 TreeMap
TreeMap是SortedMap的一个实现类
TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有key-value对处于有序状态。TreeMap也有两种排序方式:自然排序和定制排序。
Set和Map的关系非常密切,Java源码也就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有value都为null的Map集合实现了Set集合类。
TreeMap中也提供了一系列根据key顺序访问key-value对的方法。(first->last)
Map.Entry firstEntry():返回该Map中最小的key所对应的key-value对
Object firstKey():返回该Map中的最小key值
Map.Entry higherEntry(Object key):返回该Map中位于key后一位的key-value
Object higherKey(Object key):返回该Map中位于key后一位的key
NavigableMap subMap(Object formKey,boolean fromInclusive,Object toKey,boolean toInclusive):返回该Map的子Map(从fromKey到toKey,是否包含取决第2个和第4个参数是否为true)
SortedMap tailMap(Object fromKey):返回该Map的子Map,其key的范围为>=fromKey
SortedMap headMap(Object toKey):返回该Map的子Map,其key的范围是<toKey
5.6 WeakHashMap
WeakHashMap
与HashMap
的用法基本相似。与HashMap
的区别是在于,HashMap
的key
保留了队实际对象的强引用,这意味着只要该HashMap
对象不被销毁,该HashMap
的所有key
所引用的对象就不会被垃圾回收,HashMap
也不会自动删除这些key
所对应的KV
对;但WeakHashMap
的key
只保留了对实际对象的弱引用,这意味着如果WeakHashMap
对象的key
所引用的对象没有被其他强引用变量所引用,则这些key
所引用的对象可能被垃圾回收,WeakHashMap
也可能自动删除这些key
所对应的KV
。
WeakHashMap whm = new WeakHashMap();
whm.put(new String("AAA"),new String("123"));
//通知系统立即进行垃圾回收
System.gc();
System.runFinalization();
//-------此时whm中已没有key-value
5.7 IndentityHashMap
IndentityHashMap
在判断key
是否相等时和HashMap
不同,HashMap
需要重写的equals()
和hashCode()
都相等才判定是相等,而IndentityHashMap
只需要判断key1==key2
,如果相等就是相同的。
5.8 EnumMap
EnumMap
中所有的key
都必须是单个枚举类的枚举值,EnumMap
有如下特点:
EnumMap
内部以数组形式保存,所以这种实现形式非常紧凑、高效EnumMap
根据key
的自然顺序(即枚举值在枚举类中的定义顺序)来维护KV
对的顺序EnumMap
不允许null
作key
,但允许null
作value
//创建Enum类
EnumMap enumMap = new EnumMap(Season.class);
5.9 各 Map 实性能分析
对于Map
的常用类而言,虽然HashMap
和Hashtable
的实现机制几乎一样,但由于Hashtable
是一个古老的、线程安全的集合,因此HashMap
通常比Hashtable
更快。
TreeMap
通常比HashMap
要慢(尤其在插入、删除KV
对时更慢),因为TreeMap
底层采用红黑树来管理KV
对。使用TreeMap
有一个好处:TreeMap
中的KV
对总是处于有序状态,无须专门进行排序操作,当TreeMap
被填充之后,就可以调用keySet()
,取得由key
组成的Set
,然后使用toArray()
方法生成key
的数组,接下来使用Arrays
的binarySearch()
方法在已排序中快速查询对象。
对于一般的应用场景,程序应该多考虑使用HashMap
,因为HashMap
正是为快速查询设计的。但如果需要一个总是排序好的Map
,则可以考虑使用TreeMap
。
5.10 HashSet、HashMap 扩容
对于HashSet
来说,他们通过采用Hash
算法来决定集合中元素的存储位置,并通过Hash
算法来控制集合的大小;对于HashMap
来说,它采用Hash
算法来决定Map
中key
的存储,并通过Hash
算法来增加key
集合的大小。
Hash
表里可以存储元素的位置被称为“桶”(busket
),在通常情况下,单个“桶”存储一个元素,此时有最好的性能;Hash
算法可以根据hashCode
值计算出“桶”的存储位置。
HashSet
、HashMap
的Hash
表包含如下属性:
- 容量(
capacity
):Hash
表中桶的数量 - 初始化容量(
initial capacity
):创建Hash
表的桶的数量 - 尺寸(
size
):当前Hash
表记录的元素 - 负载因子(
load factor
):负载因子等于capacit/size
、负载因子为0,表示空的Hash
表,0.5表示半满的Hash
表。轻负载的Hash
表具有冲突,适宜插入与查询的特点(但是迭代时较慢)。HashSet
、HashMap
默认的负载极限为0.75,当Hash
表中75%被填满时,Hash
表会发生rehashing
。
六、集合类工具类 Collections
① 排序操作
void shuffle(List list)
:对List
集合元素进行随机排序(类似于洗牌)void sort(List list)
:根据元素的自然顺序按升序进行排序void sort(List list,Compatator c)
:对List
进行升序排序void swap(List list,int i,int j)
:交换元素void rotate(List list,int distance)
:distance
>0时,将list
后面的distance
个元素放到前面;distance
<0时,将list
前面的distance
个元素放到后面
② 查询、替换操作
int binarySearch(List list,Object key)
:使用二分搜索法搜索指定的List
集合,以获得指定对象在List
集合的索引Object max(Collection coll)
:根据自然顺序,返回集合中最大元素Object max(Collection coll,Comparator comp)
:根据定制顺序,返回集合中最大元素void fill(List list,Object obj)
:使用指定元素obj
替换List
集合中的所有元素int frequency(Collection c,Object o)
:返回集合中指定元素出现的次数int indexOfSubList(List source,List target)
:返回子List
对象在父List
对象中第一次出现的位置索引int lastIndexOfSubList(List source,List target)
:返回子List
对象在父List
对象中最后一次出现的位置索引boolean replaceAll(List list,Object oldVal,Object newVal)
:使用一个新值newVal
替换List
对象的所有旧值oldVal
③ 同步控制
Collections
类提供了多个synchronizedXxx()
方法,经方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
④ 设置不可变集合
emptyXxx()
:返回一个空的、不可变的集合对象singletonXxx()
:返回一个只包含指定对象的、不可变的集合对象unmodifiableXxx()
:返回指定集合对象的不可变视图