List

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;
        }
    }

这两种并发集合,虽然牛逼,但只适合于读多写少的情况,如果写多读少,使用这个就没意义了,因为每次写操作都要进行集合内存复制,性能开销很大,如果集合较大,很容易造成内存溢出。


   转载规则


《List》 锦泉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
String String
本文记录了String的方法和正则表达式的基本说明。都是在刷题时遇到记录下来的。
2020-12-09
本篇 
List List
本文记录了ArrayList的toArray方法用法,和CopyOnWriteArrayList。
2020-12-09
  目录