一.缓冲输出流
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次
package io;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 缓冲输出流写出数据的缓冲区问题
*/
public class BOS_FlushDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "奥里给!";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
System.out.println("写出完毕!");
bos.flush();//冲
bos.close();
}
}
缓冲流的工作原理
读取到了一个字节/字符,先不输出,等凑足了缓冲的最大容量后一次性写出去,从而提高了工作效率,减少了对硬盘的读取次数
二.flush()方法
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲
输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现
该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
缓冲流的close()操作中会自动调用一次flush,确保所有缓存的数据会被写出
flush()方法写的次数少了,性能高,但是即使性低,所以flush()方法时看需求的,如果是qq发消息及时性需要多次flush,如果是文件复制就不需要频发的flush
知识点补充
1. BufferWriter和BufferReader是自带缓冲,自带数组,不需要指定数组
2. IO的四个顶级流都是抽象类,没有办法实例化对象
三.对象流
java.io.ObjectOutputStream和ObjectInputSteam
序列化和反序列化实际是对同一个文件进行的操作
1. 对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。
2. 对象序列化:将一个java对象按照其结构转换为一组字节的过程(将内存中的java对象放到硬盘文件上).实际上在网络传输的过程中,会把对象切成一块一块的,然后给每块编上编号,通过网络传输的方式传到文件上
3. 对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节),实际上是把硬盘文件中的一块一块的拿到内存中组装起来
关于序列化问题
1. java.io.NotSerializableException:不支持序列化
2. 参与序列化和反序列的对象,必须实现Serializable接口
3. 通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable{
}
这个接口当中什么代码都没有
那么它起到一个什么作用?
起到的标志作用,标志的作用:java虚拟看到这个类实现了这个忌口,可能会对这个类进行特殊待遇
4. 标志接口(签名接口)给java虚拟机看到这个接口之后,会自动生成一个序列化版本号
如果没有手动写出来,java虚拟机会默认提供这个序列化版本号.
- 实现序列化接口后最好主动定义序列化版本号这个常量。
- 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。
- 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
private static final long serialVersionUID = 42L;
对象的序列化
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class OOSDemo {
public static void main(String[] args) throws IOException {
//将一个Person对象写入文件person.obj
String name = "苍老师";
int age = 18;
String gender = "女";
String[] otherInfo = {"是一名台词不多的演员","来自岛国","爱好写大字","广大男性同胞的启蒙老师"};
Person p = new Person(name,age,gender,otherInfo);
System.out.println(p);
FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
}
}
对象的反序列化
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 使用对象输入流完成对象的反序列化
*/
public class OISDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//从person.obj文件中将对象反序列化回来
FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
/*
Object readObject()
该方法会进行对象的反序列化,如果对象流通过其连接的流读取的字节分析并非
是一个java对象时,会抛出异常:ClassNotFoundException
*/
Person p = (Person)ois.readObject();
System.out.println(p);
}
}
补充:对于版本号的理解
- 版本号是根据类的结构生成的,只跟源代码的结构有关系(多少个变量,方法等等,与内容无关),和类中属性的值没关系,如果更改类的结构,会发生异常
- 把版本号写成常量后,就不会再生成版本号了,会按照你的版本号,反序列化.只要版本号一致,就能反序列化成功
- 只要定义好这个类,直接写这个版本号,希望还是原来的类
四.transient:游离的,不参与序列化
transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
package io;
import java.io.Serializable;
import java.util.Arrays;
/**
* 使用当前类实例测试对象流的读写操作
*/
public class Person implements Serializable {
private String name;//姓名
private int age;//年龄
private String gender;//性别
private String[] otherInfo;//其他信息
public Person(String name, int age, String gender, String[] otherInfo) {
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", otherInfo=" + Arrays.toString(otherInfo) +
'}';
}
}
五.字符流
Reader
和Writer
- java将流按照读写单位划分为字节流与字符流.
- java.io.Reader和Writer则是所有字符流的超类,它们和字节流的超类是平级关系.
- Reader和Writer是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法.
- 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.
六.转换流
java.io.InputStreamReader 和 OutputStreamWriter
- 它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流连接中是非常重要的一环.
使用转换输出流向文件中写入文本数据
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OSWDemo {
public static void main(String[] args) throws IOException {
//向文件osw.txt中写入文字
FileOutputStream fos = new FileOutputStream("osw.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
osw.write("我可以接受你的所有,所有小脾气.");
osw.write("我可以带你去吃很多,很多好东西.");
System.out.println("写出完毕!");
osw.close();
}
}
使用转换输入流读取文本文件
package io;
import java.io.*;
/**
* 转换字符输入流
* 可以将读取的字节按照指定的字符集转换为字符
*/
public class ISRDemo {
public static void main(String[] args) throws IOException {
//将osw.txt文件中的所有文字读取回来.
FileInputStream fis = new FileInputStream("osw.txt");
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
/*
字符流读一个字符的read方法定义:
int read()
读取一个字符,返回的int值实际上表示的是一个char(低16位有效).如果返回的
int值表示的是-1则说明EOF
*/
//测试读取文件中第一个字
// int d = isr.read();
// char c = (char)d;
// System.out.println(c);
//循环将文件所有字符读取回来
int d;
while((d = isr.read()) != -1){
System.out.print((char)d);
}
isr.close();
}
}
思考:为什么用int来进行接收读取的字符
因为这里读取的是单个的字符,返回的是char,char实际上是0-65535范围内的int型,所有用int型来接收,
在输出的时候进行强制转换,是为了输出方便读取
转换流的意义:
实际开发中我们还有功能更好用的字符高级流.但是其他的字符高级流都有一个共通点:不能直接连接在字节流上.而实际操作设备的流都是低级流同时也都是字节流.因此不能直接在流连接中串联起来.转换流是一对可以连接在字节流上的字符流,其他的高级字符流可以连接在转换流上.在流连接中起到"转换器"的作用(负责字符与字节的实际转换)
七.缓冲字符流
java.io.BufferedWriter和BufferedReader
缓冲字符输出流:java.io.PrintWriter
(常用)
- 缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓冲流有一个特别的功能:可以按行读写文本数据.
java.io.PrintWriter
具有自动行刷新的缓冲字符输出流,实际开发中更常用.它内部总是会自动连接BufferedWriter作为块写加速使用.
package io;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
/**
* 缓冲字符流
* 缓冲字符流是一对高级流,在流连接中的作用是提高读写文本数据的效率,并且
* 可以按行读写字符串.
* java.io.BufferedReader和BufferedWriter
*/
public class PWDemo1 {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
/*
PrintWriter提供了对文件操作的构造方法:
PrintWriter(String path)
PrintWriter(File file)
*/
//向文件中写入字符串
PrintWriter pw = new PrintWriter("pw.txt","UTF-8");
pw.println("我看过沙漠下暴雨");
pw.println("看过大海亲吻鲨鱼");
pw.println("看过黄昏追逐黎明");
pw.println("没看过你");
System.out.println("写出完毕!");
pw.close();
}
}
八.PrintWriter的自动行刷新功能
- 如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。
即:每当我们用PW的println方法写出一行字符串后会自动flush.
PrintWriter pw = new PrintWriter(bw,true);
package io;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 在流连接中使用PW
*/
public class PWDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//文件字节输出流(是一个低级流),向文件中写入字节数据
FileOutputStream fos = new FileOutputStream("pw2.txt",true);
//转换输出流(是一个高级流,且是一个字符流)。1:衔接字符与字节流 2:将写出的字符转换为字节
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
//缓冲输出流(是一个高级流,且是一个字符流)。块写文本数据加速
BufferedWriter bw = new BufferedWriter(osw);
//具有自动行刷新的缓冲字符输出流
PrintWriter pw = new PrintWriter(bw,true);
//完成简易记事本。控制台输入的每行字符串都按行写入文件。单独输入exit时退出。
Scanner scanner = new Scanner(System.in);
while(true){
String line = scanner.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
pw.close();
}
}
- print方法是不会自动行刷新
- println方法会自动行刷新
知识点补充:
- 这个方法是字符串自己定义的,忽略比较内容字母大小写
- 这个方法不能用来验证密码一致,可以用来输验证码
if("exit".equalsIgnoreCase(line))
九.系统日志
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
/*
日志工具
*/
public class Logger {
/*
记录日志的方法
*/
public static void log(String msg) {
try {
//指向一个日志文件
PrintStream out = new PrintStream(new FileOutputStream("log.txt",true));
// 改变输出的方向
System.setOut(out);
// 日期当前时间
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime+":"+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
测试代码
public class LogTest {
public static void main(String[] args) {
//测试工具类是否好用
Logger.log("调用System类的gc()方法,建议启动垃圾回收");
Logger.log("用户尝试进行登录,验证失败");
Logger.log("用户密码更改成功");
}
}