深入分析java I/O

简介: I/O 是最为基础重要的一门工程,它是数据信息交换的渠道方式。在爆炸性海量数据的互联网时代,I/O问题尤其重要,往往影响我们系统的性能祸首之一。通过研读jdk代码,画出类层次结构图以及小实例代码说明,便于读者理解

I/O 是最为基础重要的一门工程,它是数据信息交换的渠道方式。在爆炸性海量数据的互联网时代,I/O问题尤其重要,往往影响我们系统的性能祸首之一。
常常有网络I/O网络数据上传与下载能力, 磁盘I/O即是我们硬盘的读写能力。
而I/O类主要在JDK包java.io下,类比较繁多,我们只需要挑一两个来study。根据数据流的格式和方式如下分类:
1)基于字节(抽象类):InputStream 和 OutputStream
2)基于字符(抽象类):Writer 和 Reader
3)基于磁盘文件(类):File
4)基于网络传输(类):Socket

基于字节的I/O操作
InputStream类的相关层级关系
image

代码1


    private static void inputStreamForFile(File file) throws IOException {
        InputStream fis = null;
        try {
            fis = new FileInputStream(file);
/*        int result;
        while ((result = fis.read()) != -1) {
            System.out.print((char)result);
        }*/

            byte[] buffer = new byte[(fis.available())]; // 缓存大小跟文件字节数一致
            fis.read(buffer);
            System.out.println(new String(buffer));
        } finally {
            fis.close();
        }
    }

与InputStream输入字节流相对应的是OutputStream输出字节流
image

代码2

public static void outputStreamToFile(File file) {
    String bufferStr = new String("\n hello from outStream!");
    byte buffer[] = bufferStr.getBytes();
    OutputStream out = null;
    try {
        // 以追加的方式写入文件
        out = new FileOutputStream(file, true);
        out.write(buffer);
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException ioEx) {
            System.out.println(ioEx.toString());
        }
    }
}

其中,FileOutputStream(File file, boolean append)
以追加的方式,添加入文件的末尾。
通过类层次可以知道,FileOutputStream 的子类有SocketOutputStream, 即网络输出字节流其实也是以文件流形式进行传输。

基于字符的I/O操作
最小的存储单元是字节而不是字符,所以I/O操作都是针对字节进行存储。那为啥需要引入字符呢?那是因为在我们的程序中,通常操作的数据都是以字符形式,为了操作方便需要提供一个直接写字符的 I/O 接口。

Reader 字符流,它的类层次结构如下
image

代码3

private static void readForFile(File file) throws IOException {
    StringBuffer sb;
    FileReader fileReader = null;
    try {
        sb = new StringBuffer();
        char[] buf = new char[1024];

        fileReader = new FileReader(file);
        while (fileReader.read(buf) > 0) {
            sb.append(buf);
        }
    } finally {
        fileReader.close();
    }
    System.out.println("readForFile,  " + sb.toString());
}

Writer的类层次结构
image

代码4

private static void writeForFile(File file) throws IOException {
    FileWriter fw = null;
    try {
        fw = new FileWriter(file, true);
        fw.write("this is from write");
    } finally {
        fw.close();
    }
}

刚才说了,底层是以最小单元为字节进行存储,而程序中经常以字符来进行操作,所以存在字节与字符的转化,拿InputStream 输入字节流举例,类结构如下
image

如果转换时,没有指定编码,会以默认的字符编码进行转义,默认的是“UTF-8”

  public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            String csn = AccessController.doPrivileged(
                new GetPropertyAction("file.encoding"));
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

java 访问磁盘文件
在 java 中通常的 File 并不代表一个真实存在的文件对象,当你指定一个路径描述符时,他就会返回代表这个路径的虚拟对象,它可能是一个真实存在的文件或者一个包含多个文件的文件夹。多数情况下,我们并不关心这是文件是否存在,只关心这个文件到底如何操作。那么什么时候会检查这个文件是否真实存在呢?
只有在真正需要读取文件的时候,才会去检查这个文件是否存在
前文已经提到,文件存盘的最小单位是字节,所以直接和硬盘打交道的是字节流,针对于输入文件字节流FileInputStream,在它实例化时会创建一个 FileDescritor 对象,其实FileDescritor就是代表一个存在的文件对象的描述。当我们在操作一个FileInputStream文件对象时,可以通过其 getFD() 方法来获取FileDescriptor对象。而FileDescriptor是与底层操作系统相关联文件的行为描述。

具体的行为包括如下:

/**
 * Force all system buffers to synchronize with the underlying
 * device.  This method returns after all modified data and
 * attributes of this FileDescriptor have been written to the
 * relevant device(s)…
**/
FileDescriptor.sync() 方法将操作系统缓存中的数据强制刷新到物理磁盘中。
/**
 * Tests if this file descriptor object is valid.
 */
FileDescriptor.valid() 方法将检测描述对象是否有效。

image

当传入一个文件路径时,将会根据这个路径创建一个 File 对象,然后创建StreamDecoder进行编码解码,最后将会真正创建一个关联真实存在的磁盘文件的文件描述符 FileDescriptor,这个对象可以用来操作磁盘文件。

目录
相关文章
|
26天前
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
59 1
|
2月前
|
缓存 JavaScript Java
常见java OOM异常分析排查思路分析
Java虚拟机(JVM)遇到内存不足时会抛出OutOfMemoryError(OOM)异常。常见OOM情况包括:1) **Java堆空间不足**:大量对象未被及时回收或内存泄漏;2) **线程栈空间不足**:递归过深或大量线程创建;3) **方法区溢出**:类信息过多,如CGLib代理类生成过多;4) **本机内存不足**:JNI调用消耗大量内存;5) **GC造成的内存不足**:频繁GC但效果不佳。解决方法包括调整JVM参数(如-Xmx、-Xss)、优化代码及使用高效垃圾回收器。
138 15
常见java OOM异常分析排查思路分析
|
6天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
9天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
24 2
|
10天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
26天前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
15 1
|
26天前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
35 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
29天前
|
Java
如何从Java字节码角度分析问题|8月更文挑战
如何从Java字节码角度分析问题|8月更文挑战
|
1月前
|
安全 网络协议 Java
Java反序列化漏洞与URLDNS利用链分析
Java反序列化漏洞与URLDNS利用链分析
47 3
|
14天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
17 0