CoreJava读书笔记--集合(二)--具体的集合

具体的集合

(一)链表

什么是链表?

链表就是一个可以在任何位置进行高效地插入和删除操作的有序序列。

为什么要有链表?

因为对于数组和ArrayList而言,要想从中间删除一个元素要付出的代价相当大,当某个元素被删除的时候,在这个元素之后位置的元素全部都要向前移动,而插入元素时也是这样。为了解决这个问题,就有了链表这样的数据结构。

事实上,在Java中,所有的链表都是双向链接的,即每个结点不仅存放着指向后一个结点的引用,也存放着指向前一个结点的引用。

 先看一个简单的例子,往链表中添加元素或删除元素。

package collections;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class LinkedListDemo {
	public static void main(String[] args) {
		List<String> list = new LinkedList<String>();
		list.add("Bob");
		list.add("Amy");
		list.add("Carl");
		Iterator<String> it = list.iterator();
		String first = it.next();
		String second = it.next();
		it.remove();//删除第二个元素
		System.out.println(list.toString());
	}
}

链表是一个有序集合(ordered collection),每个对象的位置十分重要。LinkedList.add方法是继承于Collection.add方法,它们都是将对象添加到链表的尾部,但是我们使用链表数据结构是为了快速地在指定位置进行添加或删除操作,由于迭代器是描述集合中位置的,所以这种依赖于位置的add方法将由迭代器负责。Java集合类库便提供了子接口ListIterator。

 与Collection.add方法不同,ListIterator.add方法不返回boolean类型值,它假定添加操作总会改变链表;

另外,ListIterator接口还有两个方法,可以用来反向遍历链表:

E previous();
boolean hasPrevious();

接下来,创建一个链表,在第二个元素前添加一个元素:

package collections;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public class LinkedListDemo{
	public static void main(String[] args) {
		List<String> list = new LinkedList<String>();
		list.add("Bob");
		list.add("Amy");
		list.add("Jack");
		list.add("Yolanda");//以上调用的都是继承于Collection的add方法
		System.out.println(list.toString());
		ListIterator<String> it = list.listIterator();//返回一个ListIterator接口的迭代器对象
		it.next();
		it.add("Second obj");//这是迭代器的add方法
		System.out.println(list.toString());
	}
}

 LinkedList类继承了很多AbstractCollection中的方法,例如toString,contains等方法。链表不支持快速访问,如果要查看第n个元素,它总是要越过前n-1个元素,没有捷径可以走。当程序需要采用整数索引访问元素时,我们往往不选用链表。

从API中可以看到,LinkedList类有个访问特定元素的get方法,这个方法效率不高,如果你发现自己正使用这个方法,你就应该考虑是否使用了错误的数据结构。绝对不应该使用这种让人误解的随机访问方法来遍历链表:

for(int i=0;i<list.size();i++){
    do something with list.get(i);
}

如果链表中只有少数元素,就不用担心使用get和set方法的开销问题。但是我们应该考虑的是使用链表的理由。使用链表的唯一理由就是尽可能减少在列表中插入或删除元素所付出的代价。

package linkedList;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * 创建两个链表,将它们合并,然后从第二个链表中每间隔一个元素就删除一个元素,最后测试removeAll方法
 *
 */
public class LinkedListTest {
	public static void main(String[] args) {
		List<String> a = new LinkedList<String>();
		a.add("a1");
		a.add("a2");
		a.add("a3");
		a.add("a4");
		List<String> b = new LinkedList<String>();
		b.add("b1");
		b.add("b2");
		b.add("b3");
		b.add("b4");
		b.add("b5");
		
		//将两个链表合并成一个链表
		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);
		
		//b中每间隔一个元素删除
		bIter = b.iterator();
		while(bIter.hasNext()) {
			bIter.next();
			if(bIter.hasNext()) {
				bIter.next();
				bIter.remove();
			}
		}
		System.out.println(b);
		
		//测试removeAll方法
		a.removeAll(b);
		
		System.out.println(a);
	}
}

 

(二)数组列表

什么是数组列表?

一个可以动态增长和缩减的索引序列。

为什么要用数组列表?

ArrayList实现了RandomAccess、Cloneable、Serializable接口,支持快速访问,复制和序列化。另外重要的一点可以动态的扩大或缩小容量。

List接口用于描述一个有序集合,并且集合中的每个元素的位置十分重要。有两种访问元素的协议:一种是使用迭代器,另一种是使用get或set方法随机访问每个元素。后一种方法不适用于LinkedList,但却适用于ArrayList,ArrayList也实现了List接口。

ArrayList不是线程安全的,它的方法不是同步的,而Vector是线程安全的,它的方法是同步的。

(三)散列集

Set也实现了Collection接口,它是没有重复元素的集合,set的add方法首先要在集合中查找是否含有这个对象,如果没有,就将该对象添加进去。

什么是HashSet?

HashSet实现了基于散列表的集合。该集合不关心其中元素的顺序,但是不能有重复元素。

为什么要有HashSet?

用于快速地查找元素,不必查看集合中所有的元素。

只有不关心集合中元素的顺序时,才应该使用HashSet。

package collections.set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class SetTest {
	public static void main(String[] args) {
		Set<String> words = new HashSet<String>();
		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> it = words.iterator();
		for(int i = 1;i<=20&&it.hasNext();i++) {
			System.out.println(it.next());
		}
		System.out.println("..............");
		System.out.println(words.size()+"distinct words."+totaltime+"milliseconds");
	}
}

(四)树集

什么是树集?

一个类似于散列集的集合,但是树集是一个有序集合。

为什么需要用树集?

散列集可以用于快速的查找元素,但是对于存储到散列集的对象无法保证其顺序,如果希望按照某种顺序来存储这些元素,就可以使用树集。

接下来看个例子,对比一下散列集的默认顺序和按照描述信息排序后的顺序。

package collections.set;

import java.util.Objects;

/**
 * 
 *
 */
public class Item implements Comparable<Item> {

	private String description;
	private int partNumber;
	
	public Item(String description,int partNumber) {
		this.description=description;
		this.partNumber=partNumber;
	}
	
	public String getDescription() {
		return description;
	}
	
	public String toString() {
		return "[description="+description
				+",partNumber="+partNumber+"]";
	}
	
	public int hashCode() {
		return Objects.hash(description,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;
	}
	@Override
	public int compareTo(Item other) {
		int diff = Integer.compare(partNumber, other.partNumber);
		return diff!=0?diff:description.compareTo(other.description);
	}
	
}
package collections.set;

import java.util.Comparator;
import java.util.NavigableSet;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * 创建两个Item对象树集,第一个按照默认顺序,第二个按照定制的比较器来排序
 *
 */
public class TreeSetTest {
	public static void main(String[] args) {
		SortedSet<Item> parts = new TreeSet<Item>();
		parts.add(new Item("Toaster",1234));
		parts.add(new Item("Widget",4562));
		parts.add(new Item("Modem",9912));
		System.out.println(parts);
		
		NavigableSet<Item> sortByDescription = new TreeSet<Item>(
				Comparator.comparing(Item::getDescription));
		sortByDescription.addAll(parts);
		System.out.println(sortByDescription);
	}
}

(五)队列与双端队列

什么是队列?

队列可以让人们有效的在尾部添加一个元素,在头部删除一个元素。

什么是双端队列?

有两个端头的队列,就是双端队列。可以让人们有效的在头部和尾部同时添加或删除元素。

不支持在队列中间添加元素。

Queue:

 Deque:

    • booleanadd(E e)

      将指定的元素插入此双端队列表示的队列中(换句话说,在此双端队列的尾部),如果它是立即可行且不会违反容量限制,返回 true在成功时和抛出 IllegalStateException如果当前没有空间可用的。

      voidaddFirst(E e)

      插入此双端队列的前面,如果它是立即可行且不会违反容量限制,抛出一个指定的元素 IllegalStateException如果当前没有空间可用。

      voidaddLast(E e)

      在插入如果它是立即可行且不会违反容量限制,抛出此双端队列的末尾指定元素 IllegalStateException如果当前没有空间可用。

      booleancontains(Object o)

      如果此deque包含指定的元素,则返回 true

      Iterator<E>descendingIterator()

      以相反的顺序返回此deque中的元素的迭代器。

      Eelement()

      检索但不删除由此deque表示的队列的头部(换句话说,该deque的第一个元素)。

      EgetFirst()

      检索,但不删除,这个deque的第一个元素。

      EgetLast()

      检索,但不删除,这个deque的最后一个元素。

      Iterator<E>iterator()

      以正确的顺序返回此deque中的元素的迭代器。

      booleanoffer(E e)

      将指定的元素插入由此deque表示的队列(换句话说,在该deque的尾部),如果可以立即执行,而不违反容量限制, true在成功时 false如果当前没有可用空间,则返回false。

      booleanofferFirst(E e)

      在此deque的前面插入指定的元素,除非它会违反容量限制。

      booleanofferLast(E e)

      在此deque的末尾插入指定的元素,除非它会违反容量限制。

      Epeek()

      检索但不删除由此deque表示的队列的头部(换句话说,此deque的第一个元素),如果此deque为空,则返回 null

      EpeekFirst()

      检索,但不删除,此deque的第一个元素,或返回 null如果这个deque是空的。

      EpeekLast()

      检索但不删除此deque的最后一个元素,如果此deque为空,则返回 null

      Epoll()

      检索并删除由此deque(换句话说,此deque的第一个元素)表示的队列的 null如果此deque为空,则返回 null

      EpollFirst()

      检索并删除此deque的第一个元素,如果此deque为空,则返回 null

      EpollLast()

      检索并删除此deque的最后一个元素,如果此deque为空,则返回 null

      Epop()

      从这个deque表示的堆栈中弹出一个元素。

      voidpush(E e)

      将元素推送到由此deque表示的堆栈(换句话说,在此deque的头部),如果可以立即执行此操作而不违反容量限制,则抛出 IllegalStateException如果当前没有可用空间)。

      Eremove()

      检索并删除由此deque表示的队列的头(换句话说,该deque的第一个元素)。

      booleanremove(Object o)

      从此deque中删除指定元素的第一个出现。

      EremoveFirst()

      检索并删除此deque的第一个元素。

      booleanremoveFirstOccurrence(Object o)

      从此deque中删除指定元素的第一个出现。

      EremoveLast()

      检索并删除此deque的最后一个元素。

      booleanremoveLastOccurrence(Object o)

      从此deque中删除指定元素的最后一次出现。

      intsize()

      返回此deque中的元素数。

 ArrayDeque:

    • booleanadd(E e)

      在此deque的末尾插入指定的元素。

      voidaddFirst(E e)

      在此deque前面插入指定的元素。

      voidaddLast(E e)

      在此deque的末尾插入指定的元素。

      voidclear()

      从这个deque中删除所有的元素。

      ArrayDeque<E>clone()

      返回此deque的副本。

      booleancontains(Object o)

      如果此deque包含指定的元素,则返回 true

      Iterator<E>descendingIterator()

      以相反的顺序返回此deque中的元素的迭代器。

      Eelement()

      检索,但不删除,由这个deque表示的队列的头。

      EgetFirst()

      检索,但不删除,这个deque的第一个元素。

      EgetLast()

      检索,但不删除,这个deque的最后一个元素。

      booleanisEmpty()

      如果此deque不包含元素,则返回 true

      Iterator<E>iterator()

      返回此deque中的元素的迭代器。

      booleanoffer(E e)

      在此deque的末尾插入指定的元素。

      booleanofferFirst(E e)

      在此deque前面插入指定的元素。

      booleanofferLast(E e)

      在此deque的末尾插入指定的元素。

      Epeek()

      检索但不删除由此deque表示的队列的头部,如果此deque为空,则返回 null

      EpeekFirst()

      检索但不删除此deque的第一个元素,如果此deque为空,则返回 null

      EpeekLast()

      检索但不删除此deque的最后一个元素,或返回 null如果此deque为空)。

      Epoll()

      检索并删除由此deque(换句话说,该deque的第一个元素)表示的队列的 null如果此deque为空,则返回 null

      EpollFirst()

      检索并删除此deque的第一个元素,如果此deque为空,则返回 null

      EpollLast()

      检索并删除此deque的最后一个元素,如果此deque为空,则返回 null

      Epop()

      从这个deque表示的堆栈中弹出一个元素。

      voidpush(E e)

      将元素推送到由此deque表示的堆栈上。

      Eremove()

      检索并删除由此deque表示的队列的头部。

      booleanremove(Object o)

      从此deque中删除指定元素的单个实例。

      EremoveFirst()

      检索并删除此deque的第一个元素。

      booleanremoveFirstOccurrence(Object o)

      删除此deque中指定元素的第一个出现(从头到尾遍历deque时)。

      EremoveLast()

      检索并删除此deque的最后一个元素。

      booleanremoveLastOccurrence(Object o)

      删除此deque中指定元素的最后一次(从头到尾遍历deque时)。

      intsize()

      返回此deque中的元素数。

      Spliterator<E>spliterator()

      创建一个late-binding失败快速 Spliterator在这个deque的元素。

      Object[]toArray()

      以适当的顺序返回一个包含此deque中所有元素的数组(从第一个到最后一个元素)。

      <T> T[]toArray(T[] a)

      以正确的顺序返回一个包含此deque中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。

 (六)优先级队列

什么是优先级队列?

就是队列中的元素可以按照任意顺序插入,却总是按照排序的顺序进行检索。

也就是说,无论何时调用remove方法,总会获得当前优先级队列中的最小元素。

与TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供的Comparator对象。

使用 优先级队列的典型示例就是任务调度。每一个任务都有一个优先级,任务可以随机顺序添加到队列中,但是启动一个新任务时,都将优先级最高的任务从队列中删除。(由于习惯上优先级越高,设置的数值越小。)

package collections;

import java.time.LocalDate;
import java.util.PriorityQueue;

public class PriorityQueueTest {
	public static void main(String[] args) {
		PriorityQueue<LocalDate> pq = new PriorityQueue<LocalDate>();
		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("Iterating over elements...");
		for(LocalDate date : pq) {
			System.out.println(date);
		}
		System.out.println("Removing elements");
		while(!pq.isEmpty()) {
			System.out.println(pq.remove());
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值