在自定义arraylist中搜索效率最高

时间:2017-10-09 13:16:29

标签: java android loops

我有一个自定义类的列表,我希望通过名称或数字搜索某些项目而不使用任何for循环,因为它有大约100000个项目

class Contact{
    String name;
    String phone;
}

但是使用循环迭代需要很长时间才能完成,有没有有效的方法来做同样的事情?

public static void main(String[] args){
    String textToSearch = "name"; //or it can be number also
    List<Contact> list = new ArrayList<>();
    list.addAll(myOldListOfCustomContacts);
    for(Contact contact : list){
        if(contact.name.toLowerCase().contains(textToSearch) || contact.phone.toLowerCase().contains(textToSearch)){
            Log.e("TAG", "found");
        }
    }
}

在提出这个问题之前,我在StackOverFlow上搜索了它,但没有得到我的回答,比如 Most efficient way to see if an ArrayList contains an object in Java

4 个答案:

答案 0 :(得分:1)

在您的联系人姓名或电话号码内搜索子字符串时,这并不容易。你需要能够迅速将数百万候选人减少到可管理数量的东西。

如果您没有找到符合您需要的库,您可以进行三元组索引。让我们解释联系人姓名的想法(然后电话号码只是同一索引的第二个化身)。

创建一个HashMap<String,List<Contact>>,其中三字符为键,多个联系人为值(那些将三字符作为其名称的子字符串的联系人)。因此,对于每个联系人姓名中的每个三字符子串(trigraph),将此Contact添加到三字键下的列表中。

当您搜索具有给定子字符串的Contact时,请取搜索字符串的前三个字符,然后从地图中读取列表。这种查找速度很快,可以为您提供百万个原始元素的一小部分。然后依次检查您在该子集中找到的Contacts是否包含完整的搜索字符串。

除了主程序之外,您可能还需要支持名称或短于三个字符的搜索字符串等极端情况。并且根据典型的搜索字符串,可能更智能地选择搜索三字符,而不是仅仅取前三个字母,例如,所有搜索字符串三字符的结果的交集。

答案 1 :(得分:1)

此解决方案使用索引列表来帮助您使用Collections.binarySearch()

/**
 * Adds indexing on a list of type C given a field accessor that can extract a field of type T from a C.
 * 
 * Please ensure your list is `RandomAccess`.
 */
class Index<C, T extends Comparable<T>>
        extends AbstractList<T> implements List<T>, RandomAccess {
    // The actual list.
    final List<C> actual;
    // My sorted index to the list.
    final List<Integer> index;
    // The getter to get a T from a C
    final Function<C, T> getter;

    public Index(List<C> actual, Function<C, T> getField) {
        this.actual = actual;
        getter = getField;
        // Start the index mapping i -> i
        index = IntStream.rangeClosed(0, actual.size() - 1).boxed().collect(Collectors.toList());
        // Sort it on the actual.
        Collections.sort(index, (o1, o2) -> get(o1).compareTo(get(o2)));
    }

    @Override
    public T get(int index) {
        // Translate all access through the index.
        return getter.apply(actual.get(this.index.get(index)));
    }

    @Override
    public int size() {
        return index.size();
    }

    // Finds all occurrences of thing.
    public List<C> find(T thing) {
        // Is it there at all?
        int where = Collections.binarySearch(this, thing);
        if (where >= 0) {
            // Step back to ensure we aren't in the middle of a run.
            while (where > 0 && get(where - 1).equals(thing)) {
                where -= 1;
            }
            // Gather the list.
            List<C> found = new ArrayList<>();
            while (where < index.size() && get(where).equals(thing)) {
                found.add(actual.get(index.get(where++)));
            }
            return found;
        } else {
            return Collections.EMPTY_LIST;
        }
    }
}

// For demo.
class Contact {
    final String name;
    final String phone;

    public Contact(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    @Override
    public String toString() {
        return "Contact{" +
                "name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

private void test(String[] args) {
    // Sample list.
    List<Contact> list = new ArrayList<>();
    list.add(new Contact("Me", "1234"));
    list.add(new Contact("You", "5678"));
    list.add(new Contact("Us", "6666"));
    list.add(new Contact("Many", "6666"));
    list.add(new Contact("Them", "9999"));
    list.add(new Contact("Them", "99999"));
    list.add(new Contact("Them", "999999"));

    // Build sorted indexes by name and phone.
    Index<Contact, String> byName = new Index<>(list, Contact::getName);
    Index<Contact, String> byPhone = new Index<>(list, Contact::getPhone);

    // Use binary search to find a name and a phone.
    int me = Collections.binarySearch(byName, "Me");
    System.out.println(me + " -> " + list.get(me));
    int six = Collections.binarySearch(byPhone, "6666");
    System.out.println(six + " -> " + list.get(six));
    System.out.println("Them -> " + byName.find("Them"));
    System.out.println("6666 -> " + byPhone.find("6666"));
}

答案 2 :(得分:0)

1)您可以按名称或电话对列表进行排序,之后搜索会更容易(二分查找等)。

2)如果您只有唯一的联系人,则可以在找到结果时中断循环。

3)如果您有多个具有相同名称或电话的联系人,请尝试排序列表。并添加一个分区,就像所有具有A,B,C等名称的联系人一样。

4)如果您在“an”之后搜索“a”,在“ana”之后搜索。您可以使用子列表。像:

1步:找到所有带“a”的联系人并使用结果。

2步:不会从完整列表中搜索,您将从步骤1的结果中搜索。

3步:从步骤2的结果中搜索。

电话号码也一样。

答案 3 :(得分:0)

您可以使用优质的旧版ThreadPoolExecutor

在开始之前,我建议您阅读其他答案和评论,其中指出了有用的数据结构,可以在不需要多线程实用程序的情况下改进搜索功能。

正如@Xenolion所提到的,如果您从数据库加载联系人,会考虑直接查询而不会阻止您的UI Thread

如果您使用ThreadPoolExecutor,则基本上将Array划分为较小的块,每个块将由Thread处理。

您可以在Android Docs中找到详尽的示例。

您需要实现一个Runnable类,该类将被分配一个数据分区。

class Worker implements Runnable {

    // ...

    public Worker(final Contact[] data, final int start, final int end) {
         // have your workers hold a reference to the data array
         this.data = data;
         // ...
    }

    @Override public void run() {
         for (int i = this.start, i < this.end; i++) {
            if (this.data[i].contains(....) || ...) {
                this.notifySearchEnded(i);
                break;
            }
         }
         // found nothing...
    }

    private void notifySearchEnded(final int index) {
        // do whatever you want with the contact
        log("Runnable " + this.id + " has found the contact at index " + index);
        // hold somehow a reference to the executor
        EXECUTOR.shutdown(); // tell the executor to stop the other threads
    }
}

然后,您将实例化ExecutorService并执行Worker Threads

    private static final int CORES = Runtime.getRuntime().availableProcessors();

    public static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(CORES);

将阵列拆分为更小的块。

final int dataSize = data.lenght; // let's say 76'513

final int step = (int) (dataSize / CORES); // 76'513 / 4 = 19128

for (int i = 0; i < CORES; i++) {
    final int start = i * step;
    // give the left-over data to the last thread
    final int end = (i == (CORES - 1)) ? (dataSize - 1) : (start + step - 1);
    EXECUTOR.execute(new Worker(this.data, start, end));
}

我的例子不是很有效率。 上面提到的Android链接包含一个更好的示例,它使用动态数量的线程和执行程序的超时。以我的示例为基础,并按照文档获取优化版本。

更多示例,here和(使用Futures),herehere