对一个列表进行检索时,我们使用得最多的是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是最好的选择。