网站首页 > 技术文章 正文
前言
刚接触 java 的时候很困惑一个事情 File相对路径,以哪个目录为参照物。
随着 io 模型的发展,java 1.7 的 nio,使用 Path、Paths 和 Files 等来方便 io 的操作。
ClassLoader 用于获取class 文件 的 io,我们也可以用于获取文件的 io,以便于我们读取文件内容。
本文设计内容
- File ,ZipFile,JarFile 读取相对路径和绝对路径文件内容。
- System.getProperty("user.dir”) 是怎么来的。
- Paths、Path、Files 读取文件内容。
- 类加载器获取文件内容,Class.getResourceAsStream 和 ClassLoader.getResourceAsStream。
- 介绍类加载器的双亲委派模型,及在代码中找到对应的加载逻辑。
代码基于 Mac 10.15.4,JDK 1.8。
基于 File 获取文件内容
绝对路径的内容获取比较简单,直接获取文件 io ,然后利用工具类读取文件内容。
获取绝对路径文件内容
final File file = new File("/Users/zhangpanqin/github/fly-java/demo.txt");
final byte[] bytes = cn.hutool.core.io.FileUtil.readBytes(file);
System.out.println(new String(bytes, StandardCharsets.UTF_8));
获取 JarFile 中的内容
JarFile 继承 ZipFile 用于获取 jar 包中的内容。比如我想获取 jar 中的某个文件的的内容。
final File file = new File("/Users/zhangpanqin/github/fly-java/src/main/resources/fastjson-1.2.68.jar");
final JarFile jarFile = new JarFile(file);
final JarEntry jarEntry = jarFile.getJarEntry("META-INF/LICENSE.txt");
final InputStream inputStream = jarFile.getInputStream(jarEntry);
// 工具类是 hutool
System.out.println(IoUtil.read(inputStream, StandardCharsets.UTF_8));
IoUtil.close(inputStream);
获取相对路径的内容
File.getAbsolutePath 查看源码可以发现,相对路径其实就是在前面拼接了 System.getProperty("user.dir")。
class UnixFileSystem extends FileSystem {
public String resolve(File f) {
if (isAbsolute(f)) return f.getPath();
return resolve(System.getProperty("user.dir"), f.getPath());
}
}
只要我们弄清楚 System.getProperty("user.dir"),问题就迎刃而解。Java System Properties 中介绍 user.dir 是用户的工作目录。
什么是用户工作目录呢?就是执行 java 命令的目录。在那个目录下执行命令,usr.dir 就会被 java 虚拟机赋值为执行命令的路径。我在 /Users/zhangpanqin/github/fly-java/test 目录下运行编译的 class 文件。-cp 指定 classpath 路径。
nio 读取文件内容
Path 可以类比 File 理解使用。然后工具类 Paths 可以获得 Path,Files 更是提供了丰富的 api 用于crud 操作文件 Path。
获取绝对路径内容
@Test
public void run33() throws IOException {
final Path path = Paths.get("/Users/zhangpanqin/github/fly-java/demo.txt");
final byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes,StandardCharsets.UTF_8));
}
获取相对路径内容
Paths 获取相对路径时,路径不以 / 开头。也可以理解成相对于 System.getProperty("user.dir") 路径。
public static void main(String[] args) {
System.out.println(System.getProperty("user.dir"));
System.out.println(Paths.get("").toAbsolutePath());
}
基于 ClassLoader 获取文件内容
ClassLoader.getResourceAsStream
// ClassLoader.getResourceAsStream java 1.8 源码
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
从代码可以看到主要逻辑还是集中在 getResource 。
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
以上代码的逻辑也即是我们常听到的 双亲委派机制。先让 父类加载 去加载资源,找不到再有自己找。类加载器单独讲。
类加载器读取读取资源,先从自己负责的路径查找。比如应用类加载器 sun.misc.Launcher.AppClassLoader#AppClassLoader 负责 classpath 查找资源。类加载器读取资源相对于 File 和 Path 优势在哪里呢?比如当我想获取一个 jar 中的资源,你用路径就比较麻烦了,ClassLoader 可以从负责的路径下寻找,还可以去 jar 包中寻找。
final URL resource = Test2.class.getClassLoader().getResource("com/alibaba/fastjson/JSONArray.class");
System.out.println(resource);
上述打印结果
jar:file:/Users/zhangpanqin/.m2/repository/com/alibaba/fastjson/1.2.62/fastjson-1.2.62.jar!/com/alibaba/fastjson/JSONArray.class
我们还可以获取一个路径的 inputstream
@Test
public void run222(){
final InputStream resourceAsStream = Test2.class.getClassLoader().getResourceAsStream("META-INF/maven/com.alibaba/fastjson/pom.properties");
System.out.println(IoUtil.read(resourceAsStream, StandardCharsets.UTF_8));
}
上述结果为:
#Generated by Maven
#Mon Oct 07 22:09:36 CST 2019
version=1.2.62
groupId=com.alibaba
artifactId=fastjson
Class.getResourceAsStream
Class.getResourceAsStream 实际也调用的 ClassLoader 加载资源,但是它会将路径补充到相对于当前类所在包的路径。
比如
// com.fly.study.java.classloader.Test2
@Test
public void run555(){
System.out.println(Test2.class.getResource("name"));
}
实际查找的资源为 com.fly.study.java.classloader.name。相对于当前类所在包的资源。
类加载器
我们经常会听到类加载器的 双亲委派模型。
去哪里可以看到这些类加载呢。
启动类加载器 不是 java 代码实现的我们看不到源码。
sun.misc.Launcher 类中有我们知道的 扩展类加载器 sun.misc.Launcher.ExtClassLoader 和 应用类加载器 sun.misc.Launcher.AppClassLoader 。
java.lang.ClassLoader#getSystemClassLoader 代码看的话,实际返回的应用类加载器。
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
}
}
}
运行代码测试,返回的是应用类加载器。
// sun.misc.Launcher$AppClassLoader@18b4aac2
@Test
public void run66(){
System.out.println(ClassLoader.getSystemClassLoader());
}
这三个类加载器负责不同路径下的类加载。
启动类加载器介绍
启动类加载器 BootClassLoader 负责System.getProperty("sun.boot.class.path") 的类加载。也即是以下类。
${JAVA_HOME}/jre/lib/*.jar 和 ${JAVA_HOME}/jre/classes 类的加载。
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/classes
@Test
public void run77() {
final URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
final URL[] urLs = bootstrapClassPath.getURLs();
Stream.of(urLs).forEach(item->{
System.out.println(item.getFile());
});
}
扩展类加载器
扩展类加载器 sun.misc.Launcher.ExtClassLoader 为加载 System.getProperty("java.ext.dirs") 中的类。
@Test
public void run99() {
final String property = System.getProperty("java.ext.dirs");
final String[] split = property.split(":");
Stream.of(split).forEach(item -> {
System.out.println(item);
});
}
/Users/zhangpanqin/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
应用类加载器
应用类加载器 sun.misc.Launcher.AppClassLoader 加载 System.getProperty("java.class.path");
# -cp 指定 classpath 路径,多个路径可以使用 : 分开(linux 下为 :,window 下为 ;),
java -cp /Users/zhangpanqin/github/fly-java/target/classes com.fly.study.java.classloader.Test2
本文由 张攀钦的博客 http://www.mflyyou.cn/ 创作。 可自由转载、引用,但需署名作者且注明文章出处。
如转载至微信公众号,请在文末添加作者公众号二维码。微信公众号名称:Mflyyou
- 上一篇: Java 合并Word文档
- 下一篇: Spring Boot配置外部静态资源访问路径
猜你喜欢
- 2024-11-25 【力扣】[Java版]刷题笔记-合并两个有序链表
- 2024-11-25 Java中对于+和append拼接字符串效率的误解
- 2024-11-25 java使用ByteBuffer并进行多文件合并和拆分
- 2024-11-25 2020-12-17:java和go,如何高效的拼接字符串?
- 2024-11-25 Java字符串拼接技术演进及阿里巴巴的贡献
- 2024-11-25 如何提升Jmeter操作?那你一定得学会BeanShell
- 2024-11-25 Java 字符串拼接 五种方法的性能比较分析
- 2024-11-25 Java合并两个数组,以及数组排序并去重
- 2024-11-25 Java Stream 流如何进行合并操作
- 2024-11-25 Java请求合并与分而治之
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)