概述
具体的集合
(一)链表
什么是链表?
链表就是一个可以在任何位置进行高效地插入和删除操作的有序序列。
为什么要有链表?
因为对于数组和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:
-
-
boolean
add(E e)
将指定的元素插入此双端队列表示的队列中(换句话说,在此双端队列的尾部),如果它是立即可行且不会违反容量限制,返回
true
在成功时和抛出IllegalStateException
如果当前没有空间可用的。void
addFirst(E e)
插入此双端队列的前面,如果它是立即可行且不会违反容量限制,抛出一个指定的元素
IllegalStateException
如果当前没有空间可用。void
addLast(E e)
在插入如果它是立即可行且不会违反容量限制,抛出此双端队列的末尾指定元素
IllegalStateException
如果当前没有空间可用。boolean
contains(Object o)
如果此deque包含指定的元素,则返回
true
。Iterator<E>
descendingIterator()
以相反的顺序返回此deque中的元素的迭代器。
E
element()
检索但不删除由此deque表示的队列的头部(换句话说,该deque的第一个元素)。
E
getFirst()
检索,但不删除,这个deque的第一个元素。
E
getLast()
检索,但不删除,这个deque的最后一个元素。
Iterator<E>
iterator()
以正确的顺序返回此deque中的元素的迭代器。
boolean
offer(E e)
将指定的元素插入由此deque表示的队列(换句话说,在该deque的尾部),如果可以立即执行,而不违反容量限制,
true
在成功时false
如果当前没有可用空间,则返回false。boolean
offerFirst(E e)
在此deque的前面插入指定的元素,除非它会违反容量限制。
boolean
offerLast(E e)
在此deque的末尾插入指定的元素,除非它会违反容量限制。
E
peek()
检索但不删除由此deque表示的队列的头部(换句话说,此deque的第一个元素),如果此deque为空,则返回
null
。E
peekFirst()
检索,但不删除,此deque的第一个元素,或返回
null
如果这个deque是空的。E
peekLast()
检索但不删除此deque的最后一个元素,如果此deque为空,则返回
null
。E
poll()
检索并删除由此deque(换句话说,此deque的第一个元素)表示的队列的
null
如果此deque为空,则返回null
。E
pollFirst()
检索并删除此deque的第一个元素,如果此deque为空,则返回
null
。E
pollLast()
检索并删除此deque的最后一个元素,如果此deque为空,则返回
null
。E
pop()
从这个deque表示的堆栈中弹出一个元素。
void
push(E e)
将元素推送到由此deque表示的堆栈(换句话说,在此deque的头部),如果可以立即执行此操作而不违反容量限制,则抛出
IllegalStateException
如果当前没有可用空间)。E
remove()
检索并删除由此deque表示的队列的头(换句话说,该deque的第一个元素)。
boolean
remove(Object o)
从此deque中删除指定元素的第一个出现。
E
removeFirst()
检索并删除此deque的第一个元素。
boolean
removeFirstOccurrence(Object o)
从此deque中删除指定元素的第一个出现。
E
removeLast()
检索并删除此deque的最后一个元素。
boolean
removeLastOccurrence(Object o)
从此deque中删除指定元素的最后一次出现。
int
size()
返回此deque中的元素数。
-
ArrayDeque:
-
-
boolean
add(E e)
在此deque的末尾插入指定的元素。
void
addFirst(E e)
在此deque前面插入指定的元素。
void
addLast(E e)
在此deque的末尾插入指定的元素。
void
clear()
从这个deque中删除所有的元素。
ArrayDeque<E>
clone()
返回此deque的副本。
boolean
contains(Object o)
如果此deque包含指定的元素,则返回
true
。Iterator<E>
descendingIterator()
以相反的顺序返回此deque中的元素的迭代器。
E
element()
检索,但不删除,由这个deque表示的队列的头。
E
getFirst()
检索,但不删除,这个deque的第一个元素。
E
getLast()
检索,但不删除,这个deque的最后一个元素。
boolean
isEmpty()
如果此deque不包含元素,则返回
true
。Iterator<E>
iterator()
返回此deque中的元素的迭代器。
boolean
offer(E e)
在此deque的末尾插入指定的元素。
boolean
offerFirst(E e)
在此deque前面插入指定的元素。
boolean
offerLast(E e)
在此deque的末尾插入指定的元素。
E
peek()
检索但不删除由此deque表示的队列的头部,如果此deque为空,则返回
null
。E
peekFirst()
检索但不删除此deque的第一个元素,如果此deque为空,则返回
null
。E
peekLast()
检索但不删除此deque的最后一个元素,或返回
null
如果此deque为空)。E
poll()
检索并删除由此deque(换句话说,该deque的第一个元素)表示的队列的
null
如果此deque为空,则返回null
。E
pollFirst()
检索并删除此deque的第一个元素,如果此deque为空,则返回
null
。E
pollLast()
检索并删除此deque的最后一个元素,如果此deque为空,则返回
null
。E
pop()
从这个deque表示的堆栈中弹出一个元素。
void
push(E e)
将元素推送到由此deque表示的堆栈上。
E
remove()
检索并删除由此deque表示的队列的头部。
boolean
remove(Object o)
从此deque中删除指定元素的单个实例。
E
removeFirst()
检索并删除此deque的第一个元素。
boolean
removeFirstOccurrence(Object o)
删除此deque中指定元素的第一个出现(从头到尾遍历deque时)。
E
removeLast()
检索并删除此deque的最后一个元素。
boolean
removeLastOccurrence(Object o)
删除此deque中指定元素的最后一次(从头到尾遍历deque时)。
int
size()
返回此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());
}
}
}
最后
以上就是文静云朵为你收集整理的CoreJava读书笔记--集合(二)--具体的集合的全部内容,希望文章能够帮你解决CoreJava读书笔记--集合(二)--具体的集合所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复