IO流
- 1>什么是IO
- 2>IO的分类
- 3>IO流中最重要的四个流
- 4>需要了解或掌握的16个流
- 5>FileInputStream与FileOutputStream
- 5.1、FileInputStream常用方法
- 5.2、FileOutputStream常用方法
- 5.3、拷贝文件
- 6>FileReader与FileWriter
- 6.1、 拷贝文本文件
- 7>BufferedReader与BufferedWriter
- 8>InputStreamReader与OutputStreamWriter
- 9>DataInputStream与DataOutputStream
- 10>PrintStream
- 11>File类
- 11.1、拷贝文件夹
- 12>序列化与反序列化
- 12.1、少量序列化
- 12.2、大量序列化
- 12.3、transient关键字
- 12.4、序列化版本号
- 12.4.1、IDEA中自动生成序列化版本号
1>什么是IO
I : Input
O : Output
通过IO可以完成硬盘文件的读和写。
Java中所有的流都在:java.io.*;下
2>IO的分类
一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫做输入(Input)。或者叫做读(Read)。
从内存中出来,叫做输出(Output)。或者叫做写(Write)。
一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节byte,
等同于一次读取8个二进制位。
这种流是万能的,什么类型的文件都可以读取。
包括:文本文件,图片,声音文件,视频文件等....
假设文件file1.txt,采用字节流的话是这样读的:
a中国bc张三fe
第一次读:一个字节,正好读到'a'
第二次读:一个字节,正好读到'中'字符的一半。
第三次读:一个字节,正好读到'中'字符的另外一半。
有的流是按照字符的方式读取数据的,一次读取一个字符,
这种流是为了方便读取普通文本文件而存在的,
这种流不能读取:图片、声音、视频等文件。只能读取纯
文本文件,连word文件都无法读取。
假设文件file1.txt,采用字符流的话是这样读的:
a中国bc张三fe
第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
第二次读:'中'字符('中'字符在windows系统中占用2个字节。)
综上所述:流的分类
输入流、输出流
字节流、字符流
3>IO流中最重要的四个流
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()
刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据
强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。
在java中只要“类名”以Stream结尾的都是字节流。
以“Reader/Writer”结尾的都是字符流。
4>需要了解或掌握的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.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
5>FileInputStream与FileOutputStream
5.1、FileInputStream常用方法
常用构造方法
FileInputStream(String name)
通过打开与实际文件的连接来创建一个 FileInputStream ,
该文件由文件系统中的路径名 name命名
FileInputStream(File file)
通过打开与实际文件的连接创建一个 FileInputStream ,
该文件由文件系统中的 File对象file命名。
常用方法
int read()
从该输入流读取一个字节的数据。
返回值为读取的Ascill码
int read(byte[] b)
从该输入流读取最多 b.length个字节的数据为字节数组。
返回值为读取了多少个字节
int read(byte[] b, int off, int len)
从该输入流读取最多 len字节的数据为字节数组。
long skip(long n)
跳过并从输入流中丢弃n字节的数据
比如一个文本"abcdef" 调用skip(3)
最后使用read()读到的就是100 (d的Ascill码为100)
int available()
返回流当中剩余的字节数
比如
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
这样的话就不需要循环读取读一次即可,
但不适用于文件特别大的情况,因为byte数组不能过大
5.2、FileOutputStream常用方法
构造方法
FileOutputStream(String name)
创建文件输出流以指定的名称写入文件。
FileOutputStream(String name, boolean append)
创建文件输出流以指定的名称写入文件。
常用方法
void write(byte[] b)
将 b.length个字节从指定的字节数组写入此文件输出流。
void write(byte[] b, int off, int len)
将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。
void write(int b)
将指定的字节写入此文件输出流。
5.3、拷贝文件
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\abc.txt");
fos = new FileOutputStream("D:\\def.txt");
byte[] bytes =new byte[1024];
int readData = 0;
while((readData = fis.read(bytes)) !=-1)
{
fos.write(bytes);
}
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();
}
}
}
}
6>FileReader与FileWriter
只能读取和写入普通文本,读取文本内容(这里的文本不单指txt文本,
可以用txt文本编辑器打开的所有文本都可以读取并写入)时,比较方便、快捷。
6.1、 拷贝文本文件
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("D:\\abc.txt");
fw = new FileWriter("D:\\hij.txt");
char[] chars = new char[1024];
int readDate = 0;
while((readDate = fr.read(chars))!= -1) {
fw.write(chars);
}
} catch (IOException e) {
e.printStackTrace();
}
}
7>BufferedReader与BufferedWriter
BufferedReader:
带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者不需要自定义byte数组,自带缓冲。
BufferedWriter:
带有缓冲的字符输出流。
public static void main(String[] args) throws Exception{
FileReader fr = new FileReader("D:\\abc.txt");
BufferedReader br = new BufferedReader(fr);
//这里fr是节点流
//br是包装流
FileWriter fw = new FileWriter("D:\\hij.txt");
BufferedWriter bw = new BufferedWriter(fw);
String s;
while((s = br.readLine())!=null)
{
System.out.println(s);
//readLine()是读取一行
//但是不带换行符
}
bw.write("xyz");
bw.write(123);
bw.flush();
br.close();//只用关闭最外层
bw.close();//只用关闭最外层
}
8>InputStreamReader与OutputStreamWriter
InputStreamReader和OutputStreamWriter都是转换流。
作用:
1>将FileInputStream转换至FileReader
2>将FileOutputStream转换至FileWriter
BufferedReader br = new BufferedReader(new InputStreamReader(new FileReader("D:\\abc.txt")));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileWriter("D:\\abc.txt")));
9>DataInputStream与DataOutputStream
数据专属流
DataOutputStream这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文档文件(无法用记事本打开)
DataOutStream写入的文件只能用DataInputStream去读,并且读的时候
必须提前知道写入的顺序,读和写的顺序要一致。才可以正常取出数据
public static void main(String[] args) throws Exception{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\Data"));
byte b =100;
short s = 200;
int i = 300;
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.flush();
dos.close();
}
public static void main(String[] args) throws Exception{
DataInputStream dis = new DataInputStream(new FileInputStream("D:\\Data"));
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
System.out.println(b);
System.out.println(s);
System.out.println(i);
}
10>PrintStream
System中的setOut()方法重定向,可以将原本打印在控制台的文本
再打入到指定PrintStream对象中
public static void main(String[] args) throws Exception{
PrintStream ps = new PrintStream(new FileOutputStream("D:\\log.txt",true));
System.setOut(ps);
System.out.println("Hello World");
System.out.println("Hey guy!");
}
11>File类
常用方法
boolean exists()
测试此抽象路径名表示的文件或目录是否存在。
String getParent()
返回此抽象路径名的父 null的路径名字符串,
如果此路径名未命名为父目录,则返回null。
File getParentFile()
返回此抽象路径名的父,
或抽象路径名 null如果此路径名没有指定父目录。
String getAbsolutePath()
返回此抽象路径名的绝对路径名字符串。
File getAbsoluteFile()
返回此抽象路径名的绝对形式。
String getName()
返回由此抽象路径名表示的文件或目录的名称。
boolean isFile()
测试此抽象路径名表示的文件是否为普通文件。
boolean isDirectory()
测试此抽象路径名表示的文件是否为目录。
long length()
返回由此抽象路径名表示的文件的长度。
boolean mkdir()
创建由此抽象路径名命名的目录。
boolean mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
File[] listFiles()
返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
11.1、拷贝文件夹
package com.bjpowernode.java.io;
import java.io.*;
/*
拷贝目录
*/
public class CopyAll {
public static void main(String[] args) {
// 拷贝源
File srcFile = new File("D:\\course\\02-JavaSE\\document");
// 拷贝目标
File destFile = new File("C:\\a\\b\\c");
// 调用方法拷贝
copyDir(srcFile, destFile);
}
/**
* 拷贝目录
* @param srcFile 拷贝源
* @param destFile 拷贝目标
*/
private static void copyDir(File srcFile, File destFile) {
if(srcFile.isFile()) {
// srcFile如果是一个文件的话,递归结束。
// 是文件的时候需要拷贝。
// ....一边读一边写。
FileInputStream in = null;
FileOutputStream out = null;
try {
// 读这个文件
// D:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
in = new FileInputStream(srcFile);
// 写到这个文件中
// C:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcFile.getAbsolutePath().substring(3);
out = new FileOutputStream(path);
// 一边读一边写
byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB
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.close();
} 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){
// 获取所有文件的(包括目录和文件)绝对路径
//System.out.println(file.getAbsolutePath());
if(file.isDirectory()){
// 新建对应的目录
//System.out.println(file.getAbsolutePath());
//D:\course\02-JavaSE\document\JavaSE进阶讲义 源目录
//C:\course\02-JavaSE\document\JavaSE进阶讲义 目标目录
String srcDir = file.getAbsolutePath();
String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcDir.substring(3);
File newFile = new File(destDir);
if(!newFile.exists()){
newFile.mkdirs();
}
}
// 递归调用
copyDir(file, destFile);
}
}
}
12>序列化与反序列化
1>参与序列化与反序列化的对象,必须实现Serializable接口。
2>通过观察源代码发现,Serializable接口只是一个标志接口:
public interface Serializable{
}
这个接口当中什么代码都没有
起到什么作用呢?
起到标识的作用,jvm看到这个类实现李这个接口,
可能会对这个类进行特殊待遇。
12.1、少量序列化
public class Student implements Serializable {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ObjectOutputStreamTest01 {
public static void main(String[] args) throws Exception{
Student s = new Student("zhangsan",19);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Student"));
oos.writeObject(s);
oos.flush();
oos.close();
}
}
public class ObjectInputStreamTest01 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Student"));
Object obj = ois.readObject();
System.out.println(obj);
}
}
ois.close();
12.2、大量序列化
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception{
List<Student> list = new ArrayList<Student>();
list.add(new Student("lisi",15));
list.add(new Student("zhaowu",16));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Student2"));
oos.writeObject(list);
}
}
oos.flush();
oos.close()
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Student2"));
List<Student> list = (List<Student>)ois.readObject();
for (Student student:list
) {
System.out.println(student);
}
}
}
ois.close()
12.3、transient关键字
tranisent关键字表示游离的 不参与序列化
public class Student implements Serializable {
String transient name;
int age;
12.4、序列化版本号
1>
JVM看到Serializable接口之后,会自动生成一个序列化版本号。
在类中不手动写出来的话,JVM自动提供一个序列化版本号。
过了一段时间,若类改变,重写编译之后形成新的字节码文件,
并且class文件再次运行的时候,JVM生成的序列化版本号也会随之改变。
2>
Java语言采用什么方式来区分类
1.通过类名比较,若类名不一致肯定不是同一个类
2.若类名一样,通过序列化版本号区分
3>
自动生成序列化版本会导致:一旦代码确定无法后续修改,
因为只要修改就会重新编译,此时便会生成全新的序列化版本号
这时JVM会认为这是一个全新的类。
所以建议将序列化版本号手动写出来,不建议自动生成
private static final long serialVersionUID = 1L;
12.4.1、IDEA中自动生成序列化版本号
Setting中选定蓝色字体点击ok
在实现Serializable的接口中Alt + Enter 自动生成序列话版本号