ArrayList
ArrayList提供了一个将List转为数组的一个非常方便的方法toArray。toArray有两个重载的方法:
(1)list.toArray()
(2)list.toArray(T[ ] a)
说明
对于第一个重载方法,是将list直接转为Object[ ] 数组;转化的话只能是取出每一个元素再转化,像这样:
Object[] arr = list.toArray();
for (int i = 0; i < arr.length; i++) {
String e = (String) arr[i];
System.out.println(e);
}
第二种方法是将list转化为你所需要类型的数组,当然我们用的时候会转化为与list内容相同的类型。
String[] array =new String[list.size()];
list.toArray(array);
下面是两个重构方法的源码:
//1.
public Object[] toArray(); {
Object[] result = new Object[size];
System.arraycopy(elementData, 0, result, 0, size);;
return result;
}
//2.
public Object[] toArray(Object a[]); {
if (a.length < size);
a = (Object[]);java.lang.reflect.Array.newInstance(
a.getClass();.getComponentType();, size);;
System.arraycopy(elementData, 0, a, 0, size);;
if (a.length > size);
a[size] = null;
return a;
}
线程安全
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
求2个集合的交集
public static List intersect(List ls, List ls2) {
List list = new ArrayList(Arrays.asList(new Object[ls.size()]));
Collections.copy(list, ls);
list.retainAll(ls2);
return list;
}
求2个集合的并集
public static List union(List ls, List ls2) {
List list = new ArrayList(Arrays.asList(new Object[ls.size()]));
Collections.copy(list, ls);//将ls的值拷贝一份到list中
list.removeAll(ls2);
list.addAll(ls2);
return list;
}
SynchronizedList
java.util.Collections.SynchronizedList,它能把所有 List 接口的实现类转换成线程安全的List,比 Vector 有更好的扩展性和兼容性,SynchronizedList的构造方法如下:
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList的部分方法源码如下:
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
很可惜,它所有方法都是带同步对象锁的,和 Vector 一样,它不是性能最优的。
CopyOnWriteArrayList
CopyOnWrite(简称:COW):即复制再写入,就是在添加元素的时候,先把原 List 列表复制一份,再添加新的元素。
java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet
先来看下它的 add 方法源码:
public boolean add(E e) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原始集合
Object[] elements = getArray();
int len = elements.length;
// 复制一个新集合
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// 替换原始集合为新集合
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
添加元素时,先加锁,再进行复制替换操作,最后再释放锁。
再来看下它的 get 方法源码:
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
可以看到,获取元素并没有加锁。
这样做的好处是,在高并发情况下,读取元素时就不用加锁,写数据时才加锁,大大提升了读取性能。
CopyOnWriteArraySet
CopyOnWriteArraySet逻辑就更简单了,就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法来去重的,添加元素的时候判断对象是否已经存在,不存在才添加进集合。
/**
* Appends the element, if not present.
*
* @param e element to be added to this list, if absent
* @return {@code true} if the element was added
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
//indexOf()返回第一个字符为e的下标
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* 加锁之后再次判断,以防在这期间有线程执行了写操作
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
synchronized (lock) {
Object[] current = getArray();
int len = current.length;
//两次若不一样,说明数组已经改变
if (snapshot != current) {
// 找到前后两次数组长度的最小值
int common = Math.min(snapshot.length, len);
//先在0-common中比较两个数组,比较有出入的地方,存在e则返回false,再比较common-len,存在e(返回值>=0)则返回false
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i]
&& Objects.equals(e, current[i]))
return false;
if (indexOfRange(e, current, common, len) >= 0)
return false;
}
//老套路,数组拷贝,新数组添加元素,指向array
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
这两种并发集合,虽然牛逼,但只适合于读多写少的情况,如果写多读少,使用这个就没意义了,因为每次写操作都要进行集合内存复制,性能开销很大,如果集合较大,很容易造成内存溢出。