Java 中常用的 IO 流
什么是流:
在 Java 中所有数据都是使用流来读写的. 流是对数据传输的总称和抽象. 即数据在两个设备间的传输称为流, 流的本质是数据传输, 根据数据传输的特性, 将流抽象为各种类.
为什么需要缓冲流:
当我们 read() 读取文件的时候, 每读取一个字节都会访问一次硬盘, 效率非常低, 当文件过大的时候尤为显著, 因此需要使用 buffered 缓冲流, 当创建 buffered 对象的时候, 会创建一个缓冲区数组, 此时读文件的时候, 会先从硬盘读到缓冲区, 然后直接从缓冲区输出.
注意: 使用转换流的时候一定要注意编码方式, 如果不给定字符集, 将使用默认的字符集, 如果java编译的编码和目标文件的编码方式不同, 就会出现乱码
字节流与字符流区别:
- 字节流操作的基本单元是字节, 字符流操作的基本单位为 Unicode 码元
- 字节流在操作的时候本身是不会用到缓冲区的, 是与文件本身直接连接的, 而字符流在操作的时候是会用到缓冲区的
- 所有文件的存储都是字节的存储, 在磁盘上保存的是字节
- 在使用字节流操作的时候, 即使没有关闭资源(close), 也能输出, 但是字符流不使用 close() 方法关闭资源的话, 是没有任何输出的
下面借助一些代码来理解一下字节流和字符流
// 字节流读取操作 (1. 使用 FileInputStream 2. 使用 getClassLoader)
@Test
public void t1() throws IOException {
// FileInputStream fis = null;
InputStream fis = null;
try {
// 第一种方式: 使用 FileInputStream + 绝对路径
// fis = new FileInputStream(new File("F:\\learnJavaWeb\\io-study\\data\\呵呵.txt"));
// 第二种方式: 使用 ClassLoader + 相对路径(推荐)
fis = this.getClass().getClassLoader().getResourceAsStream("呵呵.txt");
// abcdefg. 读取操作, 从当前位置偏移多少 位(read, new String 中的第二个参数), 读取多长(第三个参数)
byte[] bytes = new byte[1024];
int len;
// 固定写法
while ((len = fis.read(bytes)) != -1) {
String s = new String(bytes, 0, len);
System.out.println(s);
}
} finally {
if (fis != null) {
fis.close();
}
}
}
// 使用缓冲流来进行字节读取操作
@Test
public void t4() throws IOException {
FileInputStream fis = new FileInputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
BufferedInputStream bis = new BufferedInputStream(fis); // 缓冲输入流
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
String s = new String(bytes, 0, len);
System.out.println(s);
}
}
// 字符流读入操作
@Test
public void t2() throws IOException {
FileReader reader = new FileReader("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
char[] chars = new char[1024];
int len;
// 固定写法
while ((len = reader.read(chars)) != -1) {
String s = new String(chars, 0, len);
System.out.println(s);
}
}
// 字节流转换为字符流(使用缓冲流) 高效
@Test
public void t3() throws IOException {
FileInputStream fis = new FileInputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
// 转换流操作, 指定编码格式, java 编译的编码如果和目标文件的编码格式不一致, 就会出现乱码
InputStreamReader isr = new InputStreamReader(fis,"GBK" );
BufferedReader br = new BufferedReader(isr); // 包装转换的输入流
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 字节流输出操作(使用 PrintWriter 辅助输出)
@Test
public void t5() throws FileNotFoundException {
// 覆盖的方式
FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
// 以追加的形式输出, 追加到文件末尾
// FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt",true);
PrintWriter pw = new PrintWriter(fos); // 使用 PrintWriter 打印流辅助输出
pw.println("追加1");
pw.println("追加2");
pw.println("追加3");
pw.flush(); // 手动刷新缓冲区
// PrintWriter pw = new PrintWriter(fos, true); // 自动刷新缓冲区
}
// 字节流输出操作(使用 BufferedWriter , 因为还要输出成字符形式, 所以需要 OutputStreamWriter 转化流)
@Test
public void t6() throws IOException {
// 覆盖的方式
FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt");
// 追加到文件末尾
// FileOutputStream fos = new FileOutputStream("F:\\\\learnJavaWeb\\\\io-study\\\\data\\\\呵呵.txt",true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("追加1");
bw.newLine(); // 换行
bw.write("追加2");
bw.newLine(); // 换行
bw.write("追加3");
bw.newLine(); // 换行
bw.flush(); // 手动刷新缓冲区
}
注意:
案例:简单实现一下对文件的拷贝
// 简单实现一下对文件的拷贝
public class FileCopy {
public static void main(String[] args) throws IOException {
copyFile();
}
private static void copyFile() throws IOException {
// 创建输入流对象
FileInputStream fis = new FileInputStream("D:\\QQ\\386902834\\FileRecv\\IO.png");
// 创建缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(fis);
// 创建输出流对象
FileOutputStream fos = new FileOutputStream("F:\\板书\\IO");
// 创建缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 准备一个搬运工具
byte[] bytes = new byte[1024];
int len = 0;
// 循环读写数据即可
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 释放资源
bos.flush();
bos.close();
bis.close();
System.out.println("复制成功");
}
下面写一段代码理解一下什么是序列化, 什么是反序列化以及深拷贝和浅拷贝的区别
package Java05_31;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SerializableTest implements Serializable {
private String name;
private List<Food> foods = new ArrayList<>();
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerializableTest t = new SerializableTest();
t.name = "快餐店";
t.foods.add(new Food("牛排"));
t.foods.add(new Food("汉堡"));
t.foods.add(new Food("可乐"));
// 输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(t); // 序列化 java 对象为二进制数据(拷贝)
t.name = "慢慢吃";
t.foods.get(2).name = "牛奶"; // 把可乐改成牛奶
// 观察打印结果会发现, 这里虽然修改了 t 的某些值,但是 t2 还是打印的是原来的值, 这就是深拷贝
// 输入流
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// toByteArray() -> 转成二进制数组
ObjectInputStream ois = new ObjectInputStream(bais);
SerializableTest t2 = (SerializableTest) ois.readObject(); // readObject -> 读取对象
System.out.println(t);
System.out.println(t2);
}
private static class Food implements Serializable{
private String name;
public Food(String name) {
this.name = name;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
'}';
}
}
@Override
public String toString() {
return "SerializableTest{" +
"name='" + name + '\'' +
", foods=" + foods +
'}';
}
}
深拷贝和浅拷贝:
简单来说, 如果拷贝之后, 修改原来的值, 拷贝之后的值也随之变了, 那就是浅拷贝; 如果修改原来的值之后, 拷贝的值没有变, 那就是深拷贝.
浅拷贝不会拷贝堆内存中的对象, 只会拷贝基本类型的属性, 引用类型的属性, 栈中的变量; 而深拷贝则是全部都复制.
如果拷贝对象里的元素只有值, 那深拷贝和浅拷贝是相同的, 都会产生一个新对象, 两个互相分隔;
如果拷贝对象里的元素包含引用, 那就完全不同了: 浅拷贝复制的只是这些引用, 如果对引用中的元素做出改变, 那浅拷贝出的对象引用中的元素也是变化了的; 但是深拷贝不同, 深拷贝会将原对象里的引用也重新创建一份(重新申请一块内存) , 将新对象和原来的对象完全隔离开来!
上一篇: 大数据技术之Hive实战——Youtube项目(二)
下一篇: 第一个Django项目:博客案例