//本章只讲解了部分常用的IO流,并不全面
定义
I :Input
O :Output
通过IO可以完成硬盘文件的读写操作
分类
-
按照流的方向进行分析
- 往内存中去,叫做“输入”,or 读
- 从内存中出,叫做“输出”,or 写
-
按照读取数据方式不同
- 按照字节的方式读取,一次读取一个字节
- 这种流是万能的,什么类型的文件都可以读取
- 按照字符方式读取,一次读取一个字符
- 这种流仅仅是为了读取普通文本文件,即能使用记事本打开的文件(不能读取word文档)
- 按照字节的方式读取,一次读取一个字节
Java.IO流的四大家族
- Java.IO.InputStream —— 字节输入流
- Java.IO.OutputStream —— 字节输出流
- Java.IO.Reader —— 字符输入流
- Java.IO.Writer —— 字符输出流
注
- Java中只要“类名”以 Stream 结尾的都是字节流
- Java中只要“类名”以“Reader、Writer”结尾的都是字符流
- 四大家族的首领都是抽象类(abstract class)即不能直接实例化对象的类
流的close()、flush()方法
Java.IO.Closeable接口
- 所有的流都实现了该接口,所以所有的流都有close()方法
- 方法因为流就是一个管道,连接内存和硬盘之间的管道,用完之后一定要关闭,不然会占用很多的资源
Java.IO.Flushable接口
- 所有的输出流都实现了该接口,都有flash()方法
- 输出流在最终输出之后,一定要用flush()方法刷新一下,从而将管道中剩余的没有输出的数据全部输出出去(即清空管道)
- 因为如果没有flush()的话,可能会导致数据丢失
需要掌握的16个流
文件专属流
种类
- Java.IO.FileInputStream
- Java.IO.FileOutputStream
- Java.IO.FileReader
- Java.IO.FileWriter
FileInputStream
-
读取步骤
- 创建文件输入对象
- 读取数据(注意异常处理)
-
读取方法
import java.io.FileInputStream;
import java.io.IOException;
public class Test02{
public static void main(String[] args){
FileInputStream fis = null;
try{
//创建文件读取对象
fis = new FileInputStream("F:\\Hello_world.txt");
//遍历读取文件
/*while (true){
int readData = fis.read();
if(readData == -1){
break;
}
}*/
//优化while循环
int readData = 0;
while((readData = fis.read()) != -1){ //因为如果文件读完后,最后返回-1
System.out.println(readData);
}
} catch (IOException e){
e.printStackTrace();
} finally {
if (fis != null){
try {
fis.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
上述方法有着明显的缺陷:一次只能读取一个byte,导致时间、资源都耗费在内存和硬盘交互上了,所以下述方法将会一次读取多个字节
- 从byte数组中读取
- 背景:一个一个字节读取耗费时间和资源
- 作用:一次型读取多个字节,减少硬盘和内存的交互,提高程序的执行效率
- 步骤:
- 创建文件输入对象
- 创建自定义长度的byte数组(该数组长度就是一次性读取字节的长度)
- 使用read(byte b[ ])方法读取文件(该方法返回的是读取的字节数量,而不是字节的内容)
- 将读取到的文件转换成具体内容
import java.io.FileInputStream;
import java.io.IOException;
public class Test03{
public static void main(String[] args){
FileInputStream fis = null;
try{
//1、创建文件输入对象
fis = new FIleInputStream("F:\\Hello_world.txt");
//2、创建自定义长度的byte数组
byte[ ] b = new byte[4];
/*
//3、使用read(byte b[])方法读取文件
int readCount = fis.read(b);
//打印readCount的内容
System.out.println(readCount);
//4、将读取到的文件转换成具体内容
System.out.println(new String(b, 0, readCount));
*/
//循环读取文件内容
int readCount = 0;
while((readCount = fis.read(b)) != -1){
System.out.println(new String(b, 0, readCount));
}
} catch(IOException e){
e.printStackTrace(); //注:printStackTrace是一个方法,所以需要加括号
} finally{
if (fis != null){
try{
fis.close();
} catch(IOException e){
e.printStackTrace();
}
}
}
}
}
- FileInputStream中的其他方法
- int available()
- 返回流中剩余的没有读到的字节数量
- 作用:不需要使用循环,读取一次就可以了(大文件不行,因为byte[]不能太大)
- long skip(long n)
- 跳过几个字节不读
- int available()
import java.io.FileInputStream;
import java.io.IOException;
public class Test04{
public static void main(String[] args){
FileInputStream fis = null;
try {
fis = new FileInputStream("F:\\Hello_world.txt");
//总字节数:
System.out.println(fis.available());
byte b[ ] = new byte[fis.available()];
int readCount = fis.read(b);
System.out.println(new String(b)); //为什么可以直接带入数组b,见下
/*
fis.skip(3); //跳过几个字节不读
System.out.println(fis.read());
*/
} catch (IOException e){
e.printStackTrace();
} finally {
if (fis != null){
try {
fis.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
Test04的 new String 为什么能直接带入数组b
//String相对应的源码
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
//read相对应的源码
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
//所以可以看出,Test04中fis.read(b) 执行的时候,返回的是已经读取了的文件
FileOutputStream
- 文件字节输出流,负责写,即从内存到硬盘的过程
- 写入步骤:
- 创建文件(注意覆盖写入和追加写入的区别)
- 使用write()方法写入
package Day023IO流;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test05{
public static void main(String[ ] args){
FileOutputStream fos = null;
try{
//fos = new FIleOutputStream("myfile"); //覆盖写入,第一个参数是写入文件地址
fos = new FileOutputStream("myfile",true); //追加写入
//创建需要写入的内容
byte[ ] b = {97, 98, 99, 100, 101, 111};
//将数组b全部写入
fos.write(b);
//将数组b部分写入 —— 第二个参数:起始位置,第三个参数:长度
fos.write(b, 0, 2);
//写入其他中文
String s = "今天星期一";
//将字符串转换成byte数组
byte[ ] bs = s.getBytes();
//写入
fos.write(bs);
//写完之后一定要刷新
fos.flush();
} catch (IOException e){
e.printStackTrace();
} finally {
if (fos != null){
try {
fos.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
Test05中写入中文字符串的时候,为什么需要将字符串转换成byte数组?
//write方法的源代码
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
}
//从而可以看出,使用write()方法的时候,参数必须是一个byte数组,所以需要转换
文件复制
- 目的:使用FileInputStream 和 FileOutputStream 完成文件的拷贝
- 拷贝核心:一边读一边写
- 优点:任何形式的文件都可以进行拷贝
- 步骤:
- 创建一个输入流对象
- 创建一个输出流对象
- 创建数组
- 一边读一边写
- 刷新流
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test06{
public static void main(String[ ] args){
FileInputStream fis = null;
FileOutputStream fos = null;
try{
//1、创建一个输入流对象
fis = new FileInputStream("F:\\杂七杂八\\汤圆\\汤圆视频02.mp4");
//2、创建一个输出流对象
fos = new FileOutputStream("F:\\学习\\JAVA\\知识点\\PDF文件\\汤圆视频02.mp4");
//3、创建数组
byte[ ] b = new byte[1024 * 1024]; //一次拷贝1MB
//4、一边读一边写
int readCount = 0;
while((readCount = fis.read(b)) != -1){
fos.write(b, 0, readCount);
/*
* 第一个参数是:传入的数组
* 第二个参数是:数据中的开始偏移量
* 第三个参数是:读取的内容
*
* */
}
//5、刷新流
fos.flush();
} 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();
}
}
}
}
}
FileReader
- 定义:文件字符输入流,只能读取普通文本(比较方便、快捷)
- 步骤:
- 创建文件字符输入流
- 准备一个char数组
- 使用read()方法读取char数组长度的内容
- 注:FileInputStream和FIleReader的read()方法的区别:
- FIleInputStream的是,从输入流读取一个字节的数据(不是read(byte b[ ])
- FIleReader的是,将字符读入一个数组(所以可以使用foreach方法)
import java.io.FileReader;
import java.io.IOException;
public class Test07{
public static void main(String[ ] args){
FileReader reader = null;
try{
//1、创建文件字符输入流
redaer = new FileReader("myfile");
//2、准备一个char数组
char[ ] chars = new char[4];
//3、按照字符的方式读取文本内容
reader.read(chars);
/*
//使用foreach遍历读取的内容(只能读取数组长度的内容)
for(char c : chars){
System.out.println(c);
}
*/
//使用while循环遍历读取的内容(可以读取文件全部内容)
int readCount = 0;
while((readCount = reader.read(chars)) != -1){
System.out.println(new String(chars,0,readCount));
}
} catch(IOException e){
e.printStackTrace();
} finally{
if (redaer != null){
try{
reader.close();
} catch(IOException e){
e.printStackTrace():
}
}
}
}
}
FileWriter
- 文件字符输出流,写(只能输出普通文本)
- 步骤
- 创建FileWriter对象(注意写入方式——覆盖or追加)
- 使用char数组的方式写入(可以选择长度写入)
- 直接写入
- 刷新
import java.io.FileWriter;
import java.io.IOException;
public class Test08{
public static void main(String[ ] args){
FileWriter writer = null;
try{
//1、创建FileWriter对象
writer = new FileWriter("myfile"); //覆盖写入
writer = new FileWriter("myfile",true); //追加写入
//2、使用char数组的方式写入数据
char[ ] chars = {'罗','晓','辉','是','小','猪'};
writer.write(chars);
writer.write(chars, 5, 2);
//换行
writer.write("\n");
//3、直接写入
writer.write("罗晓辉是猪猪");
//4、刷新
writer.flush();
} catch (IOException e){
e.printStackTrace();
} finally {
if (writer != null){
try{
writer.close();
}
catch(IOException e){
e.printStackTrace();
}
}
}
}
}
缓冲流 and 转换流
-
种类
- Java.IO.BufferedReader
- Java.IO.BufferedWriter
- Java.IO.BufferedInputStream
- Java.IO.BufferedOutputStream
-
带有缓冲区的字符输入输出流
- 使用的时候,不需要自定义char或byte数组,因为其自带缓冲
-
注
- 当一个流的构造方法中需要一个流的时候,被传进来的流叫做节点流
- 外部负责包装的这个流,叫做包装流,或者处理流
- BufferedReader的使用
- 步骤:
- 创建FileReader流(仅仅是在举例中使用的该类,传入的是实现Reader抽象类的即可)
- 创建BufferedReader流,并将FileReader流作为参数传入
- 遍历读数据
- 关闭流
- 注:
- 使用了直接抛出异常,和try…catch两种方法
- 下面会同时写两者一次,然后后面代码为了简便,均为直接抛出
- readLine()方法读取的是文档中的一行,且不带换行符
- 步骤:
使用try…catch方式
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Test09{
public static void main(String[ ] args){
FileReader reader = null;
try{
//1、创建FileReader流
reader = new FileReader("myfile");
//2、创建BufferedReader流,并将FileReader流作为参数传入
BufferedReader br = new BufferedReader(reader); //详见解释一
//3、遍历读数据
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
} catch (IOException e){
e.printStackTrace();
} finally {
if (reader != null){
try {
//4、关闭流
reader.close(); //详见解释三
} catch (IOException e){
e.printStackTrace();
}
}
}
}
}
使用直接抛出异常的方式
import java.io.BufferedReader;
import java.io.FileReader;
public class Test010{
public static void main(String[ ] args) throws Exception{
//1、创建FileReader流
FileReader reader = new FileReader("myfile");
//2、创建BufferedReader流,并将FileReader流作为参数传入
BufferedReader br = new BufferedReader(reader);
//3、遍历读数据
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
//4、关闭流
br.close(); //详见解释二
}
}
解释一:为什么 BufferedReader 在实例化对象的时候,需要传入参数 Reader
//BufferedReader 构造方法的源代码
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
public BufferedReader(Reader in, int sz){
super(in);
if (sz <= 0){
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
}
//在实例化 BufferedReader 对象的时候,根据源代码可以知道,必须传入一个 Reader类型 的参数
public abstract class Reader implements Readable, Closeable {......}
//因为Reader类有 abstract关键字,所以Reader是一个抽象类,是不能直接实例化对象的,因为FileReader是Reader是子类,所以我们选择使用FileReader作为参数传入
解释二:为什么关闭的包装流而不是节点流
//BufferedReader 重写后close()方法的源代码
public void close() throws Exception {
synchronized (lock) {
if (in == null)
return;
try {
in.close();
} finally {
in = null;
cb = null;
}
}
}
//源码中的 变量in 就是传入的参数Reader,所以可以看出,当包装流调用close()方法的时候,实际上执行的是传入的节点流的关闭
解释三:为什么try…catch方式关闭流的时候关闭的是节点流,而直接抛出异常的方式关闭的是包装流
//暂时不知道
字节流转换成缓冲流
- 种类
- Java.IO.InputStreamReader
- Java.IO.OutputStreamWriter
- 步骤
- 创建字节流对象
- 通过转换流转换(InputStreamReader 将字节流转换成字符流)
- 创建缓冲流对象,并将转换后的字符流传入进去
- 读取数据
- 关闭流
- 注
- 步骤1~3可以合并成一个步骤(把缓冲流、转换流、字节输入流合并)
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Test011{
public static void main(String[ ] args) throws Exception{
//1、创建字节流对象
FileInputStream fis = new FileInputStream("myfile");
//2、通过转换流转换
InputStreamReader reader = new InputStreamReader(fis);
//3、创建缓冲流对象,并将转换后的字符流传入进去
BufferedReader br = new BufferedReader(reader);
//4、读取数据
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
//5、关闭流
br.close();
System.out.println("=====================分割线======================");
//将步骤1~3合并为一步:
BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream("myfile")));
//读数据
String s2 = null;
while ((s2 = br2.readLine()) != null){
System.out.println(s2);
}
//关闭流
br2.close();
}
}
BufferedWriter
- 定义:带有缓冲的字符输出流
- 步骤:(使用字节流转换为字符流方式写入数据)
- 创建字节流对象
- 通过转换流转换(OutputStreamReader)
- 创建缓冲流对象,并将转换后的字符流作为参数带入
- 写入数据
- 刷新流
- 关闭流
- 注:
- 步骤1~3可以合并(将缓冲流、转换流、字节输出流合并)
- 输出流一定要刷新
- 步骤:(直接使用BufferedWriter实例化对象写入数据)
- 实例化FileWriter对象
- 实例化BufferedWriter对象,并将FileWriter对象作为参数传入
- 写入数据
- 刷新流
- 关闭流
- 注:
- 步骤1~2可以合并(缓冲流、字符数出流合并)
(使用字节流转换为字符流方式写入数据)
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class Test012{
public static void main(String[ ] args) throws Exception{
//步骤1~3合并(将缓冲流、转换流、字节输出流合并)
BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("myfile2",true))); //注意这里是覆盖写入还是追加写入
//写入数据
br.write("Hello World");
br.write("\n"); //写入一个换行符
br.write("Hello Java");
//刷新
br.flush();
//关闭流
br.close();
}
}
(直接使用BufferedWriter实例化对象写入数据)
import java.io.BufferedWriter;
import java.io.FileWriter;
public class Test013{
public static void main(String[ ] args) throws Exception{
//同时实例化BufferedWriter对象 和 FileWriter对象
BufferedWriter br = new BufferedWriter(new FileWriter("myfile2", true));
//写入数据
br.write("Hello ZZ");
//刷新流
br.flush();
//关闭流
br.close();
}
}
数据流
-
种类
- Java.IO.DataInputStream
- Java.IO.DataOutputStream
-
注:
- DataOutputStream写入的文件,只能使用 DataIntputStream 去读,并且读的时候,需要提前知道写入的顺序,读写的顺序要一致,才能正常取出数据
DataOutputStream
-
定义:将数据连同数据类型一并写入文件
-
步骤:
- 创建数据专属的字节数出流
- 写入数据
- 刷新流
- 关闭流
-
注:
- 写入数据的时候不同的数据类型要用其特定的write…()方法
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class Test014{
public static void main(String[ ] args) throws Exception{
//创建数据专属的字节输出流
DataOutputStream 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 = 'c';
//写入数据
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();
//关闭流
dos.close();
}
}
解释四:为什么实例化 DataOutputStream对象 的时候,需要传入一个FileOutputStream
//DataOutputStream的源代码:
public DataOutputStream(OutputStream out) {
super(out);
}
//可以看出,DataOutputStream在实例化对象的时候,需要传入一个OutputStream类型的对象作为参数
//OutputStream的源代码
public abstract class OutputStream implements Closeable, Flushable{....}
//因为关键字abstract,所以OutputStream是一个抽象类,不能直接实例化对象,而FIleOutputStream是其子类,所以可以实例化
DataInputStream
-
步骤:
- 实例化DataInputStream对象
- 根据写入顺序开始读取数据
- 关闭流
-
注:
- 一定要知道写入顺序
import java.io.DataInputStream;
import java.io.FileInputStream;
public class Test015 {
public static void main(String[] args) throws Exception{
//1、实例化DataInputStream对象
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
//2、开始读
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);
//3、关闭流
dis.close();
}
}
标准输出流
- 种类
- Java.IO.PrintWriter
- Java.IO.PrintStream
- 作用:
- 改变输出方向
- 注:
- 标准输出流不需要手动close()关闭
PrintWriter
- 步骤(改变输出流方向):
- 实例化PrintStream对象1(传入的参数,是实例化OutputStream子类的对象2,对象2的参数是需要改变后的目标文件地址)
- 修改输出方向,将输出方向修改到实例化出来的对象上
- 输出
import java.io.FileOutputStream;
import java.io.PrintStream;
public class Test016{
public static void main(String[ ] args) throws Exception{
//输出到控制台联合起来写:
System.out.println("斯巴达");
//分开写:
PrintStream ps = System.out;
ps.println("斯巴达");
//改变标准输出流的方法,不再指向控制台,而是指向“log”文件
//1、实例化PrintStream对象
PrintStream printStream = new PrintStream(new FileOutputStream("log")); //详见解释六
//2、修改输出方向,将输出方向修改到实例化出来的对象上
System.setOut(printStream);
//3、输出
System.out.println("Oh my god");
}
}
- 实例 —— 日志工具
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
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数据类型获取时间
String strTime = sdf.format(nowTime);
//将记录放到log日志文件中
System.out.println(strTime + ":" + msg);
} catch (FileNotFoundException e){
e.printStackTrace();
}
}
}
测试日志工具
public class logTest{
public static void main(String[ ] args) {
Logger.log("测试一");
Logger.log("测试二");
Logger.log("测试三");
}
}
/*
2020-12-15 17:14:57 545 :测试一
2020-12-15 17:14:57 567 :测试二
2020-12-15 17:14:57 568 :测试三
*/
Java.IO.File类
- 定义:文件和目录路径名的抽象表示格式
- 注:
- File类和四大家族没有关系,所以File类不能完成文件的读和写
- File对象代表什么? —— 文件|和目录路径名的抽象表示格式
- File类需要掌握的方法:
修饰符和类型 | 方法 | 描述 |
---|---|---|
boolean | exists() | 判断File对象是否存在 |
boolean | createNewFile() | 以文件的形式创建 |
boolean | mkdir() | 以目录的形式新建(目录就是文件夹) |
boolean | mkdirs() | 以多重目录的形式新建 |
String | getParent() | 获取文件的父路径 |
File | getParentFile() | 获取父路径,但是以file的方式返回 |
String | getAbsolutePath() | 获取绝对路径 |
String | getName() | 获取文件名 |
boolean | isDirectory() | 判断是否是一个目录 |
boolean | isFile() | 判断是否是一个文件 |
long | lastModified() | 获取文件最后一次修改时间 |
long | length() | 获取文件大小 |
File[ ] | listFiles() | 获取当前目录下所有子文件 |
- 注
- lastModified()方法返回的是从1970到现在的毫秒数,所以需要转换成日期,再进行日期格式化
测试:
package Day023IO流;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test017 {
public static void main(String[] args) throws Exception {
//新建File对象
File file = new File("myfile3");
//boolean exists() 判断File对象是否存在
System.out.println(file.exists());
System.out.println("=========分割线1=========");
//boolean createNewFile() 以文件的形式创建
if (!file.exists()){
file.createNewFile();
System.out.println("文件创建成功");
} else {
System.out.println("文件已经存在");
}
System.out.println("=========分割线2=========");
//boolean mkdir() 以目录的形式新建(目录就是文件夹)
if (!file.exists()){
file.mkdir();
System.out.println("文件夹创建成功");
} else {
System.out.println("文件夹已经存在");
}
System.out.println("=========分割线3=========");
// 新建File1对象
File file1 = new File("F://学习//myfile");
//boolean mkdirs() 以多重目录的形式新建
if (!file1.exists()){
file1.mkdirs();
System.out.println("多重目录形式新建成功");
} else {
System.out.println("文件已经存在");
}
System.out.println("=========分割线4=========");
//String getParent() 获取文件的父路径
String parentPath = file1.getParent();
System.out.println(parentPath);
System.out.println("=========分割线5=========");
//File getParentFile() 获取父路径的父路径,但是以file的方式返回
File parentPath1 = file1.getParentFile();
System.out.println("获取绝对路径:" + parentPath1.getParentFile());
System.out.println("=========分割线6=========");
//String getAbsolutePath() 获取绝对路径
String absolutePath = file1.getAbsolutePath();
System.out.println(absolutePath);
System.out.println("=========分割线7=========");
//String getName() 获取文件名
String name = file1.getName();
System.out.println(name);
System.out.println("=========分割线8=========");
//boolean isDirectory() 判断是否是一个目录
System.out.println(file1.isDirectory());
System.out.println("=========分割线9=========");
//boolean isFile() 判断是否是一个文件
System.out.println(file1.isFile());
System.out.println("=========分割线10=========");
//long lastModified() 获取文件最后一次修改时间
long lm = file1.lastModified();
//将毫秒装换成日期
Date time = new Date(lm);
//日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println(strTime);
System.out.println("=========分割线11=========");
//long length() 获取文件大小
long lenght = file1.length();
System.out.println(lenght);
System.out.println("=========分割线12=========");
File file2 = new File("F://学习");
//File[ ] listFiles() 获取当前目录下所有子文件
File[] files = file2.listFiles();
for (File f : files){
System.out.println(f);
}
}
}
对象流
-
种类
- Java.IO.ObjectInputStream
- Java.IO.ObjectOutputStream
-
注
- 参与序列化和反序列化的对象,必须实现Serializable接口
- 通过源码发现,Serializable只是一个标志接口,该接口中什么都没有,仅仅起到了一个标识、标志的作用,JVM看见了类实现了这个接口,可能会对这个类进行特殊待遇
- 特殊待遇:Serializable这个接口是给JVM参考的,JVM看到这个接口后,会为该类自动生成一个序列化版本号
-
序列化和反序列化
- 序列化(Serialize):java对象存储到文件中,将java对象的状态保存下来的过程 ———— 使用ObjectOutputStream
- 反序列化(DeSerialize):将硬盘上的数据重新恢复成java对象 ———— 使用ObjectInputStream
序列化单个对象
- 步骤
- 创建java对象
- 序列化
- 序列化对象
- 刷新
- 关闭
序列化单个对象
(新建了一个Student类,来序列化该类的对象)
import java.io.Serializable;
//新建Student类
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 + '\'' +
'}';
}
}
实例化具体过程
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class Test018 {
public static void main(String[] args) throws Exception{
//1、创建Java对象
Student student = new Student(01,"张三");
//2、实例化序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Student")); //详见解释七
//3、序列化对象
oos.writeObject(student);
//4、刷新
oos.flush();
//5、关闭
oos.close();
}
}
解释七:为什么传入的是一个FileOutputStream对象
//ObjectOutputStream构造方法的源代码:
public ObjectOutputStream(OutputStream out) throws IOException {....}
//可以看出:参数位置需要传入一个 OutputStream类型 的对象
//OutputStream 的源代码
public abstract class OutputStream implements Closeable, Flushable {....}
//可以看出:OutputStream类 是一个抽象类,不能实例化对象,所以上述代码中实例化了OutputStream的子类
反序列化单个对象
- 步骤
- 新建反序列化对象
- 反序列化(读)
- 关闭流
具体流程:
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class Test019 {
public static void main(String[] args) throws Exception{
//创建反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Student"));
//开始序列化(读)
Object obj = ois.readObject();
//输出反序列化的内容
System.out.println(obj); //因为反序列化出来的是一个Student对象,所以会调用Student类的toString方法
//关闭流
ois.close();
}
}
序列化多个对象
- 步骤
- 创建集合
- 向集合添加数据
- 创建实例化对象
- 实例化集合
- 刷新流
- 关闭流
(新建了一个User类,来序列化多个该类的对象)
package 对象流.bean;
import java.io.Serializable;
public class User implements Serializable {
private int no;
private String name;
public User() {
}
public User(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 "User{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
实例化多个对象具体操作
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class Test020 {
public static void main(String[] args) throws Exception{
//1、创建List集合
List<User> userList = new ArrayList<>();
//2、向集合添加数据
userList.add(new User(01,"张三"));
userList.add(new User(02,"李四"));
userList.add(new User(03,"王五"));
//3、创建实例化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("User"));
//4、序列化List集合
oos.writeObject(userList);
//5、刷新流
oos.flush();
//6、关闭流
oos.close();
}
}
反序列化多个对象
- 步骤:
- 创建反实例化对象
- 新建List集合接收反实例化出来的内容
- 遍历输出内容
- 注:
- 第二步接收的时候,要转换读取数据类型,因为使用LIst接收,读取到的是Object型
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class Test021 {
public static void main(String[] args) throws Exception{
//1、创建反实例化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("User"));
//2、新建List集合接收反实例化出来的内容
List<User> userList = (List<User>)ois.readObject();
//3、遍历输出内容
for (User user : userList){
System.out.println(user);
}
}
}
transient关键字
- 定义: transient关键字表示游离的,不参与序列化
private transient String name; //name不参与序列化操作
/*
User{no=1, name='null'}
User{no=2, name='null'}
User{no=3, name='null'}
*/
序列化版本号
- 背景:自定义类的源代码发生改动后,就会重写编译,然后会形成全新的字节码文件。当字节码文件再次运行的时候,JVM生成的序列化版本号也会发生相应的变化
- 建议:序列化版本号手动写出来,不建议自动生成
- 注:
- JVM识别一个类的时候,先通过类名,再通过序列化版本号
改变前面的Student类:
import java.io.Serializable;
public class Student implements Serializable {
// JVM看见Serializable接口后,会自动生成一个序列化版本号
// 这里没有手动写出来,JVM会默认提供这个序列化版本号
private int no;
// transient关键字表示游离的,不参与序列化
private transient String name;
//private static final long serialVersionUID = 1L;
//JVM识别一个类的时候,先通过类名,再通过序列化版本号
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 + '\'' +
'}';
}
}
IDEA生成序列化版本号
- 步骤
- Setting —— Inspection —— Serializable class without ‘serialVersionUID’
- 勾选,然后确定
- 在实现了Serializable接口的类名上,点击alt + enter,选择"Add ‘serialVersionUID’ field "
IO和Properties联合使用
-
定义:
- IO流:文件的读和写
- Properties:是一个Map集合,key和value都是String类型
-
一个设计理念:
- 经常改变的数据,可以单独写到一个文件中,使用程序动态读取
将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器不需要重启,就可以拿到动态信息
- 经常改变的数据,可以单独写到一个文件中,使用程序动态读取
-
注:
- 类似于以上机制的这种文件被称为配置文件,并且当配置文件中的内容格式是:key=value
的时候,将这种配置文件叫做属性配置文件 - Java规范中有要求:属性配置文件建议以 .properties结尾,但是不是必须的
- 这种以.properties结尾的文件在Java中被称为:属性配置文件,其中properties对象是专门存放属性配置文件的一个类
- 类似于以上机制的这种文件被称为配置文件,并且当配置文件中的内容格式是:key=value
-
步骤:
- 新建一个输入流对象
- 新建一个Properties集合
- 调用Properties对象的load方法将文件中的数据加载到Map集合中
- 通过key获取value
(新建一个属性配置文件)
username=admin
#############在配置文件中井号是注释##################
#属性配置文件的key重复的化,value会自动覆盖
#password=1234
password=123
#最好不要有空格
data = 100
#也可以使用冒号: 但是不建议使用
username2:admin2
具体实现:
import java.io.FileReader;
import java.util.Properties;
public class Test022 {
public static void main(String[] args) throws Exception{
//1、新建一个输入流对象
FileReader reader = new FileReader("999 review//Day023IO流//user.properties");
//2、新建一个Properties集合
Properties pro = new Properties();
//3、调用Properties对象的load方法将文件中的数据加载到Map集合中
pro.load(reader);
//4、通过key获取value
String name = pro.getProperty("username");
System.out.println(name);
}
}