IO流
IO流是实现输入与输出的基础,可以向其中写入一个字节序列的称为输出流,可以从其中读入一个字节序列的称为输入流,输入与输出的来源和目的地可以是文件,网络连接,内存块等。
IO流的分类
- 按流向可以分为输入流与输出流
- 按字节的处理方式可以分为字节流和字符流
- 按使用方法可以分为节点流和包装流
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
节点流抽象基类 | InputStream | OutputStream | Reader | Writer |
包装流抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | InputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
这看起来很复杂,但是不用着急,我们先从InputStream和OutputStream说起,并会涵盖以上大部分的类。
字节流的输入与输出
InputStream和OutputStream是抽象类,并不刻意直接使用它们,而是使用它们的子类,表格上所有的字节输入和输出流都是它们的直接或间接子类。
FileInputStream和FileOutputStream是最常见的两个子类,它们数据的来源和目的地都是磁盘中的文件,下面将以这两个类来介绍输入和输出的基本用法。
读取一个文件内容
@Test
public void FileIOTest() throws IOException {
//创建一个代表当前文件的File
File iotest = new File(".","src/test/java/com/patermenkey/io/IOTest.java");
//使用File实例化一个FileInputStream
//如果不想创建File,直接使用代表路径的字符串实例化也是可以的
FileInputStream fileInputStream = new FileInputStream(iotest);
//创建一个字节数组用于取数据
byte[] bytes = new byte[1024];
//len表示一次从其中读取字节的长度
int len=0;
StringBuffer stringBuffer = new StringBuffer();
//使用循环读取文件内容
while((len=fileInputStream.read(bytes))!=-1){
//需要将读入的字节转换成字符串以方便打印
stringBuffer.append(new String(bytes,0,len));
}
System.out.println(stringBuffer.toString());
//使用完一定要关闭流
fileInputStream.close();
//这个代码将读取当前文件的内容在控制台输出
}
InputStream实现了AutoCloseable接口因此,如果你不想手动关闭可以使用try()catch{}块来完成,不过我并不打算使用这个方法。
向一个文件输出
复制一个文件
上面的代码将一个文件的内容直接写到控制台,现在使用FileOutputStream将文件的内容写到另一个文件中:
@Test
public void FileIOTest2() throws IOException {
//创建一个代表当前文件的File
File iotest = new File(".","src/test/java/com/patermenkey/io/IOTest.java");
FileInputStream fileInputStream = new FileInputStream(iotest);
//创建一个新文件,要将当前文件的内容写到这个文件中
File newFile = new File("E:\\IOTest.java");
newFile.createNewFile();
//实例化FileOutputStream
FileOutputStream fileOutputStream = new FileOutputStream(newFile);
byte[] bytes = new byte[1024];
int len=0;
//循环输出
while ((len=fileInputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,len);
}
//关闭流
fileInputStream.close();
fileOutputStream.close();
}
执行完之后就可以在E盘下看到一个与当前文件一样的文件。
Java中的输出流很多都实现了缓冲功能,输出不是直接写到目标中的而是写入一个缓冲,以便于在以后一次性写入减少IO操作,如果需要立即输出可以使用flush()方法,如果不适用flush方法,输出将会在close()时执行。
向文件中输出字符串
现在我们要想一个文件中写入一段字符串。
@Test
public void IOTest3() throws IOException {
//实例化输出流
FileOutputStream fileOutputStream = new FileOutputStream("E:\\text.txt");
//创建一个字符串
StringBuffer stringBuffer = new StringBuffer();
stringBuffer
.append("练得身形似鹤形,")
.append(System.getProperty("line.separator"))//不同平台下的换行符不同,可以用这个方法获得与平台一致的换行符
.append("千株松下两函经。")
.append(System.getProperty("line.separator"))
.append("我来问道无余说,")
.append(System.getProperty("line.separator"))
.append("云在青天水在瓶。");
//使用字节流输出需要将字符串转换成字节数组
fileOutputStream.write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
//再次强调,一定要关闭资源
fileOutputStream.close();
}
执行完毕之后就可以打开E盘下的text.txt文件可以看到一首诗。因为写入的字符串使用UTF-8转码的,所以必须保证你的文本阅读器是以UTF-8形式解析文本的,否则会出现乱码。
字符流的输入与输出
在上面使用字节流的过程中可以发现,字节流处理的对象是字节,在计算机内部任何数据都是以二进制的字节形式存储的,因此字节流更具有一般性。
那么字符流是什么呢?字符流不过是按照某种编码将字节与字符之间进行转换,以致于输入和输出的都是字符而不是字节。
读取文件内容
@Test
public void IOTest4() throws IOException {
//创建一个代表当前文件的File
File iotest = new File(".","src/test/java/com/patermenkey/io/IOTest.java");
//这里使用FileReader而不是FileInputStream
FileReader fileReader = new FileReader(iotest);
//创建一个字符数组组用于取数据
char[] chars = new char[1024];
//len表示一次从其中读取字节的长度
int len=0;
StringBuffer stringBuffer = new StringBuffer();
//使用循环读取文件内容
while((len=fileReader.read(chars))!=-1){
stringBuffer.append(new String(chars,0,len));
}
System.out.println(stringBuffer.toString());
//使用完一定要关闭流
fileReader.close();
}
向文件中输出
@Test
public void IOTest5() throws IOException {
//实例化输出流
FileWriter fileWriter = new FileWriter("E:\\text.txt");
//创建一个字符串
StringBuffer stringBuffer = new StringBuffer();
stringBuffer
.append("练得身形似鹤形,")
.append(System.getProperty("line.separator"))//不同平台下的换行符不同,可以用这个方法获得与平台一致的换行符
.append("千株松下两函经。")
.append(System.getProperty("line.separator"))
.append("我来问道无余说,")
.append(System.getProperty("line.separator"))
.append("云在青天水在瓶。");
//使用字符流可以直接输出字符串
fileWriter.write(stringBuffer.toString());
//再次强调,一定要关闭资源
fileWriter.close();
}
可以发现,与字节流相比,并没有简单多少,只是处理的对象变成了字符数组而已。只有在数据源或目的地的是文本类型是使用字符流才有意义。否则根本就不需要使用它。
还有一个问题就是字符流采用的编码是平台默认编码,如果不能保证文本的编码格式与平台默认编码一致,那么势必会出现乱码问题。但是字符流并没有提供任何方法可以修改它的编码方式,难道为了解决这个问题要去修改平台默认编码?答案是不需要,可以是转换流InputStream和OutputStream。
转换流
转换流将字节流转换为字符流,它的构造函数的第二个参数允许指定编码方式。
使用转换流以gbk编码向文本输出一段字符串
@Test
public void IOTest6() throws IOException {
//创建一个字节输出流
FileOutputStream fileOutputStream = new FileOutputStream("E:\\text.txt");
//创建转换流,转换流的第一哥参数是一个字符流第二个参数指定字符编码
OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, "gbk");
StringBuffer stringBuffer = new StringBuffer();
stringBuffer
.append("练得身形似鹤形,")
.append(System.getProperty("line.separator"))//不同平台下的换行符不同,可以用这个方法获得与平台一致的换行符
.append("千株松下两函经。")
.append(System.getProperty("line.separator"))
.append("我来问道无余说,")
.append(System.getProperty("line.separator"))
.append("云在青天水在瓶。");
writer.write(stringBuffer.toString());
//只需要关闭转换流即可
writer.close();
//e:\\text.txt中的字符将以gbk编码
}
以gbk编码格式读取文本
@Test
public void IOTest7() throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:\\text.txt");
InputStreamReader reader = new InputStreamReader(fileInputStream, "gbk");
StringBuffer stringBuffer = new StringBuffer();
char[] chars = new char[1024];
int len=0;
while((len=reader.read(chars))!=-1){
stringBuffer.append(chars);
}
System.out.println(stringBuffer.toString());
reader.close();
}
像转换流这样本身并不代表资源而是包装一个底层的节点流以提供更方便的处理方式的流称为包装流,关闭流时只需要关闭最上层的包装流即可。
打印流
再看另一种包装流——打印流,使用打印流可以方便地输出文本。在输出文本数据时应该优先考虑使用打印流。
下面使用打印流输出一首诗:
@Test
public void IOTest9() throws FileNotFoundException, UnsupportedEncodingException {
PrintStream printStream = new PrintStream("E:\\text.txt","utf-8");
printStream.println("练得身形似鹤形,");
printStream.println("千株松下两函经。");
printStream.println("我来问道无余说,");
printStream.println("云在青天水在瓶。");
printStream.close();
}
打印流还可以打印基本数据类型地文本:
@Test
public void IOTest10() throws FileNotFoundException, UnsupportedEncodingException {
PrintStream printStream = new PrintStream("E:\\text.txt", "utf-8");
int ten=10;
long hundred=100;
double thousand=1000;
printStream.println(ten);
printStream.println(hundred);
printStream.println(thousand);
printStream.close();
}
打印流会将基本类型地数据按utf-8编码打印。
关于打印流有几点说明:
- PrintStream和PrintWriter在使用上没有任何区别
- System.out实际上是PrintStream地预定义对象。
输出或输出基本类型的数据
上面的代码都是输入或输出文本,如果我们要操作其他基本类型的数据该怎么办呢?
比如,有一个整型数1234,如果以文本输出,那么它会被编码为“00 31 00 32 00 33 0 34”,但是这个整型数在计算机中实际的存储是占四个字节的“00 00 04 D2”.如果我只想输出这个整型数实际的值而不是它的文本该怎么做呢?
和之前一样,需要借助包装流,这次需要用到的是DataInputStream和DataOutputStream。
@Test
public void IOTest8() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\data");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
dataOutputStream.writeInt(1234);
dataOutputStream.writeInt(4321);
dataOutputStream.close();
FileInputStream fileInputStream = new FileInputStream("E:\\data");
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
int num1 = dataInputStream.readInt();
int num2 = dataInputStream.readInt();
System.out.println(num1);
System.out.println(num2);
System.out.println(num1+num2);
dataInputStream.close();
}
DataInputStream和DataOutputStream还提供了一些方法用于操作其他的基本类型,有两点需要注意:
- 输出的是何种类型就要用对应的类型去读,否则会出错。
- 其中有一个方法writeUTF(),该方法并不是用来写普通的字符串的,而是写用于Java虚拟机的字符串,比如编写一个生成字节码的程序时才会用到这个方法。
读入文本数据
虽然可以使用之前的方式读入数据,但是这过于麻烦。正如在输出文本时应该优先考虑打印流一样,在读入文本数据时也应该有更简便的方法。
现在,如果我们希望按行将上面代码打印的那首诗读取出来,有哪些方法?
第一种,使用BufferedReader
@Test
public void IOTest11() throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:\\text.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream, "utf-8"));
String line = null;
while ((line=bufferedReader.readLine())!=null){
System.out.println(line);
}
bufferedReader.close();
}
第二种,使用Scanner
@Test
public void IOTest() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("E:\\text.txt");
Scanner scanner = new Scanner(fileInputStream);
while (scanner.hasNextLine()){
String s = scanner.nextLine();
System.out.println(s);
}
}
向文件追加内容
以上所有代码在输出时都是直接覆盖原有的内容,现在如果文本text.txt中有一首诗了,而我们希望在文本后面追加一首诗应该如何去做:
使用内存流
@Test
public void IOTest13() throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:\\text.txt");
byte[] bytes = new byte[1024];
int len=0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((len=fileInputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
PrintStream printStream = new PrintStream(byteArrayOutputStream);
printStream.println("对酒当歌,");
printStream.println("人生几何。");
printStream.println("譬如朝露,");
printStream.println("去日苦多。");
FileOutputStream fileOutputStream = new FileOutputStream("E:\\text.txt");
byteArrayOutputStream.writeTo(fileOutputStream);
fileInputStream.close();
printStream.close();
fileOutputStream.close();
}
使用RandomAccessFile
@Test
public void IOTest14() throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile("E:\\text.txt","rw");
long length = randomAccessFile.length();
randomAccessFile.seek(length);
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("床前明月光,")
.append(System.getProperty("line.separator"))
.append("疑是地上霜。")
.append(System.getProperty("line.separator"))
.append("举头望明月")
.append(System.getProperty("line.separator"))
.append("低头思故乡");
randomAccessFile.write(stringBuffer.toString().getBytes("utf-8"));
}