如何在Java中围绕字节序列拆分字节数组?

12

如何在Java中将一个byte[]按字节序列分割? 类似于String#split(regex)byte[]版本。

示例

让我们以这个字节数组为例:
[11 11 FF FF 22 22 22 FF FF 33 33 33 33]

并且选择分隔符为
[FF FF]

然后,拆分将得到以下三个部分:
[11 11]
[22 22 22]
[33 33 33 33]

编辑:

请注意,不能将byte[]转换为String,然后再分割,然后再转换回来,因为存在编码问题。 当对字节数组进行这样的转换时,生成的byte[]会有所不同。 请参考此文: Conversion of byte[] into a String and then back to a byte[]


不,不是的。请仔细阅读。 - Ori Popowski
遍历数组;将下一个“delimiter.length”字节与分隔符进行比较,并根据需要拆分?你到底有什么困难? - Henry Keiter
是的,我可以做到,但我正在寻找现有的解决方案,而不是重新发明轮子。重用现有、经过验证和测试的代码比自己编写代码是一个好的实践。 - Ori Popowski
编码是否是一个问题,因为你正在处理保证非文本数据,还是这是一种人为的限制?如果你知道编码将会是什么,它就不再是一个问题。 - avgvstvs
1
可能是 https://dev59.com/CknSa4cB1Zd3GeqPMDyP?rq=1 的重复问题。 - avgvstvs
@avgvstvs 是的,我正在处理保证非文本数据。 - Ori Popowski
7个回答

11

这里有一个简单明了的解决方案。

与avgvstvs的方法不同,它可以处理任意长度的分隔符。顶部答案也很好,但作者尚未修复Eitan Perkal指出的问题。使用Perkal建议的方法避免了该问题。

public static List<byte[]> tokens(byte[] array, byte[] delimiter) {
        List<byte[]> byteArrays = new LinkedList<>();
        if (delimiter.length == 0) {
            return byteArrays;
        }
        int begin = 0;

        outer:
        for (int i = 0; i < array.length - delimiter.length + 1; i++) {
            for (int j = 0; j < delimiter.length; j++) {
                if (array[i + j] != delimiter[j]) {
                    continue outer;
                }
            }
            byteArrays.add(Arrays.copyOfRange(array, begin, i));
            begin = i + delimiter.length;
        }
        byteArrays.add(Arrays.copyOfRange(array, begin, array.length));
        return byteArrays;
    }

8
请注意,如果使用编码“iso8859-1”,可以可靠地将byte[]转换为String并进行相反操作,字符与字节是一对一的映射。
然而,这仍然是一个丑陋的解决方案。
我认为你需要自己开发解决方案。
我建议分两个阶段解决:
1. 找出如何查找每个分隔符出现的索引。对于短分隔符,使用更朴素的算法即可,但是请搜索“Knuth-Morris-Pratt”以获取高效算法。 2. 每次找到索引时,使用Arrays.copyOfRange()获取所需部分并添加到输出列表中。
下面是使用朴素模式查找算法的代码。如果分隔符很长,则KMP会变得更加值得(因为它可以节省回溯,但不会错过嵌入在序列中未匹配结尾的分隔符)。
public static boolean isMatch(byte[] pattern, byte[] input, int pos) {
    for(int i=0; i< pattern.length; i++) {
        if(pattern[i] != input[pos+i]) {
            return false;
        }
    }
    return true;
}

public static List<byte[]> split(byte[] pattern, byte[] input) {
    List<byte[]> l = new LinkedList<byte[]>();
    int blockStart = 0;
    for(int i=0; i<input.length; i++) {
       if(isMatch(pattern,input,i)) {
          l.add(Arrays.copyOfRange(input, blockStart, i));
          blockStart = i+pattern.length;
          i = blockStart;
       }
    }
    l.add(Arrays.copyOfRange(input, blockStart, input.length ));
    return l;
}

阅读《C程序设计语言》这本书总是很好的,它有大量的练习可以迫使你提出这些类型的解决方案。然后你可以在掌握了这个工具集之后转向Java。 - JohnMerlino
2
如果输入以模式的开头结尾(java.lang.ArrayIndexOutOfBoundsException),则上述代码将失败,例如:byte[] pattern= { (byte) 0x43, (byte) 0x23}; byte[] input = { (byte) 0x08, (byte) 0x01, (byte) 0x53, (byte) 0x43}; - 一个简单的解决方案是将split方法更改为:for(int i=0; i<input.length-pattern.length; i++) {而不是for(int i=0; i<input.length; i++) {。 - Eitan Rimon
“i = blockStart;” 这一行也是不正确的,因为之后会执行 “i++”。当模式长度为 1 时,问题就会出现。 - IARI

4

我修改了'L. Blanc'的答案,使其能够处理在开头和结尾处的分隔符。此外,我将其重命名为'split'。

private List<byte[]> split(byte[] array, byte[] delimiter)
{
   List<byte[]> byteArrays = new LinkedList<byte[]>();
   if (delimiter.length == 0)
   {
      return byteArrays;
   }
   int begin = 0;

   outer: for (int i = 0; i < array.length - delimiter.length + 1; i++)
   {
      for (int j = 0; j < delimiter.length; j++)
      {
         if (array[i + j] != delimiter[j])
         {
            continue outer;
         }
      }

      // If delimiter is at the beginning then there will not be any data.
      if (begin != i)
         byteArrays.add(Arrays.copyOfRange(array, begin, i));
      begin = i + delimiter.length;
   }

   // delimiter at the very end with no data following?
   if (begin != array.length)
      byteArrays.add(Arrays.copyOfRange(array, begin, array.length));

   return byteArrays;
}

很好,尽管如果有两个分隔符连在一起会抛出异常。 - Odys

0

自己编写是这里唯一的出路。如果您愿意使用非标准库,我能提供的最好建议就是使用Apache中的这个类:

http://commons.apache.org/proper/commons-primitives/apidocs/org/apache/commons/collections/primitives/ArrayByteList.html

Knuth的解决方案可能是最好的,但我会把数组当作堆栈来处理,然后做类似于以下的操作:

List<ArrayByteList> targetList = new ArrayList<ArrayByteList>();
while(!stack.empty()){
  byte top = stack.pop();
  ArrayByteList tmp = new ArrayByteList();

  if( top == 0xff && stack.peek() == 0xff){
    stack.pop();
    continue;
  }else{
    while( top != 0xff ){
      tmp.add(stack.pop());
    }
    targetList.add(tmp);
  }
}

我知道这很快,很粗糙,但在所有情况下都应该提供O(n)。


对于简单的两字节分隔符来说还不错,但无法处理更复杂的模式——这可能对于原帖作者来说是可以接受的。 - slim

0
这是对Roger的答案https://dev59.com/c2Eh5IYBdhLWcg3wWySr#44468124的一些改进: 假设我们有这样一个数组||||aaa||bbb和分隔符||。在这种情况下,我们得到
java.lang.IllegalArgumentException: 2 > 1
    at java.util.Arrays.copyOfRange(Arrays.java:3519)

因此,最终改进的解决方案是:

public static List<byte[]> split(byte[] array, byte[] delimiter) {
        List<byte[]> byteArrays = new LinkedList<>();
        if (delimiter.length == 0) {
            return byteArrays;
        }
        int begin = 0;

        outer:
        for (int i = 0; i < array.length - delimiter.length + 1; i++) {
            for (int j = 0; j < delimiter.length; j++) {
                if (array[i + j] != delimiter[j]) {
                    continue outer;
                }
            }

            // This condition was changed
            if (begin != i)
                byteArrays.add(Arrays.copyOfRange(array, begin, i));
            begin = i + delimiter.length;
        }

        // Also here we may change condition to 'less'
        if (begin < array.length)
            byteArrays.add(Arrays.copyOfRange(array, begin, array.length));

        return byteArrays;
    }

-3

2
Arrays.copyOfRange可以复制,但它不能分割字节数组。问题是关于分割而不是复制。 - Ahmad Hajjar

-4

参考Java String文档

你可以从byte数组构造一个String对象。猜想你知道剩下的。

public static byte[][] splitByteArray(byte[] bytes, byte[] regex, Charset charset) {
    String str = new String(bytes, charset);
    String[] split = str.split(new String(regex, charset));
    byte[][] byteSplit = new byte[split.length][];
    for (int i = 0; i < split.length; i++) {
        byteSplit[i] = split[i].getBytes(charset);
    }
    return byteSplit;
}

public static void main(String[] args) {
    Charset charset = Charset.forName("UTF-8");
    byte[] bytes = {
        '1', '1', ' ', '1', '1',
        'F', 'F', ' ', 'F', 'F',
        '2', '2', ' ', '2', '2', ' ', '2', '2',
        'F', 'F', ' ', 'F', 'F',
        '3', '3', ' ', '3', '3', ' ', '3', '3', ' ', '3', '3'
    };
    byte[] regex = {'F', 'F', ' ', 'F', 'F'};
    byte[][] splitted = splitByteArray(bytes, regex, charset);
    for (byte[] arr : splitted) {
        System.out.print("[");
        for (byte b : arr) {
            System.out.print((char) b);
        }
        System.out.println("]");
    }
}

我建议写一些示例代码,这样有相同问题的用户可以更轻松地找到答案。因为他们可能不知道“剩下的部分”。谢谢! - Andrew Gies
无法工作:https://dev59.com/Yk3Sa4cB1Zd3GeqPwI95 - Ori Popowski

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