Collcetions 工具类
热衷学习,热衷生活!😄
沉淀、分享、成长,让自己和他人都能有所收获!😄
java.util.Collections
是java集合框架中的一个工具类,主要用于Collectiont提供的通用算法,比如:排序(sort
)、二分查找(binarySearch
)、洗牌(shuffle
)、旋转(rotate
)
1. Collections.sort 排序
方法使用,代码测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21List<String> list = new ArrayList<String>();
list.add("7");
list.add("4");
list.add("8");
list.add("3");
list.add("9");
// 默认排序(正序) 测试结果为3, 4, 7, 8, 9
list.sort();
// Comparator排序 可以已定义正序、倒叙, 还可以支持以对象的某个字段进行排序
Collections.sort(list, new Comparator<String>() {
public int compare(String o1, String o2) {
// 倒叙 正序的话 return o1.compareTo(o2);
return o2.compareTo(o1);
}
});
// reverseOrder 倒叙
Collections.sort(list, Collections.<String>reverseOrder());源码分析
Collections.sort集合排序最终调用的是
java.util
包下Arrays
类的Arrays.sort(T[] a, Comparator<? super T> c)
,这个方法根据有没有传入Compararot c
调用快速排序(sort()
)和优化的归并排序(TimSort.sort()
),源码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public static <T> void sort(T[] a, Comparator<? super T> c){
if (c == null) {
// 实际调用的是ComparableTimSort.sort
sort();
} else {
// 默认关闭
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
public static void sort(Object[] a) {
// 默认关闭
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}同时在快速排序和归并排序中,会判断个数是否大于32,从而选择分段排序和二分插入排序,部分源码如下:
1
2
3
4
5
6
7
8
9
10
11// ComparableTimSort类 和TimSort类方法一样
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
// ... 省略代码
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi);
// 二分插入排序
binarySort(a, lo, hi, lo + initRunLen);
return;
}
// 分段排序
}
2. Collections.binarySearch 二分查找
二分查找的前提是集合有序,否则不能满足二分算法的查找过程。
方法使用,代码测试
1
2
3
4
5
6
7
8
9
10
11
12
13List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
list.add("8");
// 二分查找, 传入list和需要查找的元素
int idx = Collections.binarySearch(list, "5");
// 输出4
System.out.println("二分查找:" + idx);源码分析
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92private static<T> binarySearch (List<? extends Comparable<? super T>> list, T key) {
// list继承RandAccess或者list的大小小于二分查找最大阙值5000
if (list instanceof RandomAccess || list.size() < BINARYSEARCH_THRESHOLD)
// 调用下标二分查找
return Collections.indexedBinarySearch(list, key);
else
// 调用迭代器二分查找 考虑到LinkedList的get方法时间复杂度是O(n)
return Collections.iteartorBinarySearch(list, key);
}
// 下标二分查找
private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
// 初始低位为0
int low = 0;
// 初始高位为list.size() - 1
int high = list.size() - 1;
// while循环低位小于等于高位
while(low <= high) {
// 位运算获取中间下标
int mid = (low + high) >>> 1;
// 获取中间下标元素
Comparable<? super T> midVal = list.get(mid);
// 调用compareTo方法, 中间元素和寻找元素做对比 大于返回大于0, 小于返回小于0, 相等返回0
int cmp = midVal.compareTo(key);
if (cmp < 0)
// 小于0说明中间元素小于寻找元素, 说明寻找元素在右边, 低位 = 中间下标 + 1
low = mit + 1;
else if (cmp > 0)
// 大于0说明中间元素大于寻找元素, 说明寻找元素在左边, 高位 = 中间下标 - 1
high = mit - 1;
else
// 相等 找到该值 返回中间下标
return mid;
}
// 没有找到返回 -(low + 1)
return -(low + 1);
}
// 迭代器二分查找, 原理和下标二分查找一样, 核心通过compareTo做比较查找元素, 区别就是在获取中间下标元素上
// 迭代器二分查找通过迭代器获取元素, 时间复杂度为O(n), 也做了优化,通过迭代器的下一个元素的下标和中间下标做比较,
// 判断中间下标元素是在前面(previous)还是后面(next)
private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) {
// 初始低位为0
int low = 0;
// 初始高位为集合大小 - 1
int high = list.size() - 1;
// 获取集合迭代器
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
// while循环低位小于等于高位
while (low <= high) {
// 通过位运算获取中间下标
int mid = (low + high) >>> 1;
// 通过迭代器获取中间下标元素值
Comparable<? super T> mitVal = get(i, mid);
// 中间值和寻找值做对于 大于返回大于0, 小于返回小于0, 相等返回等于0
int cmp = mitVal.compareTo(key);
if (cmp < 0)
// 中间值小于寻找值, 说明寻找值在右边, 低位 = 中间下标 + 1
low = mid + 1;
else if (cmp > 0)
// 中间值大于寻找值, 说明寻找值在左边, 高位 = 中间下标 - 1
high = mid - 1;
else
// 找到该值 返回下标
return mid;
}
// 没有找到返回 -(low + 1)
return -(low + 1);
}
// 迭代器获取中间下标元素
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
// 迭代器下一个元素下标
int pos = i.nextIndex();
if (pos <= inde){
// 下一个元素下标值小于等于寻找下标值, 所以寻找下标值在链表的后面(next)
do {
obj = i.next();
} while (pos++ < index);
} else {
// 下一个元素下标值小于寻找下标值, 所以寻找下标值在链表的前面(prev)
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}上面二分查找源码流程如下:
- 判断list是RandomAccess或者list的容量小于二分查找阙值5000,则调用下标二分查找,否则调用迭代器二分查找。
- 初始化低位为0,高位为
list.size()-1
,while循环知道直到元素或者low > high
- 通过位运算获取中间下标
(low + high) >>> 1
返回mid
- 通过
mid
下标元素(二分查找直接通过list.get()
获取,迭代器二分查找通过迭代器get()
获取)。 - 中间元素和寻找元素调用
comparteTo()
方法做比较返回cmp
,如果cmp < 0
,说明中间值小于寻找值,则寻找元素在右边,低位 =mid + 1
,如果cmp > 0
,说明中间值大于寻找值,则寻找元素在左边,高位 =mid - 1
,如果mid = 0
说明找到寻找元素,返回中间下标。
3. Collections.shuffle 洗牌算法
洗牌算法就是将List集合中的元素打乱,一般可以用于抽奖、摇号、洗牌等场景。
方法使用,代码测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
list.add("8");
//两种使用方式 一种是直接传入list
//另一种还可以传入固定随机种子, 这种方式可以控制洗牌范围
Collections.shuffle(list);
Collections.shuffle(list, new Random(100));源码分析
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55public static void shuffle(List<?> list) {
Random rnd = r;
// 没有传入固定随机种子则初始化
if (rnd == null)
r = rnd = new Random();
// 洗牌算法
shuffle(list, rnd);
}
public static void shuffle(List<?> list, Random rnd) {
// 集合容量
int size = list.size();
// 集合容量小于洗牌阙值5 或者 是RandomAccess
if (size < SHUFFLE_THRESHOLD || list.instanceof RandomAccess) {
// for循环调用交换算法
for (int i = size; i > 1; i--)
swap(list, i - 1, rnd.nextInt(i));
} else {
// 集合容量大于洗牌阙值5, 且不是RandomAccess, 比如LindedList
// 转成数组,对数组做调换操作, 不调用list的set方法主要是因为LindedList的set方法要获取传入下标的元素,获取元素
// 时间复杂度为O(n)性能低
Object[] arr = list.toArray();
// for循环对数组进行调换算法
for (int i = size; i > 1; i--)
swap(arr, i - 1, rnd,nextInt(i));
// 通过迭代器进行元素的调换, 使用迭代器的set方法性能就是很高,因为可以通过迭代器获取元素
ListIterator it = list.listIterator();
for (int i = 0; i < arr.length; i++){
it.next();
it.set(arr[i]);
}
}
}
public static void swap (List<?> list, int i, int j){
final List l = list;
// 通过list.set(int index, E e)方法进行元素调换, 该方法返回原元素
// 比如list(1, 2, 3, 4, 5, 6, 7, 8) i = 7, j = 0
// 首先 l.set(j, l.get(i)) 将下标为7的元素调到下位为0的位置,并返回元素1, 此时list为(8,2,3,4,5,6,7,8)
// l.set(i, 1), 将元素1设置到下标7, 此时list为(8,2,3,4,5,6,7,1)
// 这样子就完成了元素调换
l.set(i, l.set(j, l.get(i)));
}
public static void swap(Object[] arr, int i, int j) {
// 下标i元素
Object tmp = arr[i];
// 将下标j元素赋值到下标i位置
arr[i] = arr[j];
// 下标i元素赋值到下标j位置
// 完成下标i, j的调换
arr[j] = tmp;
}上面洗牌算法
shuffle(List<?> list, Random rnd)
流程如下- 判断容量是否小于洗牌阙值
5
或者是RandomAccess
,来调用不同的调换算法 - 容量小于
5
或者是RandomAccess
,通过循环容量大小得到当前下标和随机下标,然后通过list.set(int index, E e)
方法将两个下标元素调换。 - 如果容量大于
5
且不是RandomAccess
,通过将list
转成数组,然后循环容量大小得到当前下标和随机下标,然后通过数组赋值调换元素,最后通过循环数组,通过list的迭代器将数组里面的元素赋值到list
。
- 判断容量是否小于洗牌阙值
1.4 Collections.rotate 旋转算法
rotate
旋转算法可以把ArrayList
、LinkedList
从指定的位置开始,进行顺时针或者逆时针旋转操作。
方法使用,代码测试
1
2
3
4
5
6
7
8
9
10
11
12List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
// 将list顺时针旋转2位, 逆时针旋转为负数
// 输出为 4,5,1,2,3
Collections.rotate(list, 2);
// 输出为 3,4,5,1,2
Collections.rotate(list, -2);源码分析
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68public static void rotate(List<?> list, int distance) {
if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)
rotate1(list, distance);
else
rotate2(list, distance);
}
private static <T> void rotate1(List<?> list, int distance) {
// 集合容量
int size = list.size();
// 集合容量为0直接结束
if (size == 0)
return;
// 计算旋转的距离
distance = distance % size;
if (distance < 0)
distance += size;
if (distance == 0)
return;
// for循环和do while,配合每次的旋转操作
for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {
// 旋转的元素
T displaced = list.get(cycleStart);
// 旋转的元素下标
int i = cycleStart;
do {
i += distance;
if (i > size)
i -= size;
// 通过list.set方法旋转元素
displaced = list.set(i, displaced);
nMoved++;
} while (i != cycleStart);
}
}
private static <T> void rotate2(List<?> list, int distance) {
int size = list.size();
if (size == 0)
return;
int mid = -distance % size;
if (mid < 0)
mid += size;
if (mid == 0)
return;
reverse(list.subList(0, mid));
reverse(list.subList(mid, size));
reverse(list);
}
public static void reverse(List<?> list) {
int size = list.size();
if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
swap(list, i, j);
} else {
// instead of using a raw type here, it's possible to capture
// the wildcard but it will require a call to a supplementary
// private method
ListIterator fwd = list.listIterator();
ListIterator rev = list.listIterator(size);
for (int i=0, mid=list.size()>>1; i<mid; i++) {
Object tmp = fwd.next();
fwd.set(rev.previous());
rev.set(tmp);
}
}
}rotate1
方法流程如下:- 判断集合容量,并计算旋转的距离。
for
循环和do while
配合每次的旋转,比如这里第一次会把0位置元素替换到2位置,此时list
是1,2,1,4,5
,do while
中会判断i != cycleStart
,当i == cycleStart
的时候说明旋转全部的元素了,然后继续把被替换2位置的元素继续往下替换,比如这里第二次会把被替换2位置替换到4位置,此时list是1,2,1,4,3
,直到一轮循环把所有元素都放置到正确位置。- 移动的次数为
size
表明list
全部元素都旋转,结束for循环。
rotate2
方法流程如下:该方法主要针对大于100个元素的LinkedList进行操作。- 定位拆链位置, distance % size + size ,也就是我们要旋转后找到的元素位置。
- 第一次翻转,把从位置 0 到拆链位置
- 第三次翻转,翻转整个链表。
1.5 Collections其他API
最大最小值,主要依赖compareTo方法比较大小
1
2
3
4
5
6
7
8
9
10List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
// 最小值
String min = Collections.min(list);
// 最大值
String max = Collections.max(list);元素替换,会判断list是RandomAccess,小于替换阙值11,调用不同的替换值方法,一个是使用list的set方法,一个是使用迭代器的set方法。
1
2
3
4
5
6
7List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Collections.replaceAll(list, "5", "6");连续集合位置判断
1
2
3
4
5
6
7
8List<String> list = new ArrayList<String>();
list.add("7");
list.add("4");
list.add("8");
list.add("3");
list.add("9");
// 返回2
int idx = Collections.indexOfSubList(list, Arrays.asList("8", "3"));synchronized方法,将非线程安全的集合转换成线程安全集合。Collections内部定义了静态类
SynchronizedList
、SynchronizedMap
,这两个类定义List、Map然后操作方法加锁1
2List<String> list = Collections.synchronizedList(new ArrayList<String>());
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>()