17.1 完整的容器分类方法

17.2 填充容器

import java.util.*;
class StringAddress {
private String s;
public StringAddress(String s) { this.s = s; }
public String toString() {
return super.toString() + " " + s;
}
}
public class FillingLists {
public static void main(String[] args) {
List<StringAddress> list= new ArrayList<StringAddress>(
Collections.nCopies(4, new StringAddress("Hello")));//创建并填充
System.out.println(list);
Collections.fill(list, new StringAddress("World!"));//只能用于替换已有的元素
System.out.println(list);
}
} /* Output: (Sample)
[StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello]
[StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!]
*///:~

17.2.1 一种Generator解决方案

所有的Collection子类型都有一个接受另一个Collection对象的构造器,用所接受的Collection对象种的元素来填充新的容器。

import java.util.*;
import net.mindview.util.*; class Government implements Generator<String> {
String[] foundation = ("strange women lying in ponds " +
"distributing swords is no basis for a system of " +
"government").split(" ");
private int index;
public String next() { return foundation[index++]; }
} public class CollectionDataTest {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<String>(
new CollectionData<String>(new Government(), 15));
System.out.println(set);
// Using the convenience method:
List<String> list=new ArrayList<>();
list.addAll(CollectionData.list(new Government(), 15));
System.out.println(list);
}
} /* Output:
[strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, system, of, government]
*///:~

17.2.2 Map生成器

Map适配器现在都可以使用各种不同的Generator、Iterator和常量值的组合来填充Map初始化对象:

package net.mindview.util;
import java.util.Iterator;
import java.util.LinkedHashMap;
public class MapData<K, V> extends LinkedHashMap<K, V> {
public MapData(Generator<Pair<K, V>> gen, int quantity) {
for(int i = 0; i < quantity; ++i) {
Pair<K, V> p = (Pair)gen.next();
this.put(p.key, p.value);
} }
public MapData(Generator<K> genK, Generator<V> genV, int quantity) {
for(int i = 0; i < quantity; ++i) {
this.put(genK.next(), genV.next());
} }
public MapData(Generator<K> genK, V value, int quantity) {
for(int i = 0; i < quantity; ++i) {
this.put(genK.next(), value);
} }
public MapData(Iterable<K> genK, Generator<V> genV) {
Iterator var4 = genK.iterator(); while(var4.hasNext()) {
K key = (Object)var4.next();
this.put(key, genV.next());
} }
public MapData(Iterable<K> genK, V value) {
Iterator var4 = genK.iterator(); while(var4.hasNext()) {
K key = (Object)var4.next();
this.put(key, value);
} }
public static <K, V> MapData<K, V> map(Generator<Pair<K, V>> gen, int quantity) {
return new MapData(gen, quantity);
}
public static <K, V> MapData<K, V> map(Generator<K> genK, Generator<V> genV, int quantity) {
return new MapData(genK, genV, quantity);
}
public static <K, V> MapData<K, V> map(Generator<K> genK, V value, int quantity) {
return new MapData(genK, value, quantity);
}
public static <K, V> MapData<K, V> map(Iterable<K> genK, Generator<V> genV) {
return new MapData(genK, genV);
}
public static <K, V> MapData<K, V> map(Iterable<K> genK, V value) {
return new MapData(genK, value);
}
}

下面是一个使用MapData的示例。LettersGenerator通过产生一个Iterator还实现了Iterable,通过这种方式,它可以被用来测试MapData.map()方式,而这些方法都需要用到Iterable:

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; class Letters implements Generator<Pair<Integer,String>>,
Iterable<Integer> {
private int size = 9;
private int number = 1;
private char letter = 'A';
public Pair<Integer,String> next() {
return new Pair<Integer,String>(
number++, "" + letter++);
}
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
public Integer next() { return number++; }
public boolean hasNext() { return number < size; }
public void remove() {
throw new UnsupportedOperationException();
}
};
}
} public class MapDataTest {
public static void main(String[] args) {
// Pair Generator:
print(MapData.map(new Letters(), 11));
// Two separate generators:
print(MapData.map(new CountingGenerator.Character(),
new RandomGenerator.String(3), 8));
// A key Generator and a single value:
print(MapData.map(new CountingGenerator.Character(),
"Value", 6));
// An Iterable and a value Generator:
print(MapData.map(new Letters(),
new RandomGenerator.String(3)));
// An Iterable and a single value:
print(MapData.map(new Letters(), "Pop"));
}
} /* Output:
{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J, 11=K}
{a=YNz, b=brn, c=yGc, d=FOW, e=ZnT, f=cQr, g=Gse, h=GZM}
{a=Value, b=Value, c=Value, d=Value, e=Value, f=Value}
{1=mJM, 2=RoE, 3=suE, 4=cUO, 5=neO, 6=EdL, 7=smw, 8=HLG}
{1=Pop, 2=Pop, 3=Pop, 4=Pop, 5=Pop, 6=Pop, 7=Pop, 8=Pop}
*///:~

17.3 Collection的功能方法

下面展示了Collection所有这些方法:

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class CollectionMethods {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.addAll(Countries.names(6));
c.add("ten");
c.add("eleven");
print(c);
// Make an array from the List:
Object[] array = c.toArray();
// Make a String array from the List:
String[] str = c.toArray(new String[0]);//将list转换为需要的数组
System.out.println("str="+Arrays.deepToString(str));
// Find max and min elements; this means
// different things depending on the way
// the Comparable interface is implemented:
print("Collections.max(c) = " + Collections.max(c));
print("Collections.min(c) = " + Collections.min(c));
// Add a Collection to another Collection
Collection<String> c2 = new ArrayList<String>();
c2.addAll(Countries.names(6));
c.addAll(c2);
print(c);
c.remove(Countries.DATA[0][0]);
print(c);
c.remove(Countries.DATA[1][0]);
print(c);
// Remove all components that are
// in the argument collection:
c.removeAll(c2);
print(c);
c.addAll(c2);
print(c);
// Is an element in this Collection?
String val = Countries.DATA[3][0];
print("c.contains(" + val + ") = " + c.contains(val));
// Is a Collection in this Collection?
print("c.containsAll(c2) = " + c.containsAll(c2));
Collection<String> c3 =
((List<String>)c).subList(3, 5);
// Keep all the elements that are in both
// c2 and c3 (an intersection of sets):
c2.retainAll(c3);
print(c2);
// Throw away all the elements
// in c2 that also appear in c3:
c2.removeAll(c3);
print("c2.isEmpty() = " + c2.isEmpty());
c = new ArrayList<String>();
c.addAll(Countries.names(6));
print(c);
c.clear(); // Remove all elements
print("after c.clear():" + c);
}
}

17.4 可选操作

执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。

接口是面向对象设计中的契约,它声明"无论你选择如何实现该接口,我保证你可以向该接口发送这些消息"。但是可选操作违反这个非常基本的原则,它声明调用某些方法将不会执行有意义的行为。

"未获支持的操作"这种方式可以实现Java容器类库一个重要目标:容器应该易学易用。未获支持的操作是一种特例,可以延迟到需要时在实现。

为了让你种方式能够工作:

  1. UnsupportedOperationException必须是一种罕见事件。对于大多数类来说,所有操作都应该可以工作,只有在特例中才会有未获支持的操作。
  2. 如果一个操作未获支持的,那么在实现接口的时候可能会导致 UnsupportedOperationException异常,而不是将产品程序交给客户以后才出现异常。它表示编程上有错误:使用了不正确的接口实现。
  3. 未获支持的操作只有在运行时才能检测到,因此它们表示动态类型检查。

17.4.1 未获支持的操作

当你用Arrays.aslist()将数组转换未List时,就会得到这样的容器。你可以通过使用Collections类中"不可修改"的方法,选择创建任何会抛出 UnsupportedOperationException的容器。

import java.util.*;

public class Unsupported {
static void test(String msg, List<String> list) {
System.out.println("--- " + msg + " ---");
Collection<String> c = list;
Collection<String> subList = list.subList(1,8);
// Copy of the sublist:
Collection<String> c2 = new ArrayList<String>(subList);
try { c.retainAll(c2); } catch(Exception e) {
System.out.println("retainAll(): " + e);
}
try { c.removeAll(c2); } catch(Exception e) {
System.out.println("removeAll(): " + e);
}
try { c.clear(); } catch(Exception e) {
System.out.println("clear(): " + e);
}
try { c.add("X"); } catch(Exception e) {
System.out.println("add(): " + e);
}
try { c.addAll(c2); } catch(Exception e) {
System.out.println("addAll(): " + e);
}
try { c.remove("C"); } catch(Exception e) {
System.out.println("remove(): " + e);
}
// The List.set() method modifies the value but
// doesn't change the size of the data structure:
try {
list.set(0, "X");
} catch(Exception e) {
System.out.println("List.set(): " + e);
}
}
public static void main(String[] args) {
List<String> list =
Arrays.asList("A B C D E F G H I J K L".split(" "));//返回固定尺寸的List
test("Modifiable Copy", new ArrayList<String>(list));
test("Arrays.asList()", list);//
test("unmodifiableList()",
Collections.unmodifiableList(
new ArrayList<String>(list)));//产生不可修改的列表,任何情况下都不可修改
}
} /* Output:
--- Modifiable Copy ---
--- Arrays.asList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
--- unmodifiableList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
List.set(): java.lang.UnsupportedOperationException
*///:~

Arrays.asList()会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作。任何会引起对底层数据结构尺寸进行修改的方法都会产生 UnsupportedOperationException异常,以表示对未获取支持操作的调用。

17.5 List基本功能方法

基本的LIst很容易使用:add()添加对象,get()一次取出一个元素,iterator()获取用于该序列的Iterator。

下面的例子每个方法都涵盖了不同动作:

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class Lists {
private static boolean b;
private static String s;
private static int i;
private static Iterator<String> it;
private static ListIterator<String> lit;
public static void basicTest(List<String> a) {//包含每个List都可执行的操作
a.add(1, "x"); // Add at location 1
a.add("x"); // Add at end
// Add a collection:
a.addAll(Countries.names(25));
// Add a collection starting at location 3:
a.addAll(3, Countries.names(25));
b = a.contains("1"); // Is it in there?
// Is the entire collection in there?
b = a.containsAll(Countries.names(25));
// Lists allow random access, which is cheap
// for ArrayList, expensive for LinkedList:
s = a.get(1); // Get (typed) object at location 1
i = a.indexOf("1"); // Tell index of object
b = a.isEmpty(); // Any elements inside?
it = a.iterator(); // Ordinary Iterator
lit = a.listIterator(); // ListIterator
lit = a.listIterator(3); // Start at loc 3
i = a.lastIndexOf("1"); // Last match
a.remove(1); // Remove location 1
a.remove("3"); // Remove this object
a.set(1, "y"); // Set location 1 to "y"
// Keep everything that's in the argument
// (the intersection of the two sets):
a.retainAll(Countries.names(25));
// Remove everything that's in the argument:
a.removeAll(Countries.names(25));
i = a.size(); // How big is it?
a.clear(); // Remove all elements
}
public static void iterMotion(List<String> a) {//使用Iterator遍历元素
ListIterator<String> it = a.listIterator();
b = it.hasNext();
b = it.hasPrevious();
s = it.next();
i = it.nextIndex();
s = it.previous();
i = it.previousIndex();
}
public static void iterManipulation(List<String> a) {//使用Iterator修改元素
ListIterator<String> it = a.listIterator();
it.add("47");
// Must move to an element after add():
it.next();
// Remove the element after the newly produced one:
it.remove();
// Must move to an element after remove():
it.next();
// Change the element after the deleted one:
it.set("47");
}
public static void testVisual(List<String> a) {//查看List的操作效果
print(a);
List<String> b = Countries.names(25);
print("b = " + b);
a.addAll(b);
a.addAll(b);
print(a);
// Insert, remove, and replace elements
// using a ListIterator:
ListIterator<String> x = a.listIterator(a.size()/2);
x.add("one");
print(a);
print(x.next());
x.remove();
print(x.next());
x.set("47");
print(a);
// Traverse the list backwards:
x = a.listIterator(a.size());
while(x.hasPrevious())
printnb(x.previous() + " ");
print();
print("testVisual finished");
}
// There are some things that only LinkedLists can do:
public static void testLinkedList() {//LinkedList专用的操作
LinkedList<String> ll = new LinkedList<String>();
ll.addAll(Countries.names(25));
print(ll);
// Treat it like a stack, pushing:
ll.addFirst("one");
ll.addFirst("two");
print(ll);
// Like "peeking" at the top of a stack:
print(ll.getFirst());
// Like popping a stack:
print(ll.removeFirst());
print(ll.removeFirst());
// Treat it like a queue, pulling elements
// off the tail end:
print(ll.removeLast());
print(ll);
}
public static void main(String[] args) {
// Make and fill a new list each time:
basicTest(
new LinkedList<String>(Countries.names(25)));
basicTest(
new ArrayList<String>(Countries.names(25)));
iterMotion(
new LinkedList<String>(Countries.names(25)));
iterMotion(
new ArrayList<String>(Countries.names(25)));
iterManipulation(
new LinkedList<String>(Countries.names(25)));
iterManipulation(
new ArrayList<String>(Countries.names(25)));
testVisual(
new LinkedList<String>(Countries.names(25)));
testLinkedList();
}
} /* (Execute to see output) *///:~

17.6 Set和存储顺序

类型 描述
Set(interface) 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。
HashSet 为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()
TreeSet 保持次序的Set,底层为树结构。使用它可以从set中提取有序的序列。元素必须实现Comparable接口
LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序。

使用特定的Set实现类型而必须定义的方法:

import java.util.*;

class SetType {
int i;
public SetType(int n) { i = n; }
public boolean equals(Object o) {
return o instanceof SetType && (i == ((SetType)o).i);
}
public String toString() { return Integer.toString(i); }
} class HashType extends SetType {
public HashType(int n) { super(n); }
public int hashCode() { return i; }
} class TreeType extends SetType
implements Comparable<TreeType> {
public TreeType(int n) { super(n); }
public int compareTo(TreeType arg) {
return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
}
} public class TypesForSets {
static <T> Set<T> fill(Set<T> set, Class<T> type) {
try {
for(int i = 0; i < 10; i++)
set.add(
type.getConstructor(int.class).newInstance(i));
} catch(Exception e) {
throw new RuntimeException(e);
}
return set;
}
static <T> void test(Set<T> set, Class<T> type) {
fill(set, type);
fill(set, type); // Try to add duplicates
fill(set, type);
System.out.println(set);
}
public static void main(String[] args) {
test(new HashSet<HashType>(), HashType.class);//实现hashCode确保排序并且不会重复
test(new LinkedHashSet<HashType>(), HashType.class);
test(new TreeSet<TreeType>(), TreeType.class);//实现Comparable接口排序并不会重复
// Things that don't work:
test(new HashSet<SetType>(), SetType.class);
test(new HashSet<TreeType>(), TreeType.class);
test(new LinkedHashSet<SetType>(), SetType.class);
test(new LinkedHashSet<TreeType>(), TreeType.class);
try {
test(new TreeSet<SetType>(), SetType.class);
} catch(Exception e) {
System.out.println(e.getMessage());
}
try {
test(new TreeSet<HashType>(), HashType.class);
} catch(Exception e) {
System.out.println(e.getMessage());
}
}
} /* Output: (Sample)
[2, 4, 9, 8, 6, 1, 3, 7, 5, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 5, 0, 8, 8, 8, 6, 5, 1]
[0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable
*///:~

17.6.1 SortedSet

SortedSet可以确保元素处于排序状态下:


import java.util.*;
import static net.mindview.util.Print.*; public class SortedSetDemo {
public static void main(String[] args) {
SortedSet<String> sortedSet = new TreeSet<String>();
Collections.addAll(sortedSet,
"one two three four five six seven eight"
.split(" "));
print(sortedSet);
String low = sortedSet.first();//返回第一个元素
String high = sortedSet.last();//返回最后一个元素
print(low);
print(high);
Iterator<String> it = sortedSet.iterator();
for(int i = 0; i <= 6; i++) {
if(i == 3) low = it.next();
if(i == 6) high = it.next();
else it.next();
}
print(low);
print(high);
print(sortedSet.subSet(low, high));//生成此Set子集,从low到high
print(sortedSet.headSet(high));//生成此Set子集,小于high
print(sortedSet.tailSet(low));//生成此Set子集,大于等于low
}
}

17.7 队列

Queue在Java SE5中仅有的两个实现LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。

Queue实现的大部分操作的基本示例:

import java.util.concurrent.*;
import java.util.*;
import net.mindview.util.*; public class QueueBehavior {
private static int count = 10;
static <T> void test(Queue<T> queue, Generator<T> gen) {
for(int i = 0; i < count; i++)
queue.offer(gen.next());
while(queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}
static class Gen implements Generator<String> {
String[] s = ("one two three four five six seven " +
"eight nine ten").split(" ");
int i;
public String next() { return s[i++]; }
}
public static void main(String[] args) {
test(new LinkedList<String>(), new Gen());
test(new PriorityQueue<String>(), new Gen());
test(new ArrayBlockingQueue<String>(count), new Gen());
test(new ConcurrentLinkedQueue<String>(), new Gen());
test(new LinkedBlockingQueue<String>(), new Gen());
test(new PriorityBlockingQueue<String>(), new Gen());
}
} /* Output:
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
*///:~

17.7.1 优先级队列

import java.util.*;

class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {
static class ToDoItem implements Comparable<ToDoItem> {
private char primary;
private int secondary;
private String item;
public ToDoItem(String td, char pri, int sec) {
primary = pri;
secondary = sec;
item = td;
}
public int compareTo(ToDoItem arg) {
if(primary > arg.primary)
return +1;
if(primary == arg.primary)
if(secondary > arg.secondary)
return +1;
else if(secondary == arg.secondary)
return 0;
return -1;
}
public String toString() {
return Character.toString(primary) +
secondary + ": " + item;
}
}
public void add(String td, char pri, int sec) {
super.add(new ToDoItem(td, pri, sec));
}
public static void main(String[] args) {
ToDoList toDoList = new ToDoList();
toDoList.add("Empty trash", 'C', 4);
toDoList.add("Feed dog", 'A', 2);
toDoList.add("Feed bird", 'B', 7);
toDoList.add("Mow lawn", 'C', 3);
toDoList.add("Water lawn", 'A', 1);
toDoList.add("Feed cat", 'B', 1);
while(!toDoList.isEmpty())System.out.println(toDoList.remove());
}
} /* Output:
A1: Water lawn
A2: Feed dog
B1: Feed cat
B7: Feed bird
C3: Mow lawn
C4: Empty trash
*///:~

17.7.2 双向队列

双向队列就像是一个队列,但是以可以在任何一端添加或移除元素。

实现一个双向队列:

package net.mindview.util;
import java.util.LinkedList;
public class Deque<T> {
private LinkedList<T> deque = new LinkedList();
public Deque() {
}
public void addFirst(T e) {
this.deque.addFirst(e);
}
public void addLast(T e) {
this.deque.addLast(e);
}
public T getFirst() {
return this.deque.getFirst();
}
public T getLast() {
return this.deque.getLast();
}
public T removeFirst() {
return this.deque.removeFirst();
}
public T removeLast() {
return this.deque.removeLast();
}
public int size() {
return this.deque.size();
}
public String toString() {
return this.deque.toString();
}
}

测试Deque:

import net.mindview.util.*;
import static net.mindview.util.Print.*; public class DequeTest {
static void fillTest(Deque<Integer> deque) {
for(int i = 20; i < 27; i++)
deque.addFirst(i);
for(int i = 50; i < 55; i++)
deque.addLast(i);
}
public static void main(String[] args) {
Deque<Integer> di = new Deque<Integer>();
fillTest(di);
print(di);
while(di.size() != 0)
printnb(di.removeFirst() + " ");
print();
fillTest(di);
while(di.size() != 0)
printnb(di.removeLast() + " ");
}
} /* Output:
[26, 25, 24, 23, 22, 21, 20, 50, 51, 52, 53, 54]
26 25 24 23 22 21 20 50 51 52 53 54
54 53 52 51 50 20 21 22 23 24 25 26
*///:~

17.8 理解Map

映射表(关联数组)的基本思想是它维护的是键-值对关联,因此你可以使用建来查找值。标准的Java类库中包含了Map的几种基本实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。它们都有同样的基本接口Map。

下面对Map简单实现:

import static net.mindview.util.Print.*;
public class AssociativeArray<K,V> {
private Object[][] pairs;
private int index;
public AssociativeArray(int length) {
pairs = new Object[length][2];
}
public void put(K key, V value) {
if(index >= pairs.length)
throw new ArrayIndexOutOfBoundsException();
pairs[index++] = new Object[]{ key, value };
}
@SuppressWarnings("unchecked")
public V get(K key) {
for(int i = 0; i < index; i++)
if(key.equals(pairs[i][0]))
return (V)pairs[i][1];
return null; // Did not find key
}
public String toString() {
StringBuilder result = new StringBuilder();
for(int i = 0; i < index; i++) {
result.append(pairs[i][0].toString());
result.append(" : ");
result.append(pairs[i][1].toString());
if(i < index - 1)
result.append("\n");
}
return result.toString();
}
public static void main(String[] args) {
AssociativeArray<String,String> map =
new AssociativeArray<String,String>(6);
map.put("sky", "blue");
map.put("grass", "green");
map.put("ocean", "dancing");
map.put("tree", "tall");
map.put("earth", "brown");
map.put("sun", "warm");
try {
map.put("extra", "object"); // Past the end
} catch(ArrayIndexOutOfBoundsException e) {
print("Too many objects!");
}
print(map);
print(map.get("ocean"));
}
}

17.8.1 性能

HashMap使用了特殊的值,称作散列码,来取代对健的缓慢搜索。

名称 描述
HashMap Map基于散列表的实现。插入和查询键值对的开销是固定的。可以i通过构造器设置容量和负载因子,以调整容器的性能
LinkedHashMap 类似于HashMap,但是迭代遍历它时,取得键值对的顺序是其插入次序,或者是最近最少使用(LRU)次序。只比HashMap慢一点。而在迭代访问时反而更快,因为它使用链表维护内部次序
TreeMap 基于红黑树的实现,是唯一带有subMap()的Map
WeakHashMap 弱键映射,允许释放映射所指向的对象
ConcurrentHashMap 一种线程安全的Map,不涉及同步锁
IdentityHashMap 使用==代替equals()对键进行比较的散列映射

散列是映射中存储元素时最常用的方式。

对Map中使用的键要求与对Set中的元素要求一致。

展示通过Map接口可用的操作:

import java.util.concurrent.*;
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class Maps {
public static void printKeys(Map<Integer,String> map) {
printnb("Size = " + map.size() + ", ");
printnb("Keys: ");
print(map.keySet()); // Produce a Set of the keys
}
public static void test(Map<Integer,String> map) {
print(map.getClass().getSimpleName());
map.putAll(new CountingMapData(25));
// Map has 'Set' behavior for keys:
map.putAll(new CountingMapData(25));
printKeys(map);
// Producing a Collection of the values:
printnb("Values: ");
print(map.values());
print(map);
print("map.containsKey(11): " + map.containsKey(11));
print("map.get(11): " + map.get(11));
print("map.containsValue(\"F0\"): "
+ map.containsValue("F0"));
Integer key = map.keySet().iterator().next();
print("First key in map: " + key);
map.remove(key);
printKeys(map);
map.clear();
print("map.isEmpty(): " + map.isEmpty());
map.putAll(new CountingMapData(25));
// Operations on the Set change the Map:
map.keySet().removeAll(map.keySet());
print("map.isEmpty(): " + map.isEmpty());
}
public static void main(String[] args) {
test(new HashMap<Integer,String>());
test(new TreeMap<Integer,String>());
test(new LinkedHashMap<Integer,String>());
test(new IdentityHashMap<Integer,String>());
test(new ConcurrentHashMap<Integer,String>());
test(new WeakHashMap<Integer,String>());
}
}

17.8.2 SortedMap

使用SortedMap(TreeMap是其现阶段唯一实现),可以确保键处于排序状态。

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class SortedMapDemo {
public static void main(String[] args) {
TreeMap<Integer,String> sortedMap =
new TreeMap<Integer,String>(new CountingMapData(10));
print(sortedMap);
Integer low = sortedMap.firstKey();
Integer high = sortedMap.lastKey();
print(low);
print(high);
Iterator<Integer> it = sortedMap.keySet().iterator();
for(int i = 0; i <= 6; i++) {
if(i == 3) low = it.next();
if(i == 6) high = it.next();
else it.next();
}
print(low);
print(high);
print(sortedMap.subMap(low, high));
print(sortedMap.headMap(high));
print(sortedMap.tailMap(low));
}
}

17.8.3 LinkedHashMap

为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。

可以在构造器中设定LinkedHashMap,使之采用基于访问最近最少使用算法,于是没有被访问过的元素就会出现在队列的前面。

import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<Integer,String> linkedMap =
new LinkedHashMap<Integer,String>(
new CountingMapData(9));
print(linkedMap);
// Least-recently-used order:
linkedMap =
new LinkedHashMap<Integer,String>(16, 0.75f, true);
linkedMap.putAll(new CountingMapData(9));
print(linkedMap);
for(int i = 0; i < 6; i++) // Cause accesses:
linkedMap.get(i);
print(linkedMap);
linkedMap.get(0);
print(linkedMap);
}
} /* Output:
{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}
{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}
{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}
{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}
*///:~

17.9 散列与散列码

当自己创建用作HashMap的键的类,有可能会忘记在其中放置必须的方法,而这是通常会犯的一个错误:

public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
public String toString() {
return "Groundhog #" + number;
}
}
import java.util.*;

public class Prediction {
private static Random rand = new Random(47);
private boolean shadow = rand.nextDouble() > 0.5;
public String toString() {
if(shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
} ///:~
import java.lang.reflect.*;
import java.util.*;
import static net.mindview.util.Print.*; public class SpringDetector {
// Uses a Groundhog or class derived from Groundhog:
public static <T extends Groundhog>
void detectSpring(Class<T> type) throws Exception {
Constructor<T> ghog = type.getConstructor(int.class);// 通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象
Map<Groundhog,Prediction> map =
new HashMap<Groundhog,Prediction>();
for(int i = 0; i < 10; i++)
map.put(ghog.newInstance(i), new Prediction());
print("map = " + map);
Groundhog gh = ghog.newInstance(3);
print("Looking up prediction for " + gh);
if(map.containsKey(gh))
print(map.get(gh));
else
print("Key not found: " + gh);
}
public static void main(String[] args) throws Exception {
detectSpring(Groundhog.class);
}
} /* Output:
map = {Groundhog #3=Early Spring!, Groundhog #7=Early Spring!, Groundhog #5=Early Spring!, Groundhog #9=Six more weeks of Winter!, Groundhog #8=Six more weeks of Winter!, Groundhog #0=Six more weeks of Winter!, Groundhog #6=Early Spring!, Groundhog #4=Six more weeks of Winter!, Groundhog #1=Six more weeks of Winter!, Groundhog #2=Early Spring!}
Looking up prediction for Groundhog #3
Key not found: Groundhog #3
*///:~

从上面结果可以看到,在map中无法找到3这个key,那是因为这里Groundhog 没有实现hashCode()和equals()这两个方法,默认使用Object中的这两个方法进行比较,Object中的hashCode()是根据对象地址计算出来的。

重载实现两个方法即可:

public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
public String toString() {
return "Groundhog #" + number;
} @Override
public int hashCode()
{
return number;
} @Override
public boolean equals(Object obj)
{
return obj instanceof Groundhog && (number==((Groundhog)obj).number);
} ///:~

ashMap使用equals()判断当前的键是否与表中存在的键相同。

正确的equals()方法必须满足下面5个条件:

  • 自反性。对任意x,x.equals(x)一定返回true。
  • 对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
  • 传递性。对任意x,y,z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。
  • 一致性。对任意x和y,如果对象中用于等价比较信息没有改变,那么无论调用x.equals(y)多少次,返回结果保持一致。
  • 对任何不是null的x,x.equals(null)一定返回false。

17.9.1 理解hashCode()

使用散列的目的在于:想要使用一个对象来查找另一个对象。

下面的示例用一对ArrayLists实现了一个Map:

import java.util.*;
import net.mindview.util.*; public class SlowMap<K,V> extends AbstractMap<K,V> {//使用索引位置来关联表
private List<K> keys = new ArrayList<K>();
private List<V> values = new ArrayList<V>();
public V put(K key, V value) {
V oldValue = get(key); // The old value or null
if(!keys.contains(key)) {
keys.add(key);
values.add(value);
} else
values.set(keys.indexOf(key), value);
return oldValue;
}
public V get(Object key) { // key is type Object, 这里使用Object是因为泛型引入太晚导致
if(!keys.contains(key))
return null;
return values.get(keys.indexOf(key));
}
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> set= new HashSet<Map.Entry<K,V>>();
Iterator<K> ki = keys.iterator();
Iterator<V> vi = values.iterator();
while(ki.hasNext())
set.add(new MapEntry<K,V>(ki.next(), vi.next()));
return set;
}
public static void main(String[] args) {
SlowMap<String,String> m= new SlowMap<String,String>();
m.putAll(Countries.capitals(15));
System.out.println(m);
System.out.println(m.get("BULGARIA"));
System.out.println(m.entrySet());
}
}

Map.Entry是一个接口,用来描述依赖于实现的结构,如果要创建自己的Map类型,就必须同时定义Map.Entry的实现:

import java.util.*;

public class MapEntry<K,V> implements Map.Entry<K,V> {
private K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
public V setValue(V v) {
V result = value;
value = v;
return result;
}
public int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public boolean equals(Object o) {
if(!(o instanceof MapEntry)) return false;
MapEntry me = (MapEntry)o;
return
(key == null ?
me.getKey() == null : key.equals(me.getKey())) &&
(value == null ?
me.getValue()== null : value.equals(me.getValue()));
}
public String toString() { return key + "=" + value; }
}

17.9.2 为速度而散列

散列的价值在于速度:散列使得查询得以快速进行。

散列跟进一步,它将键保持在某处,以便能够很快找到。存储一组元素最快的数据结构就是数组,所以使用它来表示键的信息。

数组不能调整容量,但我们不希望键的数量被数组的容量限制。

解决办法:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码。

为解决数组容量被固定的问题,不同的键可以产生相同的下标。也就是说,可能会有冲突。因此,数组多大就不重要了,任何键总能在数组中找到位置。

于是查询一个值得过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突,那就是一个完美散列函数了。但是这种情况只是特例。冲突有外部链接处理:数组并不直接保存值,而是保存值得list。然后对list中的值使用equals()方法进行线性查询。

public class SimpleHashMap<K,V> extends AbstractMap<K,V> {
// Choose a prime number for the hash table
// size, to achieve a uniform distribution:
static final int SIZE = 997;
// You can't have a physical array of generics,
// but you can upcast to one:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K,V>>[] buckets =
new LinkedList[SIZE];
public V put(K key, V value) {
V oldValue = null;
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null)
buckets[index] = new LinkedList<MapEntry<K,V>>();
LinkedList<MapEntry<K,V>> bucket = buckets[index];
MapEntry<K,V> pair = new MapEntry<K,V>(key, value);
boolean found = false;
ListIterator<MapEntry<K,V>> it = bucket.listIterator();
while(it.hasNext()) {
MapEntry<K,V> iPair = it.next();
if(iPair.getKey().equals(key)) {
oldValue = iPair.getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if(!found)
buckets[index].add(pair);
return oldValue;
}
public V get(Object key) {
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null) return null;
for(MapEntry<K,V> iPair : buckets[index])
if(iPair.getKey().equals(key))
return iPair.getValue();
return null;
}
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> set= new HashSet<Map.Entry<K,V>>();
for(LinkedList<MapEntry<K,V>> bucket : buckets) {
if(bucket == null) continue;
for(MapEntry<K,V> mpair : bucket)
set.add(mpair);
}
return set;
}
public static void main(String[] args) {
SimpleHashMap<String,String> m =
new SimpleHashMap<String,String>();
m.putAll(Countries.capitals(25));
System.out.println(m);
System.out.println(m.get("ERITREA"));
System.out.println(m.entrySet());
}
}

17.9.3 覆盖hashCode()

设计hashCode()最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。

下面String为例。String有个特点:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域:

public class StringHashCode {
public static void main(String[] args) {
String[] hellos = "Hello Hello".split(" ");
System.out.println(hellos[0].hashCode());
System.out.println(hellos[1].hashCode());
}
} /* Output: (Sample)
69609650
69609650
*///:~

对于String而言,hashCode()明显是基于String的内容的。

hashCode()的散列码必须是基于内容生成,不必独一无二,但是通过hashCOde()和equals()必须能够完全确定对象。

怎样写出一份像样的hashCode基本指导:

  1. 给int变量result赋予某个非零值常量。例如17
  2. 为对象内每个有意义的域f计算一个int散列码c:

    |域类型|计算|

    |:--

    Java编程思想之十七 容器深入研究的更多相关文章

    1. Java编程思想——第17章 容器深入研究 读书笔记(三)

      七.队列 排队,先进先出. 除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: ad ...

    2. Java编程思想——第17章 容器深入研究(two)

      六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add ...

    3. JAVA编程思想读书笔记(二)--容器

      接上篇JAVA编程思想读书笔记(一) 第八章.对象的容纳 No1: java提供了四种类型的集合类:Vector(矢量).BitSet(位集).Stack(堆栈).Hashtable(散列表) No2 ...

    4. JAVA编程思想——分析阅读

      需要源码.JDK1.6 .编码风格参考阿里java规约 7/12开始 有点意识到自己喜欢理论大而泛的模糊知识的学习,而不喜欢实践和细节的打磨,是因为粗心浮躁导致的么? cron表达式使用 设计能力.领 ...

    5. 《Java编程思想》读书笔记(三)

      前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十一章到第十六章的内容,这一次 ...

    6. Java编程思想(11~17)

      [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第十一章 持有对象 11.1 泛型和类型安全的容器>eg: List<St ...

    7. Java编程思想(后)

      Java编程思想(后) 持有对象 如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序. Java中的库基本类型: List, Set, Queue和Map --- 称为集 ...

    8. 《Java编程思想第四版完整中文高清版.pdf》-笔记

      D.2.1 安插自己的测试代码 插入下述“显式”计时代码,对程序进行评测: long start = System.currentTimeMillis(); // 要计时的运算代码放在这儿 long ...

    9. Java编程思想(第4版) 中文清晰PDF完整版

      Java编程思想(第4版) 中文清晰PDF完整版 [日期:2014-08-11] 来源:Linux社区  作者:Linux [字体:大 中 小]     <Java编程思想>这本书赢得了全 ...

    随机推荐

    1. Spring Boot2(五):使用Spring Boot结合Thymeleaf模板引擎使用总结

      一.Thymeleaf概述 一般来说,常用的模板引擎有JSP.Velocity.Freemarker.Thymeleaf . SpringBoot推荐的 Thymeleaf – 语法更简单,功能更强大 ...

    2. JAVA 从一个List里删除包含另一个List的数据

      /** * 从listA里删除listB里有的数据 * @param listA * @param listB * @return */ public static List<String> ...

    3. POJ 1741:Tree(树上点分治)

      题目链接 题意 给一棵边带权树,问两点之间的距离小于等于K的点对有多少个. 思路 <分治算法在树的路径问题中的应用> 图片转载于http://www.cnblogs.com/Paul-Gu ...

    4. BZOJ 2435:[Noi2011]道路修建(树型DP)

      http://www.lydsy.com/JudgeOnline/problem.php?id=2435 题意:中文题意. 思路:很简单的树形DP,sz记录儿子有多少个和cur记录走的哪条弧,然后直接 ...

    5. 不使用 ASR 将虚机还原到另一个数据中心

      背景 在 Azure 上可能会遇到一个场景是将一台虚机搬到另一台数据中心,在不借助 ASR 的情况下我们该如何做? 因为 ASR 在云上更多的场景是用于灾备到异地.对于虚机的相关信息主要的是磁盘和网络 ...

    6. django执行mysql恢复的时候出现“The request's session was deleted before the request completed. The user may have logged out in a concurrent request, for example.”

      版本: django:2.1.7 python:3.7 mysql:5.7.25 今天在用django做mysql备份和恢复的时候,备份没问题,恢复时出现如下错误提示: The request's s ...

    7. 【深入浅出-JVM】(6):栈帧.

      代码 package com.mousycoder.mycode.happy_jvm; /** * @version 1.0 * @author: mousycoder * @date: 2019-0 ...

    8. Java编程思想:内部类其他知识点

      public class Test { public static void main(String[] args) { // InheritInner.test(); // EggTest.test ...

    9. Zeppelin0.5.6使用spark解释器

      Zeppelin为0.5.6 Zeppelin默认自带本地spark,可以不依赖任何集群,下载bin包,解压安装就可以使用. 使用其他的spark集群在yarn模式下. 配置: vi zeppelin ...

    10. elasticsearch5.4集群超时

      四个节点,有两个是新增加的节点,两个老节点间组成集群没有问题,新增加了两个节点,无论是四个组成集群 # --------------------------------- Discovery ---- ...