IO流
一、IO产生的原因
1、在操作系统中,一切数据都是以文件的形式存储。
2、需要将文件长久存储在外部设备。
3、程序运行时,所有的数据都需要在内存中,而内存的大小有限,因此常常需要在内存和外设之间交换数据,即IO。
4、在Java语言中,主要通过输入流和输出流完成I/O功能,实现和外设之间的数据交互。
二、IO流
1、IO流用来处理JVM和外部设备之间的数据传输,Java通过流(Stream)的方式,完成数据的传输过程。
2、IO流的分类:
(1)按数据流动的方向:
a、输入流:对应数据的读入,将外设数据读入JVM内存
b、输出流:对于数据的写出,将数据写入到外设
(2)、按流中的内容():
a、字节流:流中的数据都是以字节为单位的二进制数据。字节流可以操作一切类型的数据,只是有时操作字符数据不太方便,所有字符数据专门交给字符流。
b、字符流:流中的数据都是以字符为单位的二进制数据。文本编辑器可以打开,并且人可以看懂的数据。
3、IO流的基类:
(1)、字节流:
a、输入流:InputStream (抽象类)
b、输出流:OutputStream (抽象类)
(2)、字符流:
a、输入流:Reader(抽象类)
b、输出流:Writer(抽象类)
(3)、由这四个类派生出来的子类名字都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream;Reader的子类FileReader。
4、字节流(重要):
(1)输出流(OutputStream):
a、实例化:OutputStream是抽象类,无法直接实例化,只能间接实例化,因为通常操作的都是文件数据,所以使用其操作文件的具体子类FileOutputStream实例化
FileOutputStream的构造方法:
(1)、FileOutputStream(File file)
(2)、FileOutputStream(String name)
b、使用,即向外设写入数据:
// write()的三个重载方法
public void write(int b)
public void write(byte[] b)
public void write(byte[] b, int off, int len)
以下分别实现三个重载方法:
// void write(int b)
// 将指定的字节写入此输出流。
// write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
package com.io.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/26 20:25
* void write(int b)方法
*/
public class WriteDemo1 {
public static void main(String[] args) throws IOException {
// 创建File对象
File file = new File("E:\TestDir\a.txt");
// 1、利用OutputStream的子类FileOutputStream创建输出流对象
OutputStream out = new FileOutputStream(file);
// 2、向目标目录写入数据(写入字符串"Hello,World")
byte[] bytes = "Hello,World".getBytes();
for (int i = 0; i < bytes.length; i++) {
// 逐个读入字符
out.write(bytes[i]);
}
// 3、关闭输出流对象,并释放资源
out.close();
}
}
// void write(byte[] b)
// 将 b.length 个字节从指定的 byte 数组写入此输出流。
package com.io.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/26 20:48
* 实现 void write(byte[] b)
*/
public class WriteDemo2 {
public static void main(String[] args) throws IOException {
// 创建File对象
File file = new File("E:\TestDir\b.txt");
// 1、创建输出流对象
OutputStream out = new FileOutputStream(file);
// 2、将数据写入目标目录(向b.txt中写入
//"zhangsan
// lisi
// wangwu")
byte[] bytes = "zhangsan
lisi
wangwu
".getBytes();
out.write(bytes);
// 关闭输出流对象,并释放资源
out.close();
}
}
// void write(byte[] b, int off, int len)
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
// 参数:
// b - 数据。
// off - 数据中的初始偏移量。
// len - 要写入的字节数。
package com.io.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/26 20:57
*
* void write(byte[] b, int off, int len)
*/
public class WriteDemo3 {
public static void main(String[] args) throws IOException {
// 创建File对象
File file = new File("E:\TestDir\c.txt");
// 1、创建输出流对象
OutputStream out = new FileOutputStream(file);
// 2、向目标目录写出数据(向b.txt写出“”)
byte[] bytes = ("我是个俗气至顶的人,
" +
"见山是山,
" +
"见海是海,
" +
"见花便是花。
" +
"唯独见了你,
" +
"云海开始翻涌,
" +
"江潮开始...
").getBytes();
out.write(bytes, 0, bytes.length);
// 3、关闭输出流,并释放资源
out.close();
}
}
c、字节流写数据常见的问题:
创建字节输出流到底做了哪些事情?
1)、FileOutputStream会先在操作系统中找到目标文件:若目标文件不存在,FileOutputStream类会创建这个文件;若文件存在,则不再创建,清空文件内容,准备从文件最开始的地方写入
2)、在内存中,创建FileOutputStream对象
3)、在FileOutputStream和目录之间建立数据传送通道
数据写成功后,为什么要close()?
关闭次输出流并释放与此流有关的所有系统资源
如何实现速记的换行?(利用换行符)
windows:' ' 或 ’ ‘
linux、mac OS:’ ‘
如何实现数据的追加写入?
1)FileOutputStream(File file, boolean append)
2)创建一个指定File对象表示的文件中写入数据的文件输出流。如果第二个参数为true,则将字节写入文件末尾处(而不是写入文件开始处)
// 实现数据的追加写入
// FileOutputStream(File file, boolean append)
// 创建一个向具有指定 File 的文件中写入数据的输出文件流。
// 如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
package com.cskaoyan.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/26 23:27
实现数据的追加写入:
FileOutputStream(String name, boolean append)
创建一个向具有指定 name 的文件中写入数据的输出文件流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
*/
public class AppendDemo {
public static void main(String[] args) throws IOException {
// 创建File对象
File file = new File("E:\TestDir\d.txt");
// 1、创建输出流对象
OutputStream out = new FileOutputStream(file, true);
// 2、向目标目录文件追加写入数据
byte[] bytes = ("我们聊天的时候,有的人说“再见”,
" +
"后面是一个波浪号,是轻盈的,
" +
"这种是就算不约定几月几号也...
").getBytes();
out.write(bytes);
// 追加写入
byte[] bytes1 = ("我们年轻的时候,总是把创作的冲动误以为是创作的才华;
" +
"总是把对孤独的恐惧误以为是对爱情的向往。").getBytes();
out.write(bytes1);
// 3、关闭输出流,并释放资源
out.close();
}
}
(2)、输出流(InputStream):
a、InputStream是抽象类,无法直接实例化,只能间接实例化,因为通常操作的都是文件数据,所以使用其操作文件的具体子类FileInputStream实例化。
b、FileInputStream的构造方法:
(1)、FileInputStream(File file)
(2)、FileInputStream(String name)
FileInputStream的构造方法
// 创建一个读取指定文件(目标文件)数据的,文件字节输入流对象(目标文件以File对象的形式表示)
FileInputStream(File file)
// 创建一个读取指定文件(目标文件)数据的,文件字节输入流对象(目标文件以File对象的形式表示)
FileInputStream(String name)
c、使用,即从外设读入数据:
// read()的两个重载方法
public int read()
public int read(byte[] b)
以下分别实现read()的两个重载方法:
// int read()
package com.cskaoyan.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/26 23:53
* int read()
* 从输入流中读取数据的下一个 字节。
* 返回值:
* 1.下一个数据字节;
* 2.如果到达流的末尾(没有数据可读),则返回 -1。
*/
public class ReadDemo1 {
public static void main(String[] args) throws IOException {
// 创建File对象
File file = new File("E:\testDir\c.txt");
// 1、创建输入流对象
InputStream in = new FileInputStream(file);
// 2、int read() 逐个字符读入
int readByte;
while ((readByte = in.read()) != -1) {
char c = (char) readByte;
System.out.print(c);
}
// 3、关闭输入流对象,并释放资源
in.close();
}
}
// int read(byte[] b)
package com.cskaoyan.review;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/27 0:10
* int read(byte[] b)
* 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
* 返回值:
* 1. 读入缓冲区的总字节数;
* 2. 如果因为已经到达流末尾而不再有数据可用,则返回 -1。
*/
public class ReadDemo2 {
public static void main(String[] args) throws IOException {
// 1. 创建流对象
InputStream input = new FileInputStream("E:\TestDir\a.txt");
//2. 从目标文件读取数据
// int read(byte[] b)
// 实际开发中,通常给1024的整数倍 2^10
byte[] buffer = new byte[1024];
// len 就告诉我们实际向字节数组中填充了多少个字节数据(实际读取到的字节个数)
int len = input.read(buffer);
String s = new String(buffer,0, len);
System.out.println(s);
// 3. 关闭流并释放资源
input.close();
}
}
d、一次读入读入或写出一个字节效率高,还是一次读入或写出一个字节数组效率高
一个字节数组效率高?
==》一个字节数组效率高,原因如下:
每一次,读入或写出,即每次和外设的数据交互都需要依赖操作系统内核实现,这意味着每次读入或写出,都需要付出额外的通信代价。一次读入或写出一个字节数组的数据,平均到每个字节,付出的额外代价少很多
练习:把d:a.txt内容复制到e:.txt中,把e:**.jpg内容复制到当前项目目录下的mn.jpg
package com.cskaoyan.review;
import java.io.*;
/**
* @author: Lucas
* @version: 1.0
* @date: 2020/6/27 0:19
把d:\a.txt内容复制到e:\b.txt中
把e:\**.jpg内容复制到当前项目目录下的mn.jpg
*/
public class Exercise {
public static void main(String[] args){
// 复制文本文件
//copyFile("e:\a.txt", "d:\b.txt");
// 复制图片文件
//copyFile("e:\copy\upload.jpg", "copy.jpg");
// 演示一次复制一个字节数据
String srcFilePath = "d:\a.txt";
String destFilePath = "e:\b.txt";
copyByByte(srcFilePath, destFilePath);
}
private static void copyByByte(String srcFilePath, String destFilePath) {
InputStream input = null;
OutputStream output = null;
try {
// 创建输入流对象,用来读取源文件内容
input = new FileInputStream(srcFilePath);
// 创建输出流对象, 用来向目标文件写入源文件内容
output = new FileOutputStream(destFilePath);
//读取源文件数据, 以单个字节为单位复制
int readByte;
while ((readByte = input.read()) != -1) {
// 向目标文件写一个字节
output.write(readByte);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流并释放资源
closeQuietly(input);
//关闭输出流
closeQuietly(output);
}
}
private static void copyFile(String srcFilePath, String destFilePath) {
InputStream input = null;
OutputStream output = null;
try {
// 创建输入流对象,用来读取源文件内容
input = new FileInputStream(srcFilePath);
// 创建输出流对象, 用来向目标文件写入源文件内容
output = new FileOutputStream(destFilePath);
//读取源文件数据
byte[] buffer = new byte[2048];
int len;
while ((len = input.read(buffer)) != -1) {
// 将读取到的源文件内容,写入到输出流中
output.write(buffer, 0, len);
}
//close
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流并释放资源
closeQuietly(input);
//关闭输出流
closeQuietly(output);
}
}
private static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
// 多态
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)、缓冲流
a、产生原因:
1)、字节流一次读写一个数组的速度明显比读一个字节的速度快很多
2)、这是加入了数组这样的缓冲区效果
3)、Java本身在设计的时候,也考虑到了这样的情况,所以提供了字节缓冲区流
b、字节缓冲输出流(BufferedOutputStream)
字节缓冲输入流(BufferedInputStream)