博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重学Java集合类(二)—— List接口
阅读量:4224 次
发布时间:2019-05-26

本文共 7093 字,大约阅读时间需要 23 分钟。

前言

List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和删除元素,并且存放的顺序与插入的顺序一致。

List接口

public interface List
extends Collection
{
// 查询操作 int size(); boolean isEmpty(); boolean contains(Object o); Iterator
iterator(); // 修改操作 boolean add(E e); boolean remove(Object o); boolean containsAll(Collection
c); boolean addAll(Collection
c); boolean addAll(int index, Collection
c); boolean removeAll(Collection
c); boolean retainAll(Collection
c); void clear(); // 基于位置的访问操作 E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); // 搜寻操作 int indexOf(Object o); int lastIndexOf(Object o); ListIterator
listIterator(); ListIterator
listIterator(int index); List
subList(int fromIndex, int toIndex); ……}

常用实现类

List|------ArrayList|------Vector|        |-------Stack|------LinkedList

ArrayList

ArrayList 是 List接口的一个实现类,可以说 ArrayList 是我们使用最多的 List 集合。从类名可以看出,ArrayList应该是基于数组而生成的,而事实确实如此,ArrayList的底层实现是基于动态数组而完成,保留了数组在存取元素方面的优势。

private static final int DEFAULT_CAPACITY = 10;    private static final Object[] EMPTY_ELEMENTDATA = {};    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};    transient Object[] elementData;     private int size;    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

可以看出,ArrayList的默认容量为10,内部存储继承自Object的对象数组,如果使用无参构造函数时,使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA;如果为含参构造函数,使用EMPTY_ELEMENTDATA。elementData数组用来存储集合中的内容,数组的长度即为ArrayList的Capacity,有别于size属性,size表示当前ArrayList中保存的元素个数,数组的最大值为Integer.MAX_VALUE - 8。

LinkedList

LinkedList是双向链表,链表中的每个节点都包含了对前一个和后一个元素的引用。节点的数据结构如下:

private static class Node
{ E item; Node
next; Node
prev; Node(Node
prev, E element, Node
next) { this.item = element; this.next = next; this.prev = prev; } }
public class LinkedList
extends AbstractSequentialList
implements List
, Deque
, Cloneable, java.io.Serializable{
transient int size = 0; transient Node
first; transient Node
last; ……}

Vector和Stack

Vector的数据结构和ArrayList差不多,包含了3个成员变量:elementData,elementCount,capacityIncrement。

protected Object[] elementData;    protected int elementCount;    protected int capacityIncrement;    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;    public Vector() {        this(10);    }

elementData是Object[]的数组,初始大小为10,会不断的增长;elementCount是元素的个数;capacityIncrement是动态数组增长的系数。Stack继承至Vector,提供对数组末尾元素的新增、读取和删除操作。Vector与ArrayList最大差异在于Vector是线程安全的,而ArrayList不是。

初始化和扩容

ArrayList

ArrayList提供如下三种构造方法:

public ArrayList(int initialCapacity) {        if (initialCapacity > 0) {            this.elementData = new Object[initialCapacity];        } else if (initialCapacity == 0) {            this.elementData = EMPTY_ELEMENTDATA;        } else {            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        }    }    public ArrayList() {        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }    public ArrayList(Collection
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; } }

其中,使用ArrayList(int initialCapacity)和ArrayList()初始化后,elementData中没有元素,size为0,不过elementData的长度有可能不同。ArrayList(Collection c)将集合c转化为数组,然后检查转化的类型,如果不是Object[]类型,使用Arrays类中的copyOf方法进行复制;同时,如果c中没有元素,使用EMPTY_ELEMENTDATA初始化。

当使用诸如add(E)、add(int, E)、addAll(Collection)、addAll(int, Collection)等方法时存在扩容的可能性。ArrayList的扩容逻辑如下:

private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        // minCapacity is usually close to size, so this is a win:        elementData = Arrays.copyOf(elementData, newCapacity);    }

简单来讲,扩容后的容量为原先容量的1.5倍,最小为原先值加1,最大为Integer.MAX_VALUE或者Integer.MAX_VALUE - 8。

LinkedList

LinkedList提供如下两种构造方法:

public LinkedList() {    }    public LinkedList(Collection
c) { this(); addAll(c); }

默认从链表尾端新增元素,LinkedList由于结构特点,在新增节点时只要修改相关节点引用即可完成,因此并无特定扩容方式,从某种意义上讲,LinkedList无扩容之意义。

Vector和Stack

Vector提供如下四种构造方法:

public Vector(int initialCapacity, int capacityIncrement) {        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];        this.capacityIncrement = capacityIncrement;    }    public Vector(int initialCapacity) {        this(initialCapacity, 0);    }    public Vector() {        this(10);    }    public Vector(Collection
c) { elementData = c.toArray(); elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); }

Stack的构造方法如下:

public Stack() {    }

Vector和Stack本质上都是数组,默认情况下都是在数组尾部新增元素。Vector构造方法接受一个带有扩容增量的参数,用于限定每次扩容的增量。

两者的扩容算法一致:

private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?                                         capacityIncrement : oldCapacity);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        elementData = Arrays.copyOf(elementData, newCapacity);    }

扩容量capacityIncrement小于等于0时,扩容后的容量为原先的2倍,如果大于0,则每次扩容大小为capacityIncrement,最小值最大值参考ArrayList。

关键方法比较

ArrayList、LinkedList、Vector和Stack都实现了List接口,但是重写相关方法时逻辑不完全一样,笔者整理了如下差异:

ArrayList LinkedList Vector、Stack
add() 默认情况下尾部新增,如果指定位置新增,则需要移动元素,甚至需要扩容 默认情况下尾部新增,如果指定位置新增,则需搜索指定位置完成新增,无需移动原先元素 同ArrayList相关处理逻辑
remove() 删除相关元素,同时移动后续元素,并调整capacity 搜索相关元素,并修改相关元素引用为null,无需移动元素 同ArrayList相关处理逻辑
get()、set() 快速定位元素 遍历链表搜索 同ArrayList相关处理逻辑
toArray() 复制内部数组 遍历链表按照节点创建数组 同ArrayList相关处理逻辑

总体来说,ArrayList、Vector做插入、删除的时候,慢在数组元素的批量copy,快在寻址。同时,ArrayList的操作不是线程安全的!一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArrayList。LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Entry的引用地址。

总结

本文比较详尽叙述了ArrayList、LinkedList和Vector之间的区别,涉及到的多线程安全问题,后续会有讲解,希望对读者有帮助。

转载地址:http://isgmi.baihongyu.com/

你可能感兴趣的文章
Openfiler 配置 NFS 示例
查看>>
Oracle 11.2.0.1 RAC GRID 无法启动 : Oracle High Availability Services startup failed
查看>>
Oracle 18c 单实例安装手册 详细截图版
查看>>
Oracle Linux 6.1 + Oracle 11.2.0.1 RAC + RAW 安装文档
查看>>
Oracle 11g 新特性 -- Online Patching (Hot Patching 热补丁)说明
查看>>
Oracle 11g 新特性 -- ASM 增强 说明
查看>>
Oracle 11g 新特性 -- Database Replay (重演) 说明
查看>>
Oracle 11g 新特性 -- 自动诊断资料档案库(ADR) 说明
查看>>
CSDN博客之星 投票说明
查看>>
Oracle wallet 配置 说明
查看>>
Oracle smon_scn_time 表 说明
查看>>
VBox fdisk 不显示 添加的硬盘 解决方法
查看>>
Secure CRT 自动记录日志 配置 小记
查看>>
RMAN RAC 到 单实例 duplicate 自动分配通道 触发 ORA-19505 错误
查看>>
mysql 随机分页的优化
查看>>
DB2快速创建测试库
查看>>
SD卡驱动分析--基于高通平台
查看>>
[图文] Seata AT 模式分布式事务源码分析
查看>>
pm 源码分析
查看>>
kmsg_dump
查看>>