IO流常用类(字节流,FileInputStream,FileOutputStream)
IO流(IO流概述及其分类)
IO流概述:
IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO
包中
IO流分类:
- 按照数据流向:
-
输入流 读入数据
-
输出流 写出数据
- 按照数据类型:
-
字节流 可以读写任何类型的文件 比如音频 视频 文本文件
-
字符流 只能读写文本文件
什么情况下使用哪种流呢?
-
如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。
-
如果不知道文件中存储的是什么内容,就用字节流
IO流中的继承关系
上图中颜色一样的类是对应关系
IO流(IO流基类概述和FileOutputStream
的构造方法)
IO流基类概述:
-
字节流的抽象基类:字节流的命名规律是,末尾会有
Stream
InputStream,OutputStream。
-
字符流的抽象基类:
Reader , Writer。
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream
的子类FileInputStream
。
如:Reader
的子类FileReader
。
OutputStream
是字节输出流,但是是个抽象类,不能实现实例化,所以我们通常都是直接使用该类的子类FileOutputStream
来进行实例化和相关的操作
FileOutputStream
类的构造方法:
-
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的输出文件流,这里的文件名称如果是绝对路径就是指路径所对应的文件;如果是相对路径,就是指项目根目录下的文件 -
FileOutputStream(File file)
创建一个向指定File
对象表示的文件中写入数据的文件输出流,这里需要使用File
类来实现对目标文件的封装 -
FileOutputStream(String name, boolean append)
创建一个向具有指定 name 的文件中写入数据的输出文件流。 -
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
在第2个构造方法中参数是个File
对象,该对象所指向的文件不一定必须存在,在执行语句的时候可以会自己创建这个文件
package org.westos.java;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/**
* @Author: Administrator
* @CreateTime: 2019-05-11 11:48
*/
public class MyTest {
public static void main(String[] args) throws FileNotFoundException {
File file1 = new File("b.txt");
FileOutputStream out = new FileOutputStream(file1);
}
}
- 之前并不存在
b.txt
文件,当我们创建好一个对b.txt
文件封装后的对象file1
后,执行FileOutputStream
类的第2个构造方法后会自动创建b.txt
文件,之后就建立了字节输出流对象和文件的对象,可以实现对文件中的内容的读写 - 第4个构造方法和第二个没多大差别,但是可以实现文件中内容的追加,如果我们在一个文件中写入了一些东西,程序执行结束后再次执行,第二次的执行结果会将上次的执行结果进行覆盖,但是给第二个参数加上布尔值
true
就可以实现内容追加 - 第3个构造方法会把名为参数字符串的文件和字节输出流对象建立联系,使用时要注意相对路径和绝对路径的区别,而且和第2个构造方法一样,文件也不需要存在,如果不存在,在执行该语句时会自动创建
- 第3个构造方法同上,可以实现追加
IO流(FileOutputStream
写出数据)
写入数据的方法发重载:
-
void write(byte[] b)
将b.length
个字节从指定byte
数组写入此文件输出流中。 -
void write(byte[] b, int off, int len)
将指定byte
数组中从偏移量off
开始的len
个字节写入此文件输出流。 -
void write(int b)
将指定字节写入此文件输出流。
void write(int b)
方法的参数虽然是int
型,但是一次只能写入一个字节,超过一个字节会丢弃掉多余字节,文件中就会出现乱码的现象
代码示例:
out.write(97);
out.write(128);
在上述构造方法的代码示例中添加这两条语句,文件中的结果为:
a�
发生了乱码现象,这点必须注意
代码示例2:
out.write(new byte[]{100, 101, 102});
在执行完上述代码后文件中的结果为:
def
可以直接将字节数组中的内容写入到指定文件中
代码示例3:
String str2 = "去玩儿广泛地换个偶记 大风车";
//GBK编码一个汉字占两个字节,UTF-8 编码一个汉字占3个字节,我们这里使用UTF-8编码
out.write(str2.getBytes(), 0, 18);//一次写入字节数组的一部分,从0开始写18个字节
运行结果为:
去玩儿广泛地
它可以将字节数组中指定索引内的内容直接写入到文件中,但是要注意一个汉字所占用的字节数
注意事项:
创建字节输出流对象了做了几件事情?
-
调用系统资源创建
b.txt
文件 -
创建了一个字节输出流对象
-
让字节流对象指向这个文件,来给文件中写内容
-
一定要执行
字节输出流对象.close()
,原因:通知系统释放关于管理b.txt
文件的资源, 让Io流对象变成垃圾,等待垃圾回收器对其回收 -
在上述的讲解中可以看到,
write
方法的参数大部分情况下都是字节数组,所以如果我们要想向文件中写入一些内容,都必须将该内容抓换为字节数组才能写入,包括换行符等一些内容
代码示例4:
package org.westos.java;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("b.txt");
out.write("哈哈哈哈哈哈".getBytes()); 调用String类
out.write("\r\n".getBytes());//写入一个换行符
out.write("呃呃呃呃呃呃呃".getBytes());
out.write("\r\n".getBytes());//写入一个换行符
out.write("咔咔咔咔咔咔扩扩扩扩扩".getBytes());
out.write("\r\n".getBytes());//写入一个换行符
//释放资源
out.close();
}
}
文件中的内容为:
哈哈哈哈哈哈
呃呃呃呃呃呃呃
咔咔咔咔咔咔扩扩扩扩扩
IO流(FileOutputStream
写出数据实现换行和追加写入)
代码示例:
package org.westos.java;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("b.txt", true); //使用追加的构造方法
out.write("哈哈哈哈哈哈".getBytes());
out.write("\r\n".getBytes());//写入一个换行符
out.write("呃呃呃呃呃呃呃".getBytes());
out.write("\r\n".getBytes());//写入一个换行符
out.write("咔咔咔咔咔咔扩扩扩扩扩".getBytes());
out.write("\r\n".getBytes());//写入一个换行符
//释放资源
out.close();
}
}
通过上述代码实现了追加,运行上述程序3次,文件中的结果为:
哈哈哈哈哈哈
呃呃呃呃呃呃呃
咔咔咔咔咔咔扩扩扩扩扩
哈哈哈哈哈哈
呃呃呃呃呃呃呃
咔咔咔咔咔咔扩扩扩扩扩
哈哈哈哈哈哈
呃呃呃呃呃呃呃
咔咔咔咔咔咔扩扩扩扩扩
IO流(FileOutputStream
写出数据加入异常处理)
在上面的所有代码中,我们都是将异常(IOException
)直接进行抛出,或者逐层向上抛,谁调用就抛给谁;但是抛到main
方法最好不要再抛了,需要捕获异常,这里讲解一下捕获数据写入时所发生的异常
代码示例:
package org.westos.java;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("d.txt");
out.write(100);
if (out != null) {
out.close();
}
}
}
这里还是对异常进行了抛出,如何实现异常捕获,使用try-catch-finally
,改进后的代码如下:
package org.westos.java;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) {
//流的异常处理
FileOutputStream out = null;
try {
out = new FileOutputStream("d.txt");
out.write(100);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在这里进行几点简单的说明:
- 为了提高变量的作用域,将
FileOutputStream out = null;
代码放置到try
的外面,否则执行out.close();
语句时会报错 - 在
try
中建立了输出流对象和d.txt
文件的联系后向文件中写入内容,发生异常后会被捕获并打印异常堆栈信息 - 将
out.close();
语句放到finally
中,因为如果在try
中最后执行out.close();
语句,要是在之前发生异常,out.close();
就不会执行,无法正常关闭;放到finally
后不管前面执行如何,out.close();
都会执行,一定会正常关闭 - 如果在
try
中还有其他逻辑,在执行到out = new FileOutputStream("d.txt");
之前就已经发生了异常,此时out
还是null
,如果执行out.close();
就会报错,需要在finally
中写入一个try-catch
,捕获该异常,将out.close();
及其相关语句放置到try
中,捕获异常打印堆栈信息 - 通过以上的代码便实现了对异常的捕获
IO流(FileInputStream
读取数据)
构造方法:
-
FileInputStream(String name)
通过打开一个到实际文件的连接来创建一个FileInputStream
,该文件通过文件系统中的路径名name
指定,注意文件名是绝对路径还是相对路径 -
FileInputStream(File file)
通过打开一个到实际文件的连接来创建一个FileInputStream
,该文件通过文件系统中的File
对象file
指定。
常用方法:
-
void close()
关闭此文件输入流并释放与此流有关的所有系统资源。 -
int read()
从此输入流中读取一个数据字节,如果读取到,就返回该数据字节;否则就返回-1
-
int read(byte[] b)
从此输入流中将最多b.length
个字节的数据读入一个byte
数组中。 -
int read(byte[] b, int off, int len)
从此输入流中将最多len
个字节的数据读入一个byte
数组中。
在项目根目录下的c.txt
文件中存储如下内容:
sdfg
gsdfg
代码示例:
package org.westos.java;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream(new File("c.txt"));
//读取文件中的数据
int len = in.read(); //一次读取一个字节,如果读取不到数据,返回的是 -1 我们要拿 -1 做判断看文件是否读取完了
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
len = in.read(); //一次读取一个字节
System.out.println(len);
//释放资源
in.close();
}
}
程序运行结果为:
115
100
102
103
13
10
103
115
100
102
103
-1
-1
-1
-1
-1
-1
-1
注意:换行符会占用两个字节,所以虽然有9
个字符,但是实际读取了11
个字节
代码示例2:
package org.westos.demo4;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyTest2 {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("c.txt");
//创建一个字节数组,充当容器
byte[] bytes = new byte[1024];
int len = in.read(bytes); //把文件中的数据,读取到容器中,返回值是,读取到的有效字节个数
System.out.println(len);
String s = new String(bytes, 0, len);
System.out.println(s);
in.close();
}
}
可以创建一个长度自定义的字节数组来充当容器存放从目标文件中读取的内容,String s = new String(bytes, 0, len);
可以将字节数组中内容创建为字符串,最后打印,运行结果为:
11
sdfg
gsdfg
其他功能和上面的类似,在这里不再赘述
IO流(字节流复制文本文件)
在学习了字节输入流和字节输出流以后,我们可以完成复制文本文件的操作:
案例演示:字节流一次读写一个字节复制文本文件:
package org.westos.demo5;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFile {
public static void main(String[] args) throws IOException {
//使用文件输入输出流进行文件的复制
//ctrl+alt+空格 手动提示键
//关联源文件
FileInputStream in = new FileInputStream("MyTest2.java");
//关联目标文件
FileOutputStream out = new FileOutputStream("E:\\MyTest2.java");
//复制逻辑:读取一个字节,写入一个字节
//循环的读写
int len=0;
while ((len=in.read())!=-1){
out.write(len);
out.flush();//刷新,一定要有这条语句
}
//释放资源
in.close();
out.close();
}
}
在上述代码中,每次读取一个字节的文件,边读取边写入,可以实现对文件的复制,包括文本文件和音频文件,显然,一个字节一个字节的复制文件速度太慢,我们可以一次多复制一些字节,所以我们可以创建一个缓冲区也就是字节数组来实现快速的复制:
package org.westos.demo6;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFile3 {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("领悟1.mp3");
FileOutputStream out = new FileOutputStream("领悟1234.mp3");
//引入缓冲区的思想
byte[] bytes = new byte[1024*8]; //创建了一个8kB的缓冲区
int len=0;//定义一个变量,记录每次读取到的有效字节个数
//循环的读写操作
while ((len=in.read(bytes))!=-1){
out.write(bytes,0,len);
out.flush(); //刷新
}
//释放资源
in.close();
out.close();
}
}
通过上述操作便可实现快速的复制
IO流(复制文件中的异常处理)
代码示例:
package org.westos.demo8;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyTest2 {
public static void main(String[] args) {
FileInputStream in = null;
FileOutputStream out = null;
try {
//流的异常处理
in = new FileInputStream("领悟1.mp3");
out = new FileOutputStream("领悟2.mp3");
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = in.read(bytes)) != -1) {
out.write(bytes, 0, len);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果在项目根目录下存在文件领悟1.mp3
,要对该文件进行复制,代码实现如上所示,之前的复制文件代码演示都是直接将异常抛出,上述代码实现了对异常的捕获,捕获的流程和IO流(FileOutputStream写出数据加入异常处理)
中讲解的异常捕获流程一样
IO流(BufferedOutputStream
写出数据)
缓冲思想:
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,Java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流
BufferedOutputStream
的构造方法:
-
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 -
BufferedOutputStream(OutputStream out, int size)
创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
IO流(BufferedInputStream
读取数据)
构造方法:
-
BufferedInputStream(InputStream in)
创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 -
BufferedInputStream(InputStream in, int size)
创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
注意事项:在上面的两个构造方法中,参数为InputStream
类,该类是抽象类,不能实例化,所以需要传一个该类的子类即FileInputStream
类的对象,常用方法和FileInputStream
类中的常用方法一致
IO流(字节流BufferedInputStream
和BufferedOutputStream
类复制文件的代码示例)
package org.westos.demo8;
import java.io.*;
public class MyTest {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copy1();
//copy2();
//耗时:
//15214 毫秒
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒"); //耗时:17506毫秒 耗时:17929毫秒
}
private static void copy2() throws IOException {
FileInputStream in = new FileInputStream("E:\\疯狂动物城.mp4");
FileOutputStream out = new FileOutputStream("D:\\疯狂动物城.mp4");
BufferedInputStream bfi = new BufferedInputStream(in);
BufferedOutputStream bfw = new BufferedOutputStream(out);
byte[] bytes = new byte[1024 *1024];
int len = 0;
while ((len = bfi.read(bytes)) != -1) {
bfw.write(bytes, 0, len);
bfw.flush();
}
bfi.close();
bfw.close();
}
private static void copy1() throws IOException {
FileInputStream in = new FileInputStream("E:\\疯狂动物城.mp4");
FileOutputStream out = new FileOutputStream("D:\\疯狂动物城.mp4");
byte[] bytes = new byte[1024 *1024];
int len=0;
while ((len=in.read(bytes))!=-1){
out.write(bytes,0,len);
out.flush();
}
in.close();
out.close();
}
}
在复制较小的文件时,使用BufferedInputStream
和BufferedOutputStream
类来实现文件的读写比较方便,因为有内置缓冲区,速度比较快
但是使用FileInputStream
和FileOutputStream
类时定义一个缓冲数组也可以解决速度慢的问题,而且传参也跟快捷,所以我个人觉得使用后者更方便些
下一篇: Android网络数据开关用法简单示例