List 子类


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()方法
	}
}

程序运行结果:



  1. [World, Hello]  
  2. [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()方法
	}
}


程序运行结果:


  1. [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) + "、"); // 通过下标取得集合中的元素
		}
	}
}


程序运行结果:


  1. 由前向后输出:World、Hello、Hello、MLDN、www.mldn.cn、  
  2. 由后向前输出: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 + "、");
			// 输出每一个元素
		}
	}
}


程序运行结果:


  1. 指定数组类型:World、Hello、MLDN、www.mldn.cn、  
  2. 返回对象数组: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());
	}
}

程序运行结果:


  1. 集合操作前是否为空?true 
  2. "Hello"字符串存在! 
  3. [MLDN] 
  4. 集合截取:MLDN、  
  5. MLDN字符串的位置:2 
  6. 集合操作后是否为空?false 
List集合在刚刚实例化之后因为还有为其增加内容,所以在使用isEmpty()方法时返回的结果是true,表示集合是空的,之后向集合中增加了4个元素,所以程序的最后再使用此方法判断时就返回false,表示集合中已经存在内容。在程序中使用contains()方法判断集合中是否存在指定的元素,如果存在,则输出存在的信息;反之,输出不存在的信息。在集合中也可以使用subList()方法取出指定的子集合。

2.LinkekList

LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的。基于链表也决定了它在随机访问方面要比ArrayList逊色一点,所以使用LinkedList时不要用get方法,即使LinkedList的元素个数只有很少的几个。也要避免使用,养成好习惯,免得犯错。
LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下
List 子类
既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据,前一个节点的位置信息和后一个节点位置信息,如下图所示:
List 子类

(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 类提供了实现可增长数组的功能,随着更多元素加入其中,数组变的更大。在删除一些元素之后,数组变小。 




























上一篇:国家质量基础设施一站式线上平台建设,NQI系统开发


下一篇:node.js学习笔记(20) express中间件