欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Thking in java(第四版)-查缺补漏(第17章)

程序员文章站 2024-03-17 17:40:46
...

背景

继续查缺补漏,加油

1.Java容器类库简化图

Thking in java(第四版)-查缺补漏(第17章)

2.填充容器

(1).Collections.fill()方法只复制同一个对象 引用来填充容器,只对List对象有用。

(2).享元:可以在普通的解决方案需要过多的对象,或产生不同对象太占用空间时使用。

享元模式使得对象的一部分被具体化,因此,与对象中的所有事物都包含在对象内部不

同,我们可以在更加高效的外部表中查找对象的一部分或整体。

(3).为了创建只读Map,可以继承AbstractMap并实现entrySet()。为了创建只读的Set,

可以继承AbstracSet并实现iterator()和size()。

(4)创建只读List,继承AbstractList并实现get()和size()。例如:

package tools;
import java.util.*;
public class CountingIntegerList extends AbstractList<Integer> {
	private int size;
	public CountingIntegerList(int size){
		this.size=size<0?0:size;
	}
	public Integer get(int index){
		return Integer.valueOf(index);
	}
	public int size(){	return size;}
	public static void main(String[] args){
		System.out.println(new CountingIntegerList(30));
	}
}

(5)下面的示例使用了享元的模式:Map.Entry对象只存储了它的索引,而不是实际的键和值。

EntrySet.Iterator,每个迭代器只有一个Map.Entry,而不是为每个数据对都创建Map.Entry。

Entry对象被用数据的视窗,它只包含静态字符串数组的引用。

package tools;
import java.util.*;
public class CountingMapData extends AbstractMap<Integer,String> {
	private int size;
	private static String[] chars=
			"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ");
	public CountingMapData(int size){
		if(size<0) this.size=0;
		this.size=size;
	}
	private static class Entry implements Map.Entry<Integer, String>{
		int index;
		Entry(int index){this.index=index;}
		public boolean equals(Object o){
			return Integer.valueOf(index).equals(o);
		}
		public Integer getKey(){	return index;}
		public String getValue(){
			return chars[index%chars.length]+Integer.toString(index/chars.length);
		}
		public String setValue(String value){
			throw new UnsupportedOperationException();
		}
		public int hashCode(){
			return Integer.valueOf(index).hashCode();
		}
	}
	static class EntrySet extends AbstractSet<Map.Entry<Integer,String>>{
		private int size;
		EntrySet(int size){
			if(size<0)
				this.size=0;
			this.size=size;
		}
		public int size(){return size;}
		private class Iter implements Iterator<Map.Entry<Integer,String>>{
			private Entry entry=new Entry(-1);
			public boolean hasNext(){
				return entry.index<size-1;
			}
			public Map.Entry<Integer,String> next(){
				entry.index++;
				return entry;
			}
			public void remove(){
				throw new UnsupportedOperationException();
			}
		}
		public Iterator<Map.Entry<Integer, String>> iterator(){
			return new Iter();
		}
	}
	public Set<Map.Entry<Integer,String>> entrySet(){
		return new EntrySet(size);
	}

	public static void main(String[] args){
		System.out.println(new CountingMapData(60));
	}
}

3.可选操作

执行各种不同的添加和移除的方法在Collection接口中都是可选操作,这意味着实现类

并不需要为这些方法提供功能定义。这样做可以防止在设计中出现接口爆炸的情况。

如果一个操作未获支持,那么实现接口的时候可能就会导致UnsupportedOperationException。

4.Set和存储顺序

Thking in java(第四版)-查缺补漏(第17章)

例如:

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));
	}
}

SortedSet 按对象的比较函数对元素排序。

5.队列

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

优先级队列:

package containers;
import java.util.*;
public class Ex11 extends PriorityQueue<Ex11.Ex> {
	public static Random rand=new Random(47);
	static class Ex implements Comparable<Ex>{
		private Integer i;
		private char pri;
		private int sec;
		public Ex(Integer i,char pri,int sec){
			this.i=i;
			this.pri=pri;
			this.sec=sec;
		}
		@Override
		public int compareTo(Ex o) {
			if(pri>o.pri)
				return +1;
			if(pri==o.pri)
				if(sec>o.sec)
					return +1;
				else if(sec==o.sec)
					return 0;
			return -1;
		}
		public String toString(){
			return Character.toString(pri)+sec+": "+i.toString();
		}
	}
	public void add(Integer in,char priority,int second){
		super.add(new Ex(in,priority,second));
	}
	public static void main(String[] args){
		Ex11 e=new Ex11();
		e.add(rand.nextInt(100),'A',2);
		e.add(rand.nextInt(100),'A',3);
		while(!e.isEmpty())
			System.out.println(e.poll());
	}
}

LinkedList支持双向队列

6.Map

Thking in java(第四版)-查缺补漏(第17章)

 get()中使用线性搜索,执行速度会很慢,而HashMap使用了特殊的值,称作散列码。

HashMap就是使用对象的hashCode()进行快速查询,能提高性能。

SortedMap(TreeMap是其实现),可以确保键处于排序状态。

使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()。

LinkedHashMap,才构造器中设置,使它采用基于访问的最近最少使用算法(LRU),

没有访问过的元素就会出现在队列前面。可以应用在需要定期清理元素以节省空间的程序。

例如:

package containers;
import java.util.*;
import tools.*;
import static tools.Print.*;
public class LinkedHashMapDemo {
	public static void main(String[] args){
		LinkedHashMap<Integer,String> linkedMap=new LinkedHashMap<Integer,String>(new CountingMapData(9));
		print(linkedMap);
		linkedMap=new LinkedHashMap<Integer,String>(16,0.75f,true);
		linkedMap.putAll(new CountingMapData(9));
		print(linkedMap);
		for(int i=0;i<6;i++)
			linkedMap.get(i);
		print(linkedMap);
		linkedMap.get(0);
		print(linkedMap);
	}
	
}

7.equals()

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

(1)自反性:对任意x,x.equals(x) 一定返回true;

(2)对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true;

(3)传递性:对任意x,y,z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true

(4)一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,

返回的结果应该保持一致,要么一直是true,要么一直是false。

(5)对任何不是null的x,x.equals(null)一定返回false。

8.散列与散列码

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

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

(2)散列将键保存在某处,存储一组元素最快的数据结构是数组,所以用它来表示键的信息。

如果键的数量被数组容量限制了,该怎么办?

数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是

散列码,由hashCode()方法生成,也称为散列函数。有时候会有冲突,数组多大已经不重要

了,任何键总能在数组中找到它的位置。

查询一个值的过程首先是计算散列码,然后使用散列码查询数组。

通常冲突由外部链接处理:数组并不直接保存值,而是保存值得list,然后对list中的值使用equals()

方法进行线性查询。这部分查询会比较慢,但是如果散列函数好的话,数组的每个位置只有较少的

值。

下面是散列原理的实现:

package containers;
import java.util.*;
import tools.*;
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);
				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.put("a","A");
		m.put("b","B");
		System.out.println(m);
		System.out.println(m.get("b"));
	}
}

散列表中的“槽位”(slot)通常称为桶位(bucket) ,为使散列分布均匀,桶的数量使用质数。

为了自动处理冲突,使用了一个LinkedList的数组,每一个新的元素只是直接添加到list末尾的

某个特定桶位中。get和put使用相同的方式计算在buckets数组中的索引。

(3)好的hashCode()应该产生分布均匀的散列码。

建议:

1.给int变量result赋予某个非零值常量;

2.为对象内每个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c:

Thking in java(第四版)-查缺补漏(第17章)

3.合并计算得到的散列码: result=37*result+c;

4.返回result

5.检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。

例如:

public class CountedString {
	private static List<String> created=new ArrayList<String>();
	private String s;
	private char c;
    private int id=0;
	public int hashCode(){
		//The very simple approach:
		//return s.hashCode()*id:
		//using Joshua Bloch's recipe:
		int result=17;
		result=37*result+s.hashCode();
		result=37*result+(int)c;
		result=37*result+id;
		return result;
	}
}

9.选择接口的不同实现

容器之间的区别通常归结为由什么在背后“支持”它们。

剖析器可以把性能分析工作做得比较好,java提供了一个剖析器。

(1)ArrayList底层由数组支持;而LinkedList是由双向链表实现的,因此要经常在表中插入或删除元素

就用LinkedList,否则就使用ArrayList

(2)HashSet,查询最快;LinkedHashSet保持元素插入次序;TreeSet基于TreeMap,生成一个总是处于

排序状态的Set。注意,对于插入操作,LinkedHashSet比HashSet的代价更高;这是由维护链表所带来

额外开销造成的。

(3)Hashtable的性能大体上与HashMap相当,因为HashMap是用来替代Hashtable的,因此它们使用了相

同的底层存储和查找机制。当时用Map时,第一选择是HashMap,只有在需要Map保持有序时,才需要使

用TreeMap。LinkedHashMap在插入时比HashMap慢一点,因为它维护散列数据结构的同时还要维护链表。

10.HashMap性能因子

我们可以通过手工调整HashMap来提高其性能。

容量:表中的桶位数。

初始容量:表在创建时所拥有的桶位数。HashMap和HashSet都允许你指定初始容量。

尺寸:表中当前存储的项数。

负载因子:尺寸/容量。空表的负载因子是0,而半满表的负载因子是0.5,以此类推。负载轻的表产生冲突的

可能性小,因此对于插入和查找都是最理想的。HashMap和HashSet都具有允许你指定负载因子的构造器,表

示当负载情况达到负载因子的水平时,容器将自动增加其容量(桶位数),实现方式是使容量大致加倍,并重

新将现有对象分布到新的桶位集中(这被称为再散列)。

HashMap使用的默认负载因子是0.75。更高的负载因子可以降低表所需的空间,当时会增加查找代价。所以你可

以根据需要创建一个具有恰当大小的初始容量,从而避免自动再散列的开销。

11.colletions

(1)设定Collection或Map为不可修改:

Collection<String> c=Collections.unmodifiableCollection(new ArrayList<String>(data));

(2)Collection或Map的同步控制:

Collection<String> c=Collections.synchronizedCollection(new ArrayList<String>());

(3)使用Collections.enumration()方法从Collection生成一个Enumberation

Enumration<String> e=v.elements();
e=Collections.enumeration(new ArrayList<String>());

12.持有引用

java.lang.ref类库包含了一组类,为垃圾回收提供了更大的灵活性。有三个继承自抽象类Reference的类:

SoftReference,WeakReference,PhantomReference。

如果想继续持有对某个对对象的引用,希望以后还能够访问到该对象,但是也希望能够允许垃圾回收器释

放它,这是就应该使用Reference对象。以Reference对象作为你和普通引用之间的媒介(代理),另外不

能有普通的引用指向那个对象。因为如果垃圾回收器发现某个对象通过普通引用是可获得的,该对象就不会

释放。

(1)SoftReference用来实现内存敏感的高速缓存。

(2)WeakReference是为实现“规范映射”而设计的,它不妨碍垃圾回收期回收映射的“键”(或“值”)。“规范映射”

中对象的实例可以在程序的多次被同时使用,以节省存储空间。

(3)Phantomreference用来调度回收前的清理工作,比java终止机制更灵活

package containers;
import java.lang.ref.*;
import java.util.*;
class VeryBig{
	private static final int SIZE=10000;
	private long[] la=new long[SIZE];
	private String ident;
	public VeryBig(String id){	ident=id;}
	public String toString(){	return ident;}
	protected void finalize(){
		System.out.println("Finalizing "+ident);
	}
}
public class References {
	private static ReferenceQueue<VeryBig> rq=new ReferenceQueue<VeryBig>();
	public static void checkQueue(){
		Reference<? extends VeryBig> inq=rq.poll();
		if(inq!=null)
			System.out.println("In queue: "+inq.get());
	}
	public static void main(String[] args){
		int size=10;
		SoftReference<VeryBig> s=new SoftReference<VeryBig>(new VeryBig("soft"));
		WeakReference<VeryBig> w=new WeakReference<VeryBig>(new VeryBig("Weak"));
		System.gc();
		LinkedList<PhantomReference<VeryBig>> pa=new 
                LinkedList<PhantomReference<VeryBig>>();
		for(int i=0;i<size;i++){
			pa.add(new PhantomReference<VeryBig>(new VeryBig("Phantom"+i),rq));
			System.out.println("just created: "+pa.getLast());
			checkQueue();
			
		}
	}
}

WeakHashMap:在映射中,每个值只保存一份实例以节省存储空间。当程序需要那个“值”时,便在

影视中查询现有的对象,然后只用它。允许垃圾回收器自动清理键和值,可以节省存储空间。


public class CanonicalMapping {
	public static void main(String[] args){
		WeakHashMap<Key,Value> map=new WeakHashMap<Key,Value>();
		for(int i=0;i<size;i++){
			Key k=new Key(Integer.toString(i));
			Value v=new Value(Integer.toString(i));
			if(i%3==0)
				keys[i]=k;
			map.put(k,v);
		}
		System.gc();
	}
}

总结

这一章最大的收获是怎么写散列函数,还有怎么为HashTable调优。

相关标签: 容器 散列码