学习Java,就一定会碰到路径使用问题,如果不将它搞懂,在实际开发中碰到其实挺烦的,这并不是高级难题而是基础知识,碰到它的几率还挺大的。所以无论如何,我们必须把Java的路径问题彻底解决才行。
最基础的三个概念
我们学Java的首先就会碰到以下这三个东西。
JVM
JVM(Java Virtual Machine)即Java虚拟机,它主要用于执行被编译成Java字节码文件的Java程序。
JVM是Java程序实现跨平台的核心所在,它将Java程序与平台之间解耦- 针对不同的平台,就会有不同的Java虚拟机实现
- 这样对于同一个Java程序(同一份Java字节码),就会被具体平台相关联的Java虚拟机“翻译”成适合该平台执行的机器指令。
- 最后Java虚拟机执行被“翻译”后的Java程序
所以这就是书上或老师常说的Java程序一次编译,到处运行的具体原因。
JRE
JRE(Java Runtime Environment)即Java运行时环境,它包含了运行Java程序所需的一切:JVM实现以及Java核心类库。
为什么会有两套JRE?
默认情况下,我们都是选择完整安装JDK所有内容的。当我们安装完JDK后,我们会发现JDK安装目录下会有两套JRE,其中一套在Java\jre
目录中,另一套在Java\jdk\jre
目录中。但奇怪的是,只要有一套JRE就可以运行Java程序了啊,为什么要给我们装上两套呢?其实这是因为不管是用于开发的工具javac
命令等,或者非开发的工具java
命令等,它们都是用Java实现的,因此它们也是Java程序,所以JDK就自行附带一套JRE用于运行它们。
运行Java程序时用哪一套JRE
?
既然安装了多套JRE
,那么在运行Java程序时,运用哪套JRE的决定权都在java
命令上,它会按以下步骤查找:
java.exe
所在目录java.exe
所在目录的父目录PATH
环境变量,从左往右查找路径- 注册表
HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Encironment
里面有一个名为JavaHome
的键所对应的路径值
环境变量
下面所要介绍的都是我们在刚接触Java时就能接触到的,或许因为这些知识实在是太过基础了,显得完全不起眼,所以我们总是忽略它们的存在以及作用。这里对环境变量的讨论都是在Windows的基础上进行的。
JAVA_HOME
JAVA_HOME
是安装JDK后我们设置的一个自定义环境变量,其值被指定为JDK安装目录的路径。
E
盘,那么就配置这个值:E:\Program\Java\jdk1.8.0_121
。
这个环境变量并不是必须配的,它用于简化另一个环境变量PATH
所配置的路径,不过挺多教程、书籍上都是这么教的,所以就这么写上了。
PATH
安装完JDK后必须对环境变量PATH
或Path
进行配置(这不同于JAVA_HOME
,这是必须的),配置有两种方式:
- 如果有配置
JAVA_HOME
,那么一般都在PATH
环境变量中加上这个路径值%JAVA_HOME%\bin
即可,多个路径之间用分号;
隔开。 - 没配置
JAVA_HOME
的话,那就直接配置JDK安装目录下的bin
目录的完整路径
通常,都会使用第一种方式,先配置JAVA_HOME
,再配置PATH
。
为什么要给PATH
环境变量进行配置?
在编译
.java
文件要用到javac
命令;在运行已编译好的.class
文件要用到java
命令当我们查看JDK的
bin
目录,我们可以发现里面存放了很多可执行文件,这些都是早已编写好的工具,我们执行各种命令其实是调用这些工具而已
比如编译Java文件用的javac
命令相当于运行javac.exe
,而执行Java程序的java
命令就是运行java.exe
,执行用于打包的命令jar
命令就是运行jar.exe
- 所以只有在
PATH
系统变量里配置JDK的bin
目录的路径,那样在任何地方都能方便地调用JDK开发工具,不然每次使用这些命令时都写出它们的完整路径,那就十分麻烦了
JDK8的PATH
有所变动!
在安装JDK8后,它会自动给环境变量PATH
加上这一个路径:C:\ProgramData\Oracle\Java\javapath
,这样即使我们没有手动去配置环境变量,打开控制台也是能直接执行java
命令的。
打开这个目录可以发现里面有3个文件:java.exe
、javaw.exe
、javaws.exe
,但这没有我们熟知的javac.exe
编译工具,只有一个我们最熟悉的运行工具,所以说怎么样还是要配置PATH
环境变量。
同时,最好是把这个自动给PATH
配置的路径给删除掉,以免以后发生不必要的问题。
CLASSPATH
环境变量CLASSPATH
是用来告知各种应用包括JDK的各种工具在哪里可以找到.class
字节码文件,通常它包含一个或多个目录。
原理细节
- 当要运行Java程序时,即执行
java
命令,那后面肯定是指定全限定类名,即带包名的类名 - 首先,Java解析器会将全限定类名中的每个句点
.
替换成斜杠(/
还是\
取决于操作系统),这样就得到一个相对路径 - 然后,解析器会把
CLASSPATH
里所配置的绝对路径一一作为参考路径,尝试从它们里面找到该相对路径所指代的.class
字节码文件
所以现在很清楚了,如果在运行Java程序时提示找不到某个类,通常这就说明在CLASSPATH
所配置的所有路径都找不到某个类,你需要看看CLASSPATH
环境变量是否没配好。
配置和查看方式
这里只说Windows环境下的做法,在命令行对CLASSPATH
的操作如下。
查看CLASSPATH
的配值:1
echo %CLASSPATH%
给CLASSPATH
配置指定路径:1
2
3set CLASSPATH=path1;path2;path3;...
或
set classpath=path1;path2;path3;...
将所有路径清除:1
2
3set CLASSPATH=
或
set classpath=
不写路径就是把所有配值清除。
当然也可以在系统环境变量增加CLASSPATH
配置,很多教程都是给它配置上.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
,但我发现其实配不配置也是OK的,不配置也没发现什么大问题。
理解相对路径
在Java程序里使用各种各样的路径,你要根据路径所处的上下文处理好一些细节问题,不然会各种碰壁,特别是使用相对路径的时候,要更加细心谨慎。而使用绝对路径来定位资源一般是不会有问题的,都是绝对的了,绝对路径代表是哪个资源就是哪个资源,是定死不变的。所以一般在开发种遇到涉及路径的问题,其实90%都是关于相对路径的问题。所以,绝对路径没啥好讨论的,这里只探讨相对路径,并且也只针对普通磁盘文件IO这一种情况来讨论。
先来看一段程序:
1 | import java.io.File; |
这里我通过IDEIntelliJ IDEA以及Windows的控制台执行这个程序,得出的运行结果就如注释里所写那样。
所以从这个运行结果就能够分析并总结出2点:
- 其实通过IDE执行Java程序跟使用控制台执行Java程序一个样,只不过IDE每次在执行
java
命令前先把路径设置为当前工程/模块的根目录 - 给处理磁盘文件的各种IO组件指定相对路径,该相对路径在运行时的真实含义是根据执行
java
命令时所在的路径变化而变化
总的来说就是:对于Java普通磁盘IO这种情况来说,所指定的相对路径是相对于执行java
命令时所在的路径。
但其实就算是其他情况,我觉得也是大同小异,下面是一点点思路(或许不太正确),当自己使用相对路径的时候可以像下面一样思考:
- 光相对路径自身是无法指代某个具体资源的,即只有相对路径是毫无意义的
- 相对路径必须依靠一个绝对路径作为参考,那它才能定位到某个具体资源
- 要想知道所使用的相对路径是否精确地定位到自己所想要的资源,那先得反问一下自己,自己给相对路径所指定作为参考的绝对路径是否符合自己的需求
所以,当相对路径不能根据自己的需求工作时,那就首先自问一句:该相对路径是相对于哪个绝对路径?确认绝对路径正是解决需求的路径,那才从别的角度入手。
与类字节码相关的相对路径
需求
在实际开发中,这么一个需求是很常见的:程序需要读取某些外部文件从而获得某些配值。
直接在程序中使用绝对路径,是没问题,特别是该程序只是自己使用的话,的确是毫无问题;但如果说要给别人用,那就十分麻烦,首先,别人不知道要将资源文件放到哪个固定位置让程序读取,这你要告诉别人,其次,别人可能用的系统和你的不一样,那文件系统都不同,这样就更没辙了,程序完全没法用。所以,使用绝对路径,程序的通用性是十分差的,一般我们都不会在程序中用绝对路径。
所以,相对路径成了我们唯一的选择,相对路径的灵活性大,这样也会使得程序的通用性大大增强,但正如前面所讲,相对路径容易错误,要确定它所定位的是哪个资源文件,那得看它相对于哪个绝对路径,这个绝对路径最好是程序运行文件所在位置或其相关位置,这样不管该程序放到哪运行,都能让相对路径准确无误地定位到资源文件。而Java中正好有现成的技术让开发者简单地进行实现。
Java中的技术
在Java中,通过调用Class
或ClassLoader
的getResource
方法并给它指定相对路径,这样就可以获取相对于字节码所在位置或其相关位置的资源。
注意到这里的方法名以及后头重点标出来的资源两字!!获取的是资源,资源、资源、资源,这个概念在Java中很重要。
存储于磁盘上的文件只不过是我们最常见的一种资源,在Java内置的API中,凡是涉及到资源概念的,这都表明这里的资源并不是单纯指的文件,资源可不是单单以文件的形式存在,它可以是通过某些程序在内存上生成,也可以存在于网络上,要通过网络连接才能进行获取,比如各种流。这应该是一个通用的概念,所以这是明白一次,再套用到其他编程语言其实也差不了多少。
底层原理
想要知道getResource
方法是如何获取资源,那得要看源码,对底层要有所了解才行。
首先来看Class
类用于获取资源的getResource
方法的源码,毕竟这个类是我们最常用的,肯定是首先关心它内部那个获取资源的方法是如何工作的。
1 | /** |
方法实现很简单,首先它调用resolveName
处理传递进去的相对路径,然后尝试获取该构筑该字节码的自定义类加载器,如果获取到就通过它来获取资源,否则就用默认的类加载器进行资源获取。
1 | /** |