bigywater.tech
Java 内部原理 · 106

Arrays.binarySearch() 真实验证:对未排序数组的结果真实是未定义的,不会报错提醒

二分查找要求数组有序,这是常识,但很多人没真的试过"如果数组没排序,binarySearch会怎样"——这篇真实用一个未排序数组喂给它,一次算对、一次算错,而且全程不会有任何异常提醒你出问题了。

负数返回值真实精确编码插入点
真实验证:找不到目标时返回的负数,真实遵循 -(插入点)-1 这条规则,可以精确反解出应该插入的位置
未排序数组真实一次对一次错
真实验证:同一个未排序数组,查找9真实碰巧给出正确索引2,查找3真实给出错误结果-1(真实索引是3)

上一篇:Collections.nCopies() 真实验证

真实验证:找不到时的负数,精确遵循 -(插入点)-1 编码

在已排序数组里查找一个不存在的值,验证返回的负数是否真的能反解出"应该插入哪里"。

ArrayBinarySearchDemo.java int[] sorted = {1, 3, 5, 7, 9, 11, 13}; Arrays.binarySearch(sorted, 6); // 6不存在,应该插在索引3(在5和7之间)
真实实测
binarySearch(sorted, 7) 真实结果: 3
binarySearch(sorted, 6) 真实结果: -4(6应该插在索引3的位置,真实编码为 -(3)-1 = -4)
真实反解出的插入点: 3,sorted[3]真实是: 7

binarySearch(sorted, 0) 真实结果: -1(应该插在索引0,编码为 -(0)-1 = -1)
binarySearch(sorted, 100) 真实结果: -8(应该插在索引7即末尾,编码为 -(7)-1 = -8)

真实结果验证了 JDK 文档里明确写出的这条编码规则:找到目标时返回真实的正索引;找不到时,返回值真实是 -(insertion point) - 1,其中 insertion point 是"如果要保持有序,这个值应该插入的位置"。这个编码之所以选择"取负再减一"而不是直接返回插入点,是为了真实区分"找到了、索引恰好是0"和"没找到"这两种情况——如果找不到时直接返回插入点(可能是0),会和"找到了索引0的元素"这个真实结果混淆,而 -(0)-1 = -1 这种编码方式,真实保证了返回值的符号本身就能明确区分"找到"和"没找到"。

真实复现:对未排序数组调用,结果真实是未定义的,不会报错

binarySearch()的前提是数组已排序——如果真的传一个没排序的数组进去,会发生什么?

ArrayBinarySearchDemo.java int[] unsorted = {5, 1, 9, 3, 7}; Arrays.binarySearch(unsorted, 9); // 数组里9真实在索引2 Arrays.binarySearch(unsorted, 3); // 数组里3真实在索引3
真实实测
对未排序数组binarySearch(unsorted, 9) 真实结果: 2(数组里9真实在索引2,但binarySearch可能给出错误答案,不会报错提醒)
对未排序数组binarySearch(unsorted, 3) 真实结果: -1(数组里3真实在索引3,验证是否找错)
真实结果比"预期会出错"更微妙。这是本次实验里最值得记住的一点:对未排序数组 {5, 1, 9, 3, 7} 查找 9,真实碰巧得到了正确答案(索引2,和数组里真实的位置一致)——这不是 binarySearch() "智能地"处理了未排序数组,而是二分查找算法在特定的中点选择路径下,恰好走到了正确的位置。但查找 3 就真实暴露了问题:数组里 3 真实在索引3,binarySearch() 却真实返回了 -1(意思是"没找到,应该插在索引0"),这是一个真实的错误结果。整个过程中,Arrays.binarySearch() 没有做任何排序检查,不会抛出异常、不会打印警告,只是按照二分查找的固定算法逻辑机械地执行,输入违反了前提假设,输出就真实是未定义的——有时候凑巧对,有时候真实错,取决于具体数据和目标值恰好落在二分路径的哪个分支上。这类"前提条件不满足但不报错,只是安静给出错误结果"的API在JDK里不算少见,使用前必须自己确保前提成立。

真实验证:真实排序后,同样的查找恢复正确

真实实测
排序后的数组: [1, 3, 5, 7, 9]
排序后binarySearch(nowSorted, 9) 真实结果: 4

真实结果证明只要满足了"数组已排序"这个前提,binarySearch() 就能真实、稳定地给出正确结果。这也顺带演示了一个实用场景:用 binarySearch() 的返回值(不管是正的索引还是负的插入点编码)可以真实实现"往有序列表里插入新元素并保持有序"的逻辑,不需要额外遍历比较。

分步演示:mid 指针是怎么一步步收缩的

每一步的 low/high/mid 和比较结果都是真实运行二分查找算法得到的。

有序数组里 low/high/mid 是怎么真实收缩定位的;同样的算法喂给未排序数组,一次蒙对、一次真实算错
当前 mid 指向的位置已经比较过、真实排除在外的位置

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,负数编码规则和未排序数组的错误案例重复运行完全一致(这是确定性的算法行为,不是随机现象)。
    • 和排序稳定性那篇一起看,能更完整理解"排序"和"查找"这两个基础算法操作在JDK里对前提条件的真实要求和边界。
    • 没有涉及:Comparator版本的binarySearch()对自定义排序规则数组的真实支持、二分查找在等值重复元素较多的数组中真实返回哪一个匹配索引(结论是不保证是第一个或最后一个)——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号