首页 / JAVA  

Java中IO详解


IO 是什么?其实就是Java中的一种输入和输出功能,也可以理解为对文件的读取和写入的操作(比如读取磁盘文件或者更改磁盘文件的内容),只不过Java中对这种操作叫做对流的操作。而流不只是对文件进行读写,还可以对内存,网络,程度操作。

IO流
Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源抽象表述为"流"。
流是一组有顺序的字节集合,是对数据传输的总称或抽象。
流有输入和输出,输入时是流从数据源流向程序(程序代码读取磁盘文件的过程)。输出时是流从程序传向数据源(程序代码写入内容到磁盘文件的过程),而数据源可以是内存,文件,网络或程序等。

分类

按照流向不同,分为 输入流、输出流
按照流对象不同,分为 字节流、字符流

字节流可以处理任意类型的数据,而字符只能处理字符类型的数据


字节流


Java的流式输入\输出是建立在四个抽象类的基础上的:InputStream、OutputStream、Reader、Writer。它们用来创建具体的流式子类。尽管程序通过具体子类执行输入/输出操作,但顶层类定义了所有流类的基本通用功能。

InputStream和OutputStream为字节流设计(读取文件和生成文件),Reader和Writer为字符流设计(读取字符串和者写入字符串),字节流和字符流形成分离的层次结构。一般来说,处理字符或字符串使用字符流类,处理字节或二进制对象使用字节流。

操作文件流时,不管是字符流还是字节流,都可以按照以下方式进行:

1、使用File类找到一个对象

2、通过File类的对象去实例化字节流或字符流的子类

3、进行字节(字符)的读、写操作

4、关闭文件流


OutputStream(字节输出流)

OutputStream是定义了Java流式字节输入模式的抽象类

FileOutputStream(文件字节输出流)

FileOutpuStream应该是Java中最常见的字节输出流了(可以在磁盘中创建一个文件,并写入内容),它创建一个可向文件写入字节的类OutputStream,它常用的构造方法如下:

1、FileOutputStream(String name)

2、FileOutputStream(File file)

3、FileOutputStream(File file, boolean append)


前两个构造方法类似,前者输入文件的绝对路径,后者输入File的实例对象,和RandomAccessFile一样,推荐后者。第三个构造方法有一点不同,append如果设置为true,文件则以搜索路径模式打开。FileOutputStream的创建不依赖于文件是否存在,在创建对象时,FileOutputSStream会在打开输出文件之前就创建它。这种情况下如果试图打开一个只读文件,会引发IOException。FileOutputStream,写一个例子,现在我的D盘下的Files文件夹里并没有"stream.txt"这个文件:

package com.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class FileOutputStreamTest {
    
    public static void main(String[] args) throws Exception{
        File file = new File("D:/Files/stream.txt");
        OutputStream out = new FileOutputStream(file);
        byte b[] = "Hello World!!!".getBytes(); //操作字节流,要转换成字节
        out.write(b);
        out.close();
    }
}

执行上面的代码,在D盘中会生成一个文件名为stream.txt的文件里面内容为:“Hello World!!!”;


InputStream(字节输入流)

InputStream是定义了Java流式字节输入模式的抽象类;读取磁盘中文件数据的

FileInputStream(文件字节输入流)

FileInputStream应该是Java中最常见的字节输入流了,它创建一个能从文件读取字节的InputStream类,它的两个常用构造方法如下:

1、FileInputStream(String name)

2、FileInputStream(File file)

操作的是上面D盘下的Files文件夹生成的"stream.txt":

package com.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamTest {
    
    public static void main(String[] args) throws Exception{
        File file = new File("D:/Files/stream.txt");
        InputStream in = new FileInputStream(file);
        byte b1[] = new byte[(int)file.length()];
        int i = 0;
        i = in.read(b1);
        System.out.println(i); // 字节长度
        System.out.println(new String(b1, 0, i)); // Hello World!!!
    }
}

OutputStream的作用是将内容由Java内存输出到文件中、InputStream是将内容由文件输入到Java内存中


字符流

    字节流提供了处理任何类型输入/输出操作的功能(因为对于计算机而言,一切都是0和1,只需把数据以字节形式表示就够了),但它们不可以直接操作Unicode字符,因为一个Unicode字符占用2个字节,而字节流一次只能操作一个字节。既然Java的口号就是"一次编写、处处运行",那么包含直接的字符输入/输出的支持是必要的。因此就有一些字符输入/输出流,字符流顶层是Reader和Writer这两个抽象类,

FileReader和FileWriter

FileReader类创建了一个可以读取文件内容的Reader类,最常用的构造方法是:

1、FileReader(String fileName)

2、FileReader(File file)

FileWriter创建了一个可以写文件的Writer类,最常用的构造方法是:

1、FileWriter(String fileName)

2、FileWriter(String fileName, boolean append)

3、FileWriter(File file)


package com.demo.io;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
public class FileReaderWriterTest {
    public static void main(String[] args) throws Exception{
        File file = new File("D:/Files/writer.txt");
        Writer out = new FileWriter(file);
        // 声明一个String类型对象
        String str = "Hello World!!!";
        out.write(str);
        out.close();
        
        //读文件操作
        Reader in = new FileReader(file);
        // 开辟一个空间用于接收文件读进来的数据
        char c0[] = new char[1024];
        int i = 0;
        // 将c0的引用传递到read()方法之中,同时此方法返回读入数据的个数
        i = in.read(c0);
        in.close();
        if(i==-1){
            System.out.println("文件中无数据");
        }else{
            System.out.println(new String(c0,0,i));
        }
    }
}

FileWriter和FileReader的使用,和FileOutputStream和FileInputStream的使用差不多,不过实际操作中一般不会用FileWriter和FileReader


字符流进阶及BufferedWriter、BufferedReader

字符流和字节流的区别

 public static void main(String[] args) throws Exception{
12         File file = new File("D:/Files/writer.txt");
13         Writer out = new FileWriter(file);
14         // 声明一个String类型对象
15         String str = "Hello World!!!";
16         out.write(str);
17         out.close();
18         
19         //读文件操作
20         Reader in = new FileReader(file);
21         // 开辟一个空间用于接收文件读进来的数据
22         char c0[] = new char[1024];
23         int i = 0;
24         // 将c0的引用传递到read()方法之中,同时此方法返回读入数据的个数
25         i = in.read(c0);
26         in.close();
27         if(i==-1){
28             System.out.println("文件中无数据");
29         }else{
30             System.out.println(new String(c0,0,i));
31         }
32     }

第17行"out.close()"注释掉可以看一下效果,"writer.txt"一定是空的,控制台上输出的是"文件中无数据",说明一下原因。

字符流和字节流非常相似,但也有区别,从网上找了一张图:

从图上看,字符流和字节流最大的区别在于,字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流操作时使用了缓冲区,通过缓冲区再操作文件。这也解释了上面程序的那个问题,为什么不对资源进行close()就无法写入文件的原因。因为在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果没有关闭,缓冲区中的内容是无法输出的。

什么是缓冲区?

   简单理解,缓冲区就是一块特殊的内存区域。

为什么要使用缓冲区?

   因为如果一个程序频繁操作一个资源(文件或数据库),则性能会很低,为了提升性能,就可以将一部分数据暂时读入(或写入)到内存的一块区域之中,以后直接从此区域读取数据即可,因为读取内存的速度要快于读取磁盘中文件内容的速度。


在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区。

如果不想在关闭时再输出字符流的内容也行,使用Writer的flush()方法就可以了。


字符流的原理

Java支持字符流和字节流,字符流本身就是一种特殊的字节流,之所以要专门有字符流,是因为Java中有大量对于字符的操作,所以专门有字符流。字节流和字符流的转换是以InputStreamReader和OutputStreamWriter为媒介的,InputStreamReader可以将一个字节流中的字节解码成字符,OutputStreamWriter可以将写入的字符编码成字节后写入一个字节流。

InputStreamReader有两个主要的构造函数

1、InputStreamReader(InputStream in)

2、InputStreamReader(InputStream in, String charsetName)

OutputStreamWriter也有两个主要的构造函数:

1、OutputStreamWriter(OutputStream out)

2、OutputStreamWriter(OutputStream out, String charsetName)

从构造函数就可以看出,字符流是利用字节流实现的。InputStreamReader和OutputStreamWriter的两个构造函数的区别在于:一个是使用的默认字符集,一个可以指定字符集名称。其实FileReader和FileWriter可以看一下源码,很简单,只有构造函数,里面都是分别根据传入的文件绝对路径或者传入的File实例,new出FileInputStream和FileOutputStream,在调用InputStreamReader和OutputStreamWriter的构造方法。这么做,帮助开发者省去了实例化FileInputStream和FileOutputStream的过程,让开发者可以直接以fileName或file作为构造函数的参数。


BufferedWriter、BufferedReader

为了达到最高的效率,避免频繁地进行字符与字节之间的相互转换,最好不要直接使用FileReader和FileWriter这两个类进行读写,而使用BufferedWriter包装OutputStreamWriter,使用BufferedReader包装InputStreamReader。同样,在D盘Files文件夹下没有"buffered"这个文件,代码示例为:

package com.demo.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
public class BufferedWriterReaderTest {
    
    public static void main(String[] args) throws Exception{
        File file = new File("D:/Files/buffered.txt");
        Writer writer = new FileWriter(file);
        BufferedWriter bw = new BufferedWriter(writer);
        bw.write("1234\n");
        bw.write("2345\n");
        bw.write("3456\n");
        bw.write("\n");
        bw.write("4567\n");
        bw.close();
        writer.close();
            
        if (file.exists() && file.getName().endsWith(".txt")){
            Reader reader = new FileReader(file);
            BufferedReader br = new BufferedReader(reader);
            String str = null;
            while ((str = br.readLine())!= null){
                System.out.println(str);
            }
            reader.close();
            br.close();
        }    
    }
}

运行一下,首先D盘Files文件夹下多出了"buffered.txt"这个文件,文件中的内容为:

1234
2345
3456

4567


1、利用BufferedWriter进行写操作,写入的内容会放在缓冲区内,直到遇到close()、flush()的时候才会将内容一次性写入文件。另外注意close()的顺序,一定要先关闭BufferedWriter,再关闭Writer,不可以倒过来,因为BufferedWriter的写操作是通过Writer的write方法写的,如果先关闭Writer的话,就无法将缓冲区内的数据写入文件了,会抛出异常。


2、利用BufferedReader进行读操作,不可以用父类Reader指向它,因为readLine()这个方法是BufferedReader独有的,readLine()的作用是逐行读取文件中的内容。

2019-10-26