Java集合框架——List

ArrayList

1、数据结构
基于数组结构,默认大小为10,支持快速随机访问 (实现了RandomAccess),但插入删除代价高

当我们装载的是基本类型的数据int,long,boolean,short,byte…的时候我们只能存储他们对应的包装类,它的主要底层实现是数组Object[] elementData。

2、主要使用的api

boolean add(E e)
E get(int index)
int size()

3、一般使用流程

		//1、创建ArrayList
        List list = new ArrayList();
        //2、添加元素
        list.add("1");
        list.add("2");
        list.add("3");
        //3、从list中取出某个元素
        String s = (String)list.get(1);
        //4、遍历整个list
        for (Object o : list) {
            String str = o.toString();
        }
        //5、指定位置添加
        list.add(2,"cf");
        //6、指定位置删除
        list.remove(2);

4、重要参数

	//默认大小
 	private static final int DEFAULT_CAPACITY = 10;		
 	//未初始化前的默认容量空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  	
    //ArrayList底层存储的数组
    transient Object[] elementData;
    //ArrayList中的元素个数
    private int size;

transient 动态序列化,只序列化有元素部分的数组

5、构造函数

public ArrayList()							//无参构造
public ArrayList(Collection<? extends E> c)	//将集合c复制到一个ArrayList中
public ArrayList(int initialCapacity)		//创建指定容量大小的ArrayList

无参构造:创建的 ArrayList 未初始化(底层数组为空数组)直到首次执行 add() 初始化扩容为默认长度10的数组

指定容量构造: ArrayList 会创建长度为 initialCapacity 的数组,首次执行 add() 一般不再触发扩容

6、add过程(以下代码均来源于jdk 11

	//1、提供给外界调用的方法
    public boolean add(E e) {
        modCount++;		//操作数,ArrayList实现了快速失败
        add(e, elementData, size);
        return true;
    }
	//2、每当size==数组长度时,扩容
	private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();	//第一次进来触发初始化扩容
        elementData[s] = e;
        size = s + 1;
    }

7、扩容grow()
条件:s == elementData.length,原数组满了
流程:

  1. 重新定义一个长度约为原数组1.5倍的新数组
  2. 将原数组中数据原封不动的复制到新数组中,再将地址指向新数组即可
    //3、扩容
    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
    	//创建一个新的数组,将原数组中的元素复制过去
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
    private int newCapacity(int minCapacity) {

        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //对1.7的优化,效率更高
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            	//首次初始化为10
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

扩容:grow();每次扩容为原容量+原容量/2(1.8优化通过右移位运算实现),通过创建新容量数组并复制旧元素到新数组中,花销代价高

8、指定位置add流程:

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        //检查是否需要扩容
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        //复制从index到末尾的数组,将它放到index+1,就是往后挪一个位置出来,当list很大时效率极低
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }

9、删除指定位置:
指定位置add的逆过程,也是要用到arraycopy,复制后面的数组挪上来

ArrayList插入删除一定很慢吗?
其实不一定,如果将它作为一个堆栈来用,它的push和pop操作不涉及数据移动,效率还是不错的。
ArrayList用来做队列合适么?不合适,会涉及数据移动
那数组适合用来做队列么?
合适,比如ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。

10、遍历性能
ArrayList的遍历和LinkedList遍历性能比较如何?

论遍历ArrayList要比LinkedList快得多,ArrayList遍历最大的优势在于内存的连续性,
CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。

11、fail-fast:快速失败
参数:modCount

  1. modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
  2. 在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。

问题:ArrayList使用forEach遍历的时候删除元素容易出现ConcurrentModificationException
因此需要遍历删除某个元素时,应该使用Iterator的方法remove,具体请参考:ArraryList遍历的时候删除元素会报错的原因

12、线程不安全
ArrayList不是线程安全的集合,在并发编程下不安全。

  1. 可以使用Collections.synchronizedList(放入list),或者Vector,但他们都是将传统方法统统加上synchronized,效率不高。
  2. concurrent并发包下的CopyOnWriteArrayList

参考文章:ArrayList

LinkedList

基于双向链表,不支持随机访问,插入删除代价低,非线程安全

Vector

Vector 和 ArrayList 类似,但属于强同步类。如果程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

线程安全,加锁同步但开销大,性能低,已经弃用;
扩容默认是两倍

与ArrayList的区别:

  1. 扩容大小:Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%。
  2. 线程安全
  3. 初始化:Vector在构造方法中就直接初始化数组且指定容量,而ArrayList在add方法中才进行初始化数组。
  4. 序列化:ArrayList使用了transient关键字进行存储优化

ArrayList 的初始化时机

构造函数 ArrayList(int initialCapacity)会不会初始化数组大小?

不会初始化数组大小!准确来说,集合大小不会初始化为指定容量。

不管指定容量多少,构造出来的集合 size == 0;

List list = new ArrayList(10);
System.out.println(list.size()); // 0
list.set(5,"init");
// IndexOutOfBoundsException: Index: 5, Size: 0

即使创建了定长数据,为什么 set/add()方法 指定index(大于size)依然报错?

因为虽然ArrayList的底层是定长数组,但是其任何操作都是围绕 size 大小来执行的,这就保证了数组非空元素连续且置顶存放:[0,1,2,3,4,5,6,7,null,null]

ArrayList 的序列化

ArrayList使用了transient关键字进行存储优化,为什么?

  • 由于其扩容机制,容量每次增加为原来的1.5倍。对于其底层数组来说,会预留一些空间,这些空间可能没有实际存储元素。

  • ArrayList重写了writeObjectreadObject序列化方法,保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间

SynchronizedList

SynchronizedList是java.util.Collections中的一个静态内部类。它与 Vector 同样是线程安全的集合,通过Collections.synchronizedList(List list)方法获得。

主要了解SynchronizedList 与 Vector 的区别:

  1. Vector使用同步方法实现,synchronizedList使用同步代码块实现。后者能选择对哪个对象加锁,前者只能给this 对象加锁
  2. SynchronizedList 有很好的扩展和兼容功能,因为它可以将 ArrayList、LinkedList等List的子类都包装成线程安全,而 Vector 只能是数组。
  3. 使用SynchronizedList的时候,进行遍历时要手动进行同步处理,因为SynchronizedList没有对 Iterator 做同步处理,而Vector却对该方法加了方法锁。

CopyOnWriteArrayList

CopyOnWriteArrayList 是 CopyOnWrite容器中的一员。

Copy-On-Write简称COW,是一种程序设计中的优化策略。基本思路是:读时共享,写时复制。

原理

CopyOnWriteArrayList 是一个读写分离容器。

  • 对它进行并发读时,不需要加锁,因为当前容器不进行写操作;
  • 当有线程执行add操作时,会先从原数组copy出一个新数组,然后在新数组中写操作,写完后将原数组引用指向新数组,在整个add过程(写)加锁。

优势

  1. CopyOnWriteArrayList 可以当成线程安全的 ArrayList
  2. 适用于读多写少的并发环境下
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值