什么是流
文件在程序中是以流的形式来操作的
流 : 数据在数据源(文件)和程序(内存)之间经历的路径
以Java程序(内存)为参考点
- 输入流 : 数据从数据源到程序
- 输出流 : 数据从程序到数据源
文件基础知识
🎪 具体操作查阅文档 ~
- 创建文件
createNewFile()
- 获取文件信息
getName
length
isFile
getAbsolutePath
getParent
等等 - 目录操作
mkdirs
delete
等等
IO流原理及分类
- I/O是input/output的缩写, 用于处理数据传输. 如读写文件, 网络通讯等
- Java程序中, 对于数据的输入/输出操作以流的方式进行
- 按操作数据单位不同分为 : 字节流(8 bit)和字符流
- 按数据流的流向不同分为 : 输入流和输出流
- 按流的角色的不同分为 : 节点流和处理流/包装流
Java的IO流共涉及到40多个类, 都是从4个抽象基类派生出来的分别是
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
四个抽象基类的常用子类
FileInputStream
文件输入流-读取
让我们来实现一个小案例, 读取D盘下demo目录中的hello.txt文件
//使用多个字符读取方法
package io_test;
import com.sun.org.apache.xml.internal.utils.StringToIntTable;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInPutStream_01 {
public static void main(String[] args) {
//要读取文件的路径
String filePath = "d:\\demo\\hello.txt";
//创建一个byte数组, 用来多个字符读取
byte[] buf = new byte[8];//每次读取8个字节
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建FileInputStream对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多buf.length字节的数据, 如果没有输入可用, 该方法将被阻止
//如果返回-1, 表示读取完毕
//如果读取正常,返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1){
//public String(byte[] bytes,要解码为字符的字节
// int offset,要解码的第一个字节的索引
// int length)要解码的字节数
System.out.print(new String(buf,0,readLen));//显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意 : 我们使用的是字节输入流的子类, 也就是说我们并不能读取中文. 因为会造成乱码.
FileOutputStream
文件输出流
让我们来实现一个小案例, 将数据写入D盘下的demo下的a.txt, 如果文件不存在, 则创建文件
//使用多个字符写入方法
package io_test;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutPutStream_01 {
public static void main(String[] args) {
//要写入文件的路径
String filePath = "d:\\demo\\a.txt";
//创建FileOutputStream对象
FileOutputStream fileOutPutStream = null;
String str = "hello,world";
try {
//得到FileOutPutStream对象
fileOutPutStream = new FileOutputStream(filePath);
//写入字符串
fileOutPutStream.write(str.getBytes());//把字符串转成byte数组
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
try {
fileOutPutStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意 : 如果是 new FileOutputStream(filePath)
创建方式, 当写入内容时, 会覆盖掉原来的内容, 但是有时我们并不想这样做, 而是想要在末尾添加.
那么我们可以使用 new FileOutputStream(filePath, true)
创建方式
FileReader
字符输入流-读取
常用方法 :
new FileReader(File/String)
read
: 每次读取单个字符, 返回该字符, 如果到文件末尾返回-1read(char[])
: 批量读取多个字符到数组, 返回读取到的字符数, 如果到文件末尾返回-1
相关API
String类
new String(char[])
: 将char[]转换成Stringnew String(char[],off,len)
: 将char[]的指定部分转换成String
FileWriter
字符输出流
常用方法 :
new FileWriter(File/String)
: 覆盖模式, 相当于流的指针在首端new FileWriter(File/String, true)
: 追加模式, 相当于流的指针在尾端write(int)
: 写入单个字符write(char[])
: 写入指定数组write(char[],off,len)
: 写入指定数组的指定部分write(string)
: 写入整个字符串write(string,off,len)
: 写入字符串的指定部分
相关API
String类
toCharArray
: 将String转换成char[]
BufferedReader
缓冲字符输入流, 属于处理流
- 节点流可以从一个特定的数据源读写数据, 也就是专门操作文件的
- 处理流(也叫包装流) 在已存在的流(节点流或处理流) 之上, 为程序提供更为强大的读写功能. 既可以消除不同节点流的实现差异, 也可以提供更方便的方法来完成输入输出
- 关闭处理流时, 只需关闭外层流就行了, 因为底层真正在操作的, 还是节点流
BufferedReader类中, 有属性Reader, 即可以封装一个节点流, 该节点流可以是任意的. 只要是Reader子类
让我们来实现一个小案例, 使用BufferedReader读取文本文件
package io_test;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReader_01 {
public static void main(String[] args) throws IOException {
String filePath = "d:\\demo\\a.java";
//创建BufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line;//按行读取
//当返回一个null时, 表示读取完毕
while ((line = bufferedReader.readLine()) != null ){
System.out.println(line);
}
//关闭流, 只需要关闭外层的处理流就行, java底层会自动去关闭节点流
bufferedReader.close();
}
}
BufferedWriter
缓冲字符输出流, 属于处理流
让我们来实现一个小案例, 把上一个案例读取到的数据写入到b.java中
package io_test;
import java.io.*;
public class BufferedWriter_01 {
public static void main(String[] args) throws IOException {
//源文件
String srcFilePath = "d:\\demo\\a.java";
//目标文件
String destFilePath = "d:\\demo\\b.java";
//按行读取
String line;
//创建BufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(srcFilePath));
//创建BufferedWriter
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
//边读边写
while ((line = bufferedReader.readLine()) != null){
bufferedWriter.write(line);
//插入一个换行
bufferedWriter.newLine();
}
//关闭流
bufferedReader.close();
bufferedWriter.close();
}
}
注意 : BufferedReader 和 BufferedWriter 是按照字符操作的, 不要去操作二进制文件, 可能造成文件损坏
BufferedInputStream与BufferedOutputStream
字节处理流
让我们来实现一个小案例, 通过字节处理流完成图片的拷贝
package io_test;
import pc.B;
import java.io.*;
public class BufferedOutputStream_01 {
public static void main(String[] args) throws IOException {
//源文件
String srcFilePath = "d:\\demo\\demo1\\drb.png";
//目标文件
String destFilePath = "d:\\demo\\drb1.png";
//每次读取1024个字节
byte [] buf = new byte[1024];
int len;//实际读取的字节
//创建BufferedInputStream
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFilePath));
//创建BufferedOutputStream
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
//边读边写
while ((len = bufferedInputStream.read(buf)) != -1){
bufferedOutputStream.write(buf, 0, len);
}
//关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
}
}
对象流
什么是对象流?
让我们首先看两个需求 :
- 将
int num = 100;
这个int数据保存到文件中, 注意不是100数字, 而是int 100
, 并且, 能够从文件中直接恢复int 100
- 将
Dog dog = new Dog("小黄",3);
这个dog对象保存到文件中, 并且能够从文件恢复
上面的要求就是在进行序列化和反序列化操作.
那么什么是序列化和反序列化呢? 序列化就是在保存数据时, 保存数据的值和数据类型. 反序列化就是在恢复数据时, 恢复数据的值和数据类型.
为了让某个对象支持序列化机制, 必须实现两个接口之一(Serializable/Externalizable), 使其类是可序列化的.
ObjectOutputStream
对象字节输出流
//main中
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer(实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean(实现了 Serializable)
oos.writeChar("a");// char -> Character(实现了 Serializable)
oos.writeUTF("jack");//String
//保存一个Dog对象
oos.writeObject(new Dog("旺财",10));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
//如果需要序列化某个类,需要接口Serializable(标记接口,没有方法)
class Dog implements Serializable{
private String name;
private int age;
public Dog(String name,int age){
this.name = name;
}
}
ObjectInputStream
对象字节输入流
读取指定文件, 并进行反序列化恢复数据
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取
//读取的顺序需要和保存数据的顺序一致, 否则会出现异常
System.out.println(ois.readIne());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object->Dog
//关闭流
ois.close();
对象处理流使用细节
- 要求实现Serializable或者Externalizable接口, 一般而言, 我们使用的是Serializable. 因为它是一个标记接口, 而Externalizable需要我们实现其方法, 才能够使用.
- 读写顺序要一致
- 序列化对象时, 要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性, 也就是如果某类已经实现了序列化, 则它的所有子类也已经默认实现了序列化
- 序列化对象时, 默认将里面所有属性都进行序列化, 但除了static或transient修饰的成员
模拟修饰器设计模式
在前文我们提到的处理流就是使用修饰器设计模式, 不会直接与数据源相连.
下面让我们来模拟实现这种设计模式的源码
public abstract class Reader_{//抽象类 模拟为Reader
public void readFile(){}
public void readString(){}
}
public class FileReader_ extends Reader_{//节点流
public void readFile(){
System.out.println("对文件进行读取...");
}
}
public class StringReader_ extends Reader_{//节点流
public void readString(){
System.out.println("读取字符串...");
}
}
public class BufferedReader_ extends Reader_{//处理流
private Reader_ reader_;//属性
//构造器
public BufferedReader_(Reader_ reader_){
this.reader_ = reader_;
}
//扩展
//让方法更加灵活,多次读取文件
public void readFiles(int num){
for(int i =0;i < num;i++){
reader_.readFile();
}
}
//扩展 readString,批量处理字符串数据
public void readStrings(int num){
for(int i =0;i < num;i++){
reader_.readString();
}
}
}
//main中
BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
bufferedReader_.readFiles(10);//读十次文件
BufferedReader_ bufferedReader_ = new BufferedReader_(new StringReader_());
bufferedReader_.readStrings(5);//读取5次字符串
如何解决乱码问题?
默认情况下, 我们使用UTF-8编码, 但是如果编码出现了问题, 不是使用UTF-8而是其他编码, 例如ANsI, 我们会出现中文乱码的问题. 如何解决? 我们可以使用转换流来避免出现乱码问题.
转换流 : 可以把字节流转换成字符流.
当处理纯文本数据时, 如果使用字符流效率更高, 并且可以有效解决中文乱码问题, 所以建议将字节流转换成字符流, 可以在使用时指定编码格式
InputStreamReader
Reader的子类, 可以将InputStream(字节流)转换成Reader(字符流)
将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 UTF-8
package io_test;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//把FileInputStream 转成 InputStreamReader, 指定了utf-8码
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "utf-8");
//把InputStreamReader 传入 BufferedReader ,使用BufferedReader读取(效率高)
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//读取
String s = bufferedReader.readLine();
System.out.println("读取的内容" + s);
//关闭流
bufferedReader.close();
}
}
OutputStreamWriter
Writer的子类, 可以将OutputStream(字节流)转换成Writer(字符流)
将FileOutputStream(字节流) 转换成 OutputStreamWriter(字符流) 指定编码形式gbk
package io_test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
outputStreamWriter.write("hi,哒哒");
outputStreamWriter.close();
}
}
配置文件properties
//读取
//使用Properties类来读取mysql.properties文件
//1.创建Properties对象
Properties properties = new Properties();
//2.加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3.把k-v显示到控制台
properties.list(System.out);
//4.根据key获取对应的值
String user = properties.getProperty("user");
//修改
//使用Properties类添加key-val到新文件mysql.properties中
//1.如果该文件没有key就是创建
//2.如果该文件有key就是修改
Properties properties = new Properties();
properties.setProperty("charset","utf8");
properties.setProperty("user","汤姆");
//将 k-v存储到文件中
properties.store(new FileOutputStream("src:\\mysql2.properties"),null);//注释这里为null