Я ищу наиболее базовое решение для создания нескольких индексов в коллекции Java.
Конечно, я мог бы написать класс, который управляет несколькими Картами (это не сложно, но похоже, что вы изобретаете колесо). Поэтому я хотел бы знать, если это можно сделать без - при этом все еще простое использование, похожее на использование одной индексированной java.util.Map.
Это выглядит так, как будто мы ничего не нашли. Мне нравятся все ваши ответы - саморазвивающиеся версии, ссылки на базы данных, подобные библиотекам.
Вот то, что я действительно хочу: Чтобы иметь функциональность в (а) коллекциях Apache Commons или (b) в Google Collections/Guava. Или, может быть, очень хорошая альтернатива.
Другие пропустили эту функцию в этих библиотеках? Они предоставляют всевозможные вещи, такие как MultiMaps, MulitKeyMaps, BidiMaps,... Я чувствую, что он будет хорошо вписываться в эти библиотеки - его можно было бы назвать MultiIndexMap
. Как вы думаете?
Ответ 6
Вам нужно проверить Бун.:)
http://rick-hightower.blogspot.com/2013/11/what-if-java-collections-and-java.html
Вы можете добавить n число индексов поиска и индексов поиска. Он также позволяет эффективно запрашивать примитивные свойства.
Вот пример из вики (я автор).
repoBuilder.primaryKey("ssn")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").searchIndex("empNum", true)
.usePropertyForAccess(true);
Вы можете переопределить это, предоставив true флаг в качестве второго аргумента searchIndex.
Обратите внимание, что empNum - уникальный уникальный для поиска индекс.
Что делать, если было легко запросить сложный набор объектов Java во время выполнения? Что, если бы был API, который поддерживал ваши индексы объектов (на самом деле просто TreeMaps и HashMaps) в синхронизации.? Хорошо, тогда у вас будет репозиторий данных Boon. В этой статье показано, как использовать утилиты репозитория данных Boon для запроса объектов Java. Это часть первая. Может быть много, много частей.:)
Boo data repo делает запросы, основанные на индексах, намного проще.
Почему репозиторий данных Boon
Boo data repo позволяет обрабатывать коллекции Java больше как база данных, по крайней мере, когда дело доходит до запросов к коллекциям. Boo data repo не является базой данных в памяти и не может заменить организацию ваших объектов в структуры данных, оптимизированные для вашего приложения.
Если вы хотите потратить свое время на предоставление клиенту ценности и построение своих объектов и классов и использование API Collections для ваших структур данных, то DataRepo предназначен для вас. Это не исключает выхода из книг Кнута и оптимизации оптимизированной структуры данных. Это просто помогает сохранить мирские вещи, поэтому вы можете потратить свое время на то, чтобы сделать все возможное.
Рожденный из необходимости
Этот проект вышел из-за необходимости. Я работал над проектом, который планировал хранить большую коллекцию доменных объектов в памяти для скорости, и кто-то задал все важные вопросы, которые я забыл. Как мы будем запрашивать эти данные. Мой ответ заключался в том, что мы будем использовать API Collections и Streaming API. Тогда я попытался это сделать... Хммм...
Я также устал использовать API потока JDK 8 для большого набора данных, и он был медленным. (Репозиторий данных Boon работает с JDK7 и JDK8). Это был линейный поиск/фильтр. Это по дизайну, но для того, что я делал, это не сработало. Мне нужны индексы для поддержки произвольных запросов.
Репо данных Boon расширяет API потоковой передачи.
Репозиторий данных Boon не пытается заменить API потока JDK 8, и на самом деле он хорошо работает с ним. Boo data repo позволяет создавать индексированные коллекции. Индексы могут быть любыми (он подключается).
В настоящий момент индексы репо-данных Boon основаны на ConcurrentHashMap и ConcurrentSkipListMap.
По дизайну репозитория данных Boon работает со стандартными библиотеками коллекции. Не существует плана создания набора пользовательских коллекций. Нужно быть в состоянии подключить Guava, Concurrent Trees или Trove, если вы этого захотите.
Это обеспечивает упрощенный API для этого. Он позволяет линейно искать смысл завершения, но я рекомендую использовать его в первую очередь для использования индексов, а затем использовать API потоковой передачи для остальных (для обеспечения безопасности и скорости).
перед шагом шаг за шагом
Скажем, у вас есть метод, который создает 200 000 таких объектов:
List<Employee> employees = TestHelper.createMetricTonOfEmployees(200_000);
Итак, теперь у нас 200 000 сотрудников. Пусть их искать...
Первый перенос сотрудников в запрос на поиск:
employees = query(employees);
Теперь выполните поиск:
List<Employee> results = query(employees, eq("firstName", firstName));
Итак, в чем основное отличие между вышеописанным и потоковым API?
employees.stream().filter(emp -> emp.getFirstName().equals(firstName)
Примерно на 20 000% быстрее использовать Boon DataRepo! А сила HashMaps и TreeMaps.:)
Существует API, который выглядит так же, как и ваши встроенные коллекции. Существует также API, который больше похож на объект DAO или объект Repo.
Простой запрос с объектом Repo/DAO выглядит следующим образом:
List<Employee> employees = repo.query(eq("firstName", "Diana"));
Более сложный запрос будет выглядеть следующим образом:
List<Employee> employees = repo.query(
and(eq("firstName", "Diana"), eq("lastName", "Smith"), eq("ssn", "21785999")));
Или это:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), lte("salary", 200_000),
gte("salary", 190_000)));
Или даже это:
List<Employee> employees = repo.query(
and(startsWith("firstName", "Bob"), eq("lastName", "Smith"), between("salary", 190_000, 200_000)));
Или, если вы хотите использовать API потока JDK 8, это работает с ним не против него:
int sum = repo.query(eq("lastName", "Smith")).stream().filter(emp -> emp.getSalary()>50_000)
.mapToInt(b -> b.getSalary())
.sum();
Выше было бы намного быстрее, если бы число сотрудников было довольно большим. Это сузило бы сотрудников, чье имя началось со Смита и имело зарплату выше 50 000. Скажем, у вас было 100 000 сотрудников и только 50 названных Смитом, поэтому теперь вы быстро сокращаетесь до 50, используя индекс, который эффективно вытягивает 50 сотрудников из 100 000, тогда мы фильтруем только 50 вместо 100 000.
Вот эталонный пробег из репо-данных линейного поиска по сравнению с индексированным поиском в наносекундах:
Name index Time 218
Name linear Time 3709120
Name index Time 213
Name linear Time 3606171
Name index Time 219
Name linear Time 3528839
Кто-то недавно сказал мне: "Но с помощью потокового API вы можете запустить фильтр в parralel).
Посмотрите, как математика держится:
3,528,839 / 16 threads vs. 219
201,802 vs. 219 (nano-seconds).
Индексы выигрывают, но это был финал фотографии. НЕ!:)
Это было всего на 9500% быстрее, а не на 40 000% быстрее. Так близко.....
Я добавил еще несколько функций. Они интенсивно используют индексы.:)
repo.updateByFilter(значения (значение ( "firstName", "Di" )), и (eq ( "firstName", "Diana" ), eq ( "lastName", "Smith" ), eq ( "ssn", "21785999" )));
Выше было бы эквивалентно
ОБНОВЛЕНИЕ Сотрудник e SET e.firstName = 'Di' ГДЕ e.firstName = 'Диана' и e.lastName = 'Smith' и e.ssn = '21785999'
Это позволяет вам устанавливать сразу несколько полей на нескольких записях, поэтому, если вы делаете массовое обновление.
Существуют перегруженные методы для всех базовых типов, поэтому, если у вас есть одно значение для обновления для каждого элемента, возвращаемого из фильтра:
repo.updateByFilter("firstName", "Di",
and( eq("firstName", "Diana"),
eq("lastName", "Smith"),
eq("ssn", "21785999") ) );
Вот некоторые основные возможности выбора:
List <Map<String, Object>> list =
repo.query(selects(select("firstName")), eq("lastName", "Hightower"));
У вас может быть столько избранных, сколько хотите. Вы также можете отсортировать список:
List <Map<String, Object>> list =
repo.sortedQuery("firstName",selects(select("firstName")),
eq("lastName", "Hightower"));
Вы можете выбрать свойства связанных свойств (т.е. employee.department.name).
List <Map<String, Object>> list = repo.query(
selects(select("department", "name")),
eq("lastName", "Hightower"));
assertEquals("engineering", list.get(0).get("department.name"));
Вышеупомянутое попытается использовать поля классов. Если вы хотите использовать фактические свойства (emp.getFoo() vs. emp.foo), вам нужно использовать selectPropertyPath.
List <Map<String, Object>> list = repo.query(
selects(selectPropPath("department", "name")),
eq("lastName", "Hightower"));
Обратите внимание, что select ( "department", "name" ) намного быстрее, чем selectPropPath ( "department", "name" ), что может иметь значение в узком цикле.
По умолчанию все индексы поиска и индексы поиска позволяют дублировать (кроме индекса первичного ключа).
repoBuilder.primaryKey("ssn")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").searchIndex("empNum", true)
.usePropertyForAccess(true);
Вы можете переопределить это, предоставив true флаг в качестве второго аргумента searchIndex.
Обратите внимание, что empNum - уникальный уникальный для поиска индекс.
Если вы предпочитаете или нуждаетесь, вы можете получить даже простые запросы обратно в виде карт:
List<Map<String, Object>> employees = repo.queryAsMaps(eq("firstName", "Diana"));
Я не уверен, что это особенность или ошибка. Я думал, что, когда вы работаете с данными, вам нужно представить эти данные таким образом, чтобы не привязывать потребителей данных к вашему фактическому API. Наличие карты строк/базовых типов, по-видимому, является способом достижения этого.
Обратите внимание, что преобразование объекта в карту идет глубоко, как в:
System.out.println(employees.get(0).get("department"));
Урожайность:
{class=Department, name=engineering}
Это может быть полезно для отладки и специальных запросов для оснастки. Я рассматриваю возможность добавления поддержки для простого преобразования в строку JSON.
Добавлена возможность запрашивать свойства коллекции. Это должно работать с коллекциями и массивами, как глубоко вложенными, как вам нравится. Прочтите это снова, потому что это было реальное MF для реализации!
List <Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "metas3", "name3")),
eq("lastName", "Hightower"));
print("list", list);
assertEquals("3tag1", idx(list.get(0).get("tags.metas.metas2.metas3.name3"), 0));
Вывод из вышесказанного выглядит следующим образом:
list [{tags.metas.metas2.metas3.name3=[3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3,
3tag1, 3tag2, 3tag3, 3tag1, 3tag2, 3tag3]},
...
Я создал несколько классов отношений, чтобы проверить это:
public class Employee {
List <Tag> tags = new ArrayList<>();
{
tags.add(new Tag("tag1"));
tags.add(new Tag("tag2"));
tags.add(new Tag("tag3"));
}
...
public class Tag {
...
List<Meta> metas = new ArrayList<>();
{
metas.add(new Meta("mtag1"));
metas.add(new Meta("mtag2"));
metas.add(new Meta("mtag3"));
}
}
public class Meta {
...
List<Meta2> metas2 = new ArrayList<>();
{
metas2.add(new Meta2("2tag1"));
metas2.add(new Meta2("2tag2"));
metas2.add(new Meta2("2tag3"));
}
}
...
public class Meta2 {
List<Meta3> metas3 = new ArrayList<>();
{
metas3.add(new Meta3("3tag1"));
metas3.add(new Meta3("3tag2"));
metas3.add(new Meta3("3tag3"));
}
public class Meta3 {
...
Вы также можете выполнить поиск по типу:
List<Employee> results = sortedQuery(queryableList, "firstName", typeOf("SalesEmployee"));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
Приведенное выше показывает, что все сотрудники имеют простое имя класса SalesEmployee. Он также работает с полным именем класса, как в:
List<Employee> results = sortedQuery(queryableList, "firstName", typeOf("SalesEmployee"));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
Вы также можете выполнить поиск по соответствующему классу:
List<Employee> results = sortedQuery(queryableList, "firstName", instanceOf(SalesEmployee.class));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
Вы также можете запрашивать классы, реализующие определенные интерфейсы:
List<Employee> results = sortedQuery(queryableList, "firstName",
implementsInterface(Comparable.class));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
Вы также можете индексировать вложенные поля/свойства, и они могут быть полями сбора или полями нецелевых свойств, как глубоко вложенными, как вы хотели бы:
/* Create a repo, and decide what to index. */
RepoBuilder repoBuilder = RepoBuilder.getInstance();
/* Look at the nestedIndex. */
repoBuilder.primaryKey("id")
.searchIndex("firstName").searchIndex("lastName")
.searchIndex("salary").uniqueSearchIndex("empNum")
.nestedIndex("tags", "metas", "metas2", "name2");
Позже вы можете использовать nestedIndex для поиска.
List<Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "name2")),
eqNested("2tag1", "tags", "metas", "metas2", "name2"));
Безопасный способ использования nestedIndex - использовать eqNested. Вы можете использовать eq, gt, gte и т.д., Если у вас есть такой индекс:
List<Map<String, Object>> list = repo.query(
selects(select("tags", "metas", "metas2", "name2")),
eq("tags.metas.metas2.name2", "2tag1"));
Вы также можете добавить поддержку подклассов
List<Employee> queryableList = $q(h_list, Employee.class, SalesEmployee.class,
HourlyEmployee.class);
List<Employee> results = sortedQuery(queryableList, "firstName", eq("commissionRate", 1));
assertEquals(1, results.size());
assertEquals("SalesEmployee", results.get(0).getClass().getSimpleName());
results = sortedQuery(queryableList, "firstName", eq("weeklyHours", 40));
assertEquals(1, results.size());
assertEquals("HourlyEmployee", results.get(0).getClass().getSimpleName());
Репо данных имеет аналогичную функцию в своем методе DataRepoBuilder.build(...) для указания подклассов. Это позволяет вам без видимых полей запроса формировать подклассы и классы в той же коллекции репозитория или поиска.