按照输入/输出分类
把硬盘中的数据读入内存,叫输入流
把内存中的数据写入硬盘,叫输出流
按照字节/字符分类
IO流还可以分为字节流和字符流
所以两种分类结合,就可以分为这个四类,这几个类都是IO流的顶层父类
一切皆为字节,计算机上的文件、图片、视频,都是以字节的形式存储的。传输时,字节流可以传输任意类型的文件。
java.io.OutputStream是一个抽象类,是所有字节输出流的超类。
这个类定义了子类共性的成员方法。
成员方法
方法1.public void close()
关闭字节输出流,并释放与此流相关的任何系统资源。
方法2.public void flush()
刷新字节输出流,并强制缓冲中的字节被写出。
方法3.public void write(byte[] b)
将字节数组b,写入此输出流
byte[]是字节数组,byte是字节
1个字节=8个比特位(例如10100111)
方法4.public void write(byte[] b, int off, int len)
将字节数组b,从off开始的,len长度的字节,写入此输出流。
方法5.public abstract void write(int b)
将指定的字节写入此输出流
1.继承关系: java.io.FileOutputStream extends OutputStream
2.功能: FileOutputStream把内存中的数据,写入到硬盘的文件中。
3.把数据写入文件的原理:
java程序 —> JVM —> OS操作系统 —> OS调用写数据的方法 —> 把数据写入文件中
构造方法1.FileOutputStream(String name)
String name是写入数据的文件路径
构造方法2.FileOutputStream(File file)
File file是写入数据的文件
以上两个构造方法的作用:
1.创建一个FIleOutputStream对象;
2.根据构造方法中的文件/文件路径,创造一个空的文件;
3.把FileOutputStream对象指向文件
构造方法3.FileOutputStream(String name, boolean append)
String name是写入数据的文件路径
构造方法4.FileOutputStream(File file, boolean append)
File file是写入数据的文件
以上两个构造方法的boolean append表示追加写
1.true创建FileOutputStream对象时,不会覆盖原文件,继续在原文件末尾追加写;
2.false创建FileOutputStream对象时,会创建一个新文件,覆盖原文件,不会在原文件追加写;
FileOutputStream的使用步骤
1.创建一个FileOutputStream对象,构造方法的参数是写入数据的目的地;
2.调用FileOutputStream对象的write方法,把数据写入到文件中;
3.释放系统资源(流使用会占用内存,使用完毕要清内存)。
举例1
使用FileOutputStream的public abstract void write(int b)方法
public static void mian(String[] args) throw IOException {
// step1.创建一个FileOutputStream对象,构造方法的参数是写入数据的目的地
FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\a.txt");
// step2.调用FileOutputStream对象的write方法,把数据写入到文件中;
// 这里用public abstract void write(int b),将指定的字节写入此输出流
fos.write(97);
// step3.释放系统资源
fos.close();
}
注意:
使用FileOutputStream的public void write(byte[] b)方法或者public void write(byte[] b, int off, int len)方法,可以一次输出多个字节到文件
举例1
使用public void write(byte[] b)方法
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b)
byte[] bytes = new byte[]{65, 66, 67, 68, 69};
fos.write(bytes);
fos.close();
}
此时,b.txt打开显示的是ABCDE,因为bytes里的值在0-127之间会查询ASCII表。
举例2
public void write(byte[] b)方法
改变bytes的值,里面有不在0-127范围的值
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b)
byte[] bytes = new byte[]{-65, 66, -67, 68, 69};
fos.write(bytes);
fos.close();
}
有不在0-127范围内的,此时会查询系统的默认编码表,按照编码表的规则将字节转换为字符。
举例3
使用public void write(byte[] b, int off, int len)方法
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
// 这里用public void write(byte[] b, int off, int len)
byte[] bytes = new byte[]{65, 66, 67, 68, 69};
fos.write(bytes,1,2);
fos.close();
}
此时打开b.txt,只有BC
举例4
使用String的getBytes()方法获取字节数组
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream(new File("09_IOAndProperties\\b.txt"));
byte[] bytes = "您好".getBytes();
System.out.println(bytes); // [-28, -67, -96, -27, -91, -67] 此时两个字符打印出来有6个字节,是因为IDEA里的编码是UTF-8,一个中文字符对应3个字节,而windows里的默认编码是GBK,一个中文字符对应2个字节。
fos.write(bytes);
fos.close();
}
此时b.txt可以看到"您好"
举例
在FileOutputStream的构造方法里,把boolean append设置为true
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\b.txt", true);
byte[] bytes = "您好".getBytes();
fos.write(bytes);
fos.close();
}
执行第一次,b.txt里面是“您好”。执行第二次,b.txt里面是“您好您好”。
不同操作系统的换行符不同,windows中是\r\n,linux中是/n,mac中是/r
public static void mian(String[] args) throw IOException {
FileOutputStream fos = new FileOutputStream("09_IOAndProperties\\b.txt", true);
for(int i=0; i < 5; i++) {
fos.write("您好".getBytes());
fos.write("\r\n".getBytes());
}
fos.close();
}
此时,b.txt中会出现换行的“您好”
InputStream是抽象类,是所有字节输入流的超类。
这个类定义了子类共性的成员方法
成员方法
方法1.public int read(),从输入流中,读取数据的下一个字节
方法2.public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中
方法3.public void close(), 关闭输入流,并释放相关系统资源
java.io.FileInputStream继承了InputStream类
作用:FileInputStream把硬盘文件中的数据,读取到内存中使用
构造方法1. FileInputStream(String name)
参数:String name读取的文件路径
构造方法2. FileInputStream(File file)
参数:File file读取的文件
构造方法的作用
读取的原理
硬盘–>内存:
java程序 --> JVM --> OS --> 调用OS的读取方法 --> 读取文件
FIleInputStream的使用步骤:
1.创建FileInputStream对象,构造方法中传要读取的文件
2.使用FileInputStream对象的read方法,读取文件
3.释放资源
此时a.txt里的内容是abc
public static void main(String[] args) throws IOException{
// 1.创建FileInputStream对象,构造方法中传要读取的文件
FileInputStream fis = new FileInputStream("09_IOAndProperties\\a.txt");
// 2.使用FileInputStream对象的read方法,读取文件
// 这里使用int read()读取文件中的一个字节并返回,读取到文件的末尾返回-1
int res = fis.read();
System.out.println(res); // 打印出来是97,也就是a
res = fis.read();
System.out.println(res); // 打印出来是98,也就是b
res = fis.read();
System.out.println(res); // 打印出来是99,也就是c
res = fis.read();
System.out.println(res); // 打印出来是-1
// 3.释放资源
fis.close();
}
以上有重复的步骤,使用while循环读,结束条件是读取到-1
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("09_IOAndProperties\\a.txt");
int res = 0; // 记录读取到的字节
while((res=fis.read()) != -1) {
System.out.println(res);
}
fis.close();
}
打印出来是97 98 99
注意
2.写成这样就是错的,因为fis.read()每读一次,指针会往后移动
while(fis.read() != -1) {
System.out.println(fis.read());
}
b.txt里的是ABCDE
public static void main(String[] args) throws IOException{
// 1.创建FileInputStream对象,构造方法中传要读取的文件
FileInputStream fis = new FileInputStream("09_IOAndProperties\\b.txt");
// 2.使用FileInputStream对象的read方法,读取文件
// 这里使用public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中。
byte[] bytes = new byte[2];
int len = fis.read(bytes);
System.out.println(len); // 打印的是2
System.out.println(Arrays.toString(bytes)); // 打印的是[65,66]
System.out.println(new String(bytes)); // 打印的是AB
len = fis.read(bytes);
System.out.println(len); // 打印的是2
System.out.println(new String(bytes)); // 打印的是CD
len = fis.read(bytes);
System.out.println(len); // 打印的是1
System.out.println(new String(bytes)); // 打印的是ED
len = fis.read(bytes);
System.out.println(len); // 打印的是-1
System.out.println(new String(bytes)); // 打印的是ED
// 3.释放资源
fis.close();
}
注意:
1.String类的构造方法里有:
2.要注意public int read(byte[] b)方法里的int是啥?byte[]是啥?
3.读取的原理
以上代码可以使用while来优化,结束条件读取到-1
此时打印出来是ABCDE
public static void main(String[] args) throws IOException{
// 1.创建FileInputStream对象,构造方法中传要读取的文件
FileInputStream fis = new FileInputStream("09_IOAndProperties\\b.txt");
// 2.使用FileInputStream对象的read方法,读取文件
// 这里使用public int read(byte[] b),从输入流中,读取一定数量的字节,并存储在缓冲区的字节数组b中。
byte[] bytes = new byte[2];
int len = 0; // 记录每次读取的有效字节个数
while((len = fis.reas(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
// 3.释放资源
fis.close();
}
将C盘中的1.jpg图片,复制到D盘。
步骤:
public static void main(String[] args) throws IOException{
// 1.创建一个字节输入流对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
// 2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
// 3.使用字节输入流对象的read方法读取文件
byte[] bytes = new bytes[1024];
int len = 0;
while((len = fis.read(bytes)) != -1) {
// 4.使用字节输出流的write方法,把读取到的字节写入到目的文件中
fos.write(bytes, 0, len);
}
// 5. 释放资源, 先关闭写的流对象,再关闭读的流对象。因为如果写完了,肯定已经读完了。
fos.close();
fis.close();
}
使用字节流读取中文时会遇到问题,因为GBK中,1个中文占用2个字节,UTF-8中,一个中午占用3个字节。
举例
c.txt的内容是"你好",编码格式是UTF-8,也就是"你"占用3个字节,"好"占用3个字节。此时打印出来的是6个数字,因为每次就读取1/3个中文,如果用char强转,打印出来是乱码。但是用字节流读取英文字符是没有问题的。
为了解决这个问题,Java提供了字符流,字符流一次读写一个字符,不管字符是中文、英文还是数字。
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("c.txt");
int len = 0;
while((len = fis.read()) != -1) {
System.out.println(len); // 打印出来的是数字
System.out.println((char)len); // 打印出来的是乱码
}
fis.close();
}
构造方法1.FileReader(String fileName)
参数String fileName是要读取的文件路径名
构造方法2.FileReader(File file)
参数File file是要读取的文件
FileReader的使用步骤
举例
c.txt的内容是"您好abc123"
public static void main(String[] args) throws IOException{
// 1.创建FileReader对象,构造方法中传要读取的数据源
FileReader fr = new FileReader("src\\c.txt");
// 2.使用FileReader对象的read()方法读取文件
// 使用int read()读取单个字符并返回
int len = 0;
while((len = fr.read()) != -1) {
System.out.println(len); // 此时打印的是数字,因为len就是int型,要转回字符
System.out.print(len); // 此时打印的是: 您好123abc###
}
fis.close();
}
c.txt的内容是"您好abc123"
public static void main(String[] args) throws IOException{
// 1.创建FileReader对象,构造方法中传要读取的数据源
FileReader fr = new FileReader("src\\c.txt");
// 2.使用FileReader对象的read()方法读取文件
// 使用int read(char[] cbuf)一次读取多个字符,并将字符存在数组char[] cbuf中,返回有效读取的字符个数
int len = 0; // 每次读取到的有效字符个数
char[] cs = new char[1024];
while((len = fr.read(cs)) != -1) {
System.out.printlnl(new String(cs, offset, count)); //打印出来是:您好abc123###
}
fis.close();
}
构造方法1 FileWriter(String fileName)
参数String fileName是要写入数据的文件路径名
构造方法2 FileWriter(File file)
参数File file是要写入数据的文件
构造方法3 FileWriter(String fileName, boolean apend)
参数String fileName是要写入数据的文件路径名
构造方法4 FileWriter(File file, boolean append)
参数File file是要写入数据的文件
以上两个构造函数中的boolean append:
使用步骤
字符输出流与字节输出流的最大区别是,字符输出流不是直接把数据写入到文件中,而是写入到内存缓冲区中。字节输出流是直接把数据写入到硬盘的文件中。
此时,c.txt里显示a
public static void main(String[] args) throws IOException{
// 1.创建FileWriter对象,构造方法中绑定要写入数据的目的地
FileWriter fw = new FileWriter("src\\c.txt");
// 2.使用FileWriter对象的write方法,把数据写入内存缓冲区中(这里有个字符转换成字节的过程)
// 使用void write(int c) 写入单个字符
fw.write(97); // 如果没有后续的flush或者close操作,数据不会被写入到c.txt中。此时数据还在内存中,停止程序,数据会消失。
// 3.使用FileWriter对象的flush方法,把内存缓冲区中的数据,刷新到文件中
fw.flush();
// 4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
fw.close();
}
+ flush:刷新缓冲区,流对象可以继续使用
+ close:先刷新缓冲区,然后通知系统释放资源,流对象不可以继续使用
public static void main(String[] args) throws IOException{
FileWriter fw = new FileWriter("src\\d.txt");
fw.write(97);
fw.flush();
// 刷新后,流对象可以继续使用
fw.write(98);
fw.flush();
fw.close();
// close方法后,流对象已经关闭,从内存中消失了,不能再使用
fw.write(99); // 这里会报IOException,告诉Stream Closed
}
d.txt里只有ab,没有c
public static void main(String[] args) throws IOException{
FileWriter fw = new FileWriter("src\\e.txt");
char[] charArr = {'a', 'b', 'c', 'd', 'e'};
// 1.void write(char[] cbuf)写入字符数组
fw.write(charArr); // abcde
// 2.abstract void write(char[] cbuf, int off, int len)写入字符数组的一部分,从off开始,写len个字符
fw.write(charArr, 1, 3); // bcd
// 3.void write(String str) 写入字符串
fw.write("我是程序员"); // 我是程序员
// 4.void write(String str, int off, int len) 写入字符串的一部分,从off开始,写入len个字符
fw.write("我是程序员", 2, 3); //程序员
fw.close();
}
public static void main(String[] args) throws IOException{
// 构造方法里的boolean append设置为true,追加写
FileWriter fw = new FileWriter("src\\f.txt", true);
for(int i=0; i < 10; i++) {
fw.write("helloworld");
}
fw.close();
}
运行一次,f.txt文件内容是10次不带换行的helloworld
运行2次,f.txt文件内容是20次不带换行的helloworld
换行符号
public static void main(String[] args) throws IOException{
// 构造方法里的boolean append设置为true,追加写
FileWriter fw = new FileWriter("src\\f.txt", true);
for(int i=0; i < 10; i++) {
fw.write("helloworld" + "\r\n");
}
fw.close();
}
运行一次,f.txt文件里的是10个带换行的helloworld
运行2次,f.txt文件里的是20个带换行的helloworld,说明是追加写的
public static void main(String[] args) {
// 提高fw变量的作用域,让finally里可以使用fw
FileWriter fw = null;
try{
// 可能会产生异常的代码
fw = new FileWriter("src\\c.txt", true);
for(int i=0; i < 10; i++) {
fw.write("helloworld" + "\r\n");
}
// fw.close(); 关闭资源的代码放在这里有问题,因为一旦上面的代码执行有异常,这里就不会执行
} catch (IOException e) {
// 异常的处理
System.out.println(e);
} finally {
// 一定要执行的代码,比如释放资源
// 如果最上面的fw创建的时候,没有创建成功,fw是null,下面的fw.close()还会抛出空指针异常,所以要先if判断下
if(fw != null) {
try {
// fw.close也会抛出IOException异常,所以要在这里try catch
fw.close()
} catch(IOException e) {
Syste.out.println(e);
}
}
}
}
以上的实现非常的复杂
JDK7的新特性
在try的后面可以增加一个(),这个括号中可以定义流对象,流对象的作用域是try中有效,try中代码执行完毕,会自动把流对象释放,不用写finally的部分来释放流对象了。
格式
try(定义流对象1;定义流对象2…){
可能产生异常的代码
}catch(异常类 变量名){
异常的处理逻辑
}
以复制图片为例
public static void main(String[] args) {
// 在try的()里定义流对象
try(
FileInputStream fis = new FileInputStream(c:\\1.jpg);
FileOutputStream fos = new FileOutputStream(d:\\2.jpg);
){
// 可能会产生异常的代码
int len = 0;
while((len = fis.read()) != -1) {
fos.write(len);
}
} catch (IOException e) {
// 异常的处理
System.out.println(e);
}
}
之前的字节流和字符流都是IO流的入门,还有几种强大的流
缓冲流有4种
缓冲流的原理:创建流对象时,会创建一个内置的缓冲区数组,通过缓冲区读写,减少系统的IO次数,从而提高读写的效率。
构造方法1. BufferedOutputStream(OutputStream out)
构造方法2.BufferedOutputStream(OutputStream out, int size)
以上两个构造方法的参数:
public static void main(String[] args) throws IOException{
// 1.创建一个FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos = new FileOutputStream("src\\a.txt");
// 2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FIleOutputStream的效率
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 3.使用BufferedOutputStream对象的write方法,把数据写入到缓冲区中
bos.write("写入数据到缓冲区".getBytes());
// 4.使用BufferedOutputStream对象的flush方法,把缓冲区中的数据,刷新到文件中
bos.flush();
// 5.释放资源(会先自动调用flush方法刷新数据,所以第4步可以省略)
bos.close();
}
以上两个方法的参数:
使用步骤
此时a.txt内容是abcde,打印出来的是abced
public static void main(String[] args) throws IOException{
// 1.创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("src\\a.txt");
// 2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
BufferedInputStream bis = new BufferedInputStream(fis);
// 3.使用BufferedInputStream对象的read方法,读取文件中的内容
byte[] arr = new byte[1024]; // 存储每次读取到的数据
int len = 0; //每次读取的有效字节个数
while((len = bis.read(arr)) != -1){
System.out.println(new String(bytes, 0, len));
}
// 5. 释放资源(关闭缓冲流就行了,不用手动关闭基本的字节流,因为关闭缓冲流的时候,自动地关闭字节流)
bis.close();
}