集合(2)

List

  • List判断两个对象是否相等的标志是只要通过equals方法返回true即可。
1
2
3
4
5
6
7
List<String> list = new ArrayList<>();
list.add("first");
list.add("second");
System.out.println(list); //[first, second]
String s = "first";
list.remove(s);
System.out.println(list); //[second]

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ListTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i); //添加元素
}
System.out.println(list);
System.out.println(list.contains(8)); //是否包含该元素
System.out.println(list.contains(11));
//在索引为5的位置添加元素15(原先索引大于等于5的元素都后移一位)
list.add(5, 15);
System.out.println(list);
list.set(5, 16); //将索引为5的元素修改为16
System.out.println(list);
//返回元素(第一个)索引,若没有该元素则返回-1
System.out.println("元素9(第一个)的索引是:" + list.indexOf(9));
//返回索引为[2, 7)的子集
System.out.println(list.subList(2, 7));
list.remove(7); //删除索引为7的元素(注意删除后的索引改变引发的问题)
System.out.println(list);
list.clear(); //清除所有元素
System.out.println(list);
}
}

输出结果:

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
    11
    Queue<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
    8
    Deque<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
    8
    Deque<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的区别

  1. Hashtable是一个线程安全的Map实现,而HashMap是一个线程不安全的实现。所有HashMap的性能会高一点。
  2. 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的区别

  1. HashMap是无序的:其遍历的顺序与其插入的顺序不一样,而LinkedHashMap是有序的:其遍历的顺序和其插入的顺序一致
  2. 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提供了返回空的或者只有一个元素的不可变集合,以及将普通集合转换成不可变集合的操作。不可变集合里的元素只能访问而不可修改
-------------    本文到此结束  感谢您的阅读    -------------
0%