一、IO流,什么是IO?
I :Input
O:Output
通过IO可以完成硬盘文件的读和写
二、IO流的分类
有多种分类方式:
1. 按照流的方向进行分类(输入流、输出流):
以内存作为参照物
往内存中去,叫做输入(Input),或者叫做读(Read)。
从内存中出来,叫做输出(Output),或者叫做写(Write)。
2. 按照读取数据方式不同进行分类(字节流、字符流):
按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制。这种流是万能的,什么类型的文件都可以读取。包括:文本文件、图片、声音文件、视频文件等。
假设文件file.txt,采用字节流的话是这样读的:文件内容:a我是中国人
第一次读:一个字节,正好读到 'a'
第二次读:一个字节,正好读到'中'字符的一半
第三次读:一个字节,正好读到'中'字符的另外一半 ……
按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件,只能读纯文本文件(能用 .txt 打开的文件,没有特殊格式,文件名后缀不一定是.txt,比如.java文件也属于纯文本文件),word文件不属于纯文本文件。
假设文件file.txt,采用字符流的话是这样读的:文件内容:a我是中国人
第一次读:一个字符,正好读到 'a'('a'字符在Windows系统中占用1个字节)
第二次读:一个字节,正好读到'中'('中'字符在Windows系统中占用2个字节)
……
三、IO流的四大家族
首先,Java中所有的流都是在 java.io.*
下
四大家族:
Tips:如何分辨字节流和字符流:在Java中只要“类名”以
Stream
结尾的都是字节流;以“ Reader/Writer
”结尾的都是字符流
两点注意:
1、所有的流都实现了 java.io.Closeable
接口,都是可关闭的,都有 close()
方法。流是一个管道,连接内存和硬盘,用完之后一定要关闭,不然会耗费(占用)很多资源。2、所有的输出流都实现了 java.io.Flushable
接口,都是可刷新的,都有 flush()
方法。在输出流的最终输出之后,一定要记得 flush()
刷新一下,这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道),如果没有 flush()
可能会导致丢失数据。
四、需要掌握的流
java.io包下需要掌握的流有16个
文件流专属:
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamReader
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
五、代码详解
接下来的代码演示中,我把很多重点直接放在了注释中,方便大家理解
先展示一下示例文档的内容,接下来我们开始对其读取
1、最原始,逐字节读取(使用 java.io.FileInputStream
)
package JavaIO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class Test { public static void main(String[] args) { //创建一个输入流 FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("Text"); int readData = 0; //逐字节读取,当文档被读完时,read()方法返回-1,循环结束 while ((readData = fileInputStream.read()) != -1) { System.out.println(readData); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //在finally块中关闭流 if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
运行结果:
可见读到了文档每个字符的ASCII码值
2、上述方法内存和硬盘之间的交互过于频繁,每个字节都要交互一次,效率极低,所以接下来我们使用int read(byte[] b)方法,一次读取多个字符,减少硬盘和内存的交互,提高程序的执行效率。(使用 java.io.FileInputStream
)
package JavaIO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class Test { public static void main(String[] args) { //创建一个输入流 FileInputStream fileInputStream = null; try { //这里说明一下,工程Project的根就是IDEA的默认当前路径,注意相对路径和绝对路径的区别 fileInputStream = new FileInputStream("Text"); //准备一个4个长度的byte数组,一次最多读取4个字节,这里大小可以任意定 //这里再次说明,数组开的太大会导致内存溢出,所以一定要合理选择 byte[] bytes = new byte[4]; //用来记录每次读到的字节数,很重要! int readCount = 0; //每4个字节读取一次,当文档被读完时,read()方法返回-1,循环结束 while ((readCount = fileInputStream.read(bytes)) != -1) { //把byte转换为字符串,读到多少个转换多少个 System.out.println(new String(bytes,0,readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //在finally块中关闭流 if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
运行结果:
通过String类将字节转换成字符串输出,输出达到预期
这里必须要解释一下int read(byte[] b)方法的运行机制,大家就能明白readCount这个变量的重要性了
根据上图,如果把readCount换成数组长度
System.out.println(new String(bytes,0,readCount));
换成
System.out.println(new String(bytes,0,bytes.length));
则运行结果如下图,显然不符合我们的要求,所以要注意int read(byte[] b)方法返回值的使用:
在这里我们再介绍两种常用方法:
// @return an estimate of the number of remaining bytes that can be read// (or skipped over) from this input stream without blocking.//此方法可以返回此文件还有多少字节没有读,可以做为while循环终止条件使用public int available() throws IOException { return available0();}//This method may skip more bytes than what are remaining in the backing file.//此方法可以跳过几个字节不读取,比如一些文件有规定好的读取方式,跳过几个字节读几个字节才是正确的读入 public long skip(long n) throws IOException { return skip0(n);}
3、FileOutputStream的使用和FileOutputStream类似,这里举一个文件复制的例子(使用 java.io.FileInputStream
和 java.io.FileOutputStream;
)
package JavaIO;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class Test2 { public static void main(String[] args) { //创建一个输入流 FileInputStream fileInputStream = null; //创建一个输出流 FileOutputStream fileOutputStream = null; try { //这里说明一下,工程Project的根就是IDEA的默认当前路径,注意相对路径和绝对路径的区别 fileInputStream = new FileInputStream("Text"); //下面如果写成这样,就会在原文档之后累加,之后的输出流也一样 //fileOutputStream = new FileOutputStream("Output.txt",true); fileOutputStream = new FileOutputStream("Output.txt"); //准备一个4个长度的byte数组,一次最多读取4个字节,这里大小可以任意定 //这里再次说明,数组开的太大会导致内存溢出,所以一定要合理选择 byte[] bytes = new byte[4]; //用来记录每次读到的字节数,很重要! int readCount = 0; //每4个字节读取一次,当文档被读完时,read()方法返回-1,循环结束 while ((readCount = fileInputStream.read(bytes)) != -1) { //最核心的:一边读,一边写 fileOutputStream.write(bytes,0,readCount); } //输出流最后要刷新 fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //在finally块中关闭流 //两部分要分开写,否则发生异常可能会导致有通道没有关闭 if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
运行结果:
被复制到了
4、FileReader的使用,只不过是按字符读,和fileInputStream没什么区别
package JavaIO;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;public class Test3 { public static void main(String[] args) { //创建一个输入流 FileReader fileReader = null; try { //这里说明一下,工程Project的根就是IDEA的默认当前路径,注意相对路径和绝对路径的区别 fileReader = new FileReader("Text"); //准备一个char数组 //这里再次说明,数组开的太大会导致内存溢出,所以一定要合理选择 char[] chars = new char[4]; //往char数组中读 int readCount = 0; while ((readCount = fileReader.read(chars)) != -1) { for (int i = 0; i < readCount; i++) { System.out.println(chars[i]); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { //在finally块中关闭流 if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
运行结果:
可见是逐个读取的字符
5、再讲讲BufferedReader缓冲流
这个流说白了就是无需我们自己定义byte[]数组来承接,其自带缓冲
这里还要明白节点流和包装流的概念,这两个概念往往是相对的
package JavaIO;import java.io.*;public class Test4 { public static void main(String[] args) throws Exception{ //这里对异常不再处理只是为了演示方便,实际情况千万不要这么做 FileReader fileReader = new FileReader("Text"); // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流 // 外部负责包装的这个流,叫做:包装流,或者叫处理流 // 像当前这个程序来说:FileReader就是一个节点流,BufferedReader就是包装流/处理流 BufferedReader bufferedReader = new BufferedReader(fileReader); //readLine()方法读取一个文本行,但不带换行符 String s = null; while ((s = bufferedReader.readLine()) != null) { System.out.println(s); } //对于包装流来说,只需关闭最外层流就行,里面的节点流会自动关闭。 bufferedReader.close(); }}
运行结果:
成功读取
6、唠唠转换流
转换流的作用就是三层套娃中间的二娃
这里对异常不再处理只是为了演示方便,实际情况千万不要这么做
package JavaIO;import java.io.*;public class Test5 { public static void main(String[] args) throws Exception{ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("Text2"))); out.write("我们都有一个家"); out.write("名字叫中国!"); //刷新 out.flush(); //关闭最外层即可 out.close(); }}
运行结果:
7、再谈谈数据流
能干什么:DataOutputStream写的文件,只有DataInputStream去读,并且已知读取规则,才可以正常取出数据
package JavaIO;import java.io.*;public class Test6 { public static void main(String[] args) throws Exception{ DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("Text3")); byte b = 100; short s = 200; int i = 300; long l = 400L; float f = 3.0F; double d = 3.14; boolean flag = false; char c = 'a'; //开始写,把数据以及数据的类型一并写入到文件当中 dataOutputStream.writeByte(b); dataOutputStream.writeShort(s); dataOutputStream.writeInt(i); dataOutputStream.writeLong(l); dataOutputStream.writeFloat(f); dataOutputStream.writeDouble(d); dataOutputStream.writeBoolean(flag); dataOutputStream.writeChar(c); dataOutputStream.flush(); dataOutputStream.close(); DataInputStream dataInputStream = new DataInputStream(new FileInputStream("Text3")); //开始读 byte b1 = dataInputStream.readByte(); short s1 = dataInputStream.readShort(); int i1 = dataInputStream.readInt(); long l1 = dataInputStream.readLong(); float f1 = dataInputStream.readFloat(); double d1 = dataInputStream.readDouble(); boolean flag1 = dataInputStream.readBoolean(); System.out.println(b1); System.out.println(s1); System.out.println(i1); System.out.println(l1); System.out.println(f1); System.out.println(d1); System.out.println(flag1); dataInputStream.close(); }}
运行结果:
8、标准输出流
本质是向控制台输出,但是我们可以改变输出方向,一般将其作为日志工具类
定义Logger类
package JavaIO;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.PrintStream;import java.text.SimpleDateFormat;import java.util.Date;public class Logger { public static void log(String msg) { try { //指向一个日志文件 PrintStream printStream = new PrintStream(new FileOutputStream("log.txt", true)); //改变输出方向 System.setOut(printStream); //取当前日期和时间 Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String str = simpleDateFormat.format(date); System.out.println(date + ":" + msg); printStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } }
}
定义测试类
package JavaIO;public class Test7{ public static void main(String[] args) { //测试工具类 Logger.log("程序运行正常"); Logger.log("xxxxxx"); }}
运行结果:
好了,今天这篇文章就到这里啦,相信你已经大体掌握了JavaIO流?,Java的学习一定要多写多练。笔者会不定期做一些技术分享和工具使用心得,欢迎大家点赞和收藏!