目录
6.3、使用FileInputStream + FileOutStream完成文件的拷贝
6.6、使用FileReader + FileWriter完成文件的拷贝
6.7、BufferedReader :带有缓冲区的字符输入流
6.9、java.io.DataOutputStream:数据专属的流 + DataInputStream:数据字节输入流
1、IO流。什么是IO?
- I : Input
- O : Outout
- 通过IO可以完成硬盘的读和写
2、IO流的分类?
IO流的分类有多种分类方式:
(1)、一种方式是按照流的方向进行分类 以内存作为参照物:
- 往内存中去,叫做输入(Input),或者叫做读
- 从内存中出,叫做输出(Output),或作叫做写
(2)、另一种方式是按照读取数据方式不同进行分类:
- 有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制,这种流是万能的,什么类型的文件都可以读取,包括:文本文件、图片。声音文件,视频
- 有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取文本文件,而存在的。这种流不能读取:图片、声音、视频等文件,只能读取纯文本文件,链Word文件都无法读取。
(3)、综上所示:流的分类
- 输入流,输出流
- 字节流,字符流
3、IO流中主要需要掌握什么?
- Java中所有的流都是在:java.io.*;下 ,其中IO流都已经写好了;
- 我们程序员不需要关心,最主要掌握在Java中已经掌握哪些流,每个流的特点是什么,每个流对象的特点有哪些?
- Java中主要还是研究:怎么new流对象,调用流对象的那个方法是读,那个方法是写;
4、Java IO流有四大家族
(1)、 java.io.InputStreame 字节输入流
(2)、 java,io,OutoutStreame 字节输出流
(3)、java.io.Reader 字符输入流
(4)、java.io.Writer 字节输出流
四大家族的首领都是抽象类(abastract class), java中只要“类名”以Stream结尾的都是字节流,以“Readar/Writer”结尾的都是字符流
注意:
- 所有的流都实现了:java.io.Closeable接口,都是可关闭的,都有close()方法,流毕竟是一个管道,是内存与硬盘之间的通道,用完之后一定眼观鼻,否则会耗费大量资源。
- 所有的java.io.Flushable接口,都是可刷新的,都有Flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush(), 刷新一下。刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道)
- 注意:没有fluse()可能会导致丢失数据
5、java.io包下需要掌握的流有16个
(1)、文件专属:
- java.io.FileInputStream(掌握)
- java.io.FileOutputStream(掌握)
- java.io.FileReader
- java.io.FileWriter
(2)、转换流:(将字节转换成字符流)
- java.io.InputStreamReader
- java.io.OutputStreamReader
(3)缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
(4)、数据流专属:
- java.io.DataInputStream
- java.io.DateOutputStream
(5)、标准输出流:
- java.io.PrintWriter
- java.io.PrintStream(掌握)
(6)、对象专属流:
- java.io.ObjectInputStream(掌握)
- java.io.ObjectOutputStream(掌握)
6、IO中部分实现类解析
6.1、FileInputStream:字节输入流
(1)、
public class Main {
public static void main(String[] args) {
try {
fis = new FileInputStream("文件的具体路径");
while(true){
int fist = fis.read();
if(fist == -1){
break;
}
System.out.println(fist);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
分析这程序缺点:一次读取一个字节byte,内存与硬盘交互太频繁,耗费时间资源
(2)、对以上程序进行改进:
public class Main {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("文件路径");
// 准备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));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
(3)、FileInputStream类的其他常用方法
方法 | 说明 |
int available(); | 返回流当中剩余的没有用到的字节数量 |
long skip(long n); | 跳过几个字节不读 |
6.2、FileOutputStream:字节输出流
(负责写,从内存到硬盘)
(1)、
public class Main {
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};
// 将byte数组全部写出
fos.write(bytes);// 先写出abcd
// 将byte数组一部分输出
fos.write(bytes,0,2); // 再写出ef
String s = "我是一个中国人";
// 将字符串转为byte数组
byte[] bs = s.getBytes();
fos.write(bs);
// 写完之后一定要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.3、使用FileInputStream + FileOutStream完成文件的拷贝
- 拷贝的过程应该是一边读,一边写
- 使用以上的字节流拷贝文件的时候,文件类型随意,万能型,什么样的文件都可以拷贝
public class Main {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 创建输入流对象
fis = new FileInputStream("F:\\Java_非凡\\数据结构\\DataStructure\\DataStructure.iml"); // 被拷贝文件路径
fos = new FileOutputStream("E:\\f盘拷贝文件"); // 需要给出拷贝路径
// 一边读一边写(核心)
byte[] bytes = new byte[1024 * 1024]; // 每次最多拷贝1MB
int count = 0; // 定义变量,读入字节的数量
while ((count = fis.read(bytes)) != -1) { // 向bytes数组里读入内容
fos.write(bytes, 0, count); // 读多少写多少
}
// 刷新,输出流最后需要进行刷新
fos.flush();
} catch (FileNotFoundException e) {
System.out.println("文件路径错误");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 分开try,不要一起try
// 一起try的时候,其中一个出现异常,可能影响另一个流的关闭
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.4、FileReader:文本字符输入流
- 只能读取普通文本,读取文本内容时,比较方便,快捷
public class Main {
public static void main(String[] args) {
FileReader reader = null;
try {
// 创建文本字符输入流
reader = new FileReader("文本路径");
// 准备一个char[]数组
char[] chars = new char[4];
// 往char数组中读
reader.read(chars); // 按字符方式读
for(char c : chars){
System.out.println(c);
}
/*// 开始读
char[] chars = new char[4];
int readCount = 0;
while ((readCount = reader.read(chars)) != -1) {
System.out.print(new String(chars, 0, readCount)); // 将字符数组转为字符串
}*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.5、FileWriter,文件字符输出流
- 只能输出普通文本
public class Main {
public static void main(String[] args) {
FileWriter writer = null;
try {
// 创建文本字符输出流对象
// writer = new FileWriter("FileWrite"); // 对源文件进行清空
writer = new FileWriter("FileWrite",true); // 对原文件追加
// 开始写
char[] chars = {'我','是','中','国','人'};
writer.write("我是一名java软件工程师!");
// 添加一个换行符
writer.write("\n");
writer.write(chars);
writer.write("hello world");
// 刷新
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer != null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.6、使用FileReader + FileWriter完成文件的拷贝
- 只能拷贝“普通文本”文件
public class Main {
public static void main(String[] args) {
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("文件具体路径");
out = new FileWriter("拷贝路径");
char[] chars = new char[1024 * 512]; // 1mb
int readCount = 0;
while((readCount = in.read(chars)) != -1){
out.write(chars,0,readCount);
}
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.7、BufferedReader :带有缓冲区的字符输入流
-
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲
(1)FileReader读入缓冲区的字符输入流
public class Main {
public static void main(String[] args) throws Exception{
FileReader reader = new FileReader("CopyTest02"); // 读入某个文件
// 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
// 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流
// 对当前这个程序来说:FileReader就是一个节点流。BuffereReader就是包装流/处理流
BufferedReader br = new BufferedReader(reader); // BufferedReader需要Reader参数
/*
String Lind01 = br.readLine(); // 读一行
System.out.println(Lind01);
// 第二行
String Lind02 = br.readLine();
System.out.println(Lind02);
// 第三行
String Lind03 = br.readLine();
System.out.println(Lind03);
*/
// while循环
// 使用br.readLine()方法读取一个文本行,不带换行符。
String s = null;
while((s = br.readLine()) != null){
System.out.println(s);
}
// 关闭流, 对于包装类来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码)
br.close();
}
}
(2)、FileInputStream字节输入流读入字符流,需要使用InputStreamReader转换流
public class BufferedReaderTest02 {
public static void main(String[] args) throws Exception {
/*// 字节流
FileInputStream in = new FileInputStream("CopyTest02");
// 通过转换流转换(InputStreamReader将字节流转换成字符流)
// in 是字节流,reader是包装流
InputStreamReader reader = new InputStreamReader(in);
// 这个构造方法只能传一个字符流,不能传一个字节流
// reader是字节流,br是包装流
BufferedReader br = new BufferedReader(reader);*/
// 合并
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("写入文件路径名")));
String line = null;
while ((line = br.readLine()) != null){
System.out.println(line);
}
// 关闭最外层流
br.close();
}
}
6.8、BufferedWriter:带有缓冲的字符输出流
public class BufferedWriterTest01 {
public static void main(String[] args) throws Exception{
// 带有缓冲流的字符输出流
// BufferedWriter out = new BufferedWriter(new FileWriter("文件路径名"));
// 将字节流转换为字符流,写出
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("文件路径名",true)));
// 开始写
out.write("hello world!");
out.write("\n");
out.write("hello kity");
// 刷新
out.flush();
// 关闭
out.close();
}
}
6.9、java.io.DataOutputStream:数据专属的流 + DataInputStream:数据字节输入流
- DataOutputStream这个流可以将数据的类型一并写入文件。注意:这个文件不是普通的文本文档。(这个文档使用记事本打不开)
public class DataOutputStreamTest01 {
public static void main(String[] args) throws Exception {
// 创建数据专属的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("date"));
// 写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400l;
float f = 3.0f;
double d = 3.12;
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();
// 关闭最外层流
dos.close();
}
}
-
DataOutputStream:写的文件,只能通过DataInputStream去读。并且读的时候你需要提前知道写入的顺序(读的顺序要和写的顺序一致,才可以正常取出数据)
public class DataInputStreamTest01 {
public static void main(String[] args) throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("date"));
// 开始读
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);
// 关闭
dis.close();
}
}
6.10、PrintStream:标准的字节输出流
- 默认输出为控制台
public class PrintStreamTest01 {
public static void main(String[] args) throws Exception{
// 联合起来写
System.out.println("Hello world");
// 分开写
PrintStream ps = System.out;
ps.println("zhangsan");
ps.println("lisi");
ps.println("wangwu");
System.out.println("------------");
// 标准输出流不需要手动close();
// 可以改变标准输出流的输出方向吗? 可以
/*System.gc();
System.currentTimeMillis();
PrintStream ps2 = System.out;
System.exit(0);
System.arraycopy(...);*/
// 标准输出流不再指向控制台,指向“Log"文件
PrintStream ps2 = new PrintStream(new FileOutputStream("log"));
// 修改输出方向,将输出方向修改到"log"文件
System.setOut(ps2);
// 在输出
System.out.println("hello kitty");
System.out.println("hello zhangsan");
System.out.println("hello world");
}
}
对PrintStream的例子(记录日志),此处可以武略
class Logger {
/*
记录日志的方法
*/
public static void log(String msg){
try {
// 指定日志文件
PrintStream out = new PrintStream((new FileOutputStream("log.txt",true))); // true为追加
// 改变输出方向
System.setOut(out);
// 日前当前时间
Date nowDate = new Date(); // 1980年至现在的时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss");
String strTime = sdf.format(nowDate);
System.out.println(strTime + ":" + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
// 测试类
public class LoggerTest{
public static void main(String[] args) {
// 测试工具类是否好用
Logger.log("调用了System类的gc()方法,建议启动垃圾回收!");
Logger.log("调用了UserSeervice的doSome()方法!");
Logger.log("用户尝试进行登录,验证失败!");
Logger.log("我非常喜欢这个记录日志的工具哦!!");
}
}
7、File类
(1)、File类和四大家家族没有关系,所以File类不能完成文件的读写。
(2)、File对象代表什么?
- 文件和目录名的抽象表示形式
- 一个File对象有可能对应的是目录,也可能是文件
- File只是一个路径名的抽象表示形式。
7.1、File类常用的方法。
方法名 | 说明 |
boolean exists() | 判断文件是否存在 |
String getAbsolutePath() | 获取绝对路径 |
String getName() | 返回此抽象路径名表示的文件或目录的名称。 |
String getParent() | 获取文件的上一级目录名的字符串 |
File getParentFile() | 返回此抽象路径名的父 |
String getPath() | 将此抽象路径名转换为路径名字符串。 |
boolean isAbsolute() | 判断抽象路径名是否是绝对的 |
boolean isDirectory() | 测试此抽象路径名表示的文件是否为目录。 |
boolean isFile() | 测试此抽象路径名表示的文件是否为普通文件。 |
long lastModified() | 返回此抽象路径名表示的文件上次修改的时间。 |
long length() | 返回由此抽象路径名表示的文件的长度 |
File[] listFiles() | 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件 |
boolean mkdirs() | 创建由此抽象路径名命名的目录。 |
boolean mkdir() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 |
对以上部分方法使用:
public class FileTest01 {
public static void main(String[] args) throws IOException {
// 创建一个File对象
File f1 = new File("F:\\file");
// 判断file是否存在
System.out.println(f1.exists()); // false
// 如果 F:\file 不存在,则以文件的形式创建出来
/*if(!f1.exist()){
// 以文件形式创建
f1.createNewFile();
}*/
// 如果 F:\file 不存在,则以目录的形式创建出来
/*if(!f1.exists()){
// 以目录方式创建
f1.mkdir();
}*/
// 创建多重目录
File f2 = new File("D:/b/c/d/e");
/*if(!f2.exists()){
// 创建多重目录
f2.mkdirs();
}*/
File f3 = new File("F:\\JavaSE-Project");
// 获取文件的父路径
String parentPath = f3.getParent(); // F:\
System.out.println(parentPath);
// 获取文件绝对路径
File parentfile = f3.getParentFile();
System.out.println(parentfile);
System.out.println("获取绝对路径:" + parentfile.getAbsolutePath());
File f4 = new File("date");
System.out.println(f4.getAbsoluteFile());
}
}
7.2、文件的拷贝(复制)
public class Main {
public static void main(String[] args) {
File srcFile = new File("F:\\Java_非凡");// 拷贝源
File destFile = new File("E:\\");
copyDir(srcFile, destFile);
}
private static void copyDir(File srcFile, File destFile) {
if (srcFile.isFile()) { // srcFile如果是一个文件,递归结束
FileInputStream in = null;
FileOutputStream out = null;
try {
// 读这个文件
in = new FileInputStream(srcFile);
String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcFile.getAbsolutePath().substring(3);
// 写入这个文件
out = new FileOutputStream(path);
// 一边读,一边写
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while ((readCount = in.read(bytes)) != -1) {
out.write(bytes, 0, readCount);
}
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
// 获取源下面的子目录
File[] files = srcFile.listFiles();
for (File file : files) {
if (file.isDirectory()) {
// 新建对应的目录
System.out.println();
String strDir = file.getAbsolutePath();
// String destDir = "动态获取路径" + strDir.substring(3);
String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + strDir.substring(3);
File newFile = new File(destDir);
if (!newFile.exists()) {
newFile.mkdirs(); // 如果该文件不存在,则创建该文件
}
}
// System.out.println(file.getAbsolutePath()); // 获取所有文件(包括目录和文件),递归结束
copyDir(file, destFile);// 递归调用
}
}
}
8、Serialize序列化
8.1、序列化概述
- 对象的寿命通常随着生成该对象的程序的终止而终止。 有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复。
- 对象的输入输出流 (ObjectInputStream、ObjectOutStream),主要的作用是用于写入对象信息与读取对象信息,对象信息 一旦写到文 件上那么对象的信息就可以做到持久化了.
8.2、序列化版本号作用?
(1)、java语言中来区分类的机制:
- 第一:首先通过类名进行比对,如果类名不一样,肯定不是同一类
- 第二:如果类名一样,考序列化版本号区分。
(2)、自动生成自动化序列化的缺点:
- 不能随意修改代码,一旦代码确定之后,不能进行后续的修改,因为一旦修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候Java虚拟机会认为这是一个全新的类。
-
结论:
凡是一个类实现了Serializeable接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即便代码修改了,但是版本号不变,Java虚拟机会认为是同一个类。
简单例题说:
(1)、创建User对象
public class User implements Serializable {
private int no;
// transient 关键早表示游离的,不参与序列化
private transient String name;
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 "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
(2)、创建ObjectOutputStream,序列化
一次序列化多个对象:
可以将对象放到集合中,序列化集合。
参与序列化的ArrayList集合以及集合中的元素User都需要实现 java,io,Serializeable接口。
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception {
List<User> userList = new ArrayList<>();
userList.add(new User(1,"zhangsan"));
userList.add(new User(2,"lisi"));
userList.add(new User(3,"wangwu"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
// 序列化一个集合,这个集合对象放了很多其他对象
oos.writeObject(userList);
oos.flush();
oos.close();
}
}
(3)、反序列化集合
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));
// Object obj = ois.readObject();
// System.out.println(obj instanceof List); // true
List<User> userList = (List<User>) ois.readObject();
for(User user : userList){
System.out.println(user);
}
ois.close();
}
}
(4)、反序列化输出:
8.3、IO + Properties的联合使用
/*
IO + Properties的联合使用、
非常好的一个设计理念:
以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
将来只需要修改这个文件的内容,java代码不需要改动没不需要重新
编译,服务器也不需要重启,就可以拿到动态的信息
类似于以上以上机制的这种文件被称为配置文件
并且当配置文件中的内容格式是:
key1 = value;
key2 = value;
的时候,我们把这种配置文件叫做属性配置文件
java规范中要求:属性配置文件建议以:.properties结尾,但这不是必须的。
这种以.properties结尾的文件在Java中被称为:属性配置文件
其中Properties是专门存放属性配置文件内容的一个类
属性配置文件中 # 是注释
key重复的话,value自动覆盖
*/
public class IoPropertiesTest01 {
public static void main(String[] args) throws Exception {
/*
Properties是一个Map集合,key和value都是String类型
想将userinfo文件中的数据加载到Properties对象当中。
*/
// 新建一个输入流
FileInputStream reader = new FileInputStream("F:\\JavaSE-基础段\\JavaSE-Project\\Java-IDEA-进阶代码\\chapter23\\userinfo.properties");
// 新建一个Map集合
Properties pro = new Properties();
// 使用Propeerties对象的load方法讲文件中的数据加载到Map集合中
pro.load(reader); // 文件中的数据局顺着管道加载到Map集合中,其中等号左边做key,右边做value
// 通过key获取value
String username = pro.getProperty("username");
System.out.println(username);
String password = pro.getProperty("password");
System.out.println(password);
}
}
输出:
以上文件路径内容: