目录
使用 FileReader 与 FileWriter 实现文本文件的复制
使用 BufferedReader 与 BufferedWriter 实现文本文件的复制
DataInputStream 和 DataOutputStream 的使用
ObjectInputStream/ObjectOutputStream 的使用
使用 InputStreamReader 接收用户的输入,并输出到控制台
文件字节流
FileInputStream 通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文 本文件等)。Java 也提供了 FileReader 专门读取文本文件。
FileOutputStream 通过字节的方式写数据到文件中,适合所有类型的文件。Java 也提 供了 FileWriter 专门写入文本文件。
将字符串/字节数组的内容写入到文件中
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFileOutputStream {
public static void main(String[ ] args) {
FileOutputStream fos = null;
String string = "欢迎学习Java!";
try {
// true 表示内容会追加到文件末尾;false 表示重写整个文件内容。
fos = new FileOutputStream("d:/a.txt", true);
//该方法是直接将一个字节数组写入文件中; 而 write(int n)是写入一个字节
fos.write(string.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
用到一个 write 方法:void write(byte[ ] b),该方法不再一个字节一个字节地写入,而是直接写入一个字节数组;另外其还有一个重载的方法:void write(byte[ ] b, int off, int length),这个方法也是写入一个字节数组,但是我们程序员可以指定从字节数组 的哪个位置开始写入,写入的长度是多少。
利用文件流实现文件的复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFileCopy {
public static void main(String[ ] args) {
//将 a.txt 内容拷贝到 b.txt
copyFile("d:/a.txt", "d:/b.txt");
}
/**
* 将 src 文件的内容拷贝到 dec 文件
* @param src 源文件
* @param dec 目标文件
*/
static void copyFile(String src, String dec) {
FileInputStream fis = null;
FileOutputStream fos = null;
//为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
byte[ ] buffer = new byte[1024];
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//边读边写
//temp 指的是本次读取的真实长度,temp 等于-1 时表示读取结束
while ((temp = fis.read(buffer)) != -1) {
/*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
*如果使用 fos.write(buffer)方法,那么写入的长度将会是 1024,即缓存
*数组的长度*/
fos.write(buffer, 0, temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//两个流需要分别关闭
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在使用文件字节流时,我们需要注意以下两点:
- 为了减少对硬盘的读写次数,ᨀ高效率,通常设置缓存数组。相应地,读取时使用的方 法为:read(byte[ ] b);写入时的方法为:write(byte[ ] b, int off, int length)。
- 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流 无法关闭的情况。
文件字符流
文件字节流可以处理所有的文件,但是字节流不能很好的处理 Unicode 字 符,经常会出现“乱码”现象。所以,我们处理文本文件,一般可以使用文件字符流,它以字符为单位进行操作。
使用 FileReader 与 FileWriter 实现文本文件的复制
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TestFileCopy2 {
public static void main(String[ ] args) {
// 写法和使用 Stream 基本一样。只不过,读取时是读取的字符。
FileReader fr = null;
FileWriter fw = null;
int len = 0;
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//为了提高效率,创建缓冲用的字符数组
char[ ] buffer = new char[1024];
//边读边写
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲字节流
Java 缓冲流本身并不具有 IO 流的读取与写入功能,只是在别的流(节点流或其他处理 流)上加上缓冲功能ᨀ高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包 装流)。
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就 能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷 新时再一次性的读取到程序或写入目的地。
因此,缓冲流还是很重要的,我们在 IO 操作时记得加上缓冲流来ᨀ升性能。
BufferedInputStream 和 BufferedOutputStream 这两个流是缓冲字节流,通过内部缓存数 组来提高操作流的效率。
使用缓冲流实现文件的高效率复制
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestBufferedFileCopy1 {
public static void main(String[ ] args) {
// 使用缓冲字节流实现复制
long time1 = System.currentTimeMillis();
copyFile1("D:/电影/你的电影.mkv", "D:/电影/你的电影1.mkv");
long time2 = System.currentTimeMillis();
System.out.println("缓冲字节流花费的时间为:" + (time2 - time1));
// 使用普通字节流实现复制
long time3 = System.currentTimeMillis();
copyFile2("D:/电影/你的电影.mkv", "D:/电影/你的电影2.mkv");
long time4 = System.currentTimeMillis();
System.out.println("普通字节流花费的时间为:" + (time4 - time3));
}
/**
* 缓冲字节流实现的文件复制的方法
* @param src
* @param dec
*/
static void copyFile1(String src, String dec) {
FileInputStream fis = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
//缓存区的大小(缓存数组的长度)默认是 8192,也可以自己指定大小
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
while ((temp = bis.read()) != -1) {
bos.write(temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意:增加处理流后,注意流的关闭顺序!“后开的先关闭!”
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 普通节流实现的文件复制的方法
* @param src
* @param dec
*/
static void copyFile2(String src, String dec) {
FileInputStream fis = null;
FileOutputStream fos = null;
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
while ((temp = fis.read()) != -1) {
fos.write(temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
copyFile2 好几分钟都没复制完,我强制停止了
在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
缓存区的大小默认是 8192 字节,也可以使用其它的构造方法自己指定大小。
缓冲字符流
BufferedReader/BufferedWriter 增加了缓存机制,大大ᨀ高了读写文本文件的效率,同时, 提供了更方便的按行读取的方法:readLine(); 处理文本时,我们一般可以使用缓冲字符流。
使用 BufferedReader 与 BufferedWriter 实现文本文件的复制
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TestBufferedFileCopy2 {
public static void main(String[] args) {
// 注:处理文本文件时,实际开发中可以用如下写法,简单高效!!
FileReader fr = null;
FileWriter fw = null;
BufferedReader br = null;
BufferedWriter bw = null;
String tempString = "";
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//使用缓冲字符流进行包装
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//BufferedReader 提供了更方便的 readLine()方法,直接按行读取文本
//br.readLine()方法的返回值是一个字符串对象,即文本中的一行内容
while ((tempString = br.readLine()) != null) {
//将读取的一行字符串写入文件中
bw.write(tempString);
//下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
readLine()方法是 BufferedReader 特有的方法,可以对文本文件进行更加方便的读取操作。
写入一行后要记得使用 newLine()方法换行。
字节数组流
ByteArrayInputStream 和 ByteArrayOutputStream 经常用在需要流和数组之间转化的情 况!
说白了,FileInputStream 是把文件当做数据源。ByteArrayInputStream 则是把内存中的” 某个字节数组对象”当做数据源。
简单测试 ByteArrayInputStream 的使用
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class TestByteArray {
public static void main(String[ ] args) {
//将字符串转变成字节数组
byte[ ] b = "abcdefg".getBytes();
test(b);
}
public static void test(byte[ ] b) {
ByteArrayInputStream bais = null;
StringBuilder sb = new StringBuilder();
int temp = 0;
//用于保存读取的字节数
int num = 0;
try {
//该构造方法的参数是一个字节数组,这个字节数组就是数据源
bais = new ByteArrayInputStream(b);
while ((temp = bais.read()) != -1) {
sb.append((char) temp);
num++;
}
System.out.println(sb);
System.out.println("读取的字节数:" + num);
} finally {
try {
if (bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
数据流
数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式 从底层输入输出流中操作 Java 基本数据类型与字符串类型。
DataInputStream 和 DataOutputStream ᨀ供了可以存取与机器无关的所有 Java 基础类型 数据(如:int、double、String 等)的方法。
DataInputStream 和 DataOutputStream 是处理流,可以对其他节点流或处理流进行包装, 增加一些更灵活、更高效的功能。
DataInputStream 和 DataOutputStream 的使用
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestDataStream {
public static void main(String[] args) {
DataOutputStream dos = null;
DataInputStream dis = null;
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream("D:/data.txt");
fis = new FileInputStream("D:/data.txt");
//使用数据流对缓冲流进行包装,新增缓冲功能
dos = new DataOutputStream(new BufferedOutputStream(fos));
dis = new DataInputStream(new BufferedInputStream(fis));
//将如下数据写入到文件中
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("心目中的Java大学");
//手动刷新缓冲区:将流中数据写入到文件中
dos.flush();
//直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
System.out.println("char: " + dis.readChar());
System.out.println("int: " + dis.readInt());
System.out.println("double: " + dis.readDouble());
System.out.println("boolean: " + dis.readBoolean());
System.out.println("String: " + dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(dos!=null){
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(dis!=null){
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用数据流时,读取的顺序一定要与写入的顺序一致,否则不能正确读取数据。
对象流
数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象 (字符串除外),如果要对某个对象进行读写操作,我们需要学习一对新的处理流: ObjectInputStream/ObjectOutputStream。
ObjectInputStream/ObjectOutputStream 是以“对象”为数据源,但是必须将传输的对象进 行序列化与反序列化操作。
ObjectInputStream/ObjectOutputStream 的使用
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Date;
public class TestObjectStream {
public static void main(String[] args) {
write();
read();
}
/**
* 使用对象输出流将数据写入文件
*/
public static void write(){
// 创建 Object 输出流,并包装缓冲流,增加缓冲功能
OutputStream os = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
os = new FileOutputStream(new File("d:/bjsxt.txt"));
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
// 使用 Object 输出流
//对象流也可以对基本数据类型进行读写操作
oos.writeInt(12);
oos.writeDouble(3.14);
oos.writeChar('A');
oos.writeBoolean(true);
oos.writeUTF("哪都通大学");
//对象流能够对对象数据类型进行读写操作
//Date 是系统ᨀ供的类,已经实现了序列化接口
//如果是自定义类,则需要自己实现序列化接口
oos.writeObject(new Date());
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭输出流
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 使用对象输入流将数据读入程序
*/
public static void read() {
// 创建 Object 输入流
InputStream is = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try {
is = new FileInputStream(new File("d:/bjsxt.txt"));
bis = new BufferedInputStream(is);
ois = new ObjectInputStream(bis);
// 使用 Object 输入流按照写入顺序读取
System.out.println(ois.readInt());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭 Object 输入流
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
转换流
InputStreamReader/OutputStreamWriter 用来实现将字节流转化成字符流。比如,如下场 景:
System.in 是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必 须用到缓冲字符流 BufferedReader 特有的方法 readLine(),但是经过观察会发现在创建 BufferedReader 的 构 造 方 法 的 参 数 必 须 是 一 个 Reader 对 象 , 这 时 候 我 们 的 转 换 流 InputStreamReader 就派上用场了。
而 System.out 也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将 读取的一行字符串直接显示到控制台,就需要用到字符流的 write(String str)方法,所以我们 要使用 OutputStreamWriter 将字节流转化为字符流。
使用 InputStreamReader 接收用户的输入,并输出到控制台
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TestConvertStream {
public static void main(String[] args) {
// 创建字符输入和输出流:使用转换流将字节流转换成字符流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 使用字符输入和输出流
String str = br.readLine();
// 一直读取,直到用户输入了 exit 为止
while (!"exit".equals(str)) {
// 写到控制台
bw.write(str);
bw.newLine();// 写一行后换行
bw.flush();// 手动刷新
// 再读一行
str = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭字符输入和输出流
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
随意访问文件流
RandomAccessFile 可以实现两个作用:
- 实现对一个文件做读和写的操作。
- 可以访问文件的任意位置。不像其他流只能按照先后顺序读取。
在开发某些客户端软件时,经常用到这个功能强大可以”任意操作文件内容”的类。比如, 软件的使用次数和使用日期,可以通过本类访问文件中保存次数和日期的地方进行比对和修 改。 Java 很少开发客户端软件,所以在 Java 开发中这个类用的相对较少。
学习这个流我们需掌握三个核心方法:
- RandomAccessFile(String name, String mode) name 用来确定文件; mode 取 r(读)或 rw(可读写),通过 mode 可以确定流对文件的访问权限。
- seek(long a) 用来定位流对象读写文件的位置,a 确定读写位置距离文件开头 的字节个数。
- getFilePointer() 获得流的当前读写位置。
RandomAccessFile 的使用
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestRandomStream {
public static void main(String[] args) {
RandomAccessFile raf = null;
try {
// 将若干数据写入到 a.txt 文件
int[ ] data = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
raf = new RandomAccessFile("d:/a.txt", "rw");
for (int i = 0; i < data.length; i++) {
raf.writeInt(data[i]);
}
// 直接从 a.txt 中读取数据,位置为从 36 字节开始。
raf.seek(4);
System.out.println(raf.readInt()); // 读取 4 个字节(int 为 4 个字节)
// 直接从 a.txt 中读取数据,隔一个读一个数据
for (int i = 0; i < 10; i += 2) {
raf.seek(i * 4);
System.out.print(raf.readInt() + "\t");
}
System.out.println(); // 换行
// 在 8 字节处插入一个新数据 45,替换以前的数据 30
raf.seek(8);
raf.writeInt(45);
for (int i = 0; i < 10; i++) {
raf.seek(i * 4);
System.out.print(raf.readInt() + "\t");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}