List的特点:有序,有索引,可重复
List子类有:ArrayList,LinkedList,Vector
ArrayList:异步,非线程安全(随机访问效率高)
LinkedList:异步, 非线程安全(随机插入、删除效率高)
Vector :同步,线程安全(因为同步的要求会影响执行的效率,所以如果不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步而带来的不必要的性能开销)
1.ArrayList
ArrayList是List子类,可以直接通过对象的多态性为List接口实例化。此类的定义如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
从定义中可以发现ArrayList类继承了AbstractList类。AbstractList类的定义如下:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>
此类实现了List接口,所以可以直接使用ArrayList为List接口实例化。下面通过一些实例操作为读者讲解List接口中主要方法的使用。
(1)实例操作一:向集合中增加元素
要想完成此类操作,可以直接使用Collection接口中定义的两个方法。
增加一个元素:public boolean add(E o)。
增加一组元素:public boolean addAll(Collection<? extends E> c)。
也可以使用List扩充的add()方法在指定位置处增加元素。
在指定位置处添加元素:public void add(int index,E element)。
范例:验证增加数据的操作
package com.list.demo; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class Demo_Test1 { public static void main(String[] args) { List<String> allList = null; // 定义List对象 Collection<String> allCollection = null; // 定义Collection对象 allList = new ArrayList<String>(); // 实例化List对象,只能是String类型 allCollection = new ArrayList<String>(); // 实例化Collection,只能是String类型 allList.add("Hello"); // 从Collection继承的方法 allList.add(0, "World"); // 此方法为List扩充的方法 System.out.println(allList); // 输出集合中的内容 allCollection.add("GGG"); // 增加数据 allCollection.add("www.mldn.cn"); // 增加数据 allList.addAll(allCollection); // 从Collection继承的方法,增加一组对象 allList.addAll(0, allCollection); // 此方法是List自定义的,增加一组对象 System.out.println(allList); // 输出对象,调用toString()方法 } }
程序运行结果:
- [World, Hello]
- [GGG, www.mldn.cn, World, Hello, GGG, www.mldn.cn]
从程序的运行结果中可以发现,使用List中的add(int index,E element)方法可以在集合中的指定位置增加元素,而其他的两个add()方法只是在集合的最后进行内容的追加。
(2)实例操作二:删除元素
在类集中提供了专门的删除元素的方法,Collection和List接口都分别定义了删除数据的方法。
Collection定义的方法
每次删除一个对象:public boolean remove(Object o)。
每次删除一组对象:public boolean removeAll(Collection<?> c)。
List扩展的方法
删除指定位置的元素:public E remove(int index)。
范例:删除对象
package com.list.demo; import java.util.ArrayList; import java.util.List; public class Demo_Test1 { public static void main(String[] args) { List<String> allList = null; // 声明List对象 allList = new ArrayList<String>(); // 实例化List对象,只能是String类型 allList.add("Hello"); // 增加元素 allList.add(0, "World"); // 此方法为List扩展的增加方法 allList.add("MLDN"); // 增加元素 allList.add("www.mldn.cn"); // 增加元素 allList.remove(0); // 删除指定位置的元素 allList.remove("Hello"); // 删除指定内容的元素 System.out.println(allList); // 输出对象,调用toString()方法 } }
程序运行结果:
- [MLDN, www.mldn.cn]
在集合中增加完数据后,可以通过下标或对象的方式直接对集合中的元素进行删除。
U提示:关于使用remove(Object o)方法删除对象的说明。
在集合中可以插入任意类型的对象,在本程序中是以String类的对象为例,所以在使用remove(Object o)方法删除时可以直接删除;而对于自定义的类如果要通过此种方式删除,则必须在类中覆写Object类的equals()及hashCode()方法。这两个方法的使用在随后的章节中将为读者介绍。
(3)实例操作三:输出List中的内容
在Collection接口中定义了取得全部数据长度的方法size(),而在List接口中存在取得集合中指定位置元素的操作get(int index),使用这两个方法即可输出集合中的全部内容。
范例:输出全部元素
package com.list.demo; import java.util.ArrayList; import java.util.List; public class Demo_Test1 { public static void main(String[] args) { List<String> allList = null; // 定义List接口对象 allList = new ArrayList<String>(); // 实例化List对象,只能使String类型 allList.add("Hello"); // 增加元素 allList.add("Hello"); // 增加元素 allList.add(0, "World"); // 增加元素 allList.add("MLDN"); // 增加元素 allList.add("www.mldn.cn"); // 增加元素 System.out.print("由前向后输出:"); // 信息输出 for (int i = 0; i < allList.size(); i++) // 循环输出集合内容 { System.out.print(allList.get(i) + "、"); // 通过下标取得集合中的元素 } System.out.print("\n由后向前输出:"); for (int i = allList.size() - 1; i >= 0; i--) // 循环输出集合内容 { System.out.print(allList.get(i) + "、"); // 通过下标取得集合中的元素 } } }
程序运行结果:
- 由前向后输出:World、Hello、Hello、MLDN、www.mldn.cn、
- 由后向前输出:www.mldn.cn、MLDN、Hello、Hello、World、
从程序的运行结果中可以看出,在List集合中数据增加的顺序就是输出后的顺序,本身顺序不会发生改变。
(4)实例操作四:将集合变为对象数组
在Collection中定义了toArray()方法,此方法可以将集合变为对象数组,但是由于在类集声明时已经通过泛型指定了集合中的元素类型,所以在接收时要使用泛型指定的类型。
范例:将集合变为对象数组
package com.list.demo; import java.util.ArrayList; import java.util.List; public class Demo_Test1 { public static void main(String[] args) { List<String> allList = null; // 声明List对象 allList = new ArrayList<String>(); // 实例化List对象,只能是String类型 allList.add("Hello"); // 增加元素 allList.add(0, "World"); // 增加元素 allList.add("MLDN"); // 增加元素 allList.add("www.mldn.cn"); // 增加元素 String str[] = allList.toArray(new String[] {}); // 指定的泛型类型 System.out.print("指定数组类型:"); // 信息输出 for (int i = 0; i < str.length; i++) { // 输出字符串数组中的内容 System.out.print(str[i] + "、"); // 输出每一个元素 } System.out.print("\n返回对象数组:"); // 信息输出 Object obj[] = allList.toArray(); // 直接返回对象数组 for (int i = 0; i < obj.length; i++) { // 循环输出对象数组内容 String temp = (String) obj[i]; // 每一个对象都是String类 型实例 System.out.print(temp + "、"); // 输出每一个元素 } } }
程序运行结果:
- 指定数组类型:World、Hello、MLDN、www.mldn.cn、
- 返回对象数组:World、Hello、MLDN、www.mldn.cn、
(5)实例操作五:集合的其他相关操作
在List中还存在截取集合、查找元素位置、判断元素是否存在、集合是否为空等操作。下面直接测试以上的操作。
范例:测试其他操作
package com.hehe.coco; import java.util.ArrayList; import java.util.List; public class Test_Demo_1 { public static void main(String[] args) { List<String> allList = null;// 声明List对象 allList = new ArrayList<String>(); // 实例化List对象,只能是 String类型 System.out.println("集合操作前是否为空?" + allList.isEmpty()); allList.add("Hello");// 增加元素 allList.add(0, "World");// 增加元素 allList.add("MLDN");// 增加元素 allList.add("www.mldn.cn");// 增加元素 System.out.println(allList.contains("Hello") ? "\"Hello\"字符串存在!" : "\"Hello\"字符串不存在!"); List<String> allSub = allList.subList(2, 3);// 取出里面的部分集合 System.out.println(allSub); System.out.print("集合截取:"); for (int i = 0; i < allSub.size(); i++)// 截取部分集合 { System.out.print(allSub.get(i) + "、"); } System.out.println(""); System.out.println("MLDN字符串的位置:" + allList.indexOf("MLDN")); System.out.println("集合操作后是否为空?" + allList.isEmpty()); } }
程序运行结果:
- 集合操作前是否为空?true
- "Hello"字符串存在!
- [MLDN]
- 集合截取:MLDN、
- MLDN字符串的位置:2
- 集合操作后是否为空?false
List集合在刚刚实例化之后因为还有为其增加内容,所以在使用isEmpty()方法时返回的结果是true,表示集合是空的,之后向集合中增加了4个元素,所以程序的最后再使用此方法判断时就返回false,表示集合中已经存在内容。在程序中使用contains()方法判断集合中是否存在指定的元素,如果存在,则输出存在的信息;反之,输出不存在的信息。在集合中也可以使用subList()方法取出指定的子集合。
2.LinkekList
LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的。基于链表也决定了它在随机访问方面要比ArrayList逊色一点,所以使用LinkedList时不要用get方法,即使LinkedList的元素个数只有很少的几个。也要避免使用,养成好习惯,免得犯错。
LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下
既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据,前一个节点的位置信息和后一个节点位置信息,如下图所示:
(1)LinkedList和ArrayList性能测试
这里针对于LinkedList和ArrayList 的插入和随机访问性能做了如下测试,测试代码如下:
1.LinkedList插入和随机访问性能测试
package com.LinkedList.demo; import java.util.LinkedList; import java.util.List; public class LinkedList_Demo_1 { public static void main(String[] args) { int size = 2000000;// 200万次循环 List<String> list = new LinkedList<String>();// 实例化LinkedList对象 long addStartTime = System.currentTimeMillis();// add开始时间 for (int i = 0; i < size; i++)// 200万次循环 { list.add("Just some test data");// 增加元素 } long addEndTime = System.currentTimeMillis();// add结束时间 System.out.println("LinkedList添加200万条数据,耗时:" + (addEndTime - addStartTime) + "\t毫秒");// 打印添加200万条数据所消耗的时间 long queryStartTime = System.currentTimeMillis();// query开始时间 for (int i = 0; i < size; i++)// 循环200万次 { list.get(i);// 取出当前循环的元素 if (i % 100000 == 0)// 当元素每循环到10万条时,记录消耗毫秒数 { long queryEndTime = System.currentTimeMillis();// query结束时间 System.out.println("LinkedList查询到第: " + i + "条数据,本次耗时" + (queryEndTime - queryStartTime) + " 毫秒");// 打印查询200万条数据所消耗的时间 queryStartTime = System.currentTimeMillis();// 初始化下一次query开始时间 } } } }
程序运行结果:
LinkedList添加200万条数据,耗时:2218 毫秒
LinkedList查询到第: 0条数据,本次耗时0 毫秒
LinkedList查询到第: 100000条数据,本次耗时13121 毫秒
LinkedList查询到第: 200000条数据,本次耗时61236 毫秒
LinkedList查询到第: 300000条数据,本次耗时102181 毫秒
LinkedList查询到第: 400000条数据,本次耗时148209 毫秒
LinkedList查询到第: 500000条数据,本次耗时183442 毫秒
......
后面几条记录用省略号代替来了,由于后面的结果实在太慢的,等下去也没有意义,从这里可以看出LinkedList查询的结果是非常的慢,所以要避免使用LinkedList的get方法。
2.ArrayLis插入和随机访问性能测试
package com.ArrayList.demo; import java.util.ArrayList; import java.util.List; public class ArrayList_Demo_1 { public static void main(String[] args) { int size = 2000000;// 200万次循环 List<String> list = new ArrayList<String>();//实例化ArrayList对象 long addStartTime = System.currentTimeMillis();// add开始时间 for (int i = 0; i < size; i++)// 200万次循环 { list.add("Just some test data");// 增加元素 } long addEndTime = System.currentTimeMillis();// add结束时间 System.out.println("ArrayList添加200万条数据,耗时:" + (addEndTime - addStartTime) + " 毫秒");// 打印添加200万条数据所消耗的时间 long queryStartTime = System.currentTimeMillis();// query开始时间 for (int i = 0; i < size; i++)// 循环200万次 { list.get(i);// 取出当前循环的元素 if (i % 100000 == 0)// 当元素每循环到10万条时,记录消耗毫秒数 { long queryEndTime = System.currentTimeMillis();// query结束时间 System.out.println("ArrayList查询到第: " + i + "条数据,本次耗时" + (queryEndTime - queryStartTime) + " 毫秒");// 打印查询200万条数据所消耗的时间 queryStartTime = System.currentTimeMillis();// 初始化下一次query开始时间 } } } }
程序运行结果:
ArrayList添加200万条数据,耗时:59 毫秒
ArrayList查询到第: 0条数据,本次耗时0 毫秒
ArrayList查询到第: 100000条数据,本次耗时10 毫秒
ArrayList查询到第: 200000条数据,本次耗时0 毫秒
ArrayList查询到第: 300000条数据,本次耗时1 毫秒
ArrayList查询到第: 400000条数据,本次耗时0 毫秒
ArrayList查询到第: 500000条数据,本次耗时0 毫秒
ArrayList查询到第: 600000条数据,本次耗时1 毫秒
ArrayList查询到第: 700000条数据,本次耗时0 毫秒
ArrayList查询到第: 800000条数据,本次耗时0 毫秒
ArrayList查询到第: 900000条数据,本次耗时1 毫秒
ArrayList查询到第: 1000000条数据,本次耗时0 毫秒
ArrayList查询到第: 1100000条数据,本次耗时0 毫秒
ArrayList查询到第: 1200000条数据,本次耗时0 毫秒
ArrayList查询到第: 1300000条数据,本次耗时1 毫秒
ArrayList查询到第: 1400000条数据,本次耗时0 毫秒
ArrayList查询到第: 1500000条数据,本次耗时0 毫秒
ArrayList查询到第: 1600000条数据,本次耗时0 毫秒
ArrayList查询到第: 1700000条数据,本次耗时1 毫秒
ArrayList查询到第: 1800000条数据,本次耗时0 毫秒
ArrayList查询到第: 1900000条数据,本次耗时0 毫秒
结果很清晰,使用ArrayList的get方法随机访问对象中的元素所消耗的时间是很短的。在很多地方会看到LinkedList比ArrayList插入的效率较快,这样说是不严谨的,比如上面的测试:
ArrayList添加200万条数据,耗时:59 毫秒
LinkedList添加200万条数据,耗时:2218 毫秒
这样看起来ArrayList插入效率比LinkedList高,这样说也不严谨的。我理解是有序的插入是ArrayList效率高,无序的插入LinkedList效率高。
3.Vector
Vector非常类似ArrayList,但是Vector是同步的。ArrayList会比Vector快,因为ArrayList是非同步的,如果设计涉及到多线程,还是用Vector比较好一些
。由Vector创建的Iterator,虽然和ArrayList创建的 Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
package com.Vector.demo; import java.util.Iterator; import java.util.Vector; public class VectorDemo { /** * Vector的使用。包括Vector的创建、向Vector中添加元素、从Vector中删除元素、 * 统计Vector中元素的个数和遍历Vector中的元素。 */ public static void main(String[] args) { Vector<String> v = new Vector<String>(4);// 实例化Vector对象 v.add("Test1");// 添加元素 v.add("Test0");// 添加元素 v.add("Test2");// 添加元素 v.add("Test0");// 添加元素 v.add("Test2");// 添加元素 System.err.println(v); /** * 删除元素 */ v.remove("Test0"); // 删除指定内容的元素(这里是删除第一个出现的该元素) v.remove(0); // 按照索引下标删除元素 /** * 获得已有元素的个数 */ int size = v.size(); System.out.println("size:" + size); /** * 遍历元素 */ for (int i = 0; i < v.size(); i++) { System.out.println(v.get(i)); } /** * 迭代元素 */ Iterator<String> iterator = v.iterator(); while (iterator.hasNext()) { System.err.println(iterator.next()); } } }
程序运行结果:
[Test1, Test0, Test2, Test0, Test2]
size:3
Test2
Test0
Test2
Test2
Test0
Test2
Vector 类提供了实现可增长数组的功能,随着更多元素加入其中,数组变的更大。在删除一些元素之后,数组变小。