连接两个字节数组的简单方法

302

如何简单地连接两个 byte 数组?

比如说,

byte a[];
byte b[];

我如何将两个byte数组连接起来并存储到另一个byte数组中?


3
请注意,Apache Commons、Google的Guava、System.arrayCopyByteBuffer以及效率不高但易读的ByteArrayOutputStream都已经被涵盖在内。我们已经有超过7个重复的答案了,请不要再发布重复内容。 - Maarten Bodewes
13个回答

477

使用ByteArrayOutputStream是最优雅的方法。

byte a[];
byte b[];

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

byte c[] = outputStream.toByteArray( );

70
这段话之所以优雅,是因为如果你想在以后拼接第三个数组,你只需要加上一行代码outputStream.write(c);,而不需要回去修改创建结果字节数组的那一行。此外,重新排列这些数组也很简单,不像使用arraycopy方法那样麻烦。 - Wayne Uroda
2
此外,当处理不止两个字节数组时,这将变得更加容易。 - gardarh
3
是否浪费CPU和内存取决于你执行这个操作的频率。如果每秒执行十亿次 - 当然要进行优化。否则,可读性和可维护性可能是更重要的考虑因素。 - vikingsteve
7
如果内存消耗和/或性能是一个问题,请确保在使用ByteArrayOutputStream构造函数的参数时使用a.length + b.length。请注意,这种方法仍将所有字节复制到新数组以分配给c[]!考虑使用ByteBuffer方法作为一个接近的竞争者,它不会浪费内存。 - Maarten Bodewes
2
我无法真正给这个点赞,因为这只是一段代码片段。这里没有解释底层的部分,这是我关心的部分(我认为大多数人也是如此)。如果有System#arrayCopy(Object, int, Object, int, int)和ByteArrayOutputStream#put(byte[])之间的性能比较,并详细说明哪种情况最适合使用哪种选项,我会很乐意给这个点赞。另外,话虽如此,答案也应该包括arrayCopy,因为那是另一个解决方案。 - searchengine27

372

最简单的方法:

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

像魔法一样运作良好。 - Shuhad zaman

85

以下是使用Guavacom.google.common.primitives.Bytes提供的好的解决方案:

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

这种方法的好处在于它具有可变参数签名:

public static byte[] concat(byte[]... arrays)
这意味着您可以在单个方法调用中连接任意数量的数组。

这意味着您可以在单个方法调用中连接任意数量的数组。


这是最简单的方法。顺便说一下,如果您使用Groovy也没有区别。 - lepe

39

另一种可能性是使用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
抱歉,ReadTheDocs. ByteBuffer.allocate(int)是一个返回实例化的java.nio.HeapByteBuffer的静态方法,它是ByteBuffer的子类。.put().compact()方法以及其他抽象性都已处理好。 - kalefranz
@kalefranz已删除compact()行,因为它是不正确的。 - Maarten Bodewes
1
在使用ByteBuffer的array()方法时要小心 - 除非您绝对知道自己在做什么并且可维护性不是问题,否则不能保证bytebuffer中的零位置始终对应于字节数组的索引0。请参见此处。我通过发出bb.flip(); bb.get(result);而不是byte[] result = bb.array();来解决这个问题。 - DarqueSandu
1
@DarqueSandu 虽然这是一般的好建议,但仔细阅读allocate方法的说明会发现以下内容:“新缓冲区的位置将为零,其限制将为其容量,其标记将未定义,并且其每个元素都将初始化为零。它将有一个后备数组,其数组偏移量将为零。”因此,对于这个特定的代码片段,在内部分配ByteBuffer时,这不是一个问题。 - Maarten Bodewes

16

另一种方法是使用一个实用函数(如果您喜欢,可以将其作为通用实用类的静态方法):

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个数组等。

这种方法的优点是快速的arraycopy代码,而且非常易读易于维护。


13

你可以使用第三方库,如Apache Commons Lang等来进行代码整洁化,例如:

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

1
我尝试了ArrayUtils.addAll(a, b)byte[] c = Bytes.concat(a, b),但后者更快。 - Carlos Andrés García
也许吧。我不了解Guava库,如果是的话,最好使用它。你有检查过它在非常大的数组上的表现吗? - Tomasz Przybylski
1
当我进行测试时,第一个数组长度为68个元素,而第二个数组长度为8790688。 - Carlos Andrés García

12

如果您像@kalefranz一样更喜欢ByteBuffer,那么总有可能将两个(甚至更多)byte[]连接在一行中,就像这样:

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

2
此答案相同,但晚了一年以上。使用方法链接,但最好将其放入现有答案中。 - Maarten Bodewes

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

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

2
如果您不想操纵数组大小,只需利用字符串连接的神奇功能:
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”均表示西方Latin 1字符集,该字符集将每个字符编码为单个字节。由于不执行多字节转换,因此字符串中的字符将具有与字节相同的值(除了它们始终被解释为正值,因为char是无符号的)。至少对于Oracle提供的运行时,任何字节都将被正确“解码”,然后再次“编码”。

请注意,字符串会相当大地扩展字节数组,需要额外的内存。字符串也可能被国际化,因此不易删除。字符串也是不可变的,因此其中的值不能被销毁。因此,您不应以这种方式连接敏感数组,也不应将此方法用于较大的字节数组。还需要清楚地指示您正在做什么,因为这种数组连接方法不是常见的解决方案。


如果您不确定“l1”(它只是ISO 8859-1的别名)的含义,请不要使用“肯定”的词语。哪个特定的字节值将被清除?至于内存使用情况,问题是关于简单连接两个字节数组的方法,而不是最节省内存的方法。 - John McClane
1
我已经添加了一些警告并进行了一些测试。对于Latin 1和Oracle提供的运行时(11),这似乎是有效的。因此,我提供了额外的信息并删除了我的评论和反对票。我希望这对您来说没问题,否则请回滚。 - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接