一、IO的定义:
- I : Input
- O : Output
- 通过IO可以完成硬盘文件的读和写。
二、IO流的分类
1. 流的方向:输入流、输出流
- 相对内存来定义的
往内存中去 | 从内存中出来 |
---|---|
输入(Input)。或者读(Read) | 输出(Output)。或者写(Write) |
将文件或其它输入设备的数据加载到内存的过程 | 将内存中的数据保存到文件或其他输出设备 |
2. 读取数据方式不同:字节流、字符流
字节流 | 字符流 |
---|---|
按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位 | 按照字符的方式读取数据的,一次读取一个字符 |
这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等 | 这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件(例如:.txt结尾的),word文件也无法读取(能用记事本编辑的都是普通文本文件) |
用字节流读 | 用字符流读 |
---|---|
假设有一txt文件 | 内容是:a东bc土 |
-- | -- |
第一次读:一个字节,正好读到'a' | 第一次读:'a'字符('a'字符在windows系统中占用1个字节) |
第二次读:一个字节,正好读到'东'字符的一半。 | 第二次读:'东'字符('东'字符在windows系统中占用2个字节) |
第三次读:一个字节,正好读到'东'字符的另外一半。 | 第三次读:'b'字符('b'字符在windows系统中占用1个字节) |
三、java.io.*
1 四类io流的父类
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
- 四大分类的父类都是抽象类。(abstract class)
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据。
2. 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.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
四、FileInputStream和FileOutputStream
1. 测试FileInputStream
public static void main(String[] args) {
FileInputStream fis = null;
// 传入路径的构造方法:
// 路径格式:D:\\javadownload\\ideaworkplace\\com.vigil\\java-base\\resources\\test.txt
//D:/javadownload/ideaworkplace/com.vigil/java-base/resources/test.txt
try {
fis = new FileInputStream("resources/test.txt");
// int data = -1;
// while((data = fis.read()) != -1) {//read() 返回从这个输入流读取一个字节的数据。
// System.out.println(data);
// }
// System.out.println(data);
//fis.available()
//返回一个剩余的字节数的估计,可以从这个输入流读取(或跳过),而不阻塞该输入流的方法的下一次调用。0时返回的文件位置是在EOF。下一个调用可能是同一个线程或另一个线程。一个单一的读或跳过这许多字节将不会阻止,但可以读取或跳过更少的字节数。
//返回可以从这个输入流中读取(或跳过)的剩余字节数的估计。
System.out.println("文件总字节数:" + fis.available());
//跳过并丢弃 n字节输入流中的数据。
fis.skip(6);
//read(byte[] b)
//读到 b.length从输入流到字节数组数据字节。该方法块,直到有一个输入可用。
//返回读入缓冲的字节总数,或 -1如果没有更多的数据,因为已经到达文件末尾。
int readCount = 0;
byte[] bytes = new byte[16];
while((readCount = fis.read(bytes)) != -1) {
// System.out.print(readCount + ",");
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();
}
}
}
}
IDEA 默认的当前路径:工程Project的根
2. 测试FileOutputStream
public static void main(String[] arges) {
FileOutputStream fos = null;
try {
//没有文件,会在该路径新建文件
//有文件,会清空源文件内容,在重新写入
// fos = new FileOutputStream("resources/myfile");
//以追加的方式写入
fos = new FileOutputStream("resources/myfile",true);
byte[] bytes = {97,98,99,100,101,102};
fos.write(bytes);
//字符串
String str = "测试一下";
//字符串转字节数组
bytes = str.getBytes();
//写入
fos.write(bytes);
//写完后,要刷新
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 字节流完成拷贝
- 使用java.io.FileInputStream和java.io.FileOutputStream
//拷贝过程:一边读,一边写
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//输入流对象
fis = new FileInputStream("resources/testjpg.jpg");
//输出流对象
fos = new FileOutputStream("coypjpg.jpg");
//最核心的一边写一边读
byte[] bytes = new byte[1024];
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();
}
}
}
}
五、FileReader 和 FileWriter
字符流完成拷贝
//使用字节流只能完成 “普通文本” 拷贝
public static void main(String[] args) {
FileReader reader = null;
FileWriter writer = null;
try {
//创建输入流对象
reader = new FileReader("resources/test.txt");
//创建输出流对象
writer = new FileWriter("resources/file.txt");
//一边读一边写
char[] chars = new char[1024];
int readCount = 0;
while((readCount = reader.read(chars)) != -1) {
writer.write(chars,0,readCount);
}
String str = "通过字符流复制完成";
writer.write("\n");
writer.write(str);
writer.write(str,5,4);
writer.write("\n");
writer.write(97);
//输出完后要刷新
writer.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流,分别try保证都关闭互不影响
if (writer == null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader == null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
六、 BufferedReader和BufferedWriter
使用BufferedReader、BufferedWriter完成拷贝
public class CopyTest03 {
public static void main(String[] args) {
/*
BufferedReader、BufferedWriter
带有缓冲区的字符输入流、字符输出流
使用这个流不用自定义char数组或者byte数组。该流自带缓冲
*/
BufferedReader br = null;
BufferedWriter bw = null;
try {
// BufferedReader、BufferedWriter的构造方法需要传入一个流的对象
//被传入的流被称为:节点流
//外部负责包装的流被称为:包装流、处理流
br = new BufferedReader(new FileReader("src/com/vigil/ioTest/CopyTest03.java"));
bw = new BufferedWriter(new FileWriter("resources/JavaCopy.txt"));
String str = null;
//读一行,当没有可读的行,会返回null
//readLine()方法读取一行,不带换行符
while((str = br.readLine()) != null) {
bw.write(str);
bw.write("\n");
}
str = "通过BufferedReader、BufferedWriter复制完成";
bw.write("\n");
bw.write(str,31,4);
//输出结束,刷新
bw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//对于包装流来说,只要关闭最外层的流就行,里面的节点流会自动关闭
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
七、标准输出流:PrintStream
- 日志文件的雏形
public static void main(String[] args) {
PrintStream out = System.out;
out.println("测试");
try {
// 标准输出流不再指向控制台,指向具体路径下的log文件
out = new PrintStream(new FileOutputStream("resources/log",true));
// 修改输出方向,将输出方向修改到“log”文件
System.setOut(out);
// 输出
System.out.println("泼墨");
System.out.println("丰富");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
out.close();
}
}
八、File
- 抽象表示的文件和目录的路径名
注意:构造函数只是创建一个File实例,并没有以文件做读取等操作,因此路径即使是错误的,也可以创建实例不报错
构造方法 | 作用 |
---|---|
File(File parent, String child) | 通过父路径File实例对象和子路径字符串创建新的File实例 |
File(String pathname) | 通过给定的字符串路径(一般是文件的绝对路径)转为抽象路径名用来创建File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串(一般是相对父类的相对路径)创建新的File实例 |
File(URI uri) | 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例 |
- 常用方法
方法名 | 作用 |
---|---|
boolean exists() | 实例对象代表的文件是否存在 |
String getAbsolutePath() | 获取实例对象代表的文件的绝对路径 |
String getName() | 获取实例对象代表的文件名字(包含文件后缀) |
String getParent() | 获取实例对象代表的文件上级目录,如果路径名不叫父目录则返回 null |
File getParentFile() | 获取实例对象的父项的实例对象,如果此路径名未指定父目录,则返回null;也就是获取对象的上级目录然后再实例化一个对象 |
String getPath() | 获取实例对象代表的文件的实际路径 |
long length() | 获取文件的字节个数,只能针对文件使用,不能针对文件夹使用 |
File[] listFiles() | 返回文件夹中的所有内容的File对象(不包含子文件夹中的内容) |
boolean mkdir() | 根据实例对象的路径名创建目录(若目录已存在,则false;若路径是文件,则fasle;若路径的上级目录不存在则false) |
boolean mkdirs() | 根据实例对象的路径创建目录,包括创建那些必须的且不存在的父级目录 |
boolean isFile() | 检测实例对象代表的是否是文件 |
long lastModified() | 返回文件的抽象路径名表示上次修改时间 |
九、对象流
ObjectOutputStream
序列化:把对象转换为字节序列的过程称为对象的序列化。ObjectInputStream
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。能够序列化的对象必须实现Serializable接口
transient关键字
被该关键字修饰的属性不会被序列化,游离的意思-
序列化多个对象
- 可以借助ArrayList,该类实现了Serializable接口,同时 T 类也要实现。因为要实现序列化的对象,所有涉及的引用,都需要实现序列化接口才可以Serializable接口
-
serialVersionUID,序列化版本号:
实现了Serializable接口的类,都会有这个属性- 若没有在类中显式书写该编号,那么每次更改后编译,java虚拟机都会自动加上一个新的版本号
- private static final long serialVersionUID = 1L; 显式在类中写出,那么每一次更改编译后都不会改变序列化版本号
-
jdk api 文档里面关于接口 Serializable 的一些描述
- 类通过实现 java.io.Serializable 接口以启用其序列化功能。
- 未实现此接口的类将无法使其任何状态序列化或反序列化。
- 可序列化类的所有子类型本身都是可序列化的。因为实现接口也是间接的等同于继承。
- 序列化接口没有方法或字段,仅用于标识可序列化的语义。
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。
十、IO + Properties联合使用。
- IO流:文件的读和写。
- Properties:是一个Map集合,key和value都是String类型。
- [ ] key=value
Properties可以用来保存属性集(类似Map, 以键值的形式保存数据,不同的是Properties都是String类型的)。这个类的优势是可以从流中加载属性集,或者把属性集报错到流中。