Java基础教程-09-IO流
1. 异常
1.1 概述
观察如下的代码, 看有无问题:
int a = 10;
int b = 0;
System.out.println(a / b);
我们发现, 上述代码的第3行会报错, 即: 算术运算异常, 因为除数是不能为零的. 而在我们编写程序的时候, 可能会有很多的问题存在, 为了将来方便的表示这些问题的原因, 类型, 位置, Java就提供了异常对象供我们使用. 即: Java中的异常指的是程序出现不正常的情况.
1.2 异常的体系结构
假设电脑键盘不灵了? 请问: 该如何解决呢?
- 敲快了. 那就敲慢点.
- 进水了, 那就把水弄干净.
- 线路坏了, 换个新的.
- 线路换了换个定电脑.
Java中的异常体系如下:
Throwable
//异常体系的最顶层类
Error
//表示错误
Exception
//这个才是我们说应的
非RunTimeExceptionException
RuntimeException
1.3 JVM的默认处理异常的方式
如果出现了问题, 我们自己没有处理, JVM会采用自动的处理方法, 它会把异常的类型, 原因, 位置直接打印到控制台上, 后边的代码是不能执行的.
1.4 自己处理异常
1.4.1 try.catch.finally
• 格式
try{
//可能出问题的代码
} catch(Exception e) {
e.printStackTrace()
}
特点: 处理完后, 程序会继续向下执行.
1.4.2 声明抛出异常
• 格式
throws 异常的类型; //该内容是写到方法的形参列表之前的.
特点: 处理完后, 程序会终止执行.
2.File类
2.1 File类概述和构造方法
• File类介绍
– 它是文件和目录路径名的抽象表示
– 文件和目录是可以通过File封装成对象的
– 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
• File类的构造方法
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的 File实例 |
• 示例代码
public class FileDemo01 {
public static void main(String[] args) {
//File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File f1 = new File("E:\\itcast\\java.txt");
System.out.println(f1);
//File(String parent, String child):从父路径名字符串和子路径名字符串创建新的 File实例。
File f2 = new File("E:\\itcast","java.txt");
System.out.println(f2);
//File(File parent, String child):从父抽象路径名和子路径名字符串创建新的 File实例。
File f3 = new File("E:\\itcast");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}
2.2 File类创建功能
• 方法分类
方法名 | 说明 |
---|---|
public boolean createNewFile() | 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件 |
public boolean mkdir() | 创建由此抽象路径名命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 |
• 示例代码
public class FileDemo02 {
public static void main(String[] args) throws IOException {
//需求1:我要在E:\\itcast目录下创建一个文件java.txt
File f1 = new File("E:\\itcast\\java.txt");
System.out.println(f1.createNewFile());
System.out.println("--------");
//需求2:我要在E:\\itcast目录下创建一个目录JavaSE
File f2 = new File("E:\\itcast\\JavaSE");
System.out.println(f2.mkdir());
System.out.println("--------");
//需求3:我要在E:\\itcast目录下创建一个多级目录JavaWEB\\HTML
File f3 = new File("E:\\itcast\\JavaWEB\\HTML");
// System.out.println(f3.mkdir());
System.out.println(f3.mkdirs());
System.out.println("--------");
//需求4:我要在E:\\itcast目录下创建一个文件javase.txt
File f4 = new File("E:\\itcast\\javase.txt");
// System.out.println(f4.mkdir());
System.out.println(f4.createNewFile());
}
}
2.3 File类判断和获取功能
• 判断功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
• 获取功能
方法名 | 说明 |
---|---|
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或目录的名称 |
public String[] list() | 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组 |
public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |
• 示例代码
public class FileDemo04 {
public static void main(String[] args) {
//创建一个File对象
File f = new File("myFile\\java.txt");
// public boolean isDirectory():测试此抽象路径名表示的File是否为目录
// public boolean isFile():测试此抽象路径名表示的File是否为文件
// public boolean exists():测试此抽象路径名表示的File是否存在
System.out.println(f.isDirectory());
System.out.println(f.isFile());
System.out.println(f.exists());
// public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串
// public String getPath():将此抽象路径名转换为路径名字符串
// public String getName():返回由此抽象路径名表示的文件或目录的名称
System.out.println(f.getAbsolutePath());
System.out.println(f.getPath());
System.out.println(f.getName());
System.out.println("--------");
// public String[] list():返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
// public File[] listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组
File f2 = new File("E:\\itcast");
String[] strArray = f2.list();
for(String str : strArray) {
System.out.println(str);
}
System.out.println("--------");
File[] fileArray = f2.listFiles();
for(File file : fileArray) {
// System.out.println(file);
// System.out.println(file.getName());
if(file.isFile()) {
System.out.println(file.getName());
}
}
}
}
2.4 File类删除功能
• 方法分类
方法名 | 说明 |
---|---|
public boolean delete() | 删除由此抽象路径名表示的文件或目录 |
• 示例代码
public class FileDemo03 {
public static void main(String[] args) throws IOException {
// File f1 = new File("E:\\itcast\\java.txt");
//需求1:在当前模块目录下创建java.txt文件
File f1 = new File("myFile\\java.txt");
// System.out.println(f1.createNewFile());
//需求2:删除当前模块目录下的java.txt文件
System.out.println(f1.delete());
System.out.println("--------");
//需求3:在当前模块目录下创建itcast目录
File f2 = new File("myFile\\itcast");
// System.out.println(f2.mkdir());
//需求4:删除当前模块目录下的itcast目录
System.out.println(f2.delete());
System.out.println("--------");
//需求5:在当前模块下创建一个目录itcast,然后在该目录下创建一个文件java.txt
File f3 = new File("myFile\\itcast");
// System.out.println(f3.mkdir());
File f4 = new File("myFile\\itcast\\java.txt");
// System.out.println(f4.createNewFile());
//需求6:删除当前模块下的目录itcast
System.out.println(f4.delete());
System.out.println(f3.delete());
}
}
• 绝对路径和相对路径的区别
– 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如:E:\itcast\java.txt
– 相对路径:必须使用取自其他路径名的信息进行解释。例如:myFile\java.txt
3. IO流简介
3.1 概述
IO流就是用来处理设备间的数据传输问题的.
解释: i指的是Input(输入), o指的是Output(输出).
3.2 应用场景
• 文件复制
• 文件上传
• 文件下载
3.3 分类
• 按照流向分
1. 输入流: 读取数据
2. 输出流: 写入数据.
大白话翻译:
1. 你把你当做"Java程序", 输入流, 就是你在读取文件中的数据.
2. 输出流就是你在写入数据到文件中.
• 按照操作分
1. 字节流: 以字节为单位来操作数据.
2. 字符流: 以字符为单位来操作数据.
小技巧:
1. 字节流能操作所有的文件类型.
例如: 图片, 视频, 音频, 文本文件等.
2. 字符流只能操作纯文本文件.
3. 简单理解: 如果某个文件能用操作系统自带的记事本打开, 并且里边的内容你也能读得懂, 就可以考虑使用字符流, 否则, 使用字节流.
总结
- 我们要学习的流一共有四种, 分别是:
– 字节输入流
解释: 以字节为单位, 来读取数据. 顶层抽象类是: InputStream.
– 字节输出流
解释: 以字节为单位, 来写入数据. 顶层抽象类是: OutputStream.
– 字符输入流
解释: 以字符为单位, 来读取数据. 顶层抽象类是: Reader.
– 字符输出流
解释: 以字符为单位, 来写入数据. 顶层抽象类是: Writer.
4. 字节输出流
4.1 概述
字节输出流指的是OutputStream
, 表示以字节为单位往文件中写入数据, 但是它是一个抽象类
, 所以我们一般都是用它的子类, OutputStream类的两个常用子类如下:
• FileOutputStream: 普通的字节输出流.
• BufferedOutputStream: 高效的字节输出流.
4.2 FileOutputStream介绍
FileOutputStream表示普通的字节输出流, 用来以字节的形式将数据写入到指定的文件中.
4.2.1 构造方法
• public FileOutputStream(String name)
解释:
1. 创建普通的字节输出流对象, 关联目的地文件(字符串形式).
2. 如果文件中有数据, 当程序启动之后并往文件中写入数据时, 文件中已有的数据会被覆盖掉.
• public FileOutputStream(File file)
解释:
1. 创建普通的字节输出流对象, 关联目的地文件(File对象形式).
2. 如果文件中有数据, 当程序启动之后并往文件中写入数据时, 文件中已有的数据会被覆盖掉.
• public FileOutputStream(String name, boolean append)
解释:
1. 创建普通的字节输出流对象, 关联目的地文件(字符串形式).
2. 当append的值为true时, 表示往文件中追加数据, 即: 文件中的数据不会被覆盖掉.
• public FileOutputStream(File file, boolean append)
解释:
1. 创建普通的字节输出流对象, 关联目的地文件(File对象形式).
2. 当append的值为true时, 表示往文件中追加数据, 即: 文件中的数据不会被覆盖掉.
4.2.2 成员方法
• public void write(int b)
解释: 一次写入一个字节, 到指定的目的地文件中.
• public void write(byte[] bys)
解释: 一次写入一个字节数组, 到指定的目的地文件中.
• public void write(byte[] bys, int start, int len)
解释: 一次写入一个字节数组的一部分, 到指定的目的地文件中.
• public void close()
解释: 关闭此文件输出流并释放与此流相关联的任何系统资源.
4.3 案例
4.3.1 案例一: FileOutputStream入门
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo01 {
public static void main(String[] args) throws IOException {
//1.创建FileOutputStream对象, 关联指定的目的地文件.
//解释: 往哪个文件中写数据, 哪个文件就叫: 目的地文件.
//FileOutputStream fos = new FileOutputStream("day12/data/1.txt"); 默认是覆盖
FileOutputStream fos = new FileOutputStream("day12/data/1.txt", true); //传true表示追加
//FileOutputStream fos = new FileOutputStream(new File("day12/data/1.txt"));
//2.往文件中写入字符'a', 'b', 'c'.
//方式一: 一次写一个字节
fos.write(97);
fos.write(98);
fos.write(99);
//方式二: 一次写一个字节数组
/*byte[] bys = {65, 66, 67};
fos.write(bys);*/
//方式三: 一次写一个字节数组的一部分
/*byte[] bys = {65, 66, 67};
fos.write(bys,1, 1);*/
//3. 释放资源.
fos.close();
}
}
4.3.2 案例二: 三种写数据的方式
需求
- 创建FileOutputStream对象, 关联指定的目的地文件.
- 测试上述的3种写数据的方式.
提示: String类中有一个getBytes()方法, 可以把字符串转成其对应的字节数组形式.
参考代码
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo02 {
public static void main(String[] args) throws IOException {
//1.创建FileOutputStream对象, 关联指定的目的地文件.
FileOutputStream fos = new FileOutputStream("day12/data/2.txt", true); //目的地文件不存在, JVM会自动创建.
//2.往文件中写入10次hello, 每个hello占一行.
/* for (int i = 0; i < 10; i++) {
fos.write("hello".getBytes());
//换行符
fos.write("\r\n".getBytes()); //window操作系统: \r\n, linux: \n mac: \r
}*/
//3.往文件中追加一句话: 键盘敲烂, 月薪过万!
fos.write("键盘敲烂, 月薪过万!\r\n".getBytes());
//4. 关闭流, 释放资源
fos.close();
}
}
4.3.3 案例三: 两个小问题
通过上述的内容学习, 我们已经实现了往指定的文件中以字节(数组)的形式写入数据, 但是我们发现两个小问题:
- 上述写入目的地文件中的数据, 并没有换行.
- 如果文件中有数据, 当我们启动程序又往文件中写入数据时, 之前的数据会被覆盖掉.
那如何解决上述的两个问题?
答案:
- 换行可以采用转移符\r\n来实现.
- 可以通过FIleOutputStream类的构造方法, 实现往目的地文件中追加数据的功能.
需求
- 创建FileOutputStream对象, 关联指定的目的地文件.
- 往文件中写入10次hello, 每个hello占一行.
- 往文件中追加一句话: 键盘敲烂, 月薪过万!
提示: 演示下\n, \r\n
参考代码
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo02 {
public static void main(String[] args) throws IOException {
//1.创建FileOutputStream对象, 关联指定的目的地文件.
FileOutputStream fos = new FileOutputStream("day12/data/2.txt", true); //目的地文件不存在, JVM会自动创建.
//2.往文件中写入10次hello, 每个hello占一行.
for (int i = 0; i < 10; i++) {
fos.write("hello".getBytes());
//换行符
fos.write("\r\n".getBytes()); //window操作系统: \r\n, linux: \n mac: \r
}
//3.往文件中追加一句话: 键盘敲烂, 月薪过万!
fos.write("键盘敲烂, 月薪过万!\r\n".getBytes());
//4. 关闭流, 释放资源
fos.close();
}
}
4.3.4 案例四: 加入异常处理
需求
- 创建FileOutputStream对象, 关联指定的目的地文件.
- 往文件中写入如下内容: hello world!
- 演示通过try.catch.finally来解决异常.
参考代码
import java.io.FileOutputStream;
import java.io.IOException;
//案例: 通过try.catch.finally语句解决IO流的异常问题.
public class Demo03 {
public static void main(String[] args) {
//1.创建FileOutputStream对象, 关联指定的目的地文件.
FileOutputStream fos = null;
try{
System.out.println(1/0);
//可能会出现问题的代码
fos = new FileOutputStream("day12/data/2.txt");
//2.往文件中写入10次hello, 每个hello占一行.
for (int i = 0; i < 10; i++) {
fos.write("hello".getBytes());
//换行符
fos.write("\r\n".getBytes());
}
}catch(Exception e) {
//出现问题的异常处理代码.
e.printStackTrace();
}finally{
//3. 关闭流, 释放资源
try {
if (fos != null) {
fos.close(); //快捷键: alt + enter
fos = null; //GC会优先回收null对象.
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.3.5 案例五: 创造空文件
需求
在指定的路径下(例如: d:/abc)创建一个大小为1G的空文件.
参考代码
import java.io.FileOutputStream;
import java.io.IOException;
//案例: 创建空文件
public class Demo05 {
public static void main(String[] args) throws IOException {
//1. 创建字节输出流, 关联目的地文件.
FileOutputStream fos = new FileOutputStream("d:/abc/罗老师(4K无码).avi");
//2. 创建字节数组, 长度为1024
byte[] bys = new byte[1024]; //1024 byte = 1KB
//3. 往目的地文件中写数据.
for (int i = 0; i < 1024 * 1024; i++) {
fos.write(bys);
}
//4. 释放资源.
fos.close();
System.out.println("制造完成!");
}
}
5. 字节输入流
5.1 概述
字节输入流指的是InputStream, 表示以字节为单位从文件中读取数据, 但是它是一个抽象类, 所以我们一般都是用它的子类, InputStream类的两个常用子类如下:
• FileInputStream: 普通的字节输入流.
• BufferedInputStream: 高效的字节输入流.
5.2 FileInputStream介绍
FileInputStream表示普通的字节输入流, 用来以字节的形式从文件中读取数据.
5.2.1 构造方法
• public FileInputStream(String name)
解释: 创建普通的字节输入流对象, 关联数据源文件(字符串形式).
• public FileInputStream(File file)
解释: 创建普通的字节输入流对象, 关联数据源文件(File对象形式).
5.2.2 成员方法
• public int read()
解释: 从指定的数据源文件中, 一次读取一个字节, 并返回该字节对应的整数. 读不到则返回-1.
• public int read(byte[] bys)
解释:
- 从指定的数据源文件中, 一次读取一个字节数组, 并将读取到的内容存入到字节数组中 .
- 返回读取到的有效字节数. 如果读不到则返回-1.
• public void close()
解释: 关闭此文件输入流并释放与此流相关联的任何系统资源.
5.3 案例
3.3.1 案例一: FileInputStream入门
需求
- 创建FileInputStream对象, 关联指定的数据源文件.
- 通过一次读取一个字节的形式, 读取该文件中的数据.
注意: 如果数据源文件不存在, 程序会报错.
参考代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo01 {
public static void main(String[] args) throws IOException {
//1. 创建字节输入流对象, 封装数据源文件. 数据源文件必须存在, 不然报错.
FileInputStream fis = new FileInputStream("day12/data/1.txt");
//2. 一次读一个字节
//2.1 定义变量, 记录读取到的内容.
int len = 0;
/*
(len = fis.read()) != -1 这行代码做了3件事:
1. 执行 fis.read(), 一次读取一个字节.
2. 执行 len = fis.read() 将读取到的字节的ASCII码值赋值给变量len
3. 执行 len != -1 如果len不等于-1, 说明读到内容了, 就进循环.
*/
while((len = fis.read()) != -1) {
System.out.println(len);
}
//3. 关流.
fis.close();
}
}
5.3.2 案例二: 一次读取一个字节数组
需求
- 创建FileInputStream对象, 关联指定的数据源文件.
- 通过一次读取一个字节数组的形式, 读取该文件中的数据.
参考代码
import java.io.FileInputStream;
import java.io.IOException;
public class Demo02 {
public static void main(String[] args) throws IOException {
//1. 创建字节输入流对象, 封装数据源文件. 数据源文件必须存在, 不然报错.
FileInputStream fis = new FileInputStream("day12/data/1.txt");
//2. 一次读一个字节数组
//合并版
//2.1 定义字节数组, 记录读取到的数据.
byte[] bys = new byte[3]; //字节数组的长度: 一般是1024的整数倍. 初始值: 0, 0, 0
int len = 0; //记录读取到的有效字节数.
/*
(len = fis.read(bys)) != -1 这行代码做了3件事
1. 执行fis.read(bys), 一次读取一个字节数组, 并把读取到的内容存储到bys数组中.
2. 执行len = fis.read(bys) 将读取到的有效字节数赋值给变量len
3. 执行len != -1 如果len不等于-1, 说明读到内容了, 进循环.
*/
while((len = fis.read(bys)) != -1) {
System.out.println(new String(bys,0, len ));
}
//3. 关流.
fis.close();
//分解版
/*//第一次读取
int len1 = fis.read(bys); //bys: abc(97, 98, 99), len1 = 3
String s1 = new String(bys, 0, len1);
System.out.println(s1);
System.out.println(len1);
System.out.println("----------------------");
//第二次读取
int len2 = fis.read(bys); //bys: dbc(100, 98, 99), len2 = 1
String s2 = new String(bys, 0, len2);
System.out.println(s2);
System.out.println(len2);*/
}
}
5.3.3 案例三: 复制文本文件
需求
- 将项目下的1.txt中的内容复制到2.txt文件中.
- 通过两种方式实现.
提示:
- 方式一: 一次读写一个字节.
- 方式二: 一次读写一个字节数组.
参考代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo01 {
public static void main(String[] args) throws IOException {
//拷贝文件方式一: 一次读写一个字节.
//method01();
//拷贝文件方式二: 一次读写一个字节数组.
//1. 创建输入流对象, 关联数据源文件.
FileInputStream fis = new FileInputStream("day12/data/1.txt");
//2. 创建输出流对象, 关联目的地文件.
FileOutputStream fos = new FileOutputStream("day12/data/2.txt");
//3. 定义变量, 记录读取到的内容或者有效字节(符)数.
int len = 0;
byte[] bys = new byte[1024]; //一般是8KB
//4. 循环读取,只要条件满足就一直读, 并将读取到的内容赋值给变量.
while ((len = fis.read(bys)) != -1) {
//5. 将读取到的内容写入到目的地文件中.
fos.write(bys, 0, len);
}
//6. 关流, 释放资源.
fis.close();
fos.close();
System.out.println("拷贝完成.");
}
public static void method01() throws IOException {
//1. 创建输入流对象, 关联数据源文件.
FileInputStream fis = new FileInputStream("day12/data/1.txt");
//2. 创建输出流对象, 关联目的地文件.
FileOutputStream fos = new FileOutputStream("day12/data/2.txt");
//3. 定义变量, 记录读取到的内容或者有效字节(符)数.
int len = 0;
//4. 循环读取,只要条件满足就一直读, 并将读取到的内容赋值给变量.
while ((len = fis.read()) != -1) {
//5. 将读取到的内容写入到目的地文件中.
fos.write(len);
}
//6. 关流, 释放资源.
fis.close();
fos.close();
System.out.println("拷贝完成.");
}
}
5.3.4 案例五: 文件加密
需求
- 对项目下的a.jpg图片进行加密, 获取到一个新的加密图片b.jpg.
- 对加密后的图片b.jpg进行解密, 获取解密后的图片c.jpg.
参考代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo02 {
public static void main(String[] args) throws IOException {
System.out.println(5 ^ 5 ^ 10); //10
System.out.println(2 ^ 3 ^ 3); //2
/*
需求:
1.对项目下的a.jpg图片进行加密, 获取到一个新的加密图片b.jpg.
2.对加密后的图片b.jpg进行解密, 获取解密后的图片c.jpg.
*/
// 1. 创建输入流对象, 关联数据源文件.
FileInputStream fis = new FileInputStream("day12/data/b.jpg");
// 2. 创建输出流对象, 关联目的地文件.
FileOutputStream fos = new FileOutputStream("day12/data/c.jpg");
// 3. 定义变量, 记录读取到的内容或者有效字节(符)数.
int len = 0;
// 4. 循环读取,只要条件满足就一直读, 并将读取到的内容赋值给变量.
while ((len = fis.read()) != -1) {
// 5. 将读取到的内容写入到目的地文件中.
fos.write(len ^ 5); //这里的5就是密钥
}
// 6. 关流, 释放资源.
fis.close();
fos.close();
}
}
6. 字节缓冲流
6.1 概述
字节流一次读写一个字节数组的速度比一次读写一个字节的速度快很多(稍后测试), 这是加入了数组这样的缓冲区效果, 但是如果每次都需要我们自己来定义数组的话, 是非常繁琐的. 所以Java本身在设计的时候, 也考虑到了这样的设计思想, 并提供了字节缓冲流.
字节缓冲流也叫缓冲字节流, 高效字节流, 字节高效流, 它主要分为两个:
• BufferedInputStream: 字节缓冲输入流
• BufferedOutputStream: 字节缓冲输出流
6.2 构造方法
6.2.1 格式如下:
• BufferedOutputStream类的构造方法
– public BufferedOutputStream(OutputStream os) 创建字节缓冲输出流对象,
• BufferedInputStream类的构造方法
– public BufferedInputStream(InputStream is) 创建字节缓冲输入流对象.
6.2.2 思考题
为什么构造方法传递的是一个: OutputStream(或者InputStream), 而不是具体的文件或者路径呢?
答案: 字节缓冲流仅仅提供缓冲区, 而真正的底层的读写数据还是需要基本的流对象进行操作.
7. 铺垫知识
7.1 为什么会出现字符流
7.1.1 由来
假如某个数据源文件中包含中文, 此时通过字节流来读取数据, 就不是特别方便了, 因为中文在不同的码表中, 占用的字节数是不一样的, 针对于这种情况, Java就提供了字符流.
记忆:
- 中文在GBK码表中占2个字节, 在UTF-8码表中占3个字节.
- 不管在什么码表中, 中文的第一个字节肯定是负数.
- 字符流 = 字节流 + 编码表.
7.1.2 案例演示
需求
- 已知项目下的data文件夹下的1.txt文件中包含数据"abc中国".
- 请通过字节流一次读取一个字节的形式, 将上述文件中的内容读取出来.
- 将上述读取到的数据打印到控制台上.
解释:
- 字节流操作中文不是特别方便.
- 通过String#getBytes()方法, 可以查看中文在不同码表下占用的字节内容.
参考代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
public class Demo01 {
public static void main(String[] args) throws IOException {
/*
1.已知项目下的data文件夹下的1.txt文件中包含数据"abc中国".
2.请通过字节流一次读取一个字节的形式, 将上述文件中的内容读取出来.
3.将上述读取到的数据打印到控制台上.
解释:
1.字节流操作中文不是特别方便.
2.通过String#getBytes()方法, 可以查看中文在不同码表下占用的字节内容.
*/
//需求: 请通过字节流一次读取一个字节的形式, 将上述文件中的内容读取出来.
/* FileInputStream fis = new FileInputStream("day12/data/1.txt");
int len = 0;
while((len = fis.read()) != -1) {
System.out.println((byte)len);
}
fis.close();
*/
//演示不同码表下的中文占用的字节数.
//utf-8码表: 中国, -28,-72,-83,-27,-101,-67
String str = "中国";
byte[] bys = str.getBytes(); //默认码表: UTF-8
//byte[] bys = str.getBytes("utf-8"); //-28,-72,-83,-27,-101,-67
//byte[] bys = str.getBytes("gbk"); //-42, -48, -71, -6
System.out.println(Arrays.toString(bys));
}
}
7.1.3 思考题: 中文的字节存储方式
问: 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
答案: 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数.
7.2 编码表
计算机中存储的信息都是用二进制数表示的, 即: 计算机底层存储, 操作和运算都是用数据的二进制补码形式实现的.
• 编码: 按照某种规则, 将字符存储到计算机中, 这个过程称之为编码.
• 解码: 按照某种规则, 将存储在计算机中的二进制数据按照某种规则解析显示出来, 就称之为解码.
注意(细节):
- 按照A编码存储, 必须按照A编码解析, 这样才能显示正确的文本符号, 否则就会导致乱码现象.
- 例如: 按照GBK码表存储, 则必须按照GBK码表解析.
• 什么是字符集
是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
• 常见的字符集
– ASCII字符集:American Stanford Code For Information Interchange: 美国信息交换标准代码.
是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
– ISO-8859-1: 欧洲码表, 用一个字节的8位表示数据, 兼容ASCII.
– GBXXX字符集:
• GB2312: 中文码表, 兼容ASCII. 包含7000多个简体汉字.
• GBK:最常用的中文码表. 是GB2312码表的升级版, 融合了更多的中文文字符号, 兼容ASCII.
解释: GBK码表共收录了21003个汉字.
• GB18030: 最新的中文码表, 收录汉字70244个.
– Unicode字符集:
• 为表达任意语言的任意字符而设计, 是业界的一种标准, 也称为统一码, 标准万国码. 它最多使用4个字节的数字来表达每个字母, 符号或者文字. 有3种编码方案, 分别是: UTF-8, UTF-16, UTF-32.
• UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用1~4个字节为每个字符编码.
– 编码规则:
• 128个US-ASCII字符,只需一个字节编码
• 拉丁文等字符,需要二个字节编码
• 大部分常用字(含中文),使用三个字节编码
• 其他极少使用的Unicode辅助字符,使用四字节编码
总结:
采用何种规则编码, 就采用对应规则解码, 否则就可能会出现乱码情况.
7.3 字符串中的编码解码问题
7.3.1 编解码解释
• 编码: 把字符串数据按照特定的码表规则, 转换成其对应的二进制形式的数据(看懂的 -> 看不懂的).
– String类中的两个成员方法:
• public byte[] getBytes();
解释: 采用平台的默认字符集(UTF-8), 将字符串转成其对应的字节数组.
• public byte[] getBytes(String charsetName);
解释: 采用指定的字符集, 将字符串转成其对应的字节数组.
• 解码: 把二进制形式的数据, 按照特点的码表规则, 转换成其对应的字符串形式(即: 看不懂的 -> 看懂的).
– String类的构造方法:
• public String(byte[] bys)
解释: 采用平台的默认字符集(UTF-8), 将字节数组转成其对应的字符串.
• public String(byte[] bys, String charsetName)
解释: 采用指定的字符集, 将字节数组转成其对应的字符串.
7.3.2 案例
需求
- 通过代码, 演示上述的字符串的编解码问题.
- 打印并观察结果.
参考代码
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Demo02 {
public static void main(String[] args) throws UnsupportedEncodingException {
//1. 演示编码.
/*String str = "我爱你";
//byte[] bys = str.getBytes(); //默认码表: utf-8 [-26, -120, -111, -25, -120, -79, -28, -67, -96]
//byte[] bys = str.getBytes("utf-8"); //[-26, -120, -111, -25, -120, -79, -28, -67, -96]
byte[] bys = str.getBytes("gbk"); //[-50, -46, -80, -82, -60, -29]
System.out.println(Arrays.toString(bys));*/
//2. 演示解码
byte[] bys = {-26, -120, -111, -25, -120, -79, -28, -67, -96};
//String s = new String(bys); //默认码表: utf-8
String s = new String(bys, "utf-8");
// String s = new String(bys, "gbk"); //因为编解码不一致, 所以乱码.
System.out.println(s);
}
}
运行结果为:
我爱你
8. 字符流
8.1 概述
通过上述的内容, 我们知道字符流 = 字节流 + 编码表. 所以我们可以通过转换流的方式, 结合字节流和编码表一起使用, 从而实现字符流的功能.
8.2 实现方式
8.2.1 通过转换流实现
转换流指的是InputStreamReader
和OutputStreamWriter
这两个类. 具体如下:
• InputStreamReader:是从字节流到字符流的桥梁
– 它读取字节,并使用指定的编码将其解码为字符
– 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
• OutputStreamWriter:是从字符流到字节流的桥梁
– 是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
– 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
8.3 案例
8.3.1 案例一: 复制文件
需求
- 通过转换流, 将项目下data文件夹中1.txt文件的内容, 拷贝到data文件夹下2.txt文件中.
- 运行, 并查看返回值结果.
参考代码
import java.io.*;
public class Demo03 {
public static void main(String[] args) throws IOException {
//方式一: 通过转换流, 把 字节流 和 编码表整合到一起. 了解即可.
/* InputStreamReader isr = new InputStreamReader(new FileInputStream("day12/data/1.txt"), "utf-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day12/data/2.txt"), "utf-8");*/
//字符流拷贝文件, 一次读写一个字符.
//方式二: 直接使用封装好的字符流.
//1. 创建输入流对象, 关联数据源文件.
FileReader fr = new FileReader("day12/data/1.txt"); //只能用默认码表读.
//2. 创建输出流对象, 关联目的地文件.
FileWriter fw = new FileWriter("day12/data/2.txt"); //只能用默认码表写.
//3. 定义变量, 记录读取到的内容或者有效字节(符)数.
int len = 0;
//4. 循环读取,只要条件满足就一直读, 并将读取到的内容赋值给变量.
while ((len = fr.read()) != -1) {
//5. 将读取到的内容写入到目的地文件中.
fw.write(len);
}
//6. 关流, 释放资源.
fr.close();
fw.close();
}
}
9. 字符缓冲流
9.1 概述
字符流一次读写一个字符数组的速度比一次读写一个字符的速度快很多, 这是加入了数组这样的缓冲区效果, 但是如果每次都需要我们自己来定义数组的话, 是非常繁琐的. 所以Java本身在设计的时候, 也考虑到了这样的设计思想, 并提供了字符缓冲流.
字符缓冲流也叫缓冲字符流, 高效字符流, 字符高效流, 它主要分为两个:
• BufferedReader: 字符缓冲输入流
• BufferedWriter: 字符缓冲输出流
9.2 构造方法
9.2.1 格式如下:
• BufferedWriter类的构造方法
– public BufferedWriter(Writer w) 创建字符缓冲输出流对象,
• BufferedReader类的构造方法
– public BufferedReader(Reader r) 创建字符缓冲输入流对象.
9.2.2 思考题
为什么构造方法传递的是一个: Writer (或者Reader ), 而不是具体的文件或者路径呢?
答案: 字符缓冲流仅仅提供缓冲区, 而真正的底层的读写数据还是需要基本的流对象进行操作.
10. 序列化流
10.1 对象序列化流
• 对象序列化介绍
– 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
– 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
– 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
– 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
• 对象序列化流: ObjectOutputStream
– 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
• 构造方法
方法名 | 说明 |
---|---|
ObjectOutputStream(OutputStream out) | 创建一个写入指定的OutputStream的ObjectOutputStream |
• 序列化对象的方法
方法名 | 说明 |
---|---|
void writeObject(Object obj) | 将指定的对象写入ObjectOutputStream |
• 示例代码
学生类
public class Student implements Serializable {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
//ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myOtherStream\\oos.txt"));
//创建对象
Student s = new Student("林青霞",30);
//void writeObject(Object obj):将指定的对象写入ObjectOutputStream
oos.writeObject(s);
//释放资源
oos.close();
}
}
• 注意事项
– 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
– Serializable是一个标记接口,实现该接口,不需要重写任何方法
10.2 对象反序列化流
• 对象反序列化流: ObjectInputStream
– ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
• 构造方法
方法名 | 说明 |
---|---|
ObjectInputStream(InputStream in) | 创建从指定的InputStream读取的ObjectInputStream |
• 反序列化对象的方法
方法名 | 说明 |
---|---|
Object readObject() | 从ObjectInputStream读取一个对象 |
• 示例代码
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
//Object readObject():从ObjectInputStream读取一个对象
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
}
10.3 serialVersionUID&transient
• serialVersionUID
– 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
• 会出问题,会抛出InvalidClassException异常
– 如果出问题了,如何解决呢?
• 重新序列化
• 给对象所属的类加一个serialVersionUID
– private static final long serialVersionUID = 42L;
• transient
– 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
• 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
• 示例代码
学生类
public class Student implements Serializable {
private static final long serialVersionUID = 42L;
private String name;
// private int age;
private transient int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// @Override
// public String toString() {
// return "Student{" +
// "name='" + name + '\'' +
// ", age=" + age +
// '}';
// }
}
测试类
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// write();
read();
}
//反序列化
private static void read() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
//序列化
private static void write() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myOtherStream\\oos.txt"));
Student s = new Student("林青霞", 30);
oos.writeObject(s);
oos.close();
}
}