java.util.Arrays
arrays提供大量数组相关的操作,在这个类中可以学习到大量算法。
排序
sort方法存在大量重载方法,这里仅描述针对int[]的排序办法:
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
public static void sort(int[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
一般针对一种数据类型的sort方法都会出现上述两种重载方法,区别是一个指定了序列的范围,另一个没有。而他们内部都统一调用了DualPivotQuicksort的sort方法。DualPivotQuicksort#sort较为复杂,这里需要一点铺垫。
首先,我们知道排序算法还是很多的,不同算法在不同序列上的表现效果并不太一样,而DualPivotQuicksort在处理序列的时候就会进行选择性的使用具体哪种算法进行排序。DualPivotQuicksort内涉及到的算法有:
- 并归排序(这个基础上做了优化,成为TimSort)
- 插入排序
- 快速排序
- 计数排序
我们先针对这四种排序做简单介绍:
第一种,并归排序,我们先制造一个这样的序列(乱打的):[4,3,7,5,2,6,1,8],并归基本思路就是分治的思路。
可以看到,将序列不断细分,直到细分到最小之后进行排序,子序列不断合并成为最终的排序好了的序列。但是考虑到原有序列有可能部分已经是排序好了的,因此压根没有必要进行细分,因此诞生TimSort,目的就是为了解决这一缺陷而生。简单的说TimSort的思路是,在分的时候,直接从左往右进行划分为不同长度有序的子序列,然后针对这部分子序列进行并归。但也不是任何时候都可以使用它,如果这个序列太过杂乱无章,使用TimSort也比较浪费,所以DualPivotQuicksort源码里,当分割出的子序列数量大于MAX_RUN_COUNT时,就跳到了快排主体那里。
接着说下快排算法,一般思路是选取一个值作为一个参考,i,j指针分别指向序列头与序列尾部,保证
int[i] <= 参考值 <=int [j]。一轮遍历之后,左侧都是小于参考值的,右侧都是大于参考值的,然后再次将左子序列与右子序列重复上述逻辑。我们用图来表达:
可以看到经过一轮的排序之后,左边已经都是小于等于参考值的序列,右侧都是大于等于参考值的序列,接下去就是将左子序列与右子序列重新进行一轮基于参考值的排序。最终会将整个序列排为顺序序列。
上面的逻辑整体叫“单轴快速排序”,在DualPivotQuicksort内针对此逻辑做了优化,进化为双轴快排。也就是按两个轴,分成三个区,具体分法如下:
* Partitioning:
*
* left part center part right part
* +----------------------------------------------------------+
* | == pivot1 | pivot1 < && < pivot2 | ? | == pivot2 |
* +----------------------------------------------------------+
* ^ ^ ^
* | | |
* less k great
*
* 不变性:
*
* all in (*, less) == pivot1
* pivot1 < all in [less, k) < pivot2
* all in (great, *) == pivot2
*
* Pointer k is the first index of ?-part.
其处理逻辑是,先将k向右遍历,great向左遍历,把遍历的元素放进合适的区间。然后对三个区间进行递归处理,即得到有序序列。
第三种排序算法是计数排序:/todo
并行排序
二分查找
二分查找拥有大量重载方法,这里以int数组序列做例子。二分查找要求传入的序列是已经排序了的,然后不断将序列范围/2,这里使用 >> 代替/2运算
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
public static int binarySearch(int[] a, int fromIndex, int toIndex,
int key) {
rangeCheck(a.length, fromIndex, toIndex);
return binarySearch0(a, fromIndex, toIndex, key);
}
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
// 低位指针
int low = fromIndex;
// 高位指针
int high = toIndex - 1;
// 高低位指针碰撞则跳出循环
while (low <= high) {
// 不断取高低位中间值
int mid = (low + high) >>> 1;
int midVal = a[mid];
// 中间值与目标值进行比较,更新高位低位指针
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
// 没有找到返回一个负值
return -(low + 1); // key not found.
}
比较
可比较数组序列分为两种大类型,一个是针对基础类型数组进行比较一个是针对通用对象序列进行比较。基础数据类型直接使用==符号进行判定,而Object对象将调用equal方法进行比较。除此之外没有什么特殊的逻辑。
public static boolean equals(int[] a, int[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false;
int length = a.length;
if (a2.length != length)
return false;
for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false;
return true;
}
public static boolean equals(Object[] a, Object[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false;
int length = a.length;
if (a2.length != length)
return false;
for (int i=0; i<length; i++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return true;
}
深度比较要求传入对象数组都为非空,否则直接返回false,并要求被比较序列(a1)元素必须为非null,否则直接返回false。然后开始遍历元素,判定其是否为数组,如果是数组则进行递归比较其序列是否相等。深度比较相比普通比较增加了对元素本身是否是数组的判定以及递归比较是数组的元素。
public static boolean deepEquals(Object[] a1, Object[] a2) {
if (a1 == a2)
return true;
if (a1 == null || a2==null)
return false;
int length = a1.length;
if (a2.length != length)
return false;
for (int i = 0; i < length; i++) {
Object e1 = a1[i];
Object e2 = a2[i];
if (e1 == e2)
continue;
if (e1 == null)
return false;
// Figure out whether the two elements are equal
boolean eq = deepEquals0(e1, e2);
if (!eq)
return false;
}
return true;
}
static boolean deepEquals0(Object e1, Object e2) {
assert e1 != null;
boolean eq;
if (e1 instanceof Object[] && e2 instanceof Object[])
eq = deepEquals ((Object[]) e1, (Object[]) e2);
else if (e1 instanceof byte[] && e2 instanceof byte[])
eq = equals((byte[]) e1, (byte[]) e2);
else if (e1 instanceof short[] && e2 instanceof short[])
eq = equals((short[]) e1, (short[]) e2);
else if (e1 instanceof int[] && e2 instanceof int[])
eq = equals((int[]) e1, (int[]) e2);
else if (e1 instanceof long[] && e2 instanceof long[])
eq = equals((long[]) e1, (long[]) e2);
else if (e1 instanceof char[] && e2 instanceof char[])
eq = equals((char[]) e1, (char[]) e2);
else if (e1 instanceof float[] && e2 instanceof float[])
eq = equals((float[]) e1, (float[]) e2);
else if (e1 instanceof double[] && e2 instanceof double[])
eq = equals((double[]) e1, (double[]) e2);
else if (e1 instanceof boolean[] && e2 instanceof boolean[])
eq = equals((boolean[]) e1, (boolean[]) e2);
else
eq = e1.equals(e2);
return eq;
}
填充
fill操作将数组使用val进行填充。这个代码相对简单
public static void fill(int[] a, int val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
public static void fill(int[] a, int fromIndex, int toIndex, int val) {
rangeCheck(a.length, fromIndex, toIndex);
for (int i = fromIndex; i < toIndex; i++)
a[i] = val;
}
拷贝
copyOf可以将目标数组内元素拷贝为长度为newLength的新数组,如果newLength长度小于目标数组长度则仅抓取前newLength个元素。
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
同样的,针对数组操作可以限定范围,copyOfRange增加了from与to表达选取数组的范围,这里的copyOfRange使用了泛型,因此内部使用Array.newInstance的反射方法创建了同入参类型一致的数组实例,并使用System.arraycopy将旧数组数据拷贝至新数组。
public static <T> T[] copyOfRange(T[] original, int from, int to) {
return copyOfRange(original, from, to, (Class<? extends T[]>) original.getClass());
}
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
哈希
针对数组的hash,最终结果内会掺杂所有元素的信息。
public static int hashCode(int a[]) {
if (a == null)
return 0;
int result = 1;
for (int element : a)
result = 31 * result + element;
return result;
}
通过这部分源码,实际上我们可以轻松创建出不一样的序列但是其hash值一样的数据,例如:
[0,1].hash = (31*1+0)*31 + 1 = [1,-31] = (31*1+1)*31 -31
对于普通对象数组而言,就使用Object#hashCode进行求值,其他一致:
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
同样的,如果元素本身如果是数组的情况下,也可以使用深度hash,将元素转换为数组再次调用hash,这个过程会不断递归,直到元素本身不是一个数组为止
public static int deepHashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a) {
int elementHash = 0;
if (element instanceof Object[])
elementHash = deepHashCode((Object[]) element);
else if (element instanceof byte[])
elementHash = hashCode((byte[]) element);
else if (element instanceof short[])
elementHash = hashCode((short[]) element);
else if (element instanceof int[])
elementHash = hashCode((int[]) element);
else if (element instanceof long[])
elementHash = hashCode((long[]) element);
else if (element instanceof char[])
elementHash = hashCode((char[]) element);
else if (element instanceof float[])
elementHash = hashCode((float[]) element);
else if (element instanceof double[])
elementHash = hashCode((double[]) element);
else if (element instanceof boolean[])
elementHash = hashCode((boolean[]) element);
else if (element != null)
elementHash = element.hashCode();
result = 31 * result + elementHash;
}
return result;
}
打印字符串
toString方法可以将数组进行友好打印
public static String toString(int[] a) {
if (a == null)
return "null";
// 如果数组长度为0,则返回[]
// 这里专门拿出length-1的目的是后续在遍历最后一个元素的时候append上一个']'
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
// 使用StringBuilder作为输出承载
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
// 判定是否遍历到最后一个元素
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
同样的,如果元素本身也是一个数组,就可以使用deepToString方法进行递归打印:
public static String deepToString(Object[] a) {
if (a == null)
return "null";
int bufLen = 20 * a.length;
if (a.length != 0 && bufLen <= 0)
bufLen = Integer.MAX_VALUE;
StringBuilder buf = new StringBuilder(bufLen);
deepToString(a, buf, new HashSet<Object[]>());
return buf.toString();
}
private static void deepToString(Object[] a, StringBuilder buf,
Set<Object[]> dejaVu) {
if (a == null) {
buf.append("null");
return;
}
int iMax = a.length - 1;
if (iMax == -1) {
buf.append("[]");
return;
}
dejaVu.add(a);
buf.append('[');
for (int i = 0; ; i++) {
Object element = a[i];
if (element == null) {
buf.append("null");
} else {
Class<?> eClass = element.getClass();
if (eClass.isArray()) {
if (eClass == byte[].class)
buf.append(toString((byte[]) element));
else if (eClass == short[].class)
buf.append(toString((short[]) element));
else if (eClass == int[].class)
buf.append(toString((int[]) element));
else if (eClass == long[].class)
buf.append(toString((long[]) element));
else if (eClass == char[].class)
buf.append(toString((char[]) element));
else if (eClass == float[].class)
buf.append(toString((float[]) element));
else if (eClass == double[].class)
buf.append(toString((double[]) element));
else if (eClass == boolean[].class)
buf.append(toString((boolean[]) element));
else { // element is an array of object references
if (dejaVu.contains(element))
buf.append("[...]");
else
deepToString((Object[])element, buf, dejaVu);
}
} else { // element is non-null and not an array
buf.append(element.toString());
}
}
if (i == iMax)
break;
buf.append(", ");
}
buf.append(']');
dejaVu.remove(a);
}
java.util.Collections
不可操作转换
不可操作转换指的是将一个集合转换为不能修改的合集。凡是尝试进行元素变更的场合下都会直接抛出异常:
public boolean add(E e) {
throw new UnsupportedOperationException();
}
以下是Collections下的不可操作转换api
static Collection<T> unmodifiableCollection(Collection<T> arg0);
static List<T> unmodifiableList(List<T> arg0);
static Set<T> unmodifiableSet(Set<T> arg0);
static SortedSet<T> unmodifiableSortedSet(SortedSet<T> arg0);
static NavigableSet<T> unmodifiableNavigableSet(NavigableSet<T> arg0);
static Map<T> unmodifiableMap(Map<T> arg0);
static SortedMap<T> unmodifiableSortedMap(SortedMap<T> arg0);
static NavigableMap<T> unmodifiableNavigableMap(NavigableMap<T> arg0);
线程安全转换
线程安全转换操作是将目标合集套了一层,每个方法内部都会加上synchronized锁。
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
默认该锁对象是传入的集合对象,但是也支持从外部传入一个对象作为锁对象:
public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
return new SynchronizedCollection<>(c);
}
static <T> Collection<T> synchronizedCollection(Collection<T> c, Object mutex) {
return new SynchronizedCollection<>(c, mutex);
}
这种封装能解决线程安全问题,但是如果遇到高并发,效率并没有那么快,可以有针对性的选择java.util.concurrency下的对应线程安全类。例如CopyOnWriteLiet,ConcurentHashMap等。
以下是线程安全转换相关的api
static synchronizedSet(Set arg0);
static synchronizedSet(Set arg0,Object arg1);
static synchronizedCollection(Collection arg0);
static synchronizedCollection(Collection arg0,Object arg1);
static synchronizedList(List arg0);
static synchronizedList(List arg0,Object arg1);
static synchronizedSortedSet(SortedSet arg0);
static synchronizedNavigableSet(NavigableSet arg0);
static synchronizedMap(Map arg0);
static synchronizedSortedMap(SortedMap arg0);
static synchronizedNavigableMap(NavigableMap arg0);
类型安全转换
checked~方法可以将容器转换为可检查容器,防止错误添加元素。传入type表达当前容器需要的元素类型。
public static <E> Collection<E> checkedCollection(Collection<E> c,
Class<E> type) {
return new CheckedCollection<>(c, type);
}
// CheckedCollection
public boolean add(E e){
return c.add(typeCheck(e));
}
E typeCheck(Object o) {
if (o != null && !type.isInstance(o))
throw new ClassCastException(badElementMsg(o));
return (E) o;
}
实际上1.6引入的泛型已经可以解决这个问题,不太需要这部分的api了,这里还是列举一下转换相关的api
static checkedCollection(Collection arg0,Class arg1);
static checkedQueue(Queue arg0,Class arg1);
static checkedSet(Set arg0,Class arg1);
static checkedSortedSet(SortedSet arg0,Class arg1);
static checkedNavigableSet(NavigableSet arg0,Class arg1);
static checkedList(List arg0,Class arg1);
static checkedMap(Map arg0,Class arg1,Class arg2);
static checkedSortedMap(SortedMap arg0,Class arg1,Class arg2);
static checkedNavigableMap(NavigableMap arg0,Class arg1,Class arg2);
空容器产生
一般而言,我们创建一个空容器可以直接new一个出来,Collections#empty~相关方法本质上就是将new语句封装为了static调用,并且产生了一个单例空对象。并且无法往此容器内增加数据。如下所示,EmptyList内部没有add等变更元素的相关实现,将会请求到AbstractList,而AbstractList中默认抛出异常
public static final List EMPTY_LIST = new EmptyList<>();
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
产生空容器的相关api如下所示:
static emptyListIterator();
static emptySet();
static emptySortedSet();
static emptyNavigableSet();
static emptyList();
static emptyMap();
static emptySortedMap();
static emptyNavigableMap();
static emptyEnumeration();
static emptyIterator();
单例容器产生
单例容器指的是容器内仅有一个实例,并且不能变更其实例。
public static <T> List<T> singletonList(T o) {
return new SingletonList<>(o);
}
private static class SingletonList<E>
extends AbstractList<E>
implements RandomAccess, Serializable {
private static final long serialVersionUID = 3093736618740652951L;
private final E element;
SingletonList(E obj) {element = obj;}
public Iterator<E> iterator() {
return singletonIterator(element);
}
public int size() {return 1;}
public boolean contains(Object obj) {return eq(obj, element);}
public E get(int index) {
if (index != 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: 1");
return element;
}
// Override default methods for Collection
@Override
public void forEach(Consumer<? super E> action) {
action.accept(element);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
throw new UnsupportedOperationException();
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
throw new UnsupportedOperationException();
}
@Override
public void sort(Comparator<? super E> c) {
}
@Override
public Spliterator<E> spliterator() {
return singletonSpliterator(element);
}
}
这里列举所有单例容器的api:
static singleton(Object arg0);
static singletonIterator(Object arg0);
static singletonSpliterator(Object arg0);
static singletonList(Object arg0);
static singletonMap(Object arg0,Object arg1);
排序
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
这里的sort方法,会调用list自身的sort方法,我们拉取常用的list:
// ArrayList
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
// linkedList -> 真正实现在 java.util.List
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
可以看到都是间接委托给Arrays#sort进行排序。
二分查找
二分查找要求list是有序的,你也可以传入一个自定义比较器,用以比较目标对象与列表内对象的大小。在进行比较的时候,会优先使用RandomAceess接口来判定传入的列表是否支持索引查询。类似传入ArrayList与LinkedList对象之后,其执行逻辑会发生分叉,ArrayList会走到indexedBinarySearch方法,而LinkedList将会使用iteratorBinarySearch进行二分查找,其内部使用迭代器进行操作。
public static <T> 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);
}
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) {
if (c==null)
return binarySearch((List<? extends Comparable<? super T>>) list, key);
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key, c);
else
return Collections.iteratorBinarySearch(list, key, c);
}
这里将indexedBinarySearch和iteratorBinarySearch方法拉出来:
private static <T> int indexedBinarySearch(List<? extends T> l, T key, Comparator<? super T> c) {
int low = 0;
int high = l.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
T midVal = l.get(mid);
int cmp = c.compare(midVal, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
private static <T> int iteratorBinarySearch(List<? extends T> l, T key, Comparator<? super T> c) {
int low = 0;
int high = l.size()-1;
ListIterator<? extends T> i = l.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
T midVal = get(i, mid);
int cmp = c.compare(midVal, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
int pos = i.nextIndex();
if (pos <= index) {
do {
obj = i.next();
} while (pos++ < index);
} else {
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}
可以看到这里的二分逻辑与Arrays#binarySearch 的逻辑差不多,在iteratorBinarySearch中的get(i,mid)将使用迭代器确定索引位置的数据。因此效率不会很高。
倒序
reverse方法可以将列表内的元素进行倒排。类似二分查找,先确定列表是否支持索引定位。
如果支持索引定位,则产生头尾指针,遍历,并将元素位置进行调换。
如果不支持索引定位,则产生指向头尾指针的迭代器,通过类似操作链表的方式不断抽取头指针迭代器的后续节点及尾指针迭代器的前置节点进行交换。
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);
}
}
}
比较器
Comparator是个接口,Collection内针对Comparator也有相关工具方法。reverseOrder方法接受一个比较器,可以将比较器逻辑进行反转。其中ReverseComparator2就是对输入比较器的一个封装。
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
if (cmp == null)
return reverseOrder();
if (cmp instanceof ReverseComparator2)
return ((ReverseComparator2<T>)cmp).cmp;
return new ReverseComparator2<>(cmp);
}
private static class ReverseComparator2<T> implements Comparator<T>, Serializable {
private static final long serialVersionUID = 4374092139857L;
// 输入的比较器
final Comparator<T> cmp;
ReverseComparator2(Comparator<T> cmp) {
assert cmp != null;
this.cmp = cmp;
}
// 注意这里,将t1与t2进行交叉传递到原有比较器,实现比较的反转
public int compare(T t1, T t2) {
return cmp.compare(t2, t1);
}
public boolean equals(Object o) {
return (o == this) ||
(o instanceof ReverseComparator2 &&
cmp.equals(((ReverseComparator2)o).cmp));
}
public int hashCode() {
return cmp.hashCode() ^ Integer.MIN_VALUE;
}
@Override
public Comparator<T> reversed() {
return cmp;
}
}
除了针对一个比较器进行反转,也提供默认反转比较器的能力,其本质还是在执行compare方法的时候进行交叉传递参数进行比较,实现比较反转的能力
public static <T> Comparator<T> reverseOrder() {
return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}
private static class ReverseComparator
implements Comparator<Comparable<Object>>, Serializable {
private static final long serialVersionUID = 7207038068494060240L;
static final ReverseComparator REVERSE_ORDER
= new ReverseComparator();
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c2.compareTo(c1);
}
private Object readResolve() { return Collections.reverseOrder(); }
@Override
public Comparator<Comparable<Object>> reversed() {
return Comparator.naturalOrder();
}
}
混洗
shuffle方法可以将列表内元素打乱顺序,其内部使用Random产生随机值,从而实现乱序效果,如果外部不传递Random对象则将自行初始化。
首先判定是否支持索引访问,如果支持,则以列表长度为循环起始进行i–遍历,产生0-i内的随机值进行互换元素操作。如果列表不支持索引访问,则先将该列表数据转换为对象数组arr,执行混洗,然后从列表产生迭代器,遍历迭代器,将节点值按顺序从arr内获取并替换节点值。
public static void shuffle(List<?> list) {
Random rnd = r;
if (rnd == null)
r = rnd = new Random(); // harmless race.
shuffle(list, rnd);
}
private static Random r;
public static void shuffle(List<?> list, Random rnd) {
int size = list.size();
if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
for (int i=size; i>1; i--)
swap(list, i-1, rnd.nextInt(i));
} else {
Object[] arr = list.toArray();
// Shuffle array
for (int i=size; i>1; i--)
swap(arr, i-1, rnd.nextInt(i));
// Dump array back into list
// 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 it = list.listIterator();
for (int i=0; i<arr.length; i++) {
it.next();
it.set(arr[i]);
}
}
}
private static void swap(Object[] arr, int i, int j) {
Object tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
填充
fill操作可以将列表元素填充为目标对象。先判定是否支持索引访问,如果支持则进行循环set变更索引对应的值。如果不支持索引访问,则先获取到列表的迭代器,从迭代器获得的节点进行赋值操作。
public static <T> void fill(List<? super T> list, T obj) {
int size = list.size();
if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
for (int i=0; i<size; i++)
list.set(i, obj);
} else {
ListIterator<? super T> itr = list.listIterator();
for (int i=0; i<size; i++) {
itr.next();
itr.set(obj);
}
}
}
拷贝
copy方法可以将基准列表(dest)内的元素复制引用到目标列表。目标容器容量必须大于基准列表大小,否则报错。
public static <T> void 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 {
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());
}
}
}
逻辑多副本生成
nCopies可以被视为产生了n大小,以o填充的列表。但是其底层实际上还是使用一个e引用,并没有产生多份引用拷贝。
public static <T> List<T> nCopies(int n, T o) {
if (n < 0)
throw new IllegalArgumentException("List length = " + n);
return new CopiesList<>(n, o);
}
// java.lang.Collections.CopiesList
final int n;
final E element;
CopiesList(int n, E e) {
assert n >= 0;
this.n = n;
element = e;
}
循环移位
rotate方法帮助我们循环移位列表数据,例如存在序列 [1,2,3,4,5],循环1位会得到[5,1,2,3,4],循环两位会得到 [5,4,1,2,3]。在源码中,如果判定列表支持索引访问,则执行rotate1,否则执行rotate2。
public 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<T> list, int distance) {
int size = list.size();
// 列表长度为0则返回
if (size == 0)
return;
// 每移位size个位置则回到最开始的样子,因此做取余
distance = distance % size;
// distance为负数等价于size+distance
if (distance < 0)
distance += size;
// 如果取余后为0则直接返回
if (distance == 0)
return;
// 循环来遍历元素,每次从一个起始位置开始,用一个变量displaced来保存被移动的元素,
// 然后用一个内部循环来计算被移动元素的新位置,每次加上距离,
// 如果超过了列表大小,那么就减去列表大小,然后用列表的 set 方法来替换新位置的元素,并把被替换的元素赋值给 displaced,
// 同时记录移动的元素的个数,直到移动的元素的个数等于列表的大小为止。
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;
displaced = list.set(i, displaced);
nMoved ++;
} while (i != cycleStart);
}
}
// 计算mid,mid表达被移位的分界点,反转0-mid,与mid-size的元素
// 最终反转整个列表完成旋转的效果。
private static 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 int frequency(Collection<?> c, Object o) {
int result = 0;
if (o == null) {
for (Object e : c)
if (e == null)
result++;
} else {
for (Object e : c)
if (o.equals(e))
result++;
}
return result;
}
frequency帮助我们统计目标元素在容器内出现的次数。
交集判定
disjoint�
public static boolean disjoint(Collection<?> c1, Collection<?> c2) {
// The collection to be used for contains(). Preference is given to
// the collection who's contains() has lower O() complexity.
Collection<?> contains = c2;
// The collection to be iterated. If the collections' contains() impl
// are of different O() complexity, the collection with slower
// contains() will be used for iteration. For collections who's
// contains() are of the same complexity then best performance is
// achieved by iterating the smaller collection.
Collection<?> iterate = c1;
// Performance optimization cases. The heuristics:
// 1. Generally iterate over c1.
// 2. If c1 is a Set then iterate over c2.
// 3. If either collection is empty then result is always true.
// 4. Iterate over the smaller Collection.
if (c1 instanceof Set) {
// Use c1 for contains as a Set's contains() is expected to perform
// better than O(N/2)
iterate = c2;
contains = c1;
} else if (!(c2 instanceof Set)) {
// Both are mere Collections. Iterate over smaller collection.
// Example: If c1 contains 3 elements and c2 contains 50 elements and
// assuming contains() requires ceiling(N/2) comparisons then
// checking for all c1 elements in c2 would require 75 comparisons
// (3 * ceiling(50/2)) vs. checking all c2 elements in c1 requiring
// 100 comparisons (50 * ceiling(3/2)).
int c1size = c1.size();
int c2size = c2.size();
if (c1size == 0 || c2size == 0) {
// At least one collection is empty. Nothing will match.
return true;
}
if (c1size > c2size) {
iterate = c2;
contains = c1;
}
}
for (Object e : iterate) {
if (contains.contains(e)) {
// Found a common element. Collections are not disjoint.
return false;
}
}
// No common elements were found.
return true;
}
统计计算
Collection提供求得序列内最小最大值的能力。其实现原理是使用迭代器进行遍历判定最小/最大。
public static <T extends Object & Comparable<? super T>> 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;
}
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {
if (comp==null)
return (T)min((Collection) coll);
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (comp.compare(next, candidate) < 0)
candidate = next;
}
return candidate;
}
public static <T extends Object & Comparable<? super T>> T max(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;
}
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
if (comp==null)
return (T)max((Collection) coll);
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (comp.compare(next, candidate) > 0)
candidate = next;
}
return candidate;
}
容器转换操作
如下,是一些容器转换的api,都相对简单,使用类来对数据做封装。
static asLifoQueue(Deque arg0);
static newSetFromMap(Map arg0);
static enumeration(Collection arg0);
static list(Enumeration arg0);
java.lang.Objects
Objects的方法相对简单,提供比较,hash,以及一些简单的判定方法,在此不做过多描述。
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public static boolean deepEquals(Object a, Object b) {
if (a == b)
return true;
else if (a == null || b == null)
return false;
else
return Arrays.deepEquals0(a, b);
}
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static String toString(Object o) {
return String.valueOf(o);
}
public static String toString(Object o, String nullDefault) {
return (o != null) ? o.toString() : nullDefault;
}
public static <T> int compare(T a, T b, Comparator<? super T> c) {
return (a == b) ? 0 : c.compare(a, b);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
public static boolean isNull(Object obj) {
return obj == null;
}
public static boolean nonNull(Object obj) {
return obj != null;
}
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier.get());
return obj;
}