1. IO流
IO流的作用:读写文件数据
按照流的方向:
- 输入流
- 输出流
按照流的内容:
- 字节流:适合操作所有类型的文件,比如音频、视频、图片、文本文件的复制、转移等
- 字符流:适合操作纯文本文件,比如读写txt、java文件等
所以分为4大类:
- 字节输入流InputStream(读字节数据的)
- 字节输出流OutputStream(写字节数据出去的)
- 字符输入流Reader(读字符数据的)
- 字符输出流Writer(写字符数据出去的)
2.文件字节流
2.1 文件字节输入流(FileInputStream)
作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件 接通 |
public FileInputStream(Stream pathname) | 创建字节输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果发现没有数据可读会返回-1 |
public int read(byte[] buffer) | 每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1 |
2.1.1 读取一个字节read()
import java.io.FileInputStream;
public class FileInputStreamDemo1 {
public static void main(String args[]) throws Exception{
//1.创建文件字节输入流管道于源文件接通
FileInputStream fis=new FileInputStream("src/com/fqw/demo4fileinputstream/test01.txt");
//2.开始读取,每次读取一个字节返回,如果返回-1,则代表文件读取完毕
//定义一个变量记住每次读取的一个字节
int b;
while((b=fis.read())!=-1){
System.out.print((char)b);
}
}
}
但是对于读取汉字会出现乱码问题,因为英文和数字的编码只需一个字节,而汉字的编码需要三个字节,read()方法在读取汉字时会截断汉字编码,最终导致乱码
2.1.2 读取一个字节数组read()
一种错误的情况
这种情况的出现是因为字节数组相当于一个桶,每次读取3个字节,到下一组的时候会把读取的字节来覆盖上一组的数据,然而到最后的时候,不满3个字节,只有一个“i”,所以只能覆盖i,所以最后会多“9”和“w”,如图所示
因此,所以要设置读多少字节,输出多少字节
import java.io.FileInputStream;
public class FileInputStreamDemo2 {
public static void main(String[] args) throws Exception{
FileInputStream fis=new FileInputStream("src/com/fqw/demo4fileinputstream/test02.txt");
//定义一个字节数组用于每次读取一批字节
byte[] buffer=new byte[3];
int len;
while((len=fis.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
}
}
String()底层的实现
运行结果
优点:每次读取读取多个字节,性能得到提升,因为每次读取多个字节,可以减少硬盘和内存的交互次数,从而提升性能
缺点:依然无法避免读取汉字输出乱码的问题,存在截断汉字字节的可能
2.1.3 读取一个与文件一样大的数组
为了避免出现中文输出乱码问题,可以定义一个与文件一样大的字节数组,一次性读取完文件的全部字节
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字节返回,如果发现没有数据可读会返回-1 |
public byte[] readAllBytes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
import java.io.FileInputStream;
public class FileInputStreamDemo3 {
public static void main(String[] args) throws Exception{
FileInputStream fis=new FileInputStream("src/com/fqw/demo4fileinputstream/test03.txt");
byte[] buffer= fis.readAllBytes();
System.out.print(new String(buffer));
}
}
缺点:如果文件过大,创建的字节数组也会过大,可能引起内存溢出
综上对三种字节输入流的研究,读取文本适合用字符流,字节流适合做数据的转移,比如文件复制
2.2 文件字节输出流(FileOutputStream)
作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file, boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream(String filepath,, boolean append)) | 创建字节输出流管道与源文件对象接通,可追加数据 |
方法名 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer, int pos, int len) | 写一个字节数组的一部分出去 |
public void close() throws IOException | 关闭流 |
import java.io.FileOutputStream;
import java.io.OutputStream;
public class FileOutputStreamDemo1 {
public static void main(String[] args) throws Exception{
OutputStream os=new FileOutputStream("src/com/fqw/demo5fileoutputstream/test05.txt");//覆盖数据
//写入一个字节
os.write(97);
os.write(98);
os.write(99);
os.write("\r\n".getBytes());
//写入一个字节数组
byte[] buffer1={65,66,67,68,69};
os.write(buffer1);
os.write("\r\n".getBytes());
byte[] buffer2="as我爱你中国".getBytes();
os.write(buffer2);
os.write("\r\n".getBytes());
//写一个字节数组的一部分出去
os.write(buffer2,0,5);
os.write("\r\n".getBytes());
//注意要关闭资源
os.close();
}
}
追加数据
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class FileOutputStreamDemo2 {
public static void main(String[] args) throws Exception{
OutputStream os1=new FileOutputStream("src/com/fqw/demo5fileoutputstream/test05.txt",true);//可追加数据
os1.write("iskddmsoijds蜂蜜水".getBytes());
os1.write("\r\n".getBytes());
os1.close();
}
}
注意:无论写入写出,要注意关闭流,释放资源
2.3 字节流做文件复制
底层实现原理
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class cpDemo1 {
public static void main(String[] args) {
try {
copyFile("src/com/fqw/demo6cp/test06.txt","src/com/fqw/demo5fileoutputstream/test06.txt");
}catch (Exception e){
e.printStackTrace();
}
}
private static void copyFile(String srcFilePath, String destFilePath) throws Exception{
// 创建一个字节输出流与源文件连接
InputStream fis = new FileInputStream(srcFilePath);
// 创建一个字节输出流与目标文件连接
OutputStream fos = new FileOutputStream(destFilePath);
// 创建一个缓冲区
byte[] buf = new byte[1024];
// 读取
int len;
while ((len = fis.read(buf)) != -1) {
// 写入
fos.write(buf, 0, len);
}
System.out.println("复制完成");
fos.close();
fis.close();
}
}
注意:复制过去的时候必须带文件名,无法自动生成文件名
运行前
运行后
2.4 资源关闭释放
try-with-resource方案:该资源使用完毕后,会自动调用其close()方法,完成对资源的释放
try(定义资源1; 定义资源2;...){
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码
}
注意的点:
- ()中只能放置资源
- 资源一般指的是最终实现了AutoCloseable接口
代码
import java.io.*;
public class cpDemo2 {
public static void main(String[] args) {
try {
copyFile("src/com/fqw/demo6cp/test06.txt","src/com/fqw/demo5fileoutputstream/test06.txt");
}catch (Exception e){
e.printStackTrace();
}
}
private static void copyFile(String srcFilePath, String destFilePath) {
try {
// 创建一个字节输出流与源文件连接
InputStream fis = new FileInputStream(srcFilePath);
// 创建一个字节输出流与目标文件连接
OutputStream fos = new FileOutputStream(destFilePath);
// 创建一个缓冲区
byte[] buf = new byte[1024];
// 读取
int len;
while ((len = fis.read(buf)) != -1) {
// 写入
fos.write(buf, 0, len);
}
System.out.println("复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.文件字符流
3.1 FileReader文件字符输入流
作用:以内存为基准,可以把文件中的数据以字符形式读入到内存中去
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字符返回,如果发现没有数据可读会返回-1 |
public int read(char[] buffer ) | 每次用一个字符数组去读取数据,返回字符数组读取多少个字符,如果发现没有数据可读会返回-1 |
示例:
import java.io.FileReader;
import java.io.Reader;
public class FileReaderDemo1 {
public static void main(String[] args) {
try (
Reader fr = new FileReader("src/com/fqw/demo7filereader/test07.txt")
) {
char[] chs=new char[1024];
int len;//每次读取了多少个字符
while((len=fr.read(chs))!=-1){
System.out.print(new String(chs,0,len));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
**优点:**文件字符输入流每次读取多个字符,性能较好,而且读取中文是按照字符读取,不会出现乱码。适合读取中文
3.2 FileWriter文件字符输出流
作用:以内存为基准,可以把内存中的数据以字符形式读入到文件中去
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file, boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath,, boolean append)) | 创建字符输出流管道与源文件对象接通,可追加数据 |
方法名 | 说明 |
---|---|
public void write(int c) | 写一个字符出去 |
public void write(String str) | 写一个字符串出去 |
public void write(String str, int off, int len) | 写一个字符串的一部分出去 |
public void write(char[] cbuf) | 写一个字符数组 |
public void write(char[] cbuf, int off, int len) | 写一个字符数组的一部分出去 |
示例: |
import java.io.FileWriter;
import java.io.Writer;
public class FileWriterDemo1 {
public static void main(String[] args) {
try (
//Writer fw = new FileWriter("src/com/fqw/demo8filewriter/test08.txt")//覆盖管道
Writer fw = new FileWriter("src/com/fqw/demo8filewriter/test08.txt",true)//追加管道
) {
//写一个字符出去
fw.write('a');
fw.write(98);
fw.write('中');
fw.write("\r\n".toCharArray());
//写一个字符串
fw.write("草上莺啼声声扬。\n" +
"云卷云舒天际远,\n" +
"风来风去水中央。");
fw.write("\r\n".toCharArray());
//写字符串一部分出去
fw.write("草上莺啼声声扬。",3,4);
fw.write("\r\n".toCharArray());
//写一个字符串数组
char[] chars="只待闲心细品尝".toCharArray();
fw.write(chars);
//写一个字符串数组的一部分出去
fw.write(chars,1,2);
fw.write("\r\n".toCharArray());
}catch(Exception e){
e.printStackTrace();
}
}
}
4.缓冲流
主要分为缓冲字节输入流(BufferedInputStream)、缓冲字节输出流(BufferedOutputStream)、缓冲字符输入流(BufferReader)、缓冲字符输出流(BufferWriter),分别是对FileInputStream、FileInputStream、FilerReader、FilerWriter的改进。
4.1 缓冲字节流
作用:可以提高字节输入流读取是数据的性能
原理; 缓冲字节输入流自带了8KB的缓冲池;缓冲字节输出流也自带了8KB的缓冲池
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能 |
public BufferedOutputStream(OutputStream is) | 把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
4.2 缓冲字符输入流
作用:自带8KB的字符缓冲池,可以提高字符输入流读取字符数据的性能
构造器 | 说明 |
---|---|
public BufferedReader(Reader r) | 把低级的字符输入流包装成一个高级的缓冲字符输入流,从而提高读数据的性能 |
字符缓冲输入流新增的功能:按照行读取字符
方法 | 说明 |
---|---|
public String readLine() | 读取一行数据返回,如果没有数据可读了会返回null |
以上代码是目前读取文本最优雅的方案、性能好,可以按照行读取
4.3 缓冲字符输出流
作用:自带8KB的字符缓冲池,可以提高字符输出流字符写数据的性能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer r) | 把低级的字符输出流包装成一个高级的缓冲字符输出流,从而提高读数据的性能 |
字符缓冲输出流新增的功能:换行
方法 | 说明 |
---|---|
public void newLine() | 换行 |
4.4 缓冲流案例学习
3.逮(dai第四声,通“待”,等到)奉圣朝,沐浴清化。前太守臣逵(kuí)察臣孝廉,后刺史臣荣举臣秀才。臣以供养无主,辞不赴命。诏书特下,拜臣郎中,寻蒙国恩,除臣洗(xiǎn)马。
8.臣密今年四十有(yòu)四,祖母今年九十有(yòu)六,是臣尽节于陛下之日长,报养刘之日短也。乌鸟私情,愿乞终养。臣之辛苦,非独蜀之人士及二州牧伯所见明知,皇天后土,实所共鉴。
4.猥(wěi)以微贱,当侍东宫,非臣陨首所能上报。臣具以表闻,辞不就职。
2.臣少(shào)多疾病,九岁不行(xíng),零丁孤苦,至于成立。既无伯叔,终鲜(xiǎn)兄弟;门衰祚(zuò)薄,晚有儿息。外无期(jī)功强(qiǎng)近之亲,内无应门五尺之僮(tóng)。茕茕(qióng)孑(jie第二声)立,形影相吊。而刘夙婴(yīng)疾病,常在床蓐(rù);臣侍汤药,未曾废离。
1.臣密言:臣以险衅(xìn),夙(sù)遭闵(mǐn)凶。生孩六月,慈父见背。行(xíng)年四岁,舅夺母志。祖母刘悯(mǐn)臣孤弱,躬亲抚养。
9.愿陛下矜(jīn)悯愚诚,听臣微志,庶刘侥幸,保卒余年。臣生当陨首,死当结草。臣不胜(shēng)犬马怖惧之情,谨拜表以闻。
6.臣欲奉诏奔驰,则刘病日笃(dǔ);欲苟顺私情,则告诉不许:臣之进退,实为狼狈。
7.伏惟圣朝以孝治天下,凡在故老,犹蒙矜(jīn)育,况臣孤苦,特为尤甚。且臣少仕伪朝,历职郎署,本图宦(huàn)达,不矜名节。今臣亡国贱俘,至微至陋。过蒙拔擢(zhuó),宠命优渥(wò),岂敢盘桓(huán),有所希冀(jì)!但以刘日薄西山,气息奄奄,人命危浅,朝不虑夕。臣无祖母,无以至今日;祖母无臣,无以终余年。母孙二人,更(gēng)相为命。是以区区不能废远。
5.诏书切峻,责臣逋(bū)慢。郡县逼迫,催臣上道;州司临门,急于星火。
需求:把《陈情表》的文章顺序进行恢复到一个新文件中
分析:
- 定义一个缓存字符输入流管道与源文件接通
- 定义一个List集合存储读取的每行数据
- 定义一个循环按照行读取数据,存入到List集合中去
- 对List集合中的每行数据按照首字符编号升序排序
- 定义一个缓存字符输出管道与目标文件接通。
- 遍历List集合中的每个元素,用缓冲通道写出并换行
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BufferedTest {
public static void main(String[] args) {
try (
//1. 定义一个缓存字符输入流管道与源文件接通
BufferedReader br = new BufferedReader(new FileReader("src/com/fqw/demo11BufferedWriter/cql.txt"));
// 5. 定义一个缓存字符输出管道与目标文件接通。
BufferedWriter bw = new BufferedWriter(new FileWriter("src/com/fqw/demo11BufferedWriter/cql_out.txt"));
) {
// 2. 定义一个List集合存储读取的每行数据
List<String> data=new ArrayList<>();
// 3. 定义一个循环按照行读取数据,存入到List集合中去
String line;
while ((line=br.readLine())!=null){
data.add(line);
}
// 4. 对List集合中的每行数据按照首字符编号升序排序
Collections.sort(data);
// 6. 遍历List集合中的每个元素,用缓冲通道写出并换行
for (String s : data) {
bw.write(s);
bw.newLine();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
4.5 缓冲流性能分析
-
测试用例:分别使用原始的字节流,一级字节缓冲流复制一个很大的视频
-
测试步骤:
1.使用低级的字节流按照一个一个字节的形式复制下来
2.使用低级的字节流按照字节数组的形式复制文件
3.使用高级的缓冲字节流按照一个一个字节的形式复制文件
4.使用高级的缓冲字节按照字节数组的形式复制文件
测试代码:
import java.io.*;
public class TimerTest {
private static final String SRC_FILEPATH="D:\\BaiduNetdiskDownload\\test.mp4";
private static final String DEST_FILEPATH="D:\\";
public static void main(String[] args) {
copyByByte();
copyByByteArray();
copyByBuffered();
copyByBufferedArray();
}
//使用低级的字节流按照一个一个字节的形式复制下来
public static void copyByByte(){
//系统当前时间
long start=System.currentTimeMillis();//此刻毫秒值
try(
InputStream fis=new FileInputStream(SRC_FILEPATH);
OutputStream fos=new FileOutputStream(DEST_FILEPATH+"test01.mp4");
){
int len;
while ((len=fis.read())!=-1){
fos.write(len);
}
}catch (Exception e){
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("使用低级的字节流按照一个一个字节的形式复制下来,复制完成,耗时:"+(end-start)/1000.0+"毫秒");
}
//使用低级的字节流按照字节数组的形式复制文件
public static void copyByByteArray(){
long start=System.currentTimeMillis();
try(
InputStream fis=new FileInputStream(SRC_FILEPATH);
OutputStream fos=new FileOutputStream(DEST_FILEPATH+"test02.mp4");
){
byte[] bytes=new byte[1024];
int len;
while ((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
}catch(Exception e){
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("使用低级的字节流按照字节数组的形式复制文件,复制完成,耗时:"+(end-start)/1000.0+"毫秒");
}
//使用高级的缓冲字节流按照一个一个字节的形式复制文件
public static void copyByBuffered(){
long start=System.currentTimeMillis();
try(
InputStream fis=new FileInputStream(SRC_FILEPATH);
InputStream bis=new BufferedInputStream(fis);
OutputStream fos=new FileOutputStream(DEST_FILEPATH+"test03.mp4");
OutputStream bos=new BufferedOutputStream(fos);
){
int len;
while ((len=bis.read())!=-1){
bos.write(len);
}
}catch(Exception e){
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("使用高级的缓冲字节流按照一个一个字节的形式复制文件,复制完成,耗时:"+(end-start)/1000.0+"毫秒");
}
//使用高级的缓冲字节按照字节数组的形式复制文件
public static void copyByBufferedArray(){
long start=System.currentTimeMillis();
try(
InputStream fis=new FileInputStream(SRC_FILEPATH);
InputStream bis=new BufferedInputStream(fis);
OutputStream fos=new FileOutputStream(DEST_FILEPATH+"test04.mp4");
OutputStream bos=new BufferedOutputStream(fos);
){
byte[] bytes=new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
}catch (Exception e){
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("使用高级的缓冲字节按照字节数组的形式复制文件,复制完成,耗时:"+(end-start)/1000.0+"毫秒");
}
}
最终根据运行结果可以得出结论:
1.使用低级的字节流按照一个一个字节的形式复制下来:非常的慢
2.使用低级的字节流按照字节数组的形式复制文件:比较慢,还能接受
3.使用高级的缓冲字节流按照一个一个字节的形式复制文件:虽然是高级管道,还是比较慢
4.使用高级的缓冲字节按照字节数组的形式复制文件:非常快,推荐使用的
如果将低级管道的桶变大为8KB,高级管道的桶也变为8KB
它们的运行效率差不多
通过将桶大小不断增加至64KB大小,来观察性能
由图中可知,8KB以后性能提升的幅度是比较小的
5 其它流
5.1 字符输入转换流(InputStreamReader)
由图,正常读取一个GBK编码的文档时会出现乱码
字符输入转换流的作用:解决不同编码时,字符流读取文本内容乱码的问题
解决思路:先获取文件的原始字节流,再按其真实的字符集编码转成字符输入流,这样输入流中的字符就不乱码了
构造器 | 说明 |
---|---|
public InputStreamReader(InputStream is) | 把原始的字节输入流,按照默认编码转成字符输入流(与直接使用FileReader的效果一样) |
public InputStreamReader(InputStream is, String charset) | 把原始的字节输入流,按照指定编码转成字符输入流 |
5.2 打印流(PrintStream/PrinterWriter)
作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去
构造器 | 说明 |
---|---|
public PrintStream(OutStream/File/String) | 打印流直接通向字节输出流/文件/文件路径 |
public PrintStream(String fileName, Charset charset) | 可以指定写出去的字符编码 |
public PrintStream(OutputStream out, boolean autoFlush) | 可以指定实现自动刷新 |
public PrintStream(OutputStream out, boolean autoFlush, String encoding) | 可以指定实现自动刷新,并可指定字符的编码 |
方法 | 说明 |
---|---|
public void println(Xxx xx) | 打印任意类型的数据出去 |
public void write(int/byte[]/byte[] 一部分) | 可以支持字节数据出去 |
5.3 特殊流(DataOutputStream/DataInputStream)
5.3.1 DataOutputStream 数据输出流
允许把数据和其类型一并写出去
构造器 | 说明 |
---|---|
public DataOutputStream(OutputStream out) | 创建数据输出流包装基础的字节输出流 |
方法 | 说明 |
---|---|
public final void writeByte(int v) throws IOException | 将byte类型的数据写入基础的字节输出流 |
public final void writeInt(int v) throws IOException | int类型的数据写入基础的字节输出流 |
public final void writeDouble(int v) throws IOException | 将double类型的数据写入基础的字节输出流 |
public final void writeUTF(int v) throws IOException | 将字符串数据以UTF-8编码成字节写入基础的字节输出流 |
public final void write(int/byte/byte[] 一部分) throws IOException | 支持字节数据写出去 |
6 IO框架
封装了java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等
6.1 导入commons-io-2.18.0.jar框架到项目中去
1.在项目中创建一个文件夹:lib
2下载commons包下载(commons-io官网下载),将commons-io-2.18.0.jar框架文件复制到lib文件夹
3.在jar文件上点击右键,选择Add as Library-> 点击OK
- 在类中导包