集合(二)List,ArrayList,LinekList,Vector

ArrayList,LinkedList,Vector

List,Queue中的元素有序可重复

ArrayList,LinekList,Vector 均为可伸缩数组,即就是可以动态改变长度的数组。

1.ArrayList

数组实现,查询速度快,插入慢,非线程安全。ArrayList底层维护了一个Object[]用于存储对象,默认数组的长度是10。可以通过new ArrayList(20)显式的指定用于存储对象的数组的长度。

它继承于AbstractList,实现了List, RandomAccess(标记接口,无任何方法。快速随机访问存储元素), Cloneable(克隆), Serializable(序列化,hessian协议传输)接口。

1.添加元素时,ensureCapacityInternal()方法会计算ArrayList的扩容大小。如果需要扩容,扩容的大小为原来的1.5倍,实际上是使用Arrays.copyOf()方法实现的。

添加元素时的步骤:

(1)调用ensureCapacityInternal()确定容量是否满足需求,不满足则开辟空间,使用Array.copy()方法重新创建对象指向这个引用

(2)在elementData对应位置上设置值

elementData[size] = data

size++

2.删除元素时,如果不是最后一个元素,最终都会调用System.arraycopy()方法进行数组复制操作

3.modeCount变量的含义:在迭代器初始化时,将modeCount的值赋值给expectedModCount,每一次增删改,modCount的值都会加1,因此modeCount记录了ArrayList内发生改变的次数。

迭代获取下一个元素的时候,会判断expectedModCount的值与modeCount是否一致,如果不一致,则会报异常

4.transient变量:当序列化对象的时候,如果对象的某个属性不进行序列化操作,则用transient修饰就可以。

5.elementData是ArrayList集合中保存元素的数组。ArrayList中对于elementData变量就用transient来修饰,为什么这个变量不序列化呢?

ArrayList在添加元素时,可能会对elementData数组进行扩容操作,而扩容后的数组可能并没有全部保存元素。

例如:我们创建了new Object[10]数组对象,但是我们只向其中添加了1个元素,而剩余的9个位置并没有添加元素。当我们进行序列化时,并不会只序列化其中一个元素,而是将整个数组进行序列化操作,那些没有被元素填充的位置也进行了序列化操作,间接的浪费了磁盘的空间,以及程序的性能。所以,ArrayList才会在elementData属性前加上transient修饰符。

ArrayList在序列化时会调用writeObject(),直接将elementData写入ObjectOutputStream;

而反序列化时则调用readObject(),从ObjectInputStream获取elementData;

Arrays.copyOf()方法的底层调用System.arrayCopy()方法。arrayCopy()方法使用了native关键字修饰,调用的是c++的底层函数

ArrayList为什么是线程不安全的?

ArrayList是数组实现的,有两个变量;

transient Object[] elementData;    //存放元素

private int size;                             //已经添加了多少元素

添加元素时,可以分为两个步骤:

(1)elementData[size] = data;

(2)size++;

例子:两个线程A,B.线程A执行了第一条语句,这时cpu将资源给了线程B,线程B就会覆盖线程A的值,后面两个线程都执行了第二条语句。就会出现值为Null的 情况。

出现ArrayIndexOutOfBoundsException的情况:

假设有个ArrayList的size是9,A线程在添加的时候检查容量ensureCapacityInternal()之后,没有继续执行操作,此时B线程执行,这时容量刚好够添加一个元素,于是B线程添加了一个元素。size++,现在size = 10,这时,A线程继续执行添加的操作,由于size = size+1 = 10 ,size>length;size大于实际的最大长度,所以出现异常。

参考:

A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,因此便出现了ArrayIndexOutOfBoundsException异常。

--------------------- 本文来自 男人的暴走 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/zhangxin961304090/article/details/46804065?utm_source=copy

在多线程中使用安全的:

List list = Collections.synchronizedList(new ArrayList());

List list = new Vector();

2.Vector

队列实现,并且实现了RandomAccess接口,可以随机访问,绝大多数方法都是直接或者间接同步的,是线程安全的ArrayList

Vector和ArrayList都会在内存中开辟一块连续的空间来存储,数据存储是连续的.

3.LinkedList

jdk1.7后, LinkedList变成了直线型链表结构,双向循环链表实现的,插入快,查找慢,非线程安全。查找的时候从头到尾查找,在内存中的地址不一定连续。上一个元素需要记住下一个元素。descendingIterator()函数可以逆序迭代,因此LinkedList可以模拟队列(list.offer("**")先进先出list.poll())或者堆栈(list.push("***")后进先出list.pop())数据结构

它继承AbstractSequentialList,实现了List, Deque, Cloneable, Serializable接口。

Deque是一个双向队列,既可以先入先出,也可以先入后出(既可以在头部添加元素,也可以在尾部添加元素)

node(int index)方法至关重要,通过对应角标获取到对应的集合元素。node()中是根据角标的大小是选择从前遍历还是从后遍历整个集合。也可以间接的说明,LinkedList在随机获取元素时性能很低,每次的获取都得从头或者从尾遍历半个集合。

node方法:

//设置对应角标的元素:
public E set(int index, E element) {
    checkElementIndex(index);
    //通过node()方法,获取到对应角标的元素:
    java.util.LinkedList.Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}
//获取对应角标所属于的结点:
java.util.LinkedList.Node<E> node(int index) {
    //位运算:如果位置索引小于列表长度的一半,则从头开始遍历;否则,从后开始遍历;
    if (index < (size >> 1)) {
        java.util.LinkedList.Node<E> x = first;
        //从头结点开始遍历:遍历的长度就是index的长度,获取对应的index的元素
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //从集合尾结点遍历:
        java.util.LinkedList.Node<E> x = last;
        //同样道理:
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

get()方法:核心还是调用node()方法

参考:https://blog.csdn.net/qq_33642117/article/details/51998866

https://mp.weixin.qq.com/s?__biz=MzI5ODI5NDkxMw==&mid=2247486782&idx=1&sn=67ad4a2e45dfcefc6df5e77d8a94e572&chksm=eca946d0dbdecfc6ee491c600e8899da6f05141ba839f49bae450f08f7118c9a5b8637a55c19&scene=21#wechat_redirect

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值