【JavaIO流】JavaIO流的使用

2022-08-01

JavaIO流的使用

IO流概念

  • :代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
  • 流的本质:数据传输。根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
  • 流的作用:为数据源和目的地建立一个输送通道。

IO流的分类

按照不同的分类方式,可以把流分为不同的类型。常用的分类有三种。

  • 1、按照流的流向分,可以分为输入流和输出流。(相对于程序而言的输入输出)

    • 输入流:只能从中读取数据,而不能向其写入数据。把文件中的信息读取到程序中。
    • 输出流:只能向其写入数据,而不能向其读取数据。把程序中的信息输出到文件中。
  • 2、按照操作单元划分,可以划分为字节流和字符流

    • 字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
    • 字节流主要是由InputStreamOutputStream作为基类,而字符流则主要由Reader和Writer作为基类。

从这里我们就可以发现,字符流的效率一定是比字节低的!因为计算机的磁盘的存储本质上就是字节存储,如果按照字符去读取和写入,那么我们必须先按照字节读取,然后再进行指定编码读取,再写入到目的文件。这个效率比字节读取和写入要低下。并且字符流实际上只能处理文本文件,当时字节流可以处理一切文件。但是!既然如此为什么还会存在字符流?大家可以去试试读取中文文本,每当按照字节读取时,每读取一次就输出一次,我们会发现输出的是乱码,所以这就是问题(中文字符占1-5个字节),但是当我们使用字节流时就不需要担心了,我们按照了指定的字符编码去读取和写入,所以每一次读取的都是一个字符并且经过Unicode编码,所以就不会出现乱码了。

  • 3、按照是否直接处理文件处理节点流和处理流
    • 可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,称为节点流。节点流也被称为低级流。当使用节点流进行输入和输出时,程序直接连接到实际的数据源,和实际的输入/输出节点连接,如下图所示。
    • 处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。


InputStream类

java.io.InputStream类是所有Java IO输入流的基类,它是以字节为单位的输入流。

public abstract class InputStream  extends Object  implements Closeable
  • 此抽象类是表示字节输入流的所有类的超类。需要定义 InputStream 子类的应用程序必须总是提供返回下一个输入字节的方法。

InputStream类结构图

  • BufferedInputStream: 提供了缓冲功能。
  • DataInputStream: 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
  • PipedInputStream: 允许以管道的方式来处理流。当连接到一个PipedOutputStream后,它会读取后者输出到管道的数据。
  • PushbackInputStream: 允许放回已经读取的数据。
  • SequenceInputStream: 能对多个inputstream进行顺序处理。

InputStream类的常用方法

方法名 用法说明
int read() 读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。
int read(byte[] buffer) 读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。
int read(byte[] buffer, int off, int len) 读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。
int available() 返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
long skip(long n) 此方法是从输入流的当前位置往后跳过n个字符,这样这n个字符就丢失了。当然如果你要找回,可以直接mark,并且reset当前的流位置,重新获取流就可以了。
void close() 使用完后,必须对我们打开的流进行关闭。
void mark(int readlimit) 设置输入流的当前位置,InputStream的mark方法不执行任何操作,只是标记输入流的位置,如果要重新定位到这个位置,需要调用reset()方法才行。
boolean markSupported() 获取输入流是否支持mark和reset操作,InputStream.markSupported只有返回true,则流将以某种方式记住在调用mark之后读取的所有字节,这个时候调用reset()方法才行。
void reset() 重新定位输入流的位置,InputStream.reset()必须先调用mark()方法标识了流的位置,并且调用markSupported返回true,这个时候才能生效。
  • 参考链接:Java InputStream类
public class File_StreamTest {
    public static void main(String[] args) {
        FileInputStream in = null;
        try {
            in = new FileInputStream(new File("D:\\Java文件IO\\Test\\e.txt"));
            //TODO:read()方法
//            int read = -1; //如果读取不到数据返回-1
////            int read = in.read();//以字节为单位读取数据,单个数据读取
//            while ((read = in.read()) != -1) {
//                System.out.print((char) read);
//            }
//            System.out.println();

            //TODO:read(bytes)方法
//            int len = in.available(); //获取文件内容的数据大小
//            byte[] bytes = new byte[len];
//            in.read(bytes);
//            System.out.println(new String(bytes));

            //TODO:5个字节5个字节读取数据
//            byte[] bytes = new byte[5]; //5个字节5个字节读取数据
//            int read = -1; //如果读取不到数据返回-1
//            while ((read = in.read(bytes)) != -1) {
//                System.out.println(new String(bytes));
//            }

            //TODO:指定每次读数据的数量
            byte[] bytes = new byte[1024]; //5个字节5个字节读取数据
            int read = -1; //每次读取的数据量
            int readLen = 0; //每次往数组中存储数据的数据下标
            int len = 3; //指定每次读数据的数量
            while ((read = in.read(bytes,readLen,len)) != -1) {
                readLen += read;
            }
            System.out.println(new String(bytes));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null; //释放强引用
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

额外知识

1、为什么read()在读取的时候是按照字节读取的,但是方法返回的却是int类型??

  • 因为我们在处理输入流时,我们最终会在read方法返回 -1 的时候中止读取,如果read方法返回的是byte类型,-1也就对应的是8个字节全部为1。这个时候有很大的概率读取到中间读取到了8个字节全部为1的情况,但是此时并没有将源文件读取完,所以就会产生错误。但是当我们用 int 类型去返回时,读取到-1时,也只是8位为1,避免了中途中断读取的错误。但是读取到32位全部为1这个概率就非常小了,所以这避免了读取到中间就中止读取的错误。

InputStream类总结

  • 1、如果你正在开发一个从流中读取数据的组件,请尝试用InputStream替代任何它的子类(比如FileInputStream)进行开发。这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。
  • 2、Java中的Inputstream是不能重复读取的,它的读取是单向的,因为读取的时候,会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,当读到最后一个字符的时候,pos指针不会重置。
  • 3、如果想要重复使用InputStream对象,可以先把InputStream转化成ByteArrayOutputStream,后面要使用InputStream对象时,再从ByteArrayOutputStream转化回来就好了。

OutputStream类

java.io.OutputStream类是所有Java IO输出流的基类,它是以字节为单位的输出流。

public abstract class OutputStream implements Closeable, Flushable
  • 此抽象类是表示字节输出流的所有类的超类。需要定义 OutputStream 子类的应用程序必须总是提供返回下一个输出字节的方法。

OutputStream类结构图

  • OutputStream是以字节为单位的输出流的超类,提供了write()函数从输出流中读取字节数据。
  • ByteArrayOutputStream是字节数组输出流,写入ByteArrayOutputStream的数据被写入到一个byte数组,缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()获取数据。
  • PipedOutputStream是管道输出流,和PipedInputStream一起使用,能实现多线程间的管道通信。
  • FilterOutputStream是过滤输出流,是DataOutputStreamBufferedOutputStreamPrintStream的超类。
  • DataOutputStream是数据输出流,用来装饰其他的输出流,允许应用程序以与机器无关方式向底层写入基本Java数据类型。
  • BufferedOutputStream是缓冲输出流,它的作用是为另一个输出流添加缓冲功能。
  • PrintStream是打印输出流,用来装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值。
  • FileOutputStream是文件输出流,通常用于向文件进行写入操作。
  • ObjectOutputStream是对象输出流,它和ObjectInputStream一起对基本数据或者对象的持久存储。

OutputStream类的常用方法

方法名 用法说明
void write(int b) 向输出流中写入一个字节数据,该字节数据为参数b的低8位。
void write(byte[] b) 将一个字节类型的数组中的数据写入输出流。
void write(byte[] b, int off, int len) 将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。
void close() 关闭并释放输出流资源。
void flush() 将输出流中缓冲的数据全部写出到目的地。
  • 参考链接:Java OutputStream类
class OutputStreamTest {
    public static void main(String[] args) {
        FileOutputStream out = null;
        try {
            //FileOutputStream默认会覆盖原文件数据,如果要在文件后面添加数据,append一定要设置成true。 
            out = new FileOutputStream(
                    new File("D:\\Java文件IO\\Test\\e.txt"),true);
            String str = "Hello World!";
            byte[] bytes = str.getBytes();
//            for (int i = 0; i < bytes.length; i++) {
//                out.write(bytes[i]);
//            }
            out.write(bytes);
            out.flush(); //将输出流中缓冲的数据全部写出到目的地
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    if(out != null) {
                        out.close(); // 关闭输出流
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

额外知识

1、什么时候需要用 flush 方法?

  • 1)在输出流关闭之前,每一次都 flush 一下(最保险的方法,推荐使用)。
  • 2)当某一个输出流对象中有缓冲区,就需要flush。

OutputStream类总结

  • 1、OutputStream类是Java IO API中所有输出流的基类。子类包括BufferedOutputStreamFileOutputStream等等。
  • 2、OutputStream是一个典型的装饰者模式,使用的时候直接new子类。
  • 3、OutputStream可以输出到console,文件,磁盘等目标媒介中。
  • 4、OutputStream也属于资源,处理完了以后务必要close()关闭并释放此流有关的所有系统资源,不然会大量占用系统内存资源,大量不释放资源会导致内存溢出。
  • 5、OutputStream.flush()方法将所有写入到OutputStream的数据冲刷到相应的目标媒介中。比如,如果输出流是FileOutputStream,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。

Reader类

Reader是一个抽象类,它是以字符为单位的输入流的公共父类。

public abstract class Reader implements Readable, Closeable

Reader类结构图

  • BufferedReader:从流里面读取文本,通过缓存的方式提高效率,读取的内容包括字符、数组和行。缓存的大小可以指定,也可以用默认的大小,大部分情况下,默认大小就够了。
  • InputStreamReader:把字节翻译成字符的,可以处理乱码问题。
  • FileReader:方便读取字符文件的。

Reader类的常用方法

方法名 用法说明
int read() 读取一个字符并以整数的形式返回(0~2^16),如果返回-1已到输入流的末尾。
int read(char[] cbuf) 读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1。
int read(char[] cbuf, int off, int len) 读取length个字符,并存储到一个数组buffer,从off位置开始存,最多读取len,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1。
void mark(int readlimit) 在此输入流中标记当前的位置,readlimit - 在标记位置失效前可以读取字节的最大限制。
boolean markSupported() 测试此输入流是否支持 mark 方法。
long skip(long n) 跳过和丢弃此输入流中数据的 n 个字节/字符。
void reset() 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
boolean ready() 判断是否准备读取此流。
  • 参考链接:Java Reader类
public class File_Test {
    public static void main(String[] args) {
        //编码对应的问题
        Reader reader = null;
        try {
            reader = new InputStreamReader(
                    new FileInputStream("D:\\Java文件IO\\Test\\b.txt"), "gbk");
            //read();
//                int read1 = reader.read();//以两个字节(一个字符)为单位读取数据
            //char : -2^15  ~ 2^15 -1
//                read1 取值范围:0 ~ 65535  数量: 65536
            int read = -1; //.read()读取不到数据的话就返回负一
            while ((read = reader.read()) != -1) {
                System.out.print((char) read);
            }
            //read(char[])
//            char[] chars = new char[5];
//            int read = -1; //.read(bytes) 读取不到数据的话就返回负一
//            while ((read = reader.read(chars)) != -1) {
//                System.out.print(new String(chars));
//            }
            //read(char[],int off,int len)
//                char[] chars = new char[1024]; //1000000   3
//                int read = -1;  //每次读到的数据数量
//                int readLen = 0; //每次往数组中存储数据的数据的下标
//                int len = 3;  //指定每次读数据的数量
//                while ((read = reader.read(chars, readLen, len)) != -1) {
//                    readLen += read;
//                }
//                System.out.println(new String(chars));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                    reader = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

额外知识

1、为什么已经有了字符流Reader / Writer,要这个处理流 InputStreamReader / OutputStreamReader 干嘛?就为了给定一个字符编码?

  • 现在处理的都是文件处理,但是在开发过程中,更多的是接收网络端源的数据,不是字节就是字符,可能是纯文本。使用传输更多的就是字节流。那么如果我们知道传输的数据是纯文本,我们是不是就可以使用到这个处理流来提高效率了!

FileReader类

FileReader用于以字符为单位读取文本文件,能够以字符流的形式读取文件内容。除了读取的单位不同之外,FileReaderFileInputStream并无太大差异,也就是说,FileReader用于读取文本。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。

  • 1、在使用FileReader 对象进行文件输入操作的时,JVM先读取本地文本文,然后将其格式转化为Unicode编码格式进行操作。再用FileWriter进行文本文件输出时则把Unicode编码格式再转换成本地(本地主机上)的编码格式(如ASCII或者GBK等)。
  • 2、FileReaderFileWriter两个类和FileInputStreamFileOutputStream两个类的操作方法基本相同,只不过前者基于字符,后者基于字节(byte),若操作的文件不是文本文件,则建议使用FileInputStreamFileOutputStream进行文件的输入输出。

Writer类

Writer类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。

public abstract class Writer implements Appendable, Closeable, Flushable

Writer类结构图

  • BufferedWriter:字符缓冲输出流。
  • FileWriter:用来写入字符串到文件。
  • OutputStreamWriter:写入字符,同时可以设置编码集。

Writer类的常用方法

方法名 用法说明
void write(int c) 向输出流中写入一个字符数据,该字节数据为参数b的低16位。
void write(char[] cbuf) 将一个字符类型的数组中的数据写入输出流。
void write(char[] cbuf, int offset, int length) 将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。
void write(String string) 将一个字符串中的字符写入到输出流。
void write(String string, int offset, int length) 将一个字符串从offset开始的length个字符写入到输出流。
void flush() 将输出流中缓冲的数据全部写出到目的地。
Writer append(char c) 将指定字符添加到此 writer。
Writer append(CharSequence csq) 将指定字符序列添加到此 writer。
Writer append(CharSequence csq, int start, int end) 将指定字符序列的子序列添加到此 writer.Appendable。
void close() 关闭此流,但要先刷新它。
  • 参考链接:Java Writer类
class IODemoTest {
    public static void main(String[] args) {
        Writer writer = null;  //字符输出流
        try {
            writer = new OutputStreamWriter(new FileOutputStream  //真正写文件的流
                    ("D:\\Java文件IO\\Test\\b.txt", true), "gbk");
//            writer.write(8);  每个数字对应一个字符
            writer.write("倒萨的机会");
//            char[] array = {'a','b','c','a','b','c','a','b','c'};
//            writer.write(array);
//            writer.write(array, 3, 2);
            //业务 1 。2。。。
            //业务 3 又需要之前存储的数据了   另外一个线程
            writer.flush();//在close之前我们向刷新缓冲区的话。
        } catch (IOException e) {
            e.printStackTrace();
        } finally {   //方法最后执行
            try {
                if (writer != null) {
                    writer.close();  //将缓冲区中的数据刷新到文件里
                    writer = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileWriter类

FileWriter是文件字符输出流,主要用于将字符写入到指定的打开的文件中,其本质是通过传入的文件名、文件、或者文件描述符来创建FileOutputStream,然后使用OutputStreamWriter使用默认编码将FileOutputStream转换成Writer(这个Writer就是FileWriter)。

  • 1、使用FileWriter写数据的时候,FileWriter内部是维护了一个1024个字符数组的,写数据的时候会先写入到它内部维护的字符数组中,如果需要把数据真正写到硬盘上,需要调用flush或者是close方法或者是填满了内部的字符数组。
  • 2、使用FileWriter的时候,如果目标文件不存在,那么会自动创建目标文件。
  • 3、使用FileWriter的时候, 如果目标文件已经存在了,那么默认情况会先情况文件中的数据,然后再写入数据 , 如果需要在原来的基础上追加数据,需要使用“new FileWriter(File , boolean)”的构造方法,第二参数为true

本文地址:https://blog.csdn.net/Sampson_S/article/details/107462455

《【JavaIO流】JavaIO流的使用.doc》

下载本文的Word格式文档,以方便收藏与打印。