在正式的介绍IO流之前,我觉得应该介绍一下File类,该类主要是对文件和目录的抽象表示,因为学习io流第一反应就是文件,该类提供了对文件的创建、删除、查找等操作。主要有以下特点
java的世界万物皆对象,文件和目录就可抽象为File对象
对于File而言,封装的并不是真正的文件,封装的仅仅是一个路径名,磁盘文件本身可以存在,也可以不存在
文件的内容不能用File读取,而是通过流来读取,File对象可以作为流的来源地和目的地
构造方法 | 方法说明 |
---|---|
File(String pathname) | 将路径字符串抽象为File实例,路径字符串可以是相对路径,也可以为绝对路径 |
File(String parent, String child) | 从父路径名和子路径名来构建File实例 |
File(File parent, String child) | 根据父File实例和子路径名来构建File实例 |
如下示例,表示这几种文件和目录的代码
// pathname
File liuBei = new File("D:/三国/刘备.jpg");
// String parent, String child
File guanYu = new File("D:/三国", "关羽.jpg");
// 目录
File sanGuo = new File("D:/三国");
// File parent, String child
File zhangFei = new File(sanGuo, "张飞.txt");
// 可以声明不存在的文件
File zhuGeLiang = new File(sanGuo, "诸葛亮.txt");
绝对路径:从盘符开始的路径,表示一个完整的路径。(经常使用) 相对路径:相对于当前项目目录的路径
File f = new File("D:/bbb.java");
// D:\bbb.java
System.out.println(f.getAbsolutePath());
File f2 = new File("bbb.java");
// F:\code\ad\bbb.java
System.out.println(f2.getAbsolutePath());
路径分隔符
windows的路径分隔符: \
linux的路径分隔符: /
java有常量separator表示路径分隔符
public static final String separator = "" + separatorChar;
换行符
windows的换行符: \r\n
linux的换行符 \n
方法名 | 方法说明 |
---|---|
boolean createNewFile() throws IOException | 当该名称的文件不存在时,创建一个由该抽象路径名的空文件并返回true,当文件存在时,返回false |
boolean mkdir() | 创建由此抽象路径名命名的目录 |
boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。级联创建目录 |
boolean delete() | 删除由此抽象路径名表示的文件或目录 |
上述方法比较简单,其中需要注意的是:
创建多级目录时,mkdir创建失败,返回false,mkdirs创建成功,返回true(推荐使用mkdirs)
删除目录时,目录内不为空时,删除失败,返回false, 即只能删除文件或者空目录
File shuiHu = new File("D:/四大名著/水浒传");
// 返回false 创建失败
boolean mkdir = shuiHu.mkdir();
// 返回true 创建失败
boolean mkdirs = shuiHu.mkdirs();
File four = new File("D:/四大名著");
// 返回false 删除目录时必须目录为空才能删除成功
boolean delete = four.delete();
File shuiHu = new File("D:/四大名著/水浒传");
// true 正确删除了水浒传目录
boolean delete1 = shuiHu.delete();
File liuBei = new File("D:/三国/刘备.jpg");
// 返回true 正确删除了刘备.jpg文件
boolean delete2 = liuBei.delete();
方法名 | 方法说明 |
---|---|
boolean isDirectory() | 判断是否是目录 |
boolean isFile() | 判断是否是文件 |
boolean exists() | 判断文件或目录是否存在 |
boolean canWrite() | 文件是否可写 |
boolean canRead() | 文件是否可读 |
boolean canExecute() | 文件是否可执行 |
long lastModified() | 返回文件的上次修改时间 |
注意的是
文件或目录不存在时, isDirectory() 或 isFile() 返回false
可读、可写、可执行是对操作系统给文件赋予的权限
File xiYou = new File("D:/西游记");
// 文件或目录不存在时 返回false
System.out.println(xiYou.isDirectory());
方法名 | 方法说明 |
---|---|
String getAbsolutePath() | 返回File对象的绝对路径字符串 |
String getPath() | 将此抽象路径名转换为路径名字符串 |
String getName() | 返回文件或目录的名称 |
long length() | 返回由此File表示的文件的字节数 |
String[] list() | 返回目录中的文件和目录的名称字符串数组 |
File[] listFiles() | 返回目录中的文件和目录的File对象数组 |
注意
length() 返回的是文件的字节数,目录的 长度是0
getPath()在用绝对路径表示的文件时相同,用相对路径表示的文件时不同
listFiles和list方法的调用,必须是实际存在的目录,否则返回null
listFiles和list 可以传入FilenameFilter的实现类,用于按照文件名称过滤文件
File shuiHu = new File("D:/水浒传");
// 0
System.out.println(shuiHu.length());
File liuBei = new File("D:/三国/刘备.jpg");
// 24591
System.out.println(liuBei.length());
File f = new File("D:/bbb.java");
// D:\bbb.java
System.out.println(f.getPath());
File f2 = new File("bbb.java");
// bbb.java
System.out.println(f2.getPath());
File sanGuo2 = new File("D:/三国2");
// 该目录不存在,返回null
String[] list = sanGuo2.list();
过滤文件的接口:
@FunctionalInterface
public interface FilenameFilter {
// 参数为目录和指定过滤名称
boolean accept(File dir, String name);
}
扩展(由读者自己实现)
读取目录下所有的文件以及目录,包括子目录下所有的文件及目录
上一章节学习了使用File类创建、查找、删除文件,但是无法读取、传输文件中的内容。
IO流主要是读取、传输、写入数据内容的。
I: input,O:output
这里的主体说的都是程序(即内存),从外部设备中读取数据到程序中 即为输入流,从程序中写出到外部程序中即为输出流
IO的分类
本地IO和网络IO
本地IO主要是操作本地文件,例如在windows上复制粘贴操作文件,都可以使用java的io来操作
网络IO主要是通过网络发送数据,或者通过网络上传、下载文件,我们每天上网无时无刻不在体验着IO的传输
按流向分,输入流和输出流
按数据类型分:字节流和字符流
按功能分:节点流和处理流
程序直接操作目标设备的类称为节点流
对节点流进行装饰,功能、性能进行增强,称为处理流
IO流主要的入口是数据源,下面列举常见的源设备和目的设备
源设备
硬盘(文件)
内存(字节数组、字符串等)
网络(Socket)
键盘(System.in)
目的设备
硬盘(文件)
内存(字节数组、字符串等)
网络(Socket)
控制台(System.out)
本文先探讨本地IO的字节流和字符流,先列举字节流和字符流的公共方法
方法名 | 方法说明 |
---|---|
void close() throws IOException | 流操作完毕后,必须释放系统资源,调用close方法,一般放在finally块中保证一定被执行! |
注意:
程序中打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,需要显式的关闭文件资源
下面的代码示例中就不显示的调用close方法,也不会处理IOException,只是为了代码的简洁,方便阅读
一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制的形式保存,都可以通过使用字节流传输。
InputStream是字节输入流的顶层抽象
// Closeable有close()方法
public abstract class InputStream implements Closeable {}
核心方法如下
方法名 | 方法说明 |
---|---|
int read() throws IOException; | 每次读取一个字节的数据,提升为int类型,读取到文件末尾时返回 -1 |
int read(byte b[])throws IOException | 每次读取到字节数组中,返回读取到的有效字节个数,读取到末尾时返回 -1(常用) |
int read(byte b[], int off, int len) | 每次读取到字节数组中,从偏移量off开始,长度为len,返回读取到的有效字节个数,读取到末尾时返回 -1 |
OutputStream是字节输出流的顶层抽象
// Flushable里面有flush()方法
public abstract class OutputStream implements Closeable, Flushable {}
核心方法如下:
方法名 | 方法说明 |
---|---|
void write(int b) throws IOException; | 将int值写入到输出流中 |
void write(byte[] b) throws IOException; | 将字节数组写入到输出流中 |
void write(byte b[], int off, int len) throws IOException | 将字节数组从偏移量off开始,写入len个长度到输出流中 |
void flush() throws IOException | 刷新输出流并强制缓冲的字节被写出 |
InputStream有很多的实现类,先介绍下文件节点流,即目标设备是文件,输入流和输出流对应的是
FileInputStream和FileOutputStream
FileInputStream主要从磁盘文件中读取数据,常用构造方法如下
public FileInputStream(File file) throws FileNotFoundException{}
public FileInputStream(String name) throws FileNotFoundException{};
当传入的文件不存在时,运行时会抛出FileNotFoundException异常
1.read()方法读取
File file = new File("D:/三国/诸葛亮.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// 核心代码
int b;
while ((b = fileInputStream.read()) != -1 ){
System.out.print((char) b);
}
// 输出结果
abcde
2.read(byte[])读取
File file = new File("D:/三国/诸葛亮.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// 核心代码
byte[] data = new byte[2];
while (fileInputStream.read(data) != -1) {
System.out.println(new String(data));
}
// 输出结果
ab
cd
ed
上述代码由于最后一次读取时,只读取一个字节 e ,数组中还是上次的数据cd,只替换了e,所以最后输出了ed
下面是使用FileInputStream读取的正确姿势:
File file = new File("D:/三国/诸葛亮.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// 核心代码
byte[] data = new byte[2];
int len;
while ((len = fileInputStream.read(data)) != -1) {
// len 为每次读取的有效的字节个数
System.out.println(new String(data, 0, len));
}
// 输出结果
ab
cd
e
注意:使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了效率,建议使用
源码解析
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
private native int readBytes(byte b[], int off, int len) throws IOException;
上面列了read()和read(byte[])的源码,可见都是调用native的方法,涉及底层的系统调用。
如果用read()读取文件,每读取一个字节就要访问一次硬盘,这种效率较低。
如果用read(byte[])读取文件,一次读取多个字节,当文件很大时,也会频繁访问硬盘。如果一次读取超多字节,效率也不会高。
FileOutputStream主要是向磁盘文件中写出数据,常用构造方法如下:
构造方法名 | 方法说明 |
---|---|
FileOutputStream(File file) throws FileNotFoundException | 使用一个File对象来构建一个FileOutputStream |
FileInputStream(String name) throws FileNotFoundException | 使用一个文件名来构建一个FileOutputStream |
FileOutputStream(File file, boolean append) throws FileNotFoundException | append传true时,会对文件进行追加 |
FileOutputStream(String name, boolean append) throws FileNotFoundException | append传true时,会对文件进行追加 |
注意:
上述构造方法执行后,如果file不存在,会自动创建该文件
如果file存在,append没有传或者传了false,会清空文件的数据
如果file存在,append传了true,不会清空文件的数据
File file = new File("D:/三国/赵云.txt");
FileOutputStream fos = new FileOutputStream(file);
FileOutputStream fos1 = new FileOutputStream("D:/三国/司马懿.txt");
// 上述代码中执行完后,赵云.txt和司马懿.txt都会自动创建出来
向文件写数据:
FileOutputStream fos = new FileOutputStream("D:/三国/司马懿.txt");
fos.write(96);
fos.write(97);
fos.write(98);
// 文件内容为 abc
FileOutputStream fos = new FileOutputStream("D:/三国/赵云.txt");
fos.write("三国赵云".getBytes());
// 文件内容为 三国赵云
上述代码每执行一次,文件里的内容就会被覆盖,有时候这不是我们想要的场景,我们一般是想追加文件
FileOutputStream fos = new FileOutputStream("D:/三国/赵云.txt", true);
fos.write("有万夫不当之勇".getBytes());
fos.close();
// 文件内容为 三国赵云有万夫不当之勇
应用场景
开发中涉及文件的上传、下载、传输都是用的这个节点流,会结合装饰后的处理流一起使用,在缓冲流部分有介绍。
扩展(由读者自己实现)
利用文件节点流实现文件的复制。
ByteArrayInputStream是从内存的字节数组中读取数据
public ByteArrayInputStream(byte buf[]) {}
注意:不需要close数据源和抛出IOException,因为不涉及底层的系统调用
ByteArrayOutputStream是向内存字节数组中写数据,内部维护了一个数组
public ByteArrayOutputStream() {
// 内部维护了一个可变的字节数组
// protected byte buf[];
this(32);
}
内存节点流代码示例
ByteArrayInputStream bis = new ByteArrayInputStream("data".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
while ((len = bis.read()) != -1){
bos.write(len);
}
// 输出data
System.out.println(new String(bos.toByteArray()));
应用场景
内存操作流一般在一些生成临时信息时会被使用,如果临时信息保存着文件中,代码执行完还要删除文件比较麻烦
结合对象流,可以实现对象和字节数组的互转
字符流封装了更加适合操作文本字符的方法
Reader用于读取文本字符
public abstract class Reader implements Readable, Closeable {}
核心方法:
方法名 | 方法说明 |
---|---|
int read() throws IOException | 从输入流中读取一个字符,读到文件末尾时返回-1 |
int read(char cbuf[]) throws IOException | 从输入流中读取字符到char数组中 |
Writer用于写出文本字符
public abstract class Writer implements Appendable, Closeable, Flushable {}
核心方法:
方法名 | 方法说明 |
---|---|
void write(int c) throws IOException | 写入单个字符到输出流中 |
void write(char[] cbuf) throws IOException | 写入字符数组到输出流中 |
void write(char[] cbuf, int off, int len) throws IOException | 写入字符数组的一部分,偏移量off开始,长度为len到输出流中 |
void write(String str) throws IOException | 直接写入字符串到输出流中(常用) |
void write(String str, int off, int len) throws IOException | 写入字符串的一部分,偏移量off开始,长度为len |
Writer append(char c) throws IOException | 追加字符到输出流中 |
字符流操作纯文本字符的文件是最合适的,主要有FileReader和FileWriter
FileReader主要是向磁盘文件中写出数据,常用构造方法如下
public FileReader(String fileName) throws FileNotFoundException{}
public FileReader(File file) throws FileNotFoundException {}
注意:当读取的文件不存在时,会抛出FileNotFoundException,这点和FileInputStream一致
1.read()循环读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt");
int b;
while ((b = fileReader.read()) != -1) {
System.out.println((char) b);
}
2.read(char[]) 读取文件
FileReader fileReader = new FileReader("D:/三国/赵云.txt");
int len;
char[] data = new char[2];
while ((len = fileReader.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
// 两个字符两个字符依次读取
FileWriter构造方法如下,和FileOutStream构造方法类似,和FileOutputStream类似。
public FileWriter(String fileName) throws IOException {}
public FileWriter(String fileName, boolean append) throws IOException {}
public FileWriter(File file) throws IOException{}
public FileWriter(File file, boolean append) throws IOException {}
常用的写数据进文件的方法:
FileWriter fileWriter = new FileWriter("D:/三国/孙权.txt");
fileWriter.write(97);
fileWriter.write('b');
fileWriter.write('C');
fileWriter.write("权");
fileWriter.append("力");
注意:
如果不执行close()或者flush()方法,数据只是保存到缓冲区,不会保存到文件。这点和与FileOutputStream不同,原因见 字节流和字符流的共同点章节
应用场景
纯文本文件的io操作,配合处理流一起实现。
字符流也有对应的内存节点流,常用的有StringWriter和CharArrayWriter
StringWriter是向内部的StringBuffer对象写数据。
// 定义
public class StringWriter extends Writer {
private StringBuffer buf;
public StringWriter() {
buf = new StringBuffer();
lock = buf;
}
}
// 应用
StringWriter sw = new StringWriter();
sw.write("hello");
StringBuffer buffer = sw.getBuffer();
// 输出hello
System.out.println(buffer.toString());
CharArrayWriter是向内部的char数组写数据
// 定义
public class CharArrayWriter extends Writer {
protected char buf[];
}
// 应用
CharArrayWriter caw = new CharArrayWriter();
caw.write("hello");
char[] chars = caw.toCharArray();
for (char c : chars) {
// 输出了h e l l o
System.out.println(c);
}
四种常用节点流的使用总结
注意到,OutputStream、Reader、Writer都实现了Flushable接口,Flushable接口有flush()方法
flush():强制刷新缓冲区的数据到目的地,刷新后流对象还可以继续使用
close(): 强制刷新缓冲区后关闭资源,关闭后流对象不可以继续使用
缓冲区:可以理解为内存区域,程序频繁操作资源(如文件)时,性能较低,因为读写内存较快,利用内存缓冲一部分数据,不要频繁的访问系统资源,是提高效率的一种方式
具体的流只要内部有维护了缓冲区,必须要close()或者flush(),不然不会真正的输出到文件中。
上面的章节介绍了字节流和字符流的常用节点流,但是真正开发中都是使用更为强大的处理流
处理流是对节点流在功能上、性能上的增强
字节流的处理流的基类是FilterInputStream和FilterOutputStream
前面说了节点流,都是直接使用操作系统底层方法读取硬盘中的数据,缓冲流是处理流的一种实现,增强了节点流的性能,为了提高效率,缓冲流类在初始化对象的时候,内部有一个缓冲数组,一次性从底层流中读取数据到数组中,程序中执行read()或者read(byte[])的时候,就直接从内存数组中读取数据。
分类
字节缓冲流:BufferedInputStream , BufferedOutputStream
字符缓冲流:BufferedReader , BufferedWriter
可见构造方法传入的是节点流,是对节点流的装饰。
// 内部默认8192 =8*1024 即8M的缓冲区
public BufferedInputStream(InputStream in) {
// 8192
// 内部维护了下面这样的字节数组
// protected volatile byte buf[];
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
这里使用复制一部1G的电影来感受缓冲流的强大
1.使用基本的流读取数据(一次传输一个字节)
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");
FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);
// 五分钟还没拷好,关闭程序了...
2.使用基本的流读取数据(一次传输一个8M的字节数组)
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:/三国/视频.mp4");
FileOutputStream fos = new FileOutputStream("D:/三国/拷贝.mp4");
int len;
byte[] data = new byte[1024 * 1024 * 1024];
while ((len = fis.read(data)) != -1) {
fos.write(data, 0, len);
}
log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);
// 拷贝电影耗时:4651ms
3.使用缓冲流读取数据(一次传输一个字节)
long start = System.currentTimeMillis();
BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);
// 拷贝电影耗时:39033ms
4.使用缓冲流读取数据(一次传输一个8M的字节数组)(最常使用)
long start = System.currentTimeMillis();
BufferedInputStream fis = new BufferedInputStream(new FileInputStream("D:/三国/视频.mp4"));
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("D:/三国/拷贝.mp4"));
int len;
byte[] data = new byte[8 * 1024];
while ((len = fis.read(data)) != -1) {
fos.write(data, 0, len);
}
log.info("拷贝电影耗时:{}ms", System.currentTimeMillis() - start);
// 拷贝电影耗时:1946ms
由上述四个例子可以得出结论,缓冲流读取数据比普通流读取数据快很多!
注意:采用边读边写的方式,一次传输几兆的数据效率比较高,如果采用先把文件的数据都读入内存,在进行写出,这样读写的次数是较小,但是占用太大的内存空间,一次读太大的数据也严重影响效率!
对字符节点流的装饰,下面是字符缓冲流的构造方法
public BufferedReader(Reader in) {
// private static int defaultCharBufferSize = 8192;
// 内部维护了一个字符数组
// private char cb[];
this(in, defaultCharBufferSize);
}
public BufferedWriter(Writer out) {
this(out, defaultCharBufferSize);
}
字符缓冲流的特有方法:
类 | 方法名 | 方法说明 |
---|---|---|
BufferedReader | String readLine() throws IOException | 一行行读取,读取到最后一行返回null |
BufferedWriter | void newLine() throws IOException | 写一个换行符到文件中,实现换行 |
// 创建流对象
BufferedReader br = new BufferedReader(new FileReader("D:/三国/赵云.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:/三国/赵子龙.txt"));
String line = null;
while ((line = br.readLine())!=null) {
System.out.println(line);
bw.write(line);
bw.newLine();
}
// 结果
我乃常山赵子龙
于万军从中,取上将首级
缓冲流是IO流中最重要的知识点,下面通过代码实现正确用IO流的姿势:
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(new File("D:/三国/视频.mp4")));
bos = new BufferedOutputStream(new FileOutputStream(new File("D:/三国/拷贝.mp4")));
int len;
// 一次传输8M的文件,实际测试这里传输的大小并不影响传输的速度
byte[] data = new byte[8 * 1024];
while ((len = bis.read(data)) != -1) {
bos.write(data, 0, len);
}
} catch (IOException e) {
log.error("error", e);
} finally {
// finally块中关闭流,确保资源一定被关闭
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
log.error("error", e);
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
log.error("error", e);
}
}
}
字符编码
计算机存储的数据都是二进制的,而我们在电脑上看到的数字、英文、汉字等都是二进制转换的结果
将字符转换成二进制,为编码
将二进制转换为字符,为解码
字符编码 就是 自然语言和二进制的对应规则。
字符集
就是一个编码表,常见的字符集有ASCII字符集、GBK字符集、Unicode字符集等,具体各个编码的介绍在这里就不介绍了。
IDEA中,使用 FileReader 读取项目中的文本文件。IDEA可以设置为GBK 编码,当读取Windows系统中创建的默认的UTF8文本文件时,就会出现乱码 。
如下例:
idea的字符集设置 默认是UTF-8,这里修改为GBK
运行的代码及结果:
FileReader fileReader = new FileReader("D:/sanguo/utf8.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
// 浣犲ソ
Reader的子类,读取字节,并使用指定的字符集将其解码为字符。字符集可以自己指定,也可以使用平台的默认字符集。
构造方法如下:
// 使用平台默认字符集
public InputStreamReader(InputStream in) {}
// 指定字符集
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException{}
读取文件的“你好",文件默认的字符集是UTF8
// 创建流对象,默认UTF8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"));
// 创建流对象,指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:/三国/utf8.txt"), "GBK");
int read;
while ((read = isr.read()) != -1) {
System.out.println((char) read);
}
while ((read = isr2.read()) != -1) {
System.out.println((char) read);
}
// 输出结果
你好
浣犲ソ
Writer的子类,使用指定的字符集将字符编码为字节。字符集可以自己指定,也可以使用平台的默认字符集。
构造方法如下:
// 使用平台默认字符集
public OutputStreamWriter(OutputStream out) {}
// 使用平台默认字符集
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException{}
如下面的代码,将你好写入文件。写入后两个文件的字符集不一样,文件大小也不同。
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:/三国/黄忠.txt"));
osw.write("你好"); // 保存为6个字节
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:/三国/马超.txt"),"GBK");
osw2.write("你好");// 保存为4个字节
jdk提供了对象序列化的方式,该序列化机制将对象转为二进制流,二进制流主要包括对象的数据、对象的类型、对象的属性。可以将java对象转为二进制流写入文件中。文件会持久保存了对象的信息。
同理,从文件中读出对象的信息为反序列化的过程。
对象想序列化,满足的条件:
该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口(没有任何抽象方法),不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性使用transient 关键字修饰
该类实现将对象序列化后写出到外部设备,如硬盘文件。
public ObjectOutputStream(OutputStream out) throws IOException{}
常用方法:
方法名 | 方法说明 |
---|---|
void writeObject(Object obj) throws IOException | 将指定的对象写出 |
如下代码,将User对象写入文件中。
public class User implements Serializable {
private static final long serialVersionUID = 8289102797441171947L;
private String name;
private Integer age;
}
// 下面是将对象输出到文件的核心代码
User user = new User("马超",20);
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/三国/马超.txt"));
// 写出对象
out.writeObject(user);
注意:
实现了Serializable的实体一定要加一个serialVersionUID变量,这也是习惯问题,idea可以设置一下。
serialVersionUID生成后不要改变,避免反序列化失败,改变后会抛出InvalidClassException异常
生成的文件内容如下:
该类将ObjectOutputStream写出的对象反序列化成java对象
public ObjectInputStream(InputStream in) throws IOException
常用方法
方法名 | 方法说明 |
---|---|
Object readObject() throws IOException, ClassNotFoundException | 读取对象 |
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/三国/马超.txt"));
// 强转为user
User user = (User) in.readObject();
System.out.println(user);
// 输出内容
User(name=马超, age=20)
利用对象流和字节数组流结合 ,可以实现java对象和byte[]之间的互转
// 将对象转为byte[]
public static byte[] t1(T t) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(t);
return bos.toByteArray();
}
// 将byte[]转为对象
public static T t2(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream bos = new ByteArrayInputStream(data);
ObjectInputStream oos = new ObjectInputStream(bos);
return (T) oos.readObject();
}
管道流主要用于两个线程间的通信,即一个线程通过管道流给另一个线程发数据
注意:线程的通信一般使用wait()/notify(),使用流也可以达到通信的效果,并且可以传递数据
使用的类是如下
PipedInputStream和PipedOutStream
PipedReader和PipedWriter
这里使用字节流为例
class Sender implements Runnable {
private PipedOutputStream pos;
private String msg;
public Sender(String msg) {
this.pos = new PipedOutputStream();
this.msg = msg;
}
@Override
public void run() {
pos.write(msg.getBytes());
}
public PipedOutputStream getPos() {
return pos;
}
}
class Receiver implements Runnable {
private PipedInputStream pis;
public Receiver() {
this.pis = new PipedInputStream();
}
@Override
public void run() {
byte[] data = new byte[1024];
int len;
while ((len = pis.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
}
}
Sender sender = new Sender("hello");
Receiver receiver = new Receiver();
receiver.getPis().connect(sender.getPos());
new Thread(sender).start();
new Thread(receiver).start();
// 控制台输出 hello
System.in和System.out代表了系统标准的输入、输出设备
默认输入设备是键盘,默认输出设备是控制台
可以使用System类的setIn,setOut方法对默认设备进行改变
我们开发中经常使用的输出到控制台上的内容的方法。
System.out.println("a");
System.out.print("b");
class System{
public final static InputStream in = null;
public final static PrintStream out = null;
}
public PrintStream(String fileName) throws FileNotFoundException{}
主要方便读取Java基本类型以及String的数据,有DataInputStream 和 DataOutputStream两个实现类。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:/三国/周瑜.txt"));
dos.writeUTF("周瑜");
dos.writeBoolean(false);
dos.writeLong(1234567890L);
DataInputStream dis = new DataInputStream(new FileInputStream("D:/三国/周瑜.txt"));
String s = dis.readUTF();
System.out.println(s);
boolean b = dis.readBoolean();
System.out.println(b);
// 输出
周瑜
false
以上各个章节详细介绍了各个流,可见流的种类比较多,记忆确实增加了困难。但是可以通过思维导图的方式整理出来,方便记忆。
字节流的导图:
字符流的导图
按照功能划分
输入、输出对应关系