难经2:URL.getFile(),是你让老虎落入陷阱?

本文探讨了在不同Java版本中使用ClassLoader加载资源时遇到的问题,尤其是在处理包含空格和中文字符的文件名时。揭示了URL.getFile()在Java 1.4.2与Java 5及以上版本之间的行为差异,并提供了使用URLDecoder.decode()进行解码的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[问题]

 由于开发需要,编写了一个简单的单元测试框架,在基类中从类路径加载资源,并执行初始化动作(类似Spring测试基类),利用ClassLoader,加载资源自是驾轻就熟。我自己通过继承这个测试基类,编写了不少测试类,感觉还算方便,也没有出过什么问题。不过,近日其他同事Z在用这个测试基类时,却经常出现加载不到资源的问题,很奇怪。

[探幽]

既然是加载不到资源,还是第一反应,肯定是资源路径有问题,可是我的环境下并没有报这个错啊,看看Z同事的测试类,要加载的资源文件明明就在那,类路径指定也没有问题啊;扫了一眼,看见资源文件的命名是中文的,不过我并没有在意,因为我用中文测过,没有问题。

 

不是中文问题,那可能是其他什么原因呢?苦思不得其解,回到自己的机器上,调试了一下程序,本机运行依然没有问题,重新审查加载资源的方法实现,用的ClassLoader定位取得URL,再用URL.getFile()取得实际路径,最后new File()读取文件,没什么问题啊?

 

要真有问题,难道是URL.getFile()不正确?想到这一点,再想想Z君的开发环境,他是在Java5下开发,而我还是坚守者1.4.2阵地,难道?难道Java5下会有问题?我飞快的抓起鼠标,把编译环境改为Tiger,运行,出错!Java5下确实找不到资源!断点调试发现,在Java5下,URL.getFile()返回的资源位置是未解码的,空格和中文成了URL转码格式。URL.getFile(),真的是你让Tiger老虎落入陷阱么?

 

为了确认问题,我单独建了一个测试项目,写了一个测试类,分别在Java1.4.2、Java5、Java6下运行, 结果确实让我惊讶:

public class URLTest {
	public static void main(String[] args) {
		
		URL url1 = URLTest.class.getResource("te st.txt");
		URL url2 = URLTest.class.getResource("中文.txt");
		
		System.out.println("te st.txt => " + url1.getFile() + ", exist => " + new File(url1.getFile()).exists());
		System.out.println("中文.txt => " + url2.getFile() + ", exist => " + new File(url2.getFile()).exists());
	} 
}

其中“te st.txt”和"中文.txt"是和测试类放在一起的测试文件。

 

 Java1.4.2下输出:

te st.txt => /E:/CODE/Test/bin/url/te st.txt, exist => true
中文.txt => /E:/CODE/Test/bin/url/中文.txt, exist => true

 

Java5和Java6均输出:

te st.txt => /E:/CODE/Test/bin/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin/url/%e4%b8%ad%e6%96%87.txt, exist => false

 

 

看来,原来URL.getFile()是在1.4.2下能自动解码的,到了老虎和野马那里却不灵了,从而导致找不到资源文件。找到了问题,我长舒了一口气。

 

既然问题已经找到,答案呼之欲出,不过,为什么Java5以后要修改URL的行为,不再自动解码呢?我还是有些疑惑。

 

接下来,我一不做二不休,不就是类路径么,我再改,把编译路径从“bin”改成“bin all”,中间加空格,再次运行。。。。。。

 

Java1.4.2下输出:

te st.txt => /E:/CODE/Test/bin%20all/url/te st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/中文.txt, exist => false

Java5和Java6均输出:

te st.txt => /E:/CODE/Test/bin%20all/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/%e4%b8%ad%e6%96%87.txt, exist => false

 

哈哈,Java1.4.2的URL终于露馅了,原来它的解码是不完全的!原来URL.getFile()本身就是陷阱啊!即使是在1.4.2下,它也只是给我们一个迷惑的假相。

 

回头想想,为什么Sun要冒着版本不兼容的危险,也要修改URL的行为呢?这或许就是其深层次的原因。

 

[解难]

搞清楚了问题的症结,按方抓药即可。URLDecoder正是用来干这个活的,同时祭出Google,看看同志们是不是早就搞定这个问题了,放眼望去,URL.getFile()搜出来的结果还真不少啊,连带上URL.getPath()也受到牵连,而且URL.getFile()和URL.getPath()得到的路径还可能不一致!又是一个陷阱啊。

 

终解,用URLDecoder:

System.out.println("te st.txt =decode=> " + URLDecoder.decode(url1.getFile(), "UTF-8"));
System.out.println("中文.txt =decode=> " + URLDecoder.decode(url2.getFile(), "UTF-8"));

 

另外,URI也是可以自动解码的:

System.out.println("te st.txt =uri=> " + new URI(url1.getFile()).getPath());
System.out.println("te st.txt =uri=> " + new URI(url2.getFile()).getPath());

 

在Java5和Java6下运行,二者皆输出正常:

te st.txt => /E:/CODE/Test/bin%20all/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/%e4%b8%ad%e6%96%87.txt, exist => false
te st.txt =decode=> /E:/CODE/Test/bin all/url/te st.txt
中文.txt =decode=> /E:/CODE/Test/bin all/url/中文.txt
te st.txt =uri=> /E:/CODE/Test/bin all/url/te st.txt
te st.txt =uri=> /E:/CODE/Test/bin all/url/中文.txt

 

不过,1.4.2下,URI又可能是一个陷阱:

te st.txt => /E:/CODE/Test/bin/url/te st.txt, exist => true
中文.txt => /E:/CODE/Test/bin/url/中文.txt, exist => true
te st.txt =decode=> /E:/CODE/Test/bin/url/te st.txt
中文.txt =decode=> /E:/CODE/Test/bin/url/中文.txt
java.net.URISyntaxException: Illegal character in path at index 24: /E:/CODE/Test/bin/url/te st.txt
	at java.net.URI$Parser.fail(URI.java:2752)
	at java.net.URI$Parser.checkChars(URI.java:2925)
	at java.net.URI$Parser.parseHierarchical(URI.java:3009)
	at java.net.URI$Parser.parse(URI.java:2967)
	at java.net.URI.<init>(URI.java:574)
	at url.URLTest.main(URLTest.java:24)
Exception in thread "main" 

URI对于不完整解码的路径,会抛语法异常。 

 

看来,还是Decoder.decode()是最终正解啊,用URI的同学要小心了,请保证你的应用跑在Java5上。

另,看到JavaEye里有兄弟自己替换"%20"为空格,来解决临时这个问题,这样很不好吧,有中文怎么办?

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值