我有一个自定义类的列表,我希望通过名称或数字搜索某些项目而不使用任何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
答案 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链接包含一个更好的示例,它使用动态数量的线程和执行程序的超时。以我的示例为基础,并按照文档获取优化版本。