Java语言基础13—IO

I/O(输入/输出)


参考资料:《Java从入门到精通》/明日科技编著. 4版. 北京:清华大学出版社,2016

一、流概述

  • 流是一种有序的数据序列,根据操作的类型,可分为输入流输出流
  • I/O(Input/Output,输入/输出)流提供了一条通道程序,可以用这条通道把源中的字节序列送到目的地。
  • 虽然I/O流通常与磁盘有关,但是程序的源和目的地也可以是键盘、鼠标、内存或显示器窗口等。

Java由数据流处理输入/输出模式,程序从指向源的输入流中读取数据,数据源可以是文件、网络、压缩包或其他数据源,这个过程也称为数据的读取,如下图所示:

image

输出流是指向数据要到达的目的地,程序通过向输出流中写入数据,把信息传递到目的地,输出流的目的地可以是文件、网络、压缩包或其他输出目标,该过程也称为数据的写入,如下图所示:

image

总的来说,在Java程序中,要想从文件中读取数据或者将数据写入文件中,都需要在程序和文件之间建立一条数据的传输通道,这个通道就是输入/输出流。

  • 当程序创建输入流对象时,Java会自动创建输入流通道;
  • 当程序创建输出流对象时,Java会自动创建输出流通道。

二、输入/输出流

Java语言在java.io包中定义了很多处理各种输入/输出的类。

  • 字节流
  1. 抽象类InputStream(字节输入流)是所有输入字节流类的父类
  2. 抽象类OutputStream(字节输出流)是所有输出字节流类的父类
  • 字符流
  1. 抽象类Reader(字符输入流)是所有输入字符流类的父类
  2. 抽象类Writer(字符输出流)是所有输出字符流类的父类

字节流与字符流的区别
image

简而言之,字节流在操作文件时不会用到缓存(内存),而是直接操作文件本身。而字符流在操作的时候使用了缓存区,而不是直接操作文件。例如,使用字符流执行写入数据操作,如果使用close()方法没有关闭输出流,则文件中看不到任何写入的内容,除非使用flush()方法强行将缓存数据写入目标文件。

本质上来说,一切都是字节流,其实没有字符流这个东西。字符只是根据编码集对字节进行解码的产物。

字节流:01010100010010100101
字符流:aah65Khannskm3bhbasv  //本质上还是字节流

技巧】:字节流可以处理所有数据文件,若处理的是纯文本数据,建议使用字符流。

1、输入流

(1)字节输入流

InputStream类是字节输入流的抽象类,是所有字节输入流的父类,其层次结构如下所示:
image

该类中所有方法遇到错误时都会引发IOException异常,下面是该类的一些常用方法:

  • read ():从输入流中读取数据的下一个字节。
  • read (byte[] b):从输入流中读入一定长度的字节,并以整数形式返回字节数。
  • mark (int readlimit):在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数。
  • reset ():将输入指针返回到当前所做的标记处。
  • skip (long n):跳过输入流上的n个字符并返回实际跳过的字节数。
  • close() :关闭此输入流,并释放与该流相关的所有系统资源。

说明:并不是所有的InputStream类的子类都支持InputStream中定义的所有方法,如skip()、mark()、reset()等方法只对某些子类有用。

(2)字符输入流

Java中的字符是Unicode编码,是双字节的,即一个字符占用两个字节。InputStream是用来处理字节的,并不适合处理字符文本。Java为字符文本的输入专门提供了一套单独的类Reader,但是Reader类并不是InputStream类的替换者,只是在处理字符串时简化了编程。

Reader类是字符输入流的抽象类,是所有字符输入流的父类

2、输出流

(1)字节输出流

OutputStream类是字节输出流的抽象类,该类是所有表示字节输出流类的父类,其层次结构如下所示:
image

OutputStream类中的所有方法均返回void,在遇到错误时会引发IOException异常,下面对OutputStream类中的方法做简单的介绍:

  • write (int b):将指定的字节写入此输出流。
  • write (byte[] b):将b个字节从指定的byte数组写入此输出流。
  • write (byte[] b, int off, int len):将指定byte数组中从偏移量off开始的len个字节写入此输出流。
  • flush():彻底完成输出并清空缓存区。
  • close():关闭输出流。

(2)字符输出流

Writer类是字符输出流的抽象类,此类为所有字符输出流类的父类,其层次结构如下所示:
image

三、File类

  • File类是java.io包中唯一代表磁盘文件本身的对象。
  • 可以通过调用File类中的方法,实现创建、删除、重命名文件等操作。
  • File类的对象主要用来获取文件本身的一些信息,如文件所在的目录、文件的长度、文件的读写权限等,但是不能对文件执行读取或写入数据的操作
  • 数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒介。

1、文件的创建与删除

可以使用File类创建一个文件对象。通常使用以下3种构造方法来创建文件对象:

  1. File (String pathname)

该构造方法通过将给定的路径名字符串,并转换为抽象路径名来创建一个新的File实例。语法格式如下:

new File(String pathname)

其中,pathname指路径名称(包含文件名),例如:

File file = new File("C:UsersXULIANGabc.txt") 
  1. File (String parent, String child)

该构造方法根据定义的父路径和子路径字符串(包含文件名)创建一个新的File对象。语法格式如下:

new File (String parent, String child)
  • parent:父路径字符串。例如,"C:Users"。
  • child:子路径字符串。例如,"letter.txt"。

】那何为父路径与子路径呢?

这里举个例子,关于"C:UsersXULIANGDocuments"这个目录,"C:"是"C:Users"的父目录,"C:Users"是"C:UsersXULIANG"的父目录。简而言之,父目录就是当前目录的上级目录。

  1. File (File f, String child)

该构造方法根据parent抽象路径名和子路径字符串创建一个新的File对象。语法格式如下:

new File (File f, String child)
  • f:父路径对象。例如,"D:/doc/"。
  • child:子路径字符串。例如,"letter.txt"。
import java.io.File;

public class FileTest {
    public static void main(String[] args) {
        //创建文件对象,并指定文件名
        File file = new File("test.txt");
        //判断该文件是否存在
        if (file.exists()){
            //若存在,则删除文件
            file.delete();
            System.out.println("文件已删除");
        }else {
            //捕捉可能出现的异常
            try {
                //若不存在,则创建该文件
                file.createNewFile();
                System.out.println("文件已创建");
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

运行结果:
文件已创建

上述代码执行后,会在该项目所在的路径下创建一个名为"test.txt"的文本文件。

3、获取文件信息

File类提供了很多方法用于获取文件的一些信息,常用的方法如下所示:

方法 返回值 说明
getName() String 获取文件的名称
canRead() boolean 判断文件是否可读
canWrite() boolean 判断文件是否可写
exits() boolean 判断文件是否存在
length() long 获取文件的长度(以字节为单位)

下面通过实例来介绍使用方法来获取文件的信息:

import java.io.File;

public class FileTest2 {
    public static void main(String[] args) {
        File file = new File("test.txt");
        if(file.exists()){
            String name = file.getName();
            long length = file.length();
            Boolean hidden = file.isHidden();
            System.out.println("文件的名字:" + name);
            System.out.println("文件的长度:" + length);
            System.out.println("是否为隐藏文件:" + hidden);
        }
    }
}

运行结果:
文件的名字:test.txt
文件的长度:0
是否为隐藏文件:false

四、文件输入/输出流

在程序的运行期间,大部分数据都保存在内存中进行操作,当程序结束后或关闭时,这些数据将消失。如果需要将数据永久保存,可使用文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。

1、FileInputStream和FileOutputStream类

FileInputStream和FileOutputStream类都是用来操作磁盘文件

(1)FileInputStream类

FileInputStream类继承于InputStream类,满足比较简单的文件读取要求。FileInputStream类常用的构造方法有两种:

  • FileInputStream (String name)
  • FileInputStream (File file)

第一种构造方法使用给定文件名name,创建一个FileInputStream对象。第二种构造方法使用File对象创建FileInputStream对象,该构造方法允许在把文件连接输入流之前对文件作进一步分析。

(2)FileOutputStream类

FileOutputStream类与FileInputStream类具有相同的构造方法,创建一个FileOutputStream

对象时,可以指定不存在的文件名,但此文件不能是一个被其他程序打开的文件。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileTest3 {
    public static void main(String[] args) {   
        File file = new File("word.txt");
        System.out.println("---------------写入操作----------------");
        try{
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            //字符串转化成字节码形式,采用Java平台默认字符集进行编码
            byte[] bytes = "Java基础班学习".getBytes();
            System.out.println(""Java基础班学习"字符串的字节码形式:");
            //遍历数组
            for (int i = 0; i < bytes.length; i++) {
                System.out.print(bytes[i] + " ");
            }
            System.out.println("
总共 " + bytes.length + " 个字节");
            //执行写入文件操作
            fileOutputStream.write(bytes);
            //关闭输出流
            fileOutputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
        System.out.println("---------------读取操作----------------");
        try{
            FileInputStream inputStream = new FileInputStream(file);
            byte bytes[] = new byte[100];
            //读取文件的数据内容,然后保存到bytes数组中,返回字节的个数
            int str = inputStream.read(bytes);
            System.out.println("该文件内容所占用的字节个数为:" + str);
            /*            
            * 将数组中的字节进行解码,并转化为字符串
            * 采用Java平台默认字符集进行解码
            * 形参0表示起始索引位置,str表示截取长度
            */
            String content = new String(bytes,0, str);
            System.out.println("文件中的内容为:" + content);
            //关闭输入流
            inputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

运行结果:
---------------写入操作----------------
"Java基础班学习"字符串的字节码形式:
74 97 118 97 -27 -97 -70 -25 -95 -128 -25 -113 -83 -27 -83 -90 -28 -71 -96 
总共 19 个字节
---------------读取操作----------------
该文件内容所占用的字节个数为:19
文件中的内容为:Java基础班学习

注意】虽说Java在程序结束时自动关闭所有打开的流,但是当使用完流后,应该显示地关闭所有打开地流仍是一个良好的习惯,一个被打开的流有可能耗尽系统资源。

2、FileReader和FileWriter类

使用FileOutputStream类向文件中写入数据与FileInputStream类从文件中读取内容,都存在一点不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占两个字节,如果使用字节流,读取不好可能会出现乱码现象,此时采用字符流Reader或Writer类即可避免这种现象。

FileReader和FileWriter字符流对应了FileInputStream和FileOutputStream类。FileReader流顺序地读取文件,只要不关闭流,每次调用read()方法就会顺序地读取源中地内容,直到源的末尾或流被关闭。

五、带缓存的输入/输出流

  • 缓存是I/O的一种性能优化
  • 缓存流为了I/O流增加了内存缓存区,有了缓存区,使得在流上执行skip()mark()和reset()方法都成为可能。

1、BufferedInputStream和BufferedOutputStream类

BufferedInputStream类可以对所有InputStream类进行缓存区的包装以达到性能优化的目的。BufferedInputStream类有两个构造方法:

  • BufferedInputStream (InputStream in)
  • BufferedInputStream (InputStream in, int size)

第一种形式的构造方法创建了一个带有32个字节的缓存流,第二种形式的构造方法按指定的大小来创建缓存区。BufferedInputStream读取文件的过程如下所示:

       数据流                 数据流                          数据流
文件  ------->  InputStream  ------->  BufferedInputStream  -------> 目的地

使用BufferedOutputStream输出信息和OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法将缓存区的数据强制写入文件中。BufferedOutputStream也有两个构造方法:

  • BufferedOutputStream (InputStream in)
  • BufferedOutputStream (InputStream in, int size)

第一种形式的构造方法创建了一个带有32个字节的缓存流,第二种形式的构造方法按指定的大小来创建缓存区。

注意】flush()方法用于即使在缓存区没有满的情况下,也将缓存区的内容强制写入到外设,习惯上将这个过程称为刷新。

2、BufferedReader与BufferedWriter类

BufferedReader与BufferedWriter类分别继承于Reader和Writer类,这两个类同样具有内部缓存机制,并可以以为单位进行输入/输出。

BufferedReader类常用的方法:

  • read() 方法:读取单个字符。
  • readLine()方法:读取一个文本,并将其返回为字符串。若无数据可读则返回null。

BufferedWriter类的方法都返回void,常用的方法:

  • write(String s, int off, int len)方法:写入字符串的某一个部分。
  • flush()方法:刷新该流的缓存。
  • newLine()方法:写入一个行分隔符

在使用BufferedWriter类的write()方法时,数据并没有立刻被写入输出流,而是首先进入缓存区中。如果想立即将缓存区中的数据写入输出流时,必须调用flush()方法。

import java.io.*;

public class Student {
    public static void main(String[] args) {
        String content[] = {"你好吗","好久不见","记得常联系"};
        File file = new File("word.txt");
        try{
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            for (int i = 0; i < content.length; i++) {
                bw.write(content[i]);   //将字符数组的元素写入磁盘文件
                bw.newLine();        //写入行分隔符
            }
            bw.close();        //关闭BufferedWriter流
            fw.close();        //关闭FileWriter流
        }catch (IOException e){
            e.printStackTrace();
        }
        try {
            FileReader fr = new FileReader(file);
            BufferedReader bufr = new BufferedReader(fr);
            String str = null;
            int i = 0;
            //读取文件的一行,若不为null,则进入循环

            while ((str = bufr.readLine()) != null){
                i++;
                System.out.println("第" + i + "行:" + str);
            }
            bufr.close();     //关闭BufferedReader流
            fr.close();       //关闭FileReader流
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

运行结果:
第1行:你好吗
第2行:好久不见
第3行:记得常联系
原文地址:https://www.cnblogs.com/xuliang-daydayup/p/12946038.html