本文笔记来自尚硅谷视频 https://www.bilibili.com/video/BV1Rx4y1V745?p=175。
(1)File 类及本章下的各种流,都定义在 java.io
包下。
(2)一个 File 对象代表硬盘或网络中可能存在的一个文件或者文件目录(俗称文件夹),与平台无关。(体会万事万物皆对象)
(3)File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流,并且 File 对象可以作为参数传递给流的构造器。
(4)想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象,但是 Java 程序中的一个 File 对象,可能没有一个真实存在的文件或目录。
(1)File 中的常用的构造器如下:
(2)有关路径的说明如下:
(3)举例:
package com.atguigu.file;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class FileObjectTest {
@Test
public void test01() {
//文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
//文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
//通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
//通过父级 File 对象和子路径字符串
File parentDir = new File("d:\\aaa");
String childFile = "bbb.txt";
File file4 = new File(parentDir, childFile);
}
}
@Test
public void test02() throws IOException {
File f1 = new File("d:\\atguigu\\javase\\HelloIO.java"); //绝对路径
System.out.println("文件/目录的名称:" + f1.getName());
System.out.println("文件/目录的构造路径名:" + f1.getPath());
System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f1.getParent());
}
test02() 的输出结果如下:
文件/目录的名称:HelloIO.java
文件/目录的构造路径名:d:\atguigu\javase\HelloIO.java
文件/目录的绝对路径名:d:\atguigu\javase\HelloIO.java
文件/目录的父目录名:d:\atguigu\javase
@Test
public void test03() throws IOException {
File f2 = new File("/HelloIO.java"); //绝对路径,从根路径开始
System.out.println("文件/目录的名称:" + f2.getName());
System.out.println("文件/目录的构造路径名:" + f2.getPath());
System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f2.getParent());
}
test03() 的输出结果如下:
文件/目录的名称:HelloIO.java
文件/目录的构造路径名:\HelloIO.java
文件/目录的绝对路径名:E:\HelloIO.java
文件/目录的父目录名:\
@Test
public void test04() throws IOException {
File f3 = new File("HelloIO.java"); //相对路径
System.out.println("user.dir =" + System.getProperty("user.dir"));
System.out.println("文件/目录的名称:" + f3.getName());
System.out.println("文件/目录的构造路径名:" + f3.getPath());
System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f3.getParent());
}
test04() 的输出结果如下:
user.dir =E:\IDEAData\javase
文件/目录的名称:HelloIO.java
文件/目录的构造路径名:HelloIO.java
文件/目录的绝对路径名:E:\IDEAData\javase\HelloIO.java
文件/目录的父目录名:null
@Test
public void test05() throws IOException {
File f5 = new File("HelloIO.java"); //相对路径
System.out.println("user.dir =" + System.getProperty("user.dir"));
System.out.println("文件/目录的名称:" + f5.getName());
System.out.println("文件/目录的构造路径名:" + f5.getPath());
System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath());
System.out.println("文件/目录的父目录名:" + f5.getParent());
}
test05() 的输出结果如下:
user.dir =E:\IDEAData\javase
文件/目录的名称:HelloIO.java
文件/目录的构造路径名:HelloIO.java
文件/目录的绝对路径名:E:\IDEAData\javase\HelloIO.java
文件/目录的父目录名:null
(4)注意事项
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
(1)相关方法以及功能如下:
相关方法 | 功能 |
---|---|
public String getName() | 获取名称 |
public String getPath() | 获取路径 |
public String getAbsolutePath() | 获取绝对路径 |
public File getAbsoluteFile() | 获取绝对路径表示的文件 |
public String getParent() | 获取上层文件目录路径,若无,返回null |
public long length() | 获取文件长度(即字节数),不能获取目录的长度 |
public long lastModified() | 获取最后一次的修改时间,毫秒值 |
(2)如果 File 对象代表的文件或目录存在,则 File 对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为 File 对象的属性赋值,否则除了路径和名称,File 对象的其他属性将会保留默认值。
(3)举例:
D:/aaa/ 目录下的 bbb.txt 文件存在。
package com.atguigu.file;
import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class FileInfoMethod {
public static void main(String[] args) {
File f = new File("D:/aaa/bbb.txt");
System.out.println("文件构造路径:" + f.getPath());
System.out.println("文件名称:" + f.getName());
System.out.println("文件长度:" + f.length() + "字节");
System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()), ZoneId.of("Asia/Shanghai")) + "\n");
File f2 = new File("d:/aaa");
System.out.println("目录构造路径:" + f2.getPath());
System.out.println("目录名称:" + f2.getName());
System.out.println("目录长度:" + f2.length() + "字节");
System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()), ZoneId.of("Asia/Shanghai")));
}
}
输出结果如下:
文件构造路径:d:\aaa\bbb.txt
文件名称:bbb.txt
文件长度:0字节
文件最后修改时间:2023-02-18T19:22:00.050
目录构造路径:d:\aaa
目录名称:aaa
目录长度:0字节
文件最后修改时间:2023-02-18T19:22:00.050
(1)相关方法以及功能如下:
相关方法 | 功能 |
---|---|
public String[] list() | 返回一个 String 数组,表示该 File 目录中的所有子文件或目录 |
public File[] listFiles() | 返回一个 File 数组,表示该 File 目录中的所有的子文件或目录 |
(2)示例:
public class DirListFiles {
public static void main(String[] args) {
File dir = new File("D:/software/JDK/JDK8");
String[] subs = dir.list();
for (String sub : subs) {
System.out.println(sub);
}
}
}
输出结果如下:
bin
COPYRIGHT
include
javafx-src.zip
jre
lib
LICENSE
README.html
release
src.zip
THIRDPARTYLICENSEREADME-JAVAFX.txt
THIRDPARTYLICENSEREADME.txt
public boolean renameTo(File dest):把文件重命名为指定的文件路径。
(1)相关方法以及功能如下:
相关方法 | 功能 |
---|---|
public boolean exists() | 此 File 表示的文件或目录是否实际存在 |
public boolean isDirectory() | 此 File 表示的是否为目录 |
public boolean isFile() | 此 File 表示的是否为文件 |
public boolean canRead() | 判断是否可读 |
public boolean canWrite() | 判断是否可写 |
public boolean isHidden() | 判断是否隐藏 |
(2)示例:
D:/aaa/ 目录下的 bbb.txt 文件存在。
public class FileIs {
public static void main(String[] args) {
File f = new File("d:\\aaa\\bbb.txt");
File f2 = new File("d:\\aaa");
// 判断是否存在
System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
System.out.println("d:\\aaa 是否存在:"+f2.exists());
// 判断是文件还是目录
System.out.println("d:\\aaa 文件?:"+f2.isFile());
System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
}
}
输出结果如下:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true
(1)相关方法以及功能如下:
相关方法 | 功能 |
---|---|
public boolean createNewFile() | 创建文件。若文件存在,则不创建,返回 false |
public boolean mkdir() | 创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建 |
public boolean mkdirs() | 创建文件目录。如果上层文件目录不存在,一并创建 |
public boolean delete() | 删除文件或者文件夹 |
删除注意事项:
① Java 中的删除不会放入回收站。
② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
(2)示例:
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// 文件的创建
File f = new File("aaa.txt");
System.out.println("aaa.txt是否存在: " + f.exists());
System.out.println("aaa.txt是否创建: " + f.createNewFile());
System.out.println("aaa.txt是否存在: " + f.exists());
// 目录的创建
File f2 = new File("newDir");
System.out.println("newDir是否存在: " + f2.exists());
System.out.println("newDir是否创建: " + f2.mkdir());
System.out.println("newDir是否存在: " + f2.exists());
// 创建一级目录
File f3 = new File("newDira\\newDirb");
System.out.println("newDira\\newDirb创建: " + f3.mkdir());
File f4 = new File("newDir\\newDirb");
System.out.println("newDir\\newDirb创建: " + f4.mkdir());
// 创建多级目录
File f5 = new File("newDira\\newDirb");
System.out.println("newDira\\newDirb创建: " + f5.mkdirs());
// 文件的删除
System.out.println("aaa.txt删除: " + f.delete());
// 目录的删除
System.out.println("newDir删除: " + f2.delete());
System.out.println("newDir\\newDirb删除: " + f4.delete());
}
}
输出结果如下:
aaa.txt是否存在: false
aaa.txt是否创建: true
aaa.txt是否存在: true
newDir是否存在: false
newDir是否创建: true
newDir是否存在: true
newDira\newDirb创建: false
newDir\newDirb创建: true
newDira\newDirb创建: true
aaa.txt删除: true
newDir删除: false
newDir\newDirb删除: true
API中说明:对于 delete 方法,如果此 File 表示目录,则目录必须为空才能删除。
(1)在 Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行,可以看做是一种数据的流动,如下图所示。
(2)I/O 流中的 I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输,例如读/写文件,网络通讯等。
(1)java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
(2)按数据的流向的不同分为:输入流和输出流:
(3)按操作数据单位的不同分为:字节流 (8 bit) 和字符流 (16 bit)。
(3)按 I/O 流的角色不同分为:节点流和处理流:
(4)小结:
(1)Java 的 I/O 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。
抽象基类 | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
(2)由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
常用的节点流:
注意:对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
常用的处理流:
Java 提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件,不能操作图片,视频等非文本文件。常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py等。需要注意的是 .doc、.xls、.ppt 这些都不是文本文件。
java.io.Reader 抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法:
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法:
(1)java.io.FileReader 类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
(2)举例:读取 hello.txt 文件中的字符数据,并显示在控制台上。
public class FileReaderWriterTest {
//实现方式 1
@Test
public void test1() throws IOException {
//1.创建File类的对象,对应着物理磁盘上的某个文件
File file = new File("hello.txt");
//2.创建FileReader流对象,将File类的对象作为参数传递到 FileReader 的构造器中
FileReader fr = new FileReader(file);
//3.通过相关流的方法,读取文件中的数据
// int data = fr.read(); //每调用一次读取一个字符
// while (data != -1) {
// System.out.print((char) data);
// data = fr.read();
// }
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
//4. 关闭相关的流资源,避免出现内存泄漏
fr.close();
}
//实现方式 2:在方式 1 的基础上改进,使用try-catch-finally处理异常,保证流是可以关闭的
@Test
public void test2() {
FileReader fr = null;
try {
//1.创建File类的对象,对应着物理磁盘上的某个文件
File file = new File("hello.txt");
//2.创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
fr = new FileReader(file);
//3.通过相关流的方法,读取文件中的数据
/*
* read():每次从对接的文件中读取一个字符,并将此字符返回。
* 如果返回值为 -1,则表示文件到了末尾,可以不再读取。
* */
// int data = fr.read();
// while(data != -1){
// System.out.print((char)data);
// data = fr.read();
// }
int data;
while ((data = fr.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭相关的流资源,避免出现内存泄漏
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//实现方式 3:调用 read(char[] cbuf),每次从文件中读取多个字符
@Test
public void test3() {
FileReader fr = null;
try {
//1.创建File类的对象,对应着物理磁盘上的某个文件
File file = new File("hello.txt");
//2.创建FileReader流对象,将 File 类的对象作为参数传递到 FileReader 的构造器中
fr = new FileReader(file);
//3.通过相关流的方法,读取文件中的数据
char[] cbuf = new char[5];
//read(char[] cbuf): 每次将文件中的数据读入到cbuf数组中,并返回读入到数组中的字符的个数
int len; //记录每次读入的字符的个数
while ((len = fr.read(cbuf)) != -1) {
//处理char[]数组即可
//错误:
// for(int i = 0;i < cbuf.length;i++){
// System.out.print(cbuf[i]);
// }
//错误:
// String str = new String(cbuf);
// System.out.print(str);
//正确:
// for(int i = 0;i < len;i++){
// System.out.print(cbuf[i]);
// }
//正确:
String str = new String(cbuf, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭相关的流资源,避免出现内存泄漏
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
不同实现方式的类比:
(1)java.io.FileWriter 类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
(2)举例:
public class FWWrite {
//注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
@Test
public void test01() throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter(new File("fw.txt"));
// 写出数据
fw.write(97); // 写出第 1 个字符
fw.write('b'); // 写出第 2 个字符
fw.write('C'); // 写出第 3 个字符
fw.write(30000); // 写出第 4 个字符,中文编码表中 30000 对应第一个汉字
//关闭资源
fw.close();
}
//注意:应该使用 try-catch-finally 处理异常,这里出于方便阅读代码,使用了 throws 的方式
@Test
public void test02() throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter(new File("fw.txt"));
// 字符串转换为字节数组
char[] chars = "尚硅谷".toCharArray();
// 写出字符数组
fw.write(chars); // 尚硅谷
// 写出从索引 1 开始,2 个字符
fw.write(chars, 1, 2); // 硅谷
// 关闭资源
fw.close();
}
//注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
@Test
public void test03() throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串
String msg = "尚硅谷";
// 写出字符数组
fw.write(msg); //尚硅谷
// 写出从索引1开始,2个字符。
fw.write(msg, 1, 2); // 硅谷
// 关闭资源
fw.close();
}
@Test
public void test04() {
FileWriter fw = null;
try {
//1.创建File的对象
File file = new File("personinfo.txt");
//2.创建FileWriter的对象,将File对象作为参数传递到FileWriter的构造器中
//如果输出的文件已存在,则会对现有的文件进行覆盖
fw = new FileWriter(file);
// fw = new FileWriter(file,false);
//如果输出的文件已存在,则会在现有的文件末尾写入数据
// fw = new FileWriter(file,true);
//3.调用相关的方法,实现数据的写出操作
//write(String str) / write(char[] cbuf)
fw.write("I love you,");
fw.write("you love him.");
fw.write("so sad".toCharArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源,避免内存泄漏
try {
if (fw != null)
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
(1)因为出现流资源的调用,为了避免内存泄漏,需要使用 try-catch-finally 处理异常。
(2)对于输入流来说,File 类的对象必须在物理磁盘上存在,否则执行就会报 FileNotFoundException。如果传入的是一个目录,则会报 IOException 异常。
(3)对于输出流来说,File类的对象是可以不存在的。
(1)因为内置缓冲区的原因,如果 FileWriter 不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush() 方法了。
注意:即便是 flush() 方法写出了数据,操作的最后还是要调用 close 方法,释放系统资源。
(2)举例:
public class FWWriteFlush {
//注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
@Test
public void test() throws IOException {
//使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
//写出数据,通过 flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第 2 个字符,写出成功
fw.flush();
//写出数据,通过close
fw.write('关'); // 写出第 1 个字符
fw.close();
fw.write('闭'); // 继续写出第 2 个字符,【报错】java.io.IOException: Stream closed
fw.close();
}
}
如果我们读取或写出的数据是非文本文件,例如 .doc、.xls、.ppt,则 Reader、Writer 就无能为力了,必须使用字节流。
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
说明:close() 方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
说明:close() 方法,当完成流的操作时,必须调用此方法,释放系统资源。
(1)java.io.FileInputStream 类是文件输入流,从文件中读取字节。
(2)举例:read.txt 文件中的内容为 abcde。
public class FISRead {
//注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
@Test
public void test() throws IOException {
//使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
//读取数据,返回一个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾,返回-1
read = fis.read();
System.out.println(read);
//关闭资源
fis.close();
/*
文件内容:abcde
输出结果:
a
b
c
d
e
-1
*/
}
@Test
public void test02()throws IOException{
//使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
//定义变量,保存数据
int b;
//循环读取
while ((b = fis.read())!=-1) {
System.out.println((char)b);
}
//关闭资源
fis.close();
}
@Test
public void test03()throws IOException{
//使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
//定义变量,作为有效个数
int len;
//定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
//循环读取
while (( len= fis.read(b))!=-1) {
//每次读取后,把数组变成字符串打印
System.out.println(new String(b));
}
//关闭资源
fis.close();
/*
输出结果:
ab
cd
ed
最后错误数据`d`,是由于最后一次读取时,只读取一个字节`e`,数组中,
上次读取的数据没有被完全替换,所以要通过`len` ,获取有效的字节
*/
}
@Test
public void test04()throws IOException{
//使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
//定义变量,作为有效个数
int len;
//定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
//循环读取
while (( len= fis.read(b))!=-1) {
//每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
}
//关闭资源
fis.close();
/*
输出结果:
ab
cd
e
*/
}
}
(1)java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。
(2)举例:
public class FOSWrite {
//注意:应该使用 try-catch-finally 处理异常。这里出于方便阅读代码,使用了 throws 的方式
@Test
public void test01() throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
/* 输出结果:abc*/
}
@Test
public void test02() throws IOException {
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
//字符串转换为字节数组
byte[] b = "abcde".getBytes();
//写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b, 2, 2);
//关闭资源
fos.close();
}
//这段程序如果多运行几次,每次都会在原来文件末尾追加 abcde
@Test
public void test03() throws IOException {
//使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt", true);
//字符串转换为字节数组
byte[] b = "abcde".getBytes();
fos.write(b);
//关闭资源
fos.close();
}
//使用FileInputStream\FileOutputStream,实现对文件的复制
@Test
public void test05() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1.造文件-造流
//复制图片:成功
// fis = new FileInputStream(new File("pony.jpg"));
// fos = new FileOutputStream(new File("pony_copy1.jpg"));
//复制文本文件:成功
fis = new FileInputStream(new File("hello.txt"));
fos = new FileOutputStream(new File("hello1.txt"));
//2. 复制操作(读、写)
byte[] buffer = new byte[1024];
int len;//每次读入到buffer中字节的个数
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
// String str = new String(buffer,0,len);
// System.out.print(str);
}
System.out.println("复制成功");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//3. 关闭资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fis != null)
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
(1)为了提高数据读写的速度,Java API 提供了带缓冲功能的流类——缓冲流。缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
(2)缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(默认使用 8192 个字节即 8 Kb 的缓冲区),通过缓冲区读写,减少系统 I/O 次数,从而提高读写的效率。
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
...
}
(1)BufferedInputStream 类的 2 个构造函数如下:
举例:
//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
//创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg"));
(2)BufferedReader 类的 2 个构造函数如下:
举例:
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
查询 API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件 (375 MB),测试它的效率。
//方法1:使用FileInputStream\FileOutputStream实现非文本文件的复制
public void copyFileWithFileStream(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1. 造文件-造流
fis = new FileInputStream(new File(srcPath));
fos = new FileOutputStream(new File(destPath));
//2. 复制操作(读、写)
byte[] buffer = new byte[100];
int len;//每次读入到buffer中字节的个数
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("复制成功");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//3. 关闭资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fis != null)
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void test1(){
String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4";
String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习2.mp4";
long start = System.currentTimeMillis();
copyFileWithFileStream(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start)); // 7677 ms
}
//方法2:使用BufferedInputStream\BufferedOuputStream实现非文本文件的复制
public void copyFileWithBufferedStream(String srcPath, String destPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.读写操作
int len;
byte[] buffer = new byte[100];
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源(如果有多个流,我们需要先关闭外面的流,再关闭内部的流)
try {
if (bos != null)
bos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (bis != null)
bis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void test2() {
String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4";
String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习2.mp4";
long start = System.currentTimeMillis();
copyFileWithBufferedStream(srcPath, destPath);
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start)); // 415 ms
}
(1)字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
(2)示例如下:
public class BufferedIOLine {
@Test
public void testReadLine() throws IOException {
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 定义字符串,保存读取的一行文字
String line;
// 循环读取,读取到最后返回 null
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 释放资源
br.close();
}
@Test
public void testNewLine() throws IOException {
// 创建流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
// 写出数据
bw.write("尚");
// 写出换行
bw.newLine();
bw.write("硅");
bw.newLine();
bw.write("谷");
bw.newLine();
// 释放资源
bw.close();
}
}
说明:
(1)引入情况1:使用 FileReader 读取项目中的文本文件。由于 IDEA 设置中针对项目设置了 UTF-8 编码,当读取 Windows 系统中创建的文本文件时,如果 Windows 系统默认的是 GBK 编码,则读入内存中会出现乱码。
public class Problem {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("E:\\File_GBK.txt");
int data;
while ((data = fileReader.read()) != -1) {
System.out.print((char)data);
}
fileReader.close();
}
}
输出结果:
���
那么如何读取GBK编码的文件呢?
(2)引入情况 2:针对文本文件,现在使用一个字节流进行数据的读入,希望将数据显示在控制台上。此时针对包含中文的文本数据,可能会出现乱码。
具体来说:
(1)转换流 java.io.InputStreamReader 是 Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
(2)构造器
(3)举例
//使用默认字符集
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"));
//使用指定字符集
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt"), "GBK");
(4)示例代码
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//定义文件路径,文件为 gbk 编码
String fileName = "E:\\file_gbk.txt";
//方式1:
//创建流对象,默认 UTF8 编码
InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));
//定义变量,保存字符
int charData;
//使用默认编码字符流读取,乱码
while ((charData = isr1.read()) != -1) {
System.out.print((char) charData); // ��Һ�
}
isr1.close();
//方式2:
//创建流对象,指定 GBK 编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName), "GBK");
//使用指定编码字符流读取,正常解析
while ((charData = isr2.read()) != -1) {
System.out.print((char) charData);// 大家好
}
isr2.close();
}
}
(1)转换流 java.io.OutputStreamWriter 是 Writer 的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
(2)构造器
(3)举例
//使用默认字符集
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
//使用指定的字符集
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt"), "GBK");
(4)示例代码
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
//定义文件路径
String FileName = "E:\\out_utf8.txt";
//创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
//写出数据
osw.write("你好"); // 保存为6个字节
osw.close();
//定义文件路径
String FileName2 = "E:\\out_gbk.txt";
//创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new
FileOutputStream(FileName2),"GBK");
//写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}
(1)计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。
(2)相关术语
(1)字符集 (Charset):也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
(2)计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有 ASCII 字符集、GBK 字符集、Unicode 字符集等。可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
(3)常见字符集
(4)举例:
Unicode 符号范围(十六进制) | UTF-8 编码方式(二进制) |
---|---|
0000 0000 - 0000 007F | 0xxxxxxx(兼容原来的 ASCII) |
0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
(5)小结
注意:在中文操作系统上,ANSI(美国国家标准学会、AMERICAN NATIONAL STANDARDS INSTITUTE: ANSI)编码即为 GBK;在英文操作系统上,ANSI 编码即为 ISO-8859-1。
(1)问题引入:如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
String name = "巫师";
Student stu = new Student("张三", 23, 89);
(2)Java 提供了数据流和对象流来处理这些类型的数据:
说明:对象流的强大之处就是既可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
(1)ObjectOutputStream 中的构造器:
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
(2)ObjectOutputStream 中的方法:
(3)ObjectInputStream 中的构造器:
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
(4)ObjectInputStream 中的方法:
对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。
序列化是 RMI(Remote Method Invoke,远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。序列化的好处在于可以将任何实现了 Serializable 接口的对象转化为字节数据,使其在保存和传输时可被还原。
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable 接口。Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException。
举例 1
public class ReadWriteDataOfAnyType {
@Test
public void save() throws IOException {
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
oos.writeUTF(name);
oos.writeInt(age);
oos.writeChar(gender);
oos.writeInt(energy);
oos.writeDouble(price);
oos.writeBoolean(relive);
oos.close();
}
@Test
public void reload() throws IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
String name = ois.readUTF();
int age = ois.readInt();
char gender = ois.readChar();
int energy = ois.readInt();
double price = ois.readDouble();
boolean relive = ois.readBoolean();
System.out.println(name + "," + age + "," + gender + "," + energy + "," + price + "," + relive);
ois.close();
}
}
举例 2
① 创建 Employee 类
package com.atguigu.serial;
import java.io.Serializable;
public class Employee implements Serializable {
//static final long serialVersionUID = 23234234234L;
// static 修饰的类变量,不会被序列化
public static String company;
public String name;
public String address;
// transient 瞬态修饰成员,不会被序列化
public transient int age;
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
", company=" + company +
'}';
}
}
② 进行序列化与反序列化
public class ReadWriteObject {
@Test
public void save() throws IOException {
Employee.setCompany("尚硅谷");
Employee e = new Employee("小谷姐姐", "宏福苑", 23);
//创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
//写出对象
oos.writeObject(e);
//释放资源
oos.close();
//姓名,地址被序列化,年龄没有被序列化
System.out.println("Serialized data is saved");
}
@Test
public void reload() throws IOException, ClassNotFoundException {
//创建反序列化流
FileInputStream fis = new FileInputStream("employee.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
//读取一个对象
Employee e = (Employee) ois.readObject();
//释放资源
ois.close();
fis.close();
System.out.println(e);
}
}
举例 3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可
public class ReadWriteCollection {
@Test
public void save() throws IOException {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "宏福苑", 23));
list.add(new Employee("李四", "白庙", 24));
list.add(new Employee("王五", "平西府", 25));
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));
// 写出对象
oos.writeObject(list);
// 释放资源
oos.close();
}
@Test
public void reload() throws IOException, ClassNotFoundException {
// 创建反序列化流
FileInputStream fis = new FileInputStream("employees.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取一个对象
ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
// 释放资源
ois.close();
fis.close();
System.out.println(list);
}
}
(1)问题 1:对于 JVM 可以反序列化对象,它必须是能够找到 class 文件的类。如果找不到该类的 class 文件,则抛出一个 ClassNotFoundException 异常。
(2)问题 2:当J VM 反序列化对象时,能找到 class 文件,但是 class 文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下:
(3)解决办法:
Serializable 接口给需要序列化的类提供了一个序列版本号:serialVersionUID。凡是实现 Serializable 接口的类都应该有一个表示序列化版本标识符的静态变量:
static final long serialVersionUID = 234242343243L; //它的值由程序员随意指定即可
public class Employee implements Serializable {
private static final long serialVersionUID = 1324234L; //增加 serialVersionUID
//其它结构:略
}
面试题:谈谈你对 java.io.Serializable 接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?
答:实现了 Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。由于大部分作为参数的类如 String、Intege r等都实现 了 java.io.Serializable 的接口,也可以利用多态的性质,作为参数使接口更灵活。
(1)System.in 和 System.out 分别代表了系统标准的输入和输出设备。默认输入设备是键盘,输出设备是显示器。System.in 的类型是 InputStream;System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的子类。
(2)重定向:通过 System 类的 setIn、setOut 方法对默认设备进行改变。
(3)举例:从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
public class SystemInTest {
public static void main(String[] args) {
System.out.println("请输入信息(退出输入 e 或 exit):");
//把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序
if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
System.out.println("安全退出!!");
break;
}
// 将读取到的整行字符串转成大写输出
System.out.println("-->:" + s.toUpperCase());
System.out.println("继续输入信息");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
请输入信息(退出输入 e 或 exit):
fasdfsad
-->:FASDFSAD
继续输入信息
fsdfa
-->:FSDFA
继续输入信息
e
安全退出!!
(4)拓展
System 类中有三个常量对象:System.out、System.in、System.err。查看 System 类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
奇怪的是:
final 声明的常量,表示在 Java 的语法体系中它们的值是不能修改的,而这三个常量对象的值是由 C/C++ 等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有 set 方法。
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
private static void checkIO() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setIO"));
}
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
(1)打印流:实现将基本数据类型的数据格式转化为字符串输出。
(2)构造器
(3)示例
public class TestPrintStream {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("io.txt");
ps.println("hello");
ps.println(1);
ps.println(1.5);
ps.close();
}
}
执行上述代码后,io.txt 中的内容为:
hello
1
1.5
(1)构造方法
(2)常用方法
package com.atguigu.file;
import org.junit.Test;
import java.io.*;
import java.util.Scanner;
public class TestScanner {
@Test
public void test01() throws IOException {
Scanner input = new Scanner(System.in);
PrintStream ps = new PrintStream("1.txt");
while (true) {
System.out.print("请输入一个单词:");
String str = input.nextLine();
if ("stop".equals(str)) {
break;
}
ps.println(str);
}
input.close();
ps.close();
}
@Test
public void test2() throws IOException {
Scanner input = new Scanner(new FileInputStream("1.txt"));
while (input.hasNextLine()) {
String str = input.nextLine();
System.out.println(str);
}
input.close();
}
}
与 Scanner 类相关的具体知识可以参考 Java 基础——Scanner 类这篇文章。
在 I/O 技术开发中,代码量很大,而且代码的重复率较高,为此 Apache 软件基金会,开发了 I/O 技术的工具类 commonsIO,大大简化了 I/O 开发。Apahce 软件基金会属于第三方,(Oracle 公司第一方,我们自己第二方,其他都是第三方)我们要使用第三方开发好的工具,需要添加相应的 jar 包。
(1)在导入 commons-io-2.5.jar 包之后,内部的 API 都可以使用。
(2)IOUtils 类的使用
示例如下:
public static void main(String[] args)throws Exception {
//- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
IOUtils.copy(new FileInputStream("E:\\Idea\\io\\1.jpg"),new FileOutputStream("E:\\Idea\\io\\file\\柳岩.jpg"));
//- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
/* FileWriter fw = null;
try {
fw = new FileWriter("day21\\io\\writer.txt");
fw.write("hahah");
} catch (IOException e) {
e.printStackTrace();
}finally {
IOUtils.closeQuietly(fw);
}*/
}
(3)FileUtils 类的使用
示例如下:
public static void main(String[] args) {
try {
//- 静态方法:void copyDirectoryToDirectory(File src,File dest);
FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io\\aa"),new File("E:\\Idea\\io\\file"));
//- 静态方法:writeStringToFile(File file,String str)
FileUtils.writeStringToFile(new File("day21\\io\\commons.txt"),"柳岩你好");
//- 静态方法:String readFileToString(File file)
String s = FileUtils.readFileToString(new File("day21\\io\\commons.txt"));
System.out.println(s);
//- 静态方法:void copyFile(File srcFile,File destFile)
FileUtils.copyFile(new File("io\\yangm.png"),new File("io\\yangm2.png"));
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
}
}