资讯专栏INFORMATION COLUMN

Java中的List集合

gplane / 2841人阅读

摘要:接口的实现类表示有序的集合元素可以重复,根据索引来寻找元素,放入其中的元素的存储顺序和放入顺序是一致的。包下的集合并发类与等方法装饰的类有什么不同先讲一下这个线程安全的类。

List接口的实现类

List表示有序的集合(元素可以重复),根据索引来寻找元素,放入其中的元素的存储顺序和放入顺序是一致的。

ArrayList

0.继承自AbstractList,拥有通用的方法如Iterator迭代器。实现了List接口。

1.底层是transient Object[] elementData 数组。可以看到默认大小是10

2.不同的初始化方式,有一点区别。

// 未指定,默认是10。构造的数组大小为0,要等到放入第一个元素时才会扩容成10个。
List list1 = new ArrayList<>();

// 构造的数组大小为0。
List list2 = new ArrayList<>(0);

// 构造的数组大小为14
List list3 = new ArrayList<>(14);

3.数组扩容

默认初始化的内部数组大小是10,当放入第11个元素时会进行第一次扩容:newCapacity = oldCapacity + oldCapacity >> 1 ,也就是变成原来的1.5倍。(所以默认情况下,放入第11个元素时,扩容成15个;放入第16个元素时,扩容成22个;放入第23个元素时,扩容成33个。)

数组扩容的操作是进行数组的复制,所以扩容消耗资源,应该尽量先指明所需的容量,来减少扩容操作。对于已经存在的ArrayList,在放入大量元素前,可以手动进行扩容:ensureCapacity(capacity) 方法来重新设置内部数组的大小。

4.有一个骚操作,把ArrayList转化成对象数组:

// 调用toArray方法,传入对象数组接收,返回Object[]数组
Object[] objects2 = appleList.toArray(new Apple[0]);
// 类型转换
Apple[] apples3 = (Apple[]) objects2;

5.ArrayList的add(int index, Object obj)remove(int index) / remove(Object obj),即随机的增加、删除操作都会进行内部数组的复制,所以ArrayList只适合顺序插入、删除,随机访问get(int index),不适合随机增加、删除操作多的场景。

ArrayList总结

1.初始化时应该指明容量,避免多次的扩容操作。如果已经分配了容量但是不够用,建议先手动扩容大小后再添加元素。

2.使用场景应该是随机查找比较多而随机增加、删除操作比较少的场景。

LinkedList

1.继承自AbstractSequentialList,拥有通用的方法如iterator。实现List接口。实现Queue接口,拥有队列的特性;实现Deque接口,拥有双端队列的特性。

2.LinkedList内部的节点。拥有前后指针,实现的是双端队列的性质。

内部的私有属性,存储了链表的节点个数以及保存了链表的头尾指针。

3.因为LinkedList实现了Queue接口、Deque接口,所以它既能作为队列也能作为堆栈来使用。又因为实现了List接口,所以又是有序的Collection。这意味着它有3种数据结构的作用,我们应该在正确的场景下正确语义化使用,这表示我们要使用合适的接口来声明LinedList。(注意双端队列Deque接口提供了传统Stack的操作方法声明,所以它可以作为堆栈Stack使用)

4.对于LinkedList的随机访问操作,它内部有一个优化。如果index<(size/2),就从头部开始查找;否则从尾部开始查找。

LinkedList总结

1.使用场景应该是随机增加、删除操作多的情况,而随机访问操作少的情况。

2.LinkedList有3种数据结构的身份,我们应该在正确的场景下进行正确的接口声明,并且使用与之对应的语义化方法来操作LinkedList。我们在使用栈stack结构的时候可以使用LinkedList,在使用队列Queue的时候可以使用LinkedList,在使用有序集合(链表)的时候可以使用LinkedList。

Vector

vector作为古老的集合类,是jdk1.2之后才改为实现List接口的。它与ArrayList的性质很像(2者的继承图是一样的),但是其内部方法都使用了synchronized修饰来保证线程安全,这使得对Vector的操作在多线程下变成了串行操作。要注意的是,它的实现是使用synchronized修饰整个方法,而不是方法内部的某段代码,这使得其锁的粒度特别大,效率十分低!

Vector已经不推荐使用了,单线程下我们建议选用ArrayList,多线程下我们建议选用java.util.concurrent包下的CopyOnWriteArray或使用Collections.synchronizedList(ArrayList list)修饰过的ArrayList。

列举synchronized修饰的add、get操作:

1.默认初始化大小为10,扩容操作时,如果构造时指明了增大的容量,则增加;否则默认变成原来的2倍

Stack

Stack作为Vector的子类,可用于实现堆栈。但是不建议使用,而是使用Deque接口的实现类如LinkedList、ArrayList来替代堆栈结构。

为什么不使用Stack,而推荐使用LinkedList呢?原因还是因为它继承自Vector,所以它的栈相关的操作也是synchronized修饰的!所以单线程下,我们还是推荐使用LinkedList所能实现的栈结构;多线程下则可以考虑java.util.concurrent包下的ConcurrentLinkedDeque所能实现的并发栈结构或Collections.synchronizedList(LinkedList list)修饰的LinkedList。

当然,我们也可以使用ArrayDeque类!
讲讲List集合对应的并发类

ArrayList对应的,java.util.concurrent.CopyOnWriteArrayList类和Collections.synchronizedList(ArrayList list)所修饰的类。

LinkedList对应的,就是Collections.synchronizedList(LinkedList list)所修饰的类。

要注意的是,到了并发集合(java.util.concurrent包)这一块,都是按照接口性质设置的并发类。所以应该讲成List接口对应的并发类是java.util.concurrent.CopyOnWriteArrayList类。

java.util.concurrent包下的集合并发类与Collections.synchronizedList()等方法装饰的类有什么不同?

先讲一下Vector这个线程安全的List类。其线程安全的实现方式是对所有操作都加上了synchronized关键字,其锁的粒度是整个方法,这种方式严重影响效率,使得程序串行进行。

而Collections.synchronizedList等方法,是采用了装饰器的模式来返回一个包装类。以Collections.synchronizedList(ArrayList list)为例,返回的包装类的部分操作如add、get、remove方法是在方法内部的代码块加上了synchronized关键字,这使得锁的粒度较小!

而java.util.concurrent包下的CopyOnWriteArrayList,其写操作是写时复制(就和名字一样),通过可重入锁显式加锁来达到同步互斥的目的。而且每次新加入元素,都复制原数组一份,然后对新数组进行增加操作,然后在替换原引用,这就达到了写入分离。(删除操作同样的道理)这里很重要,下面要讲一下get方法!

get方法没有任何加锁同步操作!这里就很有趣了!根据对写入操作的分析,如果并发时一个线程在写入,另外一个线程在读取,那么写入未完成之前,读取操作所读到的数组是原先的数组!

所以,这正是CopyOnWrite容器的缺点:CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。并发情况下,某个线程读取到的数据可能是旧数据!

其次,对内存有消耗,如果多个线程并发执行,那么数组复制时需要新数组来保存,就占用了2份内存!如果数组占用的内存较大,就会引发频繁的垃圾回收行为,降低性能。

所以对于CopyOnWrite容器来说,它适合读操作频繁,而写操作少的并发场景,比如说数据的缓存!

List集合图

下面对Collection集合下的List有序集合进行一个类图的整合:

可以看到的是ArrayList和Vector的继承图是一致的!它们的区别就是内部实现不同,所以我们可以大胆地放弃Vector类了。而LinkedList较之于ArrayList,多实现了Deque接口,这使得它不仅具备了List的特性,还拥有双端队列的特性,可以拿来做栈、队列等结构!

Collection接口继承自Iterable,这意味着所有的集合类都可以返回迭代器进行遍历。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77271.html

相关文章

  • Java集合框架——List接口

    摘要:第三阶段常见对象的学习集合框架接口按照集合框架的继承体系,我们先从中的接口开始学习一概述及功能演示概述在中充当着一个什么样的身份呢有序的也称为序列实现这个接口的用户以对列表中每个元素的插入位置进行精确地控制。线程不安全,效率高。 第三阶段 JAVA常见对象的学习 集合框架——List接口 showImg(https://segmentfault.com/img/remote/14600...

    褰辩话 评论0 收藏0
  • Java编程基础16——Colletion集合

    摘要:集合的长度的是可变的,可以根据元素的增加而增长。如果元素个数不是固定的推荐用集合。线程安全,效率低。相对查询慢线程安全的相对增删慢数组结构底层数据结构是链表,查询慢,增删快。线程不安全,效率高。 1_对象数组的概述和使用 A:案例演示 需求:我有5个学生,请把这个5个学生的信息存储到数组中,并遍历数组,获取得到每一个学生信息。 import net.allidea.bean.Stu...

    TerryCai 评论0 收藏0
  • Java 集合 Collections工具类

    摘要:提供了一个操作和等集合的工具类,该工具类提供了大量方法对集合进行排序查询和修改等操作,还提供了将集合对象置为不可变对集合对象实现同步控制等方法排序操作反转指定集合中元素的顺序对集合元素进行随机排序方法模拟了洗牌动作根据元素的自然顺序对指定集 Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类提供了大量方法对集合进行排序、查询和修改等操作,还提...

    qieangel2013 评论0 收藏0
  • Java中如何优雅地删除List中的元素

    摘要:删除元素后,立即跳出,则正常退出,但不能向后继续循环了删除后立马终端循环,会正常跳出,但代价是不能继续向后循环了使用迭代器使用迭代器可,正确无误的删除,代码简洁优雅,推荐使用使用迭代器可,正确无误的删除注意这里时而不是 在工作中的许多场景下,我们都会使用到List这个数据结构,那么同样的有很多场景下需要删除List中的某一个元素或某几个元素,那么我们该如何正确无误地删除List中的元素...

    kelvinlee 评论0 收藏0
  • fail-fast与fail-safe在Java集合中的应用

    摘要:与在迭代器中的设计在中,最典型的与就是关于迭代器的设计。缺点是,迭代器不能正确及时的反应集合中的内容,而且一定程度上也增加了内存的消耗。 fail-fast与fail-safe简介 如果一个系统,当有异常或者错误发生时就立即中断执行,这种设计称之为fail-fast。相反如果我们的系统可以在某种异常或者错误发生时继续执行,不会被中断,这种设计称之为fail-safe。 fail-fas...

    Drummor 评论0 收藏0
  • Java编程基础18——集合(Set集合

    摘要:并把最终的随机数输出到控制台。方法,在集合中如何存储元素取决于方法的返回值返回,集合中只有一个元素。创建集合对象,传入比较器。 1_HashSet存储字符串并遍历 A:Set集合概述及特点 通过API查看即可 B:案例演示 HashSet存储字符串并遍历 import java.util.HashSet; public class Demo1_HashSet { p...

    SexySix 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<