java.io.File 中的空字符是不是对存在检查有效?

Posted

技术标签:

【中文标题】java.io.File 中的空字符是不是对存在检查有效?【英文标题】:Are null characters in java.io.File valid for exists check?java.io.File 中的空字符是否对存在检查有效? 【发布时间】:2012-12-17 02:33:28 【问题描述】:

java.io.File 中的文件名中遇到空值时,该字符及其后面的所有字符都将被忽略,从而导致File.exists() 中出现一些奇怪的行为。

这种行为是我错过的java.io.File.exists() 的某些方面吗?

例子:

package os;

import java.io.File;
import java.io.IOException;

public class FileNullCheck

    public static void main(String[] args)
    
        File tmp = new File("a.txt");
        try
        
            tmp.createNewFile();
        
        catch (IOException e)
        
            e.printStackTrace();
            return;
        

        String a = "a.txt";
        System.out.printf("a.txt exists: %b (len=%d)%n",new File(a).exists(),a.length());

        String anull = new String(new byte[]  'a', '.', 't', 'x', 't', 0x00 );
        System.out.printf("a.txt (null) exists: %b (len=%d)%n",new File(anull).exists(),anull.length());

        String anullx = new String(new byte[]  'a', '.', 't', 'x', 't', 0x00, 'x' );
        System.out.printf("a.txt (nullx) exists: %b (len=%d)%n",new File(anullx).exists(),anullx.length());
    

运行结果。

a.txt 存在:true (len=5) a.txt (null) 存在:true (len=6) a.txt (nullx) 存在:true (len=7)

Linux系统有以下JVM。

Java(TM) SE 运行时环境 (build 1.7.0_10-b18) Java HotSpot(TM) 64 位服务器 VM(内部版本 23.6-b04,混合模式)

该行为似乎类似于 C,并且用于在文件系统上验证文件的字符串在 null 处被截断。

但我希望 Java 中的行为会在这些无效文件名上为 File.exists() 返回 false。

更新:2013 年 9 月 19 日

Java 1.7.0 更新 40 已将此作为 bug 的一部分进行了修复 JDK-8014846 : File and other classes in java.io do not handle embedded nulls properly

【问题讨论】:

这是与原生 API 的有趣交互。它至少能始终如一地工作吗? IE。打开名称错误的文件也能正常工作吗? 是的,您甚至可以使用无效的文件名打开读/写文件。 我称之为实现工件,可能是一个错误。绝对不是我依赖的东西。事实上,U+0000 是我永远不会在文件名中出现的唯一字符(即使底层操作系统允许,但我怀疑很多人会这样做)。 @PeterLawrey:是的,但文件分隔符不同 ;-) 我想说的是“我会将 nul 字符(或 Unicode 中的 U+0000)添加到受限制的集合中字符,即使操作系统没有。” @BrianRoach 更改为 String(byte[], "UTF-8") 不会改变报告的行为。谢谢,在这种情况下无论如何我都需要强制使用字符集。 【参考方案1】:

在 RHEL 上,nul 字节似乎终止了文件名(正如您在 C 中所期望的那样)

System.out.println("a exists " + new File("a").exists());
FileOutputStream fos = new FileOutputStream(new File("a\u0000aa"));
fos.close();
System.out.println("a exists " + new File("a").exists());

打印

a exists false
a exists true

我怀疑 Java 应该阻止您尝试使用带有空字节的文件名。

【讨论】:

【参考方案2】:

如果使用 JDK 1.7+,则 java.nio.files.Paths.get(URI) 可用于测试 Nul(似乎)

对原始测试的修改会产生有用的异常

package os;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileNullCheck

    public static void main(String[] args) throws Exception
    
        File tmp = new File("a.txt");
        try
        
            tmp.createNewFile();
        
        catch (IOException e)
        
            e.printStackTrace();
            return;
        

        String a = "a.txt";
        testExists("a.txt", a);

        String anull = new String(new byte[]  'a', '.', 't', 'x', 't', 0x00 , "UTF-8");
        testExists("a.txt (null)", anull);

        String anullx = new String(new byte[]  'a', '.', 't', 'x', 't', 0x00, 'x' , "UTF-8");
        testExists("a.txt (nullx)", anullx);
    

    private static void testExists(String label, String filename) throws IOException
    
        File file = new File(filename);
        System.out.printf("%s exists: %b%n", label, file.exists());
        System.out.printf("  filename.length = %d%n", filename.length());
        Path path = Paths.get(file.toURI());
        boolean symlink = Files.isSymbolicLink(path);
        System.out.printf("  nio issymlink = %b%n",symlink);
    

输出结果

a.txt 存在:真 文件名.长度 = 5 nio issymlink = false a.txt (null) 存在:真 文件名.长度 = 6 线程“主”java.nio.file.InvalidPathException 中的异常:不允许 Nul 字符:/home/joakim/code/***/a.txt 在 sun.nio.fs.UnixPath.checkNotNul(UnixPath.java:93) 在 sun.nio.fs.UnixPath.normalizeAndCheck(UnixPath.java:83) 在 sun.nio.fs.UnixPath.(UnixPath.java:71) 在 sun.nio.fs.UnixFileSystem.getPath(UnixFileSystem.java:281) 在 java.io.File.toPath(File.java:2069) 在 sun.nio.fs.UnixUriUtils.fromUri(UnixUriUtils.java:61) 在 sun.nio.fs.UnixFileSystemProvider.getPath(UnixFileSystemProvider.java:97) 在 java.nio.file.Paths.get(Paths.java:138) 在 os.FileNullCheck.testExists(FileNullCheck.java:39) 在 os.FileNullCheck.main(FileNullCheck.java:28)

【讨论】:

【参考方案3】:

仅由底层操作系统和文件系统决定。当您使用包含零的名称创建 File 时,在构造 File 期间不会更改此文件名:

String anull = new String(new byte[] 'a', 0x00, '.', 't', 'x', 't', 0x00);
System.out.println(anull);
System.out.println(new File(anull).getPath());

输出相同且包含零。

File 处理文件时,它使用文件系统(例如java.io.FileSystem),其实现取决于操作系统和JDK(它是内部类)。在 Windows JDK 中,几乎所有函数都是原生的,所以在这种情况下,这种行为是由底层操作系统(或 JDK dll)决定的。

【讨论】:

【参考方案4】:

嗯,这是我的第三次尝试。我在 Windows(Win 7,JDK 7 64 位)下检查了您的代码。它导致相同的结果:

a.txt exists: true (len=5)
a.txt (null) exists: true (len=6)
a.txt (nullx) exists: true (len=7)

在java源代码中我们可以看到,它使用native实现getBooleanAttributes(File f)方法。这意味着 JVM 与 JVM 的 c/c++ 库进行交互。在这种情况下,所有带有 0x00 符号的字符串都将在库中被解释为仅在 0x00 ('\0') 符号之前的字符串。

如何检查这个假设?我做了一个简单的实验。如果我关于在操作系统库中剪切这个字符串的假设是正确的,那么这段代码:

    String anull = new String(new byte[]  'a',0x00 , '.', 't', 'x', 't', 0x00 );
    System.out.printf("a.txt (null) exists: %b (len=%d)%n",new File(anull).exists(),anull.length());

将返回false。是的,就是这样:

a.txt (null) exists: false (len=7)

UPD:

这个:

String anull = new String(new byte[]  'a',0x00 , '.', 't', 'x', 't', 0x00 );
new File(anull).createNewFile();

将创建一个名为 a 的文件,不带任何扩展名。

【讨论】:

以上是关于java.io.File 中的空字符是不是对存在检查有效?的主要内容,如果未能解决你的问题,请参考以下文章

:Java之文件系统操作(Java.io.File)和文件内容的读写

第一节:Java之文件系统操作(Java.io.File)

第一节:Java之文件系统操作(Java.io.File)

java IO:File类

java.io.File中的pathSeparator与separator的差异

IO流一