Arrays.binarySearch 的性能优势依赖正确使用:小数组自动线性扫描、大数组安全二分、预排序与边界处理需定制;类型匹配、null 容错及比较逻辑一致性是关键。

Arrays.binarySearch 的性能优势明显,但它的高效只在正确使用时成立。它不是“自动加速器”,而是一把需要校准的精密工具——用对了,查找从 O(n) 降到 O(log n);用错了,结果不可靠,还掩盖问题。
为什么 binarySearch 能快?关键不在“二分”本身
真正让它快的,是三个协同机制:
- 小数组自动降级为线性扫描:JDK 8 中,长度小于 21 的数组直接遍历。现代 CPU 缓存友好,短距离循环比反复跳转更快,避免“为二分而二分”的开销
-
大数组启用安全二分:中点计算用
low + (high - low) / 2(或无符号右移),防止low + high整数溢出;边界判断严格用low ,不漏单元素区间 - 零额外对象创建:基本类型版本不装箱,对象版本复用已有 comparator 或 equals/compareTo,没有临时集合或流式开销
性能陷阱:排序成本常被低估
binarySearch 快,但前提是数组已排好序。如果每次查找前都调 Arrays.sort(),总成本变成 O(n log n) + O(log n),反而比线性查找更慢。
- 对静态数据(如配置项、字典表),排序一次,多次复用 binarySearch
- 对动态追加的数据,考虑维护一个有序结构(如
TreeSet或插入时保持有序的ArrayList),而非反复重排 - 若仅需存在性判断(不要索引),且查找频次低,
Arrays.stream(arr).anyMatch(x -> x == key)更简洁;高频场景必须预排序
重复元素与边界查找:别让 binarySearch 停得太早
binarySearch 找到任意一个匹配就返回,不区分首尾。要获取全部位置或左右边界,不能只靠它一次调用:
- 找左边界(首个等于 key 的索引):可手写二分逻辑,或用
Arrays.binarySearch(arr, key)定位后,向左线性回扫 —— 小范围重复时足够快 - 找右边界(最后一个等于 key 的索引):同理向右扩展,或改用两次定制二分(查
key和key + 1的插入点) - 频繁查重复区间?换结构更省事:用
Map<K, List<Integer>>预建索引,或引入TreeMultiset(Guava)等专用容器
类型与 null:细节决定是否真快
看似一样的调用,底层行为可能天差地别:
-
int[] 和 Integer[] 不互通:传
Integer[]给binarySearch(int[], int)会编译失败;误用binarySearch(Object[], Object)查int[]则触发自动装箱,甚至 NullPointerException -
null 值需显式容错:对象数组含 null 时,若 comparator 没处理 null(如未用
Comparator.nullsFirst()),运行时直接抛异常 - 自定义类务必实现 Comparable 或固定传 comparator:排序和查找必须用同一套比较逻辑,否则“已排序”只是假象


















