<Java>17 IO流

本文最后更新于:2023年6月27日 上午

17 IO流

17.1 文件

文件就是保存数据的地方。

文件流:文件 在 程序 中是以 流 的形式来操作的。

流:数据在数据源(文件)和程序(内存)之间经历的路径

输入流:数据从数据源到程序的路径

输出流:数据从程序到数据源的路径

17.1.1 常用的文件操作

Java 提供了 File 类,用于处理文件相关的操作

  1. 创建文件对象相关构造器和方法

    • new File(String pathname):根据路径创建一个 File 对象

      String path1 = "d:/test.jpg";
      String path2 = "d:\\test.jpg";
      File file1 = new File(path1);
      File file2 = new File(path2);			//此时只是在内存中产生了一个对象
    • new File(File parent, String child):根据父目录文件 + 子路径构建

      File parentFile1 = new File("d:\\");
      String fileName1 = "test.txt";
      File file3 = new File(parentFile1, fileName1);
    • new File(String parent, String child):根据父路径 + 子路径构建

    • creatNewFile():创建新文件

      try {
       file.createNewFile();				//这个场合,内存对象才写入磁盘
      } catch (IOException e) {
       e.printStackTrace();
      }
  2. 获取文件相关信息

    • getName():获取名称

    • getAbsolutePath():获取文件绝对路径

    • getParent():获取文件父级目录

    • long length():获取文件大小(字节)

    • exists():文件是否存在

    • isFile():是不是一个文件

    • isDirectory():是不是一个目录

    • isAbsolute():是不是绝对路径

    • canRead():是否可读

      canWirte():是否可写

    • long lastModified():最后修改时间

    • String[] list():列出符合模式的文件名

  3. 目录的操作和文件删除

    • mkdir:创建一级目录
    • mkdirs:创建多级目录
    • delete:删除空目录或文件
    • boolean renameTo(File newName):更改文件名

    其实目录(在内存看来)就是特殊的文件

注意事项:

  • File 类可以获取文件的各种相关属性,可以对其进行改名,甚至删除。但除了文件名外的属性没有修改方法
  • File 类可以用来描述一个目录,但不能改变目录名,也不能删除目录

17.2 IO流

  1. I / O 是 Input / Output 的缩写。IO 技术是非常实用的技术,用于处理数据传输。如 读 / 写 文件,网络通讯等。
  2. Java 程序中,对于数据的 输入 / 输出 操作以 “流(stream)”的方式进行
  3. java.io 包下提供了各种 “流” 类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
  4. 输入(input):读取外部数据(磁盘、光盘、网络数据等)到程序(内存)中
  5. 输出(output):将程序(内存)数据输出到外部存储

17.2.1 IO流的分类

  • 按操作数据单位不同分为:

    • 字节流(8 bit):二进制文件用该方法,能确保文件无损
    • 字符流(按照字符,字符的字节数由编码决定):文本文件,效率更高
  • 按数据流的流向不同分为:

    • 输入流:读取外部数据(磁盘、光盘、网络数据等)到程序(内存)中
    • 输出流:将程序(内存)数据输出到外部存储
  • 按流的角色不同分为:

    • 节点流
    • 处理流 / 包装流
    Σ(っ °Д °;)っ 字节流 字符流
    输入流 InputStream Reader
    输出流 OutputStream Writer

Java 的 IO流 总共涉及 40多个类,实际上都是上述 4 类的抽象基类派生的

由这 4 个类派生的子类名称都是以其父类名作为子类名后缀

17.2.2 IO流 常用类

#17.2.2.1 FileInputStream:文件字节输入流

  • 构造器:

    new FileInputStream(File file);				//通过一个 File 的路径指定创建
    new FileInputStream(String path);			//通过一个路径指定创建
    new FileInputStream(FileDescriptor fdObj);	//通过文件描述符创建
  • 方法:

    • available():返回目前可以从流中读取的字节数

      实际操作时,读取的字节数可能大于这个返回值

    • close():关闭文件输入流,释放资源

    • finalize():确保在不引用文件输入流时调用其 close() 方法

    • getChannel():返回与此流有关的唯一的 FileChannel 对象

    • getFD():返回描述符

    • read():从该输入流中读取一个数据字节

      如果没有输入可用,该方法会被阻止。返回 -1 的场合,说明到达文件的末尾。

      File file = new File("d:\\test");
      FileInputStream fileInputStream = null;
      int read;
      try {
       fileInputStream = new FileInputStream(file);
       while ((read = fileInputStream.read()) != -1){
           System.out.print((char) read);
       }
      } catch (IOException e) {
       e.printStackTrace();
      } finally {
       try {
           fileInputStream.close();
       } catch (IOException e) {
           e.printStackTrace();
       }
      }										//真 TM 复杂。throw 了算了

      这个场合,效率较低

      read(byte[] b):从该输入流中把最多 b.length 个字节的数据读入一个 byte 数组

      读取正常的场合,返回实际读取的字节数。

      ...
      byte[] b = new byte[8];					//一次读取 8 字节
      try {
       fileInputStream = new FileInputStream(file);
       while ((read = fileInputStream.read(b)) != -1){
           System.out.print(new String(b, 0, read));
           								//这一句看不懂请看[12.2 - 4]
       }
      catch 
      ...
      finally
      ...

      read(byte[] b, int off, int len):从该输入流中读取 len 字节数据,从数组下标 off 处起写入

    • skip(long n):从该输入流中跳过并去丢弃 n 个字节的数据

    • mark(int markArea):标记数据量的当前位置,并划出一个缓冲区。缓冲区大小至少为 markArea

      reset():将输入流重新定位到对此流最后调用 mark() 方法时的位置

      markSupported():测试数据流是否支持 mark()reset() 操作

#17.2.2.2 FileOutputStream:文件字节输出流

  • 构造器:

    new FileOutputStream(File file);			//通过一个 File 的路径指定创建
    new FileOutputStream(File file, boolean append);			
    							//append = false,写入采用 覆盖原文件 方式
    							//append = true 的场合,写入采用 末尾追加 方式
    new FileOutputStream(String path);			//通过一个路径指定创建
    new FileOutputStream(String path, boolean append);			
    new FileOutputStream(FileDescriptor fdObj);	//通过文件描述符创建
  • 方法:

    • close():关闭文件输入流,释放资源

    • flush():刷新此输出流并强制写出所有缓冲的输出字节

    • finalize():确保在不引用文件输入流时调用其 close() 方法

    • getChannel():返回与此流有关的唯一的 FileChannel 对象

    • getFD():返回描述符

    • write(byte[] b):将 b.length 个字节从指定 byte 数组写入此文件输出流

      File file = new File("d:\\test1");
      FileOutputStream fileOutputStream = null;
      try {
       fileOutputStream = new FileOutputStream(file);
       									//此时,若文件不存在会被创建
       fileOutputStream.write('a');
       String str = "Melody";
       fileOutputStream.write(str.getBytes());
      } 
      catch
      ...
      finally
      ...

      write(byte[] b, int off, int len):将指定 byte 数组中下标 off 开始的 len 个字节写入此文件输出流

      write(int b):将指定字节写入此文件输出流

#17.2.2.3 FileReader:文件字符输入流

与其他程序设计语言使用 ASCII 码不同,Java 使用 Unicode 码表示字符串和字符。ASCII 码的字符占用 1 字节,可以认为一个字符就是一个字节。但 Unicode 码用 2 字节表示 1 个字符,此时字符流和字节流就不相同。

  • 构造器:

    new FileRaeder(File file);
    new FileRaeder(String string);
  • 方法:

    • read():读取单个字符。
    • read(char[]):批量读取多个字符到数组。

#17.2.2.3 FileWriter:文件字符输出流

  • 构造器:

    new FileWriter(File path);
    new FileWriter(String path2);
    new FileWriter(File path3, boolean append);
    new FileWriter(String path4, boolean append);
  • 方法:

    • write(int):写入单个字符
    • write(char[]):写入指定数组
    • write(char[], off, len):写入指定数组的指定部分
    • write(string):写入字符串
    • write(string, off, len):写入字符串的指定部分
    • flush():刷新该流的缓冲。如果没有执行,内容就不会写入文件
    • close():等于 flush() + 关闭

    注意!FileWriter 使用后,必须关闭(close)或刷新(flush),否则无法真正写入

#17.2.2.4 转换流 InputStreamReaderOutputStreamWriter

  1. InputStreamReaderReader 的子类。可以把 InputStream(字节流)转换成 Reader(字符流)
  2. OutputStreamWriterWriter 的子类。可以把 OutputStream(字节流)转换成 Writer(字符流)
  3. 处理纯文本数据时,如果使用字符流效率更高,并能有效解决中文问题,建议将字节流转换成字符流。
  4. 可以在使用时指定编码格式(UTF -8、GBK 等)
  • 构造器

    InputStreamReader isr = new InputStreamReader(fileInputStream, "UTF-8");
    										//传入 字节流 和 编码类型
    BufferedReader br = new Bufferedreader(isr);
    										//用另一个处理流包装

17.2.3 节点流和处理流

  1. 节点流:从一个特定数据源读写数据。
  2. 处理流(包装流):是 “连接” 在已存在的流(节点流或处理流)上,为程序提供更强大的读写功能。

#节点流和处理流的区别和联系

  1. 节点流是 底层流 / 低级流。直接和数据源相接。
  2. 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出
  3. 处理流对节点流进行包装,使用了修饰器设计模式。不会直接与数据源相连
  4. 处理流的功能主要体现在
    • 性能的提高:以增加缓冲的方式提高输入输出的效率
    • 操作的便捷:处理流可能提供了一系列便捷方法来一次性输入大量数据,使用更加灵活方便
  5. 关闭时关闭外层流即可

#17.2.3.1 缓冲区流

缓冲区流是一种包装流。缓冲区字节流有 BufferedInputStream 和 BufferedOutputStream;缓冲区字符流有 BufferedWriter 和 BufferedReader。他们是在数据流上加了一个缓冲区。读写数据时,数据以块为单位进入缓冲区,其后的读写操作则作用于缓冲区。

这种方式能降低不同硬件设备间的速度差异,提高 I/O 效率。

构造器:

new BufferedReader(reader);					//传入一个 Reader
new BufferedReader(reader, 1024);			//传入 Reader 并指定缓冲区大小
new BufferedWriter(writer);					//传入一个 Writer
new BufferedWriter(writer, 1024);			//传入 Writer 并指定缓冲区大小
											//追加还是覆盖,取决于 writer

方法:

  • bufferedReader.readLine():按行读取(不含换行符)。

    会返回一个字符串。返回 null 时,表示读取完毕。

    String line;
    while (line = bufferedReader.readLine() != null){
     ...
    }
    bufferedReader.close();
  • bufferedWriter.write(String str):插入字符串

  • bufferedWriter.newLine():插入一个(和系统相关的)换行

#17.2.3.2 数据数据流

除了字节或字节数组外,处理的数据还有其他类型。为解决此问题,可以使用 DataInputStream 和 DataOutputStream。它们允许通过数据流来读写 Java 基本类型,如布尔型(boolean)、浮点型(float)等

构造器:

new DataInputStream(inputStream);
new DataOutputStream(outputStream);

方法:

  • byte readByte():读取下一个 byte

    int readInt()double readDouble()String readUTF()……

  • void writeByte(byte b):写入一个 byte

    void writeInt(int n)void writeUTF(String str)……

    虽然有对字符串的读写方法,但应避免使用这些方法,转而使用字符输入/输出流。

#17.2.3.3 对象流

当我们保存数据时,同时也把 数据类型 或 对象 保存。

以上要求,就是能够将 基本数据类型 或 对象 进行 序列化·反序列化 操作

序列化和反序列化

  1. 把对象转成字符序列的过程称为序列化。保存数据时,保存数据的值和数据类型
  2. 把字符序列转成对象的过程称为反序列化。恢复数据时,恢复数据的值和数据类型
  3. 需要让某个对象支持序列化机制,则必须让其类是 可序列化的。由此,该类必须实现下列接口之一
    • Serializable:推荐。因为是标记接口,没有方法
    • Externalizable:该接口有方法需要实现

transient 关键字

  1. 有一些对象状态不具有可持久性(如 Thread 对象或流对象),这样的成员变量必须用 transient 关键字标明。任何标有 transient 关键字的成员变量都不会被保存。
  2. 一些需要保密的数据,不应保存在永久介质中。为保证安全,这些变量前应加上 transient 关键字。
  • 构造器:

    new ObjectInputStream(InputStream inputStream);
    new ObjectOutputStream(OutputStream outputStream);
  • 方法:

    反序列化顺序需要和序列化顺序一致,否则出现异常。

    • writeInt(Integer):写入一个 int

      readInt():读取一个 int

    • writeBoolean(Boolaen):写入一个 boolean

      readBoolean():读取一个 boolean

    • writeChar(Character):写入一个 char

      readChar():读取一个 char

    • writeDouble(Double):写入一个 double

      readDouble():读取一个 double

    • writeUTF(String):写入一个 String

      readUTF():读取一个 String

    • writeObject(Serializable):写入一个 Obj

      readObject():读取一个 Obj

      读取的场合,如果想要调用方法,需要向下转型。

      为此,需要该类其引入,或将类的定义拷贝到可以引用的位置。

  • 注意事项

    1. 读写顺序要一致

    2. 实现序列化或反序列化的对象,要实现 SerializableExternalizable 接口

    3. 序列化的类中建议添加 SerialVersionUID 以提高版本兼容性

      private static final long serialVersionUID = 1L;

      有此序列号的场合,后续修改该类,系统会认为只是版本修改,而非新的类

    4. 序列化对象时,默认将其中所有属性进行序列化(除了 statictansient 修饰的成员)

    5. 序列化对象时,要求其属性也实现序列化接口

    6. 序列化具备可继承性。某类若实现可序列化,则其子类也可序列化

#17.2.3.4 标准输入 / 输出流

Σ( ° △ °lll) 编译类型 运行类型 默认设备
System.in:标准输入 InputStream BufferedInputStream 键盘
System.out:标准输出 PaintStream PaintStream 显示器

#17.2.3.5 打印流 PaintStreamPaintWriter

打印流只有输出流,没有输入流

  1. PaintStreamOutputStream 的子类。PaintWriterWriter 的子类。

  2. 默认情况下,System.out 输出位置是 标准输出(即:显示器)

    修改默认输出位置:

    System.setOut(new PrintStream(path));

#17.2.3.6 Properties

  1. Properties 是专门用于读写配置文件的集合类

    底层维护了一个 Entry 数组

  2. 配置文件格式:

    =值
    键=值
    …

    注意:键值对不需要空格,值不需要引号(值默认 String

  3. 常见方法:

    • load(InputStream)

      load(Reader):加载配置文件的键值对到 Properties 对象

      Properties properties = new Properties();
      properties.load(new FileReader("d:\\data.data"));
    • list(PaintStream)

      list(PaintWriter):将数据显示到指定设备

      properties.list(System.out);			//在控制台显示
    • getProperty(key):根据键获取值

      properties.get("IQ");
    • setProperty(key, value):设置键值对到 Properties 对象

      如果没有该 key,就是创建。如有,就是替换。

      properties.set("IQ", 0);
      properties.set("Balance", 0);
    • store(Writer, String)

      store(OutputStream, String):把 Properties 中的键值对存储到配置文件。

      后面的 String 是注释。如有,会被用 # 标记并写在文件最上方。注释可以为 null。

      IDEA 中,如果含有中文,会储存为 unicode 码

      查询 unicode 码

#17.2.3.7 随机访问文件

程序阅读文件时不仅要从头读到尾,还要实现每次在不同位置进行读取。此时可以使用 RandomAccessFile

构造器:

new RandomAccessFile(String name, String mode);		//通过文件名
new RandomAccessFile(File file, String mode);		//通过文件对象

参数 mode 决定以只读方式 mode = "r" 还是读写方式 mode = "rw" 访问文件。

方法:

  • long getFilePointer():返回文档指针的当前位置

  • void seek(long pos):将文档指针置于指定的绝对位置 pos

    文档指针的位置从文档开始的字符处开始计算,pos = 0L 表示文档的开始

  • long length():返回文件长度


<Java>17 IO流
https://i-melody.github.io/2022/01/06/Java/入门阶段/17 IO流/
作者
Melody
发布于
2022年1月6日
许可协议