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


249

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

Сказать,

byte a[];
byte b[];

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


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( );

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()просто возвращается вспомогательный массив, без учета смещения, положения или предела).


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

Если вы предпочитаете 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();
}

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.