例如,假设我想从数组中删除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,所以来回转换不是一个好主意?至少它浪费了大量的开销......对吧?
答案 0 :(得分:25)
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
中的每个字节都映射到有效字符,
每个字符与latin1编码具有相同的数值。
这意味着字符串与原始字节数组的长度相同,您可以通过其数值与\xFF
结构匹配任何字节,并且可以将结果字符串转换回字节数组而不会丢失信息。
我不会尝试以字符串形式显示数据 - 虽然所有字符都有效,但其中许多字符都不可打印。另外,避免在字符串形式下操纵数据;你可能会意外地做一些转义序列替换或其他编码转换而没有意识到它。事实上,我根本不会推荐做这种事情,但这不是你问的。 :)
另外,请注意,此技术不一定适用于其他编程语言或正则表达式。你必须单独测试每一个。
答案 1 :(得分:8)
虽然我怀疑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");
}
答案 2 :(得分:1)
我没有看到正则表达式对你想做什么有用。您可以做的一件事是使用Run Length Encoding对该字节数组进行编码,用空字符串替换每个“30”(读取三个0)的事件,然后解码最终的字符串。维基百科有一个简单的Java实现。
答案 3 :(得分:1)
regex不是工作的工具,你需要从头开始实现
答案 4 :(得分:1)
虽然有一个合理的ByteString库,但我见过的人都没有在它们上面实现一般的regexp库。
我建议直接解决您的问题而不是实现regexp库:)
如果你确实转换为字符串并返回,你可能找不到任何现有的编码,为你的0字节提供往返。如果是这种情况,你必须编写自己的字节数组< - >串转换器;不值得的麻烦。
答案 5 :(得分: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都支持解析/转换二进制文件。
答案 6 :(得分:0)
使用其他答案提出的正则表达式的实现比使用将输入数组中的字节复制到输出数组的循环的简单实现慢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输入阵列上进行了1,000次迭代,并且有几个数量和长度的零序列。与正则表达式方法相比,性能最差的改进是1.5倍更快。
可以在此处找到完整的测试装备:https://pastebin.com/83q9EzDc
答案 7 :(得分:-1)