Java:从字节数组中删除连续的零段

例如,假设我想从数组中删除0个长度超过3个字节的所有连续段

byte a[] = {1,2,3,0,1,2,3,0,0,0,0,4}; byte r[] = magic(a); System.out.println(r); 

结果

 {1,2,3,0,1,2,3,4} 

我想在Java中使用正则表达式,但是在字节数组而不是字符串上。

有什么东西可以帮助我内置(或有一个很好的第三方工具),还是我需要从头开始工作?

字符串是UTF-16,所以来回转换不是一个好主意? 至少它浪费了大量的开销……对吧?

正则表达式不是工作的工具,你需要从头开始实现

 byte[] a = {1,2,3,0,1,2,3,0,0,0,0,4}; String s0 = new String(a, "ISO-8859-1"); String s1 = s0.replaceAll("\\x00{4,}", ""); byte[] r = s1.getBytes("ISO-8859-1"); System.out.println(Arrays.toString(r)); // [1, 2, 3, 0, 1, 2, 3, 4] 

我使用ISO-8859-1(latin1),因为与其他编码不同,

  • 0x00..0xFF范围内的每个字节0x00..0xFF映射到一个有效字符,并且

  • 每个字符与latin1编码具有相同的数值。

这意味着字符串与原始字节数组的长度相同,您可以使用\xFF结构将其数字值与任何字节匹配,并且可以将结果字符串转换回字节数组而不会丢失信息。

我不会尝试以字符串forms显示数据 – 虽然所有字符都有效,但其中许多字符不可打印。 另外,避免在字符串forms下操纵数据; 你可能会意外地做一些转义序列替换或其他编码转换而没有意识到它。 事实上,我不建议做这种事情,但这不是你问的。 🙂

此外,请注意,此技术不一定适用于其他编程语言或正则表达式。 你必须单独测试每一个。

虽然我怀疑reg-ex是否适合这项工作,但如果你想使用它,我建议你只在字节数组上实现一个CharSequence包装器。 像这样的东西(我直接写了这个,没有编译……但你明白了)。

 public class ByteChars implements CharSequence ... ByteChars(byte[] arr) { this(arr,0,arr.length); } ByteChars(byte[] arr, int str, int end) { //check str and end are within range here strOfs=str; endOfs=end; bytes=arr; } public char charAt(int idx) { //check idx is within range here return (char)(bytes[strOfs+idx]&0xFF); } public int length() { return (endOfs-strOfs); } public CharSequence subSequence(int str, int end) { //check str and end are within range here return new ByteChars(arr,(strOfs+str,strOfs+end); } public String toString() { return new String(bytes,strOfs,(endOfs-strOfs),"ISO8859_1"); } 

我没有看到正则表达式对你想做什么有用。 您可以做的一件事是使用运行长度编码来编码该字节数组,用空字符串替换每个“30”(读取三个0),并解码最终字符串。 维基百科有一个简单的Java实现。

虽然有一个合理的ByteString库浮动,我见过的没有人在它们上实现了一般的regexp库。

我建议直接解决你的问题,而不是实现一个regexp库:)

如果你转换为字符串并返回,你可能找不到任何现有的编码,为你的0字节往返。 如果是这种情况,你必须编写自己的字节数组< - >字符串转换器; 不值得的麻烦。

我建议将字节数组转换为字符串,执行正则表达式,然后将其转换回来。 这是一个有效的例子:

 public void testRegex() throws Exception { byte a[] = { 1, 2, 3, 0, 1, 2, 3, 0, 0, 0, 0, 4 }; String s = btoa(a); String t = s.replaceAll("\u0000{4,}", ""); byte b[] = atob(t); System.out.println(Arrays.toString(b)); } private byte[] atob(String t) { char[] array = t.toCharArray(); byte[] b = new byte[array.length]; for (int i = 0; i < array.length; i++) { b[i] = (byte) Character.toCodePoint('\u0000', array[i]); } return b; } private String btoa(byte[] a) { StringBuilder sb = new StringBuilder(); for (byte b : a) { sb.append(Character.toChars(b)); } return sb.toString(); } 

对于更复杂的转换,我建议使用Lexer。 JavaCC和ANTLR都支持解析/转换二进制文件。

利用其他答案提出的正则表达式的实现比使用将输入数组中的字节复制到输出数组的循环的简单实现慢8倍。

该实现逐字节复制输入数组。 如果检测到零序列,则输出数组索引减小(重绕)。 在处理输入数组之后,输出数组甚至被再次复制以将其长度调整为实际的字节数,因为中间输出数组是用输入数组的长度初始化的。

 /** * Remove four or more zero byte sequences from the input array. * * @param inBytes the input array * @return a new array with four or more zero bytes removed form the input array */ private static byte[] removeDuplicates(byte[] inBytes) { int size = inBytes.length; // Use an array with the same size in the first place byte[] newBytes = new byte[size]; byte value; int newIdx = 0; int zeroCounter = 0; for (int i = 0; i < size; i++) { value = inBytes[i]; if (value == 0) { zeroCounter++; } else { if (zeroCounter >= 4) { // Rewind output buffer index newIdx -= zeroCounter; } zeroCounter = 0; } newBytes[newIdx] = value; newIdx++; } if (zeroCounter >= 4) { // Rewind output buffer index for four zero bytes at the end too newIdx -= zeroCounter; } // Copy data into an array that has the correct length byte[] finalOut = new byte[newIdx]; System.arraycopy(newBytes, 0, finalOut, 0, newIdx); return finalOut; } 

通过倒回第一个零字节(三个或更少)并复制这些元素来防止不必要的副本的第二种方法有趣地比第一种方法慢一点。

所有这三种实现都在Pentium N3700处理器上进行了测试,在8 x 32KB输入arrays上进行了1000次迭代,并且有几个数量和长度的零序列。 与正则表达式方法相比,性能最差的提升速度提高了1.5倍。

完整的测试装备可以在这里找到: https : //pastebin.com/83q9EzDc

Java Regex在CharSequences上运行 – 您可以使用CharBuffer来包装现有的字节数组(您可能需要将其转换为char []?)并对其进行解释,然后对其执行正则表达式?