I/O流
文件
什么是文件
文件, 对我们并不陌生, 文件是 保存数据的地方, 比如大家经常使用的word文档, txt文件, excel文件…都是文件. 它既可以保存一张图片, 也可以保存视频, 声音…
文件流
文件在程序中是以流的形式来操作的
流: 数据在数据源(文件)和程序(内存)之间经历的路径
输入流: 数据从数据源(文件)到程序(内存)的路径
输出流: 数据从程序(内存)到数据源(文件)的路径
常用的文件操作
创建文件对象相关构造器和方法
● 相关方法
new File(String pathname) //根据路径构建一个File对象
new File(File parent, String child) //根据父目录文件 + 子路径构成
new File(String parent, String chile) //根据父目录 + 子路径构成
createNewFile 创建新文件
● 应用案例演示. com.zzw.file.FileCreate.java
●需求分析
请在e盘下, 创建文件 news1.txt, news2.txt, news3.txt
, 用三种不同的方式创建.
●代码实现
public class FileCreate {
@Test
public void create01() {
//new File(String pathname) 根据路径创建一个File对象
File file = new File("E:/news1.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void create02() {
//new File(File parent, String child) 根据父目录文件+子路径构成
File parent = new File("E:/");
//这里的file对象, 在java程序中, 只是一个对象
//只有执行了createNewFile方法, 才会真正的, 在磁盘创建该文件
File file = new File(parent, "news2.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void create03() {
//new File(String parent, String child) 根据父目录+子路径构成
String parent = "E:/";
String chlid = "/news4.txt";
File file = new File(parent, chlid);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
获取文件的相关信息
●File类图
应用案例演示
●需求分析
如何获取文件的大小, 路径, 父File, 文件名, 是文件还是目录(目录本质也是文件, 一种特殊的文件), 是否存在.
●代码实现 com.hspedu.file.FileInformation.java
public class FileInformation {
@Test
public void info() {
//先创建文件对象
File file = new File("e:\\a\\news1.txt");
//调用相关方法, 得到对应信息
System.out.println("文件名--" + file.getName());//文件名--news1.txt
System.out.println("文件大小(字节)--" + file.length());//文件大小(字节)--3
System.out.println("文件绝对路径(String)--" + file.getAbsolutePath());//文件绝对路径(String)--e:\a\news1.txt
System.out.println("文件父级目录(String)--" + file.getParent());//文件父级目录(String)--e:\a
System.out.println("文件父级目录(File)--" + file.getParentFile());//文件父级目录(File)--e:\a
System.out.println("是不是一个文件--" + file.isFile());//是不是一个文件--true
System.out.println("是不是一个目录--" + file.isDirectory());//是不是一个目录--false
System.out.println("文件是否存在--" + file.exists());//文件是否存在--true
}
@Test
public void info2() {
//先创建文件对象
File file = new File("e:\\a");
//调用相关方法, 得到对应信息
System.out.println("目录名--" + file.getName());//目录名--a
System.out.println("目录大小(字节)--" + file.length());//目录大小(字节)--0
System.out.println("目录绝对路径(String)--" + file.getAbsolutePath());//目录绝对路径(String)--e:\a
System.out.println("目录父级目录(String)--" + file.getParent());//目录父级目录(String)--e:\
System.out.println("目录父级目录(File)--" + file.getParentFile());//目录父级目录(File)--e:\
System.out.println("是不是一个文件--" + file.isFile());//是不是一个文件--false
System.out.println("是不是一个目录--" + file.isDirectory());//是不是一个目录--true
System.out.println("目录是否存在--" + file.exists());//目录是否存在--true
}
}
目录的操作和文件删除
mkdir创建一级目录, mkdirs创建多级目录, delete删除空目录或文件
应用案例演示
●需求分析
1)判断 e:\news1.txt
是否存在, 如果存在就删除
2)判断 e:\demo02
是否存在, 存在就删除, 否则提示不存在
3)判断 e:\demo\a\b\c
目录是否存在, 如果存在就提示已经存在, 否则就创建
●代码实现 com.hspedu.file.FileOperation.java
public class FileOperation {
@Test
public void test01() {
//判断 e:\\news1.txt 是否存在, 如果存在就删除
File file = new File("e:/news1.txt");
if (file.exists()) {
System.out.println("文件存在");
if (file.delete()) {
System.out.println("文件删除成功");
} else {
System.out.println("文件删除失败");
}
} else {
System.out.println("该文件不存在");
}
}
@Test
public void test02() {
//判断 e:\\demo02 是否存在, 存在就删除, 否则提示不存在
File file = new File("e:/demo02");
if (file.exists()) {
System.out.println("目录存在");
if (file.delete()) {
System.out.println("目录删除成功");
} else {
System.out.println("目录删除失败");
}
} else {
System.out.println("目录不存在");
}
}
@Test
public void test03() {
//判断 e:\\demo\\a\\b\\c 目录是否存在, 如果存在就提示已经存在, 否则就创建
File file = new File("e:/demo/a/b/c");//在io中, 目录是一种特殊的文件
if (file.exists()) {
System.out.println("目录已存在");
} else {
if (file.mkdirs()) {
System.out.println("目录创建成功");
} else {
System.out.println("目录创建失败");
}
}
}
}
mkdir和mkdirs的区别
1、mkdir()
如果父目录不存在,则创建失败。
2、mkdirs()
如果父目录不存在,连同父目录一起创建。(创建文件夹多用此)
mkdir:只能用来创建文件夹,且只能创建一级目录,如果上级不存在,就会创建失败。
mkdirs:只能用来创建文件夹,且能创建多级目录 ,如果上级不存在,就会自动创建。(创建文件夹多用此)
IO流原理及流的分类
Java IO流原理
1.I/O是Input/Output的缩写, I/O技术是非常实用的技术, 用于处理数据传输. 如读/写文件, 网络通讯等.
2.Java程序中, 对于数据的输入/输出操作以 “流(stream)” 的方式进行.
3.Java.io包下提供了各种 “流” 类和接口, 用以获取不同种类的数据, 并通过方法输入或输出数据.
4.输入input:读取外部数据 (磁盘、光盘等存储设备的数据) 到程序 (内存) 中;
5.输出output:将程序 (内存) 数据输出到磁盘、光盘等存储设备中;
流的分类
√ 按操作数据单位不同分为:字节流 (8 bit) 二进制文件,字符流 (按字符) 文本文件
√ 按数据流的流向不同分为:输入流、输出流
√ 按流的角色不同分为:节点流、处理流/包装流
(抽象基类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
1)Java的IO流共涉及40多个类, 实际上非常规则, 都是从如上4个抽象基类派生的.
2)由这四个抽象基类派生出来的子类名称都是以其父类名作为子类名后缀.
IO流体系图-常用的类
1.IO流体系图
2.文件 VS 流
FileInputStream介绍
●InputStream抽象类是所有类字节输入流的超类.
FileInputStream 应用实例
●需求分析
请使用 FileInputStream
读取 e:\\hello.txt
文件, 并将文件内容显示到控制台.
●代码实现 com.hspedu.inputstream_.FileInputStream_.java
public class FileInputStream_ {
/**
* 1.使用 FileInputStream 读取 e:\\hello.txt 文件, 并将文件内容显示到控制台.
* 2.文件 --> 程序
* 3.单个字节的读取, 效率比较低
* ==>推荐使用 read(byte[] b)
*/
@Test
public static void readFile01(String[] args) {
String file = "e:/hello.txt";
int readData = 0;
//局部变量必须初始化
FileInputStream fileInputStream = null;
try {
//根据 文件/路径 创建FileInputStream对象, 用于读取文件
fileInputStream = new FileInputStream(file);
//从该输入流读取一个字节的数据. 如果没有输入可用, 此方法将阻止.
//如果返回-1, 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char)readData);//转成 char 显示
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//关闭文件流, 释放资源
try {
fileInputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 使用read(byte[] b) 读取文件, 提高效率
*/
@Test
public void readFile02() {
String file = "e:/hello.txt";
//字节数组
byte[] buf = new byte[8];//一次读取8个字节
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//根据 文件/路径 创建FileInputStream对象, 用于读取文件
fileInputStream = new FileInputStream(file);
//从该输入流读取最多buf.length字节的数据. 如果没有输入可用, 此方法将阻塞.
//如果返回-1, 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//关闭文件流, 释放资源
try {
fileInputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
FileOutStream介绍
FileOutStream 应用实例1
●需求分析
请使用 FileOutStream
在 e:\a\b.txt
文件中, 写入 hello, world
. 如果文件不存在, 就创建文件.(注意: 前提是目录已经存在).
●代码实现 com.hspedu.outputstream_.FileOutStream_.java
public class FileOutputStream01 {
/**
* 演示使用FileOutputStream将数据写入到文件中
* 如果该文件不存在, 则创建文件
*/
@Test
public void writeFile() {
String filePath = "e:\\a\\b.txt";
FileOutputStream fileOutputStream = null;
try {
File file = new File(filePath);
File parentDir = file.getParentFile();//获取父级目录
if (!parentDir.exists()) {//如果父级目录不存在
parentDir.mkdirs();//创建父目录及其所有不存在的父目录
}
if (!file.exists()) {//判断文件是否存在
file.createNewFile();
System.out.println("文件创建成功");
} else {
System.out.println("文件已经存在");
}
//得到FileOutputStream对象
//说明
//创建方式1. new FileOutputStream(filePath); 当写入内容时, 会覆盖原来的内容
//创建方式2. new FileOutputStream(filePath, true); 当写入内容时, 追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
fileOutputStream.write('z');//字符相当于数字
//写入字符串
String str = "hello, world";
//str.getBytes() 可以把字符串转成字节数组
fileOutputStream.write(str.getBytes());
//write(byte b[], int off, int len)
//将len长度的字节从位于偏移量off的指定字节数组写入此文件输出流
fileOutputStream.write(str.getBytes(), 0, 3);
System.out.println("写入成功");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
1.写入字符串时,可以定义起始位置
write(byte b[], int off, int len)
off是偏移量(即开始的位置),len是写入的个数
2.将数据写入文件时,如果希望写入文件的末尾而不是开头
FileOutStream 应用实例2
●需求分析/图解
编程完成图片/音乐
的拷贝. 将 e:\\Koala.jpg
拷贝 d:\
●代码实现 com.hspedu.outputstream_.FileCopy.java
public class FileCopy {
public static void main(String[] args) {
//需求
//完成文件拷贝, 将e:/a/1.jpg 拷贝到 d:/image/2.jpg
//思路分析
//1.创建文件的输入流, 将文件读入到程序
//2.创建文件的输出流, 将读取到的文件数据, 写入到指定的文件.
//代码实现
String srcFilePath = "e:/a/1.jpg";
String destFilePath = "d:/image/2.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
byte[] buf = new byte[1024];//一次读取1024个字节
int readLen = 0;
fileInputStream = new FileInputStream(srcFilePath);
File dir = new File(destFilePath);
File parentDir = dir.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
fileOutputStream = new FileOutputStream(destFilePath);
while ((readLen = fileInputStream.read(buf)) != -1) {
//读取到后, 就写入到文件, 通过fileOutputStream
//即 一边读, 一边写
fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法
}
System.out.println("拷贝成功");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
//关闭输入流和输出流, 释放资源
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
FileReader和FileWriter介绍
FileReader相关方法
1)new FileReader(File/String): 覆盖模式, 相当于流的指针在首端
2)read: 每次读取单个字符, 返回该字符, 如果读到文件末尾返回-1.
3)read(char[]): 批量读取多个字符到数组, 返回读取到的字符数, 如果读到文件末尾返回-1.
相关API:
1)new String(char[]): 将char[]转换成String
2)new String(char[], off, len): 将char[]的指定部分转换成String
FileWriter相关方法
1)new FileWriter(File/String): 覆盖模式, 相当于流的指针在首端
2)new FileWriter(File/String, true): 追加模式, 相当于流的指针在尾端
3)write(int): 写入单个字符
4)write(char[]): 写入指定数组
5)write(char[], off, len): 写入指定数组的指定部分
6)write(String): 写入整个字符串
7)write(String, off, len): 写入指定字符串的指定部分
相关API:
1)String.toCharArray: 将String转换成char[]
注意:
FileWriter使用后, 必须要关闭(close)或刷新(flush), 否则写入不到指定的文件.
FileReader和FileWriter应用案例
●需求分析
请使用 FileReader
读取 e:\\story.txt
的内容, 并将文件内容显示到控制台.
●代码实现 com.hspedu.reader_.FileReader_.java
public class FileReader_ {
/**
* 读取 e:\\story.txt 的内容, 并将文件内容显示到控制台.
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
try {
//创建FileReader对象
fileReader = new FileReader(filePath);
//循环读取
//使用read(), 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 读取 e:\\story.txt 的内容, 并将文件内容显示到控制台.
* 字符数组读取文件
*/
@Test
public void readFile02() {
String filePath = "e:/story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
try {
//创建FileReader对象
fileReader = new FileReader(filePath);
//循环读取. 使用read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束.
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
●需求分析
请使用 FileWriter
在 e:\a\note.txt
文件中, 写入 风雨过后, 定见彩虹
. 注意细节.
●代码实现 com.hspedu.writer_.FileWriter_.java
public class FileWriter_ {
@Test
public void writeFile() {
String filePath = "e:\\a\\note.txt";
//创建FileWriter对象
FileWriter fileWriter = null;
String str = "青蛙王子和公主的幸福生活.";
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath, true);//追加模式
//write(int):写入单个字符
fileWriter.write(97);//a
//write(char[]):写入指定数组
fileWriter.write(chars);
//write(char[],off,len):写入指定数组的指定部分
fileWriter.write(chars, 0, 2);
//write(String):写入整个字符串
fileWriter.write(str);
//write(String,off,len):写入指定字符串的指定部分
fileWriter.write(str, 0, 7);
//在数据量大的情况下, 可以使用循环操作
System.out.println("写入成功");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
//对应FileWriter, 一定要关闭流, 或者flush()才能真正地把数据写入到文件
if (fileWriter != null) {
//fileWriter.flush();
fileWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
注意: 关闭(close)文件流, 等价 flush() + 关闭. flush() 方法会强制将缓冲区中的数据写入到文件中,但并不关闭流。这意味着你可以继续向流中写入数据。一般来说,如果你确定还会继续使用流,而且不想关闭它,可以使用 flush() 方法。但如果你已经不再需要流,最好调用 close() 方法,以确保资源被释放,并且所有数据都被写入到文件中。
注意: FileWriter使用过后,一定要关闭(close)或者刷新(flush),否则数据流不能写入到文件!在使用 FileWriter 写入数据到文件时,你需要调用 close() 方法或者 flush() 方法来确保数据被写入到文件中。这是因为 FileWriter 使用了缓冲区来提高性能,而缓冲区中的数据需要通过 close() 或者 flush() 才会真正被写入到文件中。
节点流与处理流
基本介绍
1.节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
2.包装流(也叫处理流) 是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能, 也更加灵活, 如BufferedReader、BufferedWriter
节点流和处理流一览图
节点流和处理流的区别和联系
1.节点流是底层流/低级流,直接跟数据源相接;
2.处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
3.处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连;【模拟修饰器设计模式, 小伙伴就会非常清楚】.
●代码实现
1.新建com.zzw.Reader_.java
public abstract class Reader_ {
public void readFile() {}
public void readString() {}
//在Reader_抽象类中, 使用read方法统一管理.
//后面在调用时, 利于对象动态绑定机制, 绑定到对应的实现子类即可.
}
2.新建com.zzw.FileReader_.java
public class FileReader_ extends Reader_ {
@Override
public void readFile() {
System.out.println("对文件进行读取");
}
}
3.新建com.zzw.StringReader_.java
处理流的功能主要体现在以下两个方面
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率;
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便;
处理流-BufferedReader和bufferedWriter
1.BufferedReader和BufferWriter属于字符流,是按照字符来读取数据的;
2.关闭流时, 只需要关闭外层流即可[后面看源码]
●需求分析
请使用 BufferedReader
读取 e:\\hello.txt
文本文件, 并将文件内容显示到控制台.
●代码实现 com.hspedu.reader_.BufferedReader_.java
2. 关闭处理流的时候,只需要关闭外层流即可;源码:
in就是我们传入的new FileReader(filePath)i;
2.4.3 BufferedWriter
- BufferedWriter构造器不支持传入一个boolean参数,所以在字节流处添加;
2.4.4 Buffered字符流拷贝
BufferedReader和BufferedWriter只能操作字符,字符流只能读取文本;
不能读取二进制文件【音频,视频,文档,图片】,造成文件损坏;
2.5 BufferedInputStream
2.6 BufferedOutputStream
2.6.1 Buffered字节流拷贝
字节流可以操作二进制文件,但可以操作文本文件吗?可以:不要把汉字的字节拆开就不会乱码!
2.7 对象流
- 序列化和反序列化
- 序列化就是在保存数据的同时,还能保存数据的类型;
- 反序列化就是在恢复数据时,恢复数据的值和数据类型;
- 需要让某个对象支持序列化机制,就必须让其类是可序列化的;为了让某类是可序列化的,则必须让其类下面两个接口之一:
- 实现Serializable接口,这是一个标记接口,没有方法;
- 实现Externalizable接口,这个接口内有方法,需要实现,所以一般不采用;
2.7.1 ObjectOutputStream
使用ObjectOutputSteam序列化基本数据类型和一个Dog对象(age),并保存到data.txt文件中
2.7.2 ObjectInputStream
使用ObjectInputStream读取data.txt并反序列化恢复文件;
2.7.3 对象流使用细节
- 读写顺序要一致;
- 要求序列化或反序列化对象,需要实现Serializable;
- 序列化的类中建议添加SerialVersionUID,以提高版本的兼容性;
- 序列化对象时,默认将里面所有的属性都进行序列化,除了static和transient修饰的成员;
- 序列化对象时,要求里面属性的类型也需要实现序列化接口;
- 序列化具备可继承性,也就是如果某一个类实现了序列化,那么它的子类默认实现序列化;
2.8 标准输入输出流
2.9 转换流
2.9.1 InputStreamReader
InputStreamReader是Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
读取文件内容默认是按照utf-8处理,如果把文件编码改成gbk,那么读取文件时就会出现中文乱码,这时需要用到转换流
2.9.2 OutputStreamWriter
OutputStreamWriter是Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流);
当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
打印流
打印流只有输出流,没有输入流
3.1 字节打印流
3.2 字符打印流
3.1 Properties类
- 配置文件的读取写入☞传统方案
- 引入Properties
Properties是Hashtable的子类,配置文件的格式:
键=值
键=值
键值对不需要有空格,值不需要用引号括起来,默认是String
load | 加载配置文件的键值对到Properties对象 |
---|---|
list | 将数据显示到指定设备 |
getProperty(key) | 根据建获取值(键、值均是String类型) |
get(key) | 根据建获取值(返回Object对象) |
setPropertiy(key,value) | 设置键值对到Properties对象, 没有key相当于添加 |
store | 将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,则会将汉字存储为unicode码 |
get(key)返回Object类型的对象
Properties父类是Hashtable,底层是Hashtable
setProperties()底层源码是Hashtable中的put方法