1 IO流
1.1 IO流概述
Java中使用 IO流 来读取和写入,读写设备上的数据、硬盘文件、内存、键盘等等,一个流被定义为一个数据序列。
根据数据的走向可分为输入流和输出流:
- 输入流用于从源读取数据
- 输出流用于向目标写数据
根据处理的数据类型可分为*字节流和字符流:
- 字节流可以处理所有数据类型的数据,在java中以Stream结尾
- 字符流处理文本数据,在java中以Reader和Writer结尾
什么情况下使用哪种流呢?
- 如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流
- 如果你什么都不知道,就用字节流
下图是一个描述输入流和输出流的类层次图。
IO流常用基类:
- 字节流的抽象基类: InputStream ,OutputStream
- 字符流的抽象基类: Reader , Writer
- 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
- 如:InputStream的子类FileInputStream
- 如:Reader的子类FileReader
1.2 字节流写数据
通过查看API可以看到OutputStream是一个抽象类,不能实例化。所以我们需要一个继承自OutputStream的实现类,FileOutputStream文件输出流。
文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
其构造方法:FileOutputStream(File file)、FileOutputStream(String name):
- 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。如果该文件存在,但它是一个目录,而不是一个常规文件;或者该文件不存在,但无法创建它;抑或因为其他某些原因而无法打开,则抛出 FileNotFoundException
字节输出流操作步骤:
- A:创建字节输出流对象
- B:调用write()方法写数据
- C:释放资源
字节输出流方法:(抛出: IOException - 如果发生 I/O 错误)
- public void write(int b):将指定字节写入此文件输出流
- public void write(byte[] b):将 b.length 个字节从指定 byte 数组写入此文件输出流中
- public void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
//写数据
fos.write(97);
byte[] bys={97,98,99,100,101};
fos.write(bys);
fos.write("\r\n".getBytes());
fos.write("java".getBytes());
//释放资源
fos.close(); //关闭此文件输出流并释放与此流有关的所有系统资源
}
}
/*文件内容:
aabcde
java
*/
-
创建字节输出流对象了做了几件事情
- A:调用系统功能去创建文件
- B:创建fos对象
- C:把fos对象指向这个文件
-
为什么一定要close()
- A:让流对象变成垃圾,这样就可以被垃圾回收器回收了
- B:通知系统去释放跟该文件相关的资源
-
不同的系统针对不同的换行符号识别是不一样
- windows:
\r\n
- linux:
\n
- Mac:
\r
- windows:
如何实现数据的追加写入:用构造方法带第二个参数是true的情况即可。
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo3 {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
// 创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处
FileOutputStream fos = new FileOutputStream("fos3.txt", true);
// 写数据
fos.write("\r\n".getBytes());
fos.write(("hello").getBytes());
// 释放资源
fos.close();
}
}
/*文件内容:
aabcde
java
hello
*/
标准代码(带异常处理)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo4 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.txt");
fos.write("java".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 如果fos不是null,才需要close()
if (fos != null) {
// 为了保证close()一定会执行,就放到这里了
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.3 字节流读数据
和OutputStream一样,InputStream也是一个抽象类。所以我们使用其具体实现类FileInputStream文件输入流。FileInputStream 从文件系统中的某个文件中获得输入字节。
其操作步骤如下:
- A:创建字节输入流对象
- B:调用read()方法读取数据,并把数据显示在控制台
- C:释放资源
读取数据的方式:
- int read():一次读取一个字节。如果已到达文件末尾,则返回 -1
- int read(byte[] b):一次读取一个字节数组,返回读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1(数组的长度一般是1024或者1024的整数倍)
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
int by = 0;
// 读取,赋值,判断
while ((by = fos.read()) != -1) {
System.out.print((char) by);
}
fis.close();
}
}
当fos.txt里面有中文时,是不能正常显示的,因为by被强制转换成字符输出了。
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.txt");
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
fis.close();
}
}
在计算机中中文的存储分两个字节:第一个字节肯定是负数;第二个字节常见的是负数,可能有正数,但是没影响。当识别到负数的时候,计算机就把两个字节转换为一个中文。所以上述的 bys 可以输出中文。
1.4 字节流复制文件
需求:把 e:\a.mp4 复制到当前项目目录下的 copy.mp4 中。
- 数据源:
- e:\a.mp4--读取数据--FileInputStream
- 目的地:
- copy.mp4--写出数据--FileOutputStream
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyMp4Demo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("e:\\a.mp4");
// 封装目的地
FileOutputStream fos = new FileOutputStream("copy.mp4");
// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 释放资源
fos.close();
fis.close();
}
}
1.5 字节缓冲区流
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想,所以提供了字节缓冲区流。
- 字节缓冲输出流 BufferedOutputStream
- 字节缓冲输入流 BufferedInputStream
构造方法传入的是一个字节流对象(还可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了)。
为什么不传递一个具体的文件或者文件路径,而是传递一个字节流对象呢?
- 原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("bos.txt"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 读数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
// 写数据
bos.write("hello".getBytes());
// 释放资源
bos.close();
bis.close();
}
}
2 字符流
2.1 字符流概述
由于字节流操作中文数据不是特别的方便,所以就出现了转换流。而转换流的作用就是把字节流转换成字符流来使用。
转换流其实是一个字符流:字符流 = 字节流 + 编码表。
2.2 编码表
编码表是由字符和对应的数值组成的一张表。
常见编码表:
字符串中的编码:
- 编码:String --> byte[]:byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组
- 解码:byte[] --> String:String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "你好";
// String -- byte[]
byte[] bys = s.getBytes(); // [-60, -29, -70, -61]
byte[] bys1 = s.getBytes("GBK");// [-60, -29, -70, -61]
byte[] bys2 = s.getBytes("UTF-8");// [-28, -67, -96, -27, -91, -67]
// byte[] -- String
String ss = new String(bys); // 你好
String ss1 = new String(bys, "GBK"); // 你好
String ss2 = new String(bys, "GBK"); // 浣犲ソ
}
}
2.3 OutputStreamWriter类及其方法
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
- OutputStreamWriter(OutputStream os):默认编码
- OutputStreamWriter(OutputStream os, String charsetName):指定编码
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "UTF-8"); // 指定UTF-8
// 写数据
osw.write("中国");
// 释放资源
osw.close();
}
}
方法:
- public void write(int c):写一个字符
- public void write(char[] cbuf):写一个字符数组
- public void write(char[] cbuf, int off, int len):写一个字符数组的一部分
- public void write(String str):写一个字符串
- public void write(String str, int off, int len):写一个字符串的一部分
查阅文档可以发现,IO流中每一个类都实现了Closeable接口,它们进行资源操作之后都需要执行close()方法将流关闭 。但字节流与字符流的不同之处在于:字节流是直接与数据产生交互,而字符流在与数据交互之前要经过一个缓冲区 。如下图:
为什么循环写入的时候,每一次循环里边都要刷新呢?
- 因为缓冲区有一个默认大小,假设为1k,当操作的文件大于1k时需要循环几次才能写入完毕,所以需要每次循环最后刷新一下,不必等到缓冲区满自动刷新。当循环一次就足够时,只在最后需要close(),关闭流并且刷新。
close()和flush()的区别:
- public void flush():仅仅刷新流的缓冲区,刷新之后流对象还可以继续使用(Reader类及其子类没有此方法)
- public void close():关闭流对象,关闭前会刷新一次缓冲区。关闭之后,流对象不可以继续再使用了(如果不使用close()方法且没有刷新,则读取的数据将保存在缓冲区中)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 创建对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw2.txt"));
char[] chs = {'a','b','c','d','e'};
// public void write(char[] cbuf):写一个字符数组
osw.write(chs);
// public void write(char[] cbuf,int off,int len):写一个字符数组的一部分
osw.write(chs,1,3);
// public void write(String str):写一个字符串
osw.write("我爱林青霞");
// public void write(String str,int off,int len):写一个字符串的一部分
osw.write("我爱林青霞", 2, 3);
// 刷新缓冲区
osw.flush();
osw.write("我爱林青霞", 2, 3);
// 释放资源
osw.close();
// osw.write("我爱林青霞", 2, 3); // java.io.IOException: Stream closed
}
}
2.4 InputStreamReader类及其方法
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
- InputStreamWriter(InputStream os):默认编码
- InputStreamWriter(InputStream os, String charsetName):指定编码
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "UTF-8");
// 读取数据,一次读取一个字符
int ch = 0;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
// 释放资源
isr.close();
}
}
方法:
- int read():一次读取一个字符,如果已到达流的末尾,则返回 -1
- int read(char[] chs):一次读取一个字符数组,如果已到达流的末尾,则返回 -1,否则返回读取字符的个数(win下的换行
\r\n
算两个字符)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// 创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("StringDemo.java"));
// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = isr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
// 释放资源
isr.close();
}
}
2.5 字符缓冲流
字符流为了高效读写,也提供了对应的字符缓冲流。字符缓冲流有自己的特殊方法。构造方法传入一个字符流。
-
BufferedWriter:字符缓冲输出流。将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。可以指定缓冲区的大小,或者接受默认的大小
- public void newLine():根据系统来决定换行符
-
BufferedReader:字符缓冲输入流。从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。可以指定缓冲区的大小,或者可使用默认的大小
- public String readLine():一次读取一行,包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedDemo {
public static void main(String[] args) throws IOException {
write();
read();
}
private static void read() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("bw2.txt"));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
private static void write() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("bw2.txt"));
for (int x = 0; x < 10; x++) {
bw.write("hello" + x);
bw.newLine();
bw.flush();
}
bw.close();
}
}
其中 BufferedReader 还有一个子类 LineNumberReader ,他有两个特殊方法:
- public int getLineNumber():获得当前行号
- public void setLineNumber(int lineNumber):设置当前行号
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
public class LineNumberReaderDemo {
public static void main(String[] args) throws IOException {
LineNumberReader lnr = new LineNumberReader(new FileReader("my.txt"));
// 从10开始
// lnr.setLineNumber(10);
String line = null;
while ((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
}
}
2.6 IO流小结
字节流:
- 字节输入流 InputStream
- int read():一次读取一个字节,末尾返回-1
- int read(byte[] bys):一次读取一个字节数组,末尾返回-1,否则返回读取字节数
- FileInputStream
- BufferedInputStream
- 字节输出流 OutputStream
- void write(int by):一次写一个字节
- void write(byte[] bys, int off, int len):一次写一个字节数组的一部分
- FileOutputStream
- BufferedOutputStream
字符流:
- 字符输入流 Reader
- int read():一次读取一个字符
- int read(char[] bys):一次读取一个字符数组
- InputStreamReader
- FileReader
- BufferedReader
- String readLine():一次读取一个字符串
- 字符输出流 Writer
- void write(int by):一次写一个字符
- void write(char[] bys, int off, int len):一次写一个字符数组的一部分
- OutputStreamWriter
- FileWriter
- BufferedWriter
- void newLine():写一个换行符
- void writer(String line):一次写一个字符串
- LineNumberReader
- public int getLineNumber():获得当前行号
- public void setLineNumber(int lineNumber):设置当前行号
2.7 五种字符流复制文件方法
除了 OutputStreamWriter、InputStreamReader 可以用来读写文件,Java还提供了简单类名的 FileWriter、FileReader 来读写文件,当然,还有更高效的 BufferedWriter、BufferdReader 。
推荐使用字符缓冲流一次读写一个字符串。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*
* 分析:
* 复制数据,如果用记事本打开并能够读懂,就用字符流,否则用字节流。采用字符流更方便一些。
* 数据源:
* c:\\a.txt -- FileReader -- BufferdReader
* 目的地:
* d:\\b.txt -- FileWriter -- BufferedWriter
*/
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
String srcString = "c:\\a.txt";
String destString = "d:\\b.txt";
// method1(srcString, destString);
// method2(srcString, destString);
// method3(srcString, destString);
// method4(srcString, destString);
method5(srcString, destString);
}
// 字符缓冲流一次读写一个字符串
private static void method5(String srcString, String destString)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(srcString));
BufferedWriter bw = new BufferedWriter(new FileWriter(destString));
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
br.close();
}
// 字符缓冲流一次读写一个字符数组
private static void method4(String srcString, String destString)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(srcString));
BufferedWriter bw = new BufferedWriter(new FileWriter(destString));
char[] chs = new char[1024];
int len = 0;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
}
bw.close();
br.close();
}
// 字符缓冲流一次读写一个字符
private static void method3(String srcString, String destString)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(srcString));
BufferedWriter bw = new BufferedWriter(new FileWriter(destString));
int ch = 0;
while ((ch = br.read()) != -1) {
bw.write(ch);
}
bw.close();
br.close();
}
// 基本字符流一次读写一个字符数组
private static void method2(String srcString, String destString)
throws IOException {
FileReader fr = new FileReader(srcString);
FileWriter fw = new FileWriter(destString);
char[] chs = new char[1024];
int len = 0;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
}
fw.close();
fr.close();
}
// 基本字符流一次读写一个字符
private static void method1(String srcString, String destString)
throws IOException {
FileReader fr = new FileReader(srcString);
FileWriter fw = new FileWriter(destString);
int ch = 0;
while ((ch = fr.read()) != -1) {
fw.write(ch);
}
fw.close();
fr.close();
}
}
2.8 四种字节流复制图片方法
推荐掌握第4种,字节缓冲流一次读写一个字节数组。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 数据源:
* c:\\a.jpg -- FileInputStream -- BufferedInputStream
* 目的地:
* d:\\b.jpg -- FileOutputStream -- BufferedOutputStream
*/
public class CopyImageDemo {
public static void main(String[] args) throws IOException {
// 使用File对象做为参数
File srcFile = new File("c:\\a.jpg");
File destFile = new File("d:\\b.jpg");
// method1(srcFile, destFile);
// method2(srcFile, destFile);
// method3(srcFile, destFile);
method4(srcFile, destFile);
}
// 字节缓冲流一次读写一个字节数组
private static void method4(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
// 字节缓冲流一次读写一个字节
private static void method3(File srcFile, File destFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 基本字节流一次读写一个字节数组
private static void method2(File srcFile, File destFile) throws IOException {
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 基本字节流一次读写一个字节
private static void method1(File srcFile, File destFile) throws IOException {
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
}
2.9 字节流复制多级文件夹
需求:复制多极文件夹
- 数据源:E:\JavaSE\day21\code\demos
- 目的地:E:\
分析:
- A:封装数据源File
- B:封装目的地File
- C:判断该File是文件夹还是文件
- a:是文件夹
- 就在目的地目录下创建该文件夹
- 获取该File对象下的所有文件或者文件夹File对象
- 遍历得到每一个File对象
- 回到C
- b:是文件
- 就复制(字节流)
- a:是文件夹
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFoldersDemo {
public static void main(String[] args) throws IOException {
// 封装数据源File
File srcFile = new File("E:\\JavaSE\\day21\\code\\demos");
// 封装目的地File
File destFile = new File("E:\\");
// 复制文件夹的功能
copyFolder(srcFile, destFile);
}
private static void copyFolder(File srcFile, File destFile) throws IOException {
// 判断该File是文件夹还是文件
if (srcFile.isDirectory()) {
// 文件夹
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
// 获取该File对象下的所有文件或者文件夹File对象
File[] fileArray = srcFile.listFiles();
for (File file : fileArray) {
copyFolder(file, newFolder);
}
} else {
// 文件
File newFile = new File(destFile, srcFile.getName());
copyFile(srcFile, newFile);
}
}
private static void copyFile(File srcFile, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}
网友评论