第九章 集合
文章目录
- 第九章 集合
- `Java`集合框架
- 集合接口与实现分离
- `Collection`接口
- 迭代器
- 泛型实用方法
- 集合框架中的接口
- 具体集合
- 链表
- 数组列表
- 散列集
- 树集
- 优先队列
- 映射
- 映射的基本操作
- 更新映射条目
- 映射视图
- 弱散列试图
- 链接散列集与映射
- 枚举集与映射
- 标识散列映射
- 视图与包装器
- 小集合
- 子范围
- 不可修改视图
- 同步视图
- 算法
- 为什么使用泛型算法
- 排序与混排
- 二分查找
- 简单算法
- 批操作
- 集合和数组的转换
- 遗留的集合
- 栈
- 位集
Java
集合框架
集合接口与实现分离
- 队列,先进先出
- 队列接口最简形式:
public interface Queue<E>{
void add(E element);
E remove();
int size();
}
- 队列实现: 1. 使用循环数组 2. 使用链表
public class CirclarArrayQueue<E> implements Queue<E>{
private int head;
private int tail;
CirclarArrayQueue(int capacity);
public void add(E element);
public E remove();
public int size();
private E[] element;
}
Collection
接口
- 集合类的基本接口是Collection接口
public interface Collection<E>{
boolean add(E element);
Iterator<E> iterator();
...
}
迭代器
-
Iterator
接口包含
public interface Iterator<E>
{
E next();
boolean hasNext();
void remove();
default void forEachRemaining(Consumer<? super E> action);
}
- 访问集合中的元素
Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while(iter.hasNext()){
String element = iter.next();
...
}
或者直接
for(String elem: c){
...
}
-
for-each
循环可以处理任何实现了Iterable
接口的对象
public interface Iterable<E>{
Iterator<E> iterator();
}
Collection
接口扩展了Iterable
接口,所以标准库中任何集合都有可以使用for-each
循环
- 也可以
forEachRemaining(element->do sth. with element)
对于迭代器每个元素调用这个lambda表达式直到没有元素为止 - java迭代器位于两个元素之间,调用next,迭代器越过下一个元素, 并且返回刚刚越过元素的引用
-
Iterator
接口的remove
方法将删除上次调用next方法返回的元素
Iterator<Stirng> it = c.iterator();
it.next();
it.remove();
//删除该字符串的第一个元素
- remove方法调用之前没有调用next方法会抛出IllegalStateException异常
泛型实用方法
- P372
集合框架中的接口
- 集合中有两个基本接口
Collection, Map
- 在集合中插入元素
boolean add(E element);
映射包含键值对,插入用V put(K key, V value);
- 从映射中读取值
V get(K key);
-
List
是一个有序集合,可以用迭代器访问,可以随机访问(访问整数索引) -
Set
接口等同于Colletion
接口,其add
方法不允许增加重复元素,hasCode
保证包含相同元素得到相同的散列码,equals
相等
具体集合
链表
- java中, 所有链表实际上都是双向链接的
- 先添加3个元素, 再将第二个元素删除
var a = new LinkedList<String>();
a.add("Amy");
a.add("Carl");
a.add("Erica");
Iterator<Stirng> iter = staff.iterator();
String first = iter.next();
String second = iter.next();
iter.remove();
- 链表和泛型集合之间的区别: 链表是一个有序集合, LinkList.add方法将对象添加到链表尾部
- 反向遍历链表:
LinkIterator
接口: E previous(); boolean hasPrevious(); - 为了避免并发修改异常:可以根据需要为集合关联过多个迭代器,但是迭代器只能读取集合
LinkedList
类提供了get方法访问某个特定元素,但是效率不高
package chapter9_set.linkedList;
import java.util.*;
public class LinkedListTest {
public static void main(String[] args) {
var a = new LinkedList<String>();
a.add("Amy");
a.add("Carl");
a.add("Erica");
var b = new LinkedList<String>();
b.add("Bob");
b.add("Doug");
b.add("Frances");
b.add("Gloria");
//merge the words from b into a
ListIterator<String> aIter = a.listIterator();
Iterator<String> bIter = b.iterator();
while (bIter.hasNext()) {
if(aIter.hasNext()) aIter.next();
aIter.add(bIter.next());
}
/*
for (String s : b) {
if (aIter.hasNext()) aIter.next();
aIter.add(s);
}*/
System.out.println(a);
//remove every second word from b
bIter = b.iterator();
while (bIter.hasNext()) {
bIter.next();
if (bIter.hasNext()) {
bIter.next();
bIter.remove();
}
}
System.out.println(b);
//bulk operation: remove all words in b from a
a.removeAll(b);
System.out.println(a);
}
}
/*
Connected to the target VM, address: '127.0.0.1:54324', transport: 'socket'
[Amy, Bob, Carl, Doug, Erica, Frances, Gloria]
[Bob, Frances]
[Amy, Carl, Doug, Erica, Gloria]
Disconnected from the target VM, address: '127.0.0.1:54324', transport: 'socket'
*/
数组列表
-
ArrayList
封装了一个动态再分配的对象数组 -
Vector
类创建动态数组,其所有方法都是同步的,如果只是一个线程的话用ArrayList
散列集
- 散列表用链表数组实现
- 每个列表被称为桶,计算散列码,与桶的总数取余,得到的结果就是保存这个元素桶的索引
- 装填因子(一般0.75)确定何时对于这个表进行再散列
树集
- 树集是一个有序集合
- 任意顺序插入,但是遍历时,值按照排序过后的方式呈现
package chapter9_set.treeSet;
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
var 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);
var sortByDescription = new TreeSet<Item>(Comparator.comparing(Item::getDescription));
sortByDescription.addAll(parts);
System.out.println(sortByDescription);
}
}
package chapter9_set.treeSet;
import java.util.*;
public 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 + ", partNumber = " + partNumber + " ]";
}
public boolean equals(Object otherObject) {
if (this == otherObject) return true;
if(otherObject == null) return false;
if(getClass() != otherObject.getClass()) return false;
var other = (Item) otherObject;
return Objects.equals(description, other.description) && partNumber == other.partNumber;
}
public int hasCode() {
return Objects.hash(description, partNumber);
}
public int compareTo(Item other) {
int diff = Integer.compare(partNumber, other.partNumber);
return diff != 0 ? diff : description.compareTo(other.description);
}
}
优先队列
- 优先队列中元素可以按照任意顺序插入,但会按照有序顺序检索
- 只要调用
remove
方法,总会获得当前优先队列中最小的元素 - 优先队列使用堆,其添加和删除操作将最小元素移动到根
- 这里迭代并没有按照顺序来访问元素,删除操作却总是删除剩余元素中最小的那个
package chapter9_set.priorityQueue_Heap;
import java.util.*;
import java.time.*;
public class PriorityQueueTest {
public static void main(String[] args) {
var pq = new PriorityQueue<LocalDate>();
pq.add(LocalDate.of(1906, 12, 9));
pq.add(LocalDate.of(1806, 12, 9));
pq.add(LocalDate.of(1206, 12, 9));
pq.add(LocalDate.of(1826, 12, 9));
pq.add(LocalDate.of(1826, 2, 9));
pq.add(LocalDate.of(1826, 1, 10));
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());
}
}
映射
-
map
映射存放键值对
映射的基本操作
-
HashMao, TreeMap
: 散列映射稍快,不需要按照有序顺序访问键选择散列映射 - 键必须唯一
-
remove
删除给定键对应的元素,size
方法返回映射中元素数 - 迭代处理映射的键和值,使用
forEach
方法
Map<String, Integer>scores = ...;
int score = scores.getOrDeaufault(id, 0);
scores.forEach((k. v) -> System.out.println("key" + k + ", value=" + v)); //lambda
package chapter9_set.map;
import java.util.HashMap;
public class MapTest {
public static void main(String[] args) {
var staff = new HashMap<String, Employee>();
staff.put("123-15-1555", new Employee("Amy Lee"));
staff.put("157-58-9999", new Employee("Harry Hacker"));
staff.put("154-87-9999", new Employee("Ou Kunnan"));
staff.put("548-84-1111", new Employee("Da Bendan"));
System.out.println(staff.toString());
//System.out.println(staff);
staff.remove("123-15-1555");
staff.put("999-99-9999", new Employee("Hee"));
System.out.println(staff.get("999-99-9999"));
staff.forEach((key, value) -> System.out.println("key=" + key + ", values=" + value));
}
}
package chapter9_set.map;
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
更新映射条目
counts.put(word, counts.get(word) + 1);
//第一次看到word会出现`NullPointerException`
counts.put(words, counts.getOrDefault(word, 0) + 1);
counts.putIfAbsent(word, 0);
counts.put(word, counts.get(word) + 1);
counts.merge(word, 1, Integer::sum);
映射视图
- 3种视图: 键集, 值集, 键值集
Set keySet();
Collection values();
Set<Map, Entry<K, V>> entrySet();
- 枚举映射条目:
for(Map.Entry<String, Employee> entry : staff.entrySet()){
//for(var entry : staff.entrySet())
String k = entry.getKey();
Employee v = entry.getValue();
......
}
- 在键集视图可以调用
remove
,会删除键值对, 但是不能添加元素(会抛出UnsupportedOperationException
) - 映射条目集视图也有同样的限制
弱散列试图
-
WeakHashMap
使用弱引用保存键 - P400
链接散列集与映射
-
LinkedHashSet, LinkedHashMap
会记住元素插入顺序
var staff = new LinkedHashMap<String, Employee>();
staff.put("153156", new Employee("BJv"));
staff.put("2516256", new Employee("Dv"));
staff.put("5453156", new Employee("AJv"));
staff.put("953156", new Employee("BAJVJv"));
var it = staff.keySet().iterator();
while(it.hasNext()){
System.out.println(it.next());
}
// staff.values().iterator()
- 最近最少使用原则: 访问频率高的放在内存种,访问频率低的放在数据库
var cache = new LinkedHashMap<K, V>(128, 0.75F, true){
protected boolean removeEldestEntry(Map.Entry<K, V> eldest){
return size() > 100;
}
}
枚举集与映射
-
EnumSet
没有公共构造器,使用静态工厂方法构造这个集:
enum Weekday{MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY};
EnumSet<Weekday> a = EnumSet.allOf(Weekday.class);
...
-
EnumMap
是一个键类型为枚举类型的映射
var a = new EnumMap<Weekday, Employee>(Weekday.class);
标识散列映射
- 类
IdenetityHashMap
: 键值不是使用函数hadCode
, 而是方法System.identityHashCode
- 根据内存地址计算散列码
- 两个对象进行比较时,使用
==
, 而不使用equals
视图与包装器
- keySet方法返回了一个实现了Set接口的类对象,由这个类的方法操纵原映射.这种集合称为视图
- 视图: 根据用户观点所定义的数据结构
小集合
List<String> names = List.of("Peter", "Paul", "Mary");
Set<Integer> numbers = Set.of(2, 3, 5);
Map<String, Integer> scores = Map.of("Peter", 2, "Paul", 3, "Mary", 5);
- 以上集合不可更改,需要更改的话
var names = new ArrayList<>(List.of("Peter", "Paul", "Mary"));
子范围
- 可以为很多集合建立子范围视图
List<Employee> group2 = staff.subList(10, 20); //10~19
group2.clear(); // 元素会自动的从staff中删除
SortedSet<E> subSet(E from, E to);// 返回大于from, 小于to的所有元素构成的子集
不可修改视图
- 这些视图对现有的集合增加了一个运行检查.如果发现试图对集合修改,抛出异常
- P406
同步视图
-
Collections
类的静态方法synchronizedMap
方法可以将任何一个映射转换成有同步访问方法的Map
var map = Collections.synchronizedMap(new HashMaP<String, Employee>());
算法
为什么使用泛型算法
- 数组中最大元素:
if(a.length == 0) throw new NoSuchElementException();
T largest = a[0];
for (int i = 0; i < a.length; i++) {
if (largest.compareTo(a[i]) < 0) {
largest = a[i];
}
}
- 数组列表中最大元素
if(a.size() == 0) throw new NoSuchElementException();
T largest = a.get(0);
for (int i = 0; i < a.size(); i++) {
if (largest.compareTo(a.get(i)) < 0) {
largest = a.get(i);
}
}
- 链表没有高效随机访问操作,可以使用迭代器
if (l.isEmpty()) throw new NoSuchElementException();
Iterator<T> it = l.iterator();
T largest = it.next();
while (it.hasNext()) {
T next = it.next();
if (largest.compareTo(next) < 0) {
largest = next;
}
}
- 可以使用max方法实现能够接受任何实现了
Collection
接口的对象
public static <T extends Comparable> T max(Collection<T> collection) {
if (collection.isEmpty()) throw new NoSuchElementException();
Iterator<T> iter = collection.iterator();
T largest = iter.next();
while (iter.hasNext()) {
T next = iter.next();
if (largest.compareTo(next) < 0) {
largest = next;
}
}
return largest;
}
排序与混排
-
sort
方法对实现了List
接口的集合排序
Colletion.sort(staff);
- 列表元素实现了
Comparable
接口
staff.sort(Comparator.comparingDouble(Employee::getSalary))
- 逆序
Collection.reverseOrder()
staff.sort(Comparator.comparingDouble(Employee::getSalary).reversed())
- 列表是可修改的支持set方法,可改变大小的支持add, remove方法
package chapter9_set.shuffle;
import java.util.*;
public class ShuffleTest {
public static void main(String[] args) {
var numbers = new ArrayList<Integer>();
for (int i = 1; i < 49; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
List<Integer> winningCombination = numbers.subList(0, 10);
Collections.sort(winningCombination);
System.out.println(winningCombination);
}
}
二分查找
-
Collection
的binarySearch
方法实现了二分查找算法,要求集合必须有序 - 集合必须实现
List
接口, 如果没有采用Comparable
接口的CompareTo
方法进行排序,那么需要提供一个比较器对象
i = Collection.binarySearch(c, element, comparator);
返回非负值:匹配对象的索引; 返回负值, 插入位置为-i-1
- 如果提供的是链表则退化为线性查找
简单算法
- P416
批操作
l1.retainAll(l2);
l1.removeAll(l2);
- 键集是映射的一个视图,
staff.keySet().removeAll(terminatedIDs);
键和相关的员工名字会自动的从映射中删除
集合和数组的转换
- 数组转换为集合:
String[] names = ;
var name = new HashSet<>(List.of(names));
- 集合得到数组使用
toArray
方法,返回对象数组
String[] values = staff.toArray(new String[0));
遗留的集合
- 略
栈
-
Stack
类,有pop, push
方法, 扩展了Vector
类 -
peek
返回栈顶元素
位集
-
BitSet
: 高效存储位序列
//计算前两百万位素数数量 计数使用时间
package chapter9_set.sieve;
import java.util.*;
public class Sieve {
public static void main(String[] args) {
int n = 2000000;
long start = System.currentTimeMillis();
int i;
int count = 0;
var bitSet = new BitSet(n + 1);
for (i = 2; i <= n; i++)
bitSet.set(i);
i = 2;
while (i * i <= n) {
if (bitSet.get(i)) {
count++;
int k = 2 * i;
while (k <= n) {
bitSet.clear(k);
k += i;
}
}
i++;
}
// i = sqrt(n);
while (i <= n) {
if (bitSet.get(i))
count++;
i++;
}
long end = System.currentTimeMillis();
System.out.println("From 2~2000000, there are " + count + " primes.");
System.out.println("Time consumed:" + (end - start));
}
}