不推荐使用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中的前后位置)


本文转自SummerChill博客园博客,原文链接:http://www.cnblogs.com/DreamDrive/p/5660155.html,如需转载请自行联系原作者

相关文章
|
存储 Kubernetes Linux
helm 简介及基本使用
helm 简介及基本使用
3368 0
helm 简介及基本使用
|
关系型数据库 MySQL 数据库
深入探讨MySQL中的幻读现象:原因、影响及解决方案
**导言:** 在数据库领域中,幻读(Phantom Read)是一个常见但容易被忽视的问题。它可能会导致事务的隔离级别无法满足预期,从而引发数据一致性问题。MySQL作为广泛使用的关系型数据库,也不免遇到幻读问题。本文将深入解析MySQL中的幻读现象,探讨其原因、影响以及可能的解决方案。
2306 0
|
存储 Java 调度
开发踩坑记录之二:谨慎使用Spring中的@Scheduled注解
在一些业务场景中需要执行定时操作来完成一些周期性的任务,比如每隔一周删除一周前的某些历史数据以及定时进行某项检测任务等等。在日常开发中比较简单的实现方式就是使用Spring的@Scheduled(具体使用方法不再赘述)注解。但是在修改服务器时间时会导致定时任务不执行情况的发生,解决的办法是当修改服务器时间后,将服务进行重启就可以避免此现象的发生。本文将主要探讨服务器时间修改导致@Scheduled注解失效的原因,同时找到在修改服务器时间后不重启服务的情况下,定时任务仍然正常执行的方法。 @Scheduled失效原因分析 解析流程图 使用新的方法
开发踩坑记录之二:谨慎使用Spring中的@Scheduled注解
|
Java Spring
Spring AOP切点表达式(Pointcut)详解
Spring 的 AOP 中的一个核心概念是切点(Pointcut),切点表达式定义通知(Advice)执行的范围。
5042 0
|
10月前
|
负载均衡 安全 网络安全
|
12月前
|
机器学习/深度学习 算法 测试技术
「软件项目管理」一文详解软件项目成本计划
该文章详细解释了软件项目成本估算的过程与方法,涵盖了代码行估算法、功能点估算法、用例点估算法、类比估算法、自下而上估算法、参数模型估算法及专家估算法等多种技术,并探讨了成本预算的制定步骤。
「软件项目管理」一文详解软件项目成本计划
使用System.getProperty获取系统属性
使用System.getProperty获取系统属性
|
人工智能 安全 专有云
阿里云飞天企业版获信通院可信云技术最佳实践奖
在中国信息通信研究院举办的“2024可信云大会”上,阿里云飞天企业版凭借“一云多算”能力拿下“可信云技术最佳实践”奖。此外飞天企业版还通过了《“云+应用”一体化运维能力要求》、《行业云平台一体化运营平台评估L4卓越级》等多项评估。
376 1
|
SQL 关系型数据库 MySQL
深入探究MySQL中的NULL
不知道大家有没有遇到这样的问题,当我们在对MySQL数据库进行查询操作时,条件写的是status!=1,理论上会将所有不符合条件的查询出来,但奇怪的是结果为NULL的就查不出来,必须得拼接上条件or status IS NULL。本篇文章我们就一起探究一下MySQL中的NULL。
896 0
|
设计模式 JSON Dubbo
超越接口:探索Dubbo的泛化调用机制
超越接口:探索Dubbo的泛化调用机制
1081 0