Java 基本的IO流
目录
FileInputStream & FileOutputStream
BufferedInputStream & BufferedOutputStream
BufferedReader & BufferedWriter
ByteArrayInputStream & ByteArrayOutputStream
转换流->InputStreamReader & OutputStreamWriter
包装流->DataInputStream & DataOutputStream
对象序列化->ObjectInputStream & ObjectOutputStream
流的分类
按照流向来分类:输入流、输出流,针对于内存来说。
按照编码格式来分类:字节流、字符流,字节对应byte(1字节8bit),字符对应char(2字节16bit)。
按照等级来分类:节点流、处理流,节点流是低级流(IO设备、网络之间),处理流是高级流(封装了节点流)。
4个抽象基类
Java中的IO流由4个抽象基类派生,分别是:
输入流:InputStream(字节流)、Reader(字符流)。
输出流:OutputStream(字节流)、Writer(字符流)。
对于输入流,InputStream和Reader抽象类都定义了5个方法:
int read()
int read(byte[] / char[])
int read(byte[] / char[], int offset, int length)
void reset() 将文件指针定位到初始状态
void skip(long n) 从文件指针当前位置,向文件末尾移动n个字节或者字符。
对于输出流,OutputStream和Writer抽象类也定义了3个方法:
void write(int c),c即可是byte对应的ASCII,也可是字符的unicode码。
void write(byte[] / char[])
void write(byte[] / char[], int offset, int length)
Writer抽象类多提供的两个方法:
void write(String str)
void write(String str, int offset, int length)
输入输出流的所有类
下表(截图来自《疯狂Java 第三版》)

看到上面这么多的类,其实,很有规律,比如FileInputStream、FileReader、FileOutputStream、FileWriter为例。
File类的使用
Java提供了File类进行一些文件操作,包括文件信息的查看,文件的操作。
import java.io.File;
import java.io.IOException;
public class UseFile {
public static void useSeparator() {
System.out.println(File.separator);
System.out.println(File.pathSeparator);
// 推荐使用格式
String path = "E:/a/b/c.txt";
System.out.println(path);
}
public static void useFileBasic() {
String path = "E:/simpleEclipseWordSpace/IO/data.txt";
File file = new File(path);
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
file = new File("E:/simpleEclipseWordSpace/", "IO/data.txt");
file = new File("E:/simpleEclipseWordSpace/IO", "data.txt");
}
public static void useFileAPI() {
String path = "E:/simpleEclipseWordSpace/IO/data.txt";
File file = new File(path);
// 名称信息
System.out.println("getName -> " + file.getName());
System.out.println("getPath -> " + file.getPath());
System.out.println("getAbsolutePath -> " + file.getAbsolutePath());
System.out.println("getParent -> " + file.getParent());
// 判断状态
System.out.println("exists -> " + file.exists());
System.out.println("isFile -> " + file.isFile());
System.out.println("isDirectory -> " + file.isDirectory());
// 文件信息
System.out.println("length -> " + file.length()); // 单位字节,length为0,可能是文件不存在,也可能是该路径表示的是文件夹
System.out.println("canRead -> " + file.canRead());
System.out.println("canWrite -> " + file.canWrite());
// 文件操作
File newFile = new File("test.txt");
boolean created = false;
try {
// 不存在则创建成功
created = newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("success -> " + created);
// 删除文件,已经存在的路径
boolean deleted = false;
deleted = newFile.delete();
System.out.println("deleted -> " + deleted);
}
public static void createDirectory() {
// 使用mkdir和mkdirs来创建目录,区别如下:
// 使用mkdir来创建目录时,如果path中任意一个目录不存在,则创建失败
// 使用mkdirs来创建目录时,如果path中的目录不存在,则会创建目录
String path = "E:/demo/test";
File directory = new File(path);
directory.mkdirs();
directory.mkdir();
}
public static void directoryOperation() {
String path = "E:/eclipseWorkspace";
File dir = new File(path);
// 使用list()返回下一级的文件名或者目录名
String[] subNames = dir.list();
for (String s : subNames) {
System.out.println(s);
}
// 使用list()返回下一级的文件或者目录的File对象
File[] subFiles = dir.listFiles();
for (File f : subFiles) {
System.out.println(f.getAbsolutePath());
}
// 列出所有盘符
File[] roots = dir.listRoots();
for (File f : roots) {
System.out.println(f.getAbsolutePath());
}
}
// 扫描目录
public static void scanDirectory(File src) {
System.out.println(src.getAbsolutePath());
if (null == src || !src.exists()) {
return;
} else if (src.isDirectory()){
for (File s : src.listFiles()) {
scanDirectory(s);
}
}
}
public static void main(String[] args) {
try {
System.out.println("aaa");
return;
} catch (Exception e) {
e.getMessage();
} finally {
System.out.println("hello");
}
}
}
FileOutputStream 与 FileInputStream
对于FileInputStream和FileOutputStream,需要注意:
1、都是对文件进行操作的,创建InputStream对象的时候,可以传入File对象,也可以传入文件的路径path。
2、数据的格式是二进制字节码(byte)格式。
3、使用输出流的时候, 一定要flush,不要等到关闭输出流的时候,让他自动刷新缓冲区,一定要手动flush,防止数据滞留。
@Test
public void testFileInputStream() throws IOException {
InputStream inputStream = new FileInputStream(new File("pom.xml"));
// 一次读一个字节,同时指针后移
int data = inputStream.read();
// 创建缓冲数组
byte[] buf = new byte[10];
// 一次读取多个数组长度的内容,返回读取的长度
int length = inputStream.read(buf);
// 一次读取多个字节
length = inputStream.read(buf, 0, 5);
inputStream.close();
}
@Test
public void testFileOutputStream() throws IOException {
// 默认清空写,可以加第二个参数true表示append写
OutputStream outputStream = new FileOutputStream("pom.txt");
// 写入一个字节(注意不是写入99,而是它对应的ASCII码
outputStream.write(99);
// 将字节数组中的数据写入
byte[] data = "hello world".getBytes();
outputStream.write(data);
// 将字节数组中的一部分写入文件中
outputStream.write("123456789".getBytes(), 0, 5);
outputStream.flush();
outputStream.close();
}
FileReader 和 FileWriter类
FileReader和FileWriter分别是Reader和Writer的子类。在操作数据的时候,是以字符为单位,而不是字节。所以,如果使用FileReader和FileWriter来操作二进制文件,可能会因为编码的不同,导致文件出现错误(导致文件内容修改)。
使用输出流的时候, 一定要flush,不要等到关闭输出流的时候,让他自动刷新缓冲区,一定要手动flush,防止数据滞留。
@Test
public void testFileReader() throws IOException {
FileReader reader = new FileReader("pom.xml");
// 读取一个字符
int c = reader.read();
System.out.println((char) c);
char[] buf = new char[10];
// 将数据读出后存到字符数组中,返回读取的长度
int length = reader.read(buf);
// 读取指定长度的数据(字符),存放到字符数组中,返回读取的长度
length = reader.read(buf, 0, 5);
System.out.println(new String(buf));
reader.close();
}
@Test
public void testFileWriter() throws IOException {
FileWriter writer = new FileWriter("pom.txt");
// 写入一个字符
writer.write('中');
char[] data = "hello".toCharArray();
// 将字符数组内容全部写入
writer.write(data);
// 将字符数组中的部分内容写入
writer.write(data, 0, 3);
// 可以直接写String
writer.write("hello");
// 利用append可以多次追加
writer.append("world").append("ok");
writer.flush();
writer.close();
}
BufferedInputStream 和 BufferedOutputStream
前面我们使用文件输入输出流,基本都是这样的:
1、一次读一个字节(字符),或者一次写一个字节(字符)
2、一次读几个字节(字符), 或者一次写几个字节(字符)
这样效率很低,为了提高效率,可以使用带有缓冲的输出输出流,当数据长度到达一定长度的时候,再进行一次写入或者写出,这样可以提高效率。
@Test
public void testBufferedInputStream() throws IOException {
InputStream inputStream = new FileInputStream("pom.xml");
BufferedInputStream bis = new BufferedInputStream(inputStream);
byte[] buf = new byte[1024 * 10];
// 就像使用FileInputStream一样使用
int length = -1;
while ((length = bis.read(buf)) != -1) {
System.out.print(new String(buf));
}
// 如果使用了缓冲流,那么不推荐手动关闭节点流
//inputStream.close();
// 直接关闭缓冲流,节点流也会被关闭
bis.close();
}
@Test
public void testBufferedOutputStream() throws IOException {
OutputStream outputStream = new FileOutputStream("pom.xml", true);
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
// 就像使用FileOutputStream一样
bos.write("hello".getBytes());
// 直接使用缓冲流的flush即可
bos.flush();
// 如果使用了缓冲流,那么不推荐手动关闭节点流
// outputStream.close();
bos.close();
}
BufferedReader 和 BufferedWriter
@Test
public void testBufferedReader() throws IOException {
FileReader reader = new FileReader("pom.xml");
BufferedReader bufferedReader = new BufferedReader(reader);
// 使用方式和FileReader方式一样
String s = bufferedReader.readLine();
System.out.println(s);
// 如果使用了缓冲流,那么不推荐手动关闭节点流
// reader.close();
bufferedReader.close();
}
@Test
public void testBufferedWriter() throws IOException {
FileWriter writer = new FileWriter("pom.txt",true);
BufferedWriter bufferedWriter = new BufferedWriter(writer);
// 插入新的一行(相当于一个换行)
bufferedWriter.newLine();
bufferedWriter.write("hello");
bufferedWriter.append(" world").append("ok");
bufferedWriter.flush();
// 如果使用了缓冲流,那么不推荐手动关闭节点流
// writer.close();
bufferedWriter.close();
}
ByteArrayInputStream 和 ByteArrayOutputStream
之前,我们操作的数据都是文件中的,文件存储在硬盘中,io效率比较低(硬盘的读取和写入相对于内存来说,很低)。Java提供了ByteArrayInputStream 和 ByteArrayOutputStream,可以让我们操作内存中的数据,如果我们频繁地从一个硬盘中读取一个文件数据,就会导致性能问题; 为此,我们可以将硬盘中的文件读取一次,读到内存中,然后在内容中反复使用,使用的就是上面的ByteArrayInputStream 和 ByteArrayOutputStream。因为内存中的数据都是字节二进制格式,而不是字符格式,所以,只有ByteArrayInputStream和ByteArrarOutputStream,而没有ByteArrayReader 和 ByteArrayWriter。
注意点:
1、ByteArrayInputStream 和 ByteArrayOutputStream操作的数据都是字节格式,可以通过getBytes()来转为字节数组。
2、他们操作的数据都是来自内存的,所以,不需要关闭,即不需要调用close()。
@Test
public void testByteArrayInputStream() throws IOException {
byte[] data = "hello world".getBytes();
// 对于ByteArrayInputStream来说,数据的来源是“内存”,也就是上面的data
InputStream inputStream = new ByteArrayInputStream(data);
// 读取操作和 FileInputStream的用法相同
byte[] buf = new byte[5];
int length = inputStream.read(buf);
System.out.println(new String(buf));
// ByteArrayInputStream不需要关闭
}
@Test
public void testByteArrayOutputStream() throws IOException {
// ByteArrayOutputStream的dest是内存,所以不需要指定dest
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 要写入内存的数据
byte[] data = "hello world".getBytes();
baos.write(data);
baos.flush();
// 获取写入内存的数据
byte[] res = baos.toByteArray();
System.out.println(new String(res));
}
InputStreamReader 和 OutputStreamWriter
前面几个输入输出流,大抵都是这样的,从源点读入数据,可以使用字节流,也可以使用字符流,但是一旦将数据读入到内存,操作数据时就会有所不同:
1、如果读入的时候使用的是字符流,那么后面就要使用字符流方式来操作内存中数据
2、如果读入的时候使用的是字节流,那么后面就要使用字节流方式来操作内存中数据
其实这样是多有不便的,比如我们从文本文件中以字符流形式读入数据,想通过字节流形式操作数据,这就比较麻烦了,因为涉及到字符编码的问题。于是Java提供了转换流,可以将转换流的工作图表示如下:
/**
* InputStreamReader继承自Reader,是指使用以字节流形式打开资源,但是却使用字符流的方式读取
*/
@Test
public void testInpuStreamReader() throws IOException {
// 以字节流的方式打开资源
FileInputStream inputStream = new FileInputStream("pom.txt");
// 创建InputStreamReader需要接受字节流对象
Reader reader = new InputStreamReader(inputStream);
// 还可以指定字符集
// InputStreamReader isr = new InputStreamReader(inputStream, "utf-8");
// 注意,每一次是读一个char,而不是一个byte
int c = reader.read();
System.out.println((char)c);
// 不用关闭节点流
reader.close();
}
/**
* OutputStreamWriter继承自Writer,是指 将 字符串(字符数组)写入到 用字节流方式打开的资源中
*/
@Test
public void testOutputStreamWriter() throws IOException {
// 资源使用字节流方式打开
OutputStream outputStream = new FileOutputStream("pom.txt", true);
// 接收一个OutputStream
Writer writer = new OutputStreamWriter(outputStream);
// 还可以指定字符集
// Writer writer = new OutputStreamWriter(outputStream, "utf-8");
writer.write("hello world");
writer.append("ok").append("fine");
writer.flush();
writer.close();
}
DataInputStream 和 DataOutputStream
读取和写入的数据是“有类型”的,所以在使用这两个数据流的时候,需要主要读取的顺序要和写入时的顺序相对应。
/**
* DataInputStream和DataOutputStream是装饰流,读写的数据是有"类型"的
* DataInputStream是以字节流的形式读取数据
* DataOutputStream是将数据以字节流形式输出
* DataInputStream和DataInputStream需要配合使用,并且两者操作的顺序需要对应
*/
@Test
public void testDataStream() throws IOException {
/*
* 写数据
*/
OutputStream outputStream = new FileOutputStream("pom.txt");
DataOutputStream das = new DataOutputStream(outputStream);
// 开始写入数据,注意数据是什么类型,就需要调用writeXxx
das.writeBoolean(true);
das.writeDouble(9.99);
das.writeUTF("hello world");
das.flush();
das.close();
/**
* 读数据
*/
InputStream inputStream = new FileInputStream("pom.txt");
DataInputStream dis = new DataInputStream(inputStream);
// 注意读数据时,调用的API要和写入时的API对应,顺序不要颠倒
boolean flag = dis.readBoolean();
double d = dis.readDouble();
String s = dis.readUTF();
System.out.println(flag); // true
System.out.println(d); // 9.99
System.out.println(s); // hello world
}
ObjectInputStream 和 ObjectOutputStream
这两个对象流和DataInputStream、DataOutputStream用法相同,但是他还有另外一个很重要的功能:实现对象的序列化。
当一个对象需要序列化的时候,这个对象所属的类,需要实现Serializable接口。如果类中某些属性不希望序列化的时候显式(想隐藏属性和属性值),可以在属性名之前加transient关键字。
/**
* ObjectOutputStream和ObjectInputStream这两个对象流,与DataOutputStream和DataInputStream使用方法相同
* 不过ObejctOutputStream和ObjectInputStream可以完成对象的序列化。
*/
@Test
public void testObjectStream() throws IOException, ClassNotFoundException {
/**
* 写入数据
*/
OutputStream outputStream = new FileOutputStream("pom.txt");
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeBoolean(true);
// 对象序列化,要进行序列化的类必须实现Serializable接口
oos.writeObject(new Person(1,"ganlixin"));
oos.flush();
oos.close();
/**
* 读取数据
*/
InputStream inputStream = new FileInputStream("pom.txt");
ObjectInputStream ois = new ObjectInputStream(inputStream);
// 读取顺序和写入时的顺序相同
boolean flag = ois.readBoolean();
Object o = ois.readObject();
if (o instanceof Person) {
System.out.println(o); //Person{id=1, name='ganlixin'}
}
}
PrintStream
import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class Use_PrintStream {
public static void main(String[] args) throws FileNotFoundException {
/**
* PrintStream 打印流,比如System.out
*/
/**
PrintStream out = System.out;
out.println("hello");
*/
// 指定输出端
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("demo.txt")));
out.println("yes");
// 可以通过System.setOut来指定输出端
System.setOut(out);
System.out.print("该内容会输出到新指定的地方");
// FileDescriptor.out;// 标准输出
}
}
RandomAccessFile类
这个类,其实和PHP中的fopen的功能差不多,指定需要操作的文件名以及操作的类型(模式)。
测试读文件
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test_RandomAccessFile_Read{
public static void main(String[] args) throws IOException {
// 注意RandomAccessFile处理文件的编码格式是字节流格式,而不是字符流
// RandomAccessFile.RandomAccessFile(String name, String mode)
RandomAccessFile raf = new RandomAccessFile("data.txt", "r");
// data.txt的内容:0123456789abcdefghijklmnopqrstuvwxyz
System.out.println(raf.length()); //35
// 返回文件内容的大小(字节),注意文件大小不等同于文件内容大小
raf.seek(3); // 定位到文件内容的第n字节出
byte[] buf = new byte[5];
raf.read(buf);
System.out.println(new String(buf));// 34567
//一次性读取一行
System.out.println(raf.readLine()); // 89abcdefghijklmnopqrstuvwxyz
}
}
测试写文件
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test {
public static void main(String[] args) throws IOException {
// RandomAccessFile.RandomAccessFile(String name, String mode)
RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
// data.txt的内容:0123456789
//RandomAccessFile.write(int)
//RandomAccessFile.write(byte b[])
//RandomAccessFile.write(byte b[], int off, int len)
raf.write("abcd".getBytes());
//raf.writeXxx
// raf.writeInt(5);
// raf.writeChar('Y');
// raf.writeChars("hello");
raf.close();
//在写文件的时候,如果没有使用seek来移动文件指针,那么会先清空文件内容,在填入内容,填入内容的格式是二进制字节流
}
}
浙公网安备 33010602011771号