Java IO 基础
文件
文件就是保存数据的地方。
- 流: 数据在数据源(文件)和程序(内存)之间经历的路径
- 输入流:数据从数据源(文件)到程序(内存)的路径
- 输出流: 数据从程序(内存)到数据源(文件)的路径
常用的文件操作
new File(String pathname)
: 根据路径构建一个File
对象new File(File parent, String child)
: 根据父目录文件+子路径构建new File(String parent, String child)
: 根据父目录+子路径构建
import org.junit.Test;
import java.io.File;
import java.io.IOException;
// 演示创建文件
public class FileCreate {
public static void main(String[] args) {
}
// 方式1
@Test
public void create01() {
String filePath = "f:\\news1.txt";
File file = new File(filePath);
try {
// 执行了这个方法才会在磁盘创建
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 方式2
@Test
public void create02() {
File parentFile = new File("f:\\");
String fileName = "news2.txt";
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("创建成功02");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 方法3
public void create03() {
String parentPath = "f:\\";
String fileName = "news03.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("03 创建成功");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
获取文件的信息
import org.junit.Test;
import java.io.File;
public class FileInformation {
public static void main(String[] args) {
}
// 获取文件的信息
@Test
public void info() {
File file = new File("f:\\news1.txt");
// 得到对应的信息
System.out.println("文件名: "+ file.getName());
System.out.println("绝对路径: "+ file.getAbsolutePath());
System.out.println("父级目录: "+ file.getParent());
// UTF-8 一个中文是3个字节
System.out.println("文件大小(字节):" + file.length());
System.out.println("文件是否存在: "+ file.exists());
System.out.println("文件是否是文件: "+ file.isFile());
System.out.println("文件是否是目录:"+ file.isDirectory());
}
}
文件夹操作
import org.junit.Test;
import java.io.File;
public class Directory_ {
public static void main(String[] args) {
}
// 判断 F:\\news1.txt 是否存在, 如果存在就删除
@Test
public void m1() {
String filePath = "F:\\news1.txt";
File file = new File(filePath);
if (file.exists()){
if (file.delete()) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
} else {
System.out.println("该文件不存在");
}
}
// 在 Java 编程中, 目录也当成文件
// 判断
@Test
public void m2() {
String filePath = "F:\\filedemo";
File file = new File(filePath);
if (file.exists()){
if (file.delete()) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
} else {
System.out.println("该目录不存在");
}
}
// 判断文件夹是否存在, 如果不存在就创建
// 创建多级目录要用 mkdirs
// 创建一级目录用 mkdir 就行
@Test
public void m3() {
String directoryPath = "F:\\demo2\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()) {
System.out.println(directoryPath + "已存在");
} else {
if (file.mkdirs()) {
System.out.println("文件夹创建成功");
} else {
System.out.println("文件夹创建失败");
}
}
}
}
IO
IO 流原理及流的分类
- 按操作数据单位分为: 字节流(8bit,二进制文件), 字符流(按字符, 文本文件)
- 按数据流的流向分为: 输入流, 输出流
- 按流的角色分为: 节点流, 处理流/包装流
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
InputStream
- FileInputStream: 文件输入流
- BufferedInputStream: 缓冲字节输入流
- ObjectInputStream: 对象字节输入流
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStream_ {
public static void main(String[] args) {
}
// 演示读取文件
// 单个字节的读取, 效率比较低
@Test
public void readFile01() {
String filePath = "F:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
// 创建这个对象用于读取文件
fileInputStream = new FileInputStream(filePath);
// 从该输入流中读取一个字节的数据, 如果没有输入可用, 此方法将被阻止
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭文件流, 释放资源
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 使用 read(byte[]) 进行优化
@Test
public void readFile02() {
String filePath = "F:\\hello.txt";
int readLen = 0;
// 字节数组
byte[] buf = new byte[8];
FileInputStream fileInputStream = null;
try {
// 创建这个对象用于读取文件
fileInputStream = new FileInputStream(filePath);
// 如果读取完毕返回 -1
// 如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf,0, readLen)); // 转成
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭文件流, 释放资源
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
OutPutStream
- FileOutputStream
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream01 {
public static void main(String[] args) {
}
@Test
public void writerFile() {
// 创建 FileOutputStream 对象
String filePath = "F:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
// 得到 FileOutputStream 对象
// 这种方式会覆盖原来的内容
// 如果希望是追加的话, new FileOutputStream(filePath, true)
fileOutputStream = new FileOutputStream(filePath);
// 写入一个字节
fileOutputStream.write('a');
// 写入一个字符串
fileOutputStream.write("Hello,world".getBytes());
// 另一种方法, start(offset) 和 length
fileOutputStream.write("000start,end000".getBytes(), 3, 9);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
文件拷贝
主要就是边读边拷.
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
}
// 完成文件拷贝
//
@Test
public void copyFile() {
// 将F:\\a.txt 拷贝到 E:\\
// 如果文件大, 应该是读取部分数据, 就写入到指定的文件.
String filePath = "f:\\bun.mp4";
String outputFilePath = "f:\\bun2.mp4";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream= null;
int readLen = 0;
try {
fileInputStream = new FileInputStream(filePath);
fileOutputStream = new FileOutputStream(outputFilePath, true);
// 定义一个字节数组
byte[] buf = new byte[1024];
while((readLen = fileInputStream.read(buf)) != -1) {
fileOutputStream.write(buf,0 ,readLen);
}
System.out.println("拷贝成功");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fileInputStream != null){
fileInputStream.close();
}
if (fileOutputStream != null){
fileOutputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
FileReader
// 单个字符读取文件
public class FileReader_ {
public static void main(String[] args) throws IOException {
String filePath = "F:\\a.txt";
int data;
try (FileReader fileReader = new FileReader(filePath)) {
// 循环读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void readFile02() {
String filePath = "F:\\a.txt";
char[] buf =new char[4];
int readLen;
try (FileReader fileReader = new FileReader(filePath)) {
// 循环读取
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf,0,readLen));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
FileWriter
fileWrite 已经要关闭(close)或者刷新(flush)
import org.junit.Test;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriter_ {
public static void main(String[] args) {
}
@Test
public void writeFile01() throws IOException {
String filePath = "F:\\a.txt";
// 这个写法有点像 python 的
// with open
try (FileWriter fileWriter = new FileWriter(filePath)) {
// 写入单个字符 write(int)
fileWriter.write("L");
// 写入指定数组 write(char[])
char[] buf = {'U','O' };
fileWriter.write(buf);
// 写入指定数组的指定部分 write(char[], off, len)
fileWriter.write(" file",0,5);
// write(string) 写入整个字符串
fileWriter.write(" test");
// write(string, off,len)
fileWriter.write(" textaaaaaaaaaaaaaaa",0,5);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
节点流和处理流
- 节点流可以从一个特定的数据源读取数据,如 FileReader, FileWriter
- 处理流(也叫包装流)是“连接”再已经存在的流(节点流或处理流)之上, 为程序提供更加强大的独写功能, 也更加灵活, 如 BufferedReader, BufferedWriter
常见的节点流可以分为:
- 字节输入流
- 字节输出流
- 字符输入流
- 字符输出流
可以根据访问的数据源分类
- 访问文件
- 访问数组
- 访问管道
- 访问字符串
节点流和处理流的区别和联系
- 节点流是底层流, 直接与数据源相关
- 处理流(包装流)包装节点流, 既可以消除不同节点流的实现差异, 也可以提供更方便的方法来完成输入输出.
- 处理流(包装流)对节点流进行包装, 使用了修饰器设计模式, 不会直接与数据源相连.
处理流的功能
- 性能的提高: 主要增加缓冲的方式来提高输入输出的效率.
- 操作的便捷: 处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据, 使用更加灵活方便.
处理流
BufferedReader
和 BufferedWriter
属于字符流, 是按照字符来读取数据的.
关闭处理流时, 只需要关闭外层流即可.
public class BufferedReader_ {
@Test
public void readFile01() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("F:\\a.txt"));
// 读取
String line;
// 1. BufferedReader.readLine() 是按行读取文件
// 2. 当返回 null 时, 表示文件读取完毕.
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
// 关闭流, 只需要关闭 BufferedReader, 底层源码会自己去关闭被包装的流.
bufferedReader.close();
}
}
public class BufferedWriter_ {
@Test
public void writeFile01() throws IOException {
// 如果要追加模式应当在 FileWriter 里面构造的时候 FileWriter("F:\\b.txt", true)
// 应该很容易理解吧.
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("F:\\b.txt"));
bufferedWriter.write("你好");
bufferedWriter.write("测试会不会换行");
// 看来不会换行
bufferedWriter.newLine();
// 这个是换行符, 是根据系统相关的换行符.
bufferedWriter.write("新的一行");
bufferedWriter.close();
}
}
文件拷贝
public class BufferedCopy_ {
@Test
public void copyFile01() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("F:\\a.txt"));
String destFilePath = "F:\\demo\\b.txt";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
String line;
while ((line = bufferedReader.readLine()) != null) {
bufferedWriter.write(line);
// 记得插入换行符
bufferedWriter.newLine();
}
bufferedWriter.close();
bufferedReader.close();
}
}
不要用 Reader
, Writer
去拷贝二进制文件, 图片, 视频等, 试过了真的会坏.
二进制文件用字符流, 看下面
@Test
public void copyFile02() throws IOException {
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
String srcPath = "F:\\pyy.PNG";
String destPath = "F:\\demo\\pyy.PNG";
byte[] buff = new byte[1024];
try {
bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath));
int readLen = 0;
// 当 readLen 为 -1 时就表示读取完毕
while ((readLen = bufferedInputStream.read(buff)) != -1){
bufferedOutputStream.write(buff, 0, readLen);
}
} catch (IOException e){
e.printStackTrace();
} finally {
if (bufferedInputStream != null){
bufferedInputStream.close();
}
if (bufferedOutputStream != null) {
bufferedOutputStream.close();
}
}
}
对象流
有时候需要把数据保存到文件中, 把对象保存到文件中, 因此需要ObjectOutputStream, 需要保存数据值和数据类型, 就是要能够将对象进行序列化和反序列化.
为了让某个对象支持序列化机制, 就必须让这个类时可序列化的, 为了让某个类时可序列化的, 该类必须实现如下两个接口之一:
- Serializable(一般用这个)
- Externalizable(因为这个有方法需要实现)
public class ObjectOutputStream_ {
@Test
public void saveObject() throws IOException {
// 序列化后保存的文件格式不是文本, 而是他的方法
String filePath = "F:\\object.dat";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
// Integer 实现了 Serializable 接口
objectOutputStream.writeInt(100);
// 放字符串不是用 writeString
objectOutputStream.writeUTF("你好");
objectOutputStream.writeObject(new Person("allison"));
objectOutputStream.close();
}
@Test
public void readObject() throws IOException, ClassNotFoundException {
String filePath = "F:\\object.dat";
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));
// 读取的顺序(反序列化的顺序), 需要和保存数据的顺序一致
System.out.println(inputStream.readInt());
System.out.println(inputStream.readUTF());
// 就算这个类的定义一模一样, 但是如果不在同一个包下也会有问题, 比如我在别的包下定义的 Person 类,
// 哪怕代码一模一样, 也无法读取, 因为所在的包不一样.
Object p = inputStream.readObject();
System.out.println("类型: "+p.getClass());
System.out.println(p);
}
}
class Person implements Serializable {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// @Override
// public String toString() {
// return "Person{" +
// "name='" + name + '\'' +
// '}';
// }
}
注意:
- 独写顺序要一致
- 要求序列化或者反序列化对象, 需要实现 Serializable
- 序列化的类中建议添加 SerialVersionUID, 为了提高版本的兼容性
- 序列化对象时, 默认将里面所有属性都进行序列化, 但除了 static 或 transient 修饰的成员.
- 序列化对象时, 要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性, 也就是如果某类已经实现了序列化, 则它的所有子类也已经默认实现了序列化.
标准输入输出流
public class InputAndOutput {
@Test
public void aaa(){
// System.in
// 编译类型 InputStream
// 运行类型 BufferedInputStream
System.out.println(System.in.getClass());
// Scanner scanner = new Scanner(System.in);
// scanner.nextLine();
// 1. System.out
// 编译类型 PrintStream
// 运行类型 PrintStream
// 表示输出到显示屏上
System.out.println(System.out.getClass());
}
}
转换流
InputStreamReader: Reader 的子类, 可以将 InputStream(字节流)包装成Reader(字符流)
OutputStreamWriter: Writer 的子类, 可以将 OutputStream(字节流)包装成 Writer(字符流)
常用于不同编码方式的读取
public class CodeQuestion {
@Test
public void readFile01() throws IOException {
// 读取 F:\\a.txt
// 1. 创建一个字符输入流 BufferedReader [处理流]
// 2. 使用 BufferedReader 读取 a.txt
// 3. 在默认情况下, 读取文件时按照 UTF-8 进行读取的, 当文件不是用 UTF-8 保存的时候, 可能会出现乱码
//
String filePath = "F:\\a.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
String s = bufferedReader.readLine();
while ((s = bufferedReader.readLine()) != null){
System.out.println("读取到的内容: "+s);}
bufferedReader.close();
// 如果不是 UTF-8 就可能会乱码
}
@Test
public void readFile02() throws IOException {
String filePath = "F:\\a.txt";
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gb18030"));
String s = bufferedReader.readLine();
while ((s = bufferedReader.readLine()) != null){
System.out.println("读取到的内容: "+s);}
bufferedReader.close();
// 指定了编码就可以了, 就不会乱码了
}
@Test
public void saveFile01() throws IOException {
String filePath = "F:\\c.txt";
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(Paths.get(filePath)), "gbk"));
bufferedWriter.write("你好!");
bufferedWriter.newLine();
bufferedWriter.write("Hello");
bufferedWriter.close();
}
}
打印流
打印流只有输出流没有输入流: PrintWriter
, PrintStream
public class Print {
@Test
public void printStream() throws IOException {
PrintStream out = System.out;
// 默认情况下, PrintStream 输出数据的位置是 标准输出, 即显示器
out.println("你好");
// 基本等价于
// out.write("你好".getBytes());
// 好像不能直接用 newLine
// out.newLine(); 因为 newLine 是 private 方法
out.close();
System.setOut(new PrintStream("F:\\d.txt"));
System.out.println("输出到文件");
}
@Test
public void printWriter() throws IOException {
// PrintWriter(Writer)
// PrintWriter(OutputStream)
// PrintWriter(Charset, File)
PrintWriter printWriter = new PrintWriter(new FileWriter("F:\\e.txt"));
printWriter.println("你好 print writer");
// 这时候才真的写数据, 可以看源码, 也可以debug到这里看 e.txt 有没有内容
printWriter.close();
}
}
Properties 类
public class Properties_ {
@Test
public void readProperties01() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\mysql.properties"));
String line;
while((line = bufferedReader.readLine())!=null) {
System.out.println("line: " + line);
}
bufferedReader.close();
// 但是这样效果不好,也不方便
}
@Test
public void readProperties02() throws IOException {
// 使用 Properties 类来读取 mysql.properties 文件
// 1. 创建 Properties 对象
String filePath = "src\\mysql.properties";
Properties properties = new Properties();
// 2. 加载指定配置文件
properties.load(new FileReader(filePath));
// 3. 把 k-v 显示在控制台
// properties.list(System.out);
// 4. 根据 key 获取对应的值
System.out.println("IP: " + properties.getProperty("ip"));
}
@Test
public void modifiedProperties() throws IOException {
// 1. 创建 Properties 对象
String filePath = "src\\mysql.properties";
Properties properties = new Properties();
// 2. 加载指定配置文件
properties.load(new FileReader(filePath));
// 3. 修改文件
properties.setProperty("ip","192.168.0.1");
properties.store(new FileWriter(filePath), "会在properties 最上面作为一个注释");
}
}