第17章容器深入研究
1.List接口的相关方法
1)toArray
Object[]toArray()
Returnsanarraycontainingalloftheelementsinthislistinpropersequence.Obeysthe
generalcontractoftheCollection.toArraymethod.
2)toArray
<T>T[]toArray(T[]a)
Returnsanarraycontainingalloftheelementsinthislistinpropersequence;theruntime
typeofthereturnedarrayisthatofthespecifiedarray.Obeysthegeneralcontractofthe
Collection.toArray(Object[])method.
Parameters:
a-thearrayintowhichtheelementsofthislistaretobestored,ifitisbigenough;
otherwise,anewarrayofthesameruntimetypeisallocatedforthispurpose.
Returns:
anarraycontainingtheelementsofthislist.
3)set
Eset(intindex,
Eelement)
Replacestheelementatthespecifiedpositioninthislistwiththespecifiedelement
(optionaloperation).
Parameters:
index-indexofelementtoreplace.
element-elementtobestoredatthespecifiedposition.
Returns:
theelementpreviouslyatthespecifiedposition.
4)clear
voidclear()
Removesalloftheelementsfromthislist(optionaloperation).Thislistwillbeemptyafter
thiscallreturns(unlessitthrowsanexception).
5)removeAll
booleanremoveAll(Collection<?>c)
Removesfromthislistalltheelementsthatarecontainedinthespecifiedcollection
(optionaloperation).
Parameters:
c-collectionthatdefineswhichelementswillberemovedfromthislist.
Returns:
trueifthislistchangedasaresultofthecall.
2.HashSet为快速查找而设计的Set,存入HashSet的元素必须定义hashCode()
TreeSet保持次序的Set,底层为树结构,必须实现Comparable接口
LinkedHashSet,具有HashSet的查询速度,且内部维护元素的出入次序。
3.Map的实现
publicclassAssociativeArray<K,V>{
privateObject[][]pairs;
privateintindex;
publicAssociativeArray(intlength){
pairs=newObject[length][2];
}
publicvoidput(Kkey,Vvalue){
if(index>=pairs.length)
thrownewArrayIndexOutOfBoundsException();
pairs[index++]=newObject[]{key,value};
}
@SuppressWarnings("unchecked")
publicVget(Kkey){
for(inti=0;i<index;i++)
if(key.equals(pairs[i][0]))
return(V)pairs[i][1];
returnnull;//Didnotfindkey
}
publicStringtoString(){
StringBuilderresult=newStringBuilder();
for(inti=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");
}
returnresult.toString();
}
publicstaticvoidmain(String[]args){
AssociativeArray<String,String>map=newAssociativeArray<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");//Pasttheend
}catch(ArrayIndexOutOfBoundsExceptione){
System.out.println("Toomanyobjects!");
}
System.out.println(map);
System.out.println(map.get("ocean"));
}
}
上面的代码只是说明性的,而且大小还固定,要和真正的Map比起来效率差很多。
4.散列码与散列
Object的hashCode()方法生成的散列码,默认是根据对象的地址计算出的散列码。即,如果一个类不重写该方法
,那么由该类生成的不同对象的散列码是不同的。
Object的equals()方法默认比较的是对象的地址。且正确的equals方法必须满足5个条件
1)自反行。对任意x,x.equals(x)一定返回true
2)对称性。对任意的x.equals(y)那么y.equals(x)也成立
3)传递性。x.equals(y),y.equals(z),x.equals(z)
4)一致性。如果返回true,那么不管调用任意多次都返回true
5)对任何不是null的x,x.equals(null)一定返回false.
如果要使用自己创建的类作为Map的键,必须同时覆盖equals和hashCode方法。
5.线性查询方式是最忙的查询方式。
6.区别
1)、equals方法用于比较对象的内容是否相等(覆盖以后)
2)、hashcode方法只有在集合中用到
3)、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
4)、将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入
总结:同一个类不同对象的散列码可能相同,可能不同,相同对象的散列码一定相同
7.通过key对象生成一个数字,将其作为数组的下标,这个数字就是散列码。该散列码由hashCode方法自动生成。为了解决数组容量被固定的问题,不同的键对象可以生成相同的散列码,这样数组大小就不是问题了。如果散列码有冲突,再用线性方式比较,因为经过散列后,相同散列码上的线性序列已经很小了,这比一开始就用线性搜索要快的多,这就是HashMap速度快的原因。
publicclassSimpleHashMap<K,V>extendsAbstractMap<K,V>{
//Chooseaprimenumberforthehashtable
//size,toachieveauniformdistribution:
staticfinalintSIZE=997;
//Youcan'thaveaphysicalarrayofgenerics,
//butyoucanupcasttoone:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K,V>>[]buckets=newLinkedList[SIZE];
publicVput(Kkey,Vvalue){
VoldValue=null;
intindex=Math.abs(key.hashCode())%SIZE;
if(buckets[index]==null)
buckets[index]=newLinkedList<MapEntry<K,V>>();
LinkedList<MapEntry<K,V>>bucket=buckets[index];
MapEntry<K,V>pair=newMapEntry<K,V>(key,value);
booleanfound=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);//Replaceoldwithnew
found=true;
break;
}
}
if(!found)
buckets[index].add(pair);
returnoldValue;
}
publicVget(Objectkey){
intindex=Math.abs(key.hashCode())%SIZE;
if(buckets[index]==null)
returnnull;
for(MapEntry<K,V>iPair:buckets[index])
if(iPair.getKey().equals(key))
returniPair.getValue();
returnnull;
}
publicSet<Map.Entry<K,V>>entrySet(){
Set<Map.Entry<K,V>>set=newHashSet<Map.Entry<K,V>>();
for(LinkedList<MapEntry<K,V>>bucket:buckets){
if(bucket==null)
continue;
for(MapEntry<K,V>mpair:bucket)
set.add(mpair);
}
returnset;
}
publicstaticvoidmain(String[]args){
SimpleHashMap<String,String>m=newSimpleHashMap<String,String>();
m.putAll(Countries.capitals(25));
System.out.println(m);
System.out.println(m.get("ERITREA"));
System.out.println(m.entrySet());
}
}
8.由散列码组成的表称为散列表,每个散列码称为“槽为”或是“桶位”;
9.对于现代的处理器而言,除法和求余数是最慢的操作。java的散列函数都使用2的整数次方。
10.如果想让对象生成的散列码在某个范围内,比如在0到5直接,那么就在原来的散列码基础上对5求余数。
11.在设计Hashcode方法时,最重要的就是同一个对象生成的散列码应该相同,否则在get的时候就无法取到该值。因此,散列码得生成不能依赖易变的数组或者是唯一性的数据。
12.hashCode方法的编写规则
1)生成散列码得速度必须快,并且有意义(即必须基于对象内容生成散列码);
2)散列码不必是独一无二的(重点关注速度,而不是唯一性);
3)散列码分布必须均匀;
怎么样编写hashCode方法,JoshuaBlock给出了一个基本的指导
1)给int变量result赋值非零常量,例如17;
2)给对象内每个有意义的域f,计算出一个int散列码c;
3)把计算得出的散列码合并;
例如:
publicclassCountedString{
privatestaticList<String>created=newArrayList<String>();
privateStrings;
privateintid=0;
publicCountedString(Stringstr){
s=str;
created.add(s);
//idisthetotalnumberofinstances
//ofthisstringinusebyCountedString:
for(Strings2:created)
if(s2.equals(s))
id++;
}
publicStringtoString(){
return"String:"+s+"id:"+id+"hashCode():"+hashCode();
}
publicinthashCode(){
//Theverysimpleapproach:
//returns.hashCode()*id;
//UsingJoshuaBloch'srecipe:
intresult=17;
result=37*result+s.hashCode();
result=37*result+id;
returnresult;
}
}
依照上面的指导原则,大致可以生成一个均匀分布的散列码