第九章 集合
文章目录
- 第九章 集合
- `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 aListIterator<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 bbIter = 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 aa.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~19group2.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));}
}