[改善Java代碼]不推薦使用binarySearch對列表進行檢索


對一個列表進行檢索時,我們使用的最多的是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中的前后位置)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM