1. 基础IO
1.1 理解IO
I/O包括:
- BIO:同步阻塞的IO
- NIO:同步非阻塞IO
- AIO:异步非阻塞IO
1.2 IO读写方式
IO本质就是对数据的读写操作
- 本地文件IO
- 网络IO
1.3 IO的特性
-
本质上属于数据传输
-
涉及两个设备间数据的传输方向,一般来说,只关注自己程序的一方
-
数据流:
- io数据包裹另外一个io数据
- 包裹时,可以设置格式,编码
- IO读取,读取部分数据以后,这部分数据在之前的IO流就消失,也就是读取操作只操作一次
-
java进程的IO操作,对进程的影响:
- 阻塞IO会导致进程在运行态和阻塞态转变,由操作系统完成后续的IO操作
- java进程,在内核态和用户态频繁切换,性能会受到影响(下降)
-
IO 缓冲流 —— 系统缓冲区、java进程的内存
使用 / 不使用缓冲区区别:
例如:循环10000次,每次写1个字节的数据到文件中- 不使用:每次循环都是一次IO操作,涉及到进程状态由运行态转变为阻塞,由用户态转变为内核态(10000次影响)
- 使用:每次循环将数据复制到缓冲区,写完之后,flush才算IO操作,也就是写完之后再一次性写到内存中,性能更好(一次影响)
2. 文件操作
2.1 理解文件
- 文件简单的可以理解成,在外设硬盘上面保存数据的一种方式
- 文件一共可以由两部分构成:属性(文件大小,文件名,文件类型等)+内容(就是文件里面放的是什么)
- 文件操作就是对文件的属性和内容进行操作,而实际写入或者读取的过程,称之为IO。
2.2 File 文件操作类
2.2.1 File类使用
java.io.File:不是对内容的操作,而是对文件本身或头信息的操作
new操作注意事项:
- 即可以指文件,也可以指文件夹
- 通过路径创建File:可以使用绝对路径 / 相对路径
- 不管路径上是否有这个文件 / 文件夹,java中都可以创建得到一个File对象
2.2.2 File类常用API(方法)
2.2.3 File类综合运用
例:打印文件目录及文件列表
File类提供listFiles()方法只能列出本目录中的第一级信息。所以就需要通过递归或遍历的模式来完成。
- 递归思路
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public static List<File> listDir(File file){
List<File> list = new ArrayList<>();
if(file.isDirectory()){
File[] files = file.listFiles();
if(files!=null){
for(File fi:files){
if(fi.isDirectory()){ //如果是子文件夹,递归调用获取
list.addAll(listDir2(fi));
}else{
//如果是文件则加入list中
list.add(fi);
}
// 不管是文件还是目录都加入list中
// list.add(fi);
}
}
}
return list;
}
public static void main(String[] args) {
File file = new File("D:\\Test");
List<File> list = listDir(file);
//jdk1.8 集合框架使用stream操作,可以使用lambda表达式 ,也可以使用for循环遍历
list.stream()
.map(f->{
return f.getName();
}) // 把集合中的元素映射为另外一种类型
.forEach(System.out::println);
}
- 遍历思路(二叉树的层序遍历思路)
public static List<File> listDir(File file){
List<File> list = new ArrayList<>();
//采用队列
Queue<File> queue = new LinkedList<>();
if(file.isFile()){
list.add(file);
return list;
}
queue.add(file);
while(!queue.isEmpty()){
File f = queue.remove();
if(f.isDirectory()){
File[] files = f.listFiles();
for (File fi:files
) {
if(fi.isDirectory()){
queue.add(fi);
}else{
//如果是文件加入list中
//list.add(fi);
}
// 不管是文件还是目录都加入list中
list.add(fi);
}
}
}
return list;
}
//main方法与上面一致
3. 流
3.1 IO流分类
流的作用:为数据源和目的地建立一个输送通道。
3.2 流的概念
在 Java中所有数据都是使用流读写的。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
-
按照流向分:输入流;输出流
- 输入就是将数据从各种输入设备(包括硬盘、键盘等)中读取到内存中。
- 输出则正好相反,是将数据写入到各种输出设备(比如显示器、磁盘等)。
- 输入就是将数据从各种输入设备(包括硬盘、键盘等)中读取到内存中。
-
按照处理数据的单位分:字节流(8位的字节);字符流(16位的字节)
- 字节流:数据流中最小的数据单元是字节。InputStream、OutputStream
- 字符流:java中字符是Unicode编码,一个字符占用两个字节。Reader、Writer
-
按照流的功能分:
- 节点流(低级流):可以从一个特定的IO设备上读/写数据的流。
- 处理流(高级流/过滤流):是对一个已经存在的流的连接和封装,通过所封装的流的功能调用实现数据读/写操作。通常处理流的构造器上都会带有一个其他流的参数。
3.3 字节流
3.3.1 FileInputStream 和 FileOutputStream
说明:
-
FileInputStream(文件输入字节流)
- 从文件系统中的某个文件中获得输入字节。
- 用于读取诸如图像数据之类的原始字节流。
-
FileOutputStream(文件输出字节流)
- 用于将数据写入到输出流File或一个FileDescriptor
代码示例:
使用 FileInputStream 和 FileOutputStream 复制图片:
public static void main(String[] args) throws IOException {
File file = new File("D:\\Test\\20210320.png");
File copy = new File("D:\\Test\\copy.png");// 需要复制的文件格式最好与原图片格式保持一致
if(!copy.exists()){ //如果文件不存在则创建
copy.createNewFile();
}
// 循环读取
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(copy);
byte[] buf = new byte[1024];
int len;
// 循环读取 如果len!=-1 则说明还能读取到内容
while((len=fis.read(buf))!= -1){
fos.write(buf,0,len); //每次写入的长度即为每次读到的长度
}
//关闭
fis.close();
fos.close();
}
3.3.2 BufferedInputStream 和 BufferedOutputStream
说明:
-
BufferedInputStream(字节输入缓冲流)
- 缓冲输入和支持 mark 和 reset 方法的功能。 当创建 BufferedInputStream 时,将创建一个内部缓冲区数组。
- 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark 操作会记住输入流中的一点,并且 reset 操作会导致从最近的 mark 操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
-
BufferedOutputStream(字节缓冲输出流)
- 该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
代码示例:
使用缓冲字节流实现文件拷贝及前后时间对比:
- 不使用缓冲流:
// 可以随便找一个文本文件,这里以test.txt为例,大概2726kb
public static void fileCopy1() throws IOException {
//不使用缓冲区
long start = System.currentTimeMillis();
File file = new File("D:\\Test\\test.txt");
File copy = new File("D:\\Test\\tt.txt");
if(!copy.exists()){
copy.createNewFile();
}
//创建流
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(copy);
//复制
//使用数组读写
byte[] buf = new byte[1024];
int len;
while((len = fis.read(buf)) != -1){
fos.write(buf,0,len);
}
//一个字符一个字符来读写
//int ch;
//while((ch = fis.read()) != -1){
//fos.write(ch);
//}
//关闭
fis.close();
fos.close();
long end = System.currentTimeMillis();
//执行时间 ms
System.out.println("不使用缓冲区的字节流:"+(end-start)+"ms");
}
- 使用缓冲区
正如前面所说,流可以包裹另外的流,所以先创建字节流,再用缓冲流去包裹字节流
public static void fileCopy2() throws IOException {
//使用缓冲区
long start = System.currentTimeMillis();
File file = new File("D:\\Test\\test.txt");
File copy = new File("D:\\Test\\tt.txt");
if(!copy.exists()){
copy.createNewFile();
}
//创建流
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(copy);
//创建缓冲字节流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//复制
//使用数组读写
byte[] buf = new byte[1024];
int len;
while((len = bis.read(buf)) != -1){
bos.write(buf,0,len);
}
//一个字符一个字符来读写
//int ch;
//while((ch = bis.read()) != -1){
//bos.write(ch);
//}
//一定要刷新缓冲区
bos.flush();
//关闭
bis.close();
bos.close();
fis.close();
fos.close();
long end = System.currentTimeMillis();
//执行时间 ms
System.out.println("使用缓冲区的字节流:"+(end-start)+"ms");
}
前后时间对比:
不使用缓冲区的字节流:25ms
使用缓冲区的字节流:10ms
可以看到使用缓冲流效率明显提高,尤其是当文件过大时,buffer缓冲流的作用就体现出来了,上述代码是每次使用一个数组来读写,如果每次读写一个字节两者时间差会更大。
3.4 字符流
3.4.1 FileReader 和 FileWriter
说明:
- FileReader:文件字符输入流,如果要从文件中读取内容,可以直接使用FileReader,用于读取字符流。
- FileWriter:文件字符输出流,如果向文件中写入内容,可以直接使用FileWriter,用于写入字符流。
3.4.2 BufferedReader 和 BufferedWriter
说明:
-
BufferedReader:字符输入缓冲流
- 拥有8192个字符的缓冲区,加快读取字符的速度,从字符输入流读取文本,缓冲字符,以提供字符、数组、行的高效读取。
- 读取文本文件时,会先从文件中读入字符数据并放慢缓冲区,之后使用read()方法时,会先从缓冲区进行读取,如果缓冲区数据不足,才会再从文件中读取。
-
BufferedWriter:字符输出缓冲流
- 拥有8192个字符的缓冲区,加快写入字符的速度,将文本写入字符输出流,缓冲字符,以提供单个字符、数组、行的高效写入。
- 进行数据写入时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中,如果缓冲区数据满了,才会一次性的对目的地进行写出。
代码示例:
使用字符流复制文件:
- 不使用缓冲区:
//还是刚才的文件
public static void fileCopy1() throws IOException {
//不使用缓冲区 使用字符流读写
long start = System.currentTimeMillis();
File file = new File("D:\\Test\\test.txt");
File copy = new File("D:\\Test\\t.txt");
if(!copy.exists()){
copy.createNewFile();
}
//创建字符流
FileReader fr = new FileReader(file);
FileWriter fw = new FileWriter(copy);
//复制
//使用数组来读写
char[] buf = new char[1024];
int len;
while((len = fr.read(buf)) != -1){
fw.write(buf,0,len);
}
//一个字符一个字符来读写
//int ch;
//while((ch = fr.read()) != -1){
//fw.write(ch);
//}
//关闭
fr.close();
fw.close();
long end = System.currentTimeMillis();
//执行时间 ms
System.out.println("不使用缓冲区的字符流:"+(end-start)+"ms");
}
- 使用缓冲区
public static void fileCopy2() throws IOException {
//使用缓冲区 使用字符流
long start = System.currentTimeMillis();
File file = new File("D:\\Test\\test.txt");
File copy = new File("D:\\Test\\t.txt");
if(!copy.exists()){
copy.createNewFile();
}
//创建字符流
FileReader fr = new FileReader(file);
FileWriter fw = new FileWriter(copy);
//创建缓冲字符流
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
//复制
//使用数组读写
char[] buf = new char[1024];
int len;
while((len = br.read(buf)) != -1){
bw.write(buf,0,len);
}
//一行一行读取
// String str = "";
//while((str = br.readLine()) != null){
// \r\n 换行写入,如果不加可能文件复制不全
// bw.write(str+"\r\n");
// }
//刷新缓冲区
bw.flush();
//关闭
br.close();
bw.close();
fr.close();
fw.close();
long end = System.currentTimeMillis();
//执行时间 ms
System.out.println("使用缓冲区的字符流:"+(end-start)+"ms");
}
前后时间对比:
不使用缓冲区的字符流:69ms
使用缓冲区的字符流:15ms
3.5 字符字节转换流
有时候我们需要进行字节流与字符流二者之间的转换,因为这是两种不同的流,所以,在进行转换的时
候我们需要用到 OutputStreamWriter 和InputStreamReader 。
3.5.1 InputStreamWriter
说明:
- 将输入的字节流转换成字符流,读取字节,并使用指定的charset将其解码为字符
- 使用的字符集可以由名称指定,也可以被明确指定,或接受平台的默认字符集。
代码示例:
将控制台输入的那内容输出到文件中:
//字节输入流转为字符输入流
public static void inputStreamToReaderDemo() {
//创建字节流对象 System.in 代表从控制台输入
InputStream in = System.in;
//创建字符流对象
BufferedReader br = null;
BufferedWriter bw = null;
try {
//实例化字符流对象 通过InputStreamReader将字节输入流转为字符输入流
InputStreamReader isr = new InputStreamReader(in);
br = new BufferedReader(isr);
// br = new BufferedReader(new InputStreamReader(in));
// br = new BufferedReader(new InputStreamReader(in,"GBK"));
bw = new BufferedWriter(new FileWriter("D:\\Test\\a.txt"));
//定义读取数据的行
String line = null;
//读取数据
while ((line = br.readLine()) != null) {
//如果输入exit就退出
if (line.equals("exit")) {
break;
}
//将数据写入文件
//bw.write(line+"\r\n");
bw.write(line);
//写入新的一行
bw.newLine();
//刷新缓冲区
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
try {
if (bw != null) {
bw.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.5.2 OutputStreamWriter
说明:
- 将输出的字节流转换成字符流。
- 使用的字符集可以由名称指定,也可以被明确指定,或接受平台的默认字符集。
代码示例:
从文件中读取数据并输出到控制台上:
public static void outputStreamToWriterDemo() {
//从文件中读取内容并输出到控制台上
//创建输出流对象 System.out 为标准输出
OutputStream out = System.out;
//定义字符流对象
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader("D:\\Test\\a.txt"));
//将字节输出流转为字符输出流
bw = new BufferedWriter(new OutputStreamWriter(out));
//bw = new BufferedWriter(new OutputStreamWriter(out,"GBK"));
//定义读取行的字符串
String line;
//读取文件
while ((line = br.readLine()) != null) {
//将内容写到控制台中
bw.write(line);
//新的一行
bw.newLine();
//bw.write(line+"\r\n");
//刷新缓冲
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.6 打印输出流
—— PrintWriter
代码示例:
将字母a-z打印到文件中:
public static void filePrint() throws IOException {
File file = new File("D:\\Test\\t.txt");
if(!file.exists()){
file.createNewFile();
}
//文件字符输出流
FileWriter fw = new FileWriter(file);
//使用PrintWriter
PrintWriter pw = new PrintWriter(fw);
for(int i='a';i<='z';i++){
pw.println((char) i);
}
//刷新缓冲区
pw.flush();
//关闭
pw.close();
}
3.7 小结
所有的文件存储都是字节(byte)的存储,在磁盘上保留的是字节。
IO流的类,如果是包含:
-
FIle:文件操作流
-
Stream:字节流,操作的基本单元是字节,操作byte[] 或一个byte,在操作的时候本身不会用到缓冲区,使用时,即使没有关闭资源,也能输出
-
Reader、Writer:字符流,操作的基本单元为Unicode码元,字符流在操作的时候使用到缓冲区,不使用close方法,不会输出任何内容
-
Input / Reader:输入流
-
Output / Writer:输出流
-
Buffered:缓冲流,开辟缓冲区
-
InputStreamReader / OutputStreamWriter:字节字符转换流,要把字节转换为字符流,需要在中间套上字节字符转换流
-
其他:
- PrintWriter:打印输出流
- StringWriter:字符串输出流
- 带Object:对象流