一.概述
上一篇学习了集合的基本概述,了解集合接口的继承关系和接口中的基本功能。Java集合概述
这一篇学习集合中Collection接口的具体实现,首先看下类的继承关系。
二.具体集合
1.链表
其实数组也算是一种数据结构,但它不能很好的扩充容量。出现了以数组为基本的ArrayList类进行存储。它们共同的缺陷就是如果想要从数组中间插入或删除,都要进行数组的遍历,然后在插入或删除的位置,向前或向后移动其他元素的位置,这样导致效率不是很高。所以使用链表的数据结构进行存储能解决这一问题。当然链表也有缺陷,查询效率不如数组实现高。如果数据量比较小的情况下建议使用ArrayList进行增加和删除。
/**
* 链表实例
*/
public class LinkedListDemo {
public static void main(String[] args) {
List<String> a = new LinkedList<>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new LinkedList<>();
b.add("d");
b.add("e");
b.add("f");
// ListIterator存在add方法而Iterator不存在add方法
ListIterator<String> aIterator = a.listIterator();
Iterator<String> bIterator = b.iterator();
while (bIterator.hasNext()) {
if (aIterator.hasNext()) {
aIterator.next();
// add 方法只依赖于迭代器的位置
aIterator.add(bIterator.next());
}
}
System.out.println(a);
bIterator = b.iterator();
while (bIterator.hasNext()) {
bIterator.next();
if (bIterator.hasNext()) {
bIterator.next();
// remove方法依赖于迭代器的状态
bIterator.remove();
}
}
System.out.println(b);
// 移除a中存在的b
a.removeAll(b);
System.out.println(a);
aIterator = a.listIterator();
String s = aIterator.next();
// 设置next之前的元素设置为3,set方法不被视为结构性改变。
aIterator.set("3");
System.out.println(a);
}
}
注意:可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外在单独附加一个既能读又能写的迭代器。
2.散列集
链表和数组的特点是有序的,在知道索引下表的情况下可以快速的查找等操作,如果不知道索引下标的情况就必须去遍历来进行查询,散列集的特点就是无序的,它可以在不知道的情况下进行快速的查询等操作。
散列表为每一个对象计算一个整数,称为散列码。如果是String类可以通过String类的hashCode方法来生成,如果是自定义类,就需要前面所学习的自定义hashCode方法。
/**
* 散列集实例
*/
public class SetDemo {
public static void main(String[] args) {
Set<String> words = new HashSet<>();
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> iterator = words.iterator();
for (int i = 1; i <= 20 && iterator.hasNext(); i++) {
System.out.println(iterator.next());
}
System.out.println(". . .");
System.out.println(words.size() + " distinct words " + totalTime + " milliseconds.");
}
}
注意:
- 在Java中,散列表用链表存储。Java8以后,桶满时会从链表改变为平衡二叉数存储。
- 每个列表被称为桶,设置桶的默认值,可以增加查询效率,默认初始值为16。
- 当桶满接近装填因子的值时,就会将所有的散列表再次进行散列,默认装填因子是75%。
- set集合是无序不重复的,所以可以用于集合的去重处理。
3.树集
TreeSet类是一个有序集合,排序使用的是树结构(红黑树–这部分自行了解)
/**
* 树结构实例
*/
public class TreeSetDemo {
public static void main(String[] args) {
SortedSet<Item> parts = new TreeSet<>();
parts.add(new Item("a",123));
parts.add(new Item("b",124));
parts.add(new Item("c",122));
System.out.println(parts);
// 使用描述来进行排序
// Java6以后,TreeSet实现了NavigableSet接口。(自行查询API)
NavigableSet<Item> sortByDescription = new TreeSet<>(
Comparator.comparing(Item::getDescription)
);
sortByDescription.addAll(parts);
System.out.println(sortByDescription);
}
}
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 void setDescription(String description) {
this.description = description;
}
public int getPartNumber() {
return partNumber;
}
public void setPartNumber(int partNumber) {
this.partNumber = partNumber;
}
@Override
public String toString() {
return "Item{" +
"description='" + description + '\'' +
", partNumber=" + partNumber +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return partNumber == item.partNumber &&
Objects.equals(description, item.description);
}
@Override
public int hashCode() {
return Objects.hash(description, partNumber);
}
@Override
public int compareTo(Item o) {
// 按照部件号来进行排序
int diff = Integer.compare(partNumber,o.partNumber);
return diff != 0 ? diff : description.compareTo(o.description);
}
}
4.队列与双端队列
/**
* 双端队列实例
*/
public class ArrayDequeDemo {
public static void main(String[] args) {
// 在Java6以后Deque接口,这个接口就是双端队列的顶级接口,由LinkedList和ArrayDeque来实现
// 创建一个双端队列默认大小为16
ArrayDeque<String> deque = new ArrayDeque<>();
/*
* 这是Queue接口的方法
*/
// 如果队列没满添加到队列并返回true;满了抛出异常
deque.add("1");
// 如果队列没满添加到队列并返回true;满了返回false
deque.offer("2");
// 如果队列不空,删除并返回这个队列头部的元素,是空的抛出异常
deque.remove();
// 如果队列不空,删除并返回这个队列头部的元素,是空的返回null
deque.poll();
// 如果队列不空,返回这个队列头部的元素,是空的抛出异常
deque.element();
// 如果队列不空,返回这个队列头部的元素,是空的返回null
deque.peek();
/*
* 这是Deque接口的方法
*/
// 添加到双端队列的头部或尾部 队列满了抛出异常
deque.addFirst("1");
deque.addLast("2");
// 添加到双端队列的头部或尾部 队列满了返回false
deque.offerFirst("1");
deque.offerLast("2");
// 如果队列不空,删除并返回头部或尾部的元素,为空抛出异常
deque.removeFirst();
deque.removeLast();
// 如果队列不空,删除并返回头部或尾部的元素,为空返回null
deque.pollFirst();
deque.pollLast();
// 如果队列不空,返回头部或尾部的元素,为空抛出异常
deque.getFirst();
deque.getLast();
// 如果队列不空,返回头部或尾部的元素,为空返回null
deque.peekFirst();
deque.peekLast();
}
}
5.优先级队列
队列中的元素可以按照任意的顺序插入,却总是按照排序的顺序检索。无论合适调用remove方法,总会获得当前优先级队列中最小的元素。
/**
* 优先级队列
*/
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<LocalDate> queue = new PriorityQueue<>();
queue.add(LocalDate.of(1998,9,1));
queue.add(LocalDate.of(1991,12,1));
queue.add(LocalDate.of(1992,8,1));
queue.add(LocalDate.of(1995,11,1));
// 不管显示的顺序如何,删除都会删除最小的元素
System.out.println("Iterating over elements...");
for (LocalDate date : queue) {
System.out.println(date);
}
System.out.println("Removing elements...");
while (!queue.isEmpty()) {
System.out.println(queue.remove());
}
}
}
三.总结
这篇主要学习了Collection接口的一些具体集合,没种集合都有它自己的特点,在使用上要根据具体情况采取最适合的集合进行存储,这样会大大加快程序的运行效率。
下一篇学习映射类集合Map接口,也是比较常用的集合。
有些可能我理解的不够深刻,大家如果觉得我说的不够详细可以参考我的推荐书,详细的看一下。欢迎大家评论。第一时间我会回复大家。谢谢!