Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及区别
本篇博客主要讲解list接口的三个实现类arraylist、linkedlist、vector的使用方法以及三者之间的区别。
1. arraylist使用
arraylist是list接口最常用的实现类,内部通过数组来实现,因此它的优点是适合随机查找和遍历,缺点是不适合插入和删除。
arraylist类的代码声明如下所示:
public class arraylist<e> extends abstractlist<e> implements list<e>, randomaccess, cloneable, java.io.serializable { ...... }
1.1 添加元素
使用arraylist添加元素有以下两个重载:
boolean add(e e); void add(int index, e element);
boolean add(e e);
是将元素添加到集合的末尾,
void add(int index, e element);
是将元素添加到指定的索引位置(索引是从0开始的)。
使用方法如下所示:
list<string> platformlist = new arraylist<>(); // 添加元素 platformlist.add("博客园"); platformlist.add("掘金"); platformlist.add("微信公众号"); // 添加重复元素,会添加成功,因为list支持添加重复元素 platformlist.add("博客园"); platformlist.add("掘金"); platformlist.add(3, "个人博客");
1.2 获取元素
获取arraylist中指定索引处的元素的使用方法如下所示:
system.out.println("索引为3的元素为:" + platformlist.get(3));
如果指定的索引超出了集合的最大索引,比如platformlist.get(6)
就会抛出异常java.lang.indexoutofboundsexception
:
1.3 获取集合元素个数
获取arraylist元素个数的使用方法如下所示:
system.out.println("platformlist的元素个数为:" + platformlist.size());
1.4 删除元素
使用arraylist删除元素有以下两个重载:
e remove(int index); boolean remove(object o);
e remove(int index);
是删除集合中指定索引处的元素,boolean remove(object o);
是删除集合中的指定元素。
使用方法如下所示:
// 指定索引删除重复的元素 "博客园" "掘金" platformlist.remove(4); platformlist.remove(4); // 删除指定元素 "个人博客" platformlist.remove("个人博客");
1.5 修改元素
修改arraylist中指定索引处的元素值的使用方法如下所示:
platformlist.set(0, "博客园:https://www.cnblogs.com/zwwhnly/"); platformlist.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167"); platformlist.set(2, "微信公众号:申城异乡人");
1.6 判断集合是否为空
判断arraylist是否为空的使用方法如下所示:
system.out.println("isempty:" + platformlist.isempty());
1.7 遍历元素(面试常问)
遍历arraylist的元素主要有以下3种方式:
- 迭代器遍历
- for循环
- foreach循环
使用方法如下所示:
system.out.println("使用iterator遍历:"); iterator<string> platformiterator = platformlist.iterator(); while (platformiterator.hasnext()) { system.out.println(platformiterator.next()); } system.out.println(); system.out.println("使用for循环遍历:"); for (int i = 0; i < platformlist.size(); i++) { system.out.println(platformlist.get(i)); } system.out.println(); system.out.println("使用foreach遍历:"); for (string platform : platformlist) { system.out.println(platform); }
1.8 清空集合
清空arraylist中所有元素的使用方法如下所示:
platformlist.clear();
1.9 完整示例代码
上面讲解的几点,完整代码如下所示:
public static void main(string[] args) { list<string> platformlist = new arraylist<>(); // 添加元素 platformlist.add("博客园"); platformlist.add("掘金"); platformlist.add("微信公众号"); // 添加重复元素,会添加成功,因为list支持添加重复元素 platformlist.add("博客园"); platformlist.add("掘金"); platformlist.add(3, "个人博客"); system.out.println("索引为3的元素为:" + platformlist.get(3)); system.out.println("platformlist的元素个数为:" + platformlist.size()); // 指定索引删除重复的元素 "博客园" "掘金" platformlist.remove(4); platformlist.remove(4); // 删除指定元素 "个人博客" platformlist.remove("个人博客"); system.out.println("platformlist的元素个数为:" + platformlist.size()); platformlist.set(0, "博客园:https://www.cnblogs.com/zwwhnly/"); platformlist.set(1, "掘金:https://juejin.im/user/5c7ce730f265da2dca388167"); platformlist.set(2, "微信公众号:申城异乡人"); system.out.println("isempty:" + platformlist.isempty()); system.out.println("使用iterator遍历:"); iterator<string> platformiterator = platformlist.iterator(); while (platformiterator.hasnext()) { system.out.println(platformiterator.next()); } system.out.println(); system.out.println("使用for循环遍历:"); for (int i = 0; i < platformlist.size(); i++) { system.out.println(platformlist.get(i)); } system.out.println(); system.out.println("使用foreach遍历:"); for (string platform : platformlist) { system.out.println(platform); } system.out.println(); // 清空集合 platformlist.clear(); system.out.println("isempty:" + platformlist.isempty()); }
输出结果为:
索引为3的元素为:个人博客
platformlist的元素个数为:6
platformlist的元素个数为:3
isempty:false
使用iterator遍历:
博客园:
掘金:
微信公众号:申城异乡人
使用for循环遍历:
博客园:
掘金:
微信公众号:申城异乡人
使用foreach遍历:
博客园:
掘金:
微信公众号:申城异乡人
isempty:true
2. linkedlist使用
linkedlist也是list接口的实现类,内部使用链表结构存储数据,因此它的优点是适合动态插入和删除元素,缺点是随机查找和遍历速度比较慢。
linkedlist类的代码声明如下所示:
public class linkedlist<e> extends abstractsequentiallist<e> implements list<e>, deque<e>, cloneable, java.io.serializable { }
linkedlist类的使用方法和arraylist基本一样,只需修改下声明处的代码即可:
list<string> platformlist = new linkedlist<>();
3. vector使用
vector也是list接口的实现类,内部也是通过数组来实现。
vector类的代码声明如下所示:
public class vector<e> extends abstractlist<e> implements list<e>, randomaccess, cloneable, java.io.serializable { }
与arraylist不同的是,vector是线程安全的,即某一时刻只有一个线程能够写vector,避免多线程同时写而引起的不一致性。不过这也造成vector的缺点:实现线程的同步需要额外的花费,因此它的访问速度会比arraylist慢一些。
可以认为vector是arraylist在多线程环境下的实现版本。
所以vector类的使用方法和arraylist基本一样,只需修改下声明处的代码即可:
list<string> platformlist = new vector<>();
由于要支持线程同步,因此vector类的很多方法都有synchronized关键字,如下所示:
public synchronized boolean isempty() { return elementcount == 0; } public synchronized int size() { return elementcount; } public synchronized void addelement(e obj) { modcount++; ensurecapacityhelper(elementcount + 1); elementdata[elementcount++] = obj; }
4. arraylist、linkedlist、vector的区别(面试常问)
注意:以下代码使用的jdk版本为1.8.0_191
4.1 相同点
arraylist、linkedlist、vector都实现了list接口,所以使用方式很类似,通过上面的示例也能发现这一点。
4.2 不同点
但是arraylist、linkedlist、vector的内部实现方式不同,也就导致了它们之间是有区别的。
4.2.1 存储结构
arraylist和vector是基于数组实现的,linkedlist是基于双向链表实现的。
这也就导致arraylist适合随机查找和遍历,而linkedlist适合动态插入和删除元素。
关于数组和双向链表,这里不做详解,后续会单独写篇文章总结。
4.2.2 线程安全性
arraylist和linkedlist是线程不安全的,vector是线程安全的。
vector可以看做是arraylist在多线程环境下的另一种实现方式,这也导致了vector的效率没有arrayklist和linkedlist高。
如果要在并发环境下使用arraylist或者linkedlist,可以调用collections类的synchronizedlist()方法:
collections.synchronizedlist(platformlist);
4.2.3 扩容机制
arraylist和vector都是使用object类型的数组来存储数据的,arraylist的默认容量是0,vector的默认容量是10。
空说无凭,我们先看下arraylist的使用示例:
list<string> strarraylist = new arraylist<>(); for (int i = 0; i < 20; i++) { strarraylist.add(string.valueof(i)); }
执行的arraylist构造函数的源码为:
transient object[] elementdata; public arraylist() { this.elementdata = defaultcapacity_empty_elementdata; } private static final object[] defaultcapacity_empty_elementdata = {};
再看下vector的使用示例:
list<string> strvector = new vector<>(); for (int i = 0; i < 30; i++) { strvector.add(string.valueof(i)); }
执行的vector构造函数的源码为:
protected object[] elementdata; protected int capacityincrement; public vector() { this(10); } public vector(int initialcapacity) { this(initialcapacity, 0); } public vector(int initialcapacity, int capacityincrement) { super(); if (initialcapacity < 0) throw new illegalargumentexception("illegal capacity: "+ initialcapacity); this.elementdata = new object[initialcapacity]; this.capacityincrement = capacityincrement; }
当向这两种类型中添加元素时,若容量不够,就会进行扩容,扩容的本质是产生一个新数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中,使用的方法是arrays.copyof()
,其中arraylist扩容后的容量是之前的1.5倍,vector默认情况下扩容后的容量是之前的2倍。
仍然使用上面的arraylist的例子:
list<string> strarraylist = new arraylist<>(); for (int i = 0; i < 20; i++) { strarraylist.add(string.valueof(i)); }
在执行完list<string> strarraylist = new arraylist<>();
后,此时strarraylist的容量是0,
然后在添加第1个元素时,strarraylist的容量会扩容为容量10,
当添加第11个元素时,strarraylist的容量会扩容为容量15,
当添加第16个元素时,strarraylist的容量会扩容为容量22,
如果还需要扩容,依次会扩容到33-->49。
看下arraylist的源码,就明白为什么会这样扩容:
private static final int default_capacity = 10; public boolean add(e e) { ensurecapacityinternal(size + 1); // increments modcount!! elementdata[size++] = e; return true; } private void ensurecapacityinternal(int mincapacity) { ensureexplicitcapacity(calculatecapacity(elementdata, mincapacity)); } private static int calculatecapacity(object[] elementdata, int mincapacity) { if (elementdata == defaultcapacity_empty_elementdata) { return math.max(default_capacity, mincapacity); } return mincapacity; } private void ensureexplicitcapacity(int mincapacity) { modcount++; // overflow-conscious code if (mincapacity - elementdata.length > 0) grow(mincapacity); } 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); }
最核心的代码就是int newcapacity = oldcapacity + (oldcapacity >> 1);
,所以arraylist扩容后的容量是之前的1.5倍。
再看下上面的vector例子:
list<string> strvector = new vector<>(); for (int i = 0; i < 30; i++) { strvector.add(string.valueof(i)); }
在执行完list<string> strvector = new vector<>();
后,此时strvector的容量是10,
当添加第11个元素时,strvector的容量会扩容为容量20,
当添加第21个元素时,strvector的容量会扩容为容量40,
如果还需要扩容,依次会扩容到80-->160。
看下vector的源码,就明白为什么会这样扩容:
public synchronized void addelement(e obj) { modcount++; ensurecapacityhelper(elementcount + 1); elementdata[elementcount++] = obj; } private void ensurecapacityhelper(int mincapacity) { // overflow-conscious code if (mincapacity - elementdata.length > 0) grow(mincapacity); } private static final int max_array_size = integer.max_value - 8; 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); }
最核心的代码就是int newcapacity = oldcapacity + ((capacityincrement > 0) ?capacityincrement : oldcapacity);
,所以vector默认情况下扩容后的容量是之前的2倍。
4.2.4 效率
arraylist随机查找和遍历的效率会高一些,但动态插入和删除元素的效率会低一些。
linkedlist动态插入和删除元素的效率会高一些,但随机查找和遍历的效率会低一些。
如果需要在多线程下操作集合元素,建议使用vector,否则的话,建议使用arraylist。
5. 源码及参考
arraylist、linkedlist、vector的区别和实现原理
java进阶(四十六)简述arraylist、vector与linkedlist的异同点
6. 最后
打个小广告,欢迎扫码关注微信公众号:「申城异乡人」,定期分享java技术干货,让我们一起进步。
上一篇: .NET Core依赖注入集成Dynamic Proxy
下一篇: 杏仁上火吗?快让小编告诉你吧