java.util.Arrays的BUG - 二分搜索算法

Joshua Bloch在《Effective Java》一书中提到,在java.util.Arrays类的二分查找算法中存在一个Bug,该Bug会导致在特定条件下抛出ArrayIndexOutOfBoundsException异常。Bug出现在计算中间索引值时,由于整数溢出导致索引越界。
Joshua Bloch, 获得过Jolt最畅销奖的《Effective Java》的作者, 是Sun Microsystems的杰出工程师和Transarc的资深系统设计师, J2SE 5.0 Tiger的代言人和领路人, 也是还是JSR166的发起人之一..

后来, Joshua Bloch去了Google, 成为了Google的首席工程师

Joshua Bloch拥有卡耐基.梅隆大学(CMU)计算机科学的博士学位。

在最近Joshua Bloch的一篇文章里, Joshua Bloch回忆了当年在CMU上课的时候, vividly Jon Bentley 第一节算法课, 就要求所有刚进来的PHD学生, 每人都写一个二分查找算法. 然后发现, 多数人的算法都存在BUG, 这在当时给了Joshua Bloch 一个很深的印象..

在之后, Joshua Bloch 负责java.util.Arrays 代码编写的时候, 采用了Bentley 在<<Programming Pearls >>一书中的二分查找算法, 结果在8年后的今天, Joshua Bloch 发现, 这里面原来还是有一个BUG.

问题到底是出在哪里? 竟然逃过了Bentley 和Joshua Bloch 的双重检测?

一起来看看java.util.Arrays的代码:

1:     public static int binarySearch(int[] a, int key) {
2:         int low = 0;
3:         int high = a.length - 1;
4:
5:         while (low <= high) {
6:             int mid = (low + high) / 2;
7:             int midVal = a[mid];
8:
9:             if (midVal < key)
10:                 low = mid + 1;
11:             else if (midVal > key)
12:                 high = mid - 1;
13:             else
14:                 return mid; // key found
15:         }
16:         return -(low + 1);  // key not found.
17:     }


这是很经典的一个二分查找算法.

bug出现在第6行:

6:             int mid =(low + high) / 2;

在一般情况下, 这个语句是不会出错的, 但是, 当low+high的值超过了最大的正int值 (231- 1) 的时候, mid会变成负值,  这个时候, 会抛出ArrayIndexOutOfBoundsException 异常..


所以当一个数组包含超过2的30次方 个元素的时候, 就很可能会带来问题... 当然, 在一般的应用里面, 很少数组会包含那么多元素, 但是现在这样的情况已经越来越多了, 比如Google的海量运算..

那如何解决这个问题?

很简单, 修改这行语句为:

6:             int mid = low + ((high - low) / 2);
或者
6:             int mid = (low + high) >>> 1;


在c或者c++中, 则可以如下实现:
6:             mid = ((unsigned) (low + high)) >> 1;


这个问题告诉我们, 无论什么时候, 我们都不要想当然我们的程序是完美的. 我们需要细心的设计,测试再测试,符合规范的方法等等...对此, 你有什么经验和大家分享吗?

同样给我们带来的思考是: 8年了, java.util.Arrays 竟然存在这样一个bug, 这不得不让我们对JDK本身的测试性, 稳定性 怀有疑问.. 将来又会有多少个类似的bug出现呢?

package com.educoder.bigData.sparksql5; import java.util.Arrays; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.function.Function; import org.apache.spark.ml.Pipeline; import org.apache.spark.ml.PipelineModel; import org.apache.spark.ml.PipelineStage; import org.apache.spark.ml.classification.*; import org.apache.spark.ml.feature.StringIndexer; import org.apache.spark.ml.feature.Word2Vec; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.RowFactory; import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.types.DataTypes; import org.apache.spark.sql.types.Metadata; import org.apache.spark.sql.types.StructField; import org.apache.spark.sql.types.StructType; public class Case2 { public static PipelineModel training(SparkSession spark) { /********* Begin *********/ /********* End *********/ return null; } } 任务描述 本关任务:通过分类算法完成一个垃圾邮件检测。 相关知识 为了完成本关任务,你需要掌握: 标签标识转化; 分类算法使用。 标签标识转化 当标签标识不为局部向量的数字值向量,使用StringIndexer来完成转换。 StringIndexer labelIndexer = new StringIndexer().setInputCol("label").setOutputCol("indexedLabel"); 分类算法使用 分类算法常见的决策树分类器、随机森林分类器、梯度提升树分类器、逻辑回归,MLlib中实现类分别为:DecisionTreeClassifier 、RandomForestClassifier 、GBTClassifier、LogisticRegression,MLlib提供了统一的使用方法,请参考第一关进行使用。 编程要求 根据提示,在右侧编辑器补充代码,从SMSSpamCollection文件读取信息进行训练,并设置向量标签为indexedLabel,平台会对生成的PipelineModel训练模型进行准确性检测。 SMSSpamCollection文件每行开头第一列为标签类别:垃圾邮件和非垃圾邮件,每行数据内容都以空格隔开。如下图: ham Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat... ham Ok lar... Joking wif u oni... spam Congrats! 1 year special cinema pass for 2 is yours. call 09061209465 now! C Suprman V, Matrix3, StarWars3
最新发布
12-17
评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值