ZetCode

Java Collections.binarySearch 方法

上次修改时间:2025 年 4 月 20 日

Collections.binarySearch 方法是一个用于高效搜索已排序列表的实用程序。它实现了二分查找算法,该算法提供 O(log n) 的时间复杂度。为了获得正确的结果,列表必须按升序排序。

Collections.binarySearch 概述

Collections.binarySearch 方法是一个用于高效搜索已排序列表的实用程序。它实现了二分查找算法,该算法提供 O(log n) 的时间复杂度。为了获得正确的结果,列表必须按升序排序。

二分查找通过反复将搜索区间减半来工作。它将目标值与数组的中间元素进行比较。根据比较结果,它继续搜索列表的左半部分或右半部分。

binarySearch 方法有两个主要变体。一个适用于元素的自然排序,而另一个允许自定义比较器。两者都返回搜索键在列表中找到时的索引。

如果列表中找到了键,该方法将返回其索引,该索引表示键在已排序列表中的位置。

如果未找到键,该方法将返回一个负值。该值计算为 -(insertionPoint) - 1,其中 insertionPoint 是在保持列表排序顺序的情况下将插入键的索引。

返回的负值有两个目的:它表明列表中不存在键,并提供了关于可以在哪里添加它的位置的信息。

此外,如果列表元素基于指定的比较器或自然排序无法相互比较,该方法将抛出 ClassCastException

基本 binarySearch 示例

此示例演示了 binarySearch 的最简单用法。我们创建一个已排序的整数列表并搜索一个值。二分查找必须对列表进行排序才能正常工作。

BasicBinarySearch.java
package com.zetcode;

import java.util.Collections;
import java.util.List;

public class BasicBinarySearch {

    public static void main(String[] args) {

        List<Integer> numbers = List.of(1, 3, 5, 7, 9, 11, 13);

        // Search for existing value
        int index1 = Collections.binarySearch(numbers, 7);
        System.out.println("Index of 7: " + index1);

        // Search for non-existing value
        int index2 = Collections.binarySearch(numbers, 8);
        System.out.println("Index of 8: " + index2);

        // Calculate insertion point for 8
        if (index2 < 0) {
            int insertionPoint = -index2 - 1;
            System.out.println("8 should be inserted at: " + insertionPoint);
        }
    }
}

此代码显示了基本的二分查找用法。我们搜索存在的值 7,以及不存在的值 8。对于不存在的值,我们计算插入点。

输出显示了找到的值的索引以及缺失值的计算插入点。这演示了二分查找在存在和不存在元素时的行为。

使用字符串的 binarySearch

此示例展示了 binarySearch 在字符串列表中的使用。字符串按字典顺序进行比较。列表必须按升序排序。

StringBinarySearch.java
package com.zetcode;

import java.util.Collections;
import java.util.List;

public class StringBinarySearch {

    public static void main(String[] args) {

        List<String> words = List.of("apple", "banana", "cherry",
                "date", "elderberry", "fig");

        // Search for existing string
        int index1 = Collections.binarySearch(words, "cherry");
        System.out.println("Index of 'cherry': " + index1);

        // Search for non-existing string
        int index2 = Collections.binarySearch(words, "grape");
        System.out.println("Index of 'grape': " + index2);

        // Case-sensitive search
        int index3 = Collections.binarySearch(words, "Cherry");
        System.out.println("Index of 'Cherry': " + index3);
    }
}

此示例演示了使用二分查找的字符串搜索。请注意,字符串比较区分大小写。未找到 'Cherry'(以大写 C 开头),因为列表中包含小写字符串。

输出显示了成功和不成功的搜索。 'grape' 的负返回值表示它不存在,但将被插入到末尾。

使用自定义对象的 binarySearch

此示例演示了 binarySearch 与自定义对象的使用。对象必须实现 Comparable,或者我们必须提供一个 Comparator。在这里,我们使用 Comparable 定义的自然排序。

CustomObjectBinarySearch.java
package com.zetcode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

record Person(String name, int age) implements Comparable<Person> {
    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}

public class CustomObjectBinarySearch {

    public static void main(String[] args) {

        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 25));
        people.add(new Person("Bob", 30));
        people.add(new Person("Charlie", 22));
        people.add(new Person("David", 35));

        // List must be sorted by the same criteria used in compareTo
        Collections.sort(people);

        // Search by name (natural ordering)
        Person key = new Person("Charlie", 0);
        int index = Collections.binarySearch(people, key);

        if (index >= 0) {
            System.out.println("Found: " + people.get(index));
        } else {
            System.out.println("Not found. Insertion point: " + (-index - 1));
        }
    }
}

此代码显示了使用自定义 Person 对象的二分查找。Person 实现了 Comparable,以按名称定义自然排序。在搜索之前,我们必须对列表进行排序。

输出演示了按名称的成功搜索。搜索键中的年龄无关紧要,因为仅使用名称进行比较。

使用 Comparator 的 binarySearch

此示例展示了如何将 binarySearch 与自定义 Comparator 一起使用。当我们希望按与自然排序不同的标准进行搜索时,这很有用。

ComparatorBinarySearch.java
package com.zetcode;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


record Product(String name, BigDecimal price) {}

public class ComparatorBinarySearch {

    public static void main(String[] args) {

        List<Product> products = new ArrayList<>();
        products.add(new Product("Laptop", new BigDecimal("999.99")));
        products.add(new Product("Phone", new BigDecimal("699.99")));
        products.add(new Product("Tablet", new BigDecimal("399.99")));
        products.add(new Product("Monitor", new BigDecimal("249.99")));

        // Sort by price using comparator
        Comparator<Product> priceComparator = Comparator.comparing(Product::price);
        products.sort(priceComparator);

        // Search by price
        Product searchKey = new Product("", new BigDecimal("399.99"));
        int index = Collections.binarySearch(products, searchKey, priceComparator);

        if (index >= 0) {
            System.out.println("Found at index " + index + ": " + products.get(index));
        } else {
            System.out.println("Product with price $399.99 not found");
        }
    }
}

此示例演示了使用自定义比较器按价格进行搜索。产品按价格排序,允许我们按价格范围进行搜索。搜索键中的产品名称无关紧要。

输出显示了按价格的成功搜索。比较器确保在二分查找操作期间仅比较价格值。

使用重复项的 binarySearch

当列表包含重复项时,binarySearch 不保证将返回哪个重复项的索引。此示例显示了具有重复元素的行为。

DuplicateBinarySearch.java
package com.zetcode;

import java.util.Collections;
import java.util.List;

public class DuplicateBinarySearch {

    public static void main(String[] args) {

        List<Integer> numbers = List.of(1, 2, 2, 2, 3, 4, 5);

        // Search for duplicate value
        int index = Collections.binarySearch(numbers, 2);
        System.out.println("Index of 2: " + index);

        // The exact index among duplicates is not guaranteed
        System.out.println("All indices of 2:");
        for (int i = 0; i < numbers.size(); i++) {
            if (numbers.get(i) == 2) {
                System.out.println(i);
            }
        }
    }
}

此代码演示了二分查找在重复项方面的行为。该方法可以返回该值出现的任何索引。为了找到所有出现,需要额外的处理。

输出显示了找到该值的一个可能索引,然后是它出现的所有索引。这突出了二分查找对重复项的限制。

binarySearch 性能

此示例比较了二分查找与线性搜索的性能。对于大型集合,二分查找的 O(log n) 复杂度使其快得多。

BinarySearchPerformance.java
package com.zetcode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BinarySearchPerformance {

    public static void main(String[] args) {
        
        List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            numbers.add(i);
        }
        
        // Binary search timing
        long startTime = System.nanoTime();
        int index1 = Collections.binarySearch(numbers, 999999);
        long endTime = System.nanoTime();
        System.out.println("Binary search time: " + (endTime - startTime) + " ns");
        
        // Linear search timing
        startTime = System.nanoTime();
        int index2 = -1;
        for (int i = 0; i < numbers.size(); i++) {
            if (numbers.get(i) == 999999) {
                index2 = i;
                break;
            }
        }
        endTime = System.nanoTime();
        System.out.println("Linear search time: " + (endTime - startTime) + " ns");
    }
}

此示例测量了二分查找和线性搜索之间的时间差。对于更大的集合,差异变得更加明显。二分查找对于已排序的数据显然更优越。

来源

Java Collections.binarySearch 文档

在本文中,我们深入探讨了 Java Collections.binarySearch 方法。我们涵盖了基本用法、字符串搜索、自定义对象、比较器、重复项和性能。理解二分查找对于在已排序集合中进行高效的数据检索至关重要。

作者

我的名字是 Jan Bodnar,我是一位经验丰富的程序员,在该领域拥有多年的经验。我从 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和 8 本电子书。凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程