Java的流
Java 流与目标数据源和程序之间的关系图示:
下图描述了输入流和输出流的类层次图:
- 字符流:顾名思义,该流只能处理字符,但处理字符速度很快
- 字节流:可以处理所有以bit为单位储存的文件,也就是说可以处理所有的文件,但是在处理字符上的速度不如字符流
读写文本文件
1.字节输入流 InputStream 类
InputStream 作用:将文件中的数据输入内部存储器(简称内存)中,它提供了一系列和读取数据有关的方法,常用方法如下表所示:
方法名称 | 说明 |
| 读取一个字节数据 |
| 将数据读取到字节数组中 |
| 从输入流中读取最多 len 长度的字节,保存到字节数组 b 中,保存的位置从 off 开始 |
| 关闭输入流 |
| 返回输入流读取的估计字节数 |
2.字节输出流 OutputStream 类
字节输出流 OutputStream 类的作用是把内存中的数据输出到文件中,它提供了一些列向文件中写数据的方法,如下表所示:
方法名称 | 说明 |
| 写入一字节数据 |
| 写入数组 buf 的所有字节 |
| 将字节数组中从 off 位置开始,长度为 len 的字节数据输出到输出流中 |
| 关闭输出流 |
注意:close
方法是必须要有的
close的作用:把当前对象从指向文件的地方断开
以前的对象没有考虑过回收问题,现在考虑是因为:InputSteam 和 OutputSteam 对象不单单是纯内存资源,涉及到一部分物理资源。
而垃圾回收器只能回收纯内存资源,无法回收物理资源,所以需要程序员手动释放资源。
close 方法:就是把对象从跟当前操作系统资源互相连接的地方断开。
字节输出流FileOutputStream类
FileOutputStream构造方法作用详解
//FileOutputStream 构造方法:
//1.该方法每次write 都是从文件开始部分写入
//即每次都清空txt内的内容,再重新写入
FileOutputStream fos = new FileOutputSteam(file);
//2.该方法boolean=true的时候,从文件末尾进行拼接写入
//否则与1.一样,即1.是默认boolean=false
FileOutputStream fos = new FileOutputSteam(file,boolean);
File file = new File("D:\\a.txt");
//上句代码执行之后,系统中并不会创建 D:\\a.txt 文件
//而是由下一句中的 2 步骤实现的
FileOutputStream fos = new FileOutputSteam(file);
//该构造器做了3步:
//1:把对象在内存中创建出来,把路径赋值给对象
//2:把这个路径所对应的文件创建出来
//3:把当前对象指向该文件,等着输出
//其中如果2中的文件已经存在,则跳过2直接执行3
FileOutputStream常用方法
方法名称 | 说明 |
| 一次写一个字节,写出到输出流所指向的文件里面 |
| 一次写一个字节数组,写出到输出流所指向的文件里面 |
| 一次写一个字节数组的一部分,写出到输出流所指向的文件里面。从 offset 位置开始(0是第一个),写 length 长度 |
FileOutputSteam向txt中写内容
public static void main(String[] args) throws IOException {
File file = new File("D:\\a.txt");
FileOutputStream fos = new FileOutputStream(file);
//97并不是数字,而是ASCII表中的97-->a
fos.write(97);
//"\r\n"换行
fos.write("\r\n".getBytes());
//fos.write()方法内不可以传String字符串
//所以我们将String字符串转成字节数组即可
fos.write("你瞅啥?".getBytes());
fos.close();
}
//文件打开结果是:
//a
//你瞅啥?
换行在不同操作系统中的形式:
- Windows:
\r\n
- Linux:
\n
- MAC:
\r
所以在不同的系统中我们如何操作呢?这时我们可以动态获取换行符
//动态获取换行符:我们不需要知道操作系统就可以动态换行
String sep = System.getProperty("line.separator");
System.out.println("a" + sep + "b");
//输出结果:
//a
//b
字节输入流 FileInputStream 类
FileInputStream常用方法:
方法名称 | 说明 |
| 每次读取一个字节,并把读取到的字节作为返回值,如果读到文件末尾,则返回-1 注意:此种方法每次去读取一个字节,而汉字占两个字节,所以处理中文尽量不要用这个流去直接读取打印 |
| 每次读取当前参数中字节数组的最大容量,如果一次读不满,则读取一次结束,返回值是读到的字节总数,如果读到文件末尾 返回-1。 注意:此处读取到的字节数组,不要把每个字节单独拿出来处理,因为中文单独处理有问题。可以把字节数组转换成字符串,转换的长度就是当前读到的字节总数。 |
FileInputStream简单使用:
使用 FileInputStream 读取纯英文txt文件:
public static void main(String[] args) throws IOException {
File file = new File("D:\\a.txt");
FileInputStream fis = new FileInputStream(file);
int ch = 0;
while ((ch = fis.read()) != -1) {
System.out.print((char) ch);
}
}
注意:上述方法并不适用于读取汉字,因为1个汉字占2字节,我们是1个1个字节读取的,如果直接控制台输出的话会有乱码。
FileInputStream和FileOutputStream拷贝文件
注意:不能对一个同一个文件同时进行读写操作!!!!!
从 FileOutputStream 到 FileIntputStream 其实就是复制一个文件的过程,将文件读取到 FileIntputStream 中,后输出到 FileOutputStream 也就是相当于输出到了硬盘的文件中。
问题:字节数组该声明多大合适呢?
情况1:文件不大的时候,按照文件可读取的字节数声明数组:
使用fis.available()
获取当前输入流所指向的文件的最大可读取的字节总数。
情况2:文件比较大的时候:
- 一次读一个字节 多次读取
一旦读取的不是一个纯文本文件,那么直接打印出来的东西是看不懂的,也不是用来直接打印的,需要输出流配合输出到本地操作系统其它地方,依旧按照原来格式保存
这是文件拷贝,一次拷贝一个字节,效率非常低下。 - 一次读一个字节数组 多次读取
每次读取完写出去的时候要注意写字节数组一部分,因为字节数组的声明的大小到最后一次需要写读取到的字节总数,而不是整个字节数组。
通过此种方式拷贝文件效率比一个一个字节要高很多。
情况1代码示例:
public static void main(String[] args) throws IOException {
File file = new File("D:\\a.txt");
FileInputStream fis = new FileInputStream(file);
byte[] bs = new byte[fis.available()];
int total = fis.read(bs);
System.out.println(new String(bs, 0, total));
fis.close();
}
情况2的一次读一个字节,效率低:
public static void main(String[] args) throws IOException {
File file1 = new File("D:\\a.txt");
File file2 = new File("D:\\b.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
int ch = 0;
while ((ch = fis.read()) != -1) {
fos.write(ch);
}
fis.close();
fos.close();
}
情况2的一次读一个字节数组,效率高:
public static void main(String[] args) throws IOException {
File file1 = new File("D:\\a.txt");
File file2 = new File("D:\\b.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
// 用来接收当前字节数组接受了多少个字符
int total = 0;
// 数组长度自己设,就是一次读的字节个数
// 最后一个数组所包含的字节个数会小于等于255,重要!!!
byte[] bs = new byte[255];
while ((total = fis.read(bs)) != -1) {
//所以必须用(bs,0,total)方法
//否则由于最后一个数组的大小问题
//会导致拷贝出来的文件比原文件大
fos.write(bs, 0, total);
}
fis.close();
fos.close();
}
上面这个我们可以以两个桶为例,一个桶为 FileIntputStream ,另一个桶为 FileOutputStream,如果要把一个桶里的水转移到另一个桶中,我们首先需要一个水瓢,一次次的舀水才能完成我们的需求。
public static void main(String[] args) throws IOException {
File fil1 = new File("D:/111.pdf");
File fil2 = new File("D:/222.pdf");
// 一个叫输入流的桶,装满了一桶叫做D:/111.pdf文件的水
FileInputStream fis = new FileInputStream(fil1);
// 一个叫输出流的空桶,但想装满叫做"D:/222.pdf"文件的水
FileOutputStream fos = new FileOutputStream(fil2);
// 叫做buf的水瓢,一次装521个字节
byte[] buf = new byte[521];
// 用来测量每次水瓢装了多少字节
int len = -1;
// 一次次的用水瓢在输入流的桶里舀水,并用len测舀了多少水,
//当len等于-1意味着水舀光了,该结束舀水了。
while ((len = fis.read(buf)) != -1) {
// 一次次把水瓢里的水放到了输出流的桶里
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
fos.close();
}
其实这种方法可以针对于很多的输入流和输出流。
从输入流到字符串
其实这个和上一种很类似,只不过换了种实现方式。
直接上代码:
public static void main(String[] args) throws IOException {
File file = new File("D:/123.txt");
// 同样是叫做输入流的桶
FileInputStream fis = new FileInputStream(file);
// 把输出流的桶换成了StringBuffer用来储存字符串
// 其实也可以直接用String,但是StringBuffer速度更快。
StringBuffer sb = new StringBuffer();
// 水瓢没变
byte[] buf = new byte[256];
// 测水瓢舀了多少水没变
int len = -1;
// 和上面的原理基本一样,只不过换了个水瓢而已
while ((len = fis.read(buf)) != -1) {
// new String(buf, 0, buf.length)是将
//buf里面的内容转换为字符串
sb.append(new String(buf, 0, buf.length));
}
System.out.println(sb.toString());
}
文件切割
将一个mp3文件,切割成每个1m 大小的mp3文件,代码如下:
public class FileCut {
public static void main(String[] args) {
cut(new File("D:\\a.mp3"));
}
private static void cut(File file) {
try {
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = null;
// 切割成每个1m大小
byte[] bs = new byte[1024 * 1024];
int total = -1;
int num = 0;
while ((total = fis.read(bs)) != -1) {
fos = new FileOutputStream("D:\\" + (++num) + ".mp3");
fos.write(bs, 0, total);
fos.close();
}
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件合并
将上面切割好的文件合并成一个mp3文件,我的切割出来是8个文件,名字分别为:
1.mp3、2.mp3、…、8.mp3
代码如下:
public class FileConcat {
public static void main(String[] args) {
fileConcat("D:\\newConcat.mp3");
}
private static void fileConcat(String path) {
try {
FileOutputStream fos = new FileOutputStream(path);
// 上边分割的每个文件大小就是1m
byte[] bs = new byte[1024 * 1024];
int total = -1;
for (int i = 0; i < 8; i++) {
FileInputStream fis = new FileInputStream("D:\\" + (i + 1) + ".mp3");
while ((total = fis.read(bs)) != -1) {
fos.write(bs, 0, total);
}
fis.close();
}
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件加密
其实通过文件的切割和合并我们就可以做到文件加密了。
比如:
1.将文件切割成 n 个部分,每个部分固定大小。
2.在每个切割好的文件末尾加上点没用的字节。
3.将修改后的文件合并到一起,就加密成功了。
4.如果解密的话,就反过来操作:按固定大小+添加没用字节大小,来切割加密后的文件;每个文件末尾删除你添加的没用的字节;再合并到一起就变成原来的文件了。
缓冲字节流
BufferedInputStream
BufferedOutputStream
两者都是从 FileInputStream
发展过来的,将 FileInputStream
和 FileOutputStream
(低效字节流),转化为高效字节流。
不再是读一个字节或者一个字节数组,就直接写到文件中,反复读写。
而是将一个字节或者一个字节数组,一直读,直到缓冲字节流被刷新flush
,或者关闭close
,才向文件中写入数据。
如果不flush
和close
,就不向文件写数据。
如果写了close
则不需要写flush
了,close
默认执行一次flush
。
具体代码跟FileInputStream, FileOutputStream
基本一样,只是根据低效字节流进行改进。
具体代码如下方:4种字节流拷贝文件的方法的效率对比
4种字节流拷贝文件的方法的效率对比
- 一次一个字节
- 一次一个字节数组
- 字节缓冲流(高效),一次一个字节
- 字节缓冲流(高效),一次一个字节数组
测试代码如下:
//测试文件是一个9m大小的mp3文件
//
//低效字节流(FileInputStream,FileOutputStream):
// 1.一次拷贝一个字节
// 耗时:30096ms
// 2.一次拷贝一个字节数组
// 耗时:355ms
//高效字节流(BufferedInputStream,BufferedOutputStream):
// 3.一次拷贝一个字节
// 耗时:290ms
// 4.一次拷贝一个字节数组
// 耗时:35ms
public class DemoFourIOStream {
public static void main(String[] args) {
long start = System.currentTimeMillis();
read1("D:\\test.mp3", "D:\\read1.mp3");
read2("D:\\test.mp3", "D:\\read2.mp3");
read3("D:\\test.mp3", "D:\\read3.mp3");
read4("D:\\test.mp3", "D:\\read4.mp3");
long end = System.currentTimeMillis();
System.out.println("耗时为:" + (end - start) + "ms");
}
private static void read1(String srcPath, String destPath) {
try {
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
int ch = -1;
while ((ch = fis.read()) != -1) {
fos.write(ch);
}
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void read2(String srcPath, String destPath) {
try {
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
byte[] bs = new byte[100];
int total = -1;
while ((total = fis.read(bs)) != -1) {
fos.write(bs, 0, total);
}
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void read3(String srcPath, String destPath) {
try {
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int ch = -1;
while ((ch = bis.read()) != -1) {
bos.write(ch);
}
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void read4(String srcPath, String destPath) {
try {
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bs = new byte[100];
int total = -1;
while ((total = bis.read(bs)) != -1) {
bos.write(bs, 0, total);
}
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
效率结论:
高效>低效
字节数组>单个字节
所以最快的是:高效字节数组
二进制文件的读写
暂时省略
序列化和反序列化
对对象进行序列化及反序列化
使用工具:ObjectOutputStream,ObjectInputStream
介绍:将对象以文件的形式保存在硬盘中,使之能更方便的传输。
条件:必须实现Serializable接口(实现了这个接口,但并不需要重写任何方法)
例子1:
package file_io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class DemoObject implements Serializable {
int date = 23;
}
public class IoTest {
public static void main(String[] args) throws Exception {
// 建立对象输出流准备向文件中写入对象
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(new File("D:/123.obj")));
// 向文件中写入新建立的对象
oos.writeObject(new DemoObject());
// 输出流记得要flush
oos.flush();
// 建立对象输入流准备在文件中读出刚写入的对象
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(new File("D:/123.obj")));
// 建立一个新对象用于保存刚刚读出的对象
DemoObject newObject = (DemoObject) ois.readObject();
// 输出这个对象
System.out.println(newObject.date);
}
}