[改善Java代码]不推荐使用binarySearch对列表进行检索
对一个列表进行检索时,我们使用的最多的是indexOf方法,它简单好用,而且也不会出错,虽然它只能检索到第一个符合条件的值,但是我们可以生成子列表后再检索.这样也就可以查找到所有符合条件的值了.
Collections工具类也提供了一个检索的方法:binarySearch,这个是干什么的?该方法也是对一个列表进行检索的,可以查找出指定的索引值,但是在使用这个方法时就有一些注意事项,看代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; public class Client {
public static void main(String[] args) {
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);
}
}
运行结果:
索引值(indexOf):1
索引值(binarySearch):2
结果不一样,虽然有两个"广州"这样的元素.但是返回的结果都应该是1才对,为何binarySearch返回的结果是2,问题就出现在2分法搜索上,二分法搜索就是"折半折半再折半"简单,效率高.
看JDK中源码是如何实现的:
private static final int BINARYSEARCH_THRESHOLD = 5000;
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);//随机存取列表或者元素数量少于5000的顺序列表
else
return Collections.iteratorBinarySearch(list, key);//元素数量大于50000的顺序存取列表
}
ArrayList实现了RandomAccess接口,是一个顺序存取列表,使用了indexBinarySearch方法,代码如下:
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;//默认上界
int high = list.size()-1;//默认下界 while (low <= high) {
int mid = (low + high) >>> 1;//中间索引,无符号右移1位
Comparable<? super T> midVal = list.get(mid);//中间值
int cmp = midVal.compareTo(key);//比较中间值
11 //重置上界和下界
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found 找到元素
}
return -(low + 1); // key not found 没有找到元素,返回负值
} private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key); if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
以上就是二分法搜索的Java版实现,首先是获得中间索引值,我们的例子中是2,那么索引值是2的元素值是多少?正好是"广州",于是返回索引值2.正确没有问题.
那么再看indexOf的实现:
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
indexOf方法就是一个遍历,找到第一个元素值相等则返回.
两者的算法都没有问题,是我们用错了binarySearch的用法,因为二分法查询要有一个首要的前提,数据集已经实现了升序排列,否则二分法查找的值是不准确的.不排序怎么确定是在比中间值小的区域还是比中间值大的区域呢?
二分法排序首先要排序,这是二分法的首要条件.
问题清楚了,使用Collection.sort排序即可,但是这样真的可以解决吗?元素数据是从Web或数据库中传过来的,原本是一个有规则的业务数据,为了查找一个元素对其排序,改变了元素在列表中的位置.那谁来保证业务规则的正确性呢?
所以binarySearch在此处首先了.当然可以拷贝一个数组,然后再排序,再使用binarySearch查找指定值,也是可以解决问题.
当然使用binarySearch的二分法查找比indexOf遍历算法性能上高很多,特别是在大数据集而且目标值又接近尾部时,binarySearch方法与indexOf相比,性能上会提升几十倍,因此在从性能的角度考虑时可以选择binarySearch.
//==================测试binarySearch()和indexOf的时间=========
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; public class Client {
public static void main(String[] args) {
int max =1200000;
List<String> cities = new ArrayList<String>();
for(int i=0;i<max;i++){
cities.add(i+"");
}
//indexOf方法取得索引值
long start = System.nanoTime();
int index1 = cities.indexOf((max-5)+"");
long mid = System.nanoTime();
System.out.println(mid - start);
//binarySearch查找到索引值
int index2 = Collections.binarySearch(cities, (max-5)+"");
long end = System.nanoTime();
System.out.println(end - mid);
System.out.println("索引值(indexOf):"+index1);
System.out.println("索引值(binarySearch):"+index2);
}
}
运行输出:
16876685
408528
索引值(indexOf):1199995
索引值(binarySearch):-1201
这个地方binarySearch输出负值....我没有调查...如果把这个max改的小一点就没有任何问题.
两种方式的索引值都一样.
binarySearch()的索引效率比indexOf高很多...(具体还要看要查找的值在list中的前后位置)
[改善Java代码]不推荐使用binarySearch对列表进行检索的更多相关文章
- [改善Java代码]不推荐覆写start方法
多线程比较简单的方式是继承Thread类,然后覆写run()方法,在客户端程序中通过调用对象的start方法即可启动一个线程,这个是多线程程序的标准写法. 错误代码: public class Cli ...
- [改善Java代码]避开基本类型数组转换列表陷阱
开发中经常用到Arrays和Collections这两个工具类. 在数组和列表之间进行切换.非常方便.但是也会遇到一些问题. 看代码: import java.util.Arrays; import ...
- [改善Java代码]提防包装类型的null值
建议26: 提防包装类型的null值 我们知道Java引入包装类型(Wrapper Types)是为了解决基本类型的实例化问题,以便让一个基本类型也能参与到面向对象的编程世界中.而在Java 5中泛型 ...
- [改善Java代码]易变业务使用脚本语言编写
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...
- [改善Java代码]非稳定排序推荐使用List
我们知道Set与List的最大区别就是Set中的元素不可以重复(这个重复指的equals方法的返回值相等),其他方面则没有太大的区别了,在Set的实现类中有一个比较常用的类需要了解一下:TreeSet ...
- [改善Java代码]推荐覆写toString方法
建议49: 推荐覆写toString方法 为什么要覆写toString方法,这个问题很简单,因为Java提供的默认toString方法不友好,打印出来看不懂,不覆写不行,看这样一段代码: public ...
- [改善Java代码]推荐使用枚举定义常量
枚举和注解都是在Java1.5中引入的,虽然他们是后起之秀,但是功能不容小觑,枚举改变了常量的声明方式,注解耦合了数据和代码. 建议83:推荐使用枚举定义常量 一.分析 常量的声明是每一个项目中不可或 ...
- [改善Java代码]推荐在复杂字符串操作中使用正则表达式
一.分析 字符串的操作,诸如追加.合并.替换.倒序.分隔等,都是在编码过程中经常用到的,而且Java也提供了append.replace.reverse.split等方法来完成这些操作,它们使用起来 ...
- [改善Java代码] 推荐使用序列化实现对象的拷贝
建议44: 推荐使用序列化实现对象的拷贝 上一个建议说了对象的浅拷贝问题,实现Cloneable接口就具备了拷贝能力,那我们来思考这样一个问题:如果一个项目中有大量的对象是通过拷贝生成的,那我们该如何 ...
随机推荐
- CoffeeScript学习(2)—— 变量
变量基础 对于变量的定义的话,形式如下所示 xxx = yyy ------编译后------ var xxx = yyy; 保留字 我们知道,在原生js中的保留字是不能作为变量名或者属性名的.如果我 ...
- CodeForces 689E Mike and Geometry Problem (离散化+组合数)
Mike and Geometry Problem 题目链接: http://acm.hust.edu.cn/vjudge/contest/121333#problem/I Description M ...
- 删除MySQL重复数据
删除MySQL重复数据 项目背景 在最近做的一个linux性能采集项目中,发现线程的程序入库很慢,再仔细定位,发现数据库里面很多冗余数据.因为在采集中,对于同一台设备,同一个时间点应该只有一个数据,然 ...
- CodeForces 589I Lottery (暴力,水题)
题意:给定 n 和 k,然后是 n 个数,表示1-k的一个值,问你修改最少的数,使得所有的1-k的数目都等于n/k. 析:水题,只要用每个数减去n/k,然后取模,加起来除以2,就ok了. 代码如下: ...
- 在WinForm中使用Web Service来实现软件自动升级
来源:互联网 winform程序相对web程序而言,功能更强大编程更方便,但软件更新却相当麻烦,要到客户端一台一台地升级,面对这个实际问题,在最近的一个小项目中,本人设计了一个通过软件实现自动升级技术 ...
- /boot/grub/menu.lst详解
基本概念menu.lst有时候也叫grub.conf,但是/boot/grub/下会有一个名叫menu.lst的符号链接指向它.它是grub引导系统的配置文件.基本选项default 0timeout ...
- cocos2d-x 获取系统时间
转自:http://blog.csdn.net/jinjian2009/article/details/9449585 之前使用过cocos2d-x获取系统时间,毫秒级的 long getCurren ...
- ArcObjects SDK(AE)10.1在vs2012安装的方法
ArcObjects SDK(以下简称AO)10.1只支持vs2010,如果装了vs2012,再安装AO会提示一串鸡肠(英文),意思是AO10.1只支持vs2010 想在2012下安装,可以通过修改注 ...
- Codeforces Round #250 (Div. 1) D. The Child and Sequence 线段树 区间取摸
D. The Child and Sequence Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest ...
- android开发之Fragment加载到一个Activity中
Fragments 是android3.0以后添加的.主要是为了方便android平板端的开发.方便适应不同大小的屏幕.此代码是为了最简单的Fragment的使用,往一个Activity中添加Frag ...