Простой способ объединить два байтовых массива

249

Каков простой способ объединения двух byteмассивов?

Сказать,

byte a[];
byte b[];

Как объединить два byteмассива и сохранить его в другом byteмассиве?

androidGuy
источник
3
Обратите внимание , пожалуйста , что Apache Commons, гуавы Google, System.arrayCopy, ByteBufferи тому - не так эффективно , но читаемым - ByteArrayOutputStreamвсе были охвачены. У нас есть более 7 вариантов ответов, приведенных здесь. Пожалуйста, не размещайте больше дупов.
Мартен Бодевес

Ответы:

317

Самое простое:

byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
Джонатан
источник
377

Самый элегантный способ сделать это с ByteArrayOutputStream.

byte a[];
byte b[];

ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write( a );
outputStream.write( b );

byte c[] = outputStream.toByteArray( );
Kevin
источник
61
@vipw Причина, по которой это элегантно, заключается в том, что если / когда вы хотите объединить третий массив позже, вы просто добавляете строку outputStream.write( c );- вам не нужно возвращаться и редактировать строку, где вы создаете результирующий байтовый массив. Кроме того, переупорядочение массивов просто, в отличие от использования метода arraycopy.
Уэйн Урода
2
Кроме того, это намного проще при работе с более чем 2-байтовыми массивами.
gardarh
3
Потеря процессора и памяти зависит от того, как часто вы выполняете операцию. Если это миллиард раз в секунду - конечно, оптимизируйте его. В противном случае удобочитаемость и ремонтопригодность могут быть выигрышными соображениями.
Викингстев
5
Если потребление памяти и / или производительность является проблемой, обязательно используйте в a.length + b.lengthкачестве аргумента для ByteArrayOutputStreamконструктора. Обратите внимание, что этот метод все равно будет копировать все байты в новый массив для назначения c[]! Рассмотрим ByteBufferметод близкого соперника, который не тратит память.
Мартен Бодевес
Я не могу сказать, что это всего лишь фрагмент кода. Здесь нет объяснения основополагающих частей, и это та часть, которая меня волнует (и я думаю, что большинство людей будут). Я бы с радостью высказал это, если бы было сравнение производительности между System # arrayCopy (Object, int, Object, int, int) и ByteArrayOutputStream # put (byte []), и подробно описал, какой сценарий лучше подходит для обоих вариантов. Кроме того, как говорится, ответ также должен включать arrayCopy, поскольку это еще одно решение.
searchengine27
66

Вот хорошее решение с использованием гуавы «s com.google.common.primitives.Bytes:

byte[] c = Bytes.concat(a, b);

Самое замечательное в этом методе то, что он имеет подпись varargs:

public static byte[] concat(byte[]... arrays)

Это означает, что вы можете объединить произвольное количество массивов за один вызов метода.

Золтан
источник
30

Другая возможность использования java.nio.ByteBuffer.

Что-то вроде

ByteBuffer bb = ByteBuffer.allocate(a.length + b.length + c.length);
bb.put(a);
bb.put(b);
bb.put(c);
byte[] result = bb.array();

// or using method chaining:

byte[] result = ByteBuffer
        .allocate(a.length + b.length + c.length)
        .put(a).put(b).put(c)
        .array();

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

kalefranz
источник
3
@click_whir Извините, но ReadTheDocs. ByteBuffer.allocate(int)является статическим методом, который возвращает экземпляр java.nio.HeapByteBufferкласса ByteBuffer. .put()И .compact()методы - и любые другие абстрактно-Несс - позаботятся.
kalefranz
@kalefranz Удалена compact()строка, так как она неверна.
Мартен Бодьюз
1
Будьте осторожны при использовании метода array () ByteBuffer - если вы не знаете, что делаете, и удобство сопровождения не является проблемой, нет никаких гарантий, что нулевая позиция в байтовом буфере всегда соответствует индексу 0 байтового массива. Смотрите здесь . Я решаю это путем выдачи bb.flip(); bb.get(result);вместо byte[] result = bb.array();линии.
DarqueSandu
1
@DarqueSandu Несмотря на то, что в целом это хороший совет , внимательное прочтение allocateметода показывает следующее: «Позиция нового буфера будет нулевой, его предел будет его емкостью, его метка будет неопределенной, а каждый из его элементов будет инициализирован нулем. У него будет резервный массив, а смещение его массива будет равно нулю. " Так что для этого конкретного фрагмента кода, где ByteBufferон размещен внутри, это не проблема.
Maarten Bodewes
13

Другой способ - использовать служебную функцию (вы можете сделать это статическим методом универсального служебного класса, если хотите):

byte[] concat(byte[]...arrays)
{
    // Determine the length of the result array
    int totalLength = 0;
    for (int i = 0; i < arrays.length; i++)
    {
        totalLength += arrays[i].length;
    }

    // create the result array
    byte[] result = new byte[totalLength];

    // copy the source arrays into the result array
    int currentIndex = 0;
    for (int i = 0; i < arrays.length; i++)
    {
        System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length);
        currentIndex += arrays[i].length;
    }

    return result;
}

Вызовите так:

byte[] a;
byte[] b;
byte[] result = concat(a, b);

Он также будет работать для объединения 3, 4, 5 массивов и т. Д.

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

Уэйн Урода
источник
11
byte[] result = new byte[a.length + b.length];
// copy a to result
System.arraycopy(a, 0, result, 0, a.length);
// copy b to result
System.arraycopy(b, 0, result, a.length, b.length);
James.Xu
источник
Тот же ответ, что и принятый, и извините, на 5 минут позже.
Мартен Бодевес
11

Если вы предпочитаете ByteBufferкак @kalefranz, всегда есть возможность объединить два byte[](или даже больше) в одну строку, например так:

byte[] c = ByteBuffer.allocate(a.length+b.length).put(a).put(b).array();
Зевс
источник
Тот же ответ, что и этот, но с опозданием более чем на 1 год. Использует метод цепочки, но это было бы лучше вставить в существующий ответ.
Мартен Бодевес
11

Вы можете использовать сторонние библиотеки для чистого кода, такие как Apache Commons Lang, и использовать их следующим образом:

byte[] bytes = ArrayUtils.addAll(a, b);
Томаш Прзыбыльски
источник
1
Я пытался ArrayUtils.addAll(a, b)и byte[] c = Bytes.concat(a, b), но последний быстрее.
Карлос Андрес Гарсия
Может быть. Я не знаю библиотеку Гуавы, так что если она есть, то лучше ее использовать. Вы проверили это для очень больших массивов?
Томаш Прзыбыльски
1
Когда я проводил тест, массив The Firts имел длину 68 элементов и вторую длину 8790688.
Карлос Андрес Гарсия
5

Для двух или нескольких массивов можно использовать этот простой и чистый служебный метод:

/**
 * Append the given byte arrays to one big array
 *
 * @param arrays The arrays to append
 * @return The complete array containing the appended data
 */
public static final byte[] append(final byte[]... arrays) {
    final ByteArrayOutputStream out = new ByteArrayOutputStream();
    if (arrays != null) {
        for (final byte[] array : arrays) {
            if (array != null) {
                out.write(array, 0, array.length);
            }
        }
    }
    return out.toByteArray();
}
Jeroen Meulemeester
источник
1
Это пустая трата памяти. Этот метод был бы приемлем для двух меньших массивов, но он определенно облагает налогом сборщик мусора за большее количество массивов.
Мартен Бодевес
1

Объединить два байтовых массива PDF

Если вы объединяете два байтовых массива, которые содержат PDF, эта логика не будет работать. Нам нужно использовать сторонний инструмент, такой как PDFbox от Apache:

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
mergePdf.addSource(new ByteArrayInputStream(a));
mergePdf.addSource(new ByteArrayInputStream(b));
mergePdf.setDestinationStream(byteArrayOutputStream);
mergePdf.mergeDocuments();
c = byteArrayOutputStream.toByteArray();
Бала Виньеш Б
источник
немного не по теме на этот вопрос, но это именно то, что я искал.
Амос
1

Если вы не хотите связываться с размерами массивов, просто используйте магию конкатенации строк:

byte[] c = (new String(a, "l1") + new String(b, "l1")).getBytes("l1");

Или определите где-нибудь в своем коде

// concatenation charset
static final java.nio.charset.Charset cch = java.nio.charset.StandardCharsets.ISO_8859_1;

и использовать

byte[] c = (new String(a, cch) + new String(b, cch)).getBytes(cch);

Это, конечно, также работает с более чем двумя конкатенациями строк с использованием +оператора сложения.


И то "l1"и другое ISO_8859_1указывает на набор символов западной латиницы 1, который кодирует каждый символ в виде одного байта. Поскольку многобайтовые переводы не выполняются, символы в строке будут иметь те же значения, что и байты (за исключением того, что они всегда будут интерпретироваться как положительные значения, как charбез знака). Поэтому, по крайней мере, для предоставляемой Oracle среды выполнения любой байт будет правильно «декодирован», а затем снова «закодирован».

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

Джон Макклейн
источник
@MaartenBodewes Если вы не уверены в «l1» (что является псевдонимом ISO 8859-1), не используйте слово «конечно». Какое конкретное значение байта будет уничтожено? Что касается использования памяти, вопрос был о простом способе объединения двухбайтовых массивов, а не о наиболее эффективном по памяти.
Джон Макклейн
1
Я записал несколько предупреждений и провел тестирование. Для Latin 1 и Oracle, предоставленного runtime (11), это действительно работает. Таким образом, я предоставил дополнительную информацию и удалил свой комментарий и downvote. Я надеюсь, что это нормально для вас, в противном случае, пожалуйста, откат.
Maarten Bodewes
0

Это мой способ сделать это!

public static byte[] concatByteArrays(byte[]... inputs) {
    int i = inputs.length - 1, len = 0;
    for (; i >= 0; i--) {
        len += inputs[i].length;
    }
    byte[] r = new byte[len];
    for (i = inputs.length - 1; i >= 0; i--) {
        System.arraycopy(inputs[i], 0, r, len -= inputs[i].length, inputs[i].length);
    }
    return r;
}

Особенности :

  • Используйте varargs ( ...) для вызова с любым числом байтов [].
  • Используйте System.arraycopy()это реализовано с машинным родным кодом, чтобы обеспечить высокую скорость работы.
  • Создайте новый байт [] с точным размером, который нужен.
  • Выделяют немного меньше intпеременных за счет повторного использования iи lenпеременные.
  • Более быстрое сравнение с константами.

Имейте в виду :

Лучший способ сделать это - скопировать код @Jonathan . Проблема связана с массивами собственных переменных, потому что Java создает новые переменные, когда этот тип данных передается другой функции.

Даниэль Де Леон
источник
1
Нет, это способ Уэйна , ты опоздал на 5 лет.
Maarten Bodewes
@MaartenBodewes Благодаря вам, я сегодня использую ваш комментарий для написания кода, теперь он отличается от других и имеет лучшую производительность.
Даниэль Де Леон
1
Я не уверен, что это будет иметь большое значение, учитывая, что размеры массива также не меняются во время выполнения, но теперь они, по крайней мере, отличаются от других решений.
Maarten Bodewes