- 利用Java类库帮助程序设计中实现传统的数据结构:如何使用标准库中的集合类。
- Java集合框架:
- 将集合的接口与实现分离:
- 队列接口:指出可以在队列的尾部添加元素(入队),在队列的头部删除元素(出队),并且可以查找队列中元素的个数。队列的实现方式通常是:循环数组或使用链表。
- 如果需要一个循环数组队列,可以使用 ArrayDeque类。如果需要一个链表队列,就使用 LinkedList类(实现了Queue接口)。
- 可以使用 接口类型 存放 集合的引用,这样,如果需要改变想法,就可以轻松使用另一种不同的实现。
- Collection 接口:
- 在Java类库中,集合类的基本接口是 Collection接口。
- 迭代器:
public interface Iterator<E>
{
E next();
boolean hasNext();
void remove();
default void forEachRemaining( Consumer<? super E> action );
}
- 如果到达了集合的末尾,next方法将抛出一个 NoSuchElementException。因此调用 next方法前 需要 调用 hasNext方法检查。
- “for each”循环可以与任何实现了 Iterator接口 的对象一起工作。这个接口只包含一个抽象方法:Iterator<E> iterator() 。
- Java SE 8 中,可以使用 : iterator.forEachRemaining( element -> do something with element ) 。
- Java中,迭代器查找操作与位置变更是紧密相连的。查找一个元素的唯一方法是调用next,而执行查找操作的同时,迭代器的位置随之向前移动。
- Iterator接口的 remove方法 将会删除上次调用 next方法时返回的元素。
- next方法和remove方法具有互相依赖性。如果调用remove之前没有调用next将是不合法的,会抛出IllegalStateException异常。(先调用next方法越过要删除的元素)
- 泛型实用方法:
- java.util.Collection<E>:
- Iterator<E> iterator()
- int size()
- boolean isEmpty()
- boolean contains( Object obj )
- boolean containsAll( Collection<?> other )
- boolean add( Object element )
- boolean addAll( Collection<? extends E> other )
- boolean remove( Object obj )
- boolean removeAll( Collection<?> other )
- default boolean removeIf( Predicate<? super E> filter )
- void clear()
- boolean retainAll( Collection<?> other ) //交集
- Object[] toArray()
- <T> T[] toArray( T[] arrayToFill )
- java.util.Collection<E>:
- 集合框架中的接口:
- 集合有两个基本接口: Collection 和 Map 。
- 标记接口 RandomAccess :测试一个特定的集合是否支持高效的随机访问。
- 具体的集合:
- ArrayList 一种可以动态增长和缩减的索引序列。
- LinkedList 一种可以在任何位置进行高效地插入和删除操作的有序序列。
- ArrayDeque 一种用循环数组实现的双端队列。
- HashSet 一种没有重复元素的无序集合。
- TreeSet 一种有序集。
- EnumSet 一种包含枚举类型值的集。
- LinkedHashSet 一种可以记住元素插入次序的集。
- PriorityQueue 优先队列
- HashMap 一种存储 键/值 关联的数据结构。
- TreeMap 一种 键值 有序排列的映射表。
- EnumMap 一种键值属于枚举类型的映射表。
- LinkedHashMap 一种可以记住 键/值 添加次序的映射表。
- WeakHashMap 一种无用之后,可以被垃圾回收器回收的映射表。
- IdentityHashMap 一种用==而不是用equals比较键值的映射表。
- 链表:
- 数组和数组列表的重大缺陷:从数组中间删除或插入一个元素需要付出很大的代价。
- 使用链表就可以解决以上的问题。
- 在 Java 中,所有链表实际上都是双向链接的。(doubly linked)
- 链表与泛型集合之间有一个重要的区别:链表是一个有序集合,每个对象的位置十分重要。而迭代器是描述集合中位置的,所以只有对自然有序的集合使用迭代器添加元素才有实际意义。故Iterator的 子接口 ListIterator 中包含 add方法,该add方法不会返回布尔值,总是假定添加操作会改变链表,add方法会在迭代器位置之前添加一个对象。(链表的add方法只会将对象添加到链表的尾部)
- ListIterator接口有两个方法,可以用于反向遍历链表。 E previous() ;boolean hasPrevious() 。
- LinkedList类的 ListIterator方法 返回一个实现了 ListIterator接口的 迭代器对象。
- add方法只依赖于迭代器的位置,而remove方法依赖于迭代器的状态(上一步是next还是previous)。
- 如果迭代器发现它的集合被另一个迭代器修改了,或是该被集合自身的方法修改了,就会抛出一个ConcurrentModificationException异常。(并发修改异常)
- 为了避免并发修改异常,遵循以下原则:可以根据需要给容器附加许多的迭代器,但只读。另外,再单独附加一个既可读又可写的迭代器。
- java.util.List<E>:
- ListIterator<E> listIterator()
- ListIterator<E> listIterator( int index )
- void add( int i, E element )
- void addAll( int i, Collection<? extends E> elements )
- E remove(int i)
- E get(int i)
- E set(int i, E element)
- int indexOf( Object element )
- int lastIndexOf( Object element )
- java.util.ListIterator<E>
- void add(E newElement)
- void set(E newElement)
- boolean hasPrevious()
- E previous()
- int nextIndex()
- int previousIndex()
- java.util.LinkedList<E>
- LinkedList()
- LinkedList( Collection<? extends E> elements )
- void addFirst( E element )
- void addLast( E element )
- E getFirst()
- E getLast()
- E removeFirst()
- E removeLast()
- 示例程序:(交错合并两个链表,对其中一个链表每隔一个元素进行一次删除,测试removeAll方法)
package linkedList;
import java.util.*;
public class LinkedListTest {
public static void main( String[] args ) {
List<String> list1 = new LinkedList<>();
List<String> list2 = new LinkedList<>();
list1.add("Antony");
list2.add("Bryant");
list1.add("Carmelo");
list2.add("Davis");
list1.add("Evans");
list2.add("Franklin");
list2.add("George");
System.out.println("Original list1 and list2: ");
System.out.println(list1);
System.out.println(list2);
ListIterator<String> iter1 = list1.listIterator();
Iterator<String> iter2 = list2.iterator();
while( iter2.hasNext() ) {
if( iter1.hasNext() ) iter1.next();
iter1.add(iter2.next());
}
System.out.println("list1 after merge: ");
System.out.println(list1);
iter2 = list2.iterator();
while( iter2.hasNext() ) {
iter2.next();
if( iter2.hasNext() ) {
iter2.next();
iter2.remove();
}
}
System.out.println("list1 and list2 after list2's removal: ");
System.out.println(list1);
System.out.println(list2);
list1.removeAll(list2);
System.out.println("list1 after removing all the elemnts in list2(current): ");
System.out.println(list1);
}
}
- 数组列表:ArrayList类。这个类也实现了 List接口。ArrayList封装了一个动态再分配的对象数组。适用于随机访问修改元素。
- Vector类与ArrayList类:Vector类的所有方法都是同步的,可以两个线程安全地访问一个Vector对象。但,如果由一个线程访问Vector,代码要在同步操作上耗费大量时间。故,建议在不需要同步的时候使用ArrayList,而不要使用Vector。
- 散列集:
- 在Java中,散列表用链表数组实现。每个列表被称为”桶“。
- 在 Java SE 8 中,桶满时会从链表变为平衡二叉树。
- 如果想更多地控制散列表的运行性能,就要指定一个初始的桶数。
- 通常桶数设置为预计元素的75%到150%,最好将桶数设置为一个素数。(防止键的聚集)
- 标准库使用的桶数是2的幂,默认值为16。
- 设装填因子为0.75(默认值),表中超过75%的位置填入元素则这个表就会以双倍的桶数进行再散列。
- java.util.HashSet<E>
- HashSet()
- HashSet( Collection<? extends E> elements )
- HashSet(int capacity)
- HashSet(int capacity, float loadFactor)
- 示例程序:(测试HashSet,打开一个文本文件,输出里面的不重复单词数,以及添加所有单词到HashSet的总耗时多少毫秒)
package set;
import java.util.*;
import java.io.*;
public class SetTest {
public static void main( String[] args ) {
Set<String> words = new HashSet<>();
long totalTime = 0;
try( Scanner in = new Scanner(new File("C:\\Users\\86188\\alice30.txt") ) ){
while( in.hasNext() ) {
String word = in.next();
long callTime = System.currentTimeMillis();
words.add(word);
callTime = System.currentTimeMillis() - callTime;
totalTime += callTime;
}
}catch(Exception e ) {
e.printStackTrace();
}
Iterator<String> iter = words.iterator();
for( int i = 1; i <= 20 && iter.hasNext(); i++ ) {
System.out.println(iter.next());
}
System.out.println("...");
System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds." );
}
}
- 树集:
- TreeSet类与散列集十分类似。树集是一个有序集合(排序的实现是红黑树)。
- 因此将一个元素添加到树中要比添加到散列表中要慢。但是检查数组或链表中的重复元素相比还是快很多。
- 要使用树集,必须能够比较元素,要么元素实现了Comparable接口,要么提供一个比较器Comparator。
- 从 Java SE 6 开始,TreeSet类实现了 NavigableSet接口。
- java.util.TreeSet<E>:
- TreeSet()
- TreeSet( Comparator<? super E> comparator )
- TreeSet( Collection<? extends E> elements )
- TreeSet( SortedSet<E> s )
- java.util.SortedSet<E>:
- Comparator<? super E> comparator() //如果元素是使用Comparable接口的compareTo方法比较就返回null 。
- E first() //返回最小元素
- E last() //返回最大元素
- java.util.NavigableSet<E>
- E higher(E value)
- E lower(E value) //返回大于value的最小元素或小于value的最大元素
- E ceiling(E value)
- E floor(E value) //返回大于等于 value的最小元素或 小于等于 value 的最大元素。
- E pollFirst()
- E pollLast() //删除并返回最大元素或最小元素
- Iterator<E> descendingIterator() //返回一个递减顺序遍历集中元素的迭代器。
- 示例程序:(测试树集,利用树集 按照compareTo方法 和 比较器的方式 对多个对象进行排序)
package treeSet;
import java.util.*;
public class TreeSetTest {
public static void main( String[] args ) {
SortedSet<Item> sortedByCompareTo = new TreeSet<>();
sortedByCompareTo.add(new Item("Atlanta", 321) );
sortedByCompareTo.add(new Item("Boston", 231 ) );
sortedByCompareTo.add(new Item("Chicago",123 ) );
System.out.println(sortedByCompareTo);
System.out.println();
NavigableSet<Item> sortedByName = new TreeSet<>(Comparator.comparing(Item::getName));
sortedByName.addAll(sortedByCompareTo);
System.out.println(sortedByName);
}
}
package treeSet;
import java.util.Objects;
public class Item implements Comparable<Item>{
private String name;
private int id;
public Item() { name = ""; id = -1; }
public Item( String name, int id ) {
this.name = name;
this.id = id;
}
public String getName() { return name; }
public int getId() { return id; }
public String toString() {
return "[name=" + name + ", id=" + id + "]";
}
public boolean equals( Object other ) {
if( this == other ) return true;
if( other == null ) return false;
if( this.getClass() != other.getClass() ) return false;
Item otherItem = (Item) other;
return Objects.equals(name, otherItem.name) && id == otherItem.id;
}
public int hashCode() { return Objects.hash(name,id); }
public int compareTo( Item other ) {
int diff = Integer.compare(id, other.id);
if( diff != 0 ) return diff;
return name.compareTo(other.name);
}
}
- 队列与双端队列:
- 在 Java SE 6中,引入了 Deque接口, 并由 ArrayDeque类 和 LinkedList类 实现。
- java.util.Queue<E>
- boolean add( E element ) //队尾入队,若队列满了,抛出 IllegalStateException 异常。
- boolean offer( E element ) //队尾入队,若队列满了,返回false。
- E remove() //删除并返回队头元素。若队头为空,抛出 NoSuchElementException 。
- E poll() //功能同上,但若队头为空,返回null 。
- E element()
- E peek() //类似。
- java.util.Deque<E>
- void addFirst( E element )
- void addLast( E element )
- boolean offerFirst( E element )
- boolean offerLast( E element )
- E removeFirst/Last()
- E pollFirst/Last()
- E getFirst/Last()
- E peekFirst/Last()
- java.util.ArrayDeque<E>
- ArrayDeque() //初始容量默认为16
- ArrayDeque(int initialCapacity) //给定初始容量的无限双端队列
- 优先级队列:
- 无论何时调用remove方法,总会获得当前优先队列中最小的元素。
- 与TreeSet一样,一个优先队列既可以保存实现了Comparable接口的对象,也可以保存在构造器中提供的 Comparator对象。
- java.util.PriorityQueue<E>
- PriorityQueue()
- PriorityQueue( int initialCapacity )
- PriorityQueue( int initialCapacity, Comparator<? super E> c )
- 示例程序:(测试优先队列)
package priorityQueue;
import java.time.*;
import java.util.*;
public class PriorityQueueTest {
public static void main( String[] args ) {
PriorityQueue<LocalDate> heap = new PriorityQueue<>();
heap.add(LocalDate.of(1906, 12, 9));
heap.add(LocalDate.of(1815, 12, 10));
heap.add(LocalDate.of(1903, 12, 3));
heap.add(LocalDate.of(1910, 6, 22));
//迭代顺序访问优先队列的时候,貌似是采用了层序遍历
System.out.println("Iterating over elements...:");
for( LocalDate d : heap )
System.out.println(d);
System.out.println();
System.out.println("Removing elements...:");
while( !heap.isEmpty() ) {
System.out.println(heap.remove());
}
}
}
- 映射:用来存放 键/值 对。可以快速查找现有的元素。
- 基本映射操作:
- Java类库为映射提供了两个通用的实现:HashMap 和 TreeMap。这两个类都实现了 Map接口。
- 映射散列对键进行散列。树映射用键的整体顺序对元素进行排序,并将其组织成搜索树。
- 散列或比较函数只能作用于 键,与键关联的值不能进行散列或比较。
- 键必须唯一,如果对同一键两次调用put方法,会覆盖(但返回的是上一个值)。
- 要迭代处理映射的键和值,最容易的方法是使用 forEach方法,可以提供一个接收键和值的 lambda表达式。如:Scores.forEach( (k,v) -> System.out.println( "key: " + k + ", value: " + v ) ) 。
- java.util.Map<K,V>
- V get( Object key ) //键可以为null。
- default V getOrDefault( Object key, V defaultValue )
- V put( K key, V value ) // 返回键的旧值,如果键没有出现过就返回null。
- void putAll( Map<? extends K, ? extends V> entries )
- boolean containsKey( Object key )
- boolean containsValue( Object value )
- default void forEach( BiConsumer<? super K, ? super V> action )
- java.util.HashMap<K,V>
- HashMap()
- HashMap(int initialCapacity)
- HashMap( int initialCapacity, float loadFactor )
- java.util.TreeMap<K,V>
- TreeMap()
- TreeMap( Comarator<? super K> c )
- TreeMap( Map<? extends K, ? extends V> entries )
- TreeMap( SortedMap<? extends K, ? extends V> entries )
- java.util.SortedMap<K,V>
- Comparator<? super K> comparator()
- K firstKey()
- K lastKey()
- 示例程序:(测试映射HashMap)
package map;
import java.util.*;
public class MapTest {
public static void main( String[] args ) {
Map<String,String> info = new HashMap<>();
info.put("144-25-5464","Amy Lee");
info.put("567-24-2546", "Harry Hacker");
info.put("157-62-7935", "Gary Cooper");
info.put("456-62-5527","Francesca Cruz");
System.out.println(info);
//remove
info.remove("567-24-2546");
//replace
info.put("456-62-5527", "Francesca Miller");
//look up
System.out.println(info.get("157-62-7935"));
//iterate through all entries
info.forEach((k,v) -> System.out.println("Key: " + k + ", value: " + v ) );
}
}
- 更新映射项:
- 处理映射时的一个难点就是更新映射项。特殊情况:键第一次出现的更新。
- 比如,处理一个单词出现的频率:
- counts.put( word, counts.getOrDefault(word,0) + 1 )
- 1.counts.putIfAbsent(word,0) 2.counts.put(word, counts.get(word) + 1 )
- merge方法。 counts.merge( word, 1, Integer::sum )
- java.util.Map<K,V>(以下为一些更新键值的方法,虽不甚常用)
- default V merge( K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction ) //如果key与一个非null值v关联,将函数应用到v和value,将key与结果关联,或者如果结果为null,则删除这个键。否则,将key与value关联,返回get(key)。
- default V compute( K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction ) ,将函数应用到key和 get(key),将key与结果关联,如果结果为null,则删除此键。
- computeIfPresent方法
- computeIfAbsent方法
- replaceAll方法
- 映射视图:
- 集合框架不认为映射本身是一个集合。不过,可以得到映射的视图(view)——实现了Collection接口或某个子接口。
- 有三种视图:键集、值集合(不是一个集,因为可重复) 以及 键/值 对集。
- Set<K> KeySet() //不是HashSet或TreeSet实现的。
- Collection<V> values()
- Set<Map.Entry<K,V>> entrySet() //如果想同时查看键和值,可以通过枚举条目(entries)来避免查找值。
- 如果在键值视图上调用迭代器的remove方法,实际上会从映射中删除这个 键和与它相关联的值。
- 可通过视图删但不可增。
- java.util.Map.Entry<K,V>
- K getKey()
- V getValue()
- V setValue(V newValue)
- 弱散列映射:
- 垃圾回收器跟踪活动的对象。只要映射的对象是活动的,其中所有的桶都是活动的,它们就不能被回收。
- 使用WeakHashMap时,当对键的唯一引用来自散列条目时,这一数据结构将与垃圾回收器协同工作一起删除 键/值对。
- 链接散列集 与 映射:
- LinkedHashSet 和 LinkedHashMap 类 可以记住插入元素项的顺序。
- 实现方法是:在原本HashSet 和 HashMap 的基础上,当条目插入到表中时,就并入一个双向链表之中。尔后就可以利用这个双向链表进行迭代。
- 可以构造一个用访问顺序的链接散列表,对条目进行迭代:
- 每次调用get或put,收到影响的条目都从当前的位置删除,并放到条目链表的尾部。
- LinkedHashMap<K,V>(initialCapacity, loadFactor, true) //true为访问顺序,false为插入顺序。
- 访问顺序对于实现 高速缓存的“最近最少使用”原则十分重要。
- 构造一个LinkedHashMap的子类,并覆盖 protected boolean removeEldestEntry( Map<K,V> eldest ) 方法,每当方法返回true时,就添加一个新条目,从而导致删除eldest条目。这个方法在条目添加到映射中之后调用。
- 枚举集与映射:
- EnumSet是一个枚举类型元素集的高效实现。
- 由于枚举类型只有有限个示例,所以EnumSet内部用位序列实现。如果对应的值在集中,则对应的位被置为1。
- EnumSet没有公共的构造器,可以使用静态工厂方法来构造这个集。
- 可以使用 Set接口 的常用方法来修改EnumSet。
- EnumMap是一个键类型为枚举类型的映射,它可以直接且高效地用一个 值数组 实现。如:EnumMap<Weekday,Employee> personInCharge = new EnumMap<>(Weekday.class) 。
- java.util.EnumSet<E extends Enum<E>>
- static <E extends Enum<E>> EnumSet<E> allOf( Class<E> enumType )
- noneOf
- range
- of
- java.util.EnumMap<K extends Enum<K>, V>
- EnumMap( Class<K> keyType )
- 标识散列映射:
- 在这个类中,键的散列值不是用 hashCode函数 计算而是用 System.identityHashCode方法 计算的。散列值只与对象的内存地址有关。
- 故 用 == 比较映射值而不用 equals 。
- IdentityHashMap 。