IO流的分类
- 按照流的方向进行分类
以内存作为参照物:输入流,输出流
- 往内存中去,叫做输入(Input)。或者叫做读(Read)
- 从内存中出来,叫做输出(Output)。或者叫做写(write)
- 按照读取数据方式不同进行分类
以读取字符的不同:字节流,字符流
- 有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。这种流是万能流,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等.…
- 有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片,声音,视频等文件。只能读取纯文本文件,连word文件都无法读取。
假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:'a’字符('a’字符在windows系统中占用1个字节)
第二次读:'中’字符('中’字符在windows系统中占用2个字节)
假设文件file1.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到’a’
第二次读:一个字节,正好读到’中’字符的一半
第三次读:一个字节,正好读到’中’字符的另外一半
’a’英文字母,在Windows操作系统当中是一个字节,但是’a’字符在Java中占用两个字节;file1.txt文本和Java没关系,它是Windows操作系统上的普通文件;字节流直接读取的是8个二进制位,字符流可以一个字符一个字符检测出来
Java中的IO流都已经写好了,我们程序员不需要关心;Java中的所有流都是在:java.io.*;下
Java IO流有四大家族:(都是抽象类abstract)
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
注意:在Java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流
所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道
注意:如果没有flush()可能会导致丢失数据
java.io包下需要掌握的流有16个:
文件专属:
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
转换流:(将字节流转换成字符流)
- java.io.InputStreamReader
- java.io.OutputStreamWriter
缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
对象专属流:
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
标准输出流:
- java.io.PrintWriter
- java.io.PrintStream
数据流专属:
- java.DataInputStream
- java.io.DataOutputStream
java.io.FileInputStream
- 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
- 字节的方式,完成输入的操作,完成读的操作(硬盘 —> 内存)
首先在硬盘某处创建一个文件,然后用FileInputStream去读取文件里面的内容
public static void main(String[] args) throws IOException {
// 创建文件字节输入流对象
// 文件路径:D:\text.txt (Java编译器会自动把\变成\\,因为Java中\表示转义)
// 以下都是采用了:绝对路径的方式
// 写成这个 / 也是可以的
FileInputStream fis = new FileInputStream("D:\\bbb\\test");
// int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身
// System.out.println(readData);// 97
int readData = 0;
while(readData!=-1) {// 如果读到文件内容的末尾,后面没有值了,则返回-1
readData = fis.read();
System.out.println(readData);
}
}
分析上面的缺点:一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源耗费在交互上面了
// 相对路径:一定是从当前所在的位置作为起点开始找!
/*
int read(byte[] b)
一次最多读取 b.length个字节。
减少硬盘和内存的交互,提高程序的执行效率。
往byte[]数组当中读。
*/
public static void main(String[] args) throws IOException {
// 开始读,采用byte数组,一次读取多个字节。最多读取“数组.length”个字节
byte[] b = new byte[4];// 准备一个4个长度的byte数组,一次最多读取4个字节
// 这个方法的返回值是:读取到的字节数量。(不是字节本身)
FileInputStream fis = new FileInputStream("D:\\bbb\\test");
int readData = fis.read(b); // 这个方法的返回值是:读取到的“字节”本身
System.out.println(readData);// 第一次读取到了4个字节
readData = fis.read(b); // 这个方法的返回值是:读取到的“字节”本身
System.out.println(readData);// 第二次只能读取到3个字节
readData = fis.read(b);
System.out.println(readData);// 第三次一个字节都没有读取到,返回-1
// 将字节数组全部转换成字符串
System.out.println(new String(b,0,readData));// asdc
// 不应该全部都转换,应该读取了多少字节,就转换多少字节
readData = fis.read(b); // 这个方法的返回值是:读取到的“字节”本身
System.out.println(readData);// efg
System.out.println(new String(b,0,readData));
}
最终版
public static void main(String[] args) throws IOException {
byte[] b = new byte[4];
FileInputStream fis = new FileInputStream("D:\\bbb\\test.txt");
int readCount = 0;
while((readCount = fis.read(b))!=-1) {
// 把byte数组转换成字符串,读到多少个就转多少个
System.out.print(new String(b,0,readCount));
}
fis.close();
}
}
FileInputStream类中的其他常用方法:
- int available():返回流中当中剩余的没有读到的字节数量
- long skip(long n):跳过几个字节不读。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\bbb\\test.txt");
System.out.println("总字节数量:"+fis.available());
byte[] bytes = new byte[fis.available()];// 这种方式不太适合太大的文件,因为byte[]数组不能太大。
// 不需要循环了
// 直接读一次就行了
// int readCount = fis.read(bytes);
// System.out.println(new String(bytes));
// int readByte = fis.read();
// System.out.println("剩下多少个字节:"+fis.available());
// skip跳过几个字节不读取,这个方法也可能以后会用
fis.skip(3);
System.out.println(fis.read());
}
public static void main(String[] args){
Flie file = new File("D://aaa");
System.out.println(file.getName());// 返回由此抽象路径名表示的文件或目录的名称。(得到最后的文件名)(返回String类型)
System.out.println(file.getPath());// 将此抽象路径名转换为一个路径名字符串。(得到完整的路径名)(返回String)
System.out.println(file.exists());// 测试此抽象路径名表示的文件或目录是否存在。(new的文件名是否存在)(boolean类型)
System.out.println(file.isFile());// 测试此抽象路径名表示的文件是否是一个标准文件。(boolean类型)
System.out.println(file.isDirectory());// 测试此抽象路径名表示的文件是否是一个目录。
if(!file.exists()){// 判断new里面文件是否存在
file.createNewFile();// 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
}
file.mkdir();// 创建此抽象路径名指定的目录。
file.mkdirs();// 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
String[] list = file.list();
for(String string : list) {
System.out.println(string);
}
File[] listFiles = file.listFiles();
for(File file2 : listFiles) {
System.out.println(file2.getName());
}
System.out.println(file.length()); //得到文件里面内容的字符串长度
}
java.io.FileIOutputStream
- 文件字节输出流,负责写
- 从内存到硬盘
public static void main(String[] args) throws IOException {
// "D:\\bbb\\test1.txt" 文件不存在的时候会自动创建!
// 这种方式谨慎使用,这种方式会先将原文件清空,然后重新写入
FileOutputStream fos = new FileOutputStream("D:\\bbb\\test.txt");
byte[] bytes = {97,98,99,100,101};
fos.write(bytes);// 将bytes数组全部写出
// 以追加的方式在文件末尾写入。不会清空原文件内容
FileOutputStream fos1 = new FileOutputStream("D:\\bbb\\test1.txt",true);
byte[] bytes1 = {97,98,99,100,101};
fos1.write(bytes1);// 将bytes数组全部写出
String s = "飒飒飒飒";
byte[] b = s.getBytes();// 将字符串转化为byte数组
fos1.write(b);// 将数组内容写入文件中
}
exists(),isFile()和isDirectory()的区别
exists()
// 当这个aaa无论是文件夹还是文件
Flie file = new File("D://aaa");
// 只要存在这个aaa,输出就是true
System.out.println(file.exists());
isFile()
// 当这个aaa是文件
Flie file = new File("D://aaa");
// 存在的这个aaa是文件时输出才是true
System.out.println(file.isFile());
isDirectory()
// 当这个aaa是文件夹时
File file = new File("D://aaa");
// 存在的这个aaa是文件夹时输出才是true
System.out.println(file.isDirectory());
mkdir()和mkdirs()的区别
mkdirs()可以建立多级文件夹,mkdir()只会建立一级的文件夹,如下:
对于mkdir()
new File("/tmp/one/two/three").mkdir();
执行后,不会建立任何目录,因为找不到/tmp/one/two目录,结果返回false
对于mkdirs()
new File("/tmp/one/two/three").mkdirs();
执行后,会建立/tmp/one/two/three四级目录
字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
- 字节流:一次读入或读出是8位二进制。
- 字符流:一次读入或读出是16位二进制。
设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
输入流和输出流
输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
输入字节流 InputStream
- InputStream 是所有的输入字节流的父类,它是一个抽象类。
- ByteArrayInputStream、StringBufferInputStream、FileInputStream
是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。 - PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
- ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
输出字节流 OutputStream
- OutputStream 是所有的输出字节流的父类,它是一个抽象类。
- ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。
- PipedOutputStream 是向与其它线程共用的管道中写入数据。
- ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
总结:
输入流:InputStream或者Reader:从文件中读到程序中;
输出流:OutputStream或者Writer:从程序中输出到文件中;
字节流
使用FileInputStream + FileOutputStream完成文件的拷贝。拷贝的过程应该是一边读,一边写。使用以上的字节流拷贝文件的时候,文件类型随意,万能的,什么类型的文件都能拷贝
public class Demo03 {
public static void main(String[] args) throws IOException {
File f1 = new File("D:\\a\\a.png");
File f2 = new File("D:\\b\\b.png");
if(!f2.exists()) {
f2.createNewFile();
}
FileInputStream fis = new FileInputStream(f1);// 创建一个输入流对象
FileOutputStream fos = new FileOutputStream(f2);// 创建一个输出流对象
byte[] b = new byte[1024];// 1KB(一次最多拷贝1KB)
// System.out.println(Arrays.toString(b));
// int i = fis.read(b); // i代表读取了多少有效字节
// System.out.println(i + Arrays.toString(b));
// i = fis.read(b); // i代表读取了多少有效字节
// System.out.println(i + Arrays.toString(b));
int i = 0;
while((i = fis.read(b))!=-1) { // 读取输入流的数据,放到b中
fos.write(b, 0, i); //从0~i,将读到的字节放入b中
}
fis.close();// 关闭数据源
fos.close();// 关闭数据源
}
}
字符流
FileReader:文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷
一次读取一个字符,用强制转换(int 转 char)
public class Demo04 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\a\\aa.txt");
FileReader fr = new FileReader(file);
int i = -1;
while((i = fr.read())!=-1) {
char c = (char)i;
System.out.print(c);// 一次读取一个字符
}
fr.close();
// int i = fr.read();
// char c = (char)i;
// System.out.println(c);
}
}
创建一个byte类型数组,一次读取4个字符
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\a\\aa.txt");
char[] cr = new char[4];
// fr.read(cr);// 按照字符的方式读取
// for(char c : cr) {
// System.out.println(c);
// }
int readCount = 0;
while((readCount = fr.read(cr))!=-1) {
System.out.println(new String(cr,0,readCount));// 一次读取4个字符fr.close();
}
fr.close();
}
在创建好的文本里面加上内容,且在控制界面显示出来
public class Demo05 {
public static void main(String[] args) throws IOException {
File file = new Flie("D:\\aaa\\b.txt");
if(!file.exists()){
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
String s = "啊吧啊吧";
fw.write();
fw.close();
FileReader fr = new FileReader(file);
int i = 0;
while((i = fr.read())!=-1){
char c = (char)i;
System.out.print(c);
}
fr.close();
}
}
FileWriter:文件字符输出流,写。只能输出普通文本。
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("D:\\a\\aa.txt");
char[] chars = {'我','是','中','国','人'};
fw.write(chars);
fw.write(chars, 2, 3);
// 写入一个换行符
fw.write("\n");
fw.write("中国人");
String s = "asd";
fw.write(s);
fw.close();
}
BufferedReader:带有缓冲区的字符输入流。使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲。
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("F:\\java\\代码\\com.blb.cn\\src\\com\\blb\\text7\\Demo10.java");
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
// 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
// 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
BufferedReader br = new BufferedReader(fr);
// 读一行
// String firstLine = br.readLine();
// System.out.println(firstLine);
String s =null;
while((s = br.readLine())!=null) {
System.out.println(s);
}
// 关闭流
// 对于包装流来说只需要关闭最外层流就行,里面的节点流会自动关闭。
br.close();
}
public class Demo06 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\aaa\\c.txt");
// if(!file.exists()) {
// file.createNewFile();
// }
// FileWriter fw = new FileWriter(file);
// String s = "飒飒飒飒";
// fw.write(s);
// fw.close();
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String s = null;
while((s = br.readLine())!=null) {
System.out.println(s);
}
// 关闭流,先关闭外面,再关闭里面
br.close();
fr.close();
}
}
转换流:InputStreamReader
public static void main(String[] args) throws IOException {
// // 字节流
// FileInputStream in = new FileInputStream("F:\\java\\代码\\com.blb.cn\\src\\com\\blb\\text7\\Demo10.java");
//
// // 通过转换流转换(InputStreamReader将字节流转换成字符流。)
// // in是节点流。reader是包装流。
// InputStreamReader reader = new InputStreamReader(in);
//
// // 这个构造方法只能传一个字符流。不能传字节流
// // reader是节点流。br是包装流
// BufferedReader br = new BufferedReader(reader);
// 将上述情况合并起来
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("F:\\java\\代码\\com.blb.cn\\src\\com\\blb\\text7\\Demo10.java")));
String s = null;
while((s = br.readLine())!=null) {
System.out.println(s);
}
// 关闭最外层
br.close();
}
BufferedWriter:带有缓冲的字符输出流
public static void main(String[] args) throws IOException {
// 带有缓冲区的字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\a\\aaa.txt"));
bw.write("hello world");
bw.write("\n");
bw.write("hello kitty!");
bw.close();
}
OutputStreamWriter:转化流
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\a\\aaa.txt")));
bw.write("hello world11");
bw.write("\n");
bw.write("hello kitty!");
bw.close();
}
FileReader + FileWriter:一边读一边写
能用记事本打开的都是普通文本文件
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("F:\\java\\代码\\com.blb.cn\\src\\com\\blb\\text7\\Demo02.java");
FileWriter fw = new FileWriter("F:\\java\\代码\\com.blb.cn\\src\\com\\blb\\text7\\Demo10.java");
char[] cr = new char[1024 * 512];
int readCount = 0;
while((readCount = fr.read(cr))!=-1){
System.out.println(new String(cr, 0 ,readCount));
fw.write(cr, 0, readCount);
}
fw.close();
fr.close();
}
java.io.DataOutputStream:数据专属的流
这个流可以将数据连同数据的类型一并写入文件。(注意:这个文件不是普通文本文档(这个文件使用记事本打不开))
public static void main(String[] args) throws IOException {
// 创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\a\\data"));
// 写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = false;
char c = 'a';
// 写
dos.writeByte(b);// 把数据以及数据类型一并写入到文件当中
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
dos.close();
}
DataInputStream:数据字节输入流(自带加密文件)
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致,才可以正常取出数据
读取对象里面的内容,可在控制面板显示出来
public static void main(String[] args) throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("D:\\a\\data"));
// 开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();
System.out.println(b);
System.out.println(s);
System.out.println(i+1000);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(sex);
System.out.println(c);
}
java.io.PrintStream:标准的字节输出流。默认输出到控制台
public static void main(String[] args) throws IOException {
// 联合起来写
System.out.println("hello World!");
// 分开写
PrintStream ps = System.out;
ps.println("ddddd");
ps.println("asdas");
ps.println(1000);
// 标准输出流不需要手动close关闭流
// 可以改变标准输出流的输出方向
/*
* 这些是之前System类使用过的方法和属性
* System.gc();
* System.currentTimeMillis();
* PrintStream ps2 = System.out;
* System.exit(0);
* System.arraycopy(......);
*/
// 标准输出流不再指向控制台,指向‘a’文件
PrintStream printStream = new PrintStream("D:\\a\\a");
// 修改输出方法,将输出方法修改到‘a’文件
System.setOut(printStream);
// 再输出
System.out.println("ddasdas");
System.out.println("dasdasdas");
System.out.println(123);
}
java.io.PrintStream:日志工具
记录日志的方法
public static void main(String[] args) throws IOException {
}
public static void log(String msg) throws FileNotFoundException {
// 指向一个日志文件
PrintStream out = new PrintStream(new FileOutputStream("D:\\a\\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("");
System.out.println(strTime + ":" + msg);
}
测试代码
public static void main(String[] args) throws IOException {
// 测试工具类是否好用
Demo02.log("调用了System类的gc方法,建议启动垃圾回收");
Demo02.log("用户尝试登录,验证失败");
}
File
1.File类和四大家族没有关系,所以File类不能完成文件的读和写
2.File对象代表什么
- 文件和目录路径名的抽象形式
- C:\Drivers 这是一个File对象
- C:\Drivers\Lan\Realtek\Readme.txt 也是File对象
- 一个File对象有可能对应的是目录,也可能是文件
- File只是一个路径名的抽象表示形式
3.需要掌握File类中常用的方法
public static void main(String[] args) throws Exception {
// 创建一个File对象
File f1 = new File("D:\\file");
// 判断是否存在!
System.out.println(f1.exists());
// 如果D:\file不存在,则以文件的形式创建出来
// if (!f1.exists()) {
// f1.createNewFile();
// }
// 如果D:\file不存在,则以目录的形式创建出来
// if (!f1.exists()) {
// // 以目录的形式新建
// f1.mkdir();
// }
// 可以创建多重目录
File f2 = new File("D:\\A\\b\\c\\d\\e");
if (!f2.exists()) {
f2.mkdirs();
}
File f3 = new File("D:\\a\\b");
// 获取文件的父路径
String parentPath = f3.getParent();
System.out.println(parentPath);// D:\a
File parentFile = f3.getParentFile();
System.out.println("获取绝对路径:"+parentFile.getAbsolutePath());// 获取绝对路径:D:\a
File f4 = new File("asd.txt");
System.out.println(f4.getAbsolutePath());// F:\java\代码\com.blb.con04\asd.txt
}
public static void main(String[] args) throws Exception {
File f1 = new File("D:\\a\\b\\c\\d\\e\\asd.txt");
// 获取文件名
System.out.println("文件名:"+f1.getName());// asd.txt
// 判断是否是一个目录
System.out.println(f1.isDirectory());// false
// 判断是否是一个文件
System.out.println(f1.isFile());// true
// 获取文件最后一次修改时间
long haomiao = f1.lastModified();// 这个毫秒是从1970年到现在的总毫秒数
// 将毫秒数转换成日期
Date time = new Date(haomiao);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strtime = sdf.format(time);
System.out.println(strtime);
// 获取文件大小
System.out.println(f1.length());// 0
// File[] listFiles()
// 获取当前目录下所有的子文件
File f = new File("D:\\物联网");
File[] files = f.listFiles();
// foreach
for (File file : files) {
System.out.println(file.getName());
}
}
对象的序列化和反序列化
ObjectInputStream和ObjectOutputStream是包装流
- 1.Student对象不支持序列化
- 2.参与序列化和反序列化的对象,必须实现Serializable接口
- 3.注意:通过源码发现,Serializable只是一个标志接口:接口代码中什么都没有;作用是标志作用,Java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。Serializable这个标志接口是给Java虚拟机参考的,Java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号
一次序列化多个对象,可以将对象放到集合当中,序列化集合
序列化一个对象
public class Demo07 {
public static void main(String[] args) throws Exception {
// 序列化
// write();
read();
}
public static void read() throws IOException, Exception {
FileInputStream fos = new FileInputStream(new File("D:\\aaa\\c.txt"));
ObjectInputStream in = new ObjectInputStream(fos);
Student student = (Student) in.readObject();
// Object obj = in.readObject();
// System.out.println(obj);
System.out.println(student);
in.close();
fos.close();
}
public static void write() throws IOException {
Student s = new Student();
s.setAge(18);
s.setName("seven");
FileOutputStream fos = new FileOutputStream(new File("D:\\aaa\\c.txt"));
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(s);
out.close();
fos.close();
}
}
Student对象
package com.blb.text7;
import java.io.Serializable;
public class Student implements Serializable{
/**
* Student类不支持序列化,所以需要加上接口 --- Serializable
* Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号
* 这里没有手动写出来,Java虚拟机会默认提供这个序列化版本号。
*/
private static final long serialVersionUID = 1L;// 版本号
private String name;
// private transient int age = 18;// 被transient修饰的字段表示不进行序列化
private int age;
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 String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
序列化多个对象
参与序列化的ArrayList集合以及集合中的元素Demo2都需要实现 java.io.Serializable接口
package com;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Demo1 implements Serializable{
public static void main(String[] args) throws Exception, Exception {
List<Demo2> userList = new ArrayList<Demo2>();
userList.add(new Demo2(0, "zhangsan"));
userList.add(new Demo2(1, "lisi"));
userList.add(new Demo2(2, "wangwu"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\aaa\\e.txt"));
// 序列化一个集合,这个集合对象中放了很多其他对象。
oos.writeObject(userList);
oos.flush();
oos.close();
}
}
package com;
import java.io.Serializable;
public class Demo2 implements Serializable{
private int no;
private String name;
public Demo2() {
}
public Demo2(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Demo2 [no=" + no + ", name=" + name + "]";
}
}
package com;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
// 反序列化
public class Demo3 {
public static void main(String[] args) throws Exception, Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\aaa\\e.txt"));
// Object obj = ois.readObject();
// System.out.println(obj instanceof List);
// System.out.println(obj);
List<Demo2> useList = (List<Demo2>)ois.readObject();
for (Demo2 demo2 : useList) {
System.out.println(demo2);
}
ois.close();
}
}
transient:关键字表示游离的,不参与序列化
private transient String name;
- Java虚拟机看到Serializable接口之后,会自动生成一个序列版本号。
- 这里没有手动写出来,Java虚拟机回默认提供这个序列化版本号。
- 过了很久,Student这个类源代码改动了
- 源代码改动后,需要重新编译,编译之后生成了全新的字节码文件
- 并且class文件再次运行的时候,Java虚拟器生成的序列化版本号也会发生相应的改变
java.io.InvalidClassException:
com.Demo2;
local class incompatible:
stream classdesc serialVersionUID = -4790717219540779585,(十年后)
local class serialVersionUID = -829247365871225098(十年前)
Java语言中采用什么机制来区分类的
- 第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类
- 第二:如果类名一样,再靠序列化版本号进行区别
甲编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
乙编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
不同的人编写了同一个,但“这两个类确实不是同一个类”。这个时候序列化版本就起上了作用了。对于Java虚拟机来说,Java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)
自动生成版本号的缺陷是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候Java虚拟机会认为这是一个全新的类
最终结论:凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,Java虚拟机会认为是同一个类。
private static final long SeriaVersionUID = 1561651856165L;
// Java虚拟机识别一个类的时候先通过类名,如果类名一致,则识别版本号
IO + Properties联合使用
- IO流:文件的读和写
- Properties:是一个Map集合,key和value都是String类型
- 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态信息。
- 类似于以上机制的这种文件被称为配置文件。并且当配置文件中的内容格式是:key=value 的时候,我们把这种配置文件叫做属性配置文件(key=value格式不要有空格)
- Java规范中有要求:属性配置文件建议以.properties结尾,但不是必须的。这种以.properties结尾的文件在Java中被称为:属性配置文件。其中properties对象是专门存放属性配置文件内容的一个类。
-属性配置文件 key值如果重复了,name值会覆盖
Properties
public class Demo08 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\aaa\\d.properties");
if(!file.exists()) {
file.createNewFile();
}
Properties p = new Properties();
p.setProperty("name", "seven"); // 设置值
p.setProperty("age", "18"); // 设置值
p.setProperty("sex", "男"); // 设置值
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
p.store(osw, "comment"); // 保存到文件
fos.close();
osw.close();
}
}
将Properties的值显示在控制面板上
public class Demo09 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\aaa\\d.properties");
FileInputStream fis = new FileInputStream(file);
InputStreamReader ir = new InputStreamReader(fis,"utf-8");
Properties p = new Properties();
p.load(ir);
System.out.println(p);
ir.close();
fis.close();
}
}