對一個列表進行檢索時,我們使用得最多的是indexOf方法,它簡單、好用,而且也不會出錯,雖然它只能檢索到第一個符合條件的值,但是我們可以生成子列表後再檢索,這樣也就可以查找出所有符合條件的值了。
Collections工具類也提供一個檢索方法:binarySearch,這個是幹什麼的?該方法也是對一個列表進行檢索的,可查找出指定值的索引值,但是在使用這個方法時就有一些注意事項了,我們看如下代碼:
public static void main(Stringargs){
List<String>cities=new ArrayList<String>();
cities.add(\"上海\");
cities.add(\"廣州\");
cities.add(\"廣州\");
cities.add(\"北京\");
cities.add(\"天津\");
//indexOf方法取得索引值
int index1=cities.indexOf(\"廣州\");
//binarySearch查找到索引值
int index2=Collections.binarySearch(cities,\"廣州\");
System.out.println(\"索引值(indexOf):\"+index1);
System.out.println(\"索引值(binarySearch):\"+index2);
}
先不考慮運行結果,直接看JDK上對binarySearch的描述:使用二分搜索法搜索指定列表,以獲得指定對象。其實現的功能與indexOf是相同的,只是使用的是二分法搜索列表,所以估計兩種方法返回結果是一樣的,看結果:
索引值(indexOf):1
索引值(binarySearch):2
結果不一樣,雖然說我們有兩個「廣州」這樣的元素,但是返回的結果都應該是1才對呀,為何binarySearch返回的結果是2呢?問題就出在二分法搜索上,二分法搜索就是「折半折半再折半」的搜索方法,簡單,而且效率高。看看JDK是如何實現的。
public static<T>int binarySearch(List<?extends Comparable<?super T>>list, T key){
if(list instanceof RandomAccess||list.size()<5000)
//隨機存取列表或者元素數量少於5000的順序存取列表
return Collections.indexedBinarySearch(list, key);
else
//元素數量大於5000的順序存取列表
return Collections.iteratorBinarySearch(list, key);
}
ArrayList實現了RandomAccess接口,是一個順序存取列表,使用了indexdBinarySearch方法,代碼如下:
private static<T>int indexedBinarySearch(List<?extends Comparable<?super T>>
list, T key){
//默認上界
int low=0;
//默認下界
int high=list.size()-1;
while(low<=high){
//中間索引,無符號右移1位
int mid=(low+high)>>>1;
//中間值
Comparable<?super T>midVal=list.get(mid);
//比較中間值
int cmp=midVal.compareTo(key);
//重置上界和下界
if(cmp<0)
low=mid+1;
else if(cmp>0)
high=mid-1;
else
//找到元素
return mid;
}
//沒有找到,返回負值
return-(low+1);
}
這也沒啥說的,就是二分法搜索的Java版實現。注意看加粗字體部分,首先是獲得中間索引值,我們的例子中也就是2,那索引值是2的元素值是多少呢?正好是「廣州」,於是返回索引值2,正確,沒問題。那我們再看看indexOf的實現,代碼如下:
public int indexOf(Object o){
if(o==null){
//null元素查找
for(int i=0;i<size;i++)
if(elementData[i]==null)
return i;
}else{
//非null元素查找
for(int i=0;i<size;i++)
//兩個元素是否相等,注意這裡是equals方法
if(o.equals(elementData[i]))
return i;
}
//沒找到,返回-1
return-1;
}
indexOf方法就是一個遍歷,找到第一個元素值相等則返回,沒什麼玄機。回到我們的程序上來看,for循環的第二遍即是我們要查找的「廣州」,於是就返回索引值1了,也正確,沒有任何問題。
兩者的算法都沒有問題,難道是我們用錯了?這還真是我們的錯,因為二分法查詢的一個首要前提是:數據集已經實現升序排列,否則二分法查找的值是不準確的。不排序怎麼確定是在小區(比中間值小的區域)中查找還是在大區(比中間值大的區域)中查找呢?二分法查找必須要先排序,這是二分法查找的首要條件。
問題清楚了,藐視解決方法也很簡單,使用Collections.sort排下序即可解決。但這樣真的可以解決嗎?想想看,元素數據是從Web或數據庫中傳遞進來的,原本是一個有規則的業務數據,我們為了查找一個元素對它進行了排序,也就是改變了元素在列表中的位置,那誰來保證業務規則此時的正確性呢?所以說,binarySearch方法在此處受限了。當然,拷貝一個數組,然後再排序,再使用binarySeach查找指定值,也可以解決該問題。
使用binarySearch首先要考慮排序問題,這是我們編碼人員經常容易忘記的,而且在測試期間還沒發現問題(因為測試時「製造」的數據都很有規律),等到投入生產系統後才發現查找的數據不準確,又是一個大Bug產生了,從這點來看,indexOf要比binarySearch簡單得多。
當然,使用binarySearch的二分法查找比indexOf的遍歷算法性能上高很多,特別是在大數據集而且目標值又接近尾部時,binarySearch方法與indexOf相比,性能上會提升幾十倍,因此在從性能的角度考慮時可以選擇binarySearch。
注意 從性能方面考慮,binarySearch是最好的選擇。