一、IO 流体系结构
1.字节流(万能流)
抽象类 | 抽象方法 |
InputStream字节输入流 | FileInputStream |
OutputStream字节输出流 | FileOutputStream |
2.字符流(纯文本文件)
抽象类 | 抽象类的子类 |
Reader 字符输入流 | FileReader |
Writer 字符输出流 | FileWriter |
二、FileOutputStream 字节输出流
构造方法 | 说明 |
FileOutputStream(String name) FileOutputStream(String name,boolean append) | 输出流关联文件, 文件路径以字符串形式给出,第二个参数是追加写入的开关 |
FileOutputStream(File file) FileOutputStream(File file,boolean append) | 输出流关联文件, 文件路径以File对象形式给出,第二个参数是追加写入的开关 |
成员方法 | 说明 |
void write(int b) | 写出单个字节 |
void write(byte[] b) | 写出一个字节数组 |
void write(byte[] b, int off, int len) | 写出字节数组的一部分,第二个参数是索引,第三个是写入的长度(个数) |
public static void main(String[] args) throws IOException {
// 创建字节输出流对象, 关联文件
//输出流关联文件, 文件如果不存在: 会自动创建出来
//如果文件存在: 会清空现有的内容, 然后再进行写入操作
//第二个参数决定是否可以继续写入(是否开启追加模式)
FileOutputStream fos = new FileOutputStream("D:\\A.txt", true);
byte[] bys = {97, 98, 99};
// 写出数据
fos.write(97);
fos.write(98);
fos.write(99);
fos.write(bys);
fos.write("你好".getBytes());
fos.write(bys, 1, 2);
}
流对象使用完毕后, 记得调用 close 方法关闭 不然会占用资源
/*
流对象使用完毕后需要关闭
*/
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\B.txt");
fos.write("abc".getBytes());
fos.close();
}
try (需要调用close方法的对象) { 逻辑代码... } catch (异常类名 对象名) { 异常的处理方式 }finally{
无论代码有没有异常都会执行这部分代码
}
注:try () 中的对象, 需要实现过 AutoCloseable 接口
/*
IO流的异常处理方式: jdk7版本开始
*/
public static void main(String[] args) {
//JDK7版本之后将需要关的流放到try的小括号中会自动关流,不需要写finally 调用close()
try(FileOutputStream fos = new FileOutputStream("D:\\B.txt");) {
fos.write("abc".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
自己写类的时候要重写close()方法
class Demo implements AutoCloseable {
@Override
public void close() throws Exception {
}
}
三、FileInputStream 字节输入流
构造方法 | 说明 |
FileInputStream(String name) | 输入流关联文件, 文件路径以字符串形式给出 |
FileInputStream(File file) | 输入流关联文件, 文件路径以File对象形式给出 |
注:关联的文件不存在会抛出 FileNotFoundException 异常 ,文件夹的话会拒绝访问
成员方法 | 说明 |
int read() | 读取一个字节并返回, 如果到达文件结尾则返回 -1 |
int read(byte[] b) | 将读取到字节, 放到传入的数组 返回读取到的有效字节个数 如果到达文件结尾则返回 -1 |
/*字节流读取数据 throws IOException可以try(){}catch{}*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\A.txt");
int i;
//用达文件结尾则返回 -1的特性标记循环
while ((i = fis.read()) != -1) {
System.out.println((char) i);
}
//结束后关闭流
fis.close();
}
/*
将读取到的字节, 存入数组容器, 返回读取到的有效字节个数
*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\A.txt");
// 准备容器, 用于装(字节)
byte[] bys = new byte[2];
int len;
while( (len = fis.read(bys)) != -1 ){
/*
public String(byte[] bytes, int offset, int length)
将字节数组转换为字符串
参数1 : 字节数组
参数2 : 起始索引
参数3 : 转换的个数
*/
String s = new String(bys, 0, len);
System.out.print(s);
}
fis.close();
}
案例:将 D:\1.png,拷贝到 E:\ 根目录下
public static void main(String[] args) throws IOException {
// 1. 创建输入流对象读取文件
FileInputStream fis = new FileInputStream("D:\\1.png");
// 2. 创建输出流对象关联数据目的
FileOutputStream fos = new FileOutputStream("E:\\1.png");
// 3. 读写操作,1024是为了方便计算机搬运,提高效率
byte[] bys = new byte[1024];
int len;
while( (len = fis.read(bys)) != -1){
fos.write(bys, 0, len);
}
// 4. 关流释放资源
fis.close();
fos.close();
}
四、字节缓冲流
字节缓冲流在源代码中内置了字节数组,可以提高读写效率
构造方法 | 说明 |
BufferedInputStream(InputStream in) | 对传入的字节输入流进行包装 |
BufferedOutputStream(OutputStream out) | 对传入的字节输出流进行包装 |
注:缓冲流不具备读写功能, 它们只是对普通的流对象进行包装 ,真正和文件建立关联的, 还是普 通的流对象
private static void method() throws IOException {
// 1. 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\1.ogg"));
// 2. 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\1.ogg"));
// 3. 读写操作
int i;
while ((i = bis.read()) != -1) {
bos.write(i);
}
// 4. 关流
bis.close();
bos.close();
}
在缓冲流中定义了数组拷贝new byte[8192],所以加快了速度,相对于普通流 加上自定义数组拷贝的效率很多时候是差不多的,缓冲流也可以加上自定义数组一起进行拷贝,效率会更好些,如果加大普通流 加上自定义数组的数组容量也会有相应的速度。
public static void copyFile() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\1.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\1.mp4"));
int len;
byte[] bys = new byte[1024];
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bis.close();
bos.close();
}
五、FileReader 字符输入流
用于读取纯文本文件,解决中文乱码问题
构造方法 | 说明 |
FileReader(String fileName) | 字符输入流关联文件,路径以字符串形式给出 |
FileReader(File file) | 字符输入流关联文件,路径以File对象形式给出 |
成员方法 | 说明 |
public int read() | 读取单个字符 |
public int read(char[] cbuf) | 读取一个字符数组, 返回读取到的有效字符个数 |
private static void method() throws IOException {
FileReader fr = new FileReader("D:\\A.txt");
int i;
while ((i = fr.read()) != -1) {
System.out.print((char)i);
}
fr.close();
}
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\A.txt");
char[] chs = new char[1024];
int len;
while( (len = fr.read(chs)) != -1 ){
String s = new String(chs, 0, len);
System.out.println(s);
}
fr.close();
}
字符编码:字符编码是指一种映射规则 根据这个规则可以将某个字符映射成其他形式的数据以便
在计算机中存储和传输
GBK: 每个中文占用2个字节
英文字符占用1个字节
中文字符占用2个字节
Unicode: 每个中文占用3个字节
UTF-8编码规则:用1~4个字节保存
编码和解码:
编码: 字符转字节 (基于String的)
成员方法 | 说明 |
public byte[] getBytes() | 使用平台默认字符编码方式, 对字符串编码 |
public byte[] getBytes(String charsetName) | 使用使用字符编码方式, 对字符串编码 |
解码: 字节转字符
构造方法 | 说明 |
public String(byte[] bytes) | 使用平台默认字符编码方式, 对字符串解码 |
public String(byte[] bytes, String charsetName) | 使用使用字符编码方式, 对字符串解码 |
public static void main(String[] args) throws IOException {
String s = "你好,你好";
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
byte[] gbks = s.getBytes("gbk");
System.out.println(Arrays.toString(gbks));
System.out.println("----------------------------");
byte[] utf8Bytes = {-28, -67, -96, -27, -91, -67, 44, -28, -67, -96, -27, -91, -67};
byte[] gbkBytes = {-60, -29, -70, -61, 44, -60, -29, -70, -61};
String s1 = new String(gbkBytes, "GBK");
System.out.println(s1);
}
平台默认字符编码 : Unicode - UTF-8的形式
重点记忆: 中文字符, 通常都是由负数的字节进行组成的.
特殊情况: 可能会出现正数, 但是就算有正数, 第一个字节肯定是负数
注意事项: 今后如果出现乱码问题, 大概率是因为编解码方式不一致所导致的.
六、FileWriter 字符输出流
构造方法 | 说明 |
FileWriter(String fileName) | 字符输出流关联文件,路径以字符串形式给出 |
FileWriter(String fileName, boolean append) | 参数2: 追加写入的开关 |
FileWriter(File file) | 字符输出流关联文件,路径以File对象形式给出 |
FileWriter(File file, boolean append) | 参数2: 追加写入的开关 |
成员方法 | 说明 |
public void write(int c) | 写出单个字符 |
public void write(char[] cbuf) | 写出一个字符数组 |
public write(char[] cbuf, int off, int len) | 写出字符数组的一部分 |
public void write(String str) | 写出字符串 |
public void write(String str, int off, int len) | 写出字符串的一部分 |
注:字符输出流写出数据,需要调用flush或close方法,数据才会写出
Flush后可以继续写出 Close 后不能继续写出
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("D:\\C.txt");
char[] chs = {'a','b','c'};
fw.write('a');
fw.write(chs);
fw.write(chs, 0, 2);
fw.write("你好你好~");
fw.write("哈哈哈哈哈", 0, 2);
fw.close();
}
七、案例
1.图片文件加密解密
加密思路:改变原始文件中的字节,就无法打开了
字节 ^ 2
解密思路:将文件中的字节还原成原始字节即可
字节 ^ 2
public static void main(String[] args) throws IOException {
// 1. 创建字节输入流对象, 关联要加密的图片
FileInputStream fis = new FileInputStream("D:\\1.jpg");
// 2. 创建一个容器, 用来存储读取到的字节
ArrayList<Integer> list = new ArrayList<>();
// 3. 循环读取文件中的字节, 并存入集合
int i;
while((i = fis.read()) != -1){
list.add(i);
}
// 4. 关闭输入流对象
fis.close();
// 5. 创建输出流对象, 关联图片文件.
FileOutputStream fos = new FileOutputStream("D:\\1.jpg");
// 6. 遍历集合, 从集合中取出字节, 并写出
for (Integer myByte : list) {
fos.write(myByte ^ 2);
}
// 7. 关闭输出流对象
fos.close();
}
2.统计文件中每一个字符出现的次数,随后展示在控制台
效果:A(1)B(2)C(3)
public static void main(String[] args) throws IOException {
// 1. 准备map集合, 用于统计每一种字符出现的次数
HashMap<Character, Integer> hm = new HashMap<>();
// 2. 创建字符输入流读取纯文本文件
FileReader fr = new FileReader("D:\\info.txt");
// 3. 读取字符
int i;
while ((i = fr.read()) != -1) {
char c = (char) i;
// 4. 统计这个字符出现的次数
if (!hm.containsKey(c)) {
hm.put(c, 1);
} else {
hm.put(c, hm.get(c) + 1);
}
}
// 4. 关闭输入流
fr.close();
// 5. 准备StringBuilder用于拼接操作
StringBuilder sb = new StringBuilder();
hm.forEach(new BiConsumer<Character, Integer>() {
@Override
public void accept(Character key, Integer value) {
sb.append(key).append("(").append(value).append(")");
}
});
System.out.println(sb);
}
3.拷贝一个文件夹, 考虑子文件夹,将D:\\test文件夹, 拷贝到E:\\
public static void main(String[] args) throws IOException {
File src = new File("E:\\test");
File dest = new File("D:\\");
if (src.equals(dest)) {
System.out.println("目标文件夹是源文件夹的子文件夹");
} else {
copyDir(src, dest);
}
}
public static void copyDir(File src, File dest) throws IOException {
File newDir = new File(dest, src.getName());
newDir.mkdirs();
// 从数据源中获取数据(File对象)
File[] files = src.listFiles();
// 遍历数组, 获取每一个文件或文件夹对象
for (File file : files) {
// 判断当前对象是否是文件
if (file.isFile()) {
// 是的话直接拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(newDir, file.getName()));
int len;
byte[] bys = new byte[1024];
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fis.close();
fos.close();
} else {
// 如果是文件夹, 递归调用方法
copyDir(file, newDir);
}
}
}