Java中的IO流

Java中的IO流

1、IO流分类

在这里插入图片描述

在 IO 包中,字节流的输人输出流分别用 java.io.InputStreamjava.io.OutputStream 表示,字符流的输

人输出流分别用 java.io.Readerjava.io.Writer 表示。

2、字节流

IO 流中针对字节的输人输出提供了一系列的流,统称为字节流。根据数据的传输方向可将其分为字节输入流和字

节输出流。在 JDK 中,提供了两个抽象类 InputStreamOutputStream,它们是字节流的顶级父类。

所有的字节输人流都继承自 InputStream,所有的字节输出流都继 OutputStream

InputStream 被看成一个输人管道,OutputStream 被看成一个输出管道,数据通过 InputStream 从源设备输

人到程序,通过 OutputStream 从程序输出到目标设备,从而实现数据的传输。

在这里插入图片描述

在 JDK 中,InputStream 和 OutputStream 提供了一系列与读写数据相关的方法。

InputStream 提供的读数据的相关方法:

  • int read():从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数。

  • int read(byte[] b):从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读

    取字节数。

  • int read(byte[] b,int off,int len):从输入流读取若干字节,把它们保存到参数b指定的字节数组

    中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目。

  • void close():关闭此输入流并释放与该流关联的所有系统资源。

OutputStream 提供的写数据的相关方法:

  • void write(int b):向输出流写入一个字节

  • void write(byte[] b):把参数b指定的字节数组的所有字节写到输出流。

  • void write(byte[] b,int off,int len):将指定byte数组中从偏移量off开始的len个字节写入输出

    流。

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

  • void close():关闭此输出流并释放与此流相关的所有系统资源。

InputStreamOutputStream 提供了不同的子类,这些子类形成了一个体系结构:

在这里插入图片描述

在这里插入图片描述

2.1 字节流读写文件

在操作文件时,最常见的就是从文件中读取数据并将数据写人文件,即文件的读写。针对文件的读写,JDK专门提

供了两个类,分别是 FileInputStreamFileOutputStreamFileInputStreamInputStream 的子类,

它是操作文件的字节输人流,专门用于读取文件中的数据。由于从文件读取数据是重复的操作,因此需要通过循环

语句来实现数据的持续读取。

package com.example;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author tom
 */
public class Example01 {
    public static void main(String[] args) throws IOException {
        // test.txt内容为hello
        FileInputStream in = new FileInputStream("test.txt");
        // 定义一个int类型的变量b,记住每次读取的一个字节
        int b;
        while (true) {
            // 变量b记住读取的一个字节
            b = in.read();
            // 如果读取的字节为-1,跳出while循环
            if (b == -1) {
                break;
            }
            System.out.println(b);
        }
        in.close();
    }
}
# 输出
104
101
108
108
111

与 FileInputStream 对应的是 FileOutputStream。FileOutputStream 是 OutputStream 的子类,它是操作文件

的字节输出流,专门用于把数据写人文件。

package com.example;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author tom
 */
public class Example02 {
    public static void main(String[] args) throws IOException {
        FileOutputStream out = new FileOutputStream("example.txt");
        String str = "你好";
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}
# example.txt文件写入你好
package com.example;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author tom
 */
public class Example03 {
    public static void main(String[] args) throws IOException {
        // true代表是否追加
        FileOutputStream out = new FileOutputStream("example.txt", true);
        String str = "你好";
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}
# example.txt文件写入你好你好

从前面的例程中可以看出,IO 流在进行数据读写操作时会出现异常,为了代码的简洁,在程序中使用 throws 关

键字将异常抛出。然而一旦遇到 IO 异常,IO 流的 close() 方法将无法得到执行,流对象所占用的系统资源将得不

到释放,因此,为了保证 IO 流的 close() 方法必须执行,通常将关闭流的操作写在 finally 代码块。

finally {
    try {
        //如果in不为空,关闭输入流
        if (in != null) in.close();
    } catch (Exception e){
        e.printstackTrace();
    }
    try {
        //如果out不为空,关闭输出流
        if (out != null) out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2.2 文件的拷贝

在应用程序中,IO 流通常都是成对出现的,即输入流和输出流一起使用。例如文件的拷贝就需要通过输入流来读

取文件中的数据,通过输出流将数据写入文件。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example04 {
    public static void main(String[] args) throws IOException {
        // 创建一个字节输入流,用于读取当前目录下的mp3文件
        InputStream in = new FileInputStream("Bad Romance.mp3");
        // 创建一个文件字节输出流,用于将读取的数据写人文件中
        OutputStream out = new FileOutputStream("style.mp3");
        int len;
        // 定义一个int类型的变量len,记住每次读取的一个字节
        // 获取拷贝文件前的系统时间
        long beginTime = System.currentTimeMillis();
        while ((len = in.read()) != -1) {
            // 读取一个字节并判断是否读到文件末尾
            out.write(len);
            // 将读到的字节写人文件
        }
        long endTime = System.currentTimeMillis();
        // 获取文件拷贝结束时的系统时间
        System.out.println("拷贝文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
        in.close();
        out.close();
    }
}
# 输出
拷贝文件所消耗的时间是:13765毫秒

2.3 字节流的缓冲区

当通过流的方式拷贝文件时,为了提高效率也可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取

多个字节的数据,并保存在字节数组中然后将字节数组中的数据一次性写入文件。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example05 {
    public static void main(String[] args) throws IOException {
        // 创建一个字节输入流,用于读取当前目录下的mp3文件
        InputStream in = new FileInputStream("Bad Romance.mp3");
        // 创建一个文件字节输出流,用于将读取的数据写人文件中
        OutputStream out = new FileOutputStream("style.mp3");
        byte[] buff = new byte[1024];
        int len;
        // 定义一个int类型的变量len,记住每次读取的一个字节
        // 获取拷贝文件前的系统时间
        long beginTime = System.currentTimeMillis();
        while ((len = in.read(buff)) != -1) {
            // 读取一个字节并判断是否读到文件末尾
            out.write(buff,0,len);
            // 将读到的字节写人文件
        }
        long endTime = System.currentTimeMillis();
        // 获取文件拷贝结束时的系统时间
        System.out.println("拷贝文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
        in.close();
        out.close();
    }
}
# 输出
拷贝文件所消耗的时间是:23毫秒

2.4 字节缓冲流(装饰器模式)

在 IO 包中提供两个带缓冲的字节流,分别是 BufferedlnputStreamBufferdOutputStream,这两个流都使

用了装饰设计模式。它们的构造方法中分别接收 InputStreamOutputStream 类型的参数作为被包装对象,

在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图所示。

在这里插入图片描述

从图中可以看出应用程序是通过缓冲流来完成数据读写的,而缓冲流又是通过底层被包装的字节流与设备进行关联

的。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example06 {
    public static void main(String[] args) throws IOException {
        // 创建一个带缓冲区的输入流
        // src.txt文件的内容为hello world!
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.txt"));
        // 创建一个带缓冲区的输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("des.txt"));
        int len;
        while ((len = bis.read()) != -1) {
            bos.write(len);
        }
        bis.close();
        bos.close();
    }
}
# des.txt文件写入hello world!

创建了 BufferedInputStreamBufferedOutputStream 两个缓冲流对象,这两个流内部都定义了一个大小为

8192 的字节数组,当调用 read() 或者 write() 方法读写数据时,首先将读写的数据存入定义好的字节数组,

然后将字节数组的数据一次性读写到文件中。

3、字符流

InputStream 类和 OutputStream 类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个

类就不太方便,为此 JDK 提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是 Reader

Writer。其中 Reader 是字符输入流,用于从某个源设备读取字符,Writer 是字符输出流,用于向某个目标设

备写入字符。ReaderWriter 作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出 Reader

Writer 的一些常用子类:

在这里插入图片描述

在这里插入图片描述

字符流的继承关系与字节流的继承关系有些类似,很多子类都是成对(输入流和输出流)出现,其中 FileReader

FileWriter 用于读写文件,BufferedReaderBufferedWriter 是具有缓冲功能的流,它们可以提高读写效

率。

3.1 字符流操作文件

在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符便可以使用字符输人流

FileReader,通过此流可以从关联的文件中读取一个或一组字符。

package com.example;

import java.io.FileReader;

/**
 * @author tom
 */
public class Example07 {
    public static void main(String[] args) throws Exception {
        // 创建一个FileReader对象用来读取文件中的字符
        // reader.txt文件的内容为hello world!
        FileReader reader = new FileReader("reader.txt");
        int ch;
        // 定义一个变量用于记录读取的字符
        while ((ch = reader.read()) != -1) {
            // 循环判断是否读取到文件的末尾
            System.out.println((char) ch);
            // 不是字符流末尾就转为字符打印
        }
        reader.close();
        // 关闭文件读流,释放资源
    }
}
# 程序输出
h
e
l
l
o
 
w
o
r
l
d
!

字符输人流的 read() 方法返回的是int类型的值,如果想获得字符就需要进行强制类型转换。

如果要向文件中写人字符就需要使用 FileWriter ,类 FileWriterWriter 的一个子类。

package com.example;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author tom
 */
public class Example08 {
    public static void main(String[] args) throws IOException {
        //创建一个FileWriter对象用于向文件中写人数据
        FileWriter writer = new FileWriter("writer.txt");
        // 追加
        // FileWriter writer=new FileWriter("writer.txt",true);
        String str = "hello world!";
        writer.write(str);
        //将字符数据写人到文本文件中
        writer.write("\r\n");
        //将输出语句换行
        writer.close();
        //关闭写人流,释放资源
    }
}
# writer.txt文件写入hello world!

3.2 字符流缓冲流

了解到包装流可以对一个已存在的流进行包装来实现数据读写功能,利用包装流可以有效地提高读写数据的效率。

字符流同样提供了带缓冲区的包装流,分别是 BufferedReaderBufferedWriter,其中 BufferedReader

用于对字符输人流进行包装,BufferedWriter 用于对字符输出流进行包装,需要注意的是,在

BufferedReader中有一个重要的方法 readLine(),该方法用于一次读取一行文本。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example09 {
    public static void main(String[] args) throws IOException {
        // src.txt文件内容为hello world!
        FileReader reader = new FileReader("src.txt");
        // 创建一个BufferedReader缓冲对象
        BufferedReader br = new BufferedReader(reader);
        FileWriter writer = new FileWriter("des.txt");
        // 创建一个 BufferedWriter缓冲对象
        BufferedWriter bw = new BufferedWriter(writer);
        String str;
        // 每次读取一行文本,判断是否到文件末尾
        while ((str = br.readLine()) != null) {
            bw.write(str);
            // 写入一个换行符,该方法会根据同的操作系统生成相应的换行符
            bw.newLine();
        }
        br.close();
        bw.close();
    }
}
# des.txt文件写入hello world!

其中 readLine() 方法会逐个读取字符,当读到回车 \r 或换行 \n 时会将读到的字符作为一行的内容返回。需

要注意的是,由于包装流内部使用了缓冲区,在循环中调用 BufferedWriterwrite() 方法写字符时,这些

字符首先会被写人缓冲区,当缓冲区写满时或调用 close() 方法时,缓冲区中的字符才会被写人目标文件。因此

在循环结束时一定要调用 close() 方法,否则极有可能会导致部分存在缓冲区中的数据没有被写人目标文件。

3.3 LineNumberReader

Java 程序在编译或运行期间经常会出现一些错误,在错误中通常会报告出错的行号,为了方便查找错误,需要在

代码中加人行号。JDK提供了一个可以跟踪行号的输入流— LineNumberReader,它是 BufferedReader 的直接

子类。

package com.example;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;

/**
 * @author tom
 */
public class Example10 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("example.go");
        // 创建字符输入流
        FileWriter fw = new FileWriter("example-line.go");
        // 创建字符输出流
        LineNumberReader lr = new LineNumberReader(fr);
        // 包装
        lr.setLineNumber(0);
        // 设置读取文件的起始行号
        String line;
        while ((line = lr.readLine()) != null) {
            // 将行号写入到文件中
            fw.write(lr.getLineNumber() + ":" + line);
            fw.write("\r\n");
            // 写人换行
        }
        lr.close();
        fw.close();
    }
}
# example-line.go文件写入
1:package main
2:
3:import (
4:	"fmt"
5:)
6:
7:func main() {
8:	fmt.Println("Hello,World!")
9:}

3.4 转换流

前面提到IO流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在JDK中提供了两个类可以将字

节流转换为字符流,它们分别是 InputStreamReaderOutputStreamWriter。转换流也是一种包装流,其中

OutputStreamWriterWriter 的子类,它可以将一个字节输出流包装成字符输出流,方便直接写入字符,而

InputStreamReaderReader 的子类,它可以将一个字节输人流包装成字符输人流,方便直接读取字符。

在这里插入图片描述

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example11 {
    public static void main(String[] args) throws IOException {
        //创建字节输入流
        // src.txt文件内容hello world!
        FileInputStream in = new FileInputStream("src.txt");
        InputStreamReader isr = new InputStreamReader(in);
        //将字节流输入转换成字符输人流
        BufferedReader br = new BufferedReader(isr);
        //对字符流对象进行包装
        FileOutputStream out = new FileOutputStream("des.txt");
        //将字节输出流转换成字符输出流
        OutputStreamWriter osw = new OutputStreamWriter(out);
        // 对字符输出流对象进行包装
        BufferedWriter bw = new BufferedWriter(osw);
        String line;
        while ((line = br.readLine()) != null) {
            // 判断是否读到文件末尾
            bw.write(line);
            // 输出读取到的文件
        }
        br.close();
        bw.close();
    }
}
# des.txt文件写入hello world!

字节流和字符流之间的转换,将字节流转换为字符流,从而实现直接对字符的读写。需要注意的是,在使用转换流

时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是一张图片,此时转换为字符流就会造成数据丢

失。

4、其它IO

4.1 ObjectlnputStream和ObjectOutputStream

程序运行时,会在内存中创建多个对象,然而程序结束后,这些对象便被当作垃圾回收了。如果希望永久保存这些

对象,则可以将对象转为字节数据写人到硬盘上,这个过程称为对象序列化。为此JDK中提供了

ObjectOutputStream(对象输出流)来实现对象的序列化。当对象进行序列化时,必须保证该对象实现

Serializable 接口,否则程序会出现 NotSerializableException 异常。

package com.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author tom
 */
public class Example12 {
    public static void main(String[] args) throws IOException {
        // 创建一个Person对象
        Person p = new Person("p1", "zhangsan", 20);
        // 创建文件输出流对象,将数据写入objectStream.txt文件中
        FileOutputStream fos = new FileOutputStream("objectStream.txt");
        // 创建对象输出流对象,用于处理输出流对象写人的数据
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        // 将Person对象输出到输出流中
        oos.writeObject(p);
        oos.close();
        fos.close();
    }
}

class Person implements Serializable {

    private static final long serialVersionUID = 1L;
    private String id;
    private String name;
    private int age;

    public Person(String id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

首先将 Person 对象进行实例化,然后通过调用 ObjectOutputStreamwriteObject(Object obj) 方法将

Person 对象写人 objectStream.txt 文件中,从而将 Person 对象的数据永久地保存在文件中,这个过程就是对

象的序列化。当程序运行结束后,会发现在当前目录下自动生成了一个 objectStream.txt 文件,该文件中便记

录了Person对象的数据。

Person 对象被序列化后会生成二进制数据保存在 objectStream.txt 文件中,通过这些二进制数据可以恢复序

列化之前的Java对象,此过程称为反序列化。JDK提供了 ObjectInputStream 类(对象输人流),它可以实现对象

的反序列化。

package com.example;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * @author tom
 */
public class Example13 {
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        // 创建文件输入流对象,用于读取指定文件的数据
        FileInputStream fis=new FileInputStream("objectStream.txt");
        // 创建对象输入流,并且从指定的输入流中读取数据
        ObjectInputStream ois=new ObjectInputStream(fis);
        // 从objectStream.txt中读取Person对象
        Person p=(Person)ois.readObject();
        System.out.println(p.getId() + "-" + p.getAge() + "-" + p.getName());
    }
}
# 输出
p1-20-zhangsan

通过调用 ObjectInputStreamreadObject() 方法将文件 objectStream.txt 的 Person对象读取出来,这

个过程就是反序列化。

4.2 DatalnputStream和DataOutputStream

通过上一个小节的学习,了解到如何通过IO流存储对象,但有的时候,并不需要存储整个对象的信息,而只需要

存储对象的成员数据,这些成员数据的类型又都是基本数据类型,这时,不必使用对象Object相关的流,,可以

使用IO包中提供的另外两个数据操作流,即 DataInputStreamDataOutputStreamDataInputStream

DataOutputStream 是两个与平台无关的数据操作流,它们不仅提供了读写各种基本类型数据的方法,而且还提

供了 readUTF()writeUTF() 方法。

DataInputStreamreadUTF() 方法能够从输人流中读取采用UTF-8字符编码的字符串,DataOutputStream

writeUTF() 方法则可向输出流中写人采用UTF-8字符编码的字符串。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example14 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("dataStream.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeByte(12);
        // 写一个字节
        dos.writeChar('1');
        // 写一个字符
        dos.writeBoolean(true);
        // 写一个布尔值
        dos.writeUTF("同学,你好");
        // 写一个转换成UTF-8的字符串
        dos.close();
        // 关闭流
        FileInputStream fis = new FileInputStream("dataStream.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        DataInputStream dis = new DataInputStream(bis);
        System.out.println(dis.readByte());
        // 读一个字节
        System.out.println(dis.readChar());
        // 读一个字符
        System.out.println(dis.readBoolean());
        // 读一个转换成 UTF-8编码的字符串
        System.out.println(dis.readUTF());
        dis.close();
        // 关闭流
    }
}
# 输出
12
1
true
同学,你好

首先通过 DataOutputStreamwriteByte()writeChar()writeBoolean()writeUTF() 方法依次

写人Byte、Char、Boolean 和UTF格式的数据,然后通过 DataInputStreamreadByte()readChar()

readB0olean()readUTF() 方法将对应类型的数据依次读取,需要注意的是,只有读取数据的顺序与写数据

的顺序保持一致,才能保证最终数据的正确性。

4.3 PrintStream

通过前面的学习了解到输出流在通过 write() 方法写数据时,只能输出字节或字符类型的数据。如果希望输出其

他类型,例如一个基本数据类型的int 值19、一个Student类型的对象等,此时需要将数据先转为字符串再输出。

这种操作方式显然比较麻烦,为此,在IO包中提供了一个 PrintStream 类,它提供了一系列用于打印数据的

print()println() 方法,被称作打印流。PrintStream 可以实现将基本数据类型的数据或引用数据类型的

对象格式化成字符串后再输出。

package com.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

/**
 * @author tom
 */
public class Example15 {
    public static void main(String[] args) throws IOException {
        // 创建一个PrintStream对象,将FileOutputStream读取到的数据输出
        PrintStream ps = new PrintStream(new FileOutputStream("printStream.txt"), true);
        Student stu = new Student();
        // 创建一个Student对象
        ps.print("这是一个数字:");
        ps.println(19);
        // 打印数字
        ps.println(stu);
        // 打印 Student 对象
    }
}

class Student {
    /**
     * 重写object的 toString()方法
     * @return
     */
    @Override
    public String toString() {
        return "我是一个学生";
    }
}
# printStream.txt文件内容
这是一个数字:19
我是一个学生

PrintStream 的实例对象通过 print()println() 方法向文件 printStream. txt 写入了数据。从运行结

果可以看出,在调用 println() 方法和 print() 方法输出对象数据时,对象的 toString() 方法被自动调用

了。这两个方法的区别在于 println() 方法在输出数据的同时还输出了换行符。

4.4 标准输入输出流

在System类中定义了三个常量:inouterr,它们被习惯性地称为标准输人输出流。其中,in为

InputStream 类型,它是标准输人流,默认情况下用于读取键盘输人的数据;out 为 PrintStream 类型,它是

标准输出流,默认将数据输出到命令行窗口;err也是 PrintStream类型,它是标准错误流,它和out一样也是将

数据输出到控制台。不同的是,err通常输出的是应用程序运行时的错误信息。

package com.example;

import java.io.IOException;

/**
 * @author tom
 */
public class Example16 {
    public static void main(String[] args) throws IOException {
        StringBuffer sb = new StringBuffer();
        int ch;
        // while循环用于读取键盘输入的数据
        // 判断是否读取到数据的末尾
        while ((ch = System.in.read()) != -1) {
            // 对输入的字符进行判断,如果是回车“\r”或者换行“\n”,则跳出循环
            if (ch == '\r' || ch == '\n') {
                break;
            }
            // 将读取到数据
            sb.append((char) ch);
        }
        System.out.println(sb);
        // 打印键盘输入的数据
    }
}

有的时候,程序会向命令行窗口输出大量的数据,由于输出数据滚动得太快,会导致无法阅读,这时可以将标准输

出流重新定向到其他的输出设备,例如输出到一个文件中。在System类中提供了一些静态方法,这些静态方法允

许对标准输人流和输出流进行重定向,重定向流的常用静态方法如下:

  • void setIn(InputStream in):对标准输入流重定向

  • void setOut(PrintStream out):对标准输出流重定向

  • void setErr(PrintStream out):对标准错误输出流重定向

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example17 {
    public static void main(String[] args) throws IOException {
        // 对输入流进程重定向
        System.setIn(new FileInputStream("source.txt"));
        // 对输出流进程重定向
        System.setOut(new PrintStream("target.txt"));
        // 读取键盘输人的字符
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line = br.readLine()) != null) {
            // 判断读取到的一行是否有数据
            System.out.println(line);
            // 打印读取到的一行数据
        }
    }
}
# source.txt文件内容
床前明月光,疑是地上霜。
举头望明月,低头思故乡。
# target.txt文件写入
床前明月光,疑是地上霜。
举头望明月,低头思故乡。

4.5 PipedlnputStream和PipedOutputStream

在前面学习过多线程,多个线程之间也可以通过IO流实现数据的传输,为此JDK中提供了一种管道流。管道流分为

管道输人流(PipedInputStream)和管道输出流(PipedOutputStream),它是一种比较特殊的流,必须先建立连接

才能进行彼此间通信。PipedOutputStream 用于向管道中写人数据,PipedInputStream 用于从管道中读取

写入的数据。

package com.example;

import java.io.*;

/**
 * @author tom
 */
public class Example18 {
    public static void main(String[] args) throws IOException {
        final PipedInputStream pis = new PipedInputStream();
        final PipedOutputStream pos = new PipedOutputStream();
        pis.connect(pos);
        new Thread(new Runnable() {
            @Override
            public void run() {
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                PrintStream ps = new PrintStream(pos);
                while (true) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "要求输入内容:");
                        ps.println(br.readLine());
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "发送数据的线程").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                BufferedReader br = new BufferedReader(new InputStreamReader(pis));
                while (true) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "收到的内容:" + br.readLine());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "接收数据的线程").start();
    }
}
# 输出
发送数据的线程要求输入内容:
ooo
接收数据的线程收到的内容:ooo
发送数据的线程要求输入内容:
yyyy
接收数据的线程收到的内容:yyyy
发送数据的线程要求输入内容:
iiiiiiii
接收数据的线程收到的内容:iiiiiiii
发送数据的线程要求输入内容:
aaaa
接收数据的线程收到的内容:aaaa
发送数据的线程要求输入内容:

在字符流中也有一对 PipedReaderPipedWriter 用于管道的通信,它们的用法和 PipedInputStream

PipedOutputStream 相似。

4.6 ByteArraylnputStream和ByteArrayOutputStream

在前面的学习中,都是将文件直接存储到硬盘,但有时候我们希望将文件临时存储到缓冲区,方便以后读取。为此

JDK中提供了一个ByteArrayOutputStream类。

ByteArrayOutputStream 类会在创建对象时就创建一个byte型数组的缓冲区,当向数组中写数据时,该对象会

把所有的数据先写人缓冲区,最后一次性写入文件。

package com.example;

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

/**
 * @author tom
 */
public class Example19 {
    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("source.txt");
        // 创建一个字节数组缓冲
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        FileOutputStream out = new FileOutputStream("target2.txt");
        // 下面的代码是循环读取缓冲区中的数据,并将数据一次性写入文件
        int b;
        while ((b = in.read()) != -1) {
            bos.write(b);
        }
        in.close();
        bos.close();
        // 将缓冲区中的数据一次性写入文件
        out.write(bos.toByteArray());
        out.close();
    }
}

之前小节在读取数据时,通常都会定义一个1024个字节的数组,但如果文件太大,这个数组就不能一次性把文件

读取完,此时,需要多次向文件中写人数据,这样的操作明显效率很低。这时,如果使用

ByteArrayOutputStream 创建一个缓冲区,该缓冲区会根据存人数据的多少而自动变化,因此就可以减少写数

据的次数,使程序变得更灵活,从而提高应用程序的效率。需要注意的是,如果读取的文件非常大,就不能使用这

个类,否则会造成内存溢出。

ByteArrayOutputStream 类似,ByteArrayInputStream 是从缓冲区中读取数据:

package com.example;

import java.io.ByteArrayInputStream;

/**
 * @author tom
 */
public class Example20 {
    public static void main(String[] args) {
        //创建一个字节数组
        byte[] bufs = new byte[]{97, 98, 99, 100};
        //读取字节数组中的数据
        ByteArrayInputStream bis = new ByteArrayInputStream(bufs);
        //下面的代码是循环读取缓冲区中的数据
        int b;
        while ((b = bis.read()) != -1) {
            System.out.println((char) b);
        }
    }
}
# 输出
a
b
c
d

4.7 CharArrayReader和CharArrayWriter

要想将字符型数据临时存人缓冲区中,还可以使用JDK提供的 CharArrayReaderCharArrayWriter

CharArrayReader 是从字符数组中读取数据,CharArrayWriter 是在内存中创建一个字符数组缓冲区,它们的

功能与 ByteArraylnputStreamByteArrayOutputStream 类似,只不过操作的数据是字符:

package com.example;

import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.FileReader;
import java.io.IOException;

/**
 * @author tom
 */
public class Example21 {
    public static void main(String[] args) throws IOException {
        // a.txt文件内容
        // abcd
        // efgh
        FileReader reader = new FileReader("a.txt");
        CharArrayWriter caw = new CharArrayWriter();
        int b;
        while ((b = reader.read()) != -1) {
            caw.write(b);
        }
        reader.close();
        caw.close();
        char[] c = caw.toCharArray();
        CharArrayReader cr = new CharArrayReader(c);
        int i = 0;
        while ((i = cr.read()) != -1) {
            System.out.println((char) i);
        }
    }
}
# 输出
a
b
c
d



e
f
g
h

4.8 SequencelnputStream

前面对文件进行操作时,都是通过一个流对数据进行处理。如果希望多个流处理数据,这时就需要将这些流进行合

并。例如通过下载工具下载某个文件时,通常都是采用多线程的方式分段下载数据,最后会将所有的分段数据进行

合并,这时就可以使用 SequenceInputStreamSequenceInputStream类可以将几个输人流串联在一起,合并

为一个输入流。当通过这个流来读取数据时,它会依次从所有被串联的输人流中读取数据,对程序来说,就好像是

对同一个流进行操作。

package com.example;

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

/**
 * @author tom
 */
public class Example22 {
    public static void main(String[] args) throws IOException {
        // stream1.txt文件内容hello world!
        FileInputStream in1 = new FileInputStream("stream1.txt");
        // stream2.txt文件内容你好世界!
        FileInputStream in2 = new FileInputStream("stream2.txt");
        SequenceInputStream sis = new SequenceInputStream(in1, in2);
        FileOutputStream out = new FileOutputStream("stream.txt");
        int len;
        byte[] buf = new byte[1024];
        while ((len = sis.read(buf)) != -1) {
            out.write(buf, 0, len);
            out.write("\r\n".getBytes());
        }
        sis.close();
        out.close();
    }
}
# stream.txt文件内容
hello world!
你好世界!

在创建 SequencelnputStream 对象时使用的构造方法只有两个参数,也就是说只能合并两个流。如果想将多个

流进行合并,这时需要使用 SequenceInputStream 类的另一个构造方法,具体如下:

SequenceInputStream (Enumeration<? extends Inputstream>e)

该构造方法会接收一个 Enumeration 类型的对象作为参数,Enumeraion 对象会返回一系列 InputStream 类型

的对象,以提供给 SequenceInputStream 读取。

package com.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;

/**
 * @author tom
 */
public class Example23 {
    public static void main(String[] args) throws IOException {
        Vector vector = new Vector();
        FileInputStream fis1 = new FileInputStream("1.txt");
        FileInputStream fis2 = new FileInputStream("2.txt");
        FileInputStream fis3 = new FileInputStream("3.txt");
        vector.addElement(fis1);
        vector.addElement(fis2);
        vector.addElement(fis3);
        Enumeration e = vector.elements();
        SequenceInputStream sis = new SequenceInputStream(e);
        FileOutputStream out = new FileOutputStream("stream.txt");
        int len;
        byte[] buf = new byte[1024];
        while ((len = sis.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
        sis.close();
        out.close();
    }
}

程序将三个文件合并为一个文件。例程中用到了Vector集合,首先使用该集合存储3个输人流,然后调用

elements() 方法返回一个 Enumeration 对象。在创建 SequenceInputStream 对象时,将 Enumeration 对象作

为参数传递给构造方法,从而实现多个流的合并功能。

5、RandomAccessFile

前面介绍的 IO 流有一个共同特点,就是只能按照数据的先后顺序读取源设备中的数据,或者按照数据的先后顺序

向目标设备写人数据。但如果希望从文件的任意位置开始执行读写操作,则字节流和字符流都无法实现。为此,在

IO 包中,提供了一个类 RandomAccessFile,它不属于流类,但具有读写文件数据的功能,可以随机地从文件的

任何位置开始执行读写数据的操作。

RandomAccessFile 可以将文件以只读或者读写的方式打开,具体使用哪种方式取决于创建它所采用的构造方

法。

  • RandomAccessFile(File file,String mode):参数file指定被访问的文件。

  • RandomAccessFile(String name,String mode):参数name指定被访问文件的路径。

通过这两种方法创建 RandomAccessFile 对象时,都需要接受两个参数,第一个参数指定关联的文件,第二个参

mode 指定访问文件的模式。参数 mode 有四个值,最常用的有两个,分别是 rrw,其中 r 表示以只读的

方式打开文件,如果试图对 RandomAccessFile 对象执行写入操作,会抛出 IOException 异常;rw 表示以读

写的方式打开文件,如果该文件不存在,则会自动创建该文件。

RandomAccessFile 类针对文件的随机访问操作,提供了一些用于定位文件位置的方法:

  • long getFilePointer():返回当前读写指针所处的位置。

  • void seek(long pos):设定读写指针的位置,与文件开头相隔 pos个字节数。

  • int skipBytes(int n):使读写指针从当前位置开始,跳过n个字节。

  • void setLength(long newLength):设置此文件的长度。

RandomAccessFile 对象中包含了一个记录指针,用于表示文件当前读写处的位置。当新创建一个

RandomAccessFile 对象时,该对象的文件记录指针位于文件头(也就是0处),当读写了n个字节后,文件的记录

指针就会向后移动n个字节。RandomAccessFileseek(long pos) 方法,可以使记录指针向前、向后自由移

动,通过 RandomAccessFilegetFilePointer() 方法,便可获取文件当前记录指针的位置。

package com.example;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author tom
 */
public class Example24 {
    public static void main(String[] args) throws NumberFormatException, IOException {
        RandomAccessFile raf = new RandomAccessFile("time.txt", "rw");
        // int类型的变量表示试用次数
        int times;
        // 第一次读取文件时times为5
        times = Integer.parseInt(raf.readLine());
        if (times > 0) {
            //试用一次,次数减少一次
            System.out.println("您还可以试用" + times-- + "次!");
            raf.seek(0);
            //将剩余的次数再次写人文件
            raf.writeBytes(times + "");
        } else {
            //当time<=0,告诉用户试用期已到
            System.out.println("软件试用次数已到");
        }
        //关闭RandomAccessFile对象
        raf.close();
    }
}
# 输出
您还可以试用5次!
您还可以试用4次!
您还可以试用3次!
您还可以试用2次!
您还可以试用1次!
软件试用次数已到
  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaIO操作基本程如下: 1. 创建对象:通过File类或其他相关类创建输入或输出对象。 2. 打开:使用输入或输出对象的open()方法打开,这样就可以读取或写入数据。 3. 读取或写入数据:使用输入或输出对象的read()或write()方法读取或写入数据。 4. 关闭:使用输入或输出对象的close()方法关闭,释放资源。 需要注意的是,在使用IO操作时,要遵循“先打开、后关闭”的原则,以确保数据的完整性和的正确性。同时,在操作过程也需要进行异常处理,以避免出现不必要的错误。 ### 回答2: JavaIO基本操作程如下: 1. 打开文件或者建立网络连接:使用File类或者URL类打开文件或者建立网络连接。 2. 创建对象:根据需要选择输入(读取数据)或输出(写入数据),并创建相应的对象。常见的输入有FileInputStream、BufferedReader等,常见的输出有FileOutputStream、BufferedWriter等。 3. 读取或写入数据:使用对象读取或写入数据。对于输入,可以通过调用相关方法(如read()、readline()等)逐个字符或逐行读取数据;对于输出,可以通过调用相应方法(如write()、print()等)逐个字符或逐行写入数据。 4. 关闭:读取或写入完成后,需要关闭文件或网络连接,以释放资源。可以调用对象的close()方法来关闭。 需要注意的是,在处理IO时,应该始终使用try-catch-finally块,以确保在发生异常时能够正确关闭。可以把IO操作放在try块,catch块用于捕获异常,并在finally块关闭。 另外,为了提高IO效率,可以考虑使用缓冲来进行读写操作。缓冲(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)可以通过缓冲区将数据从源读入到缓冲区,再从缓冲区写入到目标,提高读写的速度。 以上就是JavaIO基本操作的程。根据实际需要选择合适的对象,并遵循打开、读取/写入、关闭的程,可以实现灵活、高效的IO操作。 ### 回答3: 在JavaIO是用于处理输入输出操作的工具。下面是JavaIO的基本操作程: 1. 创建对象:通过使用Java的InputStream和OutputStream类来创建对象。InputStream类用于读取输入,而OutputStream类用于写入输出。 2. 打开:通过使用对象对应的构造函数和方法来打开输入和输出。根据具体情况,可以选择文件、网络或内存来打开。 3. 读取/写入数据:使用对象提供的读取和写入方法来读取和写入数据。例如,使用InputStream的`int read()`方法来读取一个字节的数据,使用OutputStream的`void write(int b)`方法来写入一个字节的数据。 4. 关闭:在读取或写入结束后,必须关闭以释放相关资源。通过调用对象的`close()`方法来关闭。 需要注意的是,在处理异常的时候,我们需要对可能出现的`IOException`进行处理。可以使用try-catch语句块来捕获和处理异常。 程示例: ```java import java.io.*; public class IOExample { public static void main(String[] args) { try { // 1. 创建对象 FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); // 2. 打开 // 3. 读取/写入数据 int data; while ((data = fis.read()) != -1) { fos.write(data); } // 4. 关闭 fis.close(); fos.close(); } catch(IOException e) { e.printStackTrace(); } } } ``` 上述示例,我们创建了一个用于将一个文件的内容拷贝到另一个文件的程序。首先,我们创建了一个FileInputStream对象来读取输入文件的内容,然后创建了一个FileOutputStream对象来写入输出文件。接下来,我们通过循环从输入读取一个字节的数据,并将其写入到输出,直到读取完所有的数据。最后,我们关闭了对象来释放资源。 这就是JavaIO的基本操作程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值