一、List集合概述
List 集合继承自 Collection 接口,是一种有序、元素可重复的列表,其实现类有ArrayList、LinkedList、Vector等
- ArrayList底层基于数组方式存储数据,是一个数组队列,具有容量动态扩展、查询快增删慢、线程不安全的特点,它的默认容量是10 ,如果容量不够增加到原来的1.5倍。
- LinkedList底层是基于链表方式存储数据,是一个双向循环列表,具有内存利用效率高、查询慢增删快、线程不安全的特点,它的容量理论上不限大小,但是实际上最大可为2147483647
- Vector底层是基于数组的方式实现数据存储,是一个矢量队列,具有查询快增删慢、线程安全、效率低的特点,它的默认长度是10 超过就会100%延长 变成20 浪费空间。
二、分类
List列表从结构上分为数组列表ArrayList、链表列表LinkedList,从线程安全性分为ArrayList和LinkedList单线程列表、Vector和Collections.SynchronizedList以及CopyOnWriteArrayList多线程列表,从检测机制分为fail-fast、safe-fast。所以,在实际的业务需求中,需要结合当前程序环境加以利用,而不会盲目选择,因为它们都具有各自的特点,例如在单线程环境中追求元素的查询可以用ArrayList,频繁更新可以用LinkedList,而需要加锁用Collections.synchronizedList(List)包装等。
三、线程安全之Collections.synchronizedList
在上述List的分类中,只有Vector是线程安全的而ArrayList、LinkedList则不是。在Collections工具类Collections.synchronizedList
()方法则将本身不是线程安全的容器变成线程安全的 例如:
List synList = Collections.synchronizedList(new ArrayList());
但是需要注意的是所谓线程安全指的是直接使用它提供的函数,如list.add(),list.get(i); 也就是说仅仅是原子操作的时候才是线程安全的。如果我们要使用非原子操作的时候是不能保证线程安全的 例如:
public class ListHelper<E> {
public List<E> list = Collection.synchronizedList(new ArrayList<E>());
...
public synchronized boolean putIfAbsent(E X){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
}
上面这种方式不能保证线程安全因为在这里的list操作并不是原子性操作。这里先进行contains判断元素是否存在,如果没有就进行添加。在多线程调用list的情况下。这里synchronized 锁住的对象是ListHelper,而不是list。list使用的是synchronizedList,就相当于 contains和add前都加上了synchronized,虽然都是同步方法,但是由于多线程的重排序性,无法保证执行的顺序。所以无法确保当putIfAbsent执行时另一个线程不会修改链表。 所以对于这种情况,我们还是需要自己同步:
public class ListHelper<E> {
public List<E> list = Collection.synchronizedList(new ArrayList<E>());
...
public synchronized boolean putIfAbsent(E X){
synchronize(list){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
}
}
虽然通过加锁的方法可以保证线程安全性,但是它会将类的加锁代码分布到其他的方法里面,也就是如果ListHelper扩展多一个方法,如果也想putIfAbsent一样的话也需要加锁synchronize(this){}。这样程序显得不够健壮。有一种更好的方法:组合。
public class ListHelper<T> implements List<T> {
private final List<T> list;
public ListHelper(List<T> list) {
this.list = list;
}
public synchronized boolean putIfAbsent(T X){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
//其它方法
public synchronized void clear(){
list.clear();
}}
synchronizedList在迭代的时候,必须需要我们自己加上线程锁控制代码进行同步,因为迭代器涉及的代码没有在java api中没有加上线程同步代码。如果整个迭代的过程中不在循环外面加同步代码,在一次次迭代之间,其他线程对于这个容器的add或者remove会影响整个迭代的预期效果,所以这里需要我们在整个循环外面加上synchronized(list)
Iterator iterator = list.iterator();
synchronized(list){
while(iterator.hasNext()) {
String next = (String) iterator.next();
System.out.println(next);
}
}
四、线程安全之CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。CopyOnWriteArrayList和CopyOnWriteArraySet
CopyOnWriteArrayList的add方法源码实现:
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//1、复制出一个新的数组
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//2、把新元素添加到新数组中
newElements[index] = element;
//3、把数组指向原来的数组
setArray(newElements);
} finally {
lock.unlock();
}
}
由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
- 如果写操作未完成,那么直接读取原数组的数据;
- 如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
- 如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。
CopyOnWriteArrayList的使用场景及缺点:
场景:CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景
缺点:
- 内存占用问题。在进行写操作的时候需要将原来的对象进行拷贝,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。特别是当容器对象过大的时候,因为拷贝而占用的内存将增加一倍(原来驻留在内存的对象仍然在使用,拷贝之后就有两份对象在内存中,所以增加了一倍内存)。而且,在高并发的场景下,因为每个线程都拷贝一份对象在内存中,这种情况体现得更明显。由于JVM的优化机制,将会触发频繁的Young GC和Full GC,从而使整个系统的性能下降
- 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。因为读线程在将引用重新指向新的对象之前再次读到的数据是旧的。因此实时一致性的场景CopyOnWriteArrayList是不能使用的
实际代码示例:
package Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListCopyOn {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
list.add("香港");
list.add("珠海");
list.add("澳门");
CopyOnWriteArrayList<String> copyList = new CopyOnWriteArrayList<String>(list);
Thread thread = new Thread(new Runnable() {
int count = 1;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
copyList.add(count++ +"");
}
}
});
thread.setDaemon(true);
thread.start();
Thread.currentThread().sleep(3);
for(String s:copyList) {
System.out.println(copyList.hashCode()+"---"+s);
}
}
}
五、集合操作方法示例
父级Collection接口:
package Collection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
public class CollectionDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// Collection : 跟接口 : 单列集合
// ---> List :有序的 ,元素是可以重复的。
// ---> Set : 无序的 ,元素是不可以重复的。
// Set集合由Set接口和Set接口的实现类组成,Set接口继承了Collection接口,因为包含Collection接口的所有方法。
//Collection集合的常用方法
/*添加功能*/
//1、add()--->添加元素到Collection集合中,返回boolean值。
Collection collection = new Vector();
collection.add("富商");
collection.add("富农");
collection.add("中农");
System.out.println(collection); //[富商, 富农, 中农]
//2、addAll()--->将指定集合中的所有元素都添加到collect此集合中,返回boolean值。
Collection collect = new Vector();
collect.add("七品知县");
collect.addAll(collection);
System.out.println(collect);//[七品知县, 富商, 富农, 中农]
/*判断功能*/
//3、contains()--->是否包含指定元素,返回boolean值。
boolean contains = collect.contains("富商");
System.out.println(contains); //true
//4、containsAll()--->是否包含指定容器中的所有元素,返回boolean值。
boolean contain = collect.containsAll(collection);
System.out.println(contain); //true
//5、isEmpty()--->判断集合是否为空,返回boolean值。
boolean empty = collect.isEmpty();
System.out.println(empty); //false(不为空)、true(空)
/*其他*/
//6、size()--->获取元素个数的方法返回int值
int size = collect.size();
System.out.println(size); //4
//7、retainAll()--->取当前列表中与所有传入Collection所包含元素的交集
Collection coll = new Vector();
coll.addAll(collect);
System.out.println(coll); //[七品知县, 富商, 富农, 中农]
coll.retainAll(collection);
System.out.println(coll); //[富商, 富农, 中农]
//8、toArray()--->将集合转为数组 (数组是一个对象数组)
Object[] array = coll.toArray();
for(int i=0;i < array.length;i++) {
System.out.println("array:"+array[i]);
}
/*遍历*/
Iterator iterator = collect.iterator();
while(iterator.hasNext()) {
String str = (String)iterator.next();
System.out.println(str); //七品知县 富商 富农 中农
}
/*删除功能*/
//9、remove()--->删除一个指定对象,返回boolean值。
collect.remove("中农");
System.out.println(collect); //[七品知县, 富商, 富农]
//10、清除当前列表中与所有传入Collection所包含的元素
System.out.println("collection:"+collection); //collection:[富商, 富农, 中农]
System.out.println("collect:"+collect); //collect:[七品知县, 富商, 富农]
collect.removeAll(collection);
System.out.println(collect); //[七品知县]
System.out.println(collection); //[富商, 富农, 中农]
//11、移除此collection集合中的所有元素
collection.clear();
System.out.println(collection); //[]
}
}
ArrayList集合:
package Collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class ArrayListDome {
public static void main(String[] args) {
// TODO Auto-generated method stub
//List接口继承自Collection接口因此Collection接口的方法也都全部继承过来,下面是List接口的方法部分和Collection是相同的
//List接口:按顺序存放对象,允许有重复的元素。
//ArrayList的常用方法---底层基于数组来实现(适用于查找多,插入删除少的操作)
//1、add()--->在列表的末尾顺序添加元素,起始索引位置从0开始
List list = new ArrayList();
list.add("航母");
list.add("护卫舰");
list.add("驱逐舰");
list.add("补给舰");
System.out.println(list); //[航母, 护卫舰, 驱逐舰, 补给舰]
//2、add()--->在指定的索引位置添加元素。索引位置必须介于0和列表中元素个数之间
list.add(3,"巡洋舰");
System.out.println(list); //[航母, 护卫舰, 驱逐舰, 巡洋舰, 补给舰]
//3、addAll()--->将指定集合中的所有元素添加到list此集合的末尾
List list1 = new ArrayList();
list1.add("两栖登陆舰");
list.addAll(list1);
System.out.println(list); //[航母, 护卫舰, 驱逐舰, 巡洋舰, 补给舰, 两栖登陆舰]
//4、addAll()---> 在指定位置将指定集合中的所有元素插入到list此集合中。
list.remove("两栖登陆舰");
list.addAll(2, list1);
System.out.println(list); //[航母, 护卫舰, 两栖登陆舰, 驱逐舰, 巡洋舰, 补给舰]
//5、size()--->返回列表中的元素个数
int size = list.size();
System.out.println(size); //6
//6、get()--->返回指定索引位置处的元素。取出的元素是Object类型,使用前需要进行强制类型转换
String str = (String)list.get(1);
System.out.println(str); //护卫舰
//7、contains()--->判断列表中是否存在指定元素
boolean contains = list.contains("补给舰");
System.out.println(contains); //true
//8、set()--->用指定的元素替代此列表中指定位置上的元素,返回以前位于该指定位置上的元素
String old = (String) list.set(2, "潜艇");
System.out.println(list); //[航母, 护卫舰, 潜艇, 驱逐舰, 巡洋舰, 补给舰]
System.out.println(old); //两栖登陆舰
//9、indexOf--->返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。
int indexOf = list.indexOf("补给舰");
System.out.println(indexOf); //5
//10、lastIndexOf()--->返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。
int lastIndexOf = list.lastIndexOf("护卫舰");
System.out.println(lastIndexOf); //1
//11、 remove()--->移除此列表中首次出现的指定元素(如果存在)
boolean remove = list.remove("巡洋舰");
System.out.println(remove); //true
System.out.println(list); //[航母, 护卫舰, 潜艇, 驱逐舰, 补给舰]
//12、remove()--->移除此列表中指定位置上的元素
String remove2 = (String) list.remove(1);
System.out.println(remove2); //护卫舰
System.out.println(list); //[航母, 潜艇, 驱逐舰, 补给舰]
/*List集合的遍历*/
List<String> lis = new ArrayList<String>();
lis.add("航母");
lis.add("护卫舰");
lis.add("驱逐舰");
lis.add("巡洋舰");
lis.add("补给舰");
//1、通过for循环和get()方法配合实现遍历
for(int i = 0;i < lis.size();i++) {
String string = lis.get(i);
System.out.println("lis:"+string);
}
//2、通过foreach循环实现遍历
for(String s:lis) {
System.out.println("lis:"+s);
}
//3、通过迭代器Iterator实现遍历
Iterator<String> iterator = lis.iterator();
while(iterator.hasNext()) {
String next = iterator.next();
System.out.println("lis:"+next);
}
}
}
LinkedList集合:
package Collection;
import java.util.Iterator;
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//LinkedList的特殊方法(上述方法LinkedList也可以适用)---底层基于链表机制来实现(适用于插入删除多,查找少的操作)
LinkedList linkedList = new LinkedList();
linkedList.add("坦克");
linkedList.add("步战车");
System.out.println(linkedList); //[坦克, 步战车]
/*添加元素*/
//1、在列表的首部添加元素
linkedList.addFirst("攻击机");
System.out.println(linkedList); //[攻击机, 坦克, 步战车]
//2、在列表的末尾添加元素
linkedList.addLast("战斗机");
System.out.println(linkedList); //[攻击机, 坦克, 步战车, 战斗机]
//3、 将指定元素添加到此列表的末尾(最后一个元素)。
linkedList.offer("侦察机");
System.out.println(linkedList); //[攻击机, 坦克, 步战车, 战斗机, 侦察机]
//4、 在此列表的开头插入指定的元素
linkedList.offerFirst("航天飞机");
System.out.println(linkedList); //[航天飞机, 攻击机, 坦克, 步战车, 战斗机, 侦察机]
//5、 在此列表末尾插入指定的元素
linkedList.offerLast("火箭炮");
System.out.println(linkedList); //[航天飞机, 攻击机, 坦克, 步战车, 战斗机, 侦察机, 火箭炮]
/*遍历 */
//6、返回以逆向顺序在此双端队列的元素上进行迭代的迭代器
Iterator descendingIterator = linkedList.descendingIterator();
while(descendingIterator.hasNext()) {
String next = (String) descendingIterator.next();
System.out.println(next);
}
/*返回元素-不移除*/
//7、返回列表中的第一个元素
String first = (String) linkedList.getFirst();
System.out.println(first); //航天飞机
//8、返回列表中的最后一个元素
String last = (String) linkedList.getLast();
System.out.println(last); //火箭炮
System.out.println(linkedList); //[航天飞机, 攻击机, 坦克, 步战车, 战斗机, 侦察机, 火箭炮]
//9、 获取但不移除此列表的头(第一个元素)。
String element = (String) linkedList.element();
System.out.println(element); //航天飞机
//10、 获取但不移除此列表的头(第一个元素)
String peek = (String) linkedList.peek();
System.out.println(peek); //航天飞机
//11、 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null
String peekFirst = (String) linkedList.peekFirst();
System.out.println(peekFirst); //航天飞机
//12、 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null
String peekLast = (String) linkedList.peekLast();
System.out.println(peekLast); //火箭炮
/*返回元素-移除*/
//13、获取并移除此列表的头(第一个元素)
String poll = (String) linkedList.poll();
System.out.println(poll); //航天飞机
//14、获取并移除此列表的第一个元素;如果此列表为空,则返回 null
String pollFirst = (String) linkedList.pollFirst();
System.out.println(pollFirst); //攻击机
//15、 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null
String pollLast = (String) linkedList.pollLast();
System.out.println(pollLast); //火箭炮
//16、获取并移除此列表的头(第一个元素)
System.out.println("---------------------");
System.out.println(linkedList);
String remove3 = (String) linkedList.remove();
System.out.println(remove3); //坦克
//17、获取并移除移除此列表中指定位置处的元素
String remove4 = (String) linkedList.remove(1);
System.out.println(remove4); //战斗机
/*移除元素 */
//18、从此列表中移除首次出现的指定元素
boolean flag = (boolean) linkedList.remove("侦察机");
System.out.println(flag); //true
//19、删除并返回列表中的第一个元素
String removeFirst = (String) linkedList.removeFirst();
System.out.println(removeFirst); //步战车
//20、删除并返回列表中的最后一个元素
linkedList.add("高射炮");
String removeLast = (String) linkedList.removeLast();
System.out.println(removeLast); //高射炮
System.out.println(linkedList); //[]
}
}
Vector集合:
package Collection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
public class VectorDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//Vector的常用方法---底层基于数组来实现(适用于查找多,插入删除少的操作)
Vector vector = new Vector();
vector.add("飞机");
vector.add("火车");
vector.add("汽车");
vector.add("高铁");
/*增加插入 */
//1、将指定的组件(元素)添加到此向量(集合)的末尾,将其大小增加 1。如果向量的大小比容量大,则增大其容量。
//此方法的功能与 add(E) 方法的功能完全相同
vector.addElement("轮船");
System.out.println(vector); //[飞机, 火车, 汽车, 高铁, 轮船]
//2、 将指定对象作为此向量中的组件插入到指定的 index 处。
vector.insertElementAt("动车",2);
System.out.println(vector); //[飞机, 火车, 动车, 汽车, 高铁, 轮船]
/*集合容量与所含元素个数 */
//3、返回此向量(集合)的当前容量。(容量!=元素个数)
int capacity = vector.capacity();
System.out.println(capacity); //10
//4、 增加此向量的容量(如有必要),以确保其至少能够保存最小容量参数指定的组件数。
vector.ensureCapacity(20);
int capacity2 = vector.capacity();
System.out.println(capacity2);// 20
//5、设置此向量的大小。
vector.setSize(30);
int size = vector.size();
System.out.println(size); //30
System.out.println(vector); //[自行车, 火车, 动车, 马车, 高铁, 轮船, null, null, null, null, null, null,
//null, null, null, null, null, null, null, null, null, null, null, null, null,
//null, null, null, null, null]
vector.setSize(6);
/*查找元素*/
//6、 返回此向量中第一次出现的指定元素的索引,如果此向量不包含该元素,则返回 -1。
int indexOf = vector.indexOf("火车"); //1
System.out.println(indexOf);
//7、返回此向量中第一次出现的指定元素的索引,从 index 处正向搜索,如果未找到该元素,则返回 -1。
int indexOf2 = vector.indexOf("高铁",2); //4
System.out.println(indexOf2);
//8、返回此向量中最后一次出现的指定元素的索引;如果此向量不包含该元素,则返回 -1。
int lastIndexOf = vector.lastIndexOf("轮船"); //5
System.out.println(lastIndexOf);
//9、 返回此向量中最后一次出现的指定元素的索引,从 index 处逆向搜索,如果未找到该元素,则返回 -1。
int lastIndexOf2 = vector.lastIndexOf("汽车", 3); //3
System.out.println(lastIndexOf2);
//10、返回指定索引处的组件。
String elementAt = (String) vector.elementAt(2);
System.out.println(elementAt); //汽车
//11、返回此向量的第一个组件(位于索引 0) 处的项)。
Object firstElement = vector.firstElement();
System.out.println(firstElement); //飞机
//12、返回此向量的最后一个组件。
String lastElement = (String) vector.lastElement();
System.out.println(lastElement); //轮船
//13、返回此向量的组件的枚举。
Enumeration elements = vector.elements();
System.out.println(elements); //java.util.Vector$1@6d06d69c
/*替换元素*/
//14、用指定的元素替换此向量中指定位置处的元素。
String set = (String)vector.set(0, "自行车");
System.out.println(vector); //[自行车, 火车, 动车, 汽车, 高铁, 轮船]
System.out.println(set); //飞机
//15、将此向量指定 index 处的组件设置为指定的对象。
vector.setElementAt("马车", 3);
System.out.println(vector); //[自行车, 火车, 动车, 马车, 高铁, 轮船]
/*其他*/
//16、 比较指定对象与此向量的相等性
Vector vec = new Vector();
vec.addAll(vector);
boolean equals = vector.equals(vec);
System.out.println(equals); //true
//17、 返回此向量的哈希码值。
int hashCode = vector.hashCode();
System.out.println(hashCode); //704595670
//18、测试此向量是否不包含组件(当且仅当此向量没有组件(也就是说其大小为零时返回 true;否则返回 false)
boolean empty = vector.isEmpty();
System.out.println(empty); //false
//20、 返回此 List 的部分视图,元素范围为从 fromIndex(包括)到 toIndex(不包括)[}
List subList = vector.subList(1, 3);
System.out.println(subList); //[火车, 动车]
//21、 返回向量(集合)的一个副本。
Object clone = vector.clone();
System.out.println(clone); //[自行车, 火车, 动车, 马车, 高铁, 轮船]
/*集合转数组*/
//22、 返回一个数组,包含此向量中以恰当顺序存放的所有元素。
Object[] array = vector.toArray();
System.out.println(array); //[Ljava.lang.Object;@7852e922
//23、将此向量的组件复制到指定的数组中
String[] copy = new String[8] ;
vector.copyInto(copy);
System.out.println(copy);//[Ljava.lang.String;@15db9742
/*移除元素 */
//24、移除此向量中指定位置的元素。
String remove = (String) vector.remove(2);
System.out.println(remove); //动车
//25、移除此向量中指定元素的第一个匹配项,如果向量不包含该元素,则元素保持不变。
boolean remove2 = vector.remove("轮船");
System.out.println(remove2); //true
System.out.println(vector); //[自行车, 火车, 马车, 高铁]
//26、从此向量中移除包含在指定 Collection 中的所有元素(交集)。
System.out.println(vec); //[自行车, 火车, 动车, 马车, 高铁, 轮船]
boolean remove3 = vec.removeAll(vector);
System.out.println(remove3); //true
System.out.println(vec); //[动车, 轮船]
//27、此向量中移除变量的第一个(索引最小的)匹配项。
boolean removeElement = vec.removeElement("汽车");
System.out.println(removeElement); //false
//28、删除指定索引处的组件
vec.removeElementAt(1);
System.out.println(vec); //[ 动车]
//29、从此向量中移除全部组件,并将其大小设置为零。
vec.removeAllElements();
System.out.println(vec);//[]
}
}