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)和文件内容的读写