Статьи

Обнаружение Трофея Обработки Коллекции Примитивов Java

Читая пост в блоге « 5 советов по сокращению накладных расходов на сборку мусора на Java» , я вспомнил о существовании небольшой библиотеки сборок Java под названием Trove, которая «обеспечивает высокоскоростные регулярные и примитивные сборки для Java». Меня особенно интересует возможность применять Trove для разрешения коллекций примитивов, а не требовать, чтобы элементы в коллекциях были полноценными ссылочными объектами. Я смотрю на Trove более подробно в этом посте.

Стандартные коллекции JDK уважают дженерики и требуют ссылки на объекты для своих элементов и не позволяют хранить примитивы в коллекциях. Даже код, который, кажется, помещает примитивы в стандартные коллекции JDK, фактически помещает ссылки на объекты в коллекцию через автобокс . Преимущество этого универсального подхода заключается в возможности работать с одинаковыми классами и методами над объектами разных типов. Стоимость — это необходимость хранить полные эталонные объекты, даже когда могут быть сохранены более простые примитивы.

Библиотека Trove имеет лицензию LPGL и является относительно небольшой (значительно меньше 10 МБ), как показано на следующем снимке экрана страницы «Загрузки»:

troveDownloads-20151219

Небольшая загрузка содержит больше, чем просто необходимую библиотеку в формате JAR. Он также содержит документацию и источник. Сам JAR библиотеки (в данном случае trove-3.1a1.jar ) имеет размер около 2,5 МБ.

troveJarSize

Одна из причин, по которой Trove прост в использовании, заключается в том, что он в значительной степени имитирует интерфейсы коллекций JDK в API своих собственных коллекций. Следующий листинг кода демонстрирует, как добавление значений в реализацию List по сути является теми же вызовами API, независимо от того, используете ли они JDK 7 List (в данном случае ArrayList) или предоставленный Trove TDoubleArrayList .

Добавление элементов в ArrayList JDK и TDoubleArrayList Trove

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Demonstrate standard JDK {@code ArrayList<Double>}
 * with some JDK 8 functionality.
 */
public void demonstrateJdkArrayListForDoubles()
{
   final ArrayList<Double> doubles = new ArrayList<>();
   doubles.add(15.5);
   doubles.add(24.4);
   doubles.add(36.3);
   doubles.add(67.6);
   doubles.add(10.0);
   out.println("JDK ArrayList<Double>:");
   out.println("\tDoubles List: " + doubles);
   out.println("\tMaximum double: " + doubles.stream().max(Double::compare));
   out.println("\tMinimum double: " + doubles.stream().min(Double::compare));
   out.println("\tSum of doubles: " + doubles.stream().mapToDouble(Double::doubleValue).sum());
}
 
/**
 * Demonstrate use of TDoubleArrayList and show how
 * similar using it is to using {@code ArrayList<Double>}.
 */
public void demonstrateTroveArrayListForDoubles()
{
   // Demonstrate adding elements to TDoubleArrayList is
   // exactly like adding elements to ArrayList<Double>.
   final TDoubleArrayList doubles = new TDoubleArrayList();
   doubles.add(15.5);
   doubles.add(24.4);
   doubles.add(36.3);
   doubles.add(67.6);
   doubles.add(10.0);
   out.println("Trove TDoubleArrayList:");  // TDoubleArrayList overrides toString()
   out.println("\tDoubles List: " + doubles);
   out.println("\tMaximum double: " + doubles.max());
   out.println("\tMinimum double: " + doubles.min());
   out.println("\tSum of doubles: " + doubles.sum());
}

Приведенный выше листинг кода также демонстрирует, насколько легко с реализацией Trove списка массивов получить доступ к максимуму, минимуму и сумме коллекции двойников. Одним из преимуществ этих коллекций, записанных для конкретного примитивного типа данных (в данном случае удваивается), является то, что в реализации могут быть предоставлены методы, которые применяются конкретно к этому типу данных. Хотя это может не иметь большого смысла для коллекции String или коллекции произвольного объекта, возвращающего максимум, минимум и суммы, смысл этих методов очевиден для коллекции, посвященной значениям типа double, таким как TDoubleArrayList . Приведенный выше листинг показывает, как этого можно добиться с помощью JDK 8 с использованием потоков .

Одно тонкое отличие, которое может быть неочевидным (из-за автобоксирования) при рассмотрении перечисленного выше кода, состоит в том, что реализация ArrayList реализации JDK хранит ссылочные объекты Double то время как реализация Trove TDoubleArrayList хранит примитивные double s. Trove предоставляет реализации списков, наборов и карт для различных числовых типов, таких как байты, символы, шорты, целые числа, длинные, плавающие и двойные числа.

Одной из интересных структур / коллекций данных, предоставляемых Trove, является TDoubleArrayStack . Опираясь на только что продемонстрированный TDoubleArrayList , TDoubleArrayStack не предоставляет методы add в своем API для добавления элементов. Скорее, его методы отражают семантику, которую можно ожидать в реализации стека «последний пришел-первым-вышел» (LIFO): push (double) для добавления, pop () для доступа и удаления наиболее недавно добавленной записи и peek () для просмотра последняя добавленная запись без ее удаления. Применение этой реализации стека показано в следующем листинге кода. Существуют реализации стека и для других числовых типов данных.

Trove’s TDoubleArrayStack

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Demonstrate Trove's Double Array Stack.
 *
 * Trove's TDoubleArrayStack allows access to its
 * contents via push, pop, and peek.
 */
public void demonstrateTroveDoubleArrayStack()
{
   final TDoubleArrayStack stack = new TDoubleArrayStack();
   stack.push(15.5);
   stack.push(17.3);
   stack.push(16.6);
   stack.push(2.2);
   out.println("Trove Array Stack of Doubles");
   out.println("\tPeek: " + stack.peek() + "; After Size: " + stack.size());
   out.println("\tPop:  " + stack.pop() + "; After Size: " + stack.size());
   out.println("\tPeek: " + stack.peek() + "; After Size: " + stack.size());
}

Хотя здесь и не показано, Trove также поддерживает структуры очередей « первым пришел -первым вышел» (FIFO) для примитивных типов Java в своем пакете gnu.trove.queue . Классы в этом пакете предоставляют методы, соответствующие семантике очереди: предложение , опрос и просмотр .

Класс java.util.Collections предоставляет много полезных функций при работе с коллекциями JDK. Trove предоставляет подмножество функциональных возможностей java.util.Collections для работы с коллекциями на основе Trove в своем собственном классе под названием gnu.trove.TCollections . В частности, на момент написания этой TCollections класс TCollections обеспечивает поддержку синхронизированных и неизмененных коллекций Trove. В следующем листинге кода демонстрируется использование TCollections а также демонстрируется использование коллекции Trove, ориентированной на тип данных, отличный от double (в данном случае int ), и на другой тип структуры данных (связанный список).

TCollections и TIntLinkedList продемонстрированы

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Demonstrate one of Trove's "equivalent"s of the
 * java.util.Collections class.
 */
public void demonstrateTroveCollectionsClass()
{
   final TIntLinkedList integers = new TIntLinkedList();
   integers.add(5);
   integers.add(7);
   integers.add(3);
   integers.add(1);
   final TIntList unmodifiableIntegers = TCollections.unmodifiableList(integers);
   try
   {
      unmodifiableIntegers.add(15);
   }
   catch (Exception ex)
   {
      out.println("\tException caught: " + ex);
   }
}

Когда кто-то хочет перебрать коллекцию на основе Trove, он может получить к ней доступ через традиционный итератор, как показано в следующем листинге кода. Хотя в этом примере коллекция и связанный с ней итератор работают с long значениями, Trove предоставляет аналогичные коллекции и итераторы для других примитивных типов данных Java.

Использование Trove Iterator для итерации коллекции Trove

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * Demonstrate "traditional" iteration of a
 * Trove collection.
 */
public void demonstrateIterationWithIterator()
{
   final TLongHashSet longs = new TLongHashSet();
   longs.add(15);
   longs.add(6);
   longs.add(12);
   longs.add(13);
   longs.add(2);
   TLongIterator longIterator = longs.iterator();
   while (longIterator.hasNext())
   {
      final long longValue = longIterator.next();
      out.println(longValue);
   }
}

Альтернативный подход для итерации коллекции Trove заключается в использовании процедуры . Это продемонстрировано в следующих двух листингах кода. Первый листинг демонстрирует пользовательскую длинноориентированную процедуру, а второй листинг демонстрирует применение этой пользовательской процедуры к итерации TLongLinkedList через ее метод forEach .

Использование процедуры Trove для итерации коллекции Trove

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
 * Demonstrate iteration of a Trove collection
 * using a Procedure.
 */
public void demonstrateIterationWithProcedure()
{
   final TLongLinkedList longs = new TLongLinkedList();
   longs.add(15);
   longs.add(6);
   longs.add(12);
   longs.add(13);
   longs.add(2);
   longs.forEach(new StandardOutputLongProcedure());
}

Реализация процедуры, использованная в предыдущем примере итерации

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package dustin.examples.trove;
 
import static java.lang.System.out;
 
import gnu.trove.procedure.TLongProcedure;
 
/**
 * Simple implementation of TLongProcedure that
 * iterates over Trove collection of {@code long}
 * values and writes those values to standard
 * output with one value per line.
 */
public class StandardOutputLongProcedure implements TLongProcedure
{
   @Override
   public boolean execute(long longValue)
   {
      out.println(longValue);
      return true;
   }
}

Стоит отметить, что коллекции Trove, как правило, предоставляют методы forEachDescending, чтобы обеспечить итерацию в обратном порядке.

Дополнительные замечания, связанные с GNU Trove

  • GNU Trove — это библиотека, предоставляющая «высокоскоростные регулярные и примитивные коллекции для Java», и ее не следует путать с Trove, который представляет собой « База данных как сервис для OpenStack ».
  • Все коллекции и структуры данных Trove имеют имена с префиксом «T» (для Trove). Фактически, все классы и интерфейсы в Trove начинаются с буквы «T», кроме HashingStrategy , IdentityHashingStrategy и Version .
  • Коллекции Trove обычно предоставляют конструктор, принимающий массив своего базового типа данных, и предоставляют методы toArray() для предоставления своих элементов данных в виде массива примитивов.
  • Коллекции Trove обычно предоставляют явно переопределенные реализации toString() которые позволяют легко записывать отдельные элементы данных, аналогично коллекциям JDK и иначе, чем массивы Java (для которых требуются методы Arrays.toString () ).
  • Дополнительные сведения о Trove можно найти в обзоре , FAQ и форумах сообщений . Другие ресурсы включают в себя « Повышение производительности коллекций с этим Treasure Trove» , « Java HashMap Performance» , « Высокопроизводительные библиотеки в Java» и TROVE — «Высокопроизводительные коллекции для Java» .
  • Java-пакеты Trove обычно организованы по типу структуры данных со всеми реализациями, специфичными для примитивного типа, для данного типа структуры данных в одном и том же пакете. Например, пакеты называются как gnu.trove.list , gnu.trove.set и так далее.
  • Поскольку каждая коллекция Trove специфична для конкретного примитивного типа данных, каждая коллекция не требует универсального параметра и не имеет никаких проблем, связанных с универсальными (например, стирание). Этот подход также позволяет каждой коллекции поддерживать методы, специфичные для типа данных, которые хранятся в этой коллекции. Например, коллекции числовых типов могут предоставлять методы sum то время как коллекции, специфичные для типов символов, могут предоставлять методы grep .

Вывод

Во многих распространенных случаях коллекции, предоставляемые JDK, будут работать достаточно хорошо, и сохранение ссылок на объекты может не быть проблемой. Однако существуют случаи, когда возможность использовать коллекции Trove и, в частности, хранить примитивы, а не ссылки на объекты, может обеспечить необходимое преимущество. Преимущество хранения примитивов, а не их эквивалентных ссылок на объекты в коллекциях, становится все более очевидным по мере увеличения коллекции.