IO流
File类的使用
概述
-
File 类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
-
File 类声明在 java.io 包下
-
File 类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法
并没有涉及到写入或读取文件内容的操作,如果需要读取或写入文件内容,则必须使用 IO 流来完成
-
后续 File 类的对象,常会作为参数传递到流的构造器中,指明读取或写入的 “终点”(可以简单理解成:位置)
说明
-
如何创建 File 类的实例
方式一:File(String filePath)
方式二:File(String parentPath, String childPath)
方式三:File(File parentFile, String childPath)
-
路径
相对路径:相较于某个路径下,指明的路径
绝对路径:包含盘符在内的文件或文件目录的路径
-
路径分隔符: 路径中的每级目录之间用一个路径分隔符隔开
windows:\\ unix:/
-
Java 程序支持跨平台运行,因此路径分隔符要慎用。
-
为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。
-
举例:
File file1 = new File("d:\\IO\\info.txt"); File file2 = new File("d:" + File.separator + "IO" + File.separator + "info.txt"); File file3 = new File("d:/IO");
-
案例
package com.laoyang.test.day1;
import org.junit.Test;
import java.io.File;
/**
* @ClassName Test
* @Description: File 类的使用
* @Author Laoyang
* @Date 2021/10/21 9:42
*/
public class FileTest {
@Test
public void testOne() {
// 构造器1
File fileA = new File("hello.txt"); // 相对于当前模块(Demo_15)下
/*
* 路径中的每级目录之间用一个路径分隔符隔开。
* 路径分隔符和系统有关:
> windows 和 DOS 系统默认使用 “\\” 来表示
> UNIX 和 URL 使用 “/” 来表示
* Java 程序支持跨平台运行,因此路径分隔符要慎用。
*为了解决这个隐患,File类提供了一个常量:
> public static final String separator。根据操作系统,动态的提供分隔符。
*/
File file1 = new File("d:\\info.txt");
File file2 = new File("d:" + File.separator + "info.txt");
File fileB = new File("D:\\Java\\项目\\JavaDemo\\Demo_15\\hi.txt"); // 绝对路径
System.out.println(fileA);
System.out.println(fileB);
// 构造器2
File fileC = new File("D:\\Java\\项目", "JavaDemo");
System.out.println(fileC);
// 构造器3:在指定路径下找到对应的文件
File fileD = new File(fileC, "hi.txt");
System.out.println(fileD);
}
}
常用方法
File 类的获取功能
方法 | 作用 |
---|---|
public String getAbsolutePath() | 获取绝对路径 |
public String getPath() | 获取路径 |
public String getName() | 获取名称 |
public String getParent() | 获取上层文件目录路径。若无,返回null |
public long length() | 获取文件长度(即:字节数)。不能获取目录的长度 |
public long lastModified() | 获取最后一次的修改时间,毫秒值 |
public String[] list() | 获取指定目录下的所有文件或者文件目录的名称数组 |
public File[] listFiles() | 获取指定目录下的所有文件或者文件目录的名称数组 |
list() 和 listFiles() 适用于文件目录
File类的重命名功能
方法 | 作用 |
---|---|
public boolean renameTo(File dest) | 把文件重命名为指定的文件路径 |
功能简单理解:将以存在硬盘中的文件进行重命名并放入指定的文件目录下
要求:要想保证返回 true(执行成功),需要 fileA 在硬盘中是存在的,并且 fileB 不能再硬盘中存在
File类的判断功能
方法 | 作用 |
---|---|
public boolean isDirectory() | 判断是否是文件目录 |
public boolean isFile() | 判断是否是文件 |
public boolean exists() | 判断是否存在 |
public boolean canRead() | 判断是否可读 |
public boolean canWrite() | 判断是否可写 |
public boolean isHidden() | 判断是否隐藏 |
File类的创建功能(创建硬盘中对应的文件或文件目录)
方法 | 作用 |
---|---|
public boolean createNewFile() | 创建文件。若文件存在,则不创建,返回false |
public boolean mkdir() | 创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。 |
public boolean mkdirs() | 创建文件目录。如果上层文件目录不存在,一并创建 |
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
File类的删除功能
方法 | 作用 |
---|---|
public boolean delete() | 删除文件或者文件夹 |
注意事项:
- Java中的删除不会进入回收站。
- 要删除一个文件目录时,该文件目录内不能包含文件或者文件目录,如果包含文件或文件目录就无法进行删除
案例
package com.laoyang.test.day1;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
/**
* @ClassName FileCommonMethod
* @Description: File 类的常用方法
* @Author Laoyang
* @Date 2021/10/21 10:27
*/
public class FileCommonMethod {
/**
File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的名称数组
*/
@Test
public void testOne() {
File fileA = new File("hello.txt");
File fileB = new File("F:\\IO\\hi.txt");
// 获取绝对路径:D:\Java\项目\JavaDemo\Demo_15\hello.txt
System.out.println(fileA.getAbsolutePath());
// 获取路径:hello.txt
System.out.println(fileA.getPath());
// 获取名称:hello.txt
System.out.println(fileA.getName());
/*
获取上层文件目录路径。若无,返回null:null
(根据参数中的路径来进行获取,像这里的相对路径,只有文件名(hello.txt),没有上一层目录信息,所以不管创不创建该文件都是返回 null)
*/
System.out.println(fileA.getParent());
/*
获取文件长度(即:字节数)。不能获取目录的长度:0
(没创建文件前返回 0,创建文件后根据里面内容的字节数返回对应长度)
*/
System.out.println(fileA.length());
/*
获取最后一次的修改时间,毫秒值:0
(没创建文件前返回 0,创建文件后根据最后一次修改的时间返回毫秒数(可以使用时间类型转换为标准时间格式进行查看))
*/
System.out.println(fileA.lastModified());
System.out.println("---------------------------------------");
// 获取绝对路径:F:\IO\hi.txt
System.out.println(fileB.getAbsolutePath());
// 获取路径:F:\IO\hi.txt
System.out.println(fileB.getPath());
// 获取名称:hi.txt
System.out.println(fileB.getName());
/*
获取上层文件目录路径。若无,返回null:F:\IO
(因为这里使用的是绝对路径,所以在参数路径中有上一层的文件目录信息(F:\\IO),所以不管最后有没有找到对应的文件,都可以得到上一层的目录信息)
*/
System.out.println(fileB.getParent());
/*
获取文件长度(即:字节数)。不能获取目录的长度:0
(没创建文件前返回 0,创建文件后根据里面内容的字节数返回对应长度)
*/
System.out.println(fileB.length());
/*
获取最后一次的修改时间,毫秒值:0
(没创建文件前返回 0,创建文件后根据最后一次修改的时间返回毫秒数(可以使用时间类型转换为标准时间格式进行查看))
*/
System.out.println(fileB.lastModified());
}
/**
如下的两个方法适用于文件目录
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
*/
@Test
public void testOneB() {
File fileA = new File("D:\\Java\\项目\\JavaDemo");
/*
使用这两个方法的时候,参数目录必须是实际存在的,否则就会报错:NullPointerException
*/
String[] list = fileA.list();
for (String s : list) {
System.out.println(s); // 获取指定目录下的所有文件或者文件目录的名称
}
File[] files = fileA.listFiles();
for (File file : files) {
System.out.println(file); // 获取指定目录下的所有文件或者文件目录
}
}
/**
File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
(该方法的功能简单理解:将以存在硬盘中的文件进行重命名并放入指定的文件目录下)
要求:要想保证返回 true(执行成功),需要 fileA 在硬盘中是存在的,并且 fileB 不能再硬盘中存在
*/
@Test
public void testTwo() {
File fileA = new File("hello.txt");
File fileB = new File("F:\\IO\\hi.txt");
boolean b = fileB.renameTo(fileA);
System.out.println(b);
}
/**
File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
*/
@Test
public void testThree() {
/*
文件测试
*/
File fileA = new File("hello.txt");
System.out.println(fileA.isDirectory()); // 是否是文件目录
System.out.println(fileA.isFile()); //是否是文件
System.out.println(fileA.exists()); // 是否存在
System.out.println(fileA.canRead()); // 是否可读
System.out.println(fileA.canWrite()); // 是否可写
System.out.println(fileA.isHidden()); // 是否隐藏
System.out.println("------------------------------------");
/*
文件目录测试,这里我写了两个文件目录,一个是存在的,一个是不存在的,大家可以都测试一下
*/
File fileB = new File("F:\\IO"); // 目录存在
// fileB = new File("F:\\ioi"); // 目录不存在
System.out.println(fileB.isDirectory()); // 是否是文件目录
System.out.println(fileB.isFile()); //是否是文件
System.out.println(fileB.exists()); // 是否存在
System.out.println(fileB.canRead()); // 是否可读
System.out.println(fileB.canWrite()); // 是否可写
System.out.println(fileB.isHidden()); // 是否隐藏
}
/**
File类的创建功能(创建硬盘中对应的文件或文件目录)
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
*/
@Test
public void testFour() throws IOException {
// 文件的创建
File fileA = new File("hi.txt");
// 如果文件不存在,在进行创建,反之则不创建
if (!fileA.exists()) {
boolean newFile = fileA.createNewFile();
if (newFile) {
System.out.println("创建成功!");
}
}
}
@Test
public void testFourB() {
// 文件目录的创建
File fileA = new File("F:\\IO\\day1\\java1"); // 因为上层目录(day1)不存在,所以会导致创建失败
boolean mkdir = fileA.mkdir();
if (mkdir) {
System.out.println("创建成功A!");
}
File fileB = new File("F:\\IO\\day1\\java2");
boolean mkdirs = fileB.mkdirs();
if (mkdirs) {
System.out.println("创建成功B!");
}
}
/**
File类的删除功能
public boolean delete():删除文件或者文件夹
删除注意事项:
1. Java中的删除不会进入回收站。
2. 要删除一个文件目录时,该文件目录内不能包含文件或者文件目录,如果包含文件或文件目录就无法进行删除
*/
@Test
public void testFive() {
// 因为它里面还有一个 java2 的文件夹,所以这里是不能进行删除的
File fileA = new File("F:\\IO\\day1");
if (fileA.exists()) {
boolean delete = fileA.delete();
if (delete)
System.out.println("删除成功!");
} else {
System.out.println("文件不存在!");
}
}
}
练习
判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
package com.laoyang.test.day2;
import org.junit.Test;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
/**
* @ClassName FindJPGFileTest
* @Description: 判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
* @Author Laoyang
* @Date 2021/10/21 17:10
*/
public class FindJPGFileTest {
/**
* 方式一:使用 list() 方法
*/
@Test
public void test1(){
File srcFile = new File("F:\\IO\\day1\\java1");
String[] fileNames = srcFile.list(); // 获取到当前目录下的所有文件名
for(String fileName : fileNames){
if(fileName.endsWith(".jpg")){ // 根据当前获取到的文件名进行判断
System.out.println(fileName);
}
}
}
/**
* 方式二:使用 listFiles() 方法
* Ps:其实跟第一种方式差不多,也是要使用 String 类中的 endsWith() 方法进行判断
*/
@Test
public void test2(){
File srcFile = new File("F:\\IO\\day1\\java1");
File[] listFiles = srcFile.listFiles();
for(File file : listFiles){
if(file.getName().endsWith(".jpg")){
System.out.println(file.getAbsolutePath());
}
}
}
/*
* 方式三:
* File 类提供了两个文件过滤器方法
* public String[] list(FilenameFilter filter)
* public File[] listFiles(FileFilter filter)
*/
@Test
public void test3() {
File srcFile = new File("F:\\IO\\day1\\java1");
// FilenameFilter
File[] subFiles = srcFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jpg");
}
});
for (File file : subFiles) {
System.out.println(file.getAbsolutePath());
}
// FileFilter
File file = new File("F:\\IO\\day1\\java1");
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".jpg");
}
});
for (File fileA : files) {
System.out.println(fileA.getAbsolutePath());
}
}
}
这里有三种实现方式,大家可自行测试
遍历指定目录所有文件名称,包括子文件目录中的文件
拓展1:并计算指定目录占用空间的大小
拓展2:删除指定文件目录及其下的所有文件
package com.laoyang.test.day2;
import org.junit.Test;
import java.io.File;
/**
* @ClassName ListFilesTest
* @Description: 遍历指定目录所有文件名称,包括子文件目录中的文件。
* @Author Laoyang
* @Date 2021/10/21 17:10
*/
public class ListFilesTest {
/**
* 方式一:递归获取
*/
public static void main(String[] args) {
// 递归:文件目录
// 1.创建目录对象
File dir = new File("F:\\IO");
// 2.打印目录的子文件
printSubFile(dir);
}
public static void printSubFile(File dir) {
// 打印目录的子文件
File[] subfiles = dir.listFiles();
for (File f : subfiles) {
// 判断是不是文件目录
if (f.isDirectory()) {
// 如果是文件目录,则使用递归的方式在次进行判断,直到没有文件目录为止
printSubFile(f);
} else {
// 如果不是文件目录则直接打印该文件的绝对路径
System.out.println(f.getAbsolutePath());
}
}
}
/**
* 方式二:循环实现
* 列出 file 目录的下级内容,仅列出一级的话
* 使用 File 类的 String[] list() 比较简单
* > 如果只是打印下一层的文件夹信息的话可以采用 String[] list 的方式
*/
@Test
public void testOne() {
// 1.创建目录对象
File dir = new File("F:\\IO\\day1");
// 2.打印目录的子文件名和文件夹名
listSubFiles(dir);
}
public void listSubFiles(File file) {
if (file.isDirectory()) {
String[] all = file.list();
for (String s : all) {
System.out.println(s);
}
} else {
System.out.println(file + "是文件!");
}
}
/**
* 方式三
*/
@Test
public void testThree() {
// 1.创建目录对象
File dir = new File("F:\\IO");
// 2.打印目录的子文件
listAllSubFiles(dir);
}
/**
列出 file 目录的下级,如果它的下级还是目录,接着列出下级的下级,依次类推
建议使用 File 类的 File[] listFiles()
*/
public void listAllSubFiles(File file) {
if (file.isFile()) {
System.out.println(file);
} else {
File[] all = file.listFiles();
/*
如果 all[i] 是文件,直接打印
如果 all[i] 是目录,接着再获取它的下一级
注意:大家这里不要被搞混了
1. 这个递归是在循环中调用的,所以递归方法执行结束的条件是:没有下一层就结束(所以大家不用困惑为什么找到文件后不会直接结束该方法)
2. 循环中的递归方法参数,除了第一次是 file,其它的都是使用 f,所以可以直接找到下一层然后继续进行查找
假设:第一次执行,发现不是文件,则使用循环遍历该文件目录下的所有文件(D:\\IO 下的所有文件信息)
第二次执行,发现不是文件,则获取子目录中的所有信息(文件与文件夹)(D:\\IO\\day1 下的所有文件信息)
第三次执行,发现是文件,则打印完该文件信息后,再次执行 else 中没有执行完的循环,遍历下一个文件/或文件夹再次进行判断
......以此类推,直到没有下一层,或没有文件为止
最后一点:当一个目录从头到尾找完以后,就会开始在第二个目录
> 比如:D:\\IO\\day1 和 D:\\IO\\day1,此时 IO 目录下有两个文件夹,这个时候会先遍历完 day1,然后在去遍历 day2,
> 所以并不会出现进入一个子目录深处之后不能再遍历主目录(IO)下的其他文件夹的问题
*/
for (File f : all) {
System.out.println("------------" + f);
listAllSubFiles(f); // 递归调用:自己调用自己就叫递归
}
}
}
}
上面也采用了三种实现方式
- 方式一使用了递归的方式实现
- 方式二只会获取到子级的信息,不会获取到子级的子级,比如:A/B/C,从A开始获取,那么最后只能获取到B的文件信息,而不能获取到C
- 方式三也是使用了递归,但是跟第一种有一丢丢不一样,大家可以结合注释进行理解
扩展
package com.laoyang.test.day2;
import org.junit.Test;
import java.io.File;
/**
* @ClassName ListFilesTest
* @Description: 遍历指定目录所有文件名称,包括子文件目录中的文件。
* @Author Laoyang
* @Date 2021/10/21 17:10
*/
public class ListFilesTest {
/**
* 扩展1:求指定目录所在空间的大小(求任意一个目录的总大小)
*/
@Test
public void testFour() {
File file = new File("F:\\IO");
long directorySize = getDirectorySize(file);
System.out.println(directorySize);
}
/**
求任意一个目录的总大小
*/
public long getDirectorySize(File file) {
// file是文件,那么直接返回file.length()
// file是目录,把它的下一级的所有大小加起来就是它的总大小
long size = 0;
if (file.isFile()) {
size += file.length();
} else {
File[] all = file.listFiles();// 获取file的下一级
// 累加all[i]的大小
for (File f : all) {
/*
f 的大小; 这个地方不要被迷惑了,因为 size 加上的是 getDirectorySize() 方法的返回值
而且这个 size 一直存在循环中,所以并不会被外面 size = 0 给影响
*/
size += getDirectorySize(f);
}
}
return size;
}
/**
* 扩展2:删除指定的目录
*/
@Test
public void testFive() {
File file = new File("F:\\IO");
deleteDirectory(file);
}
public void deleteDirectory(File file) {
/*
如果file是文件,直接delete
如果file是目录,先把它的下一级干掉,然后删除自己
*/
if (file.isDirectory()) {
File[] all = file.listFiles();
/*
循环删除的是 file 的下一层;f代表file的每一个下级
逻辑:先把子目录下的文件删除完,在把子目录给删除,最后把当前的目录给删除
*/
for (File f : all) {
deleteDirectory(f);
}
}
// 删除自己
boolean delete = file.delete();
if (delete) {
System.out.println("删除成功!");
}
}
}
IO流原理及流的分类
Java IO 流原理
- I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
- Java 程序中,对于数据的输入/输出操作以 “流(stream)” 的方式进行。
- java.io 包下提供了各种 “流” 类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
Java IO原理
- 输入 input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出 output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类
- 操作数据单位:字节流、字符流
- 数据的流向:输入流、输出流
- 流的角色:节点流、处理流
Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下4个抽象基类派生的。
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
InputStream 方法说明
- int read() :从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
- int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。
- int read(byte[] b, int off,int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。
- public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。
Reader 方法说明
- int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
- int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
- int read(char[] cbuf,int off,int len):将字符读入数组的某一部分。存到数组 cbuf 中,从 off 处开始存储,最多读 len 个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
- public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。
OutputStream 方法说明
- void write(int b) OutputStream:将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255 范围的。
- void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。
- void write(byte[] b,int off,int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
- public void flush()throws IOException:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
- public void close() throws IOException:关闭此输出流并释放与该流关联的所有系统资源。
Writer 方法说明
- void write(int c):写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。
- void write(char[] cbuf):写入字符数组。
- void write(char[] cbuf,int off,int len):写入字符数组的某一部分。从 off 开始,写入 len 个字符
- void write(String str):写入字符串。
- void write(String str,int off,int len):写入字符串的某一部分。
- void flush():刷新该流的缓冲,则立即将它们写入预期目标。
- public void close() throws IOException:关闭此输出流并释放与该流关联的所有系统资源。
因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数。
说明
- 输入流和输出流方法基本都是通用的,使用的套路也都差不多,后面的案例中大家就可以发现
- 这里是比较官方的解释,所以看起来部分解释看起来比较模糊或者官方,后面的使用中有比较接地气的解释,供大家理解
IO 流体系
节点流 与 处理流的理解
节点流:直接从数据源或目的地读写数据
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
节点流 (也可称为:文件流) 的使用
流对象
节点流 | 字节流 | 字符流 |
---|---|---|
输入流 | FileInputStream | FileReader |
输出流 | FileOutputStream | FileWriter |
字节流 read() 重载方法
int read()
int read(byte[] b)
int read(byte[] b, int off, int len)
字符流 read() 重载方法
int read()
int read(char [] c)
int read(char [] c, int off, int len)
注意:程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以需要显式关闭文件 IO 资源。
注意点
- 定义文件路径时,注意:可以用 “/” 或者 “\”。
- 在写入一个文件时,如果使用构造器 FileOutputStream(file),则目录下有同名文件将被覆盖。
- 如果使用构造器 FileOutputStream(file, true),则目录下的同名文件不会被覆盖, 会在文件内容末尾追加内容。
- 在读取文件时,必须保证该文件已存在,否则报异常。
- 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
- 字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意 .doc, excel, ppt 这些不是文本文件。
相对路径中单元测试与 main() 方法的不同
-
如果使用单元测试方法进行测试,默认找的是当前模块下对应的文件(只会在当前模块/项目中去找对应的文件)
-
如果使用 main() 方法测试,默认找的就是当前工程下对应的文件(如果使用的是父子工程的时候,就会默认去父工程中找对应的文件)
如果是使用 Eclipse,那么不管是在单元测试中还是在 main() 方法中,相对路径默认找的都是当前工程下的对应文件
案例
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName IOTest
* @Description: IO 流的使用
* @Author Laoyang
* @Date 2021/10/23 10:32
*/
public class IOTest {
/**
* 单元测试打印结果
* D:\Java\项目\JavaDemo\Demo_15\hello.txt
* D:\Java\项目\JavaDemo\Demo_15\Demo_15\hello.txt
*
* main 方法打印结果
* D:\Java\项目\JavaDemo\hello.txt
* D:\Java\项目\JavaDemo\Demo_15\hello.txt
*/
public static void main(String[] args) {
File file = new File("hello.txt"); // 相较于当前工程(JavaDemo)
System.out.println(file.getAbsoluteFile());
File fileB = new File("Demo_15\\hello.txt");
System.out.println(fileB.getAbsolutePath());
}
}
这里只演示了 mian() 方法的,大家有兴趣可以在创建一个单元测试方法进行对比
节点流的基本步骤
- 实例化 File 类的对象,指明要操作的文件
- 提供具体的流
- 数据的输入、输出操作
- 流的关闭
字符流案例
输入流 - FileReader
说明点
- read() 方法理解:返回读取的一个字符,如果达到文件末尾,返回 -1
- 异常的处理:为了保证资源一定可以执行关闭操作,需要使用 try-catch-finally 进行处理
- 读取的文件一定要存在,否则就会报 FileNotFoundException 异常
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName IOTest
* @Description: IO 流的使用
* @Author Laoyang
* @Date 2021/10/23 10:32
*/
public class IOTest {
/**
* 节点流的使用
* FileReader
* FileWriter
* 将 Demo_15(当前模块)下的 hello.txt 文件内容读入程序中,并输出到控制台
*/
@Test
public void testOne() {
FileReader reader = null;
try {
// 1. 实例化 File 类的对象,指明要操作的文件
File file = new File("hello.txt"); // 相较于当前 Module(Demo_15)
// 2. 提供具体的流
reader = new FileReader(file);
/*
3. 数据的读取
read():返回读取到的一个字符,如果达到文件末尾,返回 -1
*/
// 方式一
// int read = reader.read();
// while (read != -1) {
// System.out.print((char) read);
// read = reader.read();
// }
// 方式二,语法上针对于方式一的修改,性能方面没有什么区别
int read;
while ((read = reader.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 这里的 try{}catch(){} 写在 if 里面或者外面都是可以的
try {
/*
这里进行判断是为了防止 reader 还没有被实例化的时候就出现异常的问题
如果 reader 没有被实例化,然后直接执行了 close,那么是会导致报错的(就好比你在一个没有水的河里还想游泳)
*/
if (reader != null) {
// 4. 数据的关闭操作(一定要加上!!!)
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对 read() 方法的操作升级:使用 read() 的重载方法
-
read(char[] cbuf):返回每次读取 cbuf 数组中的字符的个数,如果达到文件末尾,返回 -1
根据 cbuf 数组设置的长度来分次读取对应文件中的字符,比如这里设置为 5,那么每一次都会从文件中读取五个字符
-
read(char[] cbuf, int off, int len):cbuf(获取字符的数组大小),off(从什么位置开始存储),len(每一次获取字符的长度)
根据 len 设置的长度来分次读取文件中的字符,即使数组长度为 100,len 设置为 5,那么每一次最多只会从文件中读取 5 个字符存储在 cbuf 数组中;off 表示把读取的字符从数组哪一个位置开始存储,比如我不想从头开始存放就可以设置为 0 以外的下标值
这个重载方法在开发中并不推荐使用!!!
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName IOTest
* @Description: IO 流的使用
* @Author Laoyang
* @Date 2021/10/23 10:32
*/
public class IOTest {
/**
* 对 read() 方法的操作升级:使用 read() 的重载方法
*/
@Test
public void testTwo() {
FileReader reader = null;
try {
// 1. File 类的实例化
File file = new File("hello.txt");
// 2. FileReader 流的实例化
reader = new FileReader(file);
/*
3. 读取的操作
read(char[] cbuf):返回每次读取 cbuf 数组中的字符的个数,如果达到文件末尾,返回 -1
> 根据 cbuf 数组设置的长度来分次读取对应文件中的字符,比如这里设置为 5,那么每一次都会从文件中读取五个字符
read(char[] cbuf, int off, int len):cbuf(获取字符的数组大小),off(从什么位置开始存储),len(每一次获取字符的长度)
> 根据 len 设置的长度来分次读取文件中的字符,即使数组长度为 100,len 设置为 5,那么每一次最多只会从文件中读取 5 个字符存储在 cbuf 数组中
> off 表示把读取的字符从数组哪一个位置开始存储,比如我不想从头开始存放就可以设置为 0 以外的下标值
> 这个重载方法在开发中并不推荐使用!!!
*/
char[] cbuf = new char[5];
int read = reader.read(cbuf);
while (read != -1) {
// 方式一
// 错误写法
// for (int i = 0; i < cbuf.length; i++) {
// System.out.print(cbuf[i]);
// }
// 正确写法
// for (int i = 0; i < read; i++) {
// System.out.print(cbuf[i]);
// }
// 方式二
// 错误写法
// String str = new String(cbuf);
// System.out.println(str);
// 正确写法
String str = new String(cbuf, 0, read);
System.out.print(str);
read = reader.read(cbuf);
}
// 4. 输出的操作,因为这里只是演示read()重载方法的使用,就不写读取操作了
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 资源的关闭
try {
if (reader != null) {
reader.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
输出流 - FileWriter
说明点
-
输出操作,对应的 File 是可以不存在的,并不会出现异常。
-
File 对应的硬盘中的文件如果不存在,在输出的过程中会自动创建此文件。
File 对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file, false) / FileWriter(file):则会对原有文件的覆盖
如果流使用的构造器是:FileWriter(file, true) :则不会对原有文件进行覆盖,而是在原文件的内容末尾进行新增
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName IOTest
* @Description: IO 流的使用
* @Author Laoyang
* @Date 2021/10/23 10:32
*/
public class IOTest {
/**
* 从内存中写出数据到硬盘的文件里
*/
@Test
public void testThree() {
FileWriter writer = null;
try {
// 1. 提供 File 类的对象,指明写入到的文件
File file = new File("helloA.txt");
/*
2. 提供 FileWriter 对象,用于数据的写入
第二个参数表示新增或覆盖,为 true 表示往文件后面新增,为 false 表示对文件进行覆盖
*/
writer = new FileWriter(file, true);
/*
3. 写入的操作
write(String str):案例:writer.write("Java!");
write(char cbuf[]):案例:writer.write("Java!".toCharArray());
*/
writer.write("java is the best language in the world!\n");
writer.write("none of them!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 流资源的关闭
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输入流和输出流的结合使用
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName IOTest
* @Description: IO 流的使用
* @Author Laoyang
* @Date 2021/10/23 10:32
*/
public class IOTest {
/**
* 输入流和输出流的结合使用
*/
@Test
public void testFour() {
FileReader reader = null;
FileWriter writer = null;
try {
// 1. 创建 File 类的对象,指明读取和写入的文件
File read = new File("hello.txt");
File write = new File("helloA.txt");
// 2. 创建输入流和输出流的对象
reader = new FileReader(read);
writer = new FileWriter(write);
// 3. 输入和输出操作
char[] cbuf = new char[5];
// 记录每次读取到 cbuf 数组中的字符的个数
int len = reader.read(cbuf);
while (len != -1) {
// 每次写入 len 个字符
writer.write(cbuf, 0, len);
len = reader.read(cbuf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭流资源,此时的关闭顺序还没什么规定
// 方式一
// try {
// if (writer != null) {
// writer.close();
// }
// } catch (IOException e) {
// e.printStackTrace();
// } finally {
// try {
// if (reader != null) {
// reader.close();
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// 方式二:建议使用
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
流程都是差不多的
字符流不能处理图片文件的测试
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* @ClassName FilePictureTest
* @Description: 字符流不能处理图片文件的测试
* @Author Laoyang
* @Date 2021/10/23 16:08
*/
public class FilePictureTest {
/**
说明:不能使用字符流来处理图片等字节数据,字符流只能处理字符数据
*/
@Test
public void testOne() {
FileReader reader = null;
FileWriter writer = null;
try {
// 1. 创建 File 对象,用于指明读取和写入的文件
File fileA = new File("2.jpg");
File fileB = new File("3.jpg");
// 2. 实例化输入流和输出流的对象
reader = new FileReader(fileA);
writer = new FileWriter(fileB);
// 3. 进行读取和写入操作
char[] cbuf = new char[5];
int read = reader.read(cbuf);
while (read != -1) {
// 可以写入成功,但是写入的文件无法正常使用,因为只能处理字符文件,图片属于字节文件
writer.write(cbuf, 0, read);
read = reader.read(cbuf);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
// 4. 关闭流操作
try {
if (writer != null)
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (reader != null)
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节流案例
说明点
-
对于文本文件(.txt、.java、.c、.cpp等),使用字符流处理
源文档 - 内存读取(如果在内存中读取的话就可能会出现乱码,例如:testOne())
源文档 - 复制(在复制的过程中不会在内存中进行读取,而是在复制完以后直接提供给我们查看,所以就不会出现乱码,例如:testFour())
-
对于非文本文件(.jpg、.mp3、.mp4、.avi、.ppt等),使用字节流处理
输入流 - FileInputStream
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @ClassName FileByteStreamTest
* @Description: 字节流的使用(FileInputStream 和 FileOutputStream 的使用)
* @Author Laoyang
* @Date 2021/10/23 16:21
*/
public class FileByteStreamTest {
/**
* 字节流 - FileInputStream 的使用
* 使用字节流 FileInputStream 处理文本文件,可能会出现乱码
*/
@Test
public void testOne() {
FileInputStream inputStream = null;
try {
// 1. 创建 File 对象,指明要读取的文件
File file = new File("hello.txt");
// 2. 实例化字节流对象
inputStream = new FileInputStream(file);
// 3. 读取操作
byte[] buffer = new byte[5];
int read = inputStream.read(buffer);
while (read != -1) {
// 一个汉字占三个字节
String str = new String(buffer, 0, read);
System.out.print(str); // 出现乱码
read = inputStream.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭流操作
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输入流与输出流(FileOutputStream)的结合使用
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @ClassName FileByteStreamTest
* @Description: 字节流的使用(FileInputStream 和 FileOutputStream 的使用)
* @Author Laoyang
* @Date 2021/10/23 16:21
*/
public class FileByteStreamTest {
/**
* 实现对图片的复制操作
*/
@Test
public void testTwo() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
// 指明要复制的文件
File fileA = new File("2.jpg");
File fileB = new File("3.jpg");
// 实例化字节对象
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
// 复制的过程
byte[] buffer = new byte[5];
int read = inputStream.read(buffer);
while (read != -1) {
outputStream.write(buffer, 0, read);
read = inputStream.read(buffer);
}
System.out.println("复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
指定路径下文件的复制 - 视频 & 文档
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @ClassName FileByteStreamTest
* @Description: 字节流的使用(FileInputStream 和 FileOutputStream 的使用)
* @Author Laoyang
* @Date 2021/10/23 16:21
*/
public class FileByteStreamTest {
/**
* 指定路径下文件的复制 - 视频
*/
@Test
public void testThree() {
long currentTimeMillis = System.currentTimeMillis();
String strPath = "C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆呆王的一天.mp4";
String destPath = "C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆王的一天.mp4";
copyFile(strPath, destPath);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("总耗时:" + (currentTimeMillis1 - currentTimeMillis));
}
/**
* 指定路径下文件的复制 - 文档
*/
@Test
public void testFour() {
long currentTimeMillis = System.currentTimeMillis();
String strPath = "hello.txt";
String destPath = "helloA.txt";
copyFile(strPath, destPath);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("总耗时:" + (currentTimeMillis1 - currentTimeMillis));
}
/**
* 通用方法
*/
public void copyFile(String strPath, String destPath) {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
// 指明要复制的文件
File fileA = new File(strPath);
File fileB = new File(destPath);
// 实例化字节对象
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
// 复制的过程
byte[] buffer = new byte[1024];
int read = inputStream.read(buffer);
while (read != -1) {
outputStream.write(buffer, 0, read);
read = inputStream.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
因为逻辑代码都差不多,所以这里为了方便就直接使用通用方法减少代码量了
处理流一:缓存流的使用
说明
-
为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个字节 (8Kb) 的缓冲区。
BufferedInputStream 部分源码
public class BufferedInputStream extends FilterInputStream { // 缓冲区,先把读取到的数据存储到 8192 大小的缓冲区内,然后在一次性获取到里面的数据 private static int DEFAULT_BUFFER_SIZE = 8192; } 比如:现在有一个10L的水杯和一个5L的水杯,要求倒满100L的水缸(期间两个水杯同时可以装满),由此可见,最后先倒满的肯定是 10L 的水杯
-
处理流,就是 “套接” 在已有的流的基础上
-
当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
-
当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个 8192 个字节数组。
-
向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满, BufferedOutputStream 才会把缓冲区中的数据一次性写到文件里。使用方法 flush() 可以强制将缓冲区的内容全部写入输出流
-
关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
-
flush() 方法的使用:手动将buffer中内容写入文件
-
如果是带缓冲区的流对象的 close() 方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出
流对象
缓冲流 | 字节流 | 字符流 |
---|---|---|
输入流 | BufferedInputStream | BufferedReader |
输出流 | BufferedOutputStream | BufferedWriter |
案例
-
缓冲流
BufferedInputStream:处理字节输入
BufferedOutputStream:处理字节输出
BufferedReader:处理字符输入
BufferedWriter处理字符输出 -
作用:提高流的读取和写入速度
提高读写速度的原因:内部提供了一个缓冲区
BufferedInputStream 和 BufferedOutputStream 的使用
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.*;
/**
* @ClassName BufferedTest
* @Description: 处理流之一:缓冲流的使用
* @Author Laoyang
* @Date 2021/10/23 17:04
*/
public class BufferedTest {
/**
* 使用 BufferedInputStream 和 BufferedOutputStream 实现非文本文件的复制
*/
@Test
public void testOne() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1. 创建 File 对象,指明要复制的文件
File fileA = new File("2.jpg");
File fileB = new File("4.jpg");
// 2. 实例化流对象
// 2.1:实例化节点流对象
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
// 2.2:实例化缓冲流对象
bis = new BufferedInputStream(inputStream);
bos = new BufferedOutputStream(outputStream);
// 3. 复制的细节操作,读取、写入
byte[] buffer = new byte[10];
int read = bis.read(buffer);
while (read != -1) {
bos.write(buffer, 0, read);
read = bis.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
/*
4. 资源关闭
要求:先关闭外层的,在关闭内层的
说明:在关闭外层流的同时,内层流也会自动的进行关闭,关于内层流的关闭,我们可以省略
*/
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 内层流的关闭可以省略
// inputStream.close();
// outputStream.close();
}
}
/**
* 实现文件复制的方法 - 视频
* 比较缓冲流和字节流的读写速度
*/
@Test
public void testTwo() {
long currentTimeMillis = System.currentTimeMillis();
String srcPath = "C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆呆王的一天.mp4";
String destPath = "C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆河马的一天.mp4";
copyFileWithBuffered(srcPath, destPath);
long currentTimeMillis1 = System.currentTimeMillis();
System.out.println("总耗时:" + (currentTimeMillis1 - currentTimeMillis));
}
/**
* 通用方法
*/
public void copyFileWithBuffered(String srcPath, String destPath) {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1. 创建 File 对象,指明要复制的文件
File fileA = new File(srcPath);
File fileB = new File(destPath);
// 2. 实例化流对象
// 2.1:实例化节点流对象
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
// 2.2:实例化缓冲流对象
bis = new BufferedInputStream(inputStream);
bos = new BufferedOutputStream(outputStream);
// 3. 复制的细节操作,读取、写入
byte[] buffer = new byte[1024];
int read = bis.read(buffer);
while (read != -1) {
bos.write(buffer, 0, read);
read = bis.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 资源关闭
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BufferedReader 和 BufferedWriter 的使用
package com.laoyang.test.day3;
import org.junit.Test;
import java.io.*;
/**
* @ClassName BufferedTest
* @Description: 处理流之一:缓冲流的使用
* @Author Laoyang
* @Date 2021/10/23 17:04
*/
public class BufferedTest {
/**
* 使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
*/
@Test
public void testThree() {
BufferedReader reader = null;
BufferedWriter writer = null;
try {
// 1. 创建 File 对象,指明要复制的文件
File fileA = new File("hello.txt");
File fileB = new File("hi.txt");
// 2. 实例化缓冲流对象(此处为了方便就直接使用匿名对象的方式传参了)
reader = new BufferedReader(new FileReader(fileA));
writer = new BufferedWriter(new FileWriter(fileB));
// 3. 读写(复制)操作
// 方式一:使用 char[],read() 每次读取指定大小的字符
// char[] cbuf = new char[5];
// int read = reader.read(cbuf);
// while (read != -1) {
// writer.write(cbuf, 0, read);
// read = reader.read(cbuf);
// }
// 方式二:使用 String,readLine() 每次读取一行字符
String data = reader.readLine();
while (data != null) {
// data 中是不包含换行符的
// writer.write(data); // 不换行
// 换行方式一:\n
// writer.write(data + "\n");
//换行方式二:newLine()
writer.write(data);
writer.newLine();
data = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
小练习
练习一
分别使用节点流:FileInputStream、FileOutputStream和缓冲流:BufferedInputStream、BufferedOutputStream实现文本文件/图片/视频文件的 复制。并比较二者在数据复制方面的效率
package com.laoyang.test.day4;
import org.junit.Test;
import java.io.*;
/**
* @ClassName CopyExerciseTest
* @Description: 练习一:分别使用节点流:FileInputStream、FileOutputStream和缓冲流:BufferedInputStream、BufferedOutputStream实现文本文件/图片/视频文件的 复制。并比较二者在数据复制方面的效率
* @Author Laoyang
* @Date 2021/10/24 15:58
*/
public class CopyExerciseTest {
/**
* 节点流
*/
@Test
public void testFile() {
long start = System.currentTimeMillis();
// 复制文件(1毫秒,文件太小了)
nodeFlow("hello.txt", "新文件.txt");
// 复制图片(13-14毫秒)
// nodeFlow("2.jpg", "新图片.jpg");
// 复制视频(83-86毫秒)
// nodeFlow("C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆呆王的一天.mp4", "新视频.mp4");
long finish = System.currentTimeMillis();
System.out.println("总耗时:" + (finish - start));
}
/**
* FileInputStream、FileOutputStream 方法
*/
public void nodeFlow(String srcPath, String destPath) {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = new FileInputStream(srcPath);
outputStream = new FileOutputStream(destPath);
byte[] buffer = new byte[1024];
int read = inputStream.read(buffer);
while (read != -1) {
outputStream.write(buffer, 0, read);
read = inputStream.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 缓冲流
*/
@Test
public void testBuffered() {
long start = System.currentTimeMillis();
// 复制文件(0毫秒,文件太小了)
bufferStream("hello.txt", "新文件.txt");
// 复制图片(3-4毫秒)
// bufferStream("2.jpg", "新图片.jpg");
// 复制视频(18-19毫秒)
// bufferStream("C:\\Users\\Laoyang\\Videos\\B站\\其它\\呆呆王的一天-英文\\呆呆王的一天.mp4", "新视频.mp4");
long finish = System.currentTimeMillis();
System.out.println("总耗时:" + (finish - start));
}
public void bufferStream(String srcPath, String descPath) {
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
input = new BufferedInputStream(new FileInputStream(srcPath));
output = new BufferedOutputStream(new FileOutputStream(descPath));
byte[] buffer = new byte[1024];
int read = input.read(buffer);
while (read != -1) {
output.write(buffer, 0, read);
read = input.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
练习二
实现图片加密解密操作。
package com.laoyang.test.day4;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @ClassName PicTest
* @Description: 练习二:实现图片加密解密操作。
* @Author Laoyang
* @Date 2021/10/24 15:13
*/
public class PicTest {
/**
* 加密操作
*/
@Test
public void testOne() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
File fileA = new File("2.jpg");
File fileB = new File("222.jpg");
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
byte[] buffer = new byte[10];
int read = inputStream.read(buffer);
while (read != -1) {
// 字符数组进行修改
// 错误写法:此时改的只是循环中的局部变量
// for (byte b : buffer) {
// b = (byte) (b ^ 5);
// }
// 正确写法
for (int i = 0; i < read; i++) {
buffer[i] = (byte) (buffer[i] ^ 5); // 加密操作
}
outputStream.write(buffer, 0, read);
read = inputStream.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 解密操作
*/
@Test
public void testTwo() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
File fileA = new File("222.jpg"); // 要解密的文件
File fileB = new File("333.jpg"); // 解密后的文件
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
byte[] buffer = new byte[10];
int read = inputStream.read(buffer);
while (read != -1) {
// 字符数组进行修改
// 错误写法:此时改的只是循环中的局部变量
// for (byte b : buffer) {
// b = (byte) (b ^ 5);
// }
// 正确写法
for (int i = 0; i < read; i++) {
buffer[i] = (byte) (buffer[i] ^ 5); // 加密操作
}
outputStream.write(buffer, 0, read);
read = inputStream.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
练习三
获取文本上字符出现的次数,把数据写入文件。
思路:
-
遍历文本每一个字符
-
字符出现的次数存在 Map 中
Map<Character,Integer> map = new HashMap<Character,Integer>();
map.put(‘a’,18);
map.put(‘你’,2);
-
把 map 中的数据写入文件
package com.laoyang.test.day4;
import org.junit.Test;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @ClassName PicTest
* @Description: 练习三:获取文本上字符出现的次数,把数据写入文件。
* @Author Laoyang
* @Date 2021/10/24 15:13
*/
public class WordCount {
/*
说明:如果使用单元测试,文件相对路径为当前module
如果使用main()测试,文件相对路径为当前工程
*/
@Test
public void testWordCount() {
FileReader fr = null;
BufferedWriter bw = null;
try {
//1.创建Map集合,Character:char 的包装类
Map<Character, Integer> map = new HashMap<Character, Integer>();
//2.遍历每一个字符,每一个字符出现的次数放到map中,这里直接使用文件路径,就可以不用提前创建 File 对象了
fr = new FileReader("hello.txt");
// 使用 read() 空参方法的时候,默认是一个一个字符进行查找
int c = 0;
while ((c = fr.read()) != -1) {
//int 还原 char
char ch = (char) c;
// 判断char是否在map中第一次出现
if (map.get(ch) == null) {
map.put(ch, 1);
} else {
// map.get(ch) + 1:表示该字符出现的次数 + 1,当key值相同的时候,此操作为修改操作
map.put(ch, map.get(ch) + 1);
}
}
//3.把map中数据存在文件 wordcount.txt
//3.1 创建Writer
bw = new BufferedWriter(new FileWriter("wordcount.txt"));
//3.2 遍历map,再写入数据
Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
for (Map.Entry<Character, Integer> entry : entrySet) {
switch (entry.getKey()) {
case ' ':
bw.write("空格=" + entry.getValue());
break;
case '\t'://\t表示tab 键字符
bw.write("tab键=" + entry.getValue());
break;
case '\r'://
bw.write("回车=" + entry.getValue());
break;
case '\n'://
bw.write("换行=" + entry.getValue());
break;
default:
bw.write(entry.getKey() + "=" + entry.getValue());
break;
}
//换行
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关流
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
处理流二:转换流的使用
说明
-
转换流(根据最后的关键词辨别是字符流还是字节流,Reader 和 Writer 表示字符流):属于字符流
-
作用:提供字节流与字符流之间的转换
-
字节流中的数据都是字符时,转成字符流操作更高效。
-
很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。
解码:字节、字节数组 转 字符数组、字符串
编码:字符数组、字符串 转 字节、字节数组
流对象
转换流 | 字符流 |
---|---|
输入流 | InputStreamReader |
输出流 | OutputStreamWriter |
InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
InputStreamReader 说明
-
实现将字节的输入流按指定字符集转换为字符的输入流。
-
需要和InputStream“套接”。
-
构造器
public InputStreamReader(InputStream in) public InputSreamReader(InputStream in,String charsetName) 如: Reader isr = new InputStreamReader(System.in,”gbk”); > gbk 表示字符集
OutputStreamWriter 说明
-
实现将字符的输出流按指定字符集转换为字节的输出流。
-
需要和OutputStream“套接”。
-
构造器
public OutputStreamWriter(OutputStream out) public OutputSreamWriter(OutputStream out,String charsetName)
案例
说明
public InputSreamReader(InputStream in,String charsetName)
public OutputSreamWriter(OutputStream out,String charsetName)
-
charsetName 表示字符集,具体使用哪个字符集,取决于指定文件(这里指 hello.txt)保存时使用的字符集。
-
如果设置的字符编码与保存时的字符编码不同,则可能会导致出现乱码,比如:保存时用 utf-8,读取时用 gbk,就会导致乱码。
-
如果不设置 charsetName,则默认使用当前编译器字符集格式,如果设置,则使用设置的字符集格式。
InputStreamReader 的使用
package com.laoyang.test.day5;
import org.junit.Test;
import java.io.*;
/**
* @ClassName InputStreamReaderTest
* @Description: 处理流二:转换流的使用
* @Author Laoyang
* @Date 2021/10/24 16:25
*/
public class InputStreamReaderTest {
/**
* 此时处理异常的时候依然是使用 try-catch-finally
* InputStreamReader 的使用:实现字节的输入流到字符的输入流的转换
*/
@Test
public void testOne() {
FileInputStream inputStream = null;
InputStreamReader streamReader = null;
try {
inputStream = new FileInputStream("hello.txt");
/*
第二个参数表示字符集:具体使用哪个字符集,取决于指定文件(这里指 hello.txt)保存时使用的字符集
如果设置的字符编码与保存时的字符编码不同,则可能会导致出现乱码,比如:保存时用 utf-8,读取时用 gbk,就会导致乱码
如果不设置参数二,则使用当前编译器默认的字符集格式,如果设置,则使用设置的字符集格式
*/
// streamReader = new InputStreamReader(inputStream, "GBK");
streamReader = new InputStreamReader(inputStream, "UTF-8");
char[] cbuf = new char[10];
int read = streamReader.read(cbuf);
while (read != -1) {
String str = new String(cbuf, 0, read);
System.out.print(str);
read = streamReader.read(cbuf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (streamReader != null) {
try {
streamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
综合使用 InputStreamReader 和 OutputStreamWriter
说明:
如果将复制的文件字符集修改为 GBK,那么在 IDEA 中打开会出现乱码的情况,因为 IDEA 我现在设置的是 UTF-8,但是用记事本、notepad++ 之类的软件打开就可以正常显示,因为它们可以自动使用对应的字符集进行显示
package com.laoyang.test.day5;
import org.junit.Test;
import java.io.*;
/**
* @ClassName InputStreamReaderTest
* @Description: 处理流二:转换流的使用
* @Author Laoyang
* @Date 2021/10/24 16:25
*/
public class InputStreamReaderTest {
/**
* 综合使用 InputStreamReader 和 OutputStreamWriter
*/
@Test
public void testTwo() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
InputStreamReader streamReader = null;
OutputStreamWriter streamWriter = null;
try {
// 1. 创建 File 对象,指明要操作的文件
File fileA = new File("hello.txt");
File fileB = new File("hello_gbk.txt");
// 2. 实例化流
inputStream = new FileInputStream(fileA);
outputStream = new FileOutputStream(fileB);
streamReader = new InputStreamReader(inputStream, "UTF-8");
streamWriter = new OutputStreamWriter(outputStream, "GBK");
// 3. 读写过程
char[] cbuf = new char[10];
int read = streamReader.read(cbuf);
while (read != -1) {
streamWriter.write(cbuf, 0, read);
read = streamReader.read(cbuf);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流
if (streamWriter != null) {
try {
streamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (streamReader != null) {
try {
streamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
扩展:字符编码
-
编码表的由来
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识 别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。 这就是编码表。
-
常见的编码表(字符集)
ASCII:美国标准信息交换码。
用一个字节的 7 位可以表示。
ISO8859-1:拉丁码表。欧洲码表
用一个字节的 8 位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的 字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用 1-4 个字节来表示一个字符。
-
在 Unicode 出现之前,所有的字符集都是和具体编码方案绑定在一起的(即字 符集≈编码方式),都是直接将字符和最终字节流绑定死了。
-
GBK 等双字节编码方式,用最高位是 1 或 0 表示两个字节和一个字节。
-
Unicode 不完美,这里就有三个问题
第一:我们已经知道,英文字母只用 一个字节表示就够了
第二:如何才能区别 Unicode 和 ASCII ?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?
第三:如果和 GBK 等双字节编码方式一样,用最高位是 1 或 0 表示两个字节和一个字节, 就少了很多值无法用于表示字符,不够表示所有字符。
Unicode 在很长一段时间内无法推广,直到互联网的出现。
-
向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
-
Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的 Unicode 编码是 UTF-8 和 UTF-16。
转换流的编码应用
- 可以将字符按指定编码格式存储
- 可以对文本数据按指定编码格式来解读
- 指定编码表的动作由构造器完成
其它处理流的使用
标准输入、输出流的使用
说明
-
System.in 和 System.out 分别代表了系统标准的输入和输出设备
-
默认输入设备:键盘
输出设备是:显示器
-
System.in 的类型是 InputStream
-
System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的子类
-
重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
案例
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
方式一:使用 Scanner 实现,调用 next() 返回一个字符串
方式二:使用 System.in 实现,System.in 转 转换流 转 BufferedReader 的 readLine() 方法
package com.laoyang.test.day6;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @ClassName OtherStreamTest
* @Description: 标准的输入、输出流的使用
* @Author Laoyang
* @Date 2021/10/24 17:21
*/
public class StandardInputAndOutputTest {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
try {
InputStreamReader streamReader = new InputStreamReader(System.in);
bufferedReader = new BufferedReader(streamReader);
while (true) {
System.out.print("请输入字符串:");
String data = bufferedReader.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束!|");
break;
}
String upperCase = data.toUpperCase(); // 将当前获取的字符串转换为大写进行打印
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
练习
创建一个名为 MyInput.java 的程序:包含读取 int 的方法,来自键盘的 double、float、boolean、short、byte 和 String 值
package com.laoyang.test.day6;
import java.io.*;
/**
* @ClassName OtherStreamTest
* @Description: 练习:创建一个名为 MyInput.java 的程序:包含读取 int 的方法,来自键盘的 double、float、boolean、short、byte 和 String 值
* @Author Laoyang
* @Date 2021/10/24 17:21
*/
public class MyInput {
/**
* 读取从键盘输入的值
*/
public static String readString() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 声明并初始化字符串
String string = "";
// 从键盘获取字符串
try {
string = br.readLine();
} catch (IOException ex) {
System.out.println(ex);
}
// 返回从键盘获取的字符串
return string;
}
/**
* 从键盘读取一个 int 值
*/
public static int readInt() {
return Integer.parseInt(readString());
}
/**
* 从键盘读取一个 double 值
*/
public static double readDouble() {
return Double.parseDouble(readString());
}
/**
* 从键盘读取一个 byte 值
*/
public static double readByte() {
return Byte.parseByte(readString());
}
/**
* 从键盘读取一个 short 值
*/
public static double readShort() {
return Short.parseShort(readString());
}
/**
* 从键盘读取一个 long 值
*/
public static double readLong() {
return Long.parseLong(readString());
}
/**
* 从键盘读取一个 float 值
*/
public static double readFloat() {
return Float.parseFloat(readString());
}
}
注意:该类虽然可以接收从键盘输入的不同类型的值,但是使用的接收类型和输入的数据类型不一致的话,还是会报错的!!!
打印流的使用
说明
- 实现将基本数据类型的数据格式转化为字符串输出
- 打印流:PrintStream 和 PrintWriter
- 提供了一系列重载的 print() 和 println() 方法,用于多种数据类型的输出
- PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
- PrintStream 和 PrintWriter 有自动 flush 功能
- PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。 在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
- System.out 返回的是 PrintStream 的实例
流对象
打印流 | 字符流 | 字节流 |
---|---|---|
输入流 | 无 | 无 |
输出流 | PrintStream | PrintWriter |
注意:打印流只有输出流,没有输入流!!!
案例
PrintStream 的使用
package com.laoyang.test.day6;
import org.junit.Test;
import java.io.*;
/**
* @ClassName PrintStreamTest
* @Description: 打印流的使用
* @Author Laoyang
* @Date 2021/10/24 17:51
*/
public class PrintStreamTest {
/**
* 打印流:PrintStream 的使用
*/
@Test
public void testOne() {
PrintStream ps = null;
try {
// 文件不存在是可以的,但是存放文件的目录必须要存在!
FileOutputStream fos = new FileOutputStream(new File("F:\\IO\\day1\\hello.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区),如果不想要自动刷新,改为false或者不写即可
ps = new PrintStream(fos, true);
// 把标准输出流(控制台输出)改成文件
if (ps != null) {
System.setOut(ps);
}
// 输出ASCII字符
for (int i = 0; i <= 255; i++) {
System.out.print((char) i);
// 每50个数据一行
if (i % 50 == 0) {
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
}
将 System.out.print(); 中的值存入指定的文件中
PrintWriter 的使用
package com.laoyang.test.day6;
import org.junit.Test;
import java.io.*;
/**
* @ClassName PrintStreamTest
* @Description: 打印流的使用
* @Author Laoyang
* @Date 2021/10/24 17:51
*/
public class PrintStreamTest {
/**
* 打印流:PrintWriter 的使用
*/
@Test
public void testTwo() {
PrintWriter printWriter = null;
try {
FileOutputStream streamWriter = new FileOutputStream(new File("F:\\IO\\day1\\hello.txt"));
printWriter = new PrintWriter(streamWriter, true);
// 将 ASCII 字符存储到指定文件中
for (int i = 0; i <= 255; i++) {
printWriter.print((char) i);
// 每50个数据一行
if (i % 50 == 0) {
printWriter.println(); // 换行
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null)
printWriter.close();
}
}
}
将 printWriter.print(); 中的值存入指定的文件中
数据流的使用
说明
-
为了方便地操作 Java 语言的基本数据类型和 String 的数据,可以使用数据流。
-
数据流有两个类:(作用:用于读取或写入基本数据类型的变量或字符串)
DataInputStream 和 DataOutputStream
分别 “套接” 在 InputStream 和 OutputStream 子类的流上
-
DataInputStream中的方法
boolean readBoolean() byte readByte() char readChar() float readFloat() double readDouble() short readShort() long readLong() int readInt() String readUTF() void readFully(byte[] b) -
DataOutputStream 中的方法:将上述的方法的 read 改为相应的 write 即可。
案例
package com.laoyang.test.day6;
import org.junit.Test;
import java.io.*;
/**
* @ClassName DataFlowTest
* @Description: 处理流五:数据流的使用
* @Author Laoyang
* @Date 2021/10/25 14:58
*/
public class DataFlowTest {
/**
* 写入的过程,DataOutputStream 的使用
*/
@Test
public void testOne(){
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("data.txt"));
// flush():刷新操作,将内存中的数据写入文件
dos.writeUTF("小白");
dos.flush();
dos.writeInt(18);
dos.flush();
dos.writeBoolean(true);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 读取:DataInputStream 的使用
* 练习:将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中
* 注意点:读取不同类型的数据的顺序要与当初写入文件时的顺序保持一致
*/
@Test
public void testTwo() {
DataInputStream dis = null;
try {
// 1. 实例化流的对象(因为流程都差不多,这里就把前面的操作合在一起了)
dis = new DataInputStream(new FileInputStream("data.txt"));
/*
2. 读取操作,此处有规定
规定:要根据写入时的顺序进行读取,否则就会报 java.io.EOFException
*/
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println(name);
System.out.println(age);
System.out.println(isMale);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在指定文件中写入数据后是不能直接打开查看的,因为会导致乱码;如果想要查看正确的数据,可以使用 DataInputStream 进行读取
对象流的使用
说明
- 对象流:ObjectInputStream 和 OjbectOutputSteam
- 作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。
- 序列化:用 ObjectOutputStream 类保存基本类型数据或对象的机制
- 反序列化:用 ObjectInputStream 类读取基本类型数据或对象的机制
- ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量
对象的序列化
-
对象序列化机制允许把内存中的 java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点;当其它程序获取了这种二进制流,就可以恢复成原来的 java 对象
-
序列化的好处在于可将任何实现了 Serializable 接口的对象转化为字节数据, 使其在保存和传输时可被还原
-
序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础
-
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。 否则,会抛出NotSerializableException异常
Serializable 和 Externalizable
-
凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID;
-
serialVersionUID 用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
-
如果类没有显示定义这个静态常量,它的值是 Java 运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。所以建议显式声明。
比如当前类没有定义 serialVersionUID 常量,属性暂时只有 name 和 age,这个时候使用 OjbectOutputSteam 写入;
然后在当前类中新加入一个属性 id,以及它的 get/set 等方法,这个时候在使用 ObjectInputStream 去读取,就会报错;
因为这个时候的版本以及不一样了,假设第一次版本为 1,然后加入 id 属性后,版本变成了 2,由于 1 版本已经升为了 2,这个时候再去找 1 版本的数据就会报错。
-
-
简单来说,Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
使用对象流序列化对象
序列化对象步骤
-
创建 ObjectOutputStream 流对象
-
调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
注意写入一次,操作 flush() 一次
-
关闭流
反序列化对象步骤
-
创建 ObjectInputStream 流对象
-
调用 readObject() 方法读取流中的对象
注意:如果添加了不同类型的对象,则读取的对象类型必须和写入的对象类型的顺序保持一致,比如第一次写入的是 String 类型对象,第二次写入的是 User 类型对象,那么读取的时候第一次必须先读取 String 对象,再来读取 User 对象,否则就会报错!
-
关闭流
注意
如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的类也不能序列化
详情请看 “使用自定义类实现序列化与反序列化操作”
案例
序列化过程:将内存中的 java 对象保存到磁盘中或通过网络传输出去
反序列化过程:将磁盘文件中的对象还原为内存中的一个 java 对象
package com.laoyang.test.day6;
import org.junit.Test;
import java.io.*;
/**
* @ClassName ObjectStreamTest
* @Description: 处理流六:对象流的使用
* @Author Laoyang
* @Date 2021/10/25 16:57
*/
public class ObjectStreamTest {
/**
序列化过程:将内存中的 java 对象保存到磁盘中或通过网络传输出去
使用 ObjectOutputStream 实现
输出的文件是不能直接打开的,如果想要查看里面的内容,则需要使用 ObjectInputStream 实现
*/
@Test
public void testOne() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("java是世界上最好的语言"));
// 刷新操作
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
反序列化过程:将磁盘文件中的对象还原为内存中的一个 java 对象
使用 ObjectInputStream 实现
*/
@Test
public void testTwo() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object object = ois.readObject();
String str = (String) object;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用自定义类实现序列化与反序列化操作
自定义类需要满足如下的要求,才可进行序列化操作
- 实现 Serializable 接口(Serializable 是一个标识接口,里面没有任何需要实现的方法)
- 当前类提供一个全局常量:serialVersionUID
注意:
- 读取的时候需要和写入时的顺序保持一致(如果都是同一种类型,则可以直接进行读取),否则会报异常
- ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量
- 如果自定义类中的子类属性不是可序列化的,那么也会报错:NotSerializableException;如果想要正常执行,那么需要把子类也声明为可序列化的
package com.laoyang.test.day6;
import org.junit.Test;
import java.io.*;
/**
* @ClassName ObjectStreamTest
* @Description: 处理流六:对象流的使用
* @Author Laoyang
* @Date 2021/10/25 16:57
*/
public class ObjectStreamTest {
/**
* 使用自定义类实现序列化与反序列化操作
* 序列化操作
*/
@Test
public void testThree() {
ObjectOutputStream oos = null;
// ObjectInputStream ois = null;
try {
// 写入
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("java是世界上最好的语言"));
// 刷新操作
oos.flush();
oos.writeObject(new Person("小白", 18));
oos.flush();
oos.writeObject(new Person("小黑", 23, new Account(5000)));
oos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化操作
*/
@Test
public void testFour() {
ObjectInputStream ois = null;
try {
// 读取,读取的时候需要和写入时的顺序保持一致(如果都是同一种类型,则可以直接进行读取),否则会报异常
ois = new ObjectInputStream(new FileInputStream("object.dat"));
String str = (String) ois.readObject();
Person person = (Person) ois.readObject();
System.out.println(str);
System.out.println(person);
/*
如果 Person 类中的 Account 属性不是可序列化的,那么就会报错:NotSerializableException
如果想要正常执行,那么需要把 Account 类也声明为可序列化的
*/
Person account = (Person) ois.readObject();
System.out.println(account);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 自定义类实现序列化与反序列化操作
* Person 类需要满足如下的要求,才可进行序列化操作
* 1. 实现 Serializable 接口(Serializable 是一个标识接口,里面没有任何需要实现的方法)
* 2. 当前类提供一个全局常量:serialVersionUID
*/
class Person implements Serializable {
// 可测试定义 serialVersionUID 与不定义 serialVersionUID 常量的区别
private static final long serialVersionUID = -1806851998099103327L;
// ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
// private static String name;
// private transient int age;
private String name;
private int age;
private Account account;
public Person(String name, int age, Account account) {
this.name = name;
this.age = age;
this.account = account;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", account=" + account +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
}
/**
* 自定义类,用于当做 Person 类的参数进行测试
* 可以将 Serializable 去掉或加上进行测试,看不同的测试效果
*/
// class Account {
class Account implements Serializable {
private static final long serialVersionUID = 1928731172189653323L;
private double balance;
@Override
public String toString() {
return "Account{" +
"balance=" + balance +
'}';
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(double balance) {
this.balance = balance;
}
public Account() {
}
}
idea设置快捷生成:File > Editor > Inspections > Serialization issues > 把 Serializable class without ‘serialVersionUID’ 勾上即可
设置好后将鼠标点击对应的类,然后按 ALT + 回车即可
随机存取文件流
RandomAccessFile 类
说明
-
RandomAccessFile 声明在 java.io 包下,但直接继承于 java.lang.Object类。并 且它实现了 DataInput、DataOutput 这两个接口,也就意味着这个类既可以读也可以写。
-
RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
支持只访问文件的部分内容
可以向已存在的文件后追加内容 -
RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile 类对象可以自由移动记录指针:
long getFilePointer():获取文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到 pos 位置
使用
-
构造器
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
-
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
r: 以只读方式打开 rw:打开以便读取和写入 rwd:打开以便读取和写入;同步文件内容的更新 rws:打开以便读取和写入;同步文件内容和元数据的更新
-
如果模式为只读 r。则不会创建文件,而是会去读取一个已经存在的文件, 如果读取的文件不存在则会出现异常。 如果模式为 rw 读写。如果文件不存在则会去创建文件,如果存在则不会创建。
其它
我们可以用 RandomAccessFile 这个类,来实现一个多线程断点下载的功能, 用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的朋友们可以自己实现下。
比如:使用百度云或迅雷下载文件的时候,在还没有下载完成的时候我们打开对应的保存位置是可以已经有一个文件存在的,这个文件就属于临时文件,如果这个时候突然没有网络导致暂停了下载,那么我们下一次下载的时候就是接着从上一次暂停的位置开始下载,而不需要从头下载,这就属于一个多线程断点下载。
案例
说明
- RandomAccessFile 直接继承了 java.lang.Object 类,实现了 DataInput 和 DataOutput 接口
- RandomAccessFile 既可以作为一个输入流,又可以作为一个输出流
- 如果 RandomAccessFile 作为输出流时,写入到的文件如果不存在,则在执行过程中自动创建
如果写入到的文件存在,则会对原有文件内容进行覆盖(默认情况下,是从头开始覆盖) - 可以通过相关的操作,实现 RandomAccessFile “插入” 数据的操作
RandomAccessFile 类的使用
构造器
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
r:以只读方式打开
rw:打开以便读取和写入
rwd:打开以便读取和写入;同步文件内容的更新
rws:打开以便读取和写入;同步文件内容和元数据的更新
一般我们使用 r 和 rw 就够用了
package com.laoyang.test.day7;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: 随机存取文件流
* @Author Laoyang
* @Date 2021/10/26 9:32
*/
public class RandomAccessFileStreamTest {
@Test
public void testOne() {
RandomAccessFile rafDataInput = null;
RandomAccessFile rafDataOutput = null;
try {
// 1. 实例化流的对象
rafDataInput = new RandomAccessFile(new File("2.jpg"), "r");
rafDataOutput = new RandomAccessFile(new File("234.jpg"), "rw");
// 2. 读写操作
byte[] buffer = new byte[10];
int read = rafDataInput.read(buffer);
while (read != -1) {
rafDataOutput.write(buffer, 0, read);
read = rafDataInput.read(buffer);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭流
if (rafDataOutput != null) {
try {
rafDataOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rafDataInput != null) {
try {
rafDataInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
关于 RandomAccessFile 只作为输出流时的文件覆盖操作
指定文件不存在的时候会创建一个文件然后在将数据写入到这个新文件中;如果文件存在,则会从头开始覆盖,比如 helloA.txt 中的数据为 Hello,当写入 AAA后,数据变为 AAAlo
package com.laoyang.test.day7;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: 随机存取文件流
* @Author Laoyang
* @Date 2021/10/26 9:32
*/
public class RandomAccessFileStreamTest {
/**
* 关于 RandomAccessFile 只作为输出流时的文件覆盖操作
*/
@Test
public void testTwo() {
RandomAccessFile raf = null;
try {
File file = new File("helloA.txt");
/*
指定文件不存在的时候会创建一个文件然后将数据写入到这个新文件中
如果存在,则会从头开始覆盖,比如 helloA.txt 中的数据为 Hello,当写入 AAA后,数据变为 AAAlo
*/
// raf = new RandomAccessFile("helloAAA.txt", "rw");
raf = new RandomAccessFile(file, "rw");
// 将指针调到角标为 3 的位置
// raf.seek(3);
// 如果想要从文件末尾开始追加数据,则可以指针指向文件最后一个字符的角标位置
raf.seek(file.length());
// 如果指定的文件存在,那么 write() 就相当于一个覆盖操作
raf.write("AAA".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (raf != null)
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
RandomAccessFile 实现数据的插入效果
大致逻辑:
-
从对应指针位置开始读取,获取到插入位置后面的所有数据,然后用 StringBuilder 存储起来
-
从对于指针位置写入对应的数据
-
将要插入的数据先执行一次写入操作,然后在把 StringBuilder 中的数据写入进去
StringBuilder 写入的数据是接在上一次写入的数据后面的,基本上也可以理解为覆盖原本后面的数据。
比如:原数据为:Hello World,第一次插入 AAA,结果变为:HelAAAWorld,第二次插入时,变为 HelAAAlo World
就相当于 “lo World” 是存放在 StringBuilder 中的,第二次插入只是为了将插入时覆盖的数据复原。
package com.laoyang.test.day7;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: 随机存取文件流
* @Author Laoyang
* @Date 2021/10/26 9:32
*/
public class RandomAccessFileStreamTest {
/**
* 使用 RandomAccessFile 实现数据的插入效果
*/
@Test
public void testThree() {
RandomAccessFile raf = null;
try {
File file = new File("helloA.txt");
raf = new RandomAccessFile(file, "rw");
// 从指针 3 的位置开始进行读取
raf.seek(3);
// 保存指针 3 后面的所有数据到 StringBuilder 中
StringBuilder builder = new StringBuilder((int) file.length());
byte[] buffer = new byte[10];
int read = raf.read(buffer);
while (read != -1) {
builder.append(new String(buffer, 0, read));
read = raf.read(buffer);
}
System.out.println(builder.toString());
// 调回指针,写入 ABC
raf.seek(3);
raf.write("ABC".getBytes());
// 将 StringBuilder 中的数据写入到文件中
raf.write(builder.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
getFilePointer() 方法的使用
package com.laoyang.test.day7;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: 随机存取文件流
* @Author Laoyang
* @Date 2021/10/26 9:32
*/
public class RandomAccessFileStreamTest {
/**
* getFilePointer() 方法的使用
* 作用:获取文件记录指针的当前位置
* @throws IOException
*/
@Test
public void test() {
RandomAccessFile file = null;
try {
file = new RandomAccessFile(new File("helloA.txt"), "rw");
System.out.println(file.getFilePointer()); // 0
file.seek(3);
System.out.println(file.getFilePointer()); // 3
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
思考:将 StringBuilder 替换为 ByteArrayOutputStream 实现 RandomAccessFile 的插入操作
package com.laoyang.test.day7;
import org.junit.Test;
import java.io.*;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: 思考:将 StringBuilder 替换为 ByteArrayOutputStream 实现 RandomAccessFile 的插入操作
* @Author Laoyang
* @Date 2021/10/26 11:04
*/
public class ByteArrayOutputStreamTest {
@Test
public void test1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("helloA.txt");
String info = readStringFromInputStream(fis);
System.out.println(info);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 读取操作
* @param fis
* @return
* @throws IOException
*/
private String readStringFromInputStream(FileInputStream fis) throws IOException {
// 方式一:可能出现乱码
// String content = "";
// byte[] buffer = new byte[1024];
// int len;
// while((len = fis.read(buffer)) != -1){
// content += new String(buffer);
// }
// return content;
// 方式二:BufferedReader
// BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
// char[] buf = new char[10];
// int len;
// String str = "";
// while ((len = reader.read(buf)) != -1) {
// str += new String(buf, 0, len);
// }
// return str;
// 方式三:避免出现乱码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[10];
int len = fis.read(buffer);
while (len != -1) {
baos.write(buffer, 0, len);
len = fis.read(buffer);
}
return baos.toString();
}
/**
* 插入操作
*/
@Test
public void testByteArrayOutputStream() {
RandomAccessFile raf = null;
try {
File file = new File("helloA.txt");
raf = new RandomAccessFile(file, "rw");
raf.seek(3);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[10];
int read = raf.read(buffer);
while (read != -1) {
baos.write(buffer, 0, read);
read = raf.read(buffer);
}
raf.seek(3);
raf.write("QQ".getBytes());
raf.write(baos.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这里演示了使用 ByteArrayOutputStream 实现读写操作
NIO.2 中 Path、Paths、Files 类的使用
Java NIO 概述
- Java NIO (New IO,Non-Blocking IO) 是从 Java 1.4 版本开始引入的一套 的 IO API,可以替代标准的 Java IO API。NIO 与原来的 IO有同样的作用和目的,但是使用的方式完全不同,NIO 支持面向缓冲区的 ( IO 是面向流的)、基于 通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。
- Java API 中提供了两套 NIO,一套是针对标准输入输出 NIO,另一套就是网络编程 NIO。
|-----java.nio.channels.Channel
|-----FileChannel:处理本地文件
|-----SocketChannel:TCP网络编程的客户端的Channel
|-----ServerSocketChannel:TCP网络编程的服务器端的Channel
|-----DatagramChannel:UDP网络编程中发送端和接收端的Channel
NIO.2
随着 JDK 7 的发布,Java 对 NIO 进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。 因为 NIO 提供的一些功能,NIO 已经成为文件处理中越来越重要的部分。
Path、Paths 和 Files 核心 API
-
早期的 Java 只提供了一个 File 类来访问文件系统,但 File 类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
-
NIO. 2 为了弥补这种不足,引入了 Path 接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path 可以看成是 File类的升级版本,实际引用的资源也可以不存在。
-
在以前 IO 操作都是这样写的:
import java.io.File; File file = new File("index.html");
-
但在Java7 中,我们可以这样写:
import java.nio.file.Path; import java.nio.file.Paths; Path path = Paths.get("index.html")
-
同时,NIO.2 在 java.nio.file 包下还提供了 Files、Paths 工具类,Files 包含了大量静态的工具方法来操作文件;Paths 则包含了两个返回 Path 的静态工厂方法。
-
Paths 类提供的静态 get() 方法用来获取 Path 对象:
static Path get(String first, String … more):用于将多个字符串串连成路径 static Path get(URI uri):返回指定 uri 对应的 Path 路径
Path 常用方法
方法 | 作用 |
---|---|
String toString() | 返回调用 Path 对象的字符串表示形式 |
boolean startsWith(String path) | 判断是否以 path 路径开始 |
boolean endsWith(String path) | 判断是否以 path 路径结束 |
boolean isAbsolute() | 判断是否是绝对路径 |
Path getParent() | 返回 Path 对象包含整个路径,不包含 Path 对象指定的文件路径 |
Path getRoot() | 返回调用 Path 对象的根路径 |
Path getFileName() | 返回与调用 Path 对象关联的文件名 |
int getNameCount() | 返回 Path 根目录后面元素的数量 |
Path getName(int idx) | 返回指定索引位置 idx 的路径名称 |
Path toAbsolutePath() | 作为绝对路径返回调用 Path 对象 |
Path resolve(Path p) | 合并两个路径,返回合并后的路径对应的Path对象 |
File toFile() | 将 Path 转化为 File 类的对象 |
Files 常用方法
说明:java.nio.file.Files 用于操作文件或目录的工具类。
常用方法
方法 | 作用 |
---|---|
Path copy(Path src, Path dest, CopyOption … how) | 文件的复制 |
Path createDirectory(Path path, FileAttribute<?> … attr) | 创建一个目录 |
Path createFile(Path path, FileAttribute<?> … arr) | 创建一个文件 |
void delete(Path path) | 删除一个文件/目录,如果不存在,执行报错 |
void deleteIfExists(Path path) | Path 对应的文件/目录如果存在,执行删除 |
Path move(Path src, Path dest, CopyOption…how) | 将 src 移动到 dest 位置 |
long size(Path path) | 返回 path 指定文件的大小 |
常用方法 · 用于判断
方法 | 作用 |
---|---|
boolean exists(Path path, LinkOption … opts) | 判断文件是否存在 |
boolean isDirectory(Path path, LinkOption … opts) | 判断是否是目录 |
boolean isRegularFile(Path path, LinkOption … opts) | 判断是否是文件 |
boolean isHidden(Path path) | 判断是否是隐藏文件 |
boolean isReadable(Path path) | 判断文件是否可读 |
boolean isWritable(Path path) | 判断文件是否可写 |
boolean notExists(Path path, LinkOption … opts) | 判断文件是否不存在 |
常用方法 · 用于操作内容
方法 | 作用 |
---|---|
SeekableByteChannel newByteChannel(Path path, OpenOption…how) | 获取与指定文件的连接,how 指定打开方式。 |
DirectoryStream
| 打开 path 指定的目录 |
InputStream newInputStream(Path path, OpenOption…how) | 获取 InputStream 对象 |
OutputStream newOutputStream(Path path, OpenOption…how) | 获取 OutputStream 对象 |
案例
Path 的使用
-
jdk 7.0 时,引入了 Path、Paths、Files 三个类。
-
此三个类声明在:java.nio.file 包下。
-
Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
-
如何实例化Path:使用Paths
static Path get(String first, String … more):用于将多个字符串串连成路径 static Path get(URI uri):返回指定uri对应的Path路径
package com.laoyang.test.day8;
import org.junit.Test;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: Path 类的使用
* @Author Laoyang
* @Date 2021/10/26 11:17
*/
public class PathTest {
/**
* 如何使用Paths实例化Path
*/
@Test
public void test1() {
Path path1 = Paths.get("F:\\nio\\hello.txt"); // new File(String filepath)
Path path2 = Paths.get("F:\\", "nio\\hello.txt"); // new File(String parent,String filename);
System.out.println(path1);
System.out.println(path2);
Path path3 = Paths.get("F:\\", "nio");
System.out.println(path3);
}
/**
* Path中的常用方法
*/
@Test
public void test2() {
Path path1 = Paths.get("F:\\", "nio\\nio1\\nio2\\hello.txt");
Path path2 = Paths.get("hello.txt");
// String toString() : 返回调用 Path 对象的字符串表示形式
System.out.println(path1);
// boolean startsWith(String path) : 判断是否以 path 路径开始
System.out.println(path1.startsWith("F:\\nio"));
// boolean endsWith(String path) : 判断是否以 path 路径结束
System.out.println(path1.endsWith("hello.txt"));
// boolean isAbsolute() : 判断是否是绝对路径
System.out.println(path1.isAbsolute() + "~");
System.out.println(path2.isAbsolute() + "~");
// Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
System.out.println(path1.getParent());
System.out.println(path2.getParent());
// Path getRoot() :返回调用 Path 对象的根路径
System.out.println(path1.getRoot());
System.out.println(path2.getRoot());
// Path getFileName() : 返回与调用 Path 对象关联的文件名
System.out.println(path1.getFileName() + "~");
System.out.println(path2.getFileName() + "~");
// int getNameCount() : 返回Path 根目录后面元素的数量
// Path getName(int idx) : 返回指定索引位置 idx 的路径名称
for (int i = 0; i < path1.getNameCount(); i++) {
System.out.println(path1.getName(i) + "*****");
}
// Path toAbsolutePath():作为绝对路径返回调用 Path 对象
System.out.println(path1.toAbsolutePath());
System.out.println(path2.toAbsolutePath());
// Path resolve(Path p):合并两个路径,返回合并后的路径对应的Path对象
Path path3 = Paths.get("F:\\", "nio");
Path path4 = Paths.get("nioo\\hi.txt");
path3 = path3.resolve(path4);
System.out.println(path3);
// File toFile(): 将Path转化为File类的对象
File file = path1.toFile();//Path--->File的转换
Path newPath = file.toPath();//File--->Path的转换
}
}
File 与 Path 之间的转换:
Path —> File 的转换:使用 toFile() 方法
Fil e—> Path 的转换:使用 toPath() 方法
Files 工具类的使用
package com.laoyang.test.day8;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
import java.util.Iterator;
/**
* @ClassName RandomStorageFileStreamTest
* @Description: Files工具类的使用:操作文件或目录的工具类
* @Author Laoyang
* @Date 2021/10/26 11:23
*/
public class FilesTest {
@Test
public void test1() throws IOException{
Path path1 = Paths.get("F:\\nio", "hello.txt");
Path path2 = Paths.get("heihei.txt");
// Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
//要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
// Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
// Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
Path path3 = Paths.get("F:\\nio\\nio1");
// Files.createDirectory(path3);
// Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
Path path4 = Paths.get("F:\\nio\\hi.txt");
// Files.createFile(path4);
// void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
// Files.delete(path4);
// void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
Files.deleteIfExists(path3);
// Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
// Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
// long size(Path path) : 返回 path 指定文件的大小
long size = Files.size(path2);
System.out.println(size);
}
@Test
public void test2() throws IOException{
Path path1 = Paths.get("F:\\nio", "hello.txt");
Path path2 = Paths.get("heihei.txt");
// boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));
// boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
//不要求此path对应的物理文件存在。
System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));
// boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
// boolean isHidden(Path path) : 判断是否是隐藏文件
//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
// System.out.println(Files.isHidden(path1));
// boolean isReadable(Path path) : 判断文件是否可读
System.out.println(Files.isReadable(path1));
// boolean isWritable(Path path) : 判断文件是否可写
System.out.println(Files.isWritable(path1));
// boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
}
/**
* StandardOpenOption.READ:表示对应的Channel是可读的。
* StandardOpenOption.WRITE:表示对应的Channel是可写的。
* StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
* StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
*/
@Test
public void test3() throws IOException{
Path path1 = Paths.get("F:\\nio", "hello.txt");
// InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);
// OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录
Path path2 = Paths.get("F:\\teach");
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
Iterator<Path> iterator = directoryStream.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
在 IDEA 中手动导入第三方 jar 包
- 右键对应的模块,点击 New,然后创建一个普通文件夹,用于存放第三方 jar 包(文件夹名一般为:lib)
- 将对应的 jar 包复制到刚创建好的文件夹下,这个时候 jar 包还是不能用的
- 右键对应的 jar 包,点击 Add as Library,然后在选择要添加到的模块,选好以后点 OK 即可
第三方 jar 包的使用
package com.laoyang.test.day9;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
/**
* @ClassName FileUtilsTest
* @Description: 第三方 jar 包的使用
* @Author Laoyang
* @Date 2021/10/26 11:25
*/
public class FileUtilsTest {
@Test
public void testOne() {
File fileA = new File("2.jpg");
File fileB = new File("666.jpg");
try {
FileUtils.copyFile(fileA, fileB);
} catch (IOException e) {
e.printStackTrace();
}
}
}
import org.apache.commons.io.FileUtils; 就是使用了我们刚才导入进去的 jar 包
好处:更加方便快捷