【每日一学】Java数据流大揭秘:高效处理字节级别的数据传输
学习总目标
本次学习目标
6 转换流
6.1 字符编码和字符集
1、编码与解码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(人能看懂的)–字节(人看不懂的)
解码:字节(人看不懂的)–>字符(人能看懂的)
?字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
编码表:生活中文字和计算机中二进制的对应规则
2、字符集
?字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
?ASCII字符集 :
–ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
–基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。
–ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
?ISO-8859-1字符集:
–拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
–ISO-8859-1使用单字节编码,兼容ASCII编码。
?GBxxx字符集:
–GB就是国标的意思,是为了显示中文而设计的一套字符集。
–GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
–GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
–GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
?Unicode字符集 :
–Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
–它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
–UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
1.128个US-ASCII字符,只需一个字节编码。
2.拉丁文等字符,需要二个字节编码。
3.大部分常用字(含中文),使用三个字节编码。
4.其他极少使用的Unicode辅助字符,使用四字节编码。
6.2 编码引出的问题
使用FileReader 读取项目中的文本文件。由于项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
package com.atguigu.transfer;
import java.io.FileReader;
import java.io.IOException;
public class Problem {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("E:\\File_GBK.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
fileReader.close();
}
}
输出结果:
???
那么如何读取GBK编码的文件呢?
6.3 InputStreamReader类
转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
?InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
?InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
构造举例,代码如下:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
示例代码:
package com.atguigu.transfer;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径,文件为gbk编码
String fileName = "E:\\file_gbk.txt";
// 创建流对象,默认UTF8编码
InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));
// 定义变量,保存字符
int charData;
// 使用默认编码字符流读取,乱码
while ((charData = isr1.read()) != -1) {
System.out.print((char)charData); // ????
}
isr1.close();
// 创建流对象,指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName) , "GBK");
// 使用指定编码字符流读取,正常解析
while ((charData = isr2.read()) != -1) {
System.out.print((char)charData);// 大家好
}
isr2.close();
}
}
6.4 OutputStreamWriter类
转换流
java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
?OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
?OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
构造举例,代码如下:
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
示例代码:
package com.atguigu.transfer;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "E:\\out_utf8.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
osw.close();
// 定义文件路径
String FileName2 = "E:\\out_gbk.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}
6.5 转换流理解图解
转换流是字节与字符间的桥梁!
7 数据流与对象流
前面学习的IO流,在程序代码中,要么将数据直接按照字节处理,要么按照字符处理。那么,如果读写Java其他数据类型的数据,怎么办呢?
String name = “巫师”;
int age = 300;
char gender = ‘男’;
int energy = 5000;
double price = 75.5;
boolean relive = true;
Student stu = new Student("张三",23,89);
Java提供了数据流和对象流来处理这些类型的数据:
?DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流(DataInputStream)将数据读入。
?DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
?ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。稍后可以使用 ObjectInputStream 将数据读入。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中接收这些数据或重构对象。
?ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
因为DataOutputStream和DataInputStream只支持Java基本数据类型和字符串的读写,而不支持Java对象的对象。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以下面直接介绍对象流ObjectOutputStream和ObjectInputStream即可。
?public ObjectOutputStream(OutputStream out):创建一个指定OutputStream的ObjectOutputStream。
?public ObjectInputStream(InputStream in):创建一个指定InputStream的ObjectInputStream。
构造举例,代码如下:
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
7.1 使用对象流读写各种类型的数据
ObjectOutpuStream也从OutputStream父类中继承基本方法:
?public void write(int b) :将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
?public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
?public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
?public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
?public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
还支持将各种Java数据类型的数据写入输出流中:
?public void writeBoolean(boolean val):写入一个 boolean 值。
?public void writeByte(int val):写入一个8位字节。
?public void writeShort(int val):写入一个16位的 short 值。
?public void writeChar(int val):写入一个16位的 char 值。
?public void writeInt(int val):写入一个32位的 int 值。
?public void writeLong(long val):写入一个64位的 long 值。
?public void writeFloat(float val):写入一个32位的 float 值。
?public void writeDouble(double val):写入一个64位的 double 值。
?public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。如果 s 为 null,则抛出 NullPointerException。根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入流中与将它作为 Object 写入流中明显不同。
ObjectInputStream除了从InputStream父类中继承基本方法之外,
?public int read():从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。
?public int read(byte[] b):从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
?public int read(byte[] b,int off,int len):从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
?public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
还支持从输入流中读取各种Java数据类型的数据:
?public boolean readBoolean():读取一个 boolean 值。
?public byte readByte():读取一个 8 位的字节。
?public short readShort():读取一个 16 位的 short 值。
?public char readChar():读取一个 16 位的 char 值。
?public int readInt():读取一个 32 位的 int 值。
?public long readLong():读取一个 64 位的 long 值。
?public float readFloat():读取一个 32 位的 float 值。
?public double readDouble():读取一个 64 位的 double 值。
?public String readUTF():读取 UTF-8 修改版格式的 String。
注意:读的顺序和方法与写的顺序和方法要一一对应。
示例代码:
package com.atguigu.object;
import org.junit.Test;
import java.io.*;
public class ReadWriteDataOfAnyType {
@Test
public void save() throws IOException {
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
oos.writeUTF(name);
oos.writeInt(age);
oos.writeChar(gender);
oos.writeInt(energy);
oos.writeDouble(price);
oos.writeBoolean(relive);
oos.close();
}
@Test
public void reload()throws IOException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
String name = ois.readUTF();
int age = ois.readInt();
char gender = ois.readChar();
int energy = ois.readInt();
double price = ois.readDouble();
boolean relive = ois.readBoolean();
System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);
ois.close();
}
}
7.2 序列化与反序列化概念
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:
ObjectOutputStream流中支持序列化的方法是:
?public final void writeObject (Object obj) : 将指定的对象写出。
ObjectInputStream流中支持反序列化的方法是:
?public final Object readObject () : 读取一个对象。
7.3 Serializable序列化接口与transient关键字
某个类的对象需要序列化输出时,该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。* 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
?该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
?静态变量的值不会序列化。因为静态变量的值不属于某个对象。
package com.atguigu.object;
import java.io.Serializable;
public class Employee implements Serializable {
public static String company; //static修饰的类变量,不会被序列化
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
", company=" + company +
'}';
}
}
package com.atguigu.object;
import org.junit.Test;
import java.io.*;
public class ReadWriteObject {
@Test
public void save() throws IOException {
Employee.setCompany("尚硅谷");
Employee e = new Employee("张三", "宏福苑", 23);
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
// 写出对象
oos.writeObject(e);
// 释放资源
oos.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
}
@Test
public void reload() throws IOException, ClassNotFoundException {
// 创建反序列化流
FileInputStream fis = new FileInputStream("employee.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取一个对象
Employee e = (Employee) ois.readObject();
// 释放资源
ois.close();
fis.close();
System.out.println(e);
}
@Test
public void writeString()throws IOException{
ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("str1.dat"));
oos1.writeUTF("atguigu");
oos1.writeInt(1);
oos1.close();
ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("str2.dat"));
oos2.writeObject("atguigu");
oos2.writeObject(1);
oos2.close();
}
}
7.4 反序列化失败问题
首先,对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。
其次,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:
?该类的序列版本号与从流中读取的类描述符的版本号不匹配
?该类包含未知数据类型
Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。如果没有声明serialVersionUID,则每次编译都会产生新的serialVersionUID序列化版本ID值,这样如果在序列化完成之后修改了类导致类重新编译,则原来的数据将无法反序列化。所以通常我们都会在实现Serializable接口时,声明一个serialVersionUID,并为其指定一个值。serialVersionUID必须是static和final修饰的long类型的数据,它的值由程序员随意指定即可。
如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
package com.atguigu.object;
import java.io.Serializable;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L; //增加serialVersionUID
public static String company; //static修饰的类变量,不会被序列化
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
", company=" + company +
'}';
}
}
7.5 序列化多个对象
如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。
package com.atguigu.object;
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
public class ReadWriteCollection {
@Test
public void save() throws IOException {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "宏福苑", 23));
list.add(new Employee("李四", "白庙", 24));
list.add(new Employee("王五", "平西府", 25));
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));
// 写出对象
oos.writeObject(list);
// 释放资源
oos.close();
}
@Test
public void reload() throws IOException, ClassNotFoundException {
// 创建反序列化流
FileInputStream fis = new FileInputStream("employees.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
// 读取一个对象
ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
// 释放资源
ois.close();
fis.close();
System.out.println(list);
}
}
8 重新认识System.out和Scanner
8.1 PrintStream类
我们每天都在用的System.out对象是PrintStream类型的。它也是IO流对象。
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;另外,PrintStream 可以设置自动刷新。
?PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
?PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
?PrintStream(OutputStream out) :创建新的打印流。
?PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。autoFlush如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 (‘’) 时都会刷新输出缓冲区。
?PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建新的打印流。
?PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。
?PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
image-20220131021502089
image-20220131021528397
package com.atguigu.systemio;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class TestPrintStream {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("io.txt");
ps.println("hello");
ps.println(1);
ps.println(1.5);
ps.close();
}
}
8.2 Scanner类
构造方法
?Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
?Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
?Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
?Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
常用方法:
?boolean hasNextXxx():如果通过使用nextXxx()方法,此扫描器输入信息中的下一个标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
?Xxx nextXxx():将输入信息的下一个标记扫描为一个Xxx
package com.atguigu.systemio;
import org.junit.Test;
import java.io.*;
import java.util.Scanner;
public class TestScanner {
@Test
public void test01() throws IOException {
Scanner input = new Scanner(System.in);
PrintStream ps = new PrintStream("1.txt");
while(true){
System.out.print("请输入一个单词:");
String str = input.nextLine();
if("stop".equals(str)){
break;
}
ps.println(str);
}
input.close();
ps.close();
}
@Test
public void test2() throws IOException {
Scanner input = new Scanner(new FileInputStream("1.txt"));
while(input.hasNextLine()){
String str = input.nextLine();
System.out.println(str);
}
input.close();
}
}
8.3 System类的三个IO流对象
System类中有三个常量对象:
?System.out
?System.in
?System.err
查看System类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
奇怪的是,
?这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
?这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
?这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
private static void checkIO() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setIO"));
}
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
9 JDK1.7之后引入新try..catch
9.1 IO流关闭和异常处理
package com.atguigu.io;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/*
JDK1.7新增的语法,称为try...catch...with...resource,专门为关闭资源和处理相应异常的新try...catch形式
语法格式:
try(
资源对象的创建和声明
){
可能发生异常的业务逻辑代码
}catch(异常类型1 参数名){
处理异常的代码
}catch(异常类型2 参数名){
处理异常的代码
}
这个形式的try...catch,可以保证在try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭。
这里有一个要求,在try()中的资源对象的类型必须实现java.lang.AutoClosable接口
*/
public class TestIOClose {
@Test
public void test05() {
try(
FileWriter fw = new FileWriter("d:/1.txt");
BufferedWriter bw = new BufferedWriter(fw);
){
bw.write("hello");
}catch(IOException e){
e.printStackTrace();
}
}
@Test
public void test03() {
FileWriter fw = null;//提取出来的目的是,为了在finally中仍然可以使用fw,bw
BufferedWriter bw = null;
try {
fw = new FileWriter("d:/1.txt");
bw = new BufferedWriter(fw);
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if(bw!=null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fw != null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test04() {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter("d:/1.txt");
bw = new BufferedWriter(fw);
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bw.close(); //如果这句代码关闭时发生异常了,下面fw.close()不执行,关闭可能不彻底
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
9.2 JDK1.7之后引入新try..catch
语法格式:
try(需要关闭的资源对象的声明){
业务逻辑代码
}catch(异常类型 e){
处理异常代码
}catch(异常类型 e){
处理异常代码
}
....
它没有finally,也不需要程序员去关闭资源对象,无论是否发生异常,都会关闭资源对象。
需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close方法。Closeable是AutoCloseable的子接口。Java7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口…)进行了改写,改写后资源类都是实现了AutoCloseable或Closeable接口,并实现了close方法。
示例代码:
@Test
public void test03() {
//从d:/1.txt(GBK)文件中,读取内容,写到项目根目录下1.txt(UTF-8)文件中
try(
FileInputStream fis = new FileInputStream("d:/1.txt");
InputStreamReader isr = new InputStreamReader(fis,"GBK");
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
){
String str;
while((str = br.readLine()) != null){
bw.write(str);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}