1. 缓冲流
1. 字节缓冲流
包装 -> 高级流: BufferedInputStream/BufferedOutputStream
Buffered: 缓冲/缓存
通常用于文件复制
扩展: 文件加密 -> 原理, 将读出来的字节做了运算后写入到新文件中
解密 -> 将加密过的文件, 再读出, 做一个还原运算, 重新写入
运算 -> 为了避免byte取值范围溢出, 同时为了避免加密解密两套代码
一般使用 ^ 运算
1 2 4 8 16 32 64
63 = 32 + 16 + 8 + 4 + 2 + 1
00111111
^ 13 = 00001101
= 00110010
^ 13 = 00001101
= 00111111
代码实现:
import org.junit.Test;
import java.io.*;
import java.util.Arrays;
public class WriteAndRead {
@Test
public void writeTest() throws IOException {
File file = new File("src/com/hzt/stream/a.txt");
System.out.println(file.getAbsolutePath());
//获取字符串编码
byte[] bw = "你好,世界!".getBytes("gbk");
FileOutputStream os = new FileOutputStream(file, true);
os.write(bw);
os.close();
}
@Test
public void readTest() throws IOException {
File file = new File("src/com/hzt/stream/a.txt");
byte[] br = new byte[3];
FileInputStream is = new FileInputStream(file);
int len;
//read方法是每次读取br.length个字节,如果读到末尾返回-1,否则返回读取的字符数
//使用写方法写入数组时要注意:
//由于一直用同一个字符串读取,最后一次只读取了一个字节,导致后两个字节是上次读取的数据
while((len = is.read(br)) != -1){
System.out.println(len);
System.out.println(Arrays.toString(br));
}
is.close();
}
@Test
//字节流用于复制
public void copy() throws IOException {
File fileI = new File("src/com/hzt/stream/a.txt");
File fileO = new File("src/com/hzt/stream/a_bak.txt");
FileInputStream is = new FileInputStream(fileI);
FileOutputStream os = new FileOutputStream(fileO, true);
byte[] b = new byte[3];
int len;
//当读取字节数为-1时表示读取到了末尾
while ((len = is.read(b)) != -1){
//将每次读取的有效字节写入
os.write(b, 0, len);
}
os.close();
is.close();
}
//缓冲字节流包装字节流 大幅度提高效率
@Test
//字节流用于复制
public void bufferCopy() throws IOException {
//图片地址
File fileI = new File("src/com/hzt/stream/p.jpg");
File fileO = new File("src/com/hzt/stream/p_bak.jpg");
//使用缓冲流包装字节流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileI));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileO));
byte[] b = new byte[1024];
int len;
long time1 = System.currentTimeMillis();
//当读取字节数为-1时表示读取到了末尾
while ((len = bis.read(b)) != -1){
//1.将每次读取的有效字节写入缓冲区
//2.当缓冲区满了之后自动刷新缓冲区释放资源,同时把数据写入到内存
bos.write(b, 0, len);
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
//先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使⽤了。
bos.close();
bis.close();
}
@Test
public void flushTest() throws IOException {
File fileI = new File("src/com/hzt/stream/a.txt");
File fileO = new File("src/com/hzt/stream/a_bak.txt");
//使用缓冲流包装字节流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileI));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileO));
long time1 = System.currentTimeMillis();
int i;
//当读取字节数为-1时表示读取到了末尾
while ((i = bis.read()) != -1){
bos.write(i);
bos.flush();//写在输出流后面
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
}
@Test
//文件的加密与解密
public void encryption(){
File fileI = new File("src/com/hzt/stream/a.txt");
File fileE = new File("src/com/hzt/stream/a_bak_encryption.txt");
File fileO = new File("src/com/hzt/stream/a_bak.txt");
// try(resource) {} 不管有没有异常, resource都会自动关闭, 不需要finally
try( //使用缓冲流包装字节流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileI));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileE));
BufferedInputStream bisE = new BufferedInputStream(new FileInputStream(fileE));
BufferedOutputStream bosE = new BufferedOutputStream(new FileOutputStream(fileO));) {
long time1 = System.currentTimeMillis();
//当读取字节数为-1时表示读取到了末尾
int i;
//当读取字节数为-1时表示读取到了末尾
while ((i = bis.read()) != -1){
//在写入时对字节进行异或操作加密,因为是逐位运算所以不会超过范围(通常运算可能会超过字节范围)
bos.write(i ^ 1);
// bos.flush();//写在输出流后
}
//解密文件
while ((i = bisE.read()) != -1){
//在写入时对字节进行异或操作解密,利用异或运算两次会复原的特性将异或加密的文件解密
bosE.write(i ^ 1);
bosE.flush();//写在输出流后面
}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字符缓冲流
包装字符流 -> 缓冲字符流
通常用来读写文件内容
BufferedReader(Reader)
String readLine(): 读取一整行, 返回null 标记读到文件末尾
代码实现:
public class BufferStreamTest {
//字符缓冲流
@Test
public void read(){
File fileI = new File("./src/com/hzt/stream/a.txt");
//构造方法:使用缓冲流包装由字符流包装的字节流,字节输出流可以设置append, 字符流可设置字符编码
try{
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fileI), "utf-8"));
int i;
String str;
//循环读取一个字符,返回值为-1时结束,本操作系统不识别/r
while ((i = br.read()) != -1){
System.out.print((char)i);
}
//循环读取一行,返回值为null时结束,""即空字符串不为null,每次读取遇到换行符结束,不包括换行符
// while ((str = br.readLine()) != null){
// System.out.println(str);
// }
}catch(IOException e){
e.printStackTrace();
}
}
@Test
public void write(){
File fileO = new File("./src/com/hzt/stream/a.txt");
//定义一个字节缓冲输出流,参数为FileWriter,通过字符流便捷类改变可添加属性append
try{
BufferedWriter bw = new BufferedWriter(new FileWriter(fileO, true));
bw.write("你好");
bw.flush();
}catch (IOException e){
e.printStackTrace();
}
}
}
2.打印流
1.字节打印流(了解)
代码实现:
@Test
public void bytePrint(){
File file = new File("src/com/hzt/stream/a.txt");
//打印流用于在文件中打印出数据流中的数据
try{
//构造方法1:(文件路径,字符集)
PrintStream ps1 = new PrintStream(file, "utf-8");
//构造方法2:(字节缓冲流(字节流(file, append)), autoFlush, enCoding)
//append 可拼接, autoFlush 自动刷新, enCoding 字符编码, 用BufferedOutputStream包装FileOutputStream
PrintStream ps2 = new PrintStream(new BufferedOutputStream(new FileOutputStream(file, true)), true, "utf-8");
ps2.println("黎明杀机");
ps2.print("怪物猎人");
ps2.print(":世界");
ps2.append('a');
//创建一个打印流向为控制台的字节打印流
PrintStream ps3 = new PrintStream(System.out);
ps3.println("赛博朋克2077");
//改变控制台的打印流为ps2
System.setOut(ps2);
System.out.println("\n控制台输出");
}catch (IOException e){
e.printStackTrace();
}
}
2.字符打印流
PrintWriter
1.只能清空原文件内容, 但是可以指定字符集
PrintWriter(String fileName, String csn)
2.可以在原文件上追加内容, 但是不可以指定字符集, 可以自动刷新缓冲区
PrintWriter(OutputStream out, boolean autoFlush)
3.可以在原文件上追加内容, 可以指定字符集, 可以自动刷新缓冲区
PrintWriter(Writer out, boolean autoFlush)
void print(Object): 写出内容, 不加换行
void println(): 写出内容, 并且换行
注意: 自动刷新的功能, 只有println方法具有
print方法 必须手动刷新
代码实现:
//字符打印流
@Test
public void characterPrint(){
File file = new File("src/com/hzt/stream/a.txt");
try{
//构造方法1:(文件路径, 字符集)
PrintWriter pw1 = new PrintWriter(file, "utf-8");
//构造方法2:(字节缓冲输出流(字节输出流(file, append)), autoFlush)
PrintWriter pw2 = new PrintWriter(new BufferedOutputStream(new FileOutputStream(file, true)), true);
//构造方法3:(字符缓冲输出流(字符输出流(
// 字节缓冲输出流(字节输出流(file, append)), charsetName)), autoFlush)
PrintWriter pw3 = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
new BufferedOutputStream(new FileOutputStream(file, true)), "utf-8")), true);
//打印
//不像PrintStream类,如果启用自动刷新,它只会在调用的println,printf,或format方法来完成
pw3.println("怪物猎人:世界");
pw3.print("怪物猎人:冰原");
//print方法需要刷新
pw3.flush();
}catch (IOException e){
e.printStackTrace();
}
}
3.对象流
对象输入输出流:
对象 -> 文件[字节] : 序列化
文件[字节] -> 对象 : 反序列化
ObjectInputStream(InputStream)
Object readObject()
ObjectOutputStream(OutputStream)
void writeObject(Object)
Serializable: 可序列化的
接口中没有常量, 也没有抽象方法, 空接口
序列化接口没有方法或字段,仅用于标识可串行化的语义。
serialVersionUID: 给类添加固定的序列版本号
transient: 修饰的变量, 在序列化的时候, 会被忽略掉
代码实现:
import org.junit.Test;
import java.io.*;
import java.util.Date;
public class ObjectStreamTest implements Serializable{
//对象流
//创建内部类类(JavaBean规范),实现序列化
//Serializable接口中没有任何方法,只是作为可序列化的标识
private class Student implements Serializable {
private String name;
private int age;
private transient char gender; // transient瞬态修饰成员,值不会被序列化
/*Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号
的木的在于验证序列化的对象和对应类是否版本匹配。*/
//所以需要定义序列号,不定义的话会随机生成,生成之后不可修改
public static final long serialVersionUID = 13065705269l;
public Student(){};
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
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;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return "name:" + this.name + " age:" + this.age + " gender:" + this.gender;
}
}
//序列化
@Test
public void writeObject(){
File file = new File("./src/com/hzt/stream/a.txt");
Student student = new Student();
student.setAge(20);
student.setName("hzt");
student.setGender('男');
Date date = new Date();//date类是可序列化的
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))){
oos.writeObject(student);//写进去的类序列号为serialVersionUID,如果没有定义就随机生成
oos.writeObject(date);
oos.writeObject(null);
}catch (IOException e){
e.printStackTrace();
}
}
//反序列化
/*
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,
那么反序列化操作也会失败,抛出一个 InvalidClassException 异常
*/
@Test
public void readObject(){
File file = new File("./src/com/hzt/stream/a.txt");
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){
Student student = (Student) ois.readObject();
Date date = (Date)ois.readObject();
Object object = (Object)ois.readObject();//对象值为null也可以读出
System.out.println(student);
System.out.println(date);
System.out.println(object);
Object end = (Object)ois.readObject();//如果读到文件末尾会抛出EOF异常(已检测异常必须处理,是IOException的子类)
System.out.println(end);
//因为gender属性是瞬态放,所以值在反序列化时就被忽略了
}catch ( ClassNotFoundException | IOException e){
e.printStackTrace();
}
}
}