ArrayList、Vector和LinkedList
List接口特点
1、有序的 collection。
2、可以对列表中每个元素的插入位置进行精确地控制。
3、可以根据元素的索引访问元素,并搜索列表中的元素。
4、列表通常允许重复的元素。
5、允许存在 null 元素。
6、实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。
ArrayList特点
1、java.util.ArrayList<E> : List 接口的大小可变数组的实现类
2、ArrayList 内部基于 数组 存储 各个元素。
3、所谓大小可变数组,是指当 数组容量不足以存放新的元素时,创建新数组,并将原数组中的内容复制过来。
4、源码分析
//elementData中已存放的元素的个数,注意:不是elementData的容量
private int size;
//elementData的默认容量为10
private static final int DEFAULT_CAPACITY = 10;
//对象数组:ArrayList的底层数据结构,transient表示该字段不进行序列化操作
transient Object[] elementData;
//实例化一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//实例化一个空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; protected transient int modCount = 0; @Native public static final int MAX_VALUE = 0x7fffffff; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
*指定初始容量的有参构造
*/
public ArrayList(int initialCapacity) {
//如果初始容量大于0就,对象数组就指向到一个新的数组,大小为所指定的大小
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果等于0就指向一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 无参构造
*/
public ArrayList() {
//对象数组就指向到一个空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 向elementData末尾中添加元素
*/
public boolean add(E e) {
//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
ensureCapacityInternal(size + 1);
//加入新元素e,size加1
elementData[size++] = e;
return true;
} // minCapacity = seize+1,即表示执行完添加操作后,数组中的元素个数
private void ensureCapacityInternal(int minCapacity) {
//判断是否是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//用最小容量和10进行比较,取最大值赋值给最小容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
} /**
*确保数组的容量足够存放新加入的元素,若不够,要扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果数组个数(size+1)数组长度就需要进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} private void grow(int minCapacity) {
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);
// 根据新的容量,将数组拷贝到新的数组并赋值给数组
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
// 如果数组个数小于0抛出OutOfMemoryError异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果最数组个数大于最大容量 就返回最大值,否则返回最大容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 向elementData指定位置添加元素
*/
public void add(int index, E element) {
//指定位置检查
rangeCheckForAdd(index);
// 扩容检查
ensureCapacityInternal(size + 1); // Increments modCount!!
//通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 找到位置添加元素
elementData[index] = element;
// 元素个数加一
size++;
}
// 判断指定位置是否超出数组容量
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* @param src 原数组.
* @param srcPos 原数组的起始位置.
* @param dest 目标数组.
* @param destPos 目标数组的起始位置.
* @param length 拷贝长度.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length); public E remove(int index) {
//指定位置检查
rangeCheck(index);
modCount++;
//保留要删除的值
E oldValue = elementData(index);
//要移动元素个数 int numMoved = size - index - 1;
if (numMoved > 0)
//通过拷贝使数组内位置为 index+1到 (size-1)的元素往前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//清除末尾元素让GC回收
elementData[--size] = null; // clear to let GC do its work
//返回删除的值
return oldValue;
}
ArrayList在无参的add方法中,每次插入新的元素时,判断数组是否为空,为空则建默认的容量10赋值给minCapacity,判断是否要扩容,第一次添加,数组的size是10,之后添加元素时,在ensureExplicitCapacity方法中判断数组元素个数即size+1(形参minCapacity)是否超过数组长度,超过则需要进行扩容操作,扩容是将旧的容量扩大到1.5倍,然后将数组拷贝到新的数组完成扩容操作。最后将元素添加,并size+1。ArrayList在指定位置添加元素时,是先检查指定位置是否在数组范围内,即数组中元素个数是1,则index得小于或者低于1。然后通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位,腾出位置之后放入元素,数组个数加一。
Vector特点
1、java.util.Vector<E> :List 接口的内部用数组存放元素的实现类
2、内部也是用数组存放元素。
3、与 ArrayList 不同的是 扩容方式不同,Vector 每次增长的容量是固定的,大部分方法都被 synchronized 关键字修饰。
4、Vector 是线程安全的。
5、源码分析
protected transient int modCount = 0;
//elementData中已存放的元素的个数,注意:不是elementData的容量
protected int elementCount;
//对象数组:Vector的底层数据结构
protected Object[] elementData;
//增长系数
protected int capacityIncrement; @Native public static final int MAX_VALUE = 0x7fffffff; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
*指定初始容量和增长系数的有参构造
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//如果初始容量大于0就,对象数组就指向到一个新的数组,大小为所指定的大小
this.elementData = new Object[initialCapacity];
//给增长系数赋值
this.capacityIncrement = capacityIncrement;
}
/**
*无参构造
*/
public Vector() {
//调用指定初始容量的有参构造,设置默认初始容量为10 this(10);
}
/**
*指定初始容量的有参构造
*/
public Vector(int initialCapacity) {
//调用指定初始容量和增长系数的有参构造,设置增长系数为0
this(initialCapacity, 0);
} /**
* 向elementData末尾添加元素
*/
public synchronized boolean add(E e) {
modCount++;
//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
ensureCapacityHelper(elementCount + 1);
//加入新元素e,elementCount加1
elementData[elementCount++] = e;
return true;
}
public synchronized void addElement(E obj) {
modCount++;
//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
ensureCapacityHelper(elementCount + 1);
//加入新元素e,elementCount加1
elementData[elementCount++] = obj;
}
/**
* 向elementData指定位置添加元素
*/
public void add(int index, E element) {
insertElementAt(element, index);
} public synchronized void insertElementAt(E obj, int index) {
modCount++;
// 如果index大于数组个数抛出ArrayIndexOutOfBoundsException异常 if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
// 找到位置添加元素
elementData[index] = obj;
// 元素个数加一
elementCount++;
} /**
* @param src 原数组.
* @param srcPos 原数组的起始位置.
* @param dest 目标数组.
* @param destPos 目标数组的起始位置.
* @param length 拷贝长度.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length); private void ensureCapacityHelper(int minCapacity) {
//元素个数如果大于数组长度就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} private void grow(int minCapacity) { 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);
} private static int hugeCapacity(int minCapacity) {
// 如果数组个数小于0抛出OutOfMemoryError异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果最数组个数大于最大容量 就返回最大值,否则返回最大容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
Vector和ArrayList一样都是基于数组实现的,且默认长度都是10,不同的是ArrayList扩容方式是扩大为原来的1.5倍,Vector默认是扩大为原来的两倍。其次Vector的方法都是同步的(Synchronized),是线程安全的,而ArrayList的方法不是。
LinkedList特点
1、java.util.LinkedList<E> :List 接口的实现类和Queue接口子类Deque的实现类
2、内部基于链表实现,增删快、迭代快,随机访问能力较差。
3、链表中的每个节点都是 LinkedList.Node 类型的对象。
4、LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。
5、这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
6、LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。
7、源码分析
// 链表中存放节点个数
transient int size = 0;
// 链表头结点
transient Node<E> first;
// 链表尾结点
transient Node<E> last;
// 节点结构
private static class Node<E> {
// 元素值
E item;
// 后置节点
Node<E> next;
// 前置节点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* 无参构造
*/
public LinkedList() {
}
/**
* 有参构造,将集合中元素插入链表
*/ public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
} public boolean addAll(Collection<? extends E> c) {
//以size作为插入下标,插入集合中所有
return addAll(size, c);
} /**
* 以index为下标,添加所有到元素尾部
*/
public boolean addAll(int index, Collection<? extends E> c) {
//指定位置检查
checkPositionIndex(index);
//集合转换为数组
Object[] a = c.toArray();
//获取新增元素数量
int numNew = a.length;
//如果新增元素为0就不增加
if (numNew == 0)
return false;
Node<E> pred, succ;
//如果index是size,在链表尾部追加数据
if (index == size) {
succ = null;//原节点为空,
pred = last;//前置节点为尾节点,
} else {
//如果index!=size通过遍历获取到原节点
succ = node(index);
//原节点的前置节点通过pred进行保留
pred = succ.prev;
}
//遍历数组,依次插入节点
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//生成新节点,前置节点为pred,后置节点为null
Node<E> newNode = new Node<>(pred, e, null);
//如果前置节点是null,则更新头节点
if (pred == null)
first = newNode; else
//如果前置节点不为null,则更新前置节点的后置节点为新节点
pred.next = newNode;
//设置当前节点为下次的前置节点,为下次插入做准备
pred = newNode;
}
//在链表尾部加数据时,原节点为null,则更新尾节点为最后插入的节点
if (succ == null) {
last = pred;
} else {
//不是在链表尾部追加数据时,将最后插入的节点的后置节点更新为原节点
pred.next = succ;
//原节点的前置节点更新为最后插入的节点
succ.prev = pred;
}
//更新节点个数
size += numNew;
modCount++;
return true;
}
/**
* 添加节点
*/
public boolean add(E e) {
//生成新节点 并插入到 链表尾部, 更新 last/first 节点。
linkLast(e);
return true;
}
//生成新节点 并插入到 链表尾部, 更新 last/first 节点。
void linkLast(E e) {
//用l记录尾节点
final Node<E> l = last;
//生成新节点
final Node<E> newNode = new Node<>(l, e, null);
//更新尾节点
last = newNode;
//如果链表为空,更新头节点
if (l == null)
first = newNode;
else
//更新原尾节点的后置节点为新节点
l.next = newNode;
//size加1
size++;
modCount++;
}
/**
* 添加节点到指定位置
*/
public void add(int index, E element) {
//指定位置检查
checkPositionIndex(index);
//指定位置等于元素个数,在尾节点插入
if (index == size)
linkLast(element);
else
//在中间插入
linkBefore(element, node(index));
} private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
} void linkBefore(E e, Node<E> succ) {
// 用pred记录指定位置节点(原节点)的前置节点
final Node<E> pred = succ.prev;
//生成新节点,前置节点为pred,后置节点为指定位置的节点(原节点)
final Node<E> newNode = new Node<>(pred, e, succ);
//将新节点作为原节点的前置节点
succ.prev = newNode;
//如果原节点的前置节点为空,更新头节点
if (pred == null)
first = newNode;
else
//如果原节点的前置节点不为空则更新前置节点的后置节点为新节点
pred.next = newNode;
//更新节点个数
size++;
modCount++;
}
/**
* 移除指定位置的节点
*/ public E remove(int index) {
//指定位置检查
checkElementIndex(index);
//移除节点
return unlink(node(index));
}
/**
* 移除节点
*/
E unlink(Node<E> x) {
// 记录节点信息
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 删除节点是否为头节点时
if (prev == null) {
// 更新头结点为删除节点的后置节点
first = next;
} else {
//更新删除节点的前置节点的后置节点为删除节点的后置节点
prev.next = next;
//删除除节点的前置节点置为null
x.prev = null;
}
// 删除节点是否为尾节点
if (next == null) {
// 更新尾结点为删除节点的前置节点
last = prev;
} else {
//更新删除节点的后置节点的前置节点为删除节点的前置节点
next.prev = prev;
//删除除节点的后置节点置为null
x.next = null;
}
//删除除节点的元素值置为null
x.item = null;
//更新节点数并返回删除节点的元素值
size--;
modCount++;
return element;
}
/**
* 获取指定位置的节点的元素值
*/
public E get(int index) {
//指定位置检查
checkElementIndex(index);
//获取元素值
return node(index).item;
}
/**
*会根据index处于前半段还是后半段 进行一个折半,以提升查询效率
*/
Node<E> node(int index) {
//如果index< size/2从头结点开始遍历
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//如果index>=size/2从尾结点开始遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
LinkedList的批量增加是找到添加的位置,然后通过循环数组,依次执行插入节点。LinkedList的移除节点则是先根据index判断及诶单是否存在,再根据所移除节点判断是否为头节点和尾节点,更新移除节点的前置节点和后置节点的信息,然后在把移除节点的信息置为null。查询操作则是根据index是处于前半段还是后半段,进行折半,以此来提高查询效率。
ArrayList和Vector
1、联系
- 都是List接口(List接口继承了Collection接口)的实现类
- 都是基于数组实现的,位置都是有序的,并且数据允许重复
2、区别
- Vector类的方法大部分用synchronized修饰,是线程安全的,而ArrayList是线程不安全的,它的方法之前不是同步的
- 扩容方式不同,Vector是默认增加容量一倍,或者是按照指定的容量增量进行增加。而ArrayList是增加容量为原来的二分之一
ArrayList和LinkedList
1、联系
- 都是List接口(List接口继承了Collection接口)的实现类
- 都是线程不安全的
2、区别
- ArrayList是基于动态数组实现的,LinkedList是基于链表实现的
- 对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList是通过先折半然后遍历节点来获取指定节点,则ArrayList只需要通过索引就可以获取到
- 对于新增和删除操作add和remove,LinkedList优于ArrayList,因为ArrayList要移动数据,而LinkedList只需要记录前后节点即可
牛客网笔试题
1、java 中的集合类包括 ArrayList 、 LinkedList 、 HashMap 等,下列关于集合类描述错误的是?
正确答案: C
A、ArrayList和LinkedList均实现了List接口
B、ArrayList访问速度比LinkedList快
C、随机添加和删除元素时,ArrayList的表现更加快速
D、HashMap实现Map接口,它允许任何类型的键和值对象,并允许将NULL用作键或值
2、实现或继承了Collection接口的是()
正确答案: B C E
A、Map
B、List
C、Vector
D、Iterator
E、Set
3、ArrayLists和LinkedList的区别,下述说法正确的有?
正确答案: A B C D
A、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
B、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
C、对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
D、ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。
参考博客链接
https://blog.csdn.net/zxt0601/article/details/77341098
转载请于明显处标明出处
https://www.cnblogs.com/AmyZheng/p/9428922.html
ArrayList、Vector和LinkedList的更多相关文章
- 常用Java API: ArrayList(Vector) 和 LinkedList
摘要: 本文主要介绍ArrayList(Vector)和LinkedList的常用方法, 也就是动态数组和链表. ArrayList ArrayList 类可以实现可增长的对象数组. 构造方法 Arr ...
- ArrayList、Vector、LinkedList的区别联系?
1.ArrayList.Vector.LinkedList类都是java.util包中,均为可伸缩数组. 2.ArrayList和Vector底层都是数组实现的,所以,索引数据快,删除.插入数据慢. ...
- ArrayList、Vector、LinkedList源码
List接口的一些列实现中,最常用最重要的就是这三个:ArrayList.Vector.LinkedList.这里我就基于JDK1.7来看一下源码. public class ArrayList< ...
- ArrayList,Vector,LinkedList
在java.util包中定义的类集框架其核心的组成接口有如下:·Collection接口:负责保存单值的最大父接口 |-List子接口:允许保存重复元素,数据的保存顺序就是数据的增加顺序: |-Set ...
- ArrayList Vector LinkedList 区别与用法
转载自: http://www.cnblogs.com/mgod/archive/2007/08/05/844011.html 最近用到了,所以依然是转载 ArrayList 和Vector是采用数组 ...
- Arraylist Vector Linkedlist区别和用法 (转)
ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢 ...
- paip.提升性能---list,arraylist,vector,linkedlist,map的选用..
paip.提升性能---list,arraylist,vector,linkedlist,map的选用.. arraylist,vector基本一样,但是,vector线程安全的. 作者Attilax ...
- ArrayList、Vector、LinkedList的区别及其优缺点? (转载)
原文链接:http://blog.csdn.net/wangzff/article/details/7296648 ArrayList,LinkedList,Vestor这三个类都实现了java.ut ...
- List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源代码解析
学习List接口实现类 ArrayList Vector LinkedList List接口的实现类中最经常使用最重要的就是这三个:ArrayList.Vector.LinkedList. JDK ...
- Java ArrayList、Vector和LinkedList等的差别与用法(转)
Java ArrayList.Vector和LinkedList等的差别与用法(转) ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都 ...
随机推荐
- bzoj 2049: [Sdoi]Cave 洞穴探测 (LCT)
第一次写lct (这是一道lct裸题 这次没有可爱(划掉)的同学教我,虽然有模板,但是配合网上的讲解还是看不懂QAQ 然后做了几道题之后总算有些感觉辣 于是决定给自己挖个坑,近期写一个lct详解(不过 ...
- 题解【洛谷P5483】[JLOI2011]小A的烦恼
我们可以灵活运用\(C++\)的语法来解决此题. 解释一下代码中会出现的语法: \(string::iterator\ it\)表示定义了一个\(string\)类型的迭代器\(it\),\(^*it ...
- Django_MTV和虚拟环境
1. MVT模型 2. 虚拟环境 """ 1.安装虚拟环境的命令: 1)sudo pip install virtualenv #安装虚拟环境 2)sudo pip in ...
- linux 系统如何复制文件到指定目录
首先使用命令函数“cp” 即copy的缩写, 一般模式: cp filename 路径. 如file1在A目录下,我们想把file1复制到B目录下 命令为 cp file1 /home/B ...
- Linux gd库安装步骤说明
gd 库是 PHP 处理图形的扩展库,它提供了一系列用来处理图片的 API(应用程序编程接口),使用 gd 库可以处理图片或者生成图片.在网站上,gd 库通常用来生成缩略图,或者对图片加水印,或者生成 ...
- C++算法导论第九章O(n)期望选择序列第i小的数字
#include<iostream> #include<vector> #include<algorithm> #include<time.h> usi ...
- thinkphp 接收文件并处理
html前台文件,上传到控制器,thinkphp处理它 前台 <form action="{:url('product/brand_addcl')}" enctype=&qu ...
- P&R 5
Floorplan: 要做好floorplan需要掌握哪些知识跟技能? 通常,遇到floorplan问题,大致的debug步骤跟方法有哪些? 如何衡量floorplan的QA? 芯片的整体架构模块划分 ...
- CRPR/CPPR
S CRPR clock reconvergence pessimism removal C CPPR clock path pessimism removal 剔除公共clock path上的悲 ...
- ios中ipa包的发布平台
目前我所知的平台 1.蒲公英 目前 限制 金融类app 区块链类app的上传,小编的号因此刚被封号,而且交涉无果,不给解封,只有提交工单,包号注销了,再注册一个[建议看一下使用规则] 小编认为, ...