一、IO流,什么是IO?
I :Input
O:Output
通过IO可以完成硬盘文件的读和写
二、IO流的分类
有多种分类方式:
1. 按照流的方向进行分类(输入流、输出流):
以内存作为参照物
往内存中去,叫做输入(Input),或者叫做读(Read)。
从内存中出来,叫做输出(Output),或者叫做写(Write)。
2. 按照读取数据方式不同进行分类(字节流、字符流):
- 按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制。这种流是万能的,什么类型的文件都可以读取。包括:文本文件、图片、声音文件、视频文件等。
假设文件,采用字节流的话是这样读的:
文件内容:
a我是中国人
第一次读:一个字节,正好读到 ‘a’
第二次读:一个字节,正好读到’中’字符的一半
第三次读:一个字节,正好读到’中’字符的另外一半
……
- 按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件,只能读纯文本文件(能用 .txt 打开的文件,没有特殊格式,文件名后缀不一定是.txt,比如.java文件也属于纯文本文件),word文件不属于纯文本文件。
假设文件,采用字符流的话是这样读的:
文件内容:
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 = ()) != -1)
{
Sy(readData);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//在finally块中关闭流
if (fileInputStream != null) {
try {
();
} 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 = (bytes)) != -1)
{
//把byte转换为字符串,读到多少个转换多少个
Sy(new String(bytes,0,readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//在finally块中关闭流
if (fileInputStream != null) {
try {
();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
通过String类将字节转换成字符串输出,输出达到预期
这里必须要解释一下int read(byte[] b)方法的运行机制,大家就能明白readCount这个变量的重要性了
根据上图,如果把readCount换成数组长度
Sy(new String(bytes,0,readCount));
换成
Sy(new String(bytes,0,by));
则运行结果如下图,显然不符合我们的要求,所以要注意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("Ou;,true);
fileOutputStream = new FileOutputStream("Ou;);
//准备一个4个长度的byte数组,一次最多读取4个字节,这里大小可以任意定
//这里再次说明,数组开的太大会导致内存溢出,所以一定要合理选择
byte[] bytes = new byte[4];
//用来记录每次读到的字节数,很重要!
int readCount = 0;
//每4个字节读取一次,当文档被读完时,read()方法返回-1,循环结束
while ((readCount = (bytes)) != -1)
{
//最核心的:一边读,一边写
(bytes,0,readCount);
}
//输出流最后要刷新
();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//在finally块中关闭流
//两部分要分开写,否则发生异常可能会导致有通道没有关闭
if (fileInputStream != null) {
try {
();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
();
} 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 = (chars)) != -1)
{
for (int i = 0; i < readCount; i++) {
Sy(chars[i]);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//在finally块中关闭流
if (fileReader != null) {
try {
();
} 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 = bu()) != null)
{
Sy(s);
}
//对于包装流来说,只需关闭最外层流就行,里面的节点流会自动关闭。
bu();
}
}
运行结果:
成功读取
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';
//开始写,把数据以及数据的类型一并写入到文件当中
da(b);
da(s);
da(i);
da(l);
da(f);
da(d);
da(flag);
da(c);
da();
da();
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("Text3"));
//开始读
byte b1 = da();
short s1 = da();
int i1 = da();
long l1 = da();
float f1 = da();
double d1 = da();
boolean flag1 = da();
Sy(b1);
Sy(s1);
Sy(i1);
Sy(l1);
Sy(f1);
Sy(d1);
Sy(flag1);
da();
}
}
运行结果:
8、标准输出流
本质是向控制台输出,但是我们可以改变输出方向,一般将其作为日志工具类
定义Logger类
package JavaIO;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.;
import java.u;
public class Logger {
public static void log(String msg) {
try {
//指向一个日志文件
PrintStream printStream = new PrintStream(new FileOutputStream("log.txt", true));
//改变输出方向
Sy(printStream);
//取当前日期和时间
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = (date);
Sy(date + ":" + msg);
();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
定义测试类
package JavaIO;
public class Test7{
public static void main(String[] args) {
//测试工具类
Logger.log("程序运行正常");
Logger.log("xxxxxx");
}
}
运行结果: