Простий спосіб об'єднати два байтові масиви


249

Який простий спосіб об'єднати два byteмасиви?

Сказати,

byte a[];
byte b[];

Як з'єднати два byteмасиви і зберегти його в іншому byteмасиві?


3
Зверніть увагу , будь ласка , що Apache Commons, гуави Google, System.arrayCopy, ByteBufferі тому - не так ефективно , але читаним - ByteArrayOutputStreamвсі були охоплені. Тут ми отримали більше 7 відповідей. Будь ласка, не публікуйте більше жодної грані.
Maarten Bodewes

Відповіді:


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

Найелегантніший спосіб зробити це за допомогою a ByteArrayOutputStream.

byte a[];
byte b[];

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

byte c[] = outputStream.toByteArray( );

61
@vipw Причина, чому це елегантно, полягає в тому, що якщо / коли ви хочете пізніше об'єднати третій масив, ви просто додаєте рядок outputStream.write( c );- вам не доведеться повертатися назад і редагувати рядок, де ви створюєте масив байтів результатів. Крім того, переупорядкувати масиви просто, на відміну від використання методу масиву копіювання.
Уейн Урода

2
Крім того, це набагато простіше при роботі з більш ніж двома байтовими масивами.
gardarh

3
Від того, чи буде витрачатися на процесор і пам'ять, залежить від того, як часто ви робите операцію. Якщо це мільярд разів на секунду - обов'язково, оптимізуйте це. В іншому випадку виграшні міркування можуть бути читабельністю та ремонтопридатністю.
vikingsteve

5
Якщо споживання пам'яті та / або продуктивність викликає занепокоєння, не забудьте використовувати це a.length + b.lengthяк аргумент для ByteArrayOutputStreamконструктора. Зауважте, що цей метод все одно буде копіювати всі байти в новий масив, який потрібно призначити c[]! Розглянемо ByteBufferметод близького суперника, який не втрачає пам'ять.
Maarten Bodewes

Я не можу зробити це великими пальцями, тому що це лише фрагмент коду. Тут немає ніяких пояснень основної частини, яка є частиною, яка мені хвилюється (і, думаю, більшість людей хотіла б). Я б із задоволенням дав цьому великий палець, якби було порівняння продуктивності між системою # arrayCopy (Object, int, Object, int, int) та ByteArrayOutputStream # put (байт []), і детально описати, який сценарій найкращий для обох варіантів. Крім того, якщо говорити, відповідь також повинна містити arrayCopy, оскільки це вже інше рішення.
searchchengine27

66

Ось приємне рішення за допомогою Guava '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()рядок, оскільки вона неправильна.
Maarten Bodewes

1
Будьте обережні при використанні методу масиву 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);

Та сама відповідь, що і прийнята, і вибачте, 5 хвилин пізніше.
Maarten Bodewes

11

Якщо вам більше ByteBufferподобається @kalefranz, завжди є можливість об'єднати два byte[](або навіть більше) в один рядок, як це:

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

Відповідь така ж, як і ця, але пізніше ніж на 1 рік. Використовує метод ланцюга, але це було б краще вписати в існуючу відповідь.
Maarten Bodewes

11

Ви можете використовувати сторонні бібліотеки для чистого коду, як Apache Commons Lang, і використовувати його так:

byte[] bytes = ArrayUtils.addAll(a, b);

1
Я спробував ArrayUtils.addAll(a, b)і byte[] c = Bytes.concat(a, b), але останній швидше.
Карлос Андрес Гарсія

Може бути. Я не знаю бібліотеки Guava, тому якщо вона є, то краще використовувати її. Ви перевіряли це на дуже великі масиви?
Томаш Пшибильський

1
Коли я робив тест, масив Фіртса становив 68 елементів довжиною y другий 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
Це витрачає пам’ять. Метод був би нормальним для двох менших масивів, але він, безумовно, оподатковує сміттєзбірник за більше масивів.
Maarten Bodewes

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
Я відклав кілька попереджень і зробив кілька тестувань. Для латиниці 1 та Oracle за умови виконання (11) це, здається, працює. Тож я надав додаткову інформацію та видалив свій коментар та відгук. Я сподіваюся, що це нормально для вас, інакше, будь ласка, відкатуйтесь.
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.