IO流
1. 介绍
-
IO流,什么是IO:
- I:Input O:Output 通过IO可以完成硬盘文件的读和写。
-
IO流的分类:
- 一种是按照流的方向进行分类:
- 往内存中去:叫输入(Input),或者叫做读(Read)
- 往内存中出来:叫输出(Output),或者叫做写(Write)
- 另一种是按照读取数据方式不同进行分类:
- 有的流是按照字节的方式读取数据,一次读取1个字节byte ,等同于一次读取8个二进制位。这种流是万能的,什么类型都可以读取。包括:文本文件,图片,声音文件,视频文件等。
- 有的流是按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片,声音,视频等文件。只能读取纯文本文件,(能用记事本打开)连word文件都无法读取。
- 一种是按照流的方向进行分类:
-
Java中的IO流都已经写好了,我们程序员不需要关心,我们最主要的还是掌握,java中所有的流都在:java.io.*;下。
-
java IO流这块有四大家族(abstract class):
java.io.InputStream:字节输入流
java.io.OutputStream:字节输出流
java.io.Reader:字符输入流
java.io.Writer:字符输出流
注意:
-
在Java中只要 “类名” 以stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
-
所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法。流毕竟是一个管道,这是是内存和硬盘之间的通道,用完之后一定要关闭。不然会占用很多资源。
-
所有的输出流都实现了:java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定记得flush()刷新一下。这个刷新表示将管道剩余未输出的数据强行输出完(清空管道)。如果没有flush()可能会导致丢失数据。
-
-
java.io包下的需要掌握的流有16个:
文件专属: | 缓冲流专属: | 转化流: |
---|---|---|
java.io.FileInputStream | java.io.BufferedInputStream | java.io.InputStreamReader |
java.io.FileOutputStream | java.io.BufferedOutputStream | java.io.OutputStreamWriter |
java.io.FileReader | java.io.BufferedReader | |
java.io.FileWriter | java.io.BufferedWriter |
数据流专属: | 对象专属流: | 标准输出流 |
---|---|---|
java.io.DataInputStream | java.io.ObjectInputStream | java.io.PrintWriter |
java.io.DataOutputStream | java.io.ObjectOutputStream | java.io.PrintStream |
2. FileInputStream:
1. 介绍:
- 文件字节输入流:万能的,任何类型的都文件可以采用这个流读。
- 字节的方式,完成输入的操作,完成读的操作(硬盘–>内存)
2. 代码演示:
-
初始代码:
public class FileInputStreamTest02 { public static void main(String[] args) { //创建文件字节输入流对象 FileInputStream fis=null; try { fis=new FileInputStream("D:\\IO流\\temp"); /* while (true){ int readData=fis.read(); //读到的是字节本身 if(readData==-1){ break; } System.out.println(readData); }*/ //改造while循环 int readData=0; while ((readData=fis.read())!=-1){ System.out.println(readData); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //在finally语句块中确保流一定关闭 //关闭流的前提是:流不是空 if(fis!=null)//避免空指针异常 { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
上面代码出现的问题: 一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上面了。
能不能一次读取多个字节呢? -
IDEA中的当前路径:
- 相对路径:相对路径一定是从当前所在位置作为起点开始找!
- IDEA默认的当前路径是哪里:工程project的根就是IDEA的默认当前路径。
- 如:
若在IO流项目中创建文件tempfile2,那路径的代码:
fis=new FileInputStream("IO流/tempfile2");
3. 改进上面代码:
-
int read(byte[]b):一次最多读取b.length个字节,减少硬盘和内存的交互,提高程序的执行效率。
-
通过String的构造器,将读取的字节,转化为字符串:String(byte[] bytes, int offset, int length)。
代码展示:
//开始读,采用byte[]数组,一次读取多个字节,最多读取 “数组.length” 个字节。 byte[]bytes=new byte[4];//准备一个长度为4的数组,一次最多读取4个字节 //readCount读到的字节数量(不是字节本身) int readCount= fis.read(bytes); System.out.println(readCount); //第一次读到4个字节 //将字节数组全部转化成字符串 //System.out.println(new String(bytes)); //abcd //不应该全部转化,应该读取到多少个字节,转换多少个 System.out.println(new String(bytes,0,readCount)); //abcd readCount=fis.read(bytes); System.out.println(readCount);//第二次只能读取到1个字节 // System.out.println(new String(bytes)); //ebcd //不应该全部转化,应该读取到多少个字节,转换多少个 System.out.println(new String(bytes,0,readCount)); //e readCount=fis.read(bytes);//一个字节都没有读到返回-1 System.out.println(readCount);//-1
4. 最终版代码(重要):
-
代码:
/* * FileInputStream 最终版 * */ public class FileInputStreamTest04 { public static void main(String[] args) { FileInputStream fis=null; try { fis=new FileInputStream("IO流/src/com/ma/io/tempfile2"); //准备一个byte[]数组 byte[]bytes=new byte[4]; /* while (true){ int readCount=fis.read(bytes); if (readCount==-1){ break; } //把byte数组转化成字符串,读到多少个转化多少个 System.out.print(new String(bytes,0,readCount)); }*/ //改进while循环 int readCount=0; while ((readCount=fis.read(bytes))!=-1){ System.out.print(new String(bytes,0,readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fis!=null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
5. FilInputStream其他常用方法:
int available() :返回流当中剩余的没有读到的字节数量
long skip(long n) :跳过几个字节不读
1.available()方法:
注意:这种方式不太适合太大的文件,因为byte[]数组不能太大。
public class FileInputStreamTest05 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis=new FileInputStream("tempfile");
System.out.println("总字节数量:"+fis.available()); //6
//读一个字节:
// int readByte=fis.read();
//还剩下可以读的字节文件:5
//System.out.println("还剩下"+fis.available()+"个字节");//还剩下5个字节
//available()方法的用处
//这种方式不太适合太大的文件,因为byte[]数组不能太大。
byte[]bytes=new byte[fis.available()];
//不用循环了,直接读一次就可以了
int readCount=fis.read(bytes); //6
System.out.println(new String(bytes)); //abcdef
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null)
{
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2. skip()方法:
//skip跳过几个字节不读取
fis.skip(3);
System.out.println(fis.read()); //100
3.FileOutputStream:
1. 介绍:
- 文件字节输出流,负责写,从内存到硬盘。
- 注意:写完之后,一定要刷新flush()和关闭流close()。
2.构造函数:
FileOutputStream(String name) :这种方式谨慎使用,这种方式会先将原文件清空,再重新写入。
FileOutputStream(String name, boolean append) :append为true时表示,再执行程序时,以追加的方式在文件末尾写入,不会清空原文件内容。
-
代码演示:
/* * 文件字节输出流,负责写。 * 从内存到硬盘 * */ public class FileOutputStreamTest01 { public static void main(String[] args) { FileOutputStream fos=null; try { //myfile文件不存在时候会自动新建 //这种方式谨慎使用,这种方式会先将原文件清空,再重新写入 //fos=new FileOutputStream("myfile"); //以追加的方式在文件末尾写入,不会清空原文件内容 fos=new FileOutputStream("myfile",true); //开始写 byte[]bytes={97,98,99,100,101}; //将byte数组全部写出 fos.write(bytes); //将byte数组一部分写出 fos.write(bytes,0,2);//再执行程序,在文件末尾加上ab //字符串 String s="我是小马呀"; //将字符串转化为byte数组 byte[]bytes1=s.getBytes(); fos.write(bytes1); //写完之后,最后一定到刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fos!=null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
3. 文件复制:
-
原理图:
-
代码:
/* * 使用FileInputStream+FileOutputStream完成文件的拷贝。 * 拷贝的过程应该是一边读,一边写 * 使用以上的字节流拷贝文件的时候,文件的类型随意。 * */ public class Copy01 { public static void main(String[] args) { FileInputStream fis=null; FileOutputStream fos=null; try { //创建一个输入流对象 fis=new FileInputStream("D:\\javascript-基础\\javascript\\js-date封装时间.txt"); //创建一个输出流对象 fos=new FileOutputStream("E:\\js-date封装时间.txt"); //最核心的,一边读一边写 byte[]bytes=new byte[1024 * 1024];//1MB (一次最多拷贝1MB) int readCount=0; while ((readCount=fis.read(bytes))!=-1){ fos.write(bytes,0,readCount); } //刷新,输出流最后要刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //分开try 不要一起try //一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭 if(fos!=null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis!=null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
4. FileReader:
-
FileReader:文件字符输入流,只能读取普通文本。读取文本内容时比较方便。
-
代码:
public class FileReaderTest { public static void main(String[] args) { FileReader fr=null; try { //创建文件字符流 fr=new FileReader("tempfile"); /* //准备一个char数组 char[]chars=new char[4]; //往char数组中读 fr.read(chars); for (char a: chars) { System.out.println(a); //按字符的方式读取4个字符 我 是 小 马 }*/ //开始读 char[]chars=new char[4];//一次读取4个字符 int readCount=0; while((readCount=fr.read(chars))!=-1){ System.out.print(new String(chars,0,readCount));//全部读出 } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(fr!=null){ try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
5. FileWriter
-
FileWriter:文件字符输出流,写。只能输出普通文本。
-
代码演示:
public class FileWriterTest { public static void main(String[] args) { FileWriter fw=null; try { //创建文件字符输出流对象 fw=new FileWriter("tempfile"); char[]chars={'哈','a','b','c','哈'}; fw.write(chars); //写出一个换行符 fw.write("\n"); fw.write(chars,1,3); fw.write("\n"); fw.write("小马同学"); //刷新` fw.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if(fw!=null){ try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
1. 复制普通文本文件:
-
注意:如果使用FileReader和FileWriter拷贝文件,只能拷贝普通文本文件。
/* * 普通文本文件复制 * */ public class Copy02 { public static void main(String[] args) { FileReader fr=null; FileWriter fw=null; try { fr=new FileReader("D:\\javascript-基础\\javascript\\js-date封装时间.txt"); fw=new FileWriter("E:\\js-date封装时间1.txt"); char[]chars=new char[1024*512];//1MB int readCount=0; while ((readCount=fr.read(chars))!=-1){ fw.write(chars,0,readCount); } fw.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
6. 带缓冲区的字符流:
1.BufferedReader:
-
介绍:BufferedReader 带有缓冲区的字符输入流。使用这种流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。
-
构造方法:
BufferedReader(Reader in); // 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
-
当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。外部负责包装的流叫做:包装流或处理流。如:
FileReader fr= new FileReader("tempfile");
BufferedReader br=new BufferedReader(fr);
上面代码中,FileReader就是一个节点流。BufferedReader就是包装流/处理流。
对于包装流来说,只需要关闭最外层的包装流就行。里面的节点流会自动关闭。
-
常用方法:
String readLine() //读取一行 但不带换行符
-
具体代码演示:
/* BufferedReader 带有缓冲区的字符输入流。 使用这种流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。 * */ public class BufferedReaderTest01 { public static void main(String[] args) { FileReader fr= null; BufferedReader br=null; try { fr = new FileReader("tempfile"); //当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。 //外部负责包装的流,叫做:包装流,还有一个名字:处理流。 //像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。 br=new BufferedReader(fr); /* //读一行 第一行 String firLine=br.readLine(); System.out.println(firLine); //第二行 String secondLine=br.readLine(); System.out.println(secondLine); //第三行 String thirdLine=br.readLine(); System.out.println(thirdLine);*/ //br.readLine()方法读取一个文本行,但不带换行符 String s=null; while ((s=br.readLine())!=null){ System.out.println(s); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (br!=null){ try { //对于包装流来说,只需要关闭最外层的流就行。里面的节点流会自动关闭。 br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2.InputStreamReader(转换流):
-
InputStreamReader:转化流,可以将字节流转为字符流。
//字节流 FileInputStream fis=new FileInputStream("tempfile"); //通过转换流:InputStreamReader 将字节流转换成字符流 //fis是节点流 isr是包装流 InputStreamReader isr=new InputStreamReader(fis); //这个只能传一个字符流。不能转字节流 //isr是节点流,br是包装流。 BufferedReader br=new BufferedReader(isr);
-
合并写法:
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("tempfile")));
-
具体代码演示:
public class BufferedReaderTest02 { public static void main(String[] args) { FileInputStream fis=null; BufferedReader br=null; try { /* //字节流 fis=new FileInputStream("tempfile"); //通过转换流:InputStreamReader 将字节流转换成字符流 //fis是节点流 isr是包装流 InputStreamReader isr=new InputStreamReader(fis); //这个只能传一个字符流。不能转字节流 //isr是节点流,br是包装流。 br=new BufferedReader(isr);*/ br=new BufferedReader(new InputStreamReader(new FileInputStream("tempfile"))); String s=null; while ((s=br.readLine())!=null){ System.out.println(s); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
3.BufferedWriter:
-
BufferedWriter:带有缓冲的字符输出流。
-
OutputStreamWriter:输出转化流
-
代码演示:
public class BufferedWriterTest { public static void main(String[] args) { BufferedWriter bw=null; try { // bw=new BufferedWriter(new FileWriter("tempfile")); bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("tempfile"))); bw.write("小马同学"); bw.write("\n"); bw.write("小赵同学"); bw.write("\n"); bw.write("一起"); //刷新 bw.flush(); } catch (IOException e) { e.printStackTrace(); }finally { if(bw!=null) { try { //关闭流 bw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
7. 数据流:
1. DataOutputStream:
-
介绍:java.io.DataOutputStream是一个数据专属的流。这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档(这个文件使用记事本打不开)。 -
代码演示:
/* java.io.DataOutputStream是一个数据专属的流。 这个流可以将数据连同数据的类型一并写入文件 注意:这个文件不是普通文本文档(这个文件使用记事本打不开) * * */ public class DataOutputStreamTest { public static void main(String[] args) { DataOutputStream dos=null; try { //创建数据专属的字节输出流 dos=new DataOutputStream(new FileOutputStream("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.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(dos!=null){ try { dos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2. DataInputStream:
-
介绍: DataInputStream:数据字节输入流。DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候,你需要提前知道写入的顺序。读的顺序需要和写的顺序一致,才能正常取出数据。
-
代码演示:
/* DataInputStream:数据字节输入流 DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候,你需要提前知道写入的 顺序。读的顺序需要和写的顺序一致,才能正常取出数据。 * */ public class DataInputStreamTest01 { public static void main(String[] args) { DataInputStream dis=null; try { dis=new DataInputStream(new FileInputStream("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); System.out.println(l); System.out.println(f); System.out.println(d); System.out.println(sex); System.out.println(c); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if(dis!=null){ try { dis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
8. 标准输出流:
1. PrintStream:
-
介绍:java.io.PrintStream:标准的字节输出流,默认输出到控制台。标准输出流,不需要手动close()关闭。
-
可以改变标准输出流的输出方向。如:使标准输出流不再指向控制台,指向’log’文件:System.out()
// 标准输出流不再指向控制台,指向'log'文件。 PrintStream pss=new PrintStream(new FileOutputStream("log")); //修改输出方向,将输出方向修改到‘log’文件。 System.setOut(pss);
-
具体代码演示:
public class PrintStreamTest { public static void main(String[] args) { //联合起来写: System.out.println("xiaoma"); //分开写: PrintStream ps=System.out; ps.println("xiaozhao"); //可以改变标准输出流的输出方向 try { // 标准输出流不再指向控制台,指向'log'文件。 PrintStream pss=new PrintStream(new FileOutputStream("log")); //修改输出方向,将输出方向修改到‘log’文件。 System.setOut(pss); //再输出 System.out.println("小马同学"); //输出到文件 System.out.println("小赵同学"); System.out.println("一起玩哈"); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
1. 编写日志功能:
/*
* 日志工具
* */
public class Logger {
public static void log(String msg){
/*
* 记录日志的方法
* */
try {
//指向一个日志文件
PrintStream pss=new PrintStream(new FileOutputStream("logFile",true));
//改变输出方向
System.setOut(pss);
//日期当前时间
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("调用了UserService的doSome()方法");
Logger.log("用户尝试登录,验证失败");
}
}
9. File类:
1.File类的理解:
- File类和四大家族没有关系,所有File类不能完成文件的读和写。
- File对象代表:**文件和目录路径名的抽象表示形式。**一个File对象有可能对应的是目录,也可能是文件。File只是一个路径名的抽象表示形式。
2. File类的常用方法:
boolean exists(); //判断文件或目录是否存在;
boolean createNewFile(); //当指定路径文件不存在时,则以文件的形式创建出来
boolean mkdir(); //创建此抽象路径名指定的目录。
boolean mkdirs();//创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
String getParent();//获取文件的父路径
File getParentFile() ;//获取文件的父路径,返回的是一个file对象
String getAbsolutePath(); //获取绝对路径
String getName() ;//获取文件或目录的名称
boolean isDirectory() ;//判断是否是一个目录
boolean isFile() ;//判断是否是一个文件
long lastModified();//获取文件最后一次修改时间
long length();//获取文件长度
boolean renameTo(File dest);//重命名
File[] listFiles() ;//获取当前目录下所有的子文件
3. 目录拷贝(重点):
- 代码:
/*
* 拷贝目录
* */
public class CopyAll {
public static void main(String[] args) {
//拷贝源
File srcFile=new File("D:\\Data");
//拷贝目标
File destFile=new File("E:\\");
//调用方法拷贝
copyDir(srcFile,destFile);
}
/**
* 拷贝目录
* @param srcFile 拷贝源
* @param destFile 拷贝目标
*/
private static void copyDir(File srcFile, File destFile) {
if (srcFile.isFile()){
//srcFile如果是一个文件的话递归结束
//是文件的时候需要拷贝
//...一边读一边写
FileInputStream fis=null;
FileOutputStream fos=null;
try {
//读这个文件
fis=new FileInputStream(srcFile);
//写到这个文件中
String path=(destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath():destFile.getAbsolutePath()+"\\")+srcFile.getAbsolutePath().substring(3);
//System.out.println(path);
fos=new FileOutputStream(path);
//一边读一边写
byte[]bytes=new byte[1024*1024]; //一次复制1MB
int readCount=0;
while ((readCount=fis.read(bytes))!=-1){
fos.write(bytes,0,readCount);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
//获取源下面的子目录
File[]files=srcFile.listFiles();
for (File file:
files) {
//获取所有文件的(包括目录和文件)的绝对路径
//System.out.println(file.getAbsolutePath());
if (file.isDirectory()){
//新建对应目录
//1.获取拷贝源的绝对路径
String srcDir=file.getAbsolutePath();
//System.out.println(srcDir.substring(3));
//字符串拼接目标目录的路径:
String destDir=(destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath():destFile.getAbsolutePath()+"\\")+srcDir.substring(3);
//System.out.println(destDir);
File newFile=new File(destDir);
if (!newFile.exists()){
newFile.mkdirs();
}
}
//file有可能是文件也有可能是目录
//递归调用
copyDir(file,destFile);
}
}
}
10:序列化和反序列化:
1. 介绍:
-
序列化:Serialize 将java对象存储到文件中,将java对象的状态保存下来的过程。 (ObjectOutputStream)
-
反序列化:DeSerialize 将硬盘上的数据重新恢复到内存当中,恢复成java对象 (ObjectInputStream)
-
注意:参与序列化和反序列化的对象,必须实现Serializable接口。
-
通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable { }
-
这个Serializable接口起到一个标志的作用,java虚拟机看到这个类实现这个接口,可能会对这个类进行特殊待遇。会为该类自动生成一个序列化版本号。
2. 代码演示:
public class Student implements Serializable {
private int no;
private String name;
public Student() {
}
public Student(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 "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
public class ObjectOutputStreamTest01 {
public static void main(String[] args) {
//创建java对象
Student s=new Student(111,"xiaoma");
ObjectOutputStream oos=null;
//序列化
try {
oos=new ObjectOutputStream(new FileOutputStream("students"));
//序列化对象
oos.writeObject(s);
//刷新
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3. 反序列化代码:
public class ObjectInputStreamTest01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("students"));
//开始反序列化,读
Object obj=ois.readObject();
//反序列化回来是一个学生对象,所以会调用学生对象的toString方法
System.out.println(obj); //Student{no=111, name='xiaoma'}
ois.close();
}
}
4. 序列化多个对象:
-
将对象放到集合中,序列化集合。
序列化集合代码:
public class ObjectOutputStreamTest02 { public static void main(String[] args) throws IOException { List<User> userList=new ArrayList<>(); userList.add(new User(1,"xiaoma")); userList.add(new User(2,"xiaozhao")); userList.add(new User(3,"xiaochen")); ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("user")); //序列化集合,这个集合中放了很多其他对象 oos.writeObject(userList); oos.flush(); oos.close(); } }
-
反序列化集合:
/* * 反序列化集合 * */ public class ObjectInputStreamTest02 { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois=new ObjectInputStream(new FileInputStream("user")); Object obj=ois.readObject(); System.out.println(obj); ois.close(); } }
5. transient关键字:
- transient:表示游离的,不参与序列化。
如:
//transient:表示游离的,不参与序列化
private transient String name; //name不参与序列化操作
6. 序列化版本号:
-
java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。
-
java语言中是采用什么机制来区分类的:
- 首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
- 如果类名一样,再靠序列化版本号进行区分。
-
自动生成序列化版本号的缺陷:一旦代码确定之后,不能进行后续的修改,因为只有修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类,这样就不好了。
-
结论:凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号,以后这个类即使修改了,但是版本号不变,java虚拟机会认为是同一个类。
-
建议将序列化版本号手动的写出来,不建议自动生成:
private static final long serialVersionUID = 8683452581122892189L;
11.IO和Properties联合使用:
1.介绍:
-
非常好的一个设计理念:
以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要编译,服务器也不需要重启。就可以拿到动态的信息。类似这样的文件被称为配置文件,并且配置文件中的内容格式是:key=value时,我们把这种文件叫做属性配置文件。
-
java规范中有要求:属性配置文件建议以:.properties结尾,但这不是必须的。其中Properties是专门存放属性配置文件内容的一个类。
-
在属性配置文件中以 “ # ”注释,key重复的话,value会自动覆盖。
2.代码演示:
-
在项目中新建一个useinfo文件:
username=root password=123
-
新建一个类:IOPropertiesTest
public class IOPropertiesTest { public static void main(String[] args) throws IOException { /* * Properties是一个Map集合,key和value都是String类型。 * 想将useinfo文件中的数据加载到Properties对象当中 * * */ //新建一个输入流对象 FileReader reader=new FileReader("IO流/useinfo"); //新建一个Map集合 Properties pro=new Properties(); //调用Properties对象的load方法将文件中的数据加载到Map集合中。 pro.load(reader); //文件中的数据顺着管道,加载到Map集合中,其中=左边是key 右边是value //通过key,获取value String username=pro.getProperty("username"); System.out.println(username); } }
1.介绍:
-
非常好的一个设计理念:
以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要编译,服务器也不需要重启。就可以拿到动态的信息。类似这样的文件被称为配置文件,并且配置文件中的内容格式是:key=value时,我们把这种文件叫做属性配置文件。
-
java规范中有要求:属性配置文件建议以:.properties结尾,但这不是必须的。其中Properties是专门存放属性配置文件内容的一个类。
-
在属性配置文件中以 “ # ”注释,key重复的话,value会自动覆盖。
2.代码演示:
-
在项目中新建一个useinfo文件:
username=root password=123
-
新建一个类:IOPropertiesTest
public class IOPropertiesTest { public static void main(String[] args) throws IOException { /* * Properties是一个Map集合,key和value都是String类型。 * 想将useinfo文件中的数据加载到Properties对象当中 * * */ //新建一个输入流对象 FileReader reader=new FileReader("IO流/useinfo"); //新建一个Map集合 Properties pro=new Properties(); //调用Properties对象的load方法将文件中的数据加载到Map集合中。 pro.load(reader); //文件中的数据顺着管道,加载到Map集合中,其中=左边是key 右边是value //通过key,获取value String username=pro.getProperty("username"); System.out.println(username); } }