文件路径和类路径

前言

在初学Java的时候,路径就是一个很有意思的话题。例如安装JDK的时候需要配置JAVA_HOME、CLASSPATH(现在可以不配置CLASSPATH了)。学习IO编程的时候,不可避免需要对文件的进行操作,例如读写文件、读取配置文件等等,每一个都是一个问题。作为一个新手,我基本上都踩了坑!哈哈!因为最近又遇到一些路径上的困扰,所以来总结一下!

文件路径 FilePath

这里想来说一下文件中的路径,当初刚学Java的IO流的时候,对于文件的路径一直都很迷惑,不过最初的目的也很简单,只是为了让程序运行起来,只是去查资料,不断尝试,希望可以试出来一个正确的路径。

万物始于一点 .

对于绝对路径,一般都是没有问题的,只要能找到文件的位置,绝对路径可以唯一标识一个文件的位置。但是相对路径有时候,就不是那么容易理解了。现在就来说一下这个.,相信很多人都遇到过这个代码File file = new File(".");。当初这个问题困扰了我好久,以至于我基本上没有用过它,我不太明白,会有文件名是一个点吗?哈哈。现在我们来看一下,这个点到底是什么意思。学习操作系统的时候,明白了,./表示当前路径。在Java中路径是可以不带这个斜杠的(windows中也可以使用正斜杠,这个是没有问题的,而且也避免了双反斜杠复制转义的问题。)

代码演示

File currentDir1 = new File(".");
System.out.println("currentDir1.getAbsolutePath(): " + currentDir1.getAbsolutePath());
System.out.println("currentDir1.getCanonicalPath(): "+ currentDir1.getCanonicalPath());
System.out.println("currentDir1.getPath(): " + currentDir1.getPath());
File currentDir2 = new File("./");
System.out.println("currentDir2.getAbsolutePath(): " + currentDir2.getAbsolutePath());
System.out.println("currentDir2.getCanonicalPath(): "+ currentDir2.getCanonicalPath());
System.out.println("currentDir2.getPath(): " + currentDir2.getPath());

注:这里这个 getCanonicalPath 返回的是规范路径,即不含有限度路径的那些东西,例如此处的 .

执行结果

在这里插入图片描述

还有一种方式可以获取到当前的路径,并且是一个绝对路径:

演示代码

// 使用系统属性获取当前路径的方法
System.out.println("user.dir: " + System.getProperty("user.dir"));

运行结果

user.dir: D:\JavaProject\SourceAnalysis

说明

. 或者说 ./ 或者 .\\ 它所表示的就是当前代码文件(字节码文件或者Class文件)所在的路径(工程的路径)。所以,当你需要加载一个在Java工程中的资源的时候,只要记得上面这种方式表示是项目的根目录,基本上就不会迷路了(找不到路径)。但是并不建议使用 ./src/packagename/xxx 这种形式,后面会提到为什么。

类路径 ClassPath

学习过Java,但是一直对这个classpath比较陌生,也没有仔细去了解过。根据目前掌握的程度,我的理解是它就是class文件的位置——即class文件的路径。那么当我们启动一个程序,需要用到的类文件在哪里呢?这是一个有趣的问题,同样也可以使用代码获取到。

演示代码

System.out.println("java.class.path: " + System.getProperty("java.class.path"));

运行结果

java.class.path: D:\JavaProject\SourceAnalysis\bin;C:\Program Files\Java\jdk1.8.0_241\bin

说明
这里可以看出来,类文件所在的位置是当前工程的bin目录和jdk的bin目录,至于哪里面是啥?猜测是不对的,眼见为实嘛!

首先是jdk的bin目录,里面是一些可执行文件(exe程序)。这里我们主要关注 java.exe 和 javac.exe就行了。因为执行代码基本上只需要它俩就行了。
在这里插入图片描述

其次是当前工程的bin目录,它下面就是java文件编译后的class文件了。
在这里插入图片描述
然后回想一下,当我们使用命令行来编译执行代码的时候,是不是有点理解了。但我们在IDE中的src目录下面写代码,但是编译后实际上的class文件是在src的同级目录bin中的,理解这一点是很重要的。马上还会提到它!

下面提供一个使用命令行编译执行代码的例子,首先是在test目录下面有一个Java文件,它的包名是
i.love.you。然后依次建立这三个文件夹,并将文件放入其中。
在这里插入图片描述
执行如下的命令,查看结果。
在这里插入图片描述

此时的Java目录,可以发现多了一个class文件,但是我们的IDE中,java源文件和class文件是不在一起的,这是由于编译时指定了class文件所在的目录,这里就不去尝试了,感兴趣的可以试一下。
在这里插入图片描述

我们还是回到上面那个cmd的截图上去看,
1.为什么编译时指定路径 ./i/love/you/TestPath.java 呢?
2.为什么执行时指定包名 i.love.you.TestPath 呢?

答:
1.其实很容易理解,首先使用j avac 编译编译文件,需要指定这个文件的位置,这里我使用绝对路径,可以让你看得更清楚。这个很容易理解,你需要指定要编译的文件,它才能找到并编译该源文件。
2.编译之后,就产生了类路径,所以可以通过使用包名来加载该类,并执行。这里这个加载是指通过指定文件的位置来加载该类,但是它其实也是相当于一种路径,因为包名和路径名是一样的。java程序也是必须知道class文件的位置,才能加载它的,当然了也可以单独指定其它的classpath,使用 -classpath 参数指定一个路径。
在这里插入图片描述

从src目录和classpath中加载配置文件

我们知道src是存放源代码的,它也是一个确定的路径,那我们可以使用它作为加载配置文件的路径了。相信很多人都用过这样的路径 ./src/packagename/xxx.properties 来加载配置文件,这样通常也是可以成功的,但是如果把项目打包了呢,它还能正常运行嘛?

Talk is cheap, show me the code!

对于这种强调实践的事情,还是要动手试一试才能知道,并且加强理解。下面提供一个简单的示例代码,用于演示。

项目结构
在这里插入图片描述

演示代码

package dragon;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class FilePathTest {
	
	public static void main(String[] args) throws IOException {
		try {
			loadFromSrc();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try {
			loadFromClass();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 方法1:从src目录下加载配置文件
	 * @throws IOException 
	 * */
	static void loadFromSrc() throws IOException {
		InputStream input = new FileInputStream("./src/dragon/test.properties");
		Properties pro = new Properties();
		pro.load(input);
		String value = pro.getProperty("dragon");
		System.out.println("从src目录下加载配置文件:" + value);
	}
	
	
	
	/**
	 * 方法2:使用类路径加载配置文件
	 * @throws IOException 
	 * */
	static void loadFromClass() throws IOException {
		InputStream input = FilePathTest.class.getResourceAsStream("test.properties");
		Properties pro = new Properties();
		pro.load(input);
		String value = pro.getProperty("dragon");
		System.out.println("从类路径下加载配置文件:" + value);
	}
}

在 IDE 环境中执行
说明,一切正常,两种方式都可以正常工作,但是不要以为它就真的可以正常工作了。
在这里插入图片描述

打包在命令行中执行
在这里插入图片描述
通过上面的执行结果可以发现,产生了异常。只有通过类路径加载配置文件的方式成功。(我对两个方法都进行了异常处理,不能放在一起处理。不然第一个崩溃,程序直接跳到异常了。) 那么这是为什么呢?为什么打包后,就无法从src目录读取配置文件了呢?
让我们带走疑问,打开jar包吧!

注:jar包和zip相似,区别在于它有一个META-INF(元信息文件夹,包含一些必要的信息。)
在这里插入图片描述
在这里插入图片描述

正常情况下打包后的文件只是包括了 class 文件的,不包括src目录的,所以你根本无法在打包后的目录中从src这个路径中加载配置文件的。所以,只能从类路径加载配置文件。这里你可能有一个疑惑,我从类路径加载配置文件那说明配置文件应该在类路径即ClassPath中,但是配置文件明明是在src目录中的!
你注意看我上面那个配置文件是在src目录,怎么现在和class文件处于同一个目录了?这是IDE的自动操作,当你在src目录创建一个配置文件时,它会自动在对应的class文件目录中复制一份配置文件的。所以,你才能使用类路径加载配置文件,不然类路径没有文件,你也是加载不到的! 这里可以看出来使用 IDE 会屏蔽掉一些使用上的细节,让我们无法一窥全貌,所以不要过度依赖工具!

PS: 当然了,如果需要加载的文件不在jar包中,在jar包外面,也是可以使用相对路径或者绝对路径加载的。如果是在jar包内的话,是不能从src目录中加载的,但是我见到了一个非常厉害的哥们的博客,解决方案是把src复制到jar包中! 哈哈,倒也不失为一种巧妙的方式吧!

说明

1.如果需要在项目中使用其它的静态资源,例如英语、图片等,建议可以单独创建一个resource文件夹,并将文件存入其中。然后使用 File file = new File("./resource/xxx.jpg"); 或者 File file = new File(System.getProperty("user.dir") + "/resource/xxx.jpg");的方式来加载资源。

2.如果需要使用配置文件的话,使用类路径加载的话,可能更加的方便一些。可以直接存入class文件的相应目录,然后使用对应的类来调用即可了。

顺带提一下 ResourceBundle 类

我以前知道使用这个类可以实现程序的国际化,但是当我使用它的使用,我感觉非常奇怪,无论如何我都无法加载我需要的配置文件。而且,网上的博客,有些似乎也是纯粹的误导,没有一点儿帮助!
关于getBundle里面的参数,应该是该配置文件的全限定名,类似于java.lang.String这种形式,不需要添加扩展名。这里我们使用 dragon.tes 即可。

代码示例

我还这个工程来举例说明,注意这里的意思是类路径的同样位置有一个test.properties配置文件。
在这里插入图片描述
配置文件的内容
在这里插入图片描述

配置文件使用的编码
在这里插入图片描述

编写代码读取配置文件

package dragon;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.Properties;
import java.util.ResourceBundle;

public class FilePathTest {
	public static void main(String[] args) {
		ResourceBundle resource = ResourceBundle.getBundle("dragon.test");		
		Enumeration<String> e = resource.getKeys();
		while (e.hasMoreElements()) {
			String str = e.nextElement();
			System.out.println(str);
		}
		
		String dragon = resource.getString("dragon");
		String love = resource.getString("love");
		System.out.println(dragon);
		System.out.println(love);
		System.out.println("当前默认的编码是:" + Charset.defaultCharset());
	}
}

代码执行结果
前两行是配置文件中的键,接着两行是配置文件中的值,最后一行是当前默认的编码集。
在这里插入图片描述

注意:可以看出来,即使是使用了UTF-8,并且文件也是UTF-编码,读取还是产生了乱码。经过查阅资料得知,当使用ResourceBundle类来加载配置文件时,它使用的是ISO-8859-1,这是和该类的功能有关,同时使用它来实现国际化。

网络上关于它的将要介绍:
因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。

所以,我们需要做一个转换,将ISO-8859-1转为UTF-8编码,这里提供一个简单的方法来实现这个转换功能。

public static String ISO2UTF_8(String value) {
	byte[] b = value.getBytes(Charset.forName("ISO-8859-1"));  //Java默认使用这个编码来读写配置文件
	return new String(b, Charset.forName("UTF-8"));
}

然后对于中文调用该方法即可。

public class FilePathTest {
	public static void main(String[] args) {
		ResourceBundle resource = ResourceBundle.getBundle("dragon.test");		
		Enumeration<String> e = resource.getKeys();
		while (e.hasMoreElements()) {
			String str = e.nextElement();
			System.out.println(str);
		}
		
		String dragon = resource.getString("dragon");
		String love = resource.getString("love");
		System.out.println(dragon);
		System.out.println(ISO2UTF_8(love));
		System.out.println("当前默认的编码是:" + Charset.defaultCharset());
	}
}

代码运行结果
可以发现经过这样的处理后,乱码问题也解决了。
在这里插入图片描述

PS:将配置文件改名成如下的形式,上面读取的代码不需要改动,仍然是可以正常读取的。这个主要和它的国际化有关。实现国际化,需要多个这样的配置文件,每个文件会跟上所用语言和国家的代码,如 zh_CN 简体中文 中国。这样程序运行时,会通过获取本机的环境,自动读取相应的国家的配置文件,即可以实现国际化了。我对此也只是了解这么多,因为这个不是这里的重点,这里就只是提一下了。
在这里插入图片描述

©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页