IO流(上)
传送门:IO流(下)
通过IO可以完成硬盘文件的读写,java中所有的流都在:java.io.*下
1. 分类
-
按流的方向分
以内存作为参照物
- 输入流:往内存中去,叫做输入(Input),或者叫做读(Read)
- 输出流:从内存中出来,叫做输出(Output),或者叫做写(Write)
-
按照读取数据的方式分
-
字节流
按照字节的方式读取数据,一次读取一个字节byte,等同于一次读取8个二级制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件、图片、声音文件、视频等
假设:文件file.txt (a中国bczd)采用字节流的话是这样读的
第一次读:一个字节,正好读到‘a’
第二次读:一个字节,读到’中‘的一半
第三次读:一个字节,读到’中‘的另一半
-
字符流
按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取图片、声音、视频等文件。只能读取纯文本文件,word文件也读取不了
假设:文件file.txt (a中国bczd)采用字符流的话是这样读的
第一次读:‘a’字符(’a‘字符再windows系统中占用1个字节)
第二次读:’中‘字符(’中‘字符再windows系统中占用2个字节)
注:java中只要”类名“以Stream结尾的都是字节流,以”Reader/Writer"结尾的都是字符流
-
2. 四大家族
以下都是抽象类:
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
所有的流都实现:
java.io.Closeable接口,都是可关闭的,都有close()方法。流毕竟一个管道,是内存和硬盘之间的通道,用完一定要关闭,不然会耗费很多资源。
所有的输出流都实现:
java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余为输 出的数据强行输出完(清空管道)。如果没有flush()可能会导致丢失数据
3. 常用流
- 文件专属:
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
- 转换流(将字节流转换成字符流):
- java.io.InputStreamReader
- java.io.OutputStreamWriter
- 缓冲流专属:
- 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
4. FileInputStream
文件字节输入流,任何类型的文件都可以采用这个流来读。(字节的方式,完成读操作 硬盘—>内存)
示例: test.txt内容:abcdef
public class Demo {
public static void main(String[] args){
FileInputStream fis= null;
try {
//创建文件字节输入流对象
//文件路径G:\.截图\test.txt IDEA会自动把\转换成\\,因为java中\表示转移
//fis = new FileInputStream("G:/.截图/test.txt"); 这样也可
fis = new FileInputStream("G:\\.截图\\test.txt");
int readData = 0;
while((readData = fis.read()) != -1){ //读到文件末尾read()方法会返回-1 readData为字节本身大小
System.out.println(readData);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//在finally语句中要确保流一定关闭
if(fis != null){ //避免空指针异常
try {
//关闭流的前提:流不是空,流是null的时候没必要关闭
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注:如果使用相对路径,IDEA中默认的当前路径为工程project的根
以上程序有一个缺点:一次读取一个字节byte,这样内存和硬盘交互太频繁。可以更改为一次读取多个字节
int read(byte[] b) //一次读取b.length个字节
更改代码:
public class Demo {
public static void main(String[] args){
FileInputStream fis= null;
try {
//创建文件字节输入流对象
fis = new FileInputStream("G:\\.截图\\test.txt");
//准备一个4个长度的byte数组,一次最多读取4个字节
byte[] bytes = new byte[4];
//这个方法的返回值是:读取到的字节数量(不是字节本身,与上面的代码做区分)
int readCount = fis.read(bytes);
System.out.println(readCount);//4
System.out.println(new String(bytes)); //abcd
readCount = fis.read(bytes);//第二次只能读到两个字节
System.out.println(readCount);//2
System.out.println(new String(bytes)); //efcd
//ef把ab覆盖,但是cd还在,所以会输出efcd
readCount = fis.read(bytes); // 一个字节都没有返回-1
System.out.println(readCount);// -1
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
可以看到,上面的代码每次都把整个数组都转换成String字符串,这样可能会造成没被覆盖的依然会被再读一次。而我们要做的是读取了多少个字节,就转换多少个字节。
再次更改代码:
public class Demo {
public static void main(String[] args){
FileInputStream fis= null;
try {
//创建文件字节输入流对象
fis = new FileInputStream("G:\\.截图\\test.txt");
//准备一个4个长度的byte数组,一次最多读取4个字节
byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1){
//构造方法,读取0到readCount长度
System.out.print(new String(bytes,0,readCount)); //abcdef
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
-
FileInputStream类的其他常用方法
-
int available():返回流当中剩余的没有读到的字节数量
-
long skip(long n):跳过几个字节不读
示例:
public class Demo { public static void main(String[] args){ FileInputStream fis= null; try { //创建文件字节输入流对象 fis = new FileInputStream("G:\\.截图\\test.txt"); //直接定义一个准确的数组长度 不需要再循环 该方法不适用于大文件 byte[] bytes = new byte[fis.available()]; int readCount = fis.read(bytes); System.out.println(new String(bytes)); //abcdef } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
//创建文件字节输入流对象 fis = new FileInputStream("G:\\.截图\\test.txt"); //直接定义一个准确的数组长度 不需要再循环 该方法不适用于大文件 fis.skip(3); //跳过三个字节不读 即不读abc 直接读d System.out.println(fis.read()); //100
-
5. FileOutputStream
文件输出流,负责写(内存 —> 硬盘)
示例:
public class Demo {
public static void main(String[] args){
FileOutputStream fos = null;
try {
//创建流
fos = new FileOutputStream("test");
byte[] bytes = {97, 98,99,100};
//将byte数组全部写出
fos.write(bytes); //abcd
fos.write(bytes,1,2); //abcdbc 第三个参数为长度
//字符串
String s = "测试";
//将字符串转换成byte数组
byte[] by = s.getBytes();
fos.write(by);
//写完之后要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注:
- 使用 FileOutputStream fos = new FileOutputStream(“text”);构造方法是会覆盖原文件的。如不想覆盖原文件,在文件末尾追加的话可以采用FileOutputStream fos = new FileOutputStream(“text”,true);
- 在写完之后,一定要flush
- 如果FileOutputStream传入的路径文件不存在,会新建
6. 使用以上两个流完成文件的拷贝
拷贝是需要经过内存的,即流从一个盘读进内存,再从内存写出到另一个盘,拷贝的过程是一边读,一边写的过程。
使用以上两个字节流拷贝文件的时候,文件类型可以任意。
public class Demo {
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建一个输入流对象
fis = new FileInputStream("test"); //相对路径
//创建一个输出流对象
fos = new FileOutputStream("testOutput");
//一边读一边写
byte[] bytes = new byte[1024 * 1024]; //一次读1MB
int readCount = 0;
while((readCount = fis.read(bytes)) != -1){
fos.write(bytes,0,readCount);
}
//写完要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流try catch最好分开写,如果写在一起,可能会互相影响
if(fos != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
7. FileReader
文件字符输入流,只能读取普通文本。
public class Demo {
public static void main(String[] args){
FileReader fr = null;
try {
fr = new FileReader("test.txt");
//与字节流有所区分,这里使用char数组
char[] chars = new char[4]; //一次读取4个字符
int readCount = 0;
while((readCount = fr.read(chars)) != -1){
System.out.println(new String(chars,0,readCount)); //4个一输出
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
8. FileWriter
文件字符输出流
public class Demo {
public static void main(String[] args){
FileWriter fw = null;
try {
//创建文件字符输出流对象
fw = new FileWriter("testWriter");
char[] chars = {'这','是','测','试'};
fw.write(chars);
fw.write("\n学习Java"); //也可以直接写字符串 \n换行符
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
9. 使用以上两个流完成文件拷贝
public class Demo {
public static void main(String[] args){
FileReader fr = null;
FileWriter fw = null;
try {
//创建字符流对象
fr = new FileReader("test.txt");
fw = new FileWriter("testCopy");
char[] chars = new char[4];
int readCount = 0;
while((readCount = fr.read(chars))!=-1){
fw.write(chars,0,readCount);
}
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}