【问题标题】:Design for determining file extension from header data in files根据文件中的标头数据确定文件扩展名的设计
【发布时间】:2026-01-22 13:25:01
【问题描述】:

我有一个问题两天来一直试图解决,但在网上(或在这里)没有找到任何东西,所以我想我会发布这个问题,看看我能得到什么样的反馈。我在业余时间编写了一个小程序,它扫描文件目录(已删除文件扩展名)并读取每个文件的文件头,以找出每个文件的文件扩展名。到目前为止,我已经使用了一系列 if/else if 语句来满足我程序的“模式匹配”功能......

private String fileHeaderCheck()
{
    /* ----- PNG File Type Verification ----- */
    if (headerCheck.buffer[0] == 0x89 && headerCheck.buffer[1] == 0x50 && headerCheck.buffer[2] == 0x4E && headerCheck.buffer[3] == 0x47
        && headerCheck.buffer[4] == 0xD && headerCheck.buffer[5] == 0xA && headerCheck.buffer[6] == 0x1A && headerCheck.buffer[7] == 0xA)
    {
        return ".png";
    }

    /* ----- JPG File Type Verification ----- */
    else if (headerCheck.buffer[0] == 0xFF && headerCheck.buffer[1] == 0xD8 && headerCheck.buffer[2] == 0xFF && headerCheck.buffer[3] == 0xE0)
    {
        return ".jpg";
    }

    // And so on and so on through the whole list of file types I am checking for

    else
        return "unknown";           
}

headerCheck 是一个 Buffer 对象,它包含一个 byte[] 保存从文件的前 512 个字节读取的数据。我在这个模式匹配过程中合并了 25-30 种不同的文件类型,所以你可以看到这个函数很快就会变得又大又丑。我想做的是使用 Node 对象的 ArrayList,每个对象都包含:1. 特定文件类型的标头信息的字节 [],以及 2. 表示文件扩展类型的字符串, 然后只需遍历数组,将 Node byte[] 与 headerCheck.buffer[] 进行比较,任何匹配的元素我都会将它的 fileType 附加到我从 headerCheck.buffer[] 中读取的文件的末尾。我的 Node 类就是这样设置的......

class Node  //if this is the "png" Node 
{
    byte[] metadata;
    String fileType;

    Node()
    {
        this.metadata = new byte[]{0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA};
        this.fileType = ".png";
    }
}

我不知道如何初始化 ArrayList。我是否必须使用预配置的数据创建/实例化 25-30 个单独的节点类来初始化 ArrayList,然后将它们添加()到它?这似乎比所有 if/else if 语句或 switch 更糟糕......第二个问题是,一旦我用所有 Node 对象实例化了我的 ArrayList,我很难想象如何遍历它的 byte[] 以便比较它到 headerCheck.buffer[]。我正在突破我的知识界限,所以对于这些问题,我没有代码,因为我不知道从哪里开始编写这些东西。我正在考虑双重嵌套的 for 循环,但我还没有走到那一步,因为我还没有找到一种有效的方法来初始化 ArrayList。我什至研究了一些设计模式作为可能的补救措施,但无济于事。任何帮助将不胜感激...谢谢

迭代方法(类似这些):

boolean match;

for (int x = 0; x < pattens.get(metadata).size(); x++)
{
    if (headerCheck.buffer[x] != patterns.get(metadata[x])
    {
        match = false;
        return;
    }   
    else
        return true;
}

【问题讨论】:

    标签: java arraylist nodes


    【解决方案1】:

    您正在使用一种非常复杂的方法,导致代码难以维护。如果您可以修改整个设计,我想提出一种替代方法,如下所示:

    创建一个名为 FileExtension 的接口:

    interface FileExtension {
        public String getExtension(byte[] buffer);
    }
    

    创建包含确定文件扩展名的逻辑的类:

    class PNGExtension implements FileExtension {
    
        @Override
        public String getExtension(byte[] buffer) {
            /* ----- PNG File Type Verification ----- */
            String fileType = null;
            if (buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47 && buffer[4] == 0xD && buffer[5] == 0xA
                    && buffer[6] == 0x1A && buffer[7] == 0xA) {
                fileType = ".png";
            }
    
            return fileType;
        }
    
    }
    
    class JPGExtension implements FileExtension {
    
        @Override
        public String getExtension(byte[] buffer) {
            /* ----- JPG File Type Verification ----- */
            String fileType = null;
            if (buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF && buffer[3] == 0xE0) {
                fileType = ".jpg";
            }
    
            return fileType;
        }
    
    }
    

    创建一个复合具体类,最终将用于根据前 512 个字节获取文件扩展名

    class CompositeFileExtension implements FileExtension {
    
        private List<FileExtension> fileExtensions;
    
        public CompositeFileExtension() {
            //in the real world, this list can be populated through an IoC container
            fileExtensions = new ArrayList<FileExtension>();
            fileExtensions.add(new PNGExtension());
            fileExtensions.add(new JPGExtension());
    
        }
    
        @Override
        public String getExtension(byte[] buffer) {
            String fileExtension = null;
            for(FileExtension extension : fileExtensions) {
                if((fileExtension=extension.getExtension(buffer))!=null) {
                    break;
                }
            }
    
            return fileExtension;
        }
    }
    

    最后,所有的代码组合起来如下:

    public class FileMapperDemo {
        public static void main(String[] args) {
            /*
             * 1. For each file in directory
             *    2. Read first 512 bytes into buffer array
             *       3. Create a new CompositeFileExtension object and pass it the buffer
             * 
             */
    
                         CompositeFileExtension fileExtensions = new CompositeFileExtension();
    
                         //4. get the file extension 
                         String fileExtension = fileExtensions.getExtension(buffer);
    
        }
    }
    

    这种方法的优点是,只要需要从缓冲区推导出新类型的文件扩展名,您只需将新的FileExtension 对象添加到ArrayList。您不需要创建两个相似的 byte 数组(您在示例中创建了两个字节数组。一个用于从数组中读取的前 512 个字节,另一个用于与此数组进行比较)

    【讨论】:

    • 当我研究一些设计模式书籍时,我正在考虑这样的事情,但只是没有像你列出的那样清楚地把它们放在一起。我实际上还有程序的另一部分它使用的设计与您概述的类似,但也许是因为我一次做的太多,以至于我的大脑超负荷了。我将相应地重构和修改我的代码。感谢您的洞察力。
    • @Misterblue01 这正是我花空闲时间为您提供整个代码的原因。可视化组件以及它们如何连接在一起需要一些练习。我相信您了解这些概念,但只需要朝着正确的方向前进。话虽如此,如果您觉得这种方法更好,请不要忘记接受答案。 :)
    【解决方案2】:

    你可以把你的节点类改成这样:

    class FilePattern {
        byte[] metadata ;
        String extension ;
    
        public FilePattern(byte[] m, String ext) {
            this.metadata = m ;
            this.extension = ext ;
        }
    
        /* add the getters here */
    }
    

    现在,您可以在向其添加元素的同时初始化 ArrayList,即

    ArrayList<FilePattern> patterns = new ArrayList<FilePattern>();
    patterns.add(new FilePattern(new byte[]{ ... }, "extension"));
    .... // for other patterns.
    

    接下来,遍历patterns的元素,提取元数据字节并与从文件中读取的字节匹配并确定文件类型。

    编辑:对于循环,您可以执行以下操作:

    for(FilePattern f : patterns)
        if(Arrays.equals(f.getMetadata(), headerCheck.buffer))
            System.out.println("Found file with extension : ", f.getExtension());
    

    【讨论】:

    • 这是有道理的,即使我使用 25-30 次 add() 函数调用,我仍然减少了模式匹配函数中的 if/else 语句。那么这是否类似于我应该如何迭代它?