.toArray (новый MyClass [0]) или .toArray (новый MyClass [myList.size ()])? - PullRequest
130 голосов
/ 06 октября 2008

Если у меня есть ArrayList

ArrayList<MyClass> myList;

И я хочу позвонить в Array, есть ли причина производительности для использования

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

над

MyClass[] arr = myList.toArray(new MyClass[0]);

Я предпочитаю второй стиль, поскольку он менее многословен, и я предполагал, что компилятор позаботится о том, чтобы пустой массив на самом деле не создавался, но мне было интересно, правда ли это.

Конечно, в 99% случаев это не имеет значения, так или иначе, но я хотел бы сохранить согласованный стиль между моим нормальным кодом и оптимизированными внутренними циклами ...

Ответы [ 9 ]

120 голосов
/ 06 октября 2008

Начиная с ArrayList в Java 5 , массив будет заполнен уже, если он имеет правильный размер (или больше). Следовательно

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

создаст один объект массива, заполнит его и вернет его в «arr». С другой стороны

MyClass[] arr = myList.toArray(new MyClass[0]);

создаст два массива. Второй - это массив MyClass с длиной 0. Таким образом, существует объект для объекта, который будет немедленно отброшен. Поскольку исходный код предполагает, что компилятор / JIT не может оптимизировать этот, чтобы он не был создан. Кроме того, использование объекта нулевой длины приводит к приведению типов в методе toArray ().

См. Источник ArrayList.toArray ():

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Используйте первый метод, чтобы создать только один объект, и избегайте (неявных, но, тем не менее, дорогостоящих) приведений.

72 голосов
/ 04 апреля 2015

Противоречиво, самая быстрая версия, на Hotspot 8, это:

MyClass[] arr = myList.toArray(new MyClass[0]);

Я запустил микро-бенчмарк, используя jmh, результаты и код приведены ниже, показывая, что версия с пустым массивом стабильно превосходит версию с предварительно определенным массивом. Обратите внимание, что если вы можете повторно использовать существующий массив правильного размера, результат может отличаться.

Результаты тестов (оценка в микросекундах, меньше = лучше):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025 ▒ 0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155 ▒ 0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512 ▒ 0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884 ▒ 0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147 ▒ 0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977 ▒ 5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019 ▒ 0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133 ▒ 0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075 ▒ 0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318 ▒ 0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652 ▒ 0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692 ▒ 8.957  us/op

Для справки, код:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

Подобные результаты, полный анализ и обсуждение можно найти в блоге Массивы Древних . Подводя итог: компилятор JVM и JIT содержит несколько оптимизаций, которые позволяют ему дешево создавать и инициализировать новый массив правильного размера, и эти оптимизации нельзя использовать, если вы создаете массив самостоятельно.

14 голосов
/ 06 октября 2008

Современные JVM оптимизируют конструкцию отражательного массива в этом случае, поэтому разница в производительности незначительна. Называть коллекцию дважды в таком шаблонном коде не очень хорошая идея, поэтому я бы избегал первого метода. Еще одним преимуществом второго является то, что он работает с синхронизированными и одновременными коллекциями. Если вы хотите выполнить оптимизацию, повторно используйте пустой массив (пустые массивы неизменны и могут использоваться совместно) или используйте профилировщик (!).

12 голосов
/ 07 июня 2018

Из проверки JetBrains Intellij Idea:

Существует два стиля для преобразования коллекции в массив: либо с использованием массив предварительно заданного размера (например, c.toArray (new String [c.size ()]) ) или использование пустого массива (например, c.toArray (new String [0]) .

In более старые версии Java, использующие предварительно заданный размер, были рекомендованы как вызов отражения, который необходим для создания массива правильного размера было довольно медленно Однако с момента поздних обновлений OpenJDK 6 этот вызов был встроен, что делает производительность пустой версии массива то же самое, а иногда даже лучше, по сравнению с предварительно версия. Также передача предварительно заданного размера опасна для одновременного или Синхронный сбор как гонка данных возможна между size и toArray вызов, который может привести к дополнительным нулям в конце массива, если коллекция была одновременно сокращена во время операции.

Эта проверка позволяет единый стиль: либо использование пустого массива (что рекомендуется в современная Java) или использование массива предварительно заданного размера (который может быть быстрее в более старые версии Java или JVM, не основанные на HotSpot).

4 голосов
/ 06 октября 2008

Первый случай более эффективен.

Это потому, что во втором случае:

MyClass[] arr = myList.toArray(new MyClass[0]);

среда выполнения фактически создает пустой массив (с нулевым размером), а затем внутри метода toArray создает другой массив для соответствия фактическим данным. Это создание выполняется с использованием отражения с использованием следующего кода (взятого из jdk1.5.0_10):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Используя первую форму, вы избегаете создания второго массива, а также избегаете кода отражения.

3 голосов
/ 06 октября 2008

toArray проверяет, что переданный массив имеет правильный размер (то есть достаточно большой, чтобы вместить элементы из вашего списка) и, если это так, использует это. Следовательно, если размер массива будет меньше требуемого, будет создан рефлексивно новый массив.

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

Возможно, более быстрая версия - передать массив правильного размера, но если вы не можете доказать , этот код является узким местом в производительности, предпочитайте удобочитаемость производительности, пока не доказано иное.

0 голосов
/ 20 августа 2013

Второй вариант немного читабелен, но улучшений так мало, что оно того не стоит. Первый способ быстрее, без недостатков во время выполнения, поэтому я использую его. Но я пишу это вторым способом, потому что это быстрее печатать. Тогда моя IDE помечает это как предупреждение и предлагает исправить это. Одним нажатием клавиши он преобразует код из второго типа в первый.

0 голосов
/ 03 августа 2013

пример кода для целого числа:

Integer[] arr = myList.toArray(new integer[0]);
0 голосов
/ 06 октября 2008

Использование 'toArray' с массивом правильного размера будет работать лучше, так как альтернатива создаст сначала массив нулевого размера, а затем массив правильного размера. Однако, как вы говорите, разница, вероятно, будет незначительной.

Также обратите внимание, что компилятор javac не выполняет никакой оптимизации. В настоящее время все оптимизации выполняются компиляторами JIT / HotSpot во время выполнения. Я не знаю каких-либо оптимизаций вокруг 'toArray' в каких-либо JVM.

Таким образом, ответ на ваш вопрос в значительной степени зависит от стиля, но для согласованности должен являться частью любых стандартов кодирования, которых вы придерживаетесь (документированных или иных).

...