✨✨hello,愿意点进来的小伙伴们,你们好呐!
🐻🐻系列专栏:【JavaEE】
🐲🐲本篇内容:详解Java IO流
🐯🐯作者简介:一名现大二的三非编程小白,日复一日,仍需努力。
IO流介绍
- 文件:
- 文件流:
- 常用文件操作 :
- IO流原理
- IO流分类
- IO流常用的类:
- FileInputStream 介绍:
- FileOptputStream 介绍:
- 案例:
- FileRead 介绍:
- FileWriter 介绍
- 节点流和处理流 介绍
- BufferedRead 介绍
- BufferedWriter 介绍
- 介绍字节缓冲流
- 对象流介绍:
- 转换流:
- 打印流
文件:
🥝🥝🥝对于文件,我相信大家都不会陌生吧,文件是电脑保存数据的载体,比如大家经常使用的word,ppt,txt…都是文件,文件可以保存一张图片,文本,亦或者视频
文件流:
🥥🥥🥥文件在程序中是以流的形式来操作的,
数据在文件与内存(程序)之间的经历的路径就是流,分为从文件读取到内存(程序)的输入流,和从内存输出到文件(程序)的输出流
常用文件操作 :
创建文件:
🍇🍇🍇 我们创建一个文件就是新建File类对象,来创建一个文件
根据File类的构造器可以看出,创建一个文件的方法还是不少的,接下来我给大家演示一下
new(String filePath) 根据文件路径构造一个File对象
new(String parent ,String child) 根据父目录路径+子文件路径构造一个File对象
new(File parent , String child) 根据父目录文件+子文件路径构造一个File对象
public class FileCreate {
public static void main(String[] args) throws IOException {
/**
* 在 d 盘下创建hello 1\2\3 .txt 文件
*/
String filePath1 = "d:\\hello1.txt";
File file = new File(filePath1);
file.createNewFile();
file = new File("d:\\","hello2.txt");
file.createNewFile();
String parentPath = "d:\\";
File fileParent = new File(parentPath);
file = new File(fileParent,"hello3.txt");
file.createNewFile();
}
}
文件创建成功
获取文件信息:
🍇🍇🍇==在File类中有很多获取文件的一些方法,我来演示如何获取文件的大小,文件名,路径,父文件是文件还是目录,是否存在==
public class File02 {
public static void main(String[] args) {
String filePath = "d:\\hello1.txt";
File file = new File(filePath);
System.out.println(file.getName());//文件名
System.out.println(file.getParent());//文件的父级目录
System.out.println(file.getAbsolutePath());//文件的绝对路径
System.out.println(file.length());//文件的大小
System.out.println(file.exists());//文件是否存在
System.out.println(file.isFile());//是不是一个文件
System.out.println(file.isDirectory());//是不是一个目录
}
}
目录的操作,和文件的删除
🍇🍇🍇==对目录的操作与对文件的创建略有些不同,接下来让我们来看看==
mkdir() 是用于创建单级目录
mkdirs() 用于创建多级目录
public class File03 {
public static void main(String[] args) {
String filePath = "d:\\a\\b\\c";
/**
* 判断是否存在该目录,存在就删除,不存在就创建
*/
//得到目录对象
File file = new File(filePath);
if(file.exists()) {
System.out.println("目录存在,我们要删除");
if(file.delete()) {
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
}else {
if(file.mkdirs()) {
System.out.println("创建成功");
}else {
System.out.println("创建失败");
}
}
}
}
运行代码一次,我们发现真的创建了该目录
继续运行
则将该目录下的C目录删除
IO流原理
🍈🍈🍈
1. I/O是Input/Output的缩写.I/O流是非常实用的技术,使用于处理数据的传输,如数据的读/写文件,网络通信等.
2. Java中,对于数据的输入/输出操作以"流(Stream)"的方式进行称呼
3.JavaIO流的类都是在java.io包中,该报包提供了各种流的类与接口,用来获取不同的数据,并提供方法输入或者输出
4.输入:input,将外部的数据(硬盘,光盘)读取到程序(内存)中
5. 输出:ouput,将程序(内存)中的数据传输到光盘,硬盘等存储设备中
IO流分类
IO流根据不同的分类方法可以分为不同类型的流
🍉🍉🍉根据操作的数据类型不同,分为字节流(二进制文件)和字符流(文本文件)
🍊🍊🍊根据对数据的操作方向不同,分为输入流和输出流
🍋🍋🍋按照流的角色不同,分为:节点流,处理流(包装流)
然后Java的IO流共有40多个类,但是实际上是很规律的,都是基于四个抽象基类派生出来的,命名也及其有规律,以抽象基类为后缀进行命名
IO流常用的类:
FileInputStream 介绍:
我们创建创建一个FileInputStream对象可以传入文件路径,或者文件对象来创建
🍎🍎🍎使用字节输入流(FileInputStream )在"d:\hello1.txt"中读取文件内容打印到控制台
🍏🍏🍏 读取文件的内容一般使用read来完成,
public class FileInputStream___ {
String filePath = "d:\\hello1.txt";
FileInputStream fileInputStream = null;
public static void main(String[] args) { }
/**
* 演示 read()
* 该方法是读取一个一个字节,然后如果读取不到数据了就返回-1
* 读取到数据返回的就是读取到的数据的值,是int类型
* 效率慢
*/
@Test
public void m1() throws IOException {
try {
//新建 FileInputStream对象
fileInputStream = new FileInputStream(filePath);
int data = 0;
while ((data = fileInputStream.read()) != -1) {
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//记得要关闭流
fileInputStream.close();
}
}
/**
* 演示 read(byte[])
* 该方法读取的是byte数组大小的字节数并存储在byte数组中,返回读取到的字节数个数,即数组的长度
* 然后如果读取不到数据了就返回-1
* 效率高
*/
@Test
public void m2() throws IOException {
try {
//新建 FileInputStream对象
fileInputStream = new FileInputStream(filePath);
byte[] buffer = new byte[1024];
int buf_len = 0;
while ((buf_len = fileInputStream.read(buffer)) != -1) {
System.out.print(new String(buffer,0,buf_len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
fileInputStream.close();
}
}
}
使用字节输入流读取文字打印到控制台的时候会出现乱码的问题,因为对于很大编码来说,一个文字不只一个字节来存储,而字节输入流读取的是一个字节的数据,打印出来就会出现乱码的问题.然后要是读取文本数据然后再输出到另一个文件中,那就可以成功地读取输出,不会出现乱码问题
FileOptputStream 介绍:
创建一个FileOutputStream,可以往其中传入文件的地址,还有文件对象来创建,然后可以传入一个布尔类型的变量,如果没有传入这个布尔类型的变量,那么该输出流是覆盖的输出流,每一次的输出都会覆盖上一次的输出内容,然后往其传入一个true后创建的输出流对象,是一个追加模式的输出流,每一次都在文件文本末尾继续追加文本
🍑🍑🍑使用字节输出流(FileOptputStream) 往"d:\hello4.txt"文件中写入"hello,world"
输出是使用write()方法来进行输出,如果输出的是一个不存在的文件,那么底层会创建文件后再输出
public class FileOutputStream___ {
String filePath = "d:\\hello4.txt";
FileOutputStream fileOutputStream = null;
public static void main(String[] args) { }
@Test
public void m1() throws IOException {
try {
fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write("hello,world".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
fileOutputStream.close();
}
}
}
案例:
我们来对FileInputStream与FileOutputStream的一个综合运用:编程完成图片/音乐 的拷贝.
/**
* 我们从 "d:\\01.jpg" 拷贝一份图片到 "d:\\01_copy.txt"
* 思路:
* 1. 先创建输入流,读取图片数据
* 2. 然后创建输出流,将数据输出
* 3. 注意要变读取数据边输出,这样子效率较高
*/
public class File_Input_Output_Stream {
public static void main(String[] args) throws IOException {
String srcFilePath = "d:\\01.jpg";
String destFilePath = "d:\\01_copy.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
byte[] buf = new byte[1024];
int data_len = 0;
while ((data_len = fileInputStream.read(buf)) != -1) {
fileOutputStream.write(buf,0,data_len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
fileOutputStream.close();
fileInputStream.close();
}
}
}
拷贝成功
FileRead 介绍:
🍒🍒🍒 FileRead是一个用来读取字符的输入流类,跟字节输入流不同的是,字符流更适合来处理文本数据的读取.效率更快
我们使用字符输入流来读取"d:\hello1文件中的数据,并打印到控制台"
public class FileReader__ {
public static void main(String[] args) throws IOException {
FileReader fileReader = null;
String filePath = "d:\\hello1.txt";
try {
fileReader = new FileReader(filePath);
char[] chs = new char[1024];//使用char数组来提高输入效率,
int len = 0;//来获取read方法返回的数组的长度
while ((len = fileReader.read(chs)) != -1) {
System.out.println(new String(chs,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
fileReader.close();
}
}
}
读取成功,字符输入流可以有效避免字节输入流读取数据后打印在控制台出现的乱码问题.
FileWriter 介绍
FileWriter 字符输出流,对于文字数据的输出有很大的作用.
使用 FileWriter 将 “风雨之后,定见彩虹” 写入到 note.txt 文件中
public class FilelWriter____ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\note.txt";
FileWriter fileWriter = null;
fileWriter = new FileWriter(filePath);
fileWriter.write("风雨之后,定见彩虹");
fileWriter.close();
}
}
在使用FileWriter输出数据的时候,一定要记得close或者flush才会真正地把数据写入文件,否则这个时候数据还在内存中,没有真正的写入硬盘
public class FilelWriter____ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\note.txt";
FileWriter fileWriter = null;
fileWriter = new FileWriter(filePath);
fileWriter.write("风雨之后,定见彩虹");
}
}
🍓🍓🍓**就需要关闭流,或者刷新一下才会真正地写入数据,但是刷新后我们也需要关闭流,所以一般直接关闭流就好了**
下面我们来看看源码,为什么要关闭流数据才会真正的写入呢?
1.进入close()方法,发现底层调用的是se的close方法,se又是一个StreamEncoder对象
2.进入se的close方法后,接下来会调用implClose()方法
3.在implClose方法中,其实writeBytes方法才是写入数据的方法.
4.在writeBytes中调用OutputStream 的 write()方法,其实底层也是字节输出流
节点流和处理流 介绍
🍓🍓🍓
节点流是从一个特定的数据源读取数据,如上述的四种流均是节点流,是读取数据最底层的流,
处理流(也叫包装流)则是对节点流或者本身之外的处理流进行包装,然后拥有更强大的读取数据的能力,也更加灵活,如:BufferedReader,BufferedWriter
🍅🍅🍅 节点流是最底层的流,直接与数据源相接,处理流用来包装节点流,既可以消除不同节点的实现差异,也可以提供更方便的方法来完成输入输出, 主要以缓冲的方式来提高输入输出的效率,处理流也提供了一些更便捷的方法来对数据进行处理,更加灵活
处理流包装节点流用到的是修饰器模式
接下来我主要介绍处理流中的缓冲流
BufferedRead 介绍
🌽🌽🌽在BufferedRead 源码中,封装了一个Reader的对象in,这个就是经典的修饰器模式,用来接收Reader抽象基类的子类
🍄🍄🍄我们可以看得出,下面类图中的BufferedReadeffer的构造器可以传入一个Reader的对象,所以继承于Reader的类都可以被BufferedReadeffer封装,形成缓冲流,且在缓冲流中关闭流,只需要关闭外层流即可
可以看出BufferedReader也是更加地灵活了,有读取一行数据的readLine()
使用BufferedReader读取数据显示到控制台
public class BufferedRead__ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\hello1.txt";
BufferedReader bufferedReader = null;
bufferedReader = new BufferedReader(new FileReader(filePath));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
}
BufferedWriter 介绍
🥒🥒🥒使用BufferedWriter来将"哈喽,你好"写入文件中
public class BufferedWriter___ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\test.txt";
BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));
writer.write("哈喽,你好");
writer.close();
}
}
BufferedReader && BufferedWriter 的综合运用
使用缓冲流,来完成文本的拷贝
public class BUfferedCopy {
public static void main(String[] args) throws IOException {
String srcFilePath = "d:\\hello1.txt";
String dstFilePath = "d:\\hello1_copy.txt";
BufferedReader br = new BufferedReader(new FileReader(srcFilePath));
BufferedWriter bw = new BufferedWriter(new FileWriter(dstFilePath));
String line = "";
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
}
}
介绍字节缓冲流
🥔🥔🥔字节缓冲流的底层也是如此,传入一个继承于基类的流类,来包装节点流
将J:\学习资料\腾讯会议 2022-12-11 08-32-17.mp4,拷贝多一份
public class BufferedStreamCopy {
public static void main(String[] args) throws IOException {
String srcFilePath = "J:\\学习资料\\腾讯会议 2022-12-11 08-32-17.mp4";
String destFilePath = "J:\\学习资料\\copy.mp4";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFilePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf,0,len);
}
bis.close();
bos.close();
}
}
文件大小一样,且可以正常播放,所以拷贝成功
🥔🥔🥔为什么在缓冲流关流的时候关闭外层流就可以了呢?我们来看一下源码究竟是什么原因
其实在缓冲流的close()方法中,调用的其实还是inputStream的close()方法,所以关闭的也是底层的节点流
对象流介绍:
在上述的流中,我们可以往文件中,存入数据,但是我们存入的数据是不带有,数据类型的.
然后在有一些场景,需要我们存入的数据带有数据类型,比如说int i = 100 存入文件中,并不是单纯的数字 100.而是int 100,从文件中恢复也是恢复为int 100,或者是想要将一个Dog dog = new Dog(“小黄”,10) 给保留数据类型输出到文件中,并且可以从文件中恢复
上述的需求,就是对基本数据类型,或者对象,进行序列化,反序列化的操作
序列化,反序列化:
- 在保存数据的时候,保存数据的类型和值,就是序列化.
- 在恢复数据的时候,恢复数据的值和类型,就是反序列化.
想要让某个对象支持序列号就要该对象实现:Serializable 和 Externalizable 接口
Serializable是一个标记接口,里面没有实现的方法
使用ObjectOutputStream序列号一个Dog对象,并保存到data.txt中
public class ObjectStream__ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\data.txt";
dog dog1 = new dog(10, "小白");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(dog1);
oos.close();
}
}
public class dog implements Serializable {
private int age;
private String name;
public dog(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "dog{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
使用ObjectInputStream反序化,打印出dog对象
public class ObjectInputStream__ {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String filePath = "d:\\data.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
dog dog = (dog)ois.readObject();
System.out.println(dog);
ois.close();
}
}
🥦🥦🥦细节:
- 读写的顺序要一致
- 要求序列化或者反序列化对象,需要实现Serializable
- 序列号类建议添加SerializableUID,为提高版本的兼容性
- 序列化对象时,默认将里面的属性,进行序列化,但是除了static和transient 修饰的成员
- 序列化对象,要求属性中的类型也实现了序列化接口
- 某个类实现了序列号,那么该类的子类也全部默认实现序列号
转换流:
我们来看一个中文乱码的问题引出转换流问题:
我们将文件的编码改为国标–GBK,然后使用字符流来读取数据,就会有中文乱码问题
public class InputStreamReader___ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\文件1.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
String line = br.readLine();
System.out.println(line);
br.close();
}
}
因为默认字符流读取到的数据编码都是unicode,然后我们将文件的编码改为gbk,就会有问题,所以导致读取到的数据都会有乱码.
这个时候,就需要,转换流来处理,转换流可以将字节流转为字符流,然后在转的过程中指定编码,这个时候就不会出现编码不同导致的乱码问题
🥕🥕🥕使用InputStreamReader解决编码不同中文乱码问题
public class InputStreamReader___ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\文件1.txt";
BufferedReader br =
new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"gbk"));
String line = br.readLine();
System.out.println(line);
br.close();
}
}
🎦🎦🎦可以指定编码读取文件,那么也肯定可以指定编码存入文件
public class OutputStreamReader___ {
public static void main(String[] args) throws IOException {
String filePath = "d:\\Test.txt";
BufferedWriter bw =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath),"gbk"));
bw.write("罗鸿基,为大厂努力");
bw.close();
}
}
打印流
🍵🍵🍵打印流只有输出流,可以用来将数据输出在文件,或者控制台上
字符打印流
字节打印流
在打印的时候,因为传参的是OutputStream对象,所以可以传入System.out,来控制输出到控制台上
public class PrintStream_ {
public static void main(String[] args) {
PrintWriter printWriter = new PrintWriter(System.out);
printWriter.write("北京,上海");
printWriter.close();
}
}
也可以控制输出到文件中去…