Java I/O流

建议阅读:

Java输入输出流
看完这个,Java IO从此不在难
java 字节流与字符流的区别
廖雪峰的官方网站

一、IO体系

Java中I/O操作主要是指使用Java进行输入,输出操作。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。
在 Java 中引入了 “流” 的概念,它表示任何有能力产生数据源或有能力接收数据源的对象。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。

1、从数据传输方式或者说是运输方式角度看,可以将 IO 类分为:

1)字节流
2)字符流

字节流和字符流的区别
字节流中最小的数据单元是字节,字符流中最小的数据单元是字符一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)字节流用来处理二进制文件(图片、MP3、视频文件),字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。简而言之,字节是个计算机看的,字符才是给人看的。
在这里插入图片描述
Java IO 相关的类确实很多,但我们常用的也就是文件相关的几个类,如文件最基本的读写类 File 开头的、文件读写带缓冲区的类 Buffered 开头的类,对象序列化反序列化相关的类 Object 开头的类。

2、从数据来源或者说是操作对象角度看,IO 类可以分为:

  1. 文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
  2. 数组([])
    • 字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
    • 字符数组(char[]):CharArrayReader、CharArrayWriter
  3. 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
  4. 基本数据类型:DataInputStream、DataOutputStream
  5. 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  6. 打印:PrintStream、PrintWriter
  7. 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
  8. 转换:InputStreamReader、OutputStreamWriter
  9. 字符串(String) Java8中已废弃StringBufferInputStream、StringBufferOutputStream、StringReader、StringWriter
二、IO类和相关方法

IO中最基本的是 4 个抽象类:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一个读 read() 方法、一个写 write() 方法。方法具体的实现还是要看继承这 4 个抽象类的子类。

输入/输出是相对于内存/程序来说的!!

InputStream 类
抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

方法方法介绍
public abstract int read()读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
public int read(byte b[])读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的
public int read(byte b[], int off, int len)从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中
public long skip(long n)忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取
public int available()返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用
public void close()读取完,关闭流,释放资源
public synchronized void mark(int readlimit)标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断
public synchronized void reset()重置读取位置为上次 mark 标记的位置
public boolean markSupported()判断当前流是否支持标记流,和上面两个方法配套使用

OutputStream 类
抽象类,基于字节的输出操作,是所有输出流的父类。定义了所有输出流都具有的共同特征。

方法方法介绍
public abstract void write(int b)写入一个字节,可以看到这里的参数是一个 int 类型,对应上面的读方法,int 类型的 32 位,只有低 8 位才写入,高 24 位将舍弃。
public void write(byte b[])将数组b中的所有字节写到输出流,实际调用的是下面的方法。
public void write(byte b[], int off, int len)将参数b的从偏移量off开始的len个字节写到输出流
public void flush()将数据缓冲区中数据全部输出,并清空缓冲区
public void close()关闭输出流并释放与流相关的系统资源

Reader 类
抽象类,基于字符的输入操作。

方法方法介绍
public int read(java.nio.CharBuffer target)读取字节到字符缓存中
public int read()读取单个字符,返回值为读取的字符
public int read(char cbuf[])读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
abstract public int read(char cbuf[], int off, int len)读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现
public long skip(long n)跳过指定长度的字符数量
public boolean ready()和上面的 available() 方法类似
public boolean markSupported()判断当前流是否支持标记流
public void mark(int readAheadLimit)标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断
public void reset()重置读取位置为上次 mark 标记的位置
abstract public void close()关闭流释放相关资源

流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时

Writer 类
抽象类,基于字符的输出操作。

方法方法介绍
public void write(int c)写入一个字符
public void write(char cbuf[])将字符数组cbuf[]写入输出流
abstract public void write(char cbuf[], int off, int len)将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流
public void write(String str)将字符串str中的字符写入输出流
public void write(String str, int off, int len)从字符串的 off 位置写入 len 数量的字符
public Writer append(CharSequence csq)追加写入一个字符序列
public Writer append(CharSequence csq, int start, int end)追加写入一个字符序列的一部分,从 start 位置开始,end 位置结束
public Writer append(char c)追加写入一个 16 位的字符
abstract public void flush()强制刷新,将缓冲中的数据写入
abstract public void close()关闭输出流,流被关闭后就不能再输出数据了

另外,在java.io包中,由File类提供了描述文件和目录的操作与管理方法。但File类不是上述四个流式抽象类的子类,它不负责数据的输入输出,而专门用来管理磁盘文件与目录。

//File类主要用于命名文件、查询文件属性和处理文件目录。
public    class   File   extends Object 
    implements Serializable,Comparable
{}

File类共提供了三个不同的构造函数,以不同的参数形式灵活地接收文件和目录名信息。构造函数:
1)File (String pathname)

//创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
File  f1 = new File("FileTest1.txt"); 

2)File (String parent , String child)

//注意:D:\\dir1目录事先必须存在,否则异常
File f2 = new  File(“D:\\dir1","FileTest2.txt") ;

3)File (File parent , String child)

//在如果 \\dir3目录不存在使用f4.mkdir()先创建
File  f4 = new File("\\dir3");
File  f5 = new File(f4,"FileTest5.txt");

一个对应于某磁盘文件或目录的File对象一经创建, 就可以通过调用它的方法来获得文件或目录的属性。

方法方法描述
public boolean exists( )判断文件或目录是否存在
public boolean isFile( )判断是文件还是目录
public boolean isDirectory( )判断是文件还是目录
public String getName( )返回文件名或目录名
public String getPath( )返回文件或目录的路径
public long length( )获取文件的长度
public String[ ] list ( )将目录中所有文件名保存在字符串数组中返回

File类中还定义了一些对文件或目录进行管理、操作的方法,常用的方法有:

方法方法描述
public boolean renameTo( File newFile )重命名文件
public void delete( )删除文件
public boolean mkdir( )创建目录
三、一些常用输入/输出流

接下来介绍下很常用的输出/输出流:

3.1 文件输入/输出FileInputStream/FileOutputStream类(字节)

1、FileInputStream类
FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回;或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
使用方法:

  • 方式1
File  fin = new File("d:/abc.txt"); 
FileInputStream in = new FileInputStream(fin);
  • 方式2
FileInputStream  in = new  FileInputStream(“d: /abc.txt”);

在这里插入图片描述

2、FileOutputStream类
FileOutputStream类用来处理以文件作为数据输出目的数据流;一个表示文件名的字符串,也可以是File或FileDescriptor对象。

创建一个文件流对象有以下几种方法:

  • 方式1
File f = new  File (“d:/myjava/write.txt ");
FileOutputStream out = new FileOutputStream (f);
  • 方式2
FileOutputStream out = new FileOutputStream(“d:/myjava/write.txt ");
  • 方式3:构造函数将 FileDescriptor()对象作为其参数
FileDescriptor fd = new FileDescriptor(); 
FileOutputStream f2 = new FileOutputStream(fd); 
  • 方式4:构造函数将文件名作为其第一参数,将布尔值作为第二参数
FileOutputStream f = new FileOutputStream("d:/abc.txt",true);  

注意:
(1)文件中写数据时,若文件已经存在,则覆盖存在的文件;
(2)读/写操作结束时,应调用close方法关闭流。

3.2 缓冲输入输出流 BufferedInputStream/ BufferedOutputStream

在这里插入图片描述
BufferedInputStream:当向缓冲流写入数据时候,数据先写到缓冲区,待缓冲区写满后,系统一次性将数据发送给输出设备。

BufferedOutputStream :当从向缓冲流读取数据时候,系统先从缓冲区读出数据,待缓冲区为空时,系统再从输入设备读取数据到缓冲区。

1)将文件读入内存:
将BufferedInputStream与FileInputStream相接

FileInputStream in = new FileInputStream( “file1.txt ” );
BufferedInputStream bin = new BufferedInputStream( in);

2)将内存写入文件:

将BufferedOutputStream与 FileOutputStream相接

FileOutputStream out = new FileOutputStream(“file1.txt”);
BufferedOutputStream bin = new BufferedInputStream(out);

3)键盘输入流读到内存

将BufferedReader与标准的数据流相接

InputStreamReader sin = new InputStreamReader (System.in) ;
BufferedReader bin = new BufferedReader(sin);
3.3 文件输入/输出FileReader/FileWriter类(字符)

1、FileReader :与FileInputStream对应,主要用来读取字符文件,使用缺省的字符编码,有三种构造函数:

  • 方式1:将文件名作为字符串
FileReader f = new FileReader(“c:/temp.txt”); 
  • 方式2:构造函数将File对象作为其参数
File f = new file(“c:/temp.txt”); 
FileReader f1 = new FileReader(f); 
  • 方式3:构造函数将File对象作为其参数
FileDescriptor() fd = new FileDescriptor() 
FileReader f2 = new FileReader(fd); 

2、FileWriter:两种构造函数

  • 方式1:创建字符输出流类对象和已存在的文件相关联。文件不存在的话,并创建
FileWriter fw = new FileWriter(String fileName);
  • 方式2:创建字符输出流类对象和已存在的文件相关联,并设置该流对文件的操作是否为续写。
//表示在fw对文件再次写入时,会在该文件的结尾续写,并不会覆盖掉。
FileWriter fw = new FileWriter("C:\\1.txt",ture)
四、一些示例

参考自:看完这个,Java IO从此不在难

1、读取控制台中的输入

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        // 三个测试方法
//        test01();
//        test02();
        test03();
    }

    public static void test01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一个字符");
        char c;
        c = (char) bufferedReader.read();
        System.out.println("你输入的字符为"+c);
    }

    public static void test02() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一个字符,按 q 键结束");
        char c;
        do {
            c = (char) bufferedReader.read();
            System.out.println("你输入的字符为"+c);
        } while (c != 'q');
    }

    public static void test03() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一行字符");
        String str = bufferedReader.readLine();
        System.out.println("你输入的字符为" + str);
    }
}

至于控制台的输出,我们其实一直都在使用呢,System.out.println() ,out 其实是 PrintStream 类对象的引用,PrintStream 类中当然也有 write() 方法,但是我们更常用 print() 方法和 println() 方法,因为这两个方法可以输出的内容种类更多,比如一个打印一个对象,实际调用的对象的 toString() 方法。

2、二进制文件的写入和读取

注意这里文件的路径,可以根据自己情况改一下,虽然这里的文件后缀是txt,但该文件却是一个二进制文件,并不能直接查看。

@Test
    public void test04() throws IOException {
        byte[] bytes = {12,21,34,11,21};
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
        // 写入二进制文件,直接打开会出现乱码
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }

    @Test
    public void test05() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        // 读取写入的二进制文件,输出字节数组
        while ((c = fileInputStream.read()) != -1) {
            System.out.print(c);
        }
    }

3、文本文件的写入和读取

write() 方法和 append() 方法并不是像方法名那样,一个是覆盖内容,一个是追加内容,append() 内部也是 write() 方法实现的,也非说区别,也就是 append() 方法可以直接写 null,而 write() 方法需要把 null 当成一个字符串写入,所以两者并无本质的区别。需要注意的是这里并没有指定文件编码,可能会出现乱码的问题。

@Test
    public void test06() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt");
        fileWriter.write("Hello,world!\n欢迎来到 java 世界\n");
        fileWriter.write("不会覆盖文件原本的内容\n");
//        fileWriter.write(null); 不能直接写入 null
        fileWriter.append("并不是追加一行内容,不要被方法名迷惑\n");
        fileWriter.append(null);
        fileWriter.flush();
        System.out.println("文件的默认编码为" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test07() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt", false); // 关闭追加模式,变为覆盖模式
        fileWriter.write("Hello,world!欢迎来到 java 世界\n");
        fileWriter.write("我来覆盖文件原本的内容");
        fileWriter.append("我是下一行");
        fileWriter.flush();
        System.out.println("文件的默认编码为" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test08() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        fileReader.close();
        bufferedReader.close();
    }

    @Test
    public void test09() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        while ((c = fileReader.read()) != -1) {
            System.out.print((char) c);
        }
    }

使用字节流和字符流的转换类 InputStreamReader 和 OutputStreamWriter 可以指定文件的编码,使用 Buffer 相关的类来读取文件的每一行。

@Test
    public void test10() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK"); // 使用 GBK 编码文件
        outputStreamWriter.write("Hello,world!\n欢迎来到 java 世界\n");
        outputStreamWriter.append("另外一行内容");
        outputStreamWriter.flush();
        System.out.println("文件的编码为" + outputStreamWriter.getEncoding());
        outputStreamWriter.close();
        fileOutputStream.close();
    }

    @Test
    public void test11() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); // 使用 GBK 解码文件
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
    }

4、复制文件

这里笔者做了一些测试,不使用缓冲对文件复制时间的影响,文件的复制实质还是文件的读写。缓冲流是处理流,是对节点流的装饰。

注:这里的时间是在我这台华硕笔记本上测试得到的,只是为了说明使用缓冲对文件的读写有好处。

@Test
    public void  test12() throws IOException {
        // 输入和输出都使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        BufferedOutputStream outBuffer = new BufferedOutputStream(out);
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            outBuffer.write(bs, 0, len);
        }
        System.out.println("复制文件所需的时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 200 多毫秒
        inBuffer.close();
        in.close();
        outBuffer.close();
        out.close();
    }


    @Test
    public void  test13() throws IOException {
        // 只有输入使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 500 多毫秒
        inBuffer.close();
        in.close();
        out.close();
    }

    @Test
    public void test14() throws IOException {
        // 输入和输出都不使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = in.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间 700 多毫秒
        in.close();
        out.close();
    }

    @Test
    public void test15() throws IOException {
        // 不使用缓冲
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        long begin = System.currentTimeMillis();
        while ((len = in.read()) != -1) {
            out.write(len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 160000 毫秒,约 2 分多钟
        in.close();
        out.close();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值