目录
6.1、File类
描述:该类是文件和目录路径名的抽象表示。
构造方法:
创建功能:
判断功能:
获取功能:
删除功能:
6.2、Files类
jdk7 引入了 Path 和 Paths 类:
- Path 用来表示文件路径
- Paths 是工具类,用来获取 Path 实例
Path source = Paths.get("1.txt"); // 相对路径 使用 user.dir 环境变量来定位 1.txt
Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了 d:\1.txt
Path source = Paths.get("d:/1.txt"); // 绝对路径 代表了 d:\1.txt
Path projects = Paths.get("d:\\data", "projects"); // 绝对路径 代表了 d:\data\projects
.
代表了当前路径..
代表了上一级路径
例如:
d:
|- data
|- projects
|- a
|- b
代码:
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path); // 原来的路径
System.out.println(path.normalize()); // 正常化路径
输出:
d:\data\projects\a\..\b
d:\data\projects\b
检查是否存在:
Path path = Paths.get("data.txt");
System.out.println(Files.exists(path));
创建一级目录:
Path path = Paths.get("helloword");
Files.createDirectory(path);
- 如果该目录已经存在了,会抛异常 FileAlreadyExistsException
- 不能一次创建多级目录,否则会抛异常 NoSuchFileException
创建多级目录:
Path path = Paths.get("helloword/hello/world");
Files.createDirectories(path);
拷贝文件方法:
Path source = Paths.get("helloword/source.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
- 如果文件已存在,会抛异常 FileAlreadyExistsException
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
移动文件方法:
Path source = Paths.get("helloword/source.txt");
Path target = Paths.get("helloword/target.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
- StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
删除文件方法:
Path target = Paths.get("helloword/target.txt");
Files.delete(target);
- 如果文件不存在,会抛异常 NoSuchFileException
删除为空目录:
Path target = Paths.get("helloword/hello/world");
Files.delete(target);
- 如果目录还有内容,会抛异常 DirectoryNotEmptyException
删除非空目录:
Files.walkFileTree(Paths.get("d:\\a"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return super.postVisitDirectory(dir, exc);
}
});
遍历指定目录:
Files.walkFileTree(Paths.get("d:\\a"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println(dir);
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("\t" + file);
return super.visitFile(file, attrs);
}
});
拷贝多级目录:
String source = "d:\\a";
String target = "d:\\b";
Files.walk(Paths.get(source)).forEach(path -> {
try {
String targetName = path.toString().replace(source, target);
// 目录文件
if (Files.isDirectory(path)) {
Files.createDirectory(Paths.get(targetName));
}
// 普通文件
else if (Files.isRegularFile(path)) {
Files.copy(path, Paths.get(targetName));
}
} catch (IOException e) {
e.printStackTrace();
}
});
6.3、IO流
概述:IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制、文件上传、文件下载、文件的读取、文件的写出等等。
分类:
按照数据流向来分:
输入流:读数据
输出流:写数据
按照数据类型来分:
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
注意:
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频、应用等二进制文件,优先使用字节流
- 如果不确定文件类型,优先使用字节流,字节流是万能的流
6.3.1、字节流
体系:
字节流写数据的三种方式:
字节流读数据的三种方式:
字节流复制文件的四种方式:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
method1();
method2();
method3();
method4();
}
// 基本字节流一次读写一个字节
public static void method1() throws IOException {
FileInputStream fis = new FileInputStream("sFolder\\demo.txt");
FileOutputStream fos = new FileOutputStream("dFolder\\demo.txt");
int by;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
// 基本字节流一次读写一个字节数组
public static void method2() throws IOException {
FileInputStream fis = new FileInputStream("sFolder\\demo.txt");
FileOutputStream fos = new FileOutputStream("dFolder\\demo.txt");
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 字节缓冲流一次读写一个字节
public static void method3() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("sFolder\\demo.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dFolder\\demo.txt"));
int by;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 字节缓冲流一次读写一个字节数组
public static void method4() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("sFolder\\demo.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dFolder\\demo.txt"));
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
字节流复制单级文件夹:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File srcFolder = new File("D:\\sFolder");
File destFolder = new File("D:\\dFolder");
copyFolder(srcFolder, destFolder);
}
/**
* 复制单级文件夹
*
* @param srcFolder 源文件夹
* @param destFolder 目的文件夹
* @throws IOException
*/
private static void copyFolder(File srcFolder, File destFolder) throws IOException {
// 判断路径是否存在
if (!destFolder.exists()) {
destFolder.mkdirs();
}
// 获取目的文件列表
File[] listFiles = srcFolder.listFiles();
// 遍历目的文件列表
for (File file : listFiles) {
copyFile(file, new File(destFolder, file.getName()));
}
}
/**
* 复制文件
*
* @param srcFile 源文件
* @param destFile 目的文件
* @throws IOException
*/
private static void copyFile(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
字节流复制多级文件夹:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File srcFolder = new File("D:\\sFolder");
File destFolder = new File("D:\\dFolder");
copyFolder(srcFolder, destFolder);
}
/**
* 复制多级文件夹
*
* @param srcFolder 源文件夹
* @param destFolder 目的文件夹
* @throws IOException
*/
private static void copyFolder(File srcFolder, File destFolder) throws IOException {
// 判断路径是否存在
if (!destFolder.exists()) {
destFolder.mkdirs();
}
// 获取目的文件列表
File[] listFiles = srcFolder.listFiles();
// 遍历目的文件列表
for (File file : listFiles) {
if (file.isDirectory()) {
copyFolder(file, new File(destFolder, file.getName()));
} else {
copyFile(file, new File(destFolder, file.getName()));
}
}
}
/**
* 复制文件
*
* @param srcFile 源文件
* @param destFile 目的文件
* @throws IOException
*/
private static void copyFile(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
6.3.2、字符流
体系:
字符流写数据的五种方式:
字符流读数据的四种方式:
字符流复制文本的七种方式:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class Main {
public static void main(String[] args) throws IOException {
method1();
method2();
method3();
method4();
method5();
method6();
method7();
}
// 基本字符流一次读写一个字符
public static void method1() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("sFolder\\demo.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("dFolder\\demo.txt"));
int ch;
while ((ch = isr.read()) != -1) {
osw.write(ch);
}
osw.close();
isr.close();
}
// 基本字符流一次读写一个字符数组
public static void method2() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("sFolder\\demo.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("dFolder\\demo.txt"));
char[] chs = new char[1024];
int len;
while ((len = isr.read(chs)) != -1) {
osw.write(chs, 0, len);
}
osw.close();
isr.close();
}
// 文件字符流一次读写一个字符
public static void method3() throws IOException {
FileReader fr = new FileReader("sFolder\\demo.txt");
FileWriter fw = new FileWriter("dFolder\\demo.txt");
int ch;
while ((ch = fr.read()) != -1) {
fw.write(ch);
}
fw.close();
fr.close();
}
// 文件字符流一次读写一个字符数组
public static void method4() throws IOException {
FileReader fr = new FileReader("sFolder\\demo.txt");
FileWriter fw = new FileWriter("dFolder\\demo.txt");
char[] chs = new char[1024];
int len;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
}
fw.close();
fr.close();
}
// 字符缓冲流一次读写一个字符
public static void method5() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("sFolder\\demo.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("dFolder\\demo.txt"));
int ch;
while ((ch = br.read()) != -1) {
bw.write(ch);
}
bw.close();
br.close();
}
// 字符缓冲流一次读写一个字符数组
public static void method6() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("sFolder\\demo.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("dFolder\\demo.txt"));
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
}
bw.close();
br.close();
}
// 字符缓冲流特有功能复制文本文件
public static void method7() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("sFolder\\demo.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("dFolder\\demo.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
bw.close();
br.close();
}
}
6.3.3、特殊流
标准输入流:
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws IOException {
InputStream is = System.in;
int by;
while ((by = is.read()) != -1) {
System.out.print((char) by);
}
is.close();
}
}
标准输出流:
import java.io.IOException;
import java.io.PrintStream;
public class Main {
public static void main(String[] args) throws IOException {
PrintStream ps = System.out;
ps.println("Hello,World");
ps.write("Hello,World".getBytes());
ps.close();
}
}
字节打印流:
import java.io.IOException;
import java.io.PrintStream;
public class Main {
public static void main(String[] args) throws IOException {
PrintStream ps = new PrintStream("ps.txt");
ps.println(97);
ps.write(97);
ps.close();
}
}
字符打印流:
import java.io.IOException;
import java.io.PrintWriter;
public class Main {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter("pw.txt");
pw.println("hello");
pw.write("Hello");
pw.close();
}
}
对象序列化流:
注意:需要实现Serializable接口,同时需要给出serialVersionUID
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Student implements Serializable {
private static final long serialVersionUID = 5923003911550370832L;
private String name;
private Integer age;
public Student() {
super();
}
public Student(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Main {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt"));
Student s = new Student("曹晨磊", 30);
oos.writeObject(s);
oos.close();
}
}
对象反序列化流:
注意:成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
class Student implements Serializable {
private static final long serialVersionUID = 5923003911550370832L;
private String name;
private Integer age;
public Student() {
super();
}
public Student(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s);
ois.close();
}
}
6.4、ByteBuffer
使用步骤:
向 buffer 写入数据,例如调用 channel.read(buffer)
调用 flip() 切换至读模式
从 buffer 读取数据,例如调用 buffer.get()
调用 clear() 或 compact() 切换至写模式
重复 1~4 步骤
代码演示:
try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(10);
while (true) {
int len = channel.read(buffer);
if (len == -1) break;
buffer.flip();
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.print((char) b);
}
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
运行效果:
Hello,World
内部结构:
ByteBuffer 有以下重要属性:
- capacity
- position
- limit
一开始:
写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态。
flip 动作发生后,position 切换为读取位置,limit 切换为读取限制。
读取 4 个字节后,状态:
clear 动作发生后,状态:
compact 方法,是把未读完的部分向前压缩,然后切换至写模式。
分配空间:
可以使用 allocate 方法为 ByteBuffer 分配空间,其它 buffer 类也有该方法。
Bytebuffer buf = ByteBuffer.allocate(16);
- ByteBuffer.allocate(16).getClass():class java.nio.HeapByteBuffer,java 堆内存,读写效率低,受到 GC 的影响,分配的效率高
- ByteBuffer.allocateDirect(16).getClass():class java.nio.DirectByteBuffer,直接内存,读写效率高,不受 GC 影响,分配的效率低
向 buffer 写入数据:
有两种办法:
- 调用 channel 的 read 方法
int readBytes = channel.read(buf);
- 调用 buffer 自己的 put 方法
buf.put((byte)127);
从 buffer 读取数据:
有两种办法:
- 调用 channel 的 write 方法
int writeBytes = channel.write(buf);
- 调用 buffer 自己的 get 方法
byte b = buf.get();
get 方法会让 position 读指针向后走,如果想重复读取数据
- 可以调用 rewind 方法将 position 重新置为 0
- 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
mark 和 reset方法:
mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置,但是,rewind 和 flip 都会清除 mark 。
字符串与 ByteBuffer 互转:
第一种方式:
ByteBuffer buffer = StandardCharsets.UTF_8.encode("abc123");
String string = StandardCharsets.UTF_8.decode(buffer).toString();
System.out.println(string);
第二种方式:
ByteBuffer buffer = Charset.forName("utf-8").encode("abc123");
String string = Charset.forName("utf-8").decode(buffer).toString();
System.out.println(string);
6.5、FileChannel
Channel 介绍:
Channel 有一点类似于 Stream,它就是读写数据的双向通道,可以从 Channel 将数据读入 buffer,也可以将 buffer 的数据写入 Channel,而之前的 Stream 要么是输入,要么是输出,Channel 比 Stream 相比更为底层。
Channel 分类:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
FileChannel 获取方法:
FileChannel 只能工作在阻塞模式下,不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法。
- 通过 FileInputStream 获取的 channel 只能读
- 通过 FileOutputStream 获取的 channel 只能写
- 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定
FileChannel 读取方法:
会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾。
int readBytes = channel.read(buffer);
FileChannel 写入方法:
写入的正确姿势如下,在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel。
ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式
while(buffer.hasRemaining()) {
channel.write(buffer);
}
FileChannel 关闭方法:
channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法,或者使用 try 代码块自动释放。
try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
} catch (IOException e) {
e.printStackTrace();
}
FileChannel 位置方法:
获取当前位置
long pos = channel.position();
设置当前位置
long newPos = ...;
channel.position(newPos);
设置当前位置时,如果设置为文件的末尾,这时读取会返回 -1 ,这时写入,会追加内容,但要注意,如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)。
FileChannel 大小方法:
使用 size 方法获取文件的大小。
FileChannel 强制写入:
操作系统出于性能考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件权限等信息)立刻写入磁盘。
FileChannel 文件拷贝:
try (
FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
long size = from.size();
// remain 变量代表还剩余多少字节
for (long remain = size; remain > 0; ) {
remain -= from.transferTo((size - remain), remain, to);
}
} catch (IOException e) {
e.printStackTrace();
}