Java之具体的集合

在Java类库的集合中,集合可以分为四个大种类,分别是列表(list),无重复元素列表(集合set),队列(queue),映射(map)。其中,前三种集合都实现了Collection接口,而映射实现了Map接口

下面是这些集合的具体类以及他们之间的继承层次

ArrayList:一种可以动态增长和缩减的索引序列

LinkedList:一种可以在任何位置高效的插入的删除操作的有序序列

ArrayDeque:一种用循环数组实现的双端队列

HashSet:一种没有重复元素的无序集合

TreeSet:一种有序集

EnumSet:一种包含枚举类型值的集

LinkedHashSet:一种可以记住元素插入次序的集

PriorityQueue:一种允许高效删除最小元素的集合

HashMap:一种存储键/值关联的数据结构

TreeMap:一种键值有序排列的顺序表

EnumMap:一种键值属于枚举类型的映射表

LinkedHashMap:一种可以记住键/值项添加次序的映射表

WeakHashMap:一种其值无用武之地后可以被垃圾回收器回收的映射表

IdentityHashMap:一种用==而不是equals比较键值的映射表

下面是他们之间的继承关系:


1.链表(LinkedList)

数组和数组列表有一个重大缺陷,就是从数组的中间位置插入或删除一个元素要付出很大代价,因为要移动大量元素,而使用链表则会解决这个问题,链表中的每个对象都存储在单独的结点中,每个结点中还存储着这个元素的前面和后面结点的引用,这样,在插入或者删除元素的时候只需要修改引用即可达到目的。因此改动集合的效率大大提高。但是,作为代价,链表的随机访问能力相当弱,进行随机访问会付出很大的代价。

我们都知道在Java中有Iterator接口,而这个接口是所有集合迭代器,所以只提供了最通用的方法,而这个接口的其中一个子接口叫做ListIterator就提供了有关顺序表的方法,其中就包括下面要说的add和remove方法。

在java中,使用add(Object)的方法在链表中添加对象,使用该链表对象调用此方法时是在此表后面添加对象,如果是该对象的迭代器使用此方法,就是在此迭代器的next()方法返回的元素后面添加对象,如果多次调用,则继续在新添加对象的后面再添加一个对象。使用remove()方法删除next()方法返回的对象。

另外ListIterator还有两个方法用来反向处理链表

E previous()
boolean hasPrevious()

除了上面这些添加和删除操作,还有一个set方法用于取代调用next或previous方法返回的上一个元素。

在下面的代码示例中,先添加3个元素,然后再将第二个删除,最后再在第一个元素后面连续添加两个元素

		List<String> staff=new LinkedList<>();
		staff.add("Amy");
		staff.add("Bob");
		staff.add("Carl");
		System.out.println("原始数据"+staff);
		ListIterator<String> iter=staff.listIterator();
		String first=iter.next();
		String second=iter.next();
		iter.remove();
		System.out.println("删除第二个对象后的数据"+staff);
		iter.add("Erica");
		iter.add("Gloria");
		System.out.println("又添加两个对象后的数据"+staff);

运行结果:


下面的示例中再链表中添加6个元素,然后将其倒数第二个对象删除,最后再在其倒数第三个对象的前面连续插入两个对象。

		List<String> staff=new LinkedList<>();
		staff.add("于理想");
		staff.add("张琛");
		staff.add("张艳杰");
		staff.add("张哲鑫");
		staff.add("闫智");
		staff.add("孙崇伟");
		System.out.println("原始数据"+staff);
		ListIterator<String> iter=staff.listIterator(staff.size());
		String lastFirst=iter.previous();
		String lastSecond=iter.previous();
		iter.remove();
		System.out.println("删除倒数第二个对象后的数据"+staff);
		iter.add("闫勇");
		iter.add("赵堃");
		System.out.println("又添加两个对象后的数据"+staff);

运行结果:


下面代码中,将链表中的第二个元素设置为另一个值:

List<String> list=new LinkedList<>();
		list.add("于理想");
		list.add("张琛");
		list.add("张艳杰");
		list.add("张哲鑫");
		list.add("闫智");
		list.add("孙崇伟");
		System.out.println("原始数据:"+list);
		ListIterator<String> iter=list.listIterator();
		String first=iter.next();
		String second=iter.next();
		iter.set("张艳杰");
		System.out.println("修改后的数据:"+list);


下面有两点需要注意:

1、我们不得不注意一个问题就是修改与遍历的同步性,如果在某个迭代器修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的情况。考虑下面一种情况:一个迭代器指向另一个迭代器刚刚删除的元素前面,这个迭代器就应该是无效的,并且不能够再被使用,在链表的迭代器的设计中,如果迭代器发现它的集合被另一个迭代器修改了,或者被该集合自身的方法修改了,就会抛出一个ConcurrentModificationException。

下面是一段示例代码:

List<String> list=new LinkedList<>();
		list.add("于理想");
		list.add("张琛");
		list.add("张艳杰");
		list.add("张哲鑫");
		list.add("闫智");
		list.add("孙崇伟");
		System.out.println("原始数据:"+list);
		ListIterator<String> iter1=list.listIterator();
		ListIterator<String> iter2=list.listIterator();
		iter1.next();
		iter1.remove();
		iter2.next();//会抛出异常

运行结果:


为了避免发生异常,Java核心技术上提到了使用下述简单规则来设计程序:可以根据需要给容器附加很多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的的迭代器。在这些集合的迭代器中,所有的迭代器都单独维护一个集合的大小的数值,因此当调用此迭代器的时候,会首先检查当前列表长度是否与原来维护的长度相等,如果不相等,则抛出上面所说的异常,因为迭代器检查的是集合长度,因此,set方法的调用不会引发上述异常。

2、Collection接口中声明了许多对链表操作的方法,其中大部分的实现是在LinkedList的超类AbstractCollection中实现的,这些实用方法有toString,contains等,其中也包括了许多有争议的方法,比如get(i)

值得注意的是,迭代器也可以返回当前的位置,而且这个方法效率非常高,因为迭代器是随时存储着元素的位置的。返回的时候使用nextIndex方法,可以返回调用next方法返回的元素在集合中的位置。

下面,最后一个综合程序综合反映了Java的链表的使用。

package Collection;

import java.util.*;

public class LinkedListTest {
	public static void main(String[] args) {
		List<String> a=new LinkedList<>();
		a.add("Amy");
		a.add("Carl");
		a.add("Erica");
		
		List<String> b=new LinkedList<>();
		b.add("bob");
		b.add("Doug");
		b.add("Frances");
		b.add("Gloria");
		
		ListIterator<String> aIter=a.listIterator();
		Iterator<String> bIter=b.iterator();
		
		while(bIter.hasNext()) {
			if(aIter.hasNext()) {
				aIter.next();
			}
			aIter.add(bIter.next());
		}
		
		System.out.println(a);
		
		bIter=b.iterator();
		while(bIter.hasNext()) {
			bIter.next();
			if(bIter.hasNext()) {
				bIter.next();
				bIter.remove();
			}
		}
		
		System.out.println(b);
		
		a.removeAll(b);
		System.out.println(a);
		
		ListIterator<String> uIter=a.listIterator();
		uIter.next();
		uIter.add("bob");
		uIter.add("Frances");
		System.out.println(a);
	}
}

运行结果:


2.ArrayList

ArrayList类应该是使用最多的一种集合了,这种集合本质是一种动态再分配的对象数组,对于这个类的最大好处就是提供了快速随机访问的功能,但是插入或删除,特别是在集合的前半部分操作的效率是非常低的。同样这个类也实现了List接口,其余操作与LinkedList相似。

和ArrayList类似的集合还有一个叫做Vector,这个集合与ArrayList的最大的区别就是这个集合是线程同步,建议在需要线程同步的时候才使用这个集合,如果不需要线程同步却使用Vector的话,效率会比ArrayList低,因为这个类会在线程同步上耗费时间。

3.散列集

散列集的目的是提高数据的查找效率,作为代价,数据是无序存放的。如果想要理解这散列集具体的原理,可以参考有关数据结构的书籍,这里只介绍这种集合在Java中如何使用。这种集合在Java中被封装为HashSet类,下面是一个关于使用它的示例:程序将读取输入的所有单词,并且将它们添加到散列集中,然后遍历散列集再次进行读入单词的输入,注意输入的单词顺序与输出的单词顺序的差别。(必须提到的是,所有类都可以使用hashCode方法生成散列码,当然也可以重定义)

在Java中,散列表用链表数组实现。每个列表被称为“桶”。

代码

package Collection;

import java.util.*;

public class SetTest {
	public static void main(String[] args) {
		Set<String> words=new HashSet<String>();//HashSet继承自Set
		long totalTime=0;
		try (Scanner in=new Scanner(System.in)){
			while(in.hasNext()) {
				String word=in.next();
				long callTime=System.currentTimeMillis();
				words.add(word);
				callTime=System.currentTimeMillis()-callTime;
				totalTime+=callTime;
			}
		}
		
		Iterator<String> iter=words.iterator();
		for(int i=1;i<=20 && iter.hasNext();i++) {
			System.out.println(iter.next());
		}
		System.out.println("......");
		System.out.println(words.size()+" distinct words."+totalTime+"milliseconds.");
	}
}

运行结果:


4.树集

TreeSet类与散列表类非常相似,不过树集是一个有序集合,意思就是可以在存入数据的时候自动按某种次序排列好,但是,作为代价,查找的效率会比散列表慢一些,但是会比顺序表快,而且有一个重要限制就是存入的类必须实现Comparable接口才可以。

从JavaSE6.0起,TreeSet类实现了NavigableSet接口,这个接口增加了几个便于定位元素以及反向遍历的方法。

下面示例中创建了两个Item对象的树集。第一个按照部件编号排序,这是Item对象的默认顺序。第二个通过使用一个定制的比较器来按照描述信息排序:

代码:

package Collection;

import java.util.*;

public class TreeSetTest {
	public static void main(String[] args) {
		SortedSet<Item> parts=new TreeSet<>();
		parts.add(new Item("Toaster",1234));
		parts.add(new Item("Widget",4562));
		parts.add(new Item("Modem",9912));
		System.out.println(parts);
		
		NavigableSet<Item> sortedByDescription=new TreeSet<>(
				Comparator.comparing(Item::getDescription)
				);
		
		sortedByDescription.addAll(parts);
		System.out.println(sortedByDescription);
	}
}

class Item implements Comparable<Item>{
	private String description;
	private int partNumber;
	
	public Item(String aDescription,int aPartNumber) {
		description=aDescription;
		partNumber=aPartNumber;
	}
	
	public String getDescription() {
		return description;
	}

	public String toString() {
		return "[description="+description+",patrNumber="+partNumber+"]";
	}
	
	public boolean equals(Object otherObject) {
		if(this==otherObject) {
			return true;
		}
		if(otherObject==null) {
			return false;
		}
		if(getClass()!=otherObject.getClass()) {
			return false;
		}
		Item other=(Item)otherObject;
		return Objects.equals(description, other.description) && partNumber==other.partNumber;
	}
	
	public int hashCode() {
		return Objects.hash(description,partNumber);
	}
	
	@Override
	public int compareTo(Item arg0) {
		int diff=Integer.compare(partNumber, arg0.partNumber);
		return diff!=0?diff:description.compareTo(arg0.description);
	}
}

运行结果:


5.队列与双端队列

队列可以让人们有效地在尾部添加一个元素,在头部删除一个元素,有两个端头的队列称为双端队列,队列不支持在中间插入或者删除元素,JavaSE6中引入Deque接口,并由ArrayDeque与LinkedList实现。

6.优先级队列

优先级队列中的元素可以按照任何顺序插入,却总是按照排序的顺序进行检索,优先级队列使用了一种称为堆的数据结构,堆是一种可以自我调整的二叉树,对树执行添加和删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。

优先级队列中的对象由于需要进行比较所以必须实现Comparable接口,可选的,在优先级队列的构造器中也可以提供一个Comparator对象

优先级队列的应用实例典型的有任务调度和带有vip服务的酒店排队等,任务调度具有优先级,优先级越高的最先被调度。酒店排队中,不管非vip用户来的多早,服务顺序都要晚于vip用户。

示例代码:

package Collection;

import java.time.*;
import java.util.*;

public class PriorityQequeTest {
	public static void main(String[] args) {
		PriorityQueue<LocalDate> pq=new PriorityQueue<>();
		pq.add(LocalDate.of(1906, 12, 9));
		pq.add(LocalDate.of(1815, 12, 10));
		pq.add(LocalDate.of(1903, 12, 3));
		pq.add(LocalDate.of(1910, 6, 22));
		System.out.println("遍历该队列");
		for(LocalDate date:pq) {
			System.out.println(date);
		}
		System.out.println("删除元素");
		while(!pq.isEmpty()) {
			System.out.println(pq.remove());
		}
	}
}

运行结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Kirito

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值