1. 流概述
1.1. 什么是IO
IO
:Input/Output即输入&输出,也称之为流(河流,水流),指的是数据从一个地点到另一个地点过程;对于计算机来说文件的拷贝过程,文件的编写保存,显示功能都是使用到IO;将数据的传输过程看做一个输入输出。现实角度考虑可以将耳朵作为输入流,将嘴巴当做输出流
- 输入输出介质:
- 文件
- 网络
- 键盘(输入)
- 显示器(输出)
1.2. IO分类
Java针对IO操作提供了相应的API,Java中几乎所有的IO操作都需要使用java.io
包;java中对于流的分类包含各种方式:
-
按流向分(输入输出过程通常都是站在程序角度考虑)
- 输入流(Input)
- 输出流(Output)
-
按流的处理类型分
- 字节流(byte)
- 字符流(char)
-
按流的功能来分
- 节点流(直接跟输入输出源交互)
- 处理流(对其他流包装的流:包装流)
1.3. 顶级父类
流虽然内容繁多,但是规律性很强,几乎所有的流都从四个基本流继承而来:
输入流 | 输出流 | |
---|---|---|
字节流 | java.io.InputStream | java.io.OutputStream |
字符流 | java.io.Reader | java.io.Writer |
以上四个流是所有java流的顶层父类,都是抽象类;
流的类型识别规律性很强:一般以Stream
结尾的都是字节流;一般以Reader/Writer
结尾的流都是字符流
1.4. 使用场景
- 文件拷贝(File)
- 文件上传下载
- Excel导入导出
- 网络程序中数据传输(聊天工具)
- …
2. 字节流
2.1. 字节概述
在计算机系统中,一切都是字节:系统中存储的各种文件(文本文档,图片,视频,音频)的存储在计算机低层都是以字节的形式存储的,因此对于任何的文件操作都是可以使用一个一个字节进行操作的(读,写);java.io中的字节流顶层父类是:InputStream/OutputStream
.
2.2. 字节输入流
java中字节输入流都是从java.io.InputStream
继承而来。由于该类为抽象类,因此无法实例化的,所以jdk对于字节输入提供了一些能够直接使用子类:
FileInputStream
ByteArrayInputStream
BufferedInputStream
ObjectInputStream
InputStream常用方法
-
int available()
:获取流中可读字节数 -
int read()
:从流中读取一个字节,返回当前读取的字节数据 -
int read(byte[] b)
:将读取的字节数据存储到字节缓冲区,并返回实际的读取字节总数 -
skip(int b)
:跳过指定个字节发生下一次读取
2.2.1. FileInputStream
FileInputStream
是一个针对字节输入流的实现流,主要用于对文件进行读取操作,内部的方法主要是针对父类的实现,
-
常见构造器
-
FileInputStream(File file)
:根据提供的文件构建的对象 -
FileInputStream(String filePath)
:根据提供的文件路径构建对象
-
-
使用
FileInputStream
进行文件读取操作-
基本读取(每次读取一个字节)
//创建File对象 File file = new File("C:\\Users\\Administrator\\Desktop\\新建文本文档.txt"); //基于File创建字节输入流 InputStream in = new FileInputStream(file); //读取一个字节 int i = in.read(); System.out.println((char)i);
Java的IO只能对标准文件发生读写操作,不允许直接对一个目录创建输入或者输出流(会导致
java.io.IOException(拒绝访问)
) -
使用字节缓冲区读取(缓冲区大小为总可读字节数)
由于以上的读取方式是每次一个字节读取,因此,读取效率很低,所以实际开发中一般会使用一个字节缓冲区,提高读取效率:
//创建File对象 File file = new File("C:\\Users\\Administrator\\Desktop\\新建文本文档.txt"); //基于File创建字节输入流 InputStream in = new FileInputStream(file); //创建字节缓冲区(大小为总可读字节数) byte[] arr = new byte[i]; //将流中读取的字节内容存储到数组中 int total = in.read(arr); //将字节数组转换为String字符串 System.out.println(new String(arr));
-
使用合适大小字节缓冲区读取
以上的读取是一次性将文件中的数据读取到缓冲区中,因此,缓冲区中容量可能会很大,如果针对一个大文件文件的读取,使用一个过大的缓冲区,可能会造成空间的消耗,从而影响其他程序的执行,所以,需要一个合适大小的缓冲区进行反复读取
//创建文件输入流对象(文件:一缸水) InputStream in = new FileInputStream("C:\\Users\\Administrator\\Desktop\\Hero.java"); //创建缓冲区(购买一个大小适中的水桶) byte[] b = new byte[1024]; //声明临时变量表示每次读取的实际字节数 int len = 0; while((len = in.read(b)) != -1){ String s = new String(b,0,len); System.out.println(s); }
以上的读取方式就是对于与字节流的读取常规解决方案
-
2.3. 字节输出流
根据数据的流向除了可以进行读取(输入)操作之外,数据的写(输出)操作也是十分常见,java中的字节输出流都是从java.io.OutputStream
继承过来
由于OutputStream
是一个抽象类,无法实例化,因此jdk也提供了针对该流的子类:
FileOutputStream
ByteArrayOutputStream
BufferedOutputStream
ObjectOutputStream
PrintStream
OutputStream类的常见方法:
-
write(int b)
:将一个字节通过输出流写出到目标输出源 -
write(byte[] b)
:将一个字节数组通过输出流写出到目标输出源 -
write(byte[] b,int offset,int len)
将一个数组的offset开始写出len个字节到目标输出源
2.3.1. FileOutputStream
FileOutputStream
是一个针对字节输出流的实现流,主要用于对文件进行写入操作,内部的方法主要是针对父类的实现
-
常用构造器
-
FileOutputStream(String filePath)
:基于一个标准文件的路径创建对其操作的输出流 -
FileOutputStream(String filePath,boolean append)
:基于一个标准文件的路径创建对其操作的输出流,使用追加模式 -
FileOutputStream(File file)
:基于一个标准文件的路径创建对其操作的输出流 -
FileOutputStream(File file,boolean append)
基于一个标准文件对象创建对其操作的输出流,使用追加模式
-
-
使用
FileOutputStream
进行文件写出操作//try...with语句:JDK1.7 //针对所有的第三方资源不再需要手动回收(关闭) //因为从jdk1.7开始,很多资源类都实现过Closeable接口,但凡实现过该接口类 //只要将其在try()中创建,不再手动关闭资源,会由JVM自动回收 try(OutputStream os = new FileOutputStream("readme.txt",true);){ //写出一个字节到文件中 // os.write(101); //荀子·劝学篇 String msg = "不积小流无以成江海,不积跬步无以至千里"; os.write(msg.getBytes()); }catch (IOException e){ e.printStackTrace(); }
2.4. 综合案例-文件拷贝
/**
* 将一个源文件拷贝到一个目标目录中
* @param src 源文件
* @param targetDir 目标目录
*/
public static void copyFile(File src, File targetDir) throws IOException {
InputStream is = null;
OutputStream os = null;
try {
//获取源文件的输入流
is = new FileInputStream(src);
//获取目标文件的输出流(目标文件是由:目录+源文件名称构成):文件输出流可以创建文件(前提:父目录必须存在)
os = new FileOutputStream(new File(targetDir,src.getName()));
//声明字节缓冲区(缓冲区越大,拷贝效率越高,但是带来空间损耗也越大)
byte[] b = new byte[1024*1024];
//临时变量表示实际读取的字节数
int len = 0;
System.out.println("开始拷贝...");
while((len = is.read(b)) != -1){
//写出读取的内容到输出流
os.write(b,0,len);
}
System.out.println("拷贝完成!");
} finally{
if(os != null){
os.close();
}
if(is != null){
is.close();
}
}
}
//测试
public static void main(String[] args) throws IOException {
//源文件
File src = new File("D:\\素材\\视频\\短视频\\test.mp4");
//目标目录
File targetDir = new File("C:\\Users\\Administrator\\Desktop");
//文件拷贝
copyFile(src,targetDir);
}
在文件拷贝的基础上实现目录拷贝:
public class FileCopy { /** * 将一个源目录拷贝到另一个目标目录中 * @param srcDir * @param targetDir */ public static void copyDir(File srcDir,File targetDir) throws IOException { //获取新目录对象 targetDir = new File(targetDir,srcDir.getName()); //如果新目录不存在,则创建 if(!targetDir.exists()){ targetDir.mkdirs(); } File[] files = srcDir.listFiles(); if(Objects.nonNull(files)){ for (File file : files) { if(file.isDirectory()){ //目录递归拷贝 copyDir(file,targetDir); }else{ //执行文件拷贝 copyFile(file,targetDir); } } } } /** * 将一个源文件拷贝到一个目标目录中 * @param src 源文件 * @param targetDir 目标目录 */ public static void copyFile(File src, File targetDir) throws IOException { InputStream is = null; OutputStream os = null; try { //获取源文件的输入流 is = new FileInputStream(src); //获取目标文件的输出流(目标文件是由:目录+源文件名称构成) os = new FileOutputStream(new File(targetDir,src.getName())); //声明字节缓冲区(缓冲区越大,拷贝效率越高,但是带来空间损耗也越大) byte[] b = new byte[1024*1024]; //临时变量表示实际读取的字节数 int len = 0; System.out.println("开始拷贝..."); while((len = is.read(b)) != -1){ //写出读取的内容到输出流 os.write(b,0,len); } System.out.println("拷贝完成!"); } finally{ if(os != null){ os.close(); } if(is != null){ is.close(); } } } public static void main(String[] args) throws IOException { //源目录 File srcDir = new File("D:\\素材\\视频"); //目标目录 File targetDir = new File("C:\\Users\\Administrator\\Desktop\\video"); //目录拷贝 copyDir(srcDir,targetDir); } }
3. 字符流
3.1. 字符概述
通常在文本文件
中,文件内容的存在形式一般是以一个个字符(一个中文汉字,一个英文字母,一个符号)的形式存在,在GBK编码模式下通常1个字符=2个字节
;字符流一般适用于对文本数据的处理,java中所有的字符流几乎都是以Reader/Writer
结尾,并且都是从两个基本的抽象类中继承:
-
java.io.Reader
:字符输入流 -
java.io.Writer
:字符输出流
3.2. 字符输入流
字符输入流一般用于对文本数据进行读取操作,常见的子类:
InputStreamReader
FileReader
BufferedReader
CharArrayReader
3.2.1. FileReader
FileReader是一个以字符流的形式进行文件内容读取的流,从InputStreamReader
继承而来,内部没有多余的方法(与父类的API一致),提供一下常见的构造方法:
-
常用构造方法
-
FileReader(File file)
:根据提供的文件对象获取文件字符输入流 -
FileReader(String path)
:根据提供的文件路径获取文件字符输入流
-
-
具体使用
//创建文件字符输入流 FileReader fr = new FileReader("D:\\文档资料\\电子书\\书籍推荐.txt"); //获取当前流使用的默认编码模式(并非目标文件的编码) // String encoding = fr.getEncoding(); // System.out.println(encoding); //读取一个字符(以int类型存在) // int read = fr.read(); // System.out.println((char)read); // int c = 0; // while((c = fr.read()) != -1) // { // System.out.print((char)c); // } //使用字符缓冲区 char[] ch = new char[100]; int len = 0; while((len = fr.read(ch)) != -1){ String s = new String(ch,0,len); System.out.print(s); }
3.3. 字符输出流
字符输出流一般用于对文本数据进行写出操作,常见的子类:
- Reader`
OutputStreamWriter
BufferedWriter
CharArrayWriter
FileWriter
PrintWriter
3.3.1 FileWriter
FileWriter是一个以字符流的形式进行文件内容写出的流,从OutputStreamWriter
继承而来,内部没有多余的方法(与父类的API一致),提供一下常见的构造方法:
-
常用构造方法
-
FileWriter(File file)
:根据提供的文件对象获取文件字符输入流 -
FileWriter(String path)
:根据提供的文件路径获取文件字符输入流 -
FileWriter(File file,boolean append)
:根据提供的文件对象获取文件字符输入流(使用追加模式) -
FileWriter(String path,boolean append)
:根据提供的文件路径获取文件字符输入流(使用追加模式)
-
-
具体使用
//基于指定的文件创建字符输出流 FileWriter fw = new FileWriter("readme.txt"); fw.write("路漫漫其修远兮,吾将上下而求索"); //允许在流未关闭之前,强制将字符缓冲区中的数据写出到目标输出源 fw.flush(); Thread.sleep(10000); fw.write("好好xio习,天天up!!!"); fw.close();
对于字符输出流,内部使用到了一个
字符缓冲区(字符数组)
,在进行数据写出时,通常是将需要写出的数据缓存存在了字符数组中,然后在关闭流时(或者缓冲区存满时),一次性将缓冲区的数据写出到目标输出源, 如果需要在流未关闭前(或者缓冲区未满时)强制的将字符缓冲区中的数据写出,可以手动调用flush()
强制输出。
4. 处理流
对于流的处理类型(功能)来分又分为节点流和处理流:
-
节点流
也称之为低级流,直接跟输入输出源沟通的流(例如:FileReader,FileWriter,FileInputStream,FileOutputStream)
-
处理流
处理流也称之为高级流或包装流,即可以用于对其他节点流进行包装,以实现流的
类型转换
或者效率的提升。处理流主要由缓冲流
和转换流
构成
包装通常使用到了一种设计模式(装饰器模式)
4.1. 缓冲流
缓冲流是一种自带缓冲区的流,主要由以下四种构成
-
BufferedInputStream
:字节缓冲输入流 -
BufferedOutputStream
:字节缓冲输出流 -
BufferedReader
:字符缓冲输入流 -
BufferedWriter
:字符缓冲输出流
long start = System.currentTimeMillis();
try(
//获取输入流
InputStream in = new FileInputStream("D:\\集团资料\\宣讲\\video\\云计算&大数据\\阿里云.mp4");
//包装节点流
BufferedInputStream bis = new BufferedInputStream(in);
//获取输出流
OutputStream os = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\阿里云.mp4");
//包装节点流
BufferedOutputStream bos = new BufferedOutputStream(os);
) {
System.out.println("开始拷贝");
byte[] b = new byte[1024*1024*8];
int len = 0;
while((len = bis.read(b)) != -1){
bos.write(b,0,len);
}
System.out.println("拷贝结束");
}catch (IOException e){
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
缓冲流内部实现原理使用了了一个默认大小为
8kb
的字节缓冲区,每次将缓冲区的空间存满之后再将数据通过流对象完成读写操作
4.2. 转换流
在一些需求中经常会需要将一个字节流的数据转换为字符流;又或者需要将一个字符流转换为字节流,此时就需要使用转换流来完成功能,转换流一般应用于:文件转码,从网络中读取的数据为字节流时,但是该流包含内容是字符时,可以使用转换流实现转换;java.io中转换流主要由以下两个类构成:
-
InputStreamReader
:用于将字节输入流转换为字符输入流(字节->字符) -
OutputStreamWriter
:用于将字符输出流转换为字节输出流(字符->字节)
使用实例:
//获取标准输入流
InputStream is = System.in;
//将字节流转换为字符流
Reader isr = new InputStreamReader(is);
//将字符节点流包装为缓冲流
BufferedReader br = new BufferedReader(isr);
//读取一行
String line = br.readLine();
System.out.println("输入的内容--->"+line);
转换流只能对流的类型转换,不能对流向转换
4.3. 综合案例:文件转码工具
日常的文件拷贝中由于多个编辑器(系统)的编码模式存在差异,因此极有可能出现文件乱码问题(对于文本文件较为常见),通过转换流可以实现文件转码的功能:
public class FileCharacterConvert {
/**
* 将一个目标文件的编码转换为新的编码
* @param file 原始文件
* @param targetDir 目标目录
* @param oldEncoding 原始编码
* @param newEncoding 新编码
*/
public static void convert(File file,File targetDir, String oldEncoding, String newEncoding) throws IOException {
//使用特定的编码获取文件的输入流
FileInputStream fis = new FileInputStream(file);
Reader reader = new InputStreamReader(fis,oldEncoding);
BufferedReader br = new BufferedReader(reader);
//使用特定的编码获取文件的输出流
FileOutputStream fow = new FileOutputStream(new File(targetDir,file.getName()));
Writer writer = new OutputStreamWriter(fow,newEncoding);
BufferedWriter bw = new BufferedWriter(writer);
//开始读写
String line = "";
//循环读取每一行文本以换行符作为一行的结束标记(但是换行符不会被作为内容读取)
while((line = br.readLine()) != null){
//写入读取的一行文本()
bw.write(line);
//手动加入一个换行标记到文件,否则所有内容会在同一行显示
bw.newLine();
//将缓冲区的数据强制输出到目标输出源
bw.flush();
}
bw.close();
br.close();
}
public static void main(String[] args) throws IOException {
//准备需要转换的文件
File f = new File("C:\\Users\\Administrator\\Desktop\\GamePanel.java");
convert(f,new File("C:\\Users\\Administrator\\Desktop\\temp"),"gbk","utf-8");
}
}
5. 打印流
java.io,对于数据的输出提供了两个特殊的流:打印流,打印流只有输出没有输入,并且可以直接针对输出源创建,也可以将其他输出流包装,打印流主要包含以下两个流:
-
PrintStream
:字节打印流 -
PrintWriter
:字符打印流
5.1. PrintStream
PrintStream
是继承自java.io.OutputStream
,是一个用于进行字节数据输出的流,内包含了大量的print/prinln
重载方法;并且System.out
实际上就是一个PrintStream
-
常见构造器
-
PrintStream(File file)
:基于指定的文件创建打印流 -
PrintStream(OutputStream os)
:将其他字节输出流包装 -
PrintStream(OutputStream os,boolean autoFlush)
:将其他字节输出流包装,可以自动刷新流中数据
-
-
常用方法
print(...)
println(...)
-
基本使用
// PrintStream ps = new PrintStream(new File("readme.txt"),true); OutputStream os = new FileOutputStream("readme.txt", true); PrintStream ps = new PrintStream(os,true); ps.println("你好中国");
5.2. PrintWriter
PrintWriter
和PrintStream
区别在于,PrintWriter
是基于字符的打印流(包含字符缓冲区),其API与PrintStream
极其相似;在使用PrintWriter
打印数据的时候记得执行flush()
方法文章来源:https://www.toymoban.com/news/detail-491955.html
FileWriter fw = new FileWriter("readme.txt",true);
PrintWriter pw = new PrintWriter(fw);
pw.println("哪里有彩虹告诉过我!!!");
pw.flush();
pw.close();
6. 资源文件读取(Properties类)
在后期框架的学习过程中,经常会涉及到一些配置文件的编写,其中属性文件是很常见的一种文件类型,java中对于属性文件的读写提供了一个java.util.Properties
类:文章来源地址https://www.toymoban.com/news/detail-491955.html
InputStream in = PropertiesDemo.class.getResourceAsStream("/jdbc.properties");
//创建属性对象
Properties prop = new Properties();
// Properties prop = System.getProperties();
//加载流到属性对象中
prop.load(in);
String user = prop.getProperty("user");
String url = prop.getProperty("url");
System.out.println(user);
System.out.println(url);
prop.setProperty("maxActive","10");
File f = new File("D:\\带班资料\\2021\\J2106\\课程资料\\code\\part1-javase\\java高级\\lesson_01_IO\\resources\\jdbc.properties");
FileWriter fw = new FileWriter(f);
prop.store(fw,"this is jdbc config file");
fw.close();
7. 流的总结
- 所有的输入流名称中包含
input
、read
- 所有的输出流名称中包含
output
、write
- 所有的字节流名称以
stream
结尾 - 所有的字符流名称以
reader
或writer
结尾 - 一般二进制类型的文件(图片,视频,音频,压缩文件等无法直接使用文本文档打开的文件)主要使用字节流操作
- 一般文本类型文件(txt,md,java等可以直接使用文本文档打开的文件)主要使用字符流操作
到了这里,关于JAVA—IO流详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!