一、File类的使用
1.1 File类
File对象,是Java中为了操作操作系统中文件而设计的
java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
File类Java提供的一个操作文件及文件夹的类。 File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
File对象可以作为参数传递给流的构造器
1.2 绝对路径和相对路径
1.2.1 绝对路径
直接可以定位文件的一种路径,是一个固定的路径,从盘符开始。
-
window操作系统:直接使用盘符查找:d:\a\b\c.jpg
-
linux:/ /user/local/bin/java
-
网络URL:https://www.baidu.com/a.jpg
1.2.2 相对路径
相对于某个参照物(相对于文件夹),进行查找。
-
./a.jpg ,指的是当前目录下的a.jpg文件
可以用.
表示当前目录,..
表示上级目录。
1.2.3 路径分隔符
路径中的每级目录之间用一个路径分隔符隔开。路径分隔符和系统有关:
-
windows和DOS系统默认使用“\”来表示
-
UNIX和URL使用“/”来表示
Java程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File类提供了一个常量 (静态的成员属性):
public static final String separator:根据操作系统,动态的提供分隔符
// Windows
File file1 = new File("d:\\user\\info.txt");
// 使用File类的separator分隔符属性
File file2 = new File("d:" + File.separator + "user" + File.separator + "info.txt");
// URI
File file3 = new File("d:/user");
1.3 File类常用构造器
-
public File(String pathname):以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果 pathname是相对路径,则默认的当前路径在系统属性user.dir(本项目的目录)中存储。
-
public File(String parent,String child):以parent为父路径,child为子路径创建File对象。
-
public File(File parent,String child):根据一个父File对象和子文件路径创建File对象
-
public File(URI uri):将URI 转换为一个抽象路径名来创建一个新的 File 实例
// 构造File对象
// 1. public File(String pathname)
File file = new File("D:\\code\\a.txt");
// 2. public File(String parent,String child)
File file1 = new File("D:", "code");
// 3. public File(File parent,String child)
File file3 = new File(file1,"a.txt");
// 4. public File(URI uri)
URI uri = new URI("file:/c:/a.txt");
File file4 = new File(uri);
1.4 File常用方法
1.4.1 File类的获取功能
-
public String getAbsolutePath():获取绝对路径
-
public String getPath() :获取路径
-
public String getName() :获取名称
-
public String getParent():获取上层文件目录路径。若无,返回null
-
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
-
public long lastModified() :获取最后一次的修改时间,毫秒值
-
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组,没有返回null
-
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File对象(路径)数组
// 构造File对象
File file = new File("D:\\code\\IdeaProjects\\test\\system.txt");
// 获取绝对路径
System.out.println(file.getAbsolutePath()); // D:\code\IdeaProjects\test\system.txt
// 获取路径
System.out.println(file.getPath()); // D:\code\IdeaProjects\test\system.txt
System.out.println(new File("system.txt").getPath()); // system.txt
System.out.println(new File("system.txt").getAbsolutePath()); // D:\code\IdeaProjects\test\system.txt
// 获取名称
System.out.println(file.getName()); // system.txt
// 获取上层文件目录路径
System.out.println(file.getParent()); // D:\code\IdeaProjects\test
System.out.println(new File("D:").getParent()); // null
// 获取文件长度(即:字节数), 不能获取目录的长度
System.out.println(file.length()); // 24
// 获取最后一次的修改时间,毫秒值
System.out.println(file.lastModified()); // 1661150820904
System.out.println(new Date(file.lastModified())); // Mon Aug 22 14:47:00 GMT+08:00 2022
// 获取指定目录下的所有文件或者文件目录的名称数组
System.out.println(Arrays.toString(new File("D:\\code").list())); // [Ideacode, IdeaProjects, 尚硅谷, 欧鹏]
// 获取指定目录下的所有文件或者文件目录的File数组
System.out.println(Arrays.toString(new File("D:\\code").listFiles()));
// [D:\code\Ideacode, D:\code\IdeaProjects, D:\code\尚硅谷, D:\code\欧鹏]
查阅File类的API,在查阅时发现File类中重载的listFiles方法,并且接受指定的过滤器
package com.openlab.day24;
import java.io.File;
import java.io.FilenameFilter;
public class FileDemo {
public static void main(String[] args) {
// 获取扩展名为.java所有文件
// 创建File对象
File file = new File("E:\\JavaSE1115\\code\\day11_code");
// 获取指定扩展名的文件,由于要对所有文件进行扩展名筛选,因此调用方法需要传递过滤器
File[] files = file.listFiles(new FileFilterBySuffix(".java"));
// 遍历获取到的所有符合条件的文件
for (File f : files) {
System.out.println(".........." + f);
}
}
}
// 定义类实现文件名称FilenameFilter过滤器
class FileFilterBySuffix implements FilenameFilter {
private String suffix;
// 在创建过滤器对象时,明确具体需要过滤的文件名称
public FileFilterBySuffix(String suffix) {
this.suffix = suffix;
}
public boolean accept(File dir, String name) {
return name.endsWith(suffix);
}
}
@Test
void test06() {
File file = new File("I:\\java\\java_se\\2021");
// 过滤特定的文件
String[] names = file.list((dir, name) -> name.endsWith(".txt"));
for (String s : names) {
System.out.println(s);
}
}
@Test
void test07() {
File file = new File("I:\\java\\java_se\\2021");
// 过滤特定的文件
File[] names = file.listFiles(pathname -> pathname.getName().endsWith(".zip"));
for (File f : names) {
System.out.println(f.getAbsolutePath());
}
}
1.4.2 File类的重命名功能
-
public boolean renameTo(File dest):把文件重命名为指定的文件路径
File file = new File("system.txt");
System.out.println(file.renameTo(new File("info.txt"))); // true
1.4.3 File类的判断功能
-
public boolea isAbsolute()判断是否是绝对路径
-
public boolean isDirectory():判断是否是文件目录
-
public boolean isFile() :判断是否是文件
-
public boolean exists() :判断是否存在
-
public boolean canRead() :判断是否可读
-
public boolean canWrite() :判断是否可写
-
public boolean canExecute() :判断该文件是否可以通过它的抽象名称可执行
-
public boolean isHidden() :判断是否隐藏
File file = new File("D:\\code\\IdeaProjects\\test\\info.txt");
// 判断是否是绝对路径
System.out.println(file.isAbsolute()); // true
// 判断是否是文件目录
System.out.println(file.isDirectory()); // false
// 判断是否是文件
System.out.println(file.isFile()); // true
// 判断是否存在
System.out.println(file.exists()); // true
// 判断是否可读
System.out.println(file.canRead()); // true
// 判断是否可写
System.out.println(file.canWrite()); // true
// 判断该文件是否可以通过它的抽象名称可执行
System.out.println(file.canExecute()); // true
// 判断是否隐藏
System.out.println(file.isHidden()); // false
1.4.4 File类的创建功能
-
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
-
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
-
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
// 创建文件
File file = new File("a.jpg");
File file1 = new File("a.jpg");
try {
// 需要做异常处理
System.out.println(file.createNewFile()); // true
System.out.println(file1.createNewFile()); // false
} catch (IOException e) {
e.printStackTrace();
}
// 创建文件目录
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a\\a").mkdir()); // false
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a").mkdir()); // true
System.out.println(new File("D:\\code\\IdeaProjects\\test\\b\\b").mkdirs()); // true
注意:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
当硬盘中真有一个真实的文件或目录存在时,创建File对象时,各个属性会显式赋值。
当硬盘中没有真实的文件或目录对应时,那么创建对象时,除了指定的目录和路径之外,其他的属性都是取成员变量的默认值。
1.4.5 File类的删除功能
public boolean delete():删除文件或者文件夹
// a文件夹中无文件和目录
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a").delete()); // true
// b文件夹中还有一个b文件夹
System.out.println(new File("D:\\code\\IdeaProjects\\test\\b").delete()); // false
注意:Java中的删除不走回收站,要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
例题:遍历指定目录所有文件名称,包括子文件目录中的文件。
public static void scannFile(String path) {
File file = new File(path);
File[] listFiles = file.listFiles();
for (File f : listFiles) {
if (f.isFile()) {
System.out.println(f.getAbsolutePath());
} else if(f.isDirectory()) {
// 文件夹
scannFile(path + File.separatorChar + f.getName());
}
}
}
二、IO流原理及流的分类
1. Java IO原理
I/O是Input/Output的缩写,即输入输出流, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。
Java程序中,对于数据在内存中输入和输出操作以“流(stream)” 的 方式进行。
从狭义上来说:数据在内存中输入和输出
从广义来说,不同电脑之间的数据流动,也是一种IO流
-
狭义上的IO流:本地进程间的数据流动
-
广义上的IO流:远程进程间的数据流动
java.io包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
2. 流的分类
按数据流的流向不同分为:
-
输入流
-
输出流
按操作数据单位不同分为:
-
字节流(8 bit)
-
字符流(16 bit)
按流的角色的不同分为:
-
节点流
-
处理流:包含①缓存流(装饰流或过滤流);②转换流;③标准输入、输出流;④打印流;⑤数据流;⑥对象流
转换字节和字符流动特殊流:转换流
Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的:
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀
IO 流体系
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
节点流 | FileInputStream | FileOutputStream | FileReader | FileWriter |
缓冲流 | BufferInputStream | BufferOutputStream | BufferReader | BufferWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
数据流 | DataInputStream | DataOutputStream |
3. InputStream & Reader
InputStream 和 Reader 是所有输入流的基类
字节流 InputStream(典型实现:FileInputStream)
-
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(典型实现:FileReader)
-
int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
-
int read(char [] c):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
-
int read(char [] c, int off, int len):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
-
public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。
@Test
void testIStream() {
InputStream is = null;
try {
is = new FileInputStream(new File("./a.txt"));
byte[] buf = new byte[1024];
while(is.read(buf) != -1) {
System.out.write(buf);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
void testIStream01() {
InputStream is = null;
try {
is = new FileInputStream(new File("I:\\java\\java_se\\2022\\day22\\src\\com\\openlab\\day22\\TestCollections.java"));
byte[] buf = new byte[1024];
while(is.read(buf) != -1) {
System.out.write(buf);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
void testIStream02() {
InputStream is = null;
try {
is = new FileInputStream(new File("I:\\java\\java_se\\2022\\day22\\src\\com\\openlab\\day22\\TestCollections.java"));
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf)) != -1) {
System.out.write(buf, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源,一定要记得关闭流!!!
FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader
4. OutputStream & Writer
OutputStream 和 Writer 也非常相似:
-
void write(int b/int c):
-
OutputStream:将指定的字节写入此输出流。向输出流写入一个字节。要写 入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
-
Writer:写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。
-
-
void write(byte[] b/char[] cbuf):
-
OutputStream:将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。
-
Writer:写入字符数组。
-
-
void write(byte[] b/char[] buff, int off, int len):
-
OutputStream:将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。
-
Writer:写入字符数组的某一部分。从off开始,写入len个字符
-
-
void flush():
-
OutputStream:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立 即写入它们预期的目标。
-
Writer:刷新该流的缓冲,则立即将它们写入预期目标。
-
-
void close():需要先刷新,再关闭此流
因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数
-
void write(String str):写入字符串。
-
void write(String str, int off, int len):写入字符串的某一部分。
FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter
三、节点流
3.1 FileInputStream和FileReader
3.1.1 读取文件的步骤
1.建立一个流对象,将已存在的一个文件加载进流。
2.创建一个临时存放数据的数组。
3.调用流对象的读取方法将流中的数据读入到数组中。
4. 关闭资源。
3.1.2 FileInputStream
FileInputStream 是 InputStream (字节流输入流) 的典型实现。
FileInputStream fis = null;
try {
// 1.建立一个流对象,将已存在的一个文件加载进流。
fis = new FileInputStream(new File("info.txt"));
// 2.创建一个临时存放数据的数组(大小4k对齐)。FileInputStream 是一个字节流
byte[] bytes = new byte[1024];
// 3.调用流对象的读取方法将流中的数据读入到数组中。
int len = 0;
while ((len = fis.read(bytes)) != -1) {
// 方法1:
// System.out.write(bytes, 0, len);
// 方法2:
System.out.println(new String(bytes, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭资源。
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.1.3 FileReader
FileReader 是 Reader (字符流输入流) 的典型实现。
FileReader fr = null;
try {
// 1.建立一个流对象,将已存在的一个文件加载进流。
fr = new FileReader(new File("info.txt"));
// 2.创建一个临时存放数据的数组(大小4k对齐)。FileReader 是一个字符流
char[] chars = new char[1024];
// 3.调用流对象的读取方法将流中的数据读入到数组中。
int len = 0;
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭资源。
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2 FileOutputStream和FileWriter
3.2.1 写入文件的步骤
1.创建流对象,建立数据存放文件;
2.调用流对象的写入方法,将数据写入流;
3.关闭流资源,并将流中的数据清空到文件中。
3.2.2 FileOutputStream
FileOutputStream 是 OutputStream (字节流输出流) 的典型实现。
FileOutputStream fos = null;
try {
// 1.创建流对象,建立数据存放文件;
// fis = new FileOutputStream(new File("info.txt")); // 则目录下有同名文件将被覆盖。
fos = new FileOutputStream(new File("info.txt"), true); // 在文件内容末尾追加内容。
// 2.调用流对象的写入方法,将数据写入流;FileOutputStream 是一个字节流
fos.write("HelloWorld!".getBytes()); // 调用getBytes()方法获取一个字节数组,将字节数组写入流
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源。
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.3 FileWriter
FileWriter 是 Writer (字符流输出流) 的典型实现。
FileWriter fw = null;
try {
// 1.创建流对象,建立数据存放文件;
fw = new FileWriter(new File("info.txt")); // 覆盖目录下同名文件。
// 2.调用流对象的写入方法,将数据写入流;FileOutputStream 是一个字节流
String msg = "HelloWorld!";
fw.write(msg.toCharArray()); // 调用toCharArray()方法获取一个字符数组,将字符数组写入流
fw.write("HelloWorld!"); // 将字符串写入流
} catch (IOException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源。
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
练习:读取一个文件写入到另一个文件中
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(new File("test.iml"));
fw = new FileWriter(new File("info.txt"));
char[] chars = new char[1024];
int len = 0;
while ((len = fr.read(chars)) != -1) {
fw.write(chars, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:
1. 定义文件路径时,注意:可以用“/”或者“\\”。
2. 在读取文件时,必须保证该文件已存在,否则报异常。
3. FileInputStream、FileOutputStream字节流一定要通过字节数组进行读取和写入,目的是为了防止内存过大,导致出现OOMError。
4. 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
5. 字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。
6. 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文 件将被覆盖。如果使用构造器FileOutputStream(file, true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。
四、缓冲流
为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区
public
class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
-
BufferedInputStream 和 BufferedOutputStream
-
BufferedReader 和 BufferedWriter
当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush() 可以强制将缓冲区的内容全部写入输出流。
关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
flush()方法的使用:手动将buffer中内容写入文件
如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。