List
- List判断两个对象是否相等的标志是只要通过equals方法返回true即可。
1 | List<String> list = new ArrayList<>(); |
基本使用
1 | public class ListTest { |
输出结果:1
2
3
4
5
6
7
8
9[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
true
false
[0, 1, 2, 3, 4, 15, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 16, 5, 6, 7, 8, 9]
元素9(第一个)的索引是:10
[2, 3, 4, 16, 5]
[0, 1, 2, 3, 4, 16, 5, 7, 8, 9]
[]
其他方法
- sort方法:使用一个Comparator对象来控制元素排序
- replaceAll方法:使用UnaryOperator对象来替换所有的元素
- listIterator方法:返回一个ListIterator接口对象。
ListIterator与普通的Iterator相比,增加了向前迭代的功能,而且还可以通过add方法向List集合中添加元素。
ArrayList和Vector
- 两个类都是List类的典型实现,底层封装了一个动态的,允许再分配的Object[]数组。
- ArrayList和Vector的显著区别是,ArrayList是线程不安全的。但Vector具有很多缺点,所以即使需要保证集合线程安全,也同样不推荐使用Vector,通过Collections工具类,可以将ArrayList变成线程安全的。
- Vector还提供了一个Stack子类,可用于模拟“栈”这种数据结构。但Stack同样是线程安全、性能较差的,因此也应该尽量少用Stack类。如果需要使用“栈”,可以考虑ArrayDeque。
固定长度的List
当我们调用Arrays.asList方法将一个数组转换为集合的时候,返回的对象是Arrays.ArrayList(Arrays的内部类ArrayList),Arrays.ArrayList是一个固定长度的List,程序只能遍历该集合的元素,而不能增加或删除该集合的元素。
Queue
- Queue用于模拟队列这种数据结构,队列通常是指“先进先出”的容器。
- Queue接口定义了以下几个方法
boolean add(E e); 将元素e加入此队列的尾部。
boolean offer(E e); 将元素e加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add方法更好。
E element(); 获取队列头部的元素,但不删除该元素。
E peek(); 获取队列头部的元素,但不删除该元素。如果此队列为空,则返回null。
E remove(); 获取队列头部的元素,并删除该元素。
E poll(); 获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。
PriorityQueue
- PriorityQueue是Queue的实现类,但是PriorityQueue保存队列的元素并不是按照加入队列的顺序,而是按照某种规则进行重新排序。
- PriorityQueue不允许插入null元素,PriorityQueue的元素有两种排序方式:自然排序和定制排序(与TreeSet类似)
- 关于PriorityQueue排序的一些问题,先看下面的代码
1
2
3
4
5
6
7
8
9
10
11Queue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(6);
priorityQueue.offer(1);
priorityQueue.offer(10);
priorityQueue.offer(-3);
System.out.println(priorityQueue); //[-3, 1, 10, 6]
priorityQueue.offer(-6);
System.out.println(priorityQueue); //[-6, -3, 10, 6, 1]
while (priorityQueue.size() > 0) {
System.out.print(priorityQueue.remove() + " "); //-6 -3 1 6 10
}
可以看出输出的队列元素并不是完全的升序,但是删除的顺序是有序的。其实PriorityQueue在添加和删除元素的时候也是有排序的, 但不是完全的排序, 只是保证需要放在头部的元素一定在队头,而队尾的元素不一定是有序的。
Deque
- Queue还有一个Deque子接口,Deque代表一个“双端队列”,双端队列可以从两端添加、删除元素,因此Deque的实现类既可以当成队列使用,也可以当成栈来使用。
- Deque接口中定义了pop(出栈)、push(入栈)方法用于实现栈。其中pop方法的作用是获取并删除队列的队头元素,push方法的作用是将指定元素插入队列的队头。
ArrayDeque
- ArrayDeque是Deque类的典型实现,它是一个基于数组的双端队列
ArrayDeque可以作为栈来使用,因此当程序需要栈这种数据结构时,应使用ArrayDeque,尽量避免使用Stack,因为Stack是古老的集合,性能较差。
1
2
3
4
5
6
7
8Deque<String> stack = new ArrayDeque<>();
//依次将三个元素入栈
stack.push("first");
stack.push("second");
stack.push("third");
System.out.println(stack); //[third, second, first]
System.out.println("将栈顶元素出栈:" + stack.pop()); //third
System.out.println(stack); //[second, first]ArrayDeque也可以作为队列来使用
1
2
3
4
5
6
7
8Deque<String> queue = new ArrayDeque<>();
//添加三个元素入队列
queue.offer("first");
queue.offer("second");
queue.offer("third");
System.out.println(queue); //[first, second, third]
System.out.println("删除队头元素:" + queue.poll()); //first
System.out.println(queue); //[second, third]
LinkedList
- LinkedList既实现了List接口也实现了Deque接口,所以可以用来作为List集合、队列、栈。
- LinkedList的内部以链表的形式来保持集合中的元素。与内部以数组的形式实现的ArrayList、ArrayDeque不同,一般来说,其在插入、删除元素时性能比较出色(只需改变指针所指的地址即可),但随机访问集合元素时性能较差(需要遍历链表)。
Map
- Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组值用于保存key,另一组用于保存value,key和value一一对应,且必须是引用类型的数据。
- 如果把所有key组成在一起,就组成了一个Set集合,实际上Map确实包含了一个keySet()方法用于返回Map里所有key组成的一个Set集合。
- Map和Set的关系非常密切,从Java源码来看,Java是先实现了Map,然后通过包装一个所有value都为null的Map就是实现了Set。
HashMap
- 用于存储键值对(key-value),其中key不可以重复,value可以重复。
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//初始化,第一个泛型是key的类型,第二个是values
HashMap<Integer, String> hashMap = new HashMap<>();
//添加元素
hashMap.put(1, "first");
hashMap.put(2, "second");
hashMap.put(3, "third");
//hashMap.put(3, "doubleThird"); //会覆盖原先的<3,third>
System.out.println(hashMap); //{1=first, 2=second, 3=third}
System.out.println(hashMap.get(0)); //null
System.out.println(hashMap.get(1)); //first
System.out.println(hashMap.containsKey(1)); //true
System.out.println(hashMap.containsValue("third")); //true
System.out.println(hashMap.remove(10)); //null
System.out.println(hashMap.remove(2)); //second
System.out.println(hashMap); //{1=first, 3=third}
//遍历
for (Object o : hashMap.keySet()) {
int key = (int) o;
System.out.println("key = " + key + ", values = " + hashMap.get(key));
}
//另一种遍历方法
Set<Map.Entry<Integer, String>> set = hashMap.entrySet();
for (Map.Entry<Integer, String> entry : set) {
System.out.println("key = " + entry.getKey() + ", values = " + entry.getValue());
}HashMap和Hashtable的区别
- Hashtable是一个线程安全的Map实现,而HashMap是一个线程不安全的实现。所有HashMap的性能会高一点。
- Hashtable不允许null作为key或value的元素,如果使用会抛出异常,但HashMap允许null作为key或value的元素。HashMap允许一个key为null,允许多个value为null。
用作key的对象必须实现了equals()和hashCode()方法,因为判断两个key相等的条件是:通过equals()方法比较后返回true,并且两者的hashCode()方法返回的值相等。所以如果重写了对象的equals()方法,必须保证两个方法的判断标准一致,即equals()方法返回true时,返回的hashCode也要相等。当我们使用自定义对象作为key时,也要重写这两个方法并保持判断标准一致。
HashMap和LinkedHashMap的区别
- HashMap是无序的:其遍历的顺序与其插入的顺序不一样,而LinkedHashMap是有序的:其遍历的顺序和其插入的顺序一致
- LinkedHashMap需要维护元素的顺序,所以性能略低于HashMap,但由于它是以链表维护元素的顺序,所以在迭代元素的时候性能会好一些。
LinkedHashMap、TreeMap和EnumMap
- LinkedHashMap、TreeMap类似于LinkedHashSet和TreeSet,排序的实现基本相同,只不过是对key排序,并且每个key都带了一个value。
- EnumMap类似于EnumSet,只不过是需要保证所有的key必须是同一个枚举类的枚举值。
WeakHashMap
- WeakHashMap与HashMap的用法基本相似。两者的区别在于:
- HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap对象的所有key所引用的对象就不会被系统回收,HashMap就不会自动删除这些key对于的key-value对。
- WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象就有可能被回收,WeakHashMap也就会自动删除那些引用对象被回收了的key所对应的key-value对。
- 如果需要使用WeakHashMap的key来保持对象的弱引用,就不用让该key所引用的对象具有任何强引用,否则将失去使用WeakHashMap的意义
IdentityHashMap
- IdentityHashMap与HashMap的用法基本相似,两者的区别在于:
- IdentityHashMap在判断两个key是否相等时,当且仅当key1 == key2时,才认为两个key相等
- HashMap则是当key1和key2通过equals方法返回true,并且它们的hashCode相等时,才认为两个key相等
操作集合的工具类:Collections
- Collections类提供了用于对List集合元素排序的方法,例如反转、自然排序、定制排序、交换等操作
- Collections类提供了查找集合的最大或最小元素、替换指定元素等操作
- Collections提供了多个synchronizedXxx方法,用于将指定集合包装成线程安全的集合
- Collections提供了返回空的或者只有一个元素的不可变集合,以及将普通集合转换成不可变集合的操作。不可变集合里的元素只能访问而不可修改