對一個列表進行檢索時,我們使用的最多的是indexOf方法,它簡單好用,而且也不會出錯,雖然它只能檢索到第一個符合條件的值,但是我們可以生成子列表后再檢索.這樣也就可以查找到所有符合條件的值了.
Collections工具類也提供了一個檢索的方法:binarySearch,這個是干什么的?該方法也是對一個列表進行檢索的,可以查找出指定的索引值,但是在使用這個方法時就有一些注意事項,看代碼:
1 import java.util.ArrayList; 2 import java.util.Collections; 3 import java.util.List; 4 5 public class Client { 6 public static void main(String[] args) { 7 List<String> cities = new ArrayList<String>(); 8 cities.add("上海"); 9 cities.add("廣州"); 10 cities.add("廣州"); 11 cities.add("北京"); 12 cities.add("天津"); 13 //indexOf方法取得索引值 14 int index1 = cities.indexOf("廣州"); 15 //binarySearch查找到索引值 16 int index2 = Collections.binarySearch(cities, "廣州"); 17 System.out.println("索引值(indexOf):"+index1); 18 System.out.println("索引值(binarySearch):"+index2); 19 } 20 }
運行結果:
索引值(indexOf):1
索引值(binarySearch):2
結果不一樣,雖然有兩個"廣州"這樣的元素.但是返回的結果都應該是1才對,為何binarySearch返回的結果是2,問題就出現在2分法搜索上,二分法搜索就是"折半折半再折半"簡單,效率高.
看JDK中源碼是如何實現的:
1 private static final int BINARYSEARCH_THRESHOLD = 5000; 2 public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { 3 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) 4 return Collections.indexedBinarySearch(list, key);//隨機存取列表或者元素數量少於5000的順序列表 5 else 6 return Collections.iteratorBinarySearch(list, key);//元素數量大於50000的順序存取列表 7 }
ArrayList實現了RandomAccess接口,是一個順序存取列表,使用了indexBinarySearch方法,代碼如下:
1 private static <T> 2 int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) 3 { 4 int low = 0;//默認上界 5 int high = list.size()-1;//默認下界 6 7 while (low <= high) { 8 int mid = (low + high) >>> 1;//中間索引,無符號右移1位 9 Comparable<? super T> midVal = list.get(mid);//中間值 10 int cmp = midVal.compareTo(key);//比較中間值 11 //重置上界和下界 12 if (cmp < 0) 13 low = mid + 1; 14 else if (cmp > 0) 15 high = mid - 1; 16 else 17 return mid; // key found 找到元素 18 } 19 return -(low + 1); // key not found 沒有找到元素,返回負值 20 } 21 22 private static <T> 23 int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) 24 { 25 int low = 0; 26 int high = list.size()-1; 27 ListIterator<? extends Comparable<? super T>> i = list.listIterator(); 28 29 while (low <= high) { 30 int mid = (low + high) >>> 1; 31 Comparable<? super T> midVal = get(i, mid); 32 int cmp = midVal.compareTo(key); 33 34 if (cmp < 0) 35 low = mid + 1; 36 else if (cmp > 0) 37 high = mid - 1; 38 else 39 return mid; // key found 40 } 41 return -(low + 1); // key not found 42 }
以上就是二分法搜索的Java版實現,首先是獲得中間索引值,我們的例子中是2,那么索引值是2的元素值是多少?正好是"廣州",於是返回索引值2.正確沒有問題.
那么再看indexOf的實現:
1 public int indexOf(Object o) { 2 if (o == null) { 3 for (int i = 0; i < size; i++) 4 if (elementData[i]==null) 5 return i; 6 } else { 7 for (int i = 0; i < size; i++) 8 if (o.equals(elementData[i])) 9 return i; 10 } 11 return -1; 12 }
indexOf方法就是一個遍歷,找到第一個元素值相等則返回.
兩者的算法都沒有問題,是我們用錯了binarySearch的用法,因為二分法查詢要有一個首要的前提,數據集已經實現了升序排列,否則二分法查找的值是不准確的.不排序怎么確定是在比中間值小的區域還是比中間值大的區域呢?
二分法排序首先要排序,這是二分法的首要條件.
問題清楚了,使用Collection.sort排序即可,但是這樣真的可以解決嗎?元素數據是從Web或數據庫中傳過來的,原本是一個有規則的業務數據,為了查找一個元素對其排序,改變了元素在列表中的位置.那誰來保證業務規則的正確性呢?
所以binarySearch在此處首先了.當然可以拷貝一個數組,然后再排序,再使用binarySearch查找指定值,也是可以解決問題.
當然使用binarySearch的二分法查找比indexOf遍歷算法性能上高很多,特別是在大數據集而且目標值又接近尾部時,binarySearch方法與indexOf相比,性能上會提升幾十倍,因此在從性能的角度考慮時可以選擇binarySearch.
//==================測試binarySearch()和indexOf的時間=========
1 import java.util.ArrayList; 2 import java.util.Collections; 3 import java.util.List; 4 5 6 public class Client { 7 public static void main(String[] args) { 8 int max =1200000; 9 List<String> cities = new ArrayList<String>(); 10 for(int i=0;i<max;i++){ 11 cities.add(i+""); 12 } 13 //indexOf方法取得索引值 14 long start = System.nanoTime(); 15 int index1 = cities.indexOf((max-5)+""); 16 long mid = System.nanoTime(); 17 System.out.println(mid - start); 18 //binarySearch查找到索引值 19 int index2 = Collections.binarySearch(cities, (max-5)+""); 20 long end = System.nanoTime(); 21 System.out.println(end - mid); 22 System.out.println("索引值(indexOf):"+index1); 23 System.out.println("索引值(binarySearch):"+index2); 24 } 25 }
運行輸出:
16876685 408528 索引值(indexOf):1199995 索引值(binarySearch):-1201
這個地方binarySearch輸出負值....我沒有調查...如果把這個max改的小一點就沒有任何問題.
兩種方式的索引值都一樣.
binarySearch()的索引效率比indexOf高很多...(具體還要看要查找的值在list中的前后位置)