一、什么是IO流
I:Input O:Output (输入流,输出流)
任何一款操作系统在管理硬盘文件的时候都离不开IO操作。可以理解通过IO完成硬盘文件的读写。比如我们日常在windows系统上复制拷贝某个文件,就是先通过输入流的方式读取文件,再将文件通过输出流输出到指定目录文件下面。
IO流又称为输入输出流,输入和输出均是以内存作为参照物。(内存:是本次IO操作所占用的资源)
java中的I/O操作主要是指使用java进行输入,输出操作。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。
1. I/O流的分类?

按照流的方向进行分类:
以内存(IDEA运行的代码程序)作为参照物,往内存中流的,称为输入流、读。从内存往外的,称之为输出流、写。
输入流:磁盘流向内存中去;
输出流:内存(程序)流向外部;
按照读取数据方式不同进行分类:
字节流 | 字符流 | |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
字节流:数据流中最小的数据单元是字节,单位是1字节8byte
字符流:数据流中最小数据单元是字符,java中的字符是Unicode编码,一个字符占用两个字节 16byte
2.java IO流四大抽象类
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流

所有的流都实现如下接口:
java.io.Colseable接口:都是可关闭的,都有close()方法. 流毕竟是一个管道,这个是内存和硬盘之间的通道,养成一个好习惯,用完流将其关闭。
java.io.Flushable接口:都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。如果没有flush()可能会导致数据丢失。
小结:在java中只要"类名"以Stream结尾的都是字节流。以"Reader/Writer"结尾的都是字符流 。
3.根据功能分为节点流和包装流
节点流:可以从或向一个特定的地方(节点)读写数据,直接连接数据源。如最常见的是文件的FileReader,还可以是数组、管道、字符串,关键字分别为ByteArray/CharArray,Piped,String。
处理流(包装流):并不直接连接数据源,是对一个已存在的流的连接和封装,是一种典型的装饰器设计模式,使用处理流主要是为了更方便的执行输入输出工作,如PrintStream,输出功能很强大,又如BufferedReader提供缓存机制,推荐输出时都使用处理流包装。
一个流对象经过其他流的多次包装,称为流的链接。
注意:一个IO流可以即是输入流又是字节流又或是以其他方式分类的流类型,是不冲突的。比如FileInputStream,它既是输入流又是字节流还是文件节点流。
4.一些特别的流类型
转换流:转换流只有字节流转换为字符流,因为字符流使用起来更方便,我们只会向更方便使用的方向转化。如:InputStreamReader与OutputStreamWriter。
缓冲流:有关键字Buffered,也是一种处理流,为其包装的流增加了缓存功能,提高了输入输出的效率,增加缓冲功能后需要使用flush()才能将缓冲区中内容写入到实际的物理节点。但是,在现在版本的Java中,只需记得关闭输出流(调用close()方法),就会自动执行输出流的flush()方法,可以保证将缓冲区中内容写入。
对象流:有关键字Object,主要用于将目标对象保存到磁盘中或允许在网络中直接传输对象时使用(对象序列化)。
二.流的使用
1.文件字节流
FileInputStream
文件字节输入流,万能的,任何类型的文件都可以采用这个流来读,读取单位是1字节。流的方向是(硬盘—>内存)
public class FileInputStreamTest {
public static void main(String[] args){
//读取文件
try {
FileInputStream fis = new FileInputStream("E:\\Student.txt");
int read1 = fis.read();//read()是读取到的字节大小
int read2 = fis.read();
//如果读不到数据,程序则默认返回-1
System.out.println(read1);
System.out.println(read2);
fis.close();
}catch (IOException e){
//因为涉及到外部file读取,java认为这种外部获取文件,是不可控且不可靠的,所以得try{}catch一下
System.out.println("找不到指定的文件"+e);
}
}
错误路径("E:\\Student1.txt")运行结果:

正确路径("E:\\Student.txt")运行结果:

上面的代码只read()了两次并没有将文件读完,read()方法返回类型的亦是int类型。 这里的read()方法在读到文件末尾时会返回-1。
/**
对上面的代码进行改进,采用循环遍历的方式,读完Student.txt文件
*/
public class FileInputStreamTest {
public static void main(String[] args){
try {
FileInputStream fis=null;
fis=new FileInputStream("E:\\Student.txt");
while (true){
int read=fis.read();
if(read==-1){
break;
}
System.out.print(read+" ");
}
if(fis !=null){
fis.close();
}
}catch (IOException e){
System.out.println("找不到指定路径"+e);
}
}
}
运行结果:一大串返回的长度。

read()方法
int read(byte[] b)
一次最多读取b.length个字节
减少硬盘和内存的交互,提高程序的执行效率往byte[]数组当中读
public class FileInputStreamTest {
public static void main(String[] args){
try {
FileInputStream fis = null; // 创建一个流
fis = new FileInputStream("E://Student.txt");
// 开始读 采用byte数组 一次读取多个字节。最多读取数组.length个字节.
byte[] bytes = new byte[4]; // 准备一个4长度的byte数组,一次最多读取4个字节
// 返回读到的字节数量(不是字节本身)(把数据读取到了byte数组当中)
int readCount = fis.read(bytes); // 4
//System.out.println(new String(bytes)); // 将字节数组全部转换成字符串
// 不应该全部转换,应该是读取了多少字节,转换多少个
System.out.println(new String(bytes,0,readCount));
System.out.println(readCount);
readCount = fis.read(bytes); // 2
System.out.println(new String(bytes,0,readCount));
System.out.println(readCount);
readCount = fis.read(bytes); // -1 (一个字节都没有读到)
System.out.println(new String(bytes,0,readCount));
System.out.println(readCount);
//---------------------------------------------------
// 准备一个byte数组
// byte[] bytes = new byte[4];
// int readCount;
// while((readCount = fis.read(bytes))!=-1){
// // 把byte数组转换成字符串,读到多少个转换多少个
// System.out.print(new String(bytes,0,readCount));
// }
// if(fis!=null){
// fis.close();
// }
//----------------------------------------------------
}
catch (IOException e){
System.out.println("找不到指定路径"+e);
}
}
}
运行结果:

补充:FileInputStream类的其它方法:
* int available(); // 返回流当中的剩余的没有读到的字节数量
* long skip(long n); // 跳过几个字节不读
//-------------------------------------------------------------------------------
FileInputStream fis = null;
fis = new FileInputStream("tempfile");
System.out.println("总字节数量:"+fis.available());
fis.skip(3);
System.out.println(fis.read());
FIleOutputStream
文件字节输出流,负责将数据写出,从内存到硬盘。
public class FileOutputStreamTest {
public static void main(String[] args) {
try{
FileOutputStream fos = null;
// myfile文件不存在的时候会自动新建!
// 不清空原文件的方式写入 则选择以追加的方式写入 不会清空原文件内容
fos = new FileOutputStream("E:\\Demo.txt",true); // true表示追加
// 开始写
byte[] bytes = {97,98,99,100};//a b c d ,输出转换成字母
// 将byte数组全部写出
fos.write(bytes); // abcd
// 将byte数组的一部分写出 输出2个字节=>ab
fos.write(bytes,0,2); //ab
// 以上两种方式都是将原文件清空之后再写入的(谨慎使用)
// 写完之后,一定要刷新
String s = "我是一个中国人,我骄傲";
// 将字符串转换为byte数组
byte[] bytes1 = s.getBytes();
fos.write(bytes1);
fos.flush();
}catch (IOException e){
System.out.println(e);
}
}
}
输出结果:

文件的复制
文件的拷贝,是先从硬盘中读取到内存里面,再通过内存写入硬盘从而完成文件的复制。使用FileInputStream + FileOutputStream 完成文件的拷贝拷贝的过程应该一边读,一边写。 文件的类型随意,万能的,什么样的文件都能拷贝。(因为是一个个字节的读写,io操作频繁,所以效率相对较低)

public class FileCopyTest {
public static void main(String[] args) {
try {
FileInputStream fis = null;
FileOutputStream fos = null;
// 创建一个输入流对象
fis = new FileInputStream("E://mydemo.txt");
// 创建一个输出流对象
fos = new FileOutputStream("D://myfile2.txt");
// 最核心的: 一边读,一边写(1024个字节是1kb)
byte[] bytes = new byte[1024 * 1024]; // 一次拷贝1M
int readCounts = 0;
while((readCounts = fis.read(bytes)) != -1){
fos.write(bytes,0,readCounts);
}
// 刷新,输出流最后要刷新
fos.flush();
fis.close();
fos.close();
}catch (IOException e){
System.out.println(e);
}
}
}
fileReader+fileWriter也可以用复制文件:
public class FileCopyTest {
public static void main(String[] args) {
try {
FileReader fileReader = null;
FileWriter fileWriter = null;
// 创建一个输入流
fileReader = new FileReader("myfile.txt");
// 创建一个输出流(没有文件会先自己创建)
fileWriter = new FileWriter("myfile2.txt");
char[] chars = new char[1024*1024];
int readCounts = 0;
while((readCounts = fileReader.read(chars))!=-1){
fileWriter.write(chars,0,readCounts);
}
fileWriter.flush();
fileReader.close();
fileWriter.close();
}catch (IOException e){
System.out.println(e);
}
}
}
FileReader用法
/*
FileReader:
读取文本内容时,比较方便,快捷。
一次读取一个字符。
*/
public class FileReaderTest {
public static void main(String[] args) {
FileReader reader=null;
try {
//创建文件字符输入流
reader=new FileReader("text02.txt");
//开始读
char[] chars=new char[4]; //一次读取4个字符(1个字符2个字节)
int readCount=0;
while((readCount=reader.read(chars))!=-1)
{
System.out.print(new String(chars,0,readCount));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
Reader/Writer复制普通文本
package com.jmpower.javase.io;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Copy02 {
public static void main(String[] args) {
FileReader in=null;
FileWriter out=null;
try {
//读
in=new FileReader("src/com/jmpower/javase/io/FileInputStreamTest02.java");
//写
out=new FileWriter("reader");
//一边读一边写
char[] chars=new char[1024*512];//1MB
int readCount=0;
while((readCount=in.read(chars))!=-1)
{
out.write(new String(chars,0,readCount));
}
//刷新
out.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//关闭流
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}