Collections
此类仅由对集合进行操作或返回集合的静态方法组成。
用例
import java.util.*;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(12);
arrayList.add(10);
arrayList.add(35);
arrayList.add(100);
Collections.fill
Collections.sort(arrayList);//排序
System.out.println(arrayList.toString());
Collections.shuffle(arrayList);//洗牌
System.out.println(arrayList.toString());
Collections.rotate(arrayList, 1);//旋转
System.out.println(arrayList.toString());
}
}
内部集合类
Collections 类在内部定义了一系列实用的集合类获取方法,主要包括不可变集合(unmodifiable)、线程安全集合(synchronized)、可检查集合(checked)、空集合(empty)和单元素集合(singleton)。
不可变集合
在 Collections 类内部,定义了一个静态类 UnmodifiableCollection,通过公用方法 unmodifiableCollection 获取。
这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法的实现。封装后创建的代理对象确实是不能修改的,但用于创建该对象的原始对象的可变性是不变的。如果可变的原始对象被修改,由于不可变集合保留了对原始对象的引用,所以这种修改会同样出现在封装后的对象中。因此,要保证集合不变性,在封装完成创建代理对象后,最好将原来的对象引用抛弃,避免后续意外修改对象的数据。
空集合
要注意的是,空集合都是不可变集合,也就是说,当空集合创建以后,不能再向其中添加或者删除元素。
List<String> list = Collections.emptyList();
源码
public static final List EMPTY_LIST = new EmptyList<>();
单元素集合
单元素集合也是不可变集合
Set<String> lst1 = Collections.singleton("hello");
List<String> lst2 = Collections.singletonList("hello");
源码
private static class SingletonSet<E>
extends AbstractSet<E>
implements Serializable
{
private static final long serialVersionUID = 3193687207550431679L;
private final E element;//不可变
SingletonSet(E e) {element = e;}
...
}
线程安全集合
Collections 类提供了一组方法,把线程不安全的集合变为线程安全的集合。但是从 Java 5 开始就引入了更高效的并发集合类,因此这部分的同步方法几乎很少会用到了。
static <T> Collection<T> |
synchronizedCollection(Collection<T> c) 返回由指定集合支持的同步(线程安全)集合。 |
---|---|
static <T> List<T> |
synchronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表。 |
static <K,V> Map<K,V> |
synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。 |
static <T> Set<T> |
synchronizedSet(Set<T> s) 返回由指定集支持的同步(线程安全)集。 |
static <K,V> SortedMap<K,V> |
synchronizedSortedMap(SortedMap<K,V> m) 返回由指定排序映射支持的同步(线程安全)排序映射。 |
static <T> SortedSet<T> |
synchronizedSortedSet(SortedSet<T> s) 返回由指定排序集支持的同步(线程安全)排序集。 |
采用synchronized关键字实现
算法
Collections 类的方法都是静态方法,每一种方法都对应一种集合算法的实现,且每一种实现都有两种,一种是适用于实现了RandomAccess接口的集合类(例如ArrayList),另一种是适用于序列存储的,例如(LinkedList)。Collections类包含的算法实现大致如下:
-
排序:排序采用归并排序,所以排序算法是稳定的,时间复杂度是确定的。
-
混淆:常规的集合数据操作(适用于List) ,包括reverse、fill、copy、swap、addAll等
-
搜索:适用于List,binarySearch
-
极值:求集合的最大元素、最小元素
Collections 中的大多数算法都只是适用于 List,使用与 List 的算法主要有: -
sort,shuffle,reverse,rotate
-
swap,replaceAll,fill,copy
-
binarySearch
-
indexOfSubList,lastIndexofSubList
排序
方法内部将列表 list 转化成一个数组,通过使用 Arrays 类的 sort 方法进行排序,然后新建一个列表迭代器,依次将排序后的元素写入,构成新的有序的列表。
static void |
reverse(List<?> list) 反转指定列表中元素的顺序。 |
---|---|
static <T> Comparator<T> |
reverseOrder() 返回一个比较器,该比较器将自然顺序的反向强加于实现该Comparable 接口的对象集合 。 |
static <T> Comparator<T> |
reverseOrder(Comparator<T> cmp) 返回一个比较器,它强制指定比较器的反向排序。 |
static <T extends Comparable<? super T>>void |
sort(List<T> list) 根据其元素的自然顺序,将指定列表按升序 排序。 |
---|---|
static <T> void |
sort(List<T> list, Comparator<? super T> c) 根据指定比较器产生的顺序对指定列表进行排序。 |
static void |
swap(List<?> list, int i, int j) 交换指定列表中指定位置的元素。 |
import java.util.*;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(12);
arrayList.add(10);
arrayList.add(35);
arrayList.add(100);
Collections.sort(arrayList);
System.out.println(arrayList.toString());
Collections.reverse(arrayList);//反转
System.out.println(arrayList.toString());
}
}
输出
[10, 12, 35, 100]
[100, 35, 12, 10]
源码
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);//采用list的sort
}
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);
}
}
}
查找
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);//二分查找
else
return Collections.iteratorBinarySearch(list, key);
}
这里出现了一个接口RandomAccess,其源码定义是一个空的接口,也就说这个接口起到一个标志接口(marker)的作用。根据源码里注释的说明,这个标志接口用于声明实现这个接口的类支持随机访问的功能,而这个功能对于随机访问的列表(如 ArrayList)或者序列化的列表(如 LinkedList)在某些通用算法的性能有很大的影响。因此,在执行一些通用的列表算法前,就可以通过检查这个标志接口以确保当前列表使用该算法能有较好的性能,否则就应该作出相应的调整,应保证运行性能。
一般来说,list 的相关算法通常都会有两种实现,一种是适合随机访问的 list,另一种是适合序列化的(sequential)的 list。在大多数情况下,随机访问的列表会比长度短的序列化列表的表现得更好。在 Collections 类定义了一系列的阈值,作为选用哪种实现方法的边界。
类似的情况还出现在这些方法中:
int binarySearch(List<? extends Comparable<? super T>> list, T key);
void reverse(List<?> list);
void shuffle(List<?> list, Random rnd);
void fill(List<? super T> list, T obj);
void copy(List<? super T> dest, List<? extends T> src);
void rotate(List<?> list, int distance);
void replaceAll(List<T> list, T oldVal, T newVal);
void indexOfSubList(List<?> source, List<?> target);
void lastIndexOfSubList(List<?> source, List<?> target);
如果需要处理的 list 是一个比较大的 LinkedList,那么就通过使用迭代器 ListIterator,或者先将整个 list 转化成数组 array 进行处理后,再通过迭代器逐个元素进行写入,返回最终处理的结果。
复制
将一个列表中的所有元素复制到另一个列表中。操作后,目标列表中每个复制元素的索引将与其在源列表中的索引相同。目标列表必须至少与源列表一样长。如果它更长,则目标列表中的其余元素不受影响。
copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())//目标列表必须至少与源列表一样长
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {//复制长度大于10,采用迭代器
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
旋转
rotate 的实现同样区分两种:
- rotate1:实现
RandomAccess
接口的 list 或者长度小于阈值 100 的序列 list; - rotate2:长度大于阈值 100 的序列 list。
第一种实现方式,利用了随机访问的特性,主要是从第一个元素开始,将其交换到正确的i = i + distance的位置上,被交换出来的元素,会继续交换到下一个i = i + distance的位置上,重复以上操作直到起始的位置被放置了新的元素。如果distance正好是列表长度的约数,那么此时需要从起始位置偏移到下一个位置,重复上述步骤直至所有位置的元素都被移动过,则旋转完成。其中每移动一次元素,用于计数的变量nMoved就会加 1。
第二种实现方式,主要是针对类似 LinkedList 这样的序列列表,首先通过mid = -distance % size找到一个连接位置,然后以这个位置为界,将 list 拆分成两个子列表,分别进行反转,最后再将整个列表反转。
前两次反转子列表的目的是为了将原列表的首尾相连,并且找到旋转后的首尾位置并从原列表中断开,最后整个列表的反转是为了将列表恢复到原来的相对顺序。前两次反转子列表的目的是为了将原列表的首尾相连,并且找到旋转后的首尾位置并从原列表中断开,最后整个列表的反转是为了将列表恢复到原来的相对顺序。
极值
Collections.min(arrayList);
根据元素的 自然顺序返回给定集合的最小元素。集合中的所有元素都必须实现Comparable
接口。此外,集合中的所有元素必须相互可比较。
此方法迭代整个集合,因此它需要与集合大小成正比的时间。
T min(Collection<? extends T> coll) {
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (next.compareTo(candidate) < 0)
candidate = next;
}
return candidate;
}