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

Posted

技术标签:

【中文标题】Java:从字节数组中删除连续的零段【英文标题】:Java: remove continious segment of zeros from byte array 【发布时间】:2010-11-26 02:24:06 【问题描述】:

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

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,所以来回转换不是一个好主意吗?至少这是很多浪费的开销……对吧?

【问题讨论】:

性能和内存使用对您的用例有多重要?一般来说,RAM 很便宜,而 CPU 速度很快。您是否真的发现了瓶颈,还是担心效率?您可以通过使用 8 位编码将 byte [] 转换为 String 来轻松尝试,进行正则表达式并检查性能。毕竟,我们不必担心 16 位字符的 Java 字符串在 ANSI 环境中的正常使用效率有多低,对吧? 这是一个高性能应用程序,我更担心循环而不是内存使用。 仍然值得进行基准测试; Hotspot VM 会将热点中的代码转换为机器代码,因为它都适合 32 位机器字,所以它将以与 8 位数据相同的速度处理 16 位数据。即使你发现它太慢了,你也不会花太多时间去发现它。 真的,我想我还是要试试,谢谢 【参考方案1】:

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

【讨论】:

语法错误,无代码,Unicode 替换问号或反问。对于问这样X/Y questions的人来说很难理解。在改进之前投反对票。【参考方案2】:

我看不出正则表达式对做你想做的事有多大用处。您可以做的一件事是使用Run Length Encoding 对该字节数组进行编码,将每次出现的“30”(读取三个0)替换为空字符串,然后解码最终字符串。 Wikipedia 有一个简单的 Java 实现。

【讨论】:

我以为 3 0 只是一个例子。【参考方案3】:

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

【讨论】:

【参考方案4】:

虽然有一个合理的 ByteString 库在四处飘荡,但我见过没有人在它们上实现通用的正则表达式库。

我建议直接解决您的问题,而不是实现一个正则表达式库:)

如果您确实转换为字符串并返回,您可能找不到任何现有的编码可以为您的 0 字节提供往返行程。如果是这种情况,您必须编写自己的字节数组 字符串转换器;不值得麻烦。

【讨论】:

【参考方案5】:
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("\\x004,", "");
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 构造通过其数值匹配任何字节,并且您可以将结果字符串转换回字节数组而不会丢失信息。

我不会尝试显示字符串形式的数据——尽管所有字符都是有效的,但其中许多是不可打印的。此外,避免在数据为字符串形式时对其进行操作;您可能不小心做了一些转义序列替换或其他编码转换而没有意识到。事实上,我根本不会推荐做这种事情,但这不是你要求的。 :)

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

【讨论】:

这真是太聪明了。【参考方案6】:

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

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("\u00004,", "");
    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 都支持解析/转换二进制文件。

【讨论】:

【参考方案7】:

虽然我质疑 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");
    

【讨论】:

我实施了这种方法,它奏效了!显然,您必须小心,因为您没有执行任何字符集解码,但对于诸如 doctype 检测之类的事情,它是完美的。【参考方案8】:

其他答案提出的使用正则表达式的实现比使用循环将字节从输入数组复制到输出数组的简单实现慢 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

【讨论】:

以上是关于Java:从字节数组中删除连续的零段的主要内容,如果未能解决你的问题,请参考以下文章

2022-08-22:给定一个数组arr,长度为n,最多可以删除一个连续子数组, 求剩下的数组,严格连续递增的子数组最大长度。 n <= 10^6。 来自字节。5.6笔试。

在 Java 中解析字节数组

可以将二维字节数组制成一个巨大的连续字节数组吗?

从 C# 中的字节数组中删除尾随空值

Java中的解析字节数组

JAVA里String数组在内存分配中分配的空间每个占几个字节?