深入分析Android系统中SparseArray的源码
前言
昨晚想在Android应用中增加一个int映射到String的字典表,使用HashMap实现的时候,Eclipse给出了一个警告,昨晚项目上线紧张,我直接给忽略了,今天看了一下具体的Eclipse提示如下:
UsenewSparseArray<String>(...)insteadforbetterperformance
这个警告的意思是使用SparseArray来替代,以获取更好的性能。
源码
因为SparseArray整体代码比较简单,先把源码展示出来,然后再分析为什么使用SparseArray会比使用HashMap有更好的性能。
publicclassSparseArray<E>implementsCloneable{ privatestaticfinalObjectDELETED=newObject(); privatebooleanmGarbage=false; privateint[]mKeys; privateObject[]mValues; privateintmSize; /** *CreatesanewSparseArraycontainingnomappings. */ publicSparseArray(){ this(10); } /** *CreatesanewSparseArraycontainingnomappingsthatwillnot *requireanyadditionalmemoryallocationtostorethespecified *numberofmappings.Ifyousupplyaninitialcapacityof0,the *sparsearraywillbeinitializedwithalight-weightrepresentation *notrequiringanyadditionalarrayallocations. */ publicSparseArray(intinitialCapacity){ if(initialCapacity==0){ mKeys=ContainerHelpers.EMPTY_INTS; mValues=ContainerHelpers.EMPTY_OBJECTS; }else{ initialCapacity=ArrayUtils.idealIntArraySize(initialCapacity); mKeys=newint[initialCapacity]; mValues=newObject[initialCapacity]; } mSize=0; } @Override @SuppressWarnings("unchecked") publicSparseArray<E>clone(){ SparseArray<E>clone=null; try{ clone=(SparseArray<E>)super.clone(); clone.mKeys=mKeys.clone(); clone.mValues=mValues.clone(); }catch(CloneNotSupportedExceptioncnse){ /*ignore*/ } returnclone; } /** *GetstheObjectmappedfromthespecifiedkey,or<code>null</code> *ifnosuchmappinghasbeenmade. */ publicEget(intkey){ returnget(key,null); } /** *GetstheObjectmappedfromthespecifiedkey,orthespecifiedObject *ifnosuchmappinghasbeenmade. */ @SuppressWarnings("unchecked") publicEget(intkey,EvalueIfKeyNotFound){ inti=ContainerHelpers.binarySearch(mKeys,mSize,key); if(i<0||mValues[i]==DELETED){ returnvalueIfKeyNotFound; }else{ return(E)mValues[i]; } } /** *Removesthemappingfromthespecifiedkey,iftherewasany. */ publicvoiddelete(intkey){ inti=ContainerHelpers.binarySearch(mKeys,mSize,key); if(i>=0){ if(mValues[i]!=DELETED){ mValues[i]=DELETED; mGarbage=true; } } } /** *Aliasfor{@link#delete(int)}. */ publicvoidremove(intkey){ delete(key); } /** *Removesthemappingatthespecifiedindex. */ publicvoidremoveAt(intindex){ if(mValues[index]!=DELETED){ mValues[index]=DELETED; mGarbage=true; } } /** *Removearangeofmappingsasabatch. * *@paramindexIndextobeginat *@paramsizeNumberofmappingstoremove */ publicvoidremoveAtRange(intindex,intsize){ finalintend=Math.min(mSize,index+size); for(inti=index;i<end;i++){ removeAt(i); } } privatevoidgc(){ //Log.e("SparseArray","gcstartwith"+mSize); intn=mSize; into=0; int[]keys=mKeys; Object[]values=mValues; for(inti=0;i<n;i++){ Objectval=values[i]; if(val!=DELETED){ if(i!=o){ keys[o]=keys[i]; values[o]=val; values[i]=null; } o++; } } mGarbage=false; mSize=o; //Log.e("SparseArray","gcendwith"+mSize); } /** *Addsamappingfromthespecifiedkeytothespecifiedvalue, *replacingthepreviousmappingfromthespecifiedkeyifthere *wasone. */ publicvoidput(intkey,Evalue){ inti=ContainerHelpers.binarySearch(mKeys,mSize,key); if(i>=0){ mValues[i]=value; }else{ i=~i; if(i<mSize&&mValues[i]==DELETED){ mKeys[i]=key; mValues[i]=value; return; } if(mGarbage&&mSize>=mKeys.length){ gc(); //Searchagainbecauseindicesmayhavechanged. i=~ContainerHelpers.binarySearch(mKeys,mSize,key); } if(mSize>=mKeys.length){ intn=ArrayUtils.idealIntArraySize(mSize+1); int[]nkeys=newint[n]; Object[]nvalues=newObject[n]; //Log.e("SparseArray","grow"+mKeys.length+"to"+n); System.arraycopy(mKeys,0,nkeys,0,mKeys.length); System.arraycopy(mValues,0,nvalues,0,mValues.length); mKeys=nkeys; mValues=nvalues; } if(mSize-i!=0){ //Log.e("SparseArray","move"+(mSize-i)); System.arraycopy(mKeys,i,mKeys,i+1,mSize-i); System.arraycopy(mValues,i,mValues,i+1,mSize-i); } mKeys[i]=key; mValues[i]=value; mSize++; } } /** *Returnsthenumberofkey-valuemappingsthatthisSparseArray *currentlystores. */ publicintsize(){ if(mGarbage){ gc(); } returnmSize; } /** *Givenanindexintherange<code>0...size()-1</code>,returns *thekeyfromthe<code>index</code>thkey-valuemappingthatthis *SparseArraystores. * *<p>Thekeyscorrespondingtoindicesinascendingorderareguaranteedto *beinascendingorder,e.g.,<code>keyAt(0)</code>willreturnthe *smallestkeyand<code>keyAt(size()-1)</code>willreturnthelargest *key.</p> */ publicintkeyAt(intindex){ if(mGarbage){ gc(); } returnmKeys[index]; } /** *Givenanindexintherange<code>0...size()-1</code>,returns *thevaluefromthe<code>index</code>thkey-valuemappingthatthis *SparseArraystores. * *<p>Thevaluescorrespondingtoindicesinascendingorderareguaranteed *tobeassociatedwithkeysinascendingorder,e.g., *<code>valueAt(0)</code>willreturnthevalueassociatedwiththe *smallestkeyand<code>valueAt(size()-1)</code>willreturnthevalue *associatedwiththelargestkey.</p> */ @SuppressWarnings("unchecked") publicEvalueAt(intindex){ if(mGarbage){ gc(); } return(E)mValues[index]; } /** *Givenanindexintherange<code>0...size()-1</code>,setsanew *valueforthe<code>index</code>thkey-valuemappingthatthis *SparseArraystores. */ publicvoidsetValueAt(intindex,Evalue){ if(mGarbage){ gc(); } mValues[index]=value; } /** *Returnstheindexforwhich{@link#keyAt}wouldreturnthe *specifiedkey,oranegativenumberifthespecified *keyisnotmapped. */ publicintindexOfKey(intkey){ if(mGarbage){ gc(); } returnContainerHelpers.binarySearch(mKeys,mSize,key); } /** *Returnsanindexforwhich{@link#valueAt}wouldreturnthe *specifiedkey,oranegativenumberifnokeysmaptothe *specifiedvalue. *<p>Bewarethatthisisalinearsearch,unlikelookupsbykey, *andthatmultiplekeyscanmaptothesamevalueandthiswill *findonlyoneofthem. *<p>Notealsothatunlikemostcollections'{@codeindexOf}methods, *thismethodcomparesvaluesusing{@code==}ratherthan{@codeequals}. */ publicintindexOfValue(Evalue){ if(mGarbage){ gc(); } for(inti=0;i<mSize;i++) if(mValues[i]==value) returni; return-1; } /** *Removesallkey-valuemappingsfromthisSparseArray. */ publicvoidclear(){ intn=mSize; Object[]values=mValues; for(inti=0;i<n;i++){ values[i]=null; } mSize=0; mGarbage=false; } /** *Putsakey/valuepairintothearray,optimizingforthecasewhere *thekeyisgreaterthanallexistingkeysinthearray. */ publicvoidappend(intkey,Evalue){ if(mSize!=0&&key<=mKeys[mSize-1]){ put(key,value); return; } if(mGarbage&&mSize>=mKeys.length){ gc(); } intpos=mSize; if(pos>=mKeys.length){ intn=ArrayUtils.idealIntArraySize(pos+1); int[]nkeys=newint[n]; Object[]nvalues=newObject[n]; //Log.e("SparseArray","grow"+mKeys.length+"to"+n); System.arraycopy(mKeys,0,nkeys,0,mKeys.length); System.arraycopy(mValues,0,nvalues,0,mValues.length); mKeys=nkeys; mValues=nvalues; } mKeys[pos]=key; mValues[pos]=value; mSize=pos+1; } /** *{@inheritDoc} * *<p>Thisimplementationcomposesastringbyiteratingoveritsmappings.If *thismapcontainsitselfasavalue,thestring"(thisMap)" *willappearinitsplace. */ @Override publicStringtoString(){ if(size()<=0){ return"{}"; } StringBuilderbuffer=newStringBuilder(mSize*28); buffer.append('{'); for(inti=0;i<mSize;i++){ if(i>0){ buffer.append(","); } intkey=keyAt(i); buffer.append(key); buffer.append('='); Objectvalue=valueAt(i); if(value!=this){ buffer.append(value); }else{ buffer.append("(thisMap)"); } } buffer.append('}'); returnbuffer.toString(); } }
首先,看一下SparseArray的构造函数:
/** *CreatesanewSparseArraycontainingnomappings. */ publicSparseArray(){ this(10); } /** *CreatesanewSparseArraycontainingnomappingsthatwillnot *requireanyadditionalmemoryallocationtostorethespecified *numberofmappings.Ifyousupplyaninitialcapacityof0,the *sparsearraywillbeinitializedwithalight-weightrepresentation *notrequiringanyadditionalarrayallocations. */ publicSparseArray(intinitialCapacity){ if(initialCapacity==0){ mKeys=ContainerHelpers.EMPTY_INTS; mValues=ContainerHelpers.EMPTY_OBJECTS; }else{ initialCapacity=ArrayUtils.idealIntArraySize(initialCapacity); mKeys=newint[initialCapacity]; mValues=newObject[initialCapacity]; } mSize=0; }
从构造方法可以看出,这里也是预先设置了容器的大小,默认大小为10。
再来看一下添加数据操作:
/** *Addsamappingfromthespecifiedkeytothespecifiedvalue, *replacingthepreviousmappingfromthespecifiedkeyifthere *wasone. */ publicvoidput(intkey,Evalue){ inti=ContainerHelpers.binarySearch(mKeys,mSize,key); if(i>=0){ mValues[i]=value; }else{ i=~i; if(i<mSize&&mValues[i]==DELETED){ mKeys[i]=key; mValues[i]=value; return; } if(mGarbage&&mSize>=mKeys.length){ gc(); //Searchagainbecauseindicesmayhavechanged. i=~ContainerHelpers.binarySearch(mKeys,mSize,key); } if(mSize>=mKeys.length){ intn=ArrayUtils.idealIntArraySize(mSize+1); int[]nkeys=newint[n]; Object[]nvalues=newObject[n]; //Log.e("SparseArray","grow"+mKeys.length+"to"+n); System.arraycopy(mKeys,0,nkeys,0,mKeys.length); System.arraycopy(mValues,0,nvalues,0,mValues.length); mKeys=nkeys; mValues=nvalues; } if(mSize-i!=0){ //Log.e("SparseArray","move"+(mSize-i)); System.arraycopy(mKeys,i,mKeys,i+1,mSize-i); System.arraycopy(mValues,i,mValues,i+1,mSize-i); } mKeys[i]=key; mValues[i]=value; mSize++; } }
再看查数据的方法:
/** *GetstheObjectmappedfromthespecifiedkey,or<code>null</code> *ifnosuchmappinghasbeenmade. */ publicEget(intkey){ returnget(key,null); } /** *GetstheObjectmappedfromthespecifiedkey,orthespecifiedObject *ifnosuchmappinghasbeenmade. */ @SuppressWarnings("unchecked") publicEget(intkey,EvalueIfKeyNotFound){ inti=ContainerHelpers.binarySearch(mKeys,mSize,key); if(i<0||mValues[i]==DELETED){ returnvalueIfKeyNotFound; }else{ return(E)mValues[i]; } }
可以看到,在put数据和get数据的过程中,都统一调用了一个二分查找算法,其实这也就是SparseArray能够提升效率的核心。
staticintbinarySearch(int[]array,intsize,intvalue){ intlo=0; inthi=size-1; while(lo<=hi){ finalintmid=(lo+hi)>>>1; finalintmidVal=array[mid]; if(midVal<value){ lo=mid+1; }elseif(midVal>value){ hi=mid-1; }else{ returnmid;//valuefound } } return~lo;//valuenotpresent }
个人认为(lo+hi)>>>1的方法有些怪异,直接用lo+(hi-lo)/2更好一些。