在Java中,如何高效地修剪0';从字节数组的开始和结束开始

在Java中,如何高效地修剪0';从字节数组的开始和结束开始,java,arrays,parsing,trim,Java,Arrays,Parsing,Trim,出于我无法控制的原因,我需要解析一个巨大的文件,该文件的开头和结尾都有大量的空字节,而实际上只有很小的部分是有效的(最多5 KBs)。这是我想出的代码: @NonNull public static byte[] readFileToByteArray(@NonNull File file, boolean bTrimNulls) throws IOException { byte[] buffer = new byte[(int) file.length()]; FileInp

出于我无法控制的原因,我需要解析一个巨大的文件,该文件的开头和结尾都有大量的空字节,而实际上只有很小的部分是有效的(最多5 KBs)。这是我想出的代码:

@NonNull
public static byte[] readFileToByteArray(@NonNull File file, boolean bTrimNulls) throws IOException {
    byte[] buffer = new byte[(int) file.length()];
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(file);
        if (fis.read(buffer) == -1) {
            throw new IOException("EOF reached while trying to read the whole file");
        }
    } finally {
        closeSafely(fis);
    }
    if (!bTrimNulls) {
        return buffer;
    }
    int nFirstValidByteIndex = 0;
    for (int i = 0; i < buffer.length; i++) {
        if (buffer[i] != 0) {
            nFirstValidByteIndex = i;
            break;
        }
    }
    int nLastValidByteIndex = 0;
    for (int i = buffer.length - 1; i > 0; i--) {
        if (buffer[i] != 0) {
            nLastValidByteIndex = i;
            break;
        }
    }
    return copyBufferRange(buffer, nFirstValidByteIndex, nLastValidByteIndex + 1);
}
@NonNull
公共静态字节[]readFileToByteArray(@NonNull File File,boolean bTrimNulls)引发IOException{
byte[]buffer=新字节[(int)file.length()];
FileInputStream fis=null;
试一试{
fis=新文件输入流(文件);
如果(fis.read(缓冲区)=-1){
抛出新IOException(“尝试读取整个文件时达到EOF”);
}
}最后{
安全关闭(fis);
}
如果(!bTrimNulls){
返回缓冲区;
}
int nFirstValidByteIndex=0;
for(int i=0;i0;i--){
如果(缓冲区[i]!=0){
nLastValidByteIndex=i;
打破
}
}
返回copyBufferRange(buffer,nFirstValidByteIndex,nLastValidByteIndex+1);
}
有没有更好的替代方案


编辑:缓冲区中的有效字节对应于XML文件。

我认为您的解决方案相当有效。实际上,您正在从数组的两端查看前1个的索引,然后创建一个子数组数据

为什么您觉得需要改进算法


小心:过早优化是编程中所有(或至少大部分)问题的根源,

您的代码的时间复杂度为n,这对于您所说的大文件来说可能太多了。幸运的是,我们知道非零部分的最大大小为m,因此我们可以按m的步长搜索文件。如果我们错过了(在有效载荷的中间命中一个零),我们需要重复它直到我们找到它为止。因此,如果有效载荷中的零概率足够低,那么复杂度将达到n/m左右

import java.util.Arrays;
import java.util.Random;

class Test
{

    public static int findNonZero(byte[] sparse, int max)
    {
        // looks quadratic but isn't in practice if the probability of zero in the payload is low, i.e. 1/256 for random values
        for(int offset=0;offset<max;offset++)
        {
            for(int i=0;(i+offset)<sparse.length; i+=max)
            {
                if(sparse[i+offset]!=0)
                {
                    return i+offset;                    
                }
            }
        }
         // in production code you could handle this differently but this is just an example
        throw new RuntimeException("Nonzero value not found");
    }

    public static byte[] trim(byte[] sparse, int max)
    {
        int index = findNonZero(sparse, max);
        // go to the left and go to the right until you find (max) zeroes
        int from = ...
        int to = ...
        return Arrays.copyOfRange(sparse, from, to);        
    }

    public static void main(String[] args)
    {
        // create test data
        int size = 5000;
        byte[] test = new byte[1_000_000_000];
        byte[] payload = new byte[size];
        Random r = new Random();
        r.nextBytes(payload);
        payload[0]=(byte)(r.nextInt(Byte.MAX_VALUE-1)+1); // ensure start isnt zero
        payload[payload.length-1]=(byte)(r.nextInt(Byte.MAX_VALUE-1)+1);  // ensure end isnt zero
        System.arraycopy(payload, 0, test, r.nextInt(test.length-size), size);

        System.out.println(Arrays.equals(payload,trim(test,size)));
    }
}
导入java.util.array;
导入java.util.Random;
课堂测试
{
公共静态int findNonZero(字节[]稀疏,int max)
{
//看起来是二次的,但如果有效载荷中的零概率很低,即随机值为1/256,则实际上不是

对于(int offset=0;offset代码很好。对于非常大的文件,可以使用有限的缓冲区,一个FileChannel, 一个带旁路缓冲器的可搜索的双字节通道

只是代码可能会更好一些。参数
Path
而不是
File
会更通用、更现代

public static byte[] readFileToByteArray(@NonNull File file, boolean trimNulls)
        throws IOException {
    Path path = file.toPath();
    byte[] content = Files.readAllBytes(path);
    if (trimNulls) {
        int start = 0;
        while (start < content.length && content[start] == 0) {
            ++start;
        }
        int end = content.length;
        while (end > start && content[end - 1] == 0) {
            --end;
        }
        content = Arrays.copyOfRange(content, start, end);
    }
    return content;
}
public static byte[]readFileToByteArray(@NonNull File File,boolean trimNulls)
抛出IOException{
路径路径=file.toPath();
byte[]content=Files.readAllBytes(路径);
如果(为空){
int start=0;
while(startstart&&content[end-1]==0){
--结束;
}
content=Arrays.copyOfRange(内容、开始、结束);
}
返回内容;
}

在文件的中间可能有空字节吗?我是说,在你到达第一个非零字节之后,在你到达最后一个非零字节之前,能出现零字节吗?是的,可能是。大的有多大?它能保证不超过5KB的数据吗?你可以做的一件事不是把整个文件复制到内存中,而是文件。读取缓冲区时(至少在开始时,结束时可能会比较棘手,我想如果您知道有效负载最多只有5公里,您也可以这样做)。Does
fis.read
(无循环)可靠地像那样工作?随时停止复制不是免费的吗?因为代码实际上相当慢,而这是应用程序框架的代码,而不是实际的应用程序,所以性能是必须的。而且我也不太喜欢Donald Knuth的那句话。很多时候,优化的最佳时间是在设计算法时,而不是以后,即使我认为我永远不应该牺牲代码的可读性,除非它是必要的。你确定缓慢的部分是数组的修剪吗?不是将整个文件读入内存?修剪不是缓慢的部分,找到第一个和最后一个索引才是。我将尝试用更大的块而不是逐字节搜索。@Thilo:我不是写完了。我现在添加了差异。它实际上不是真正的二进制搜索。你是对的,我会修改它。时间复杂度不能真正降到O(n)以下如果我们还必须读取大小为n的文件。@Thilo:这是正确的。但是,您也可以访问许多类型存储设备上的文件的随机部分,并重写代码以直接查找文件的某些部分。这是否更有效取决于n和m的特定值以及设备。不过,我也发现了这个问题从理论上讲,这很有趣,你可以假设数组已经存在于内存中。因为这将在Android中运行,所以我使用了File,而Files.readAllBytes()只存在于Android>=8.X.X中。我将在新版本上使用它,因为它可能比FileInputStream+buffer做得更好。谢谢!