List总结

目录

 

List常用实现类总结

实现类有ArrayList、LinkedList、Vector、Stack等

使用场景

ArrayList读取速度快于LinkedList,而插入和删除速度又慢于LinkedList

原因

ArrayList

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

elementData设置成了transient,那ArrayList是怎么把元素序列化的呢?

LinkedList

CopyOnWriteArrayList

List相关面试问题整理

(1)ArrayList和LinkedList有什么区别?

(2)ArrayList是怎么扩容的?

(3)ArrayList插入、删除、查询元素的时间复杂度各是多少?

(4)怎么求两个集合的并集、交集、差集?

(5)ArrayList是怎么实现序列化和反序列化的?

(6)集合的方法toArray()有什么问题?

(7)什么是fail-fast?

(8)LinkedList是单链表还是双链表实现的?

(9)LinkedList除了作为List还有什么用处?

(10)LinkedList插入、删除、查询元素的时间复杂度各是多少?

(11)什么是随机访问?

(12)哪些集合支持随机访问?他们都有哪些共性?

(13)CopyOnWriteArrayList是怎么保证并发安全的?

(14)CopyOnWriteArrayList的实现采用了什么思想?

(15)CopyOnWriteArrayList是不是强一致性的?

(16)CopyOnWriteArrayList适用于什么样的场景?

(17)CopyOnWriteArrayList插入、删除、查询元素的时间复杂度各是多少?

(18)CopyOnWriteArrayList为什么没有size属性? 


List常用实现类总结

List中的元素是有序的、可重复的,主要实现方式有动态数组和链表。

java中提供的List的实现主要有ArrayList、LinkedList、CopyOnWriteArrayList,另外还有两个古老的类Vector和Stack。

实现类有ArrayList、LinkedList、Vector、Stack等

○ArrayList是基于数组实现的,是一个数组队列。可以动态的增加容量!
○LinkedList是基于链表实现的,是一个双向循环列表。可以被当做堆栈使用!
○Vector是基于数组实现的,是一个矢量队列,是线程安全的!
○Stack是基于数组实现的,是栈,它继承与Vector,特性是FILO(先进后出)!

使用场景

  1. 当集合中对插入元素数据的速度要求不高,但是要求快速访问元素数据,则使用ArrayList。
  2. 当集合中对访问元素数据速度不做要求不高,但是对插入和删除元素数据速度要求高的情况,则使用LinkedList。
  3. 当集合中有多线程对集合元素进行操作时候,则使用Vector!但是现在BVector现在一般不再使用,如需在多线程下使用,可以用CopyOnWriteArrayList,在java.util.concurrent包下。
  4. 当集合中有需求是希望后保存的数据先读取出来,则使用Stack。

ArrayList读取速度快于LinkedList,而插入和删除速度又慢于LinkedList


原因

 

  1. ArrayList随机读取的时候采用的是get(index),根据指定位置读取元素,而LinkedList则采用size/2 ,二分法去加速一次读取元素。
  2. ArrayList插入时候要判断容量,删除之后的所有元素都要向数组的前端移动。 而LinkedList直接插入,不用判断容量,删除的时候很轻松,只需要更新被删除元素附近的链接。(链表将每个对象存放在独立的节点中,每个节点还存放着序列中下一个节点的引用)

ArrayList

常用方法:

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

 

    String[] arr = {"A","B","C","D","E","F"};
        String[] arr2 = {"G","H","I","J","K","L"};
        System.arraycopy(arr,3,arr2,2,2);
        System.out.println(JSONObject.toJSONString(arr2));

结果:    ["G","H","D","E","K","L"]

最后一个参数是需要复制的个数

arr的第3个index(包括index 3)开始的两个数据替换arr2的第2个index(包括index 2)开始的数据 替换两个数据

 类方法等细节分析

【死磕 Java 集合】— ArrayList源码分析

(1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;

(2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);

(3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1);

(4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);

(5)ArrayList从尾部删除元素极快,时间复杂度为O(1);

(6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);

(7)ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;

(8)ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;

(7)ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

elementData设置成了transient,那ArrayList是怎么把元素序列化的呢?

查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。

一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。

在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。

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

LinkedList

类方法等细节分析

【死磕 Java 集合】— LinkedList源码分析

(1)LinkedList是一个以双链表实现的List;

(2)LinkedList还是一个双端队列,具有队列、双端队列、栈的特性;

(3)LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1);

(4)LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n);

(5)LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效;

(6)LinkedList在功能上等于ArrayList + ArrayDeque;

CopyOnWriteArrayList

类方法等细节分析

【死磕 Java 集合】— CopyOnWriteArrayList源码分析

CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了只阻塞写操作,不阻塞读操作,实现读写分离

(1)CopyOnWriteArrayList使用ReentrantLock重入锁加锁,保证线程安全;

(2)CopyOnWriteArrayList的写操作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能比较低下;

(3)CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1);

(4)CopyOnWriteArrayList采用读写分离的思想,读操作不加锁,写操作加锁,且写操作占用较大内存空间,所以适用于读多写少的场合;

(5)CopyOnWriteArrayList只保证最终一致性,不保证实时一致性,因为读写是在两个容器进行的,只有当写操作执行完毕引入指向新容器后,读才能感知到容器的变化。

 

List相关面试问题整理

(1)ArrayList和LinkedList有什么区别?

      1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (LinkedList是双向链表,有next也有            previous)
     2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
     3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 

(2)ArrayList是怎么扩容的?

   List.add 方法時 里面的 ensureCapacityInternal方法下的  grow方法
private void grow(int minCapacity) {
          // 获取到ArrayList中elementData数组的内存空间长度
          int oldCapacity = elementData.length;
         // 扩容至原来的1.5倍
         int newCapacity = oldCapacity + (oldCapacity >> 1);
         // 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
          // 不够就将数组长度设置为需要的长度
         if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
         //若预设值大于默认的最大值检查是否溢出
         if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
         // 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
         // 并将elementData的数据复制到新的内存空间
         elementData = Arrays.copyOf(elementData, newCapacity);
     }

 

(3)ArrayList插入、删除、查询元素的时间复杂度各是多少?

ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);

ArrayList添加元素到尾部极快,平均时间复杂度为O(1);

ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);

ArrayList从尾部删除元素极快,时间复杂度为O(1);

ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);

(4)怎么求两个集合的并集、交集、差集?

ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;

ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;

ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

(5)ArrayList是怎么实现序列化和反序列化的?

查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。

一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。

在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。

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

(6)集合的方法toArray()有什么问题?

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

c.toArray之后返回的类型或许不是Object类型,貌似1.9已经修复了 ,不知道为什么会出现这种问题

(7)什么是fail-fast?

参考博客:https://www.cnblogs.com/myseries/p/10877362.html

fail-fast 机制是java集合(Collection)中的一种错误机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

这种“ 及时失败” 的迭代器井不是一种完备的处理机制,而只是“ 善意地” 捕获并发错误,因此只能作为并发问题的预警指示器。

public class FailFastTest {

    static final List<Integer> list = new ArrayList<>();

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            exec.execute(() -> {
                add((int)(Math.random() * 10));
                print();
            });
        }
    }

    private static void add(int number) {
        list.add(number);
    }

    private static void print() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

以上代码会抛出java.util.ConcurrentModificationException异常。

(8)LinkedList是单链表还是双链表实现的?

双链表

(9)LinkedList除了作为List还有什么用处?

LinkedList还是一个双端队列,具有队列、双端队列、栈的特性

(10)LinkedList插入、删除、查询元素的时间复杂度各是多少?

LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1);

LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n);

LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效;

(11)什么是随机访问?

java集合类中元素的访问分为随机访问和顺序访问。随机访问一般是通过index下标访问,行为类似数组的访问。而顺序访问类似于链表的访问,通常为迭代器遍历。 以List接口及其实例为例。ArrayList是典型的随机访问型,而LinkedList则是顺序访问型。List接口既定义了下标访问方法又定义了迭代器方法。所以其实例既可使用下标随机访问也可以使用迭代器进行遍历。但这两种方式的性能差异很明显。 
RandomAccess接口

JDK中的RandomAccess接口是一个标记接口,它并未定义方法。其目的是用于指示实现类具有随机访问特性,在遍历时使用下标访问较迭代器更快。如果:

for(int i = 0, n = list.size(); i < n; i++)
    list.get(i);

的运行比

for(Interator i = list.iterator();i.hasNext();)
    i.next();

快,则应实现RandomAccess接口。 
原文链接:https://blog.csdn.net/yaogebeizhan/article/details/78180448

(12)哪些集合支持随机访问?他们都有哪些共性?

ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。

(13)CopyOnWriteArrayList是怎么保证并发安全的?

      ReentrantLock, 以及 private transient volatile Object[] array 修饰 元素

(14)CopyOnWriteArrayList的实现采用了什么思想?

   写入时复制(CopyOnWrite)思想

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

(15)CopyOnWriteArrayList是不是强一致性的?

 不是,见(14),CopyOnWriteArrayList只保证最终一致性,不保证实时一致性,因为读写是在两个容器进行的,只有当写操作执行完毕引入指向新容器后,读才能感知到容器的变化。

(16)CopyOnWriteArrayList适用于什么样的场景?

适用于读多写少的场景

(17)CopyOnWriteArrayList插入、删除、查询元素的时间复杂度各是多少?

CopyOnWriteArrayList的写操作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能比较低下;

CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1)

删除也是空间复杂度是O(n)

(18)CopyOnWriteArrayList为什么没有size属性? 

因为每次修改都是拷贝一份正好可以存储目标个数元素的数组,所以不需要size属性了,数组的长度就是集合的大小,而不像ArrayList数组的长度实际是要大于集合的大小的。

比如,add(E e)操作,先拷贝一份n+1个元素的数组,再把新元素放到新数组的最后一位,这时新数组的长度为len+1了,也就是集合的size了。

参考博客:

Java List集合总结

【死磕 Java 集合】— ArrayList源码分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值