干货 | Java 集合高频知识点大汇总
链接:https://juejin.im/post/5e8572abf265da47af17ff8d
1LinkedList<Integer> lists = new LinkedList<>();
2
3lists.addFirst(1);
4lists.push(2);
5lists.addLast(3);
6lists.add(4);
7lists.addFirst(5);
8
9lists.forEach(System.out::println);
10// 5 2 1 3 4
1List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
2integers.set(2, 5); // 这个操作可以
3//integers.add(6); 这个会抛出异常
4integers.forEach(System.out::println); // 1 2 5 4 5
5
6 1. 很显然我们是可以修改 list集合的 可以使用set方法
7 2. 但是当我们尝试去使用add() 方法时,会抛出 java.lang.UnsupportedOperationException 的异常,
8不支持操作的异常
9 3.当我们使用 java9+时 可以使用 List.of()方法 ,他就是彻彻底底的不可修改的
1 1. 使用 Collections这个工具类
2List<Integer> integers1 = Collections.synchronizedList(integers);
3
4 2. java5+ 变成 CopyOnWriteArrayList
5CopyOnWriteArrayList<Integer> integers2 = (CopyOnWriteArrayList<Integer>) integers;
6
7 3. java9+ ,使用 List.of() 变成只读对象
1 1. 创建一个安全的空集合,防止NullPointerException异常
2List<String> list = Collections.<String>emptyList();
3
4 2. 拷贝集合
5Collections.addAll(list, 2,3, 4, 5, 6);
6
7 3. 构建一个安全的集合
8List<Integer> safeList = Collections.synchronizedList(list);
9
10 4. 二分查找
11Collections.binarySearch(list, 2);
12
13 5.翻转数组
14Collections.reverse(list);
我们看源码会发现他里面有一个 HashMap(用 transient 关键字标记的成员变量不参与序列化过程,因为 HashMap 已经实现 Serializable)。
1public CopyOnWriteArraySet() {
2 al = new CopyOnWriteArrayList<E>();
3 }
Hashtable 和 ConcurrentHashMap 以及 ConcurrentSkipListMap 以及 TreeMap 不允许 key 和 value 值为空,但是 HashMap 可以 key 和 value 值都可以为空。
Hashtable 的方法都加了 Synchronized 关键字修饰,所以线程安全。
它是数组+链表的实现。
LinkedList 是链表结构,队列呢也是一个列表结构,继承关系上 LinkedList 实现了 Queue,所以对于 Queue 来说,添加是 offer(obj),删除是 poll(),获取队头(不删除)是 peek() 。
1public static void main(String[] args) {
2 Queue<Integer> queue = new LinkedList<>();
3
4 queue.offer(1);
5 queue.offer(2);
6 queue.offer(3);
7
8 System.out.println(queue.poll());
9 System.out.println(queue.poll());
10 System.out.println(queue.poll());
11}
12// 1, 2 , 3
PriorityQueue 维护了一个有序列表,插入或者移除对象会进行 Heapfy 操作,默认情况下可以称之为小顶堆。当然,我们也可以给它指定一个实现了 java.util.Comparator 接口的排序类来指定元素排列的顺序。PriorityQueue 是一个无界队列,当你设置初始化大小还是不设置都不影响他继续添加元素。
ConcurrentLinkedQueue 是基于链接节点的并且线程安全的队列。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大小 ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
ArrayBlockingQueue 构造函数必须传入指定大小,所以他是一个有界队列。
LinkedBlockingQueue 分为两种情况,第一种构造函数指定大小,他是一个有界队列,第二种情况不指定大小他可以称之为无界队列,队列最大值为 Integer.MAX_VALUE。
PriorityBlockingQueue(还有一个双向的 LinkedBlockingDeque)他是一个无界队列,不管你使用什么构造函数。一个内部由优先级堆支持的、基于时间的调度队列。队列中存放 Delayed 元素,只有在延迟期满后才能从队列中提取元素。当一个元素的 getDelay() 方法返回值小于等于 0 时才能从队列中 poll 中元素,否则 poll() 方法会返回 null。
SynchronousQueue 这个队列类似于 Golang的channel,也就是 chan,跟无缓冲区的 chan 很相似。比如 take 和 put 操作就跟 chan 一模一样。但是区别在于他的 poll 和 offer 操作可以设置等待时间。
DelayQueue 延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用 poll() 方法会返回 null 值,超时判定是通过 getDelay(TimeUnit.NANOSECONDS) 方法的返回值小于等于 0 来判断。延时队列不能存放空元素。
1@Test
2public void testLinkedList() throws InterruptedException {
3
4 DelayQueue<Person> queue = new DelayQueue<>();
5
6 queue.add(new Person());
7
8 System.out.println("queue.poll() = " + queue.poll(200,TimeUnit.MILLISECONDS));
9}
10
11
12static class Person implements Delayed {
13
14 @Override
15 public long getDelay(TimeUnit unit) {
16 // 这个对象的过期时间
17 return 100L;
18 }
19
20 @Override
21 public int compareTo(Delayed o) {
22 //比较
23 return o.hashCode() - this.hashCode();
24 }
25}
26
27输出 :
28queue.poll() = null
LinkedTransferQueue 是 JDK1.7 加入的无界队列,亮点就是无锁实现的,性能高。Doug Lea 说这个是最有用的 BlockingQueue 了,性能最好的一个。Doug Lea 说从功能角度来讲,LinkedTransferQueue 实际上是 ConcurrentLinkedQueue、SynchronousQueue(公平模式)和 LinkedBlockingQueue 的超集。
他的 transfer 方法表示生产必须等到消费者消费才会停止阻塞。生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。同时我们知道上面那些 BlockingQueue 使用了大量的 condition 和 lock,这样子效率很低,而 LinkedTransferQueue 则是无锁队列。他的核心方法其实就是 xfer() 方法,基本所有方法都是围绕着这个进行的,一般就是 SYNC、ASYNC、NOW 来区分状态量。像 put、offer、add 都是 ASYNC,所以不会阻塞。下面几个状态对应的变量。
1private static final int NOW = 0; // for untimed poll, tryTransfer(不阻塞)
2private static final int ASYNC = 1; // for offer, put, add(不阻塞)
3private static final int SYNC = 2; // for transfer, take(阻塞)
4private static final int TIMED = 3; // for timed poll, tryTransfer (waiting)
小顶堆是什么:任意一个非叶子节点的权值都不大于其左右子节点的权值。
1/**
2 * 构建一个 大顶堆
3 *
4 * @param tree
5 * @param n
6 */
7static void build_heap(int[] tree, int n) {
8
9 // 最后一个节点
10 int last_node = n - 1;
11
12 // 开始遍历的位置是 : 最后一个堆的堆顶 , (以最小堆为单位)
13 int parent = (last_node - 1) / 2;
14
15 // 递减向上遍历
16 for (int i = parent; i >= 0; i--) {
17 heapify(tree, n, i);
18 }
19}
20
21
22/**
23 * 递归操作
24 * @param tree 代表一棵树
25 * @param n 代表多少个节点
26 * @param i 对哪个节点进行 heapify
27 */
28static void heapify(int[] tree, int n, int i) {
29
30 // 如果当前值 大于 n 直接返回了 ,一般不会出现这种问题 .....
31 if (i >= n) {
32 return;
33 }
34
35 // 子节点
36 int c1 = 2 * i + 1;
37 int c2 = 2 * i + 2;
38
39 // 假设最大的节点 为 i (父节点)
40 int max = i;
41
42 // 如果大于 赋值给 max
43 if (c1 < n && tree[c1] > tree[max]) {
44 max = c1;
45 }
46
47 // 如果大于 赋值给 max
48 if (c2 < n && tree[c2] > tree[max]) {
49 max = c2;
50 }
51
52 // 如果i所在的就是最大值我们没必要去做交换
53 if (max != i) {
54
55 // 交换最大值 和 父节点 的位置
56 swap(tree, max, i);
57
58 // 交换完以后 , 此时的max其实就是 i原来的数 ,就是最小的数字 ,所以需要递归遍历
59 heapify(tree, n, max);
60 }
61
62}
63
64// 交换操作
65static void swap(int[] tree, int max, int i) {
66 int temp = tree[max];
67 tree[max] = tree[i];
68 tree[i] = temp;
69}
Stack 类继承自 Vector,所有方法都加入了 sync 修饰,使得效率很低,线程安全。
1@Test
2public void testStack() {
3
4 Stack<Integer> stack = new Stack<>();
5
6 // push 添加
7 stack.push(1);
8
9 stack.push(2);
10
11 // pop 返回栈顶元素 , 并移除
12 System.out.println("stack.pop() = " + stack.pop());
13
14 System.out.println("stack.pop() = " + stack.pop());
15
16}
17
18输出 :
192 , 1
-End-
可乐记得加冰,爱我就要置顶
客官!在看一下呗