ArrayList,vector,LinkedList

集合类中线程安全的就只有,vector,hashtable,concurrentHashmap

ArrayList:

ArrayList 实现于 ListRandomAccess 接口。可以插入空数据,也支持随机访问。

ArrayList的初始容量为10,这里我们主要了解一下ArrayList的扩容机制

对于一个普通的数组,我们一个一个添加的时候,size就会每加一个就扩容一次

添加到末尾

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 在size=10的时候就会开始扩充了,第一次扩充到15,就不用每次加一这样扩容了
        //这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
}

 在任意位置插入

public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //复制,向后移动
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

扩容校验:

1.得到最小扩容量

 private void ensureCapacityInternal(int minCapacity) {
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和传入参数的较大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
      }

      ensureExplicitCapacity(minCapacity);
 }

2.判断是否需要扩容

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//版本号增加
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
}
    
private void grow(int minCapacity) { // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; //将oldCapacity 右移一位,其效果相当于oldCapacity /2, //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity;//没有超过的话就按定义的最大容量 //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }

对于扩容的总结:

每次都会扩容到1.5倍,不会每次添加都会扩容了,效率会增加很多,然后在大量的数据的时候多用ensureCapacity这个方法,这个方法是用来给用户用的,防止反复的扩容可以增加效率。

ArrayList序列化问题:

由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用。因此使用了 transient 修饰,可以防止被自动序列化。

transient Object[] elementData;

elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
    // 防止序列化期间有修改
    int expectedModCount = modCount;
    // 写出非transient非static属性(会写出size属性)
    s.defaultWriteObject();

    // 写出元素个数
    s.writeInt(size);

    // 依次写出元素
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    // 如果有修改,抛出异常
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    // 声明为空数组
    elementData = EMPTY_ELEMENTDATA;

    // 读入非transient非static属性(会读取size属性)
    s.defaultReadObject();

    // 读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
    s.readInt();

    if (size > 0) {
        // 计算容量
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        // 检查是否需要扩容
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // 依次读取元素到数组中
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

关于ArrayList的克隆的问题:

其中的源码:

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // 这不应该发生,因为我们是可以克隆的
            throw new InternalError(e);
        }
    }

用一个例子理解ArrayList的复制:

public static void main(String[] args) {
        ArrayList<Person> a=new ArrayList<>();
        Person b=new Person("lijun",21);
        a.add(b);
        ArrayList<Person> c=(ArrayList<Person>) a.clone();
        System.out.println(a==c);
        System.out.println(a.toArray(new Person[0])==c.toArray(new Person[0]));
        System.out.println(a.get(0)==c.get(0));
    }

false

false

true

其中基本上可以算是深复制了,已经重新生成了ArrayLIst了,但是ArrayList里面的数组是深复制,因为copyOf重新生成了一个新的数组,但是数组指向的仍然是原来数组里面的对象。

public static void main(String[] args) {
        ArrayList<Person> a=new ArrayList<>();
        Person b=new Person("lijun",21);
        ArrayList<Person> c=(ArrayList<Person>) a.clone();
        c.get(0).setName("yang");
        System.out.println(a.get(0));    
    }

输出:yang,所以进一步验证了我上面的结论。

ArrayList里面有几个求并交差的函数:

        // 并集
        // list1.addAll(list2);
        // 交集
        //list1.retainAll(list2);
        // 差集
        // list1.removeAll(list2);

batchremove源码:

https://blog.csdn.net/weixin_40841731/article/details/85263889

关于ArrayList的几个点:

1.ArrayList中,默认的大小是10,当你使用new ArrayList();时并不会立即为数组分配大小为10的空间,而是等插入第一个元素时才会真正分配,这样做是为了节约内存空间。

2.维护内部数组时,使用的是Arrays.copyOf()方法和System.arraycopy()方法。

3.在扩容时,默认的扩容因子是1.5,每次需要扩容时,会将原数组大小的1.5倍和实际需要的数组空间进行比较,从中取最大值作为数组大小。然后新建一个数组,把原数组中的所有元素复制到新数组中去。所以扩容其实是最耗费时间的操作,不仅仅需要重新分配空间,而且需要重新赋值。

4.多用ensureCapacity,防止频繁扩容。

5.modCount标识,使用迭代器的不要尝试修改,会报错。

vector:

基本上就上在ArrayList的所有方法上加了Synchronized关键字

LinkedList:

LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; 

LinkedList主要的优点还是插入删除很方便(仅仅就是移动指针)。

另外可以很容易的实现队列,栈这些数据结构。

对于查询,有利用到双向链表的特性:

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
}

上述代码,利用了双向链表的特性,如果index离链表头比较近,就从节点头部遍历。否则就从节点尾部开始遍历。使用空间(双向链表)来换取时间。

node()会以O(n/2)的性能去获取一个结点:如果索引值大于链表大小的一半,那么将从尾结点开始遍历

原文地址:https://www.cnblogs.com/peterleee/p/10530615.html