File类和IO流
java.io.File类
1 File类的介绍
FIle类是文件和目录路径名的抽象表现形式。
(1)File类是java.io包下代表与平台无关的文件和目录(文件夹)。
(2)我们在程序中要表现一个文件或者是目录是通过指定它的“路径”来表示的。
(3)它是一个抽象的表现形式 ==> 我们new的File对象,并不表示它一定(百分百)对应有这个文件或目录存在。
2、可以使用File类的对象干什么?
在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。
但是我们不能直接通过File对象进行文件内容的“读”和“写”,如果要对内容操作,必须依赖于IO流。
3、API
(1)构造器:
File(String pathname)
File(String parent, String child)
public class TestFile {
@Test
public void test02(){
File file = new File("d:/dyy","1.txt");
System.out.println(file);
}
@Test
public void test01(){
File file = new File("d:/1.txt");
System.out.println(file);
}
}
(2)方法
-
public String getName() :返回由此File表示的文件或目录的名称。
-
public String getPath():将此File转换为路径名字符串。
-
public long length():返回由此File表示的文件的长度。单位是字节。但是不能直接获取目录(文件夹)的大小。
-
public long lastModified():返回File对象对应的文件或目录的最后修改时间(毫秒值)
思考:数组.length 字符串.length() File对象.length() 的区别?
回答:数组.length属性,后面两个是方法。
思考:如何获取或计算文件夹的大小?
回答:把文件夹中每一级的文件的大小累加起来。
-
public String getPath():将此File转换为路径名字符串。path:构造器中指定的路径
-
public String getAbsolutePath():返回此File的绝对路径名字符串。 user.dir用户路径 + path
-
public String getCanonicalPath():返回此File对象所对应的规范路径名, 对绝对路径中出现的../(上一级)或.(当前目录)等非规范字符解析后的路径
-
public String getParent():获取父目录 。根据path获取的父目录
斜杆:
\ :在Java中是转义
/:用于表示路径,可以是操作系统路径,也可以是url(网址)的路径
早期时windows的文件系统路径是不支持/,只支持\,后来都支持了。
Java中为了考虑兼容所有的平台,建议大家使用/,或者是使用File中的一个常量
路径:
绝对路径:从根目录开始导航的路径,例如:d:\dyy\javase\HelloIO.java
windows文件目录系统的根是盘符
linux文件目录系统的根是 /
相对路径:路径的开头不是 盘符或者是 /。 例如: File f2 = new File("HelloIO.java");
在Java程序中,相对路径是相对谁?(注意 在JUnit中的相对路径和在main方法中的绝对路径是不同的)
例如:D:\javaee\JavaSE20210622\code\day0720_teacher_code JUnit读取相对路径是基于模块
例如:D:\javaee\JavaSE20210622\code main方法读取相对路径是基于项目project
-
public boolean exists() :此File表示的文件或目录是否实际存在。
-
public boolean isDirectory():此File表示的是否为目录。当这个File对象指定的目录不存在时,就算File对象代表的是目录也是返回false
-
public boolean isFile() :此File表示的是否为文件。
当这个File对象指定的文件不存在时,就算File对象代表的是文件也是返回false。当目录或文件不存在时,仅仅是JVM中一个普通对象,和操作系统中的文件或目录并无关,所以无法判断它是文件还是目录。 包括其他的属性,例如:大小、修改时间等都是默认的。
为什么目录或文件不存在时,获取路径相关的信息可以呢? 因为路径相关信息是根据new对象时,指定的路径值来计算的。
public class TestFile1 {
@Test
public void test11() throws IOException {
File f3 = new File("D:/1.txt");
System.out.println(f3.exists());//true
System.out.println(f3.isDirectory());//false
System.out.println(f3.isFile());//true
}
@Test
public void test10() throws IOException{
File f3 = new File("../../HelloIO.java");
System.out.println(f3.exists());//false
System.out.println(f3.isDirectory());//false
System.out.println(f3.isFile());//false
}
@Test
public void test9() throws IOException{
File f3 = new File("../../HelloIO.java");
System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code
System.out.println("文件/目录的名称:" + f3.getName());//HelloIO.java
System.out.println("文件/目录的构造路径名:" + f3.getPath());//..\..\HelloIO.java
System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\..\..\HelloIO.java
System.out.println("文件/目录的规范路径名:" + f3.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\HelloIO.java
System.out.println("文件/目录的父目录名:" + f3.getParent());//..\..
}
public static void main(String[] args)throws IOException {
File f2 = new File("HelloIO.java");
System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code
System.out.println("文件/目录的名称:" + f2.getName());//HelloIO.java
System.out.println("文件/目录的构造路径名:" + f2.getPath());//HelloIO.java
System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\HelloIO.java
System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\code\HelloIO.java
System.out.println("文件/目录的父目录名:" + f2.getParent());//null
}
@Test
public void test7() throws IOException{
System.out.println(File.separator);//会根据当前平台自动识别默认的路径分隔符
File file1 = new File("java" + File.separator + "io" + File.separator + "Hello.java");
File file2 = new File("java/io/Hello.java");
}
@Test
public void test6() throws IOException{
File f2 = new File("HelloIO.java");
System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code
System.out.println("文件/目录的名称:" + f2.getName());//HelloIO.java
System.out.println("文件/目录的构造路径名:" + f2.getPath());//HelloIO.java
System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\HelloIO.java
System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\HelloIO.java
System.out.println("文件/目录的父目录名:" + f2.getParent());//null 根据构造器中指定的路径来获取parent
}
@Test
public void test5() throws IOException {
File f1 = new File("d:\\dyy\\javase\\HelloIO.java");//HelloIO.java是一个文件
System.out.println("文件/目录的名称:" + f1.getName());//HelloIO.java
System.out.println("文件/目录的构造路径名:" + f1.getPath());//d:\dyy\javase\HelloIO.java
System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());//d:\dyy\javase\HelloIO.java
System.out.println("文件/目录的规范路径名:" + f1.getCanonicalPath());//d:\dyy\javase\HelloIO.java
System.out.println("文件/目录的父目录名:" + f1.getParent());//d:\dyy\javase
}
@Test
public void test04(){
File file = new File("d:/hello_210622Java_柴林燕_JavaSE");
System.out.println("name=" + file.getName());
System.out.println("path=" + file.getPath());
System.out.println("length=" + file.length());
System.out.println("lastModified=" + file.lastModified());
System.out.println("lastModified=" + new Date(file.lastModified()));
}
@Test
public void test03(){
File file = new File("d:/1.txt");
System.out.println("name=" + file.getName());
System.out.println("path=" + file.getPath());
System.out.println("length=" + file.length());
System.out.println("lastModified=" + file.lastModified());
System.out.println("lastModified=" + new Date(file.lastModified()));
}
}
../../HelloIO.java
为什么可以这样写?
因为在命令行中:
cd .. 表示返回上级目录
cd . 表示当前目录
-
public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
-
public boolean delete() :删除由此File表示的文件或目录。 只能删除空目录。 删除后回收站中也没有。操作时一定要谨慎。
-
public boolean mkdir() :创建由此File表示的目录。
-
public boolean mkdirs() :创建由此File表示的目录,如果父目录不存在,也一并创建。
思考:如何删除非空目录? 思路:逐级删除,先把目录的下一级删除,然后才能删除自己。
- public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
- public File[] listFiles():返回一个File数组,表示该File目录中的所有的子文件或目录。
- public File[] listFiles(FileFilter filter):返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(java.io.File) 方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。FileFilter是一个File过滤器接口。
API测试
public class TestFile2 {
@Test
public void test11() {
File dir = new File("d:/hello_210622Java_柴林燕_JavaSE");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile();
}
});//用匿名内部类的形式实现FileFilter
for (File file : files) {
System.out.println(file);
}
}
@Test
public void test10() {
File dir = new File("d:/hello_210622Java_柴林燕_JavaSE");
File[] files = dir.listFiles();
for (File sub : files) {
System.out.println(sub);
}
}
@Test
public void test09() {
File file = new File("d:/hello_210622Java_柴林燕_JavaSE");
String[] strings = file.list();
for (String string : strings) {//数组也支持foreach
System.out.println(string);
}
}
@Test
public void test08() {
File file = new File("java");//相对路径
file.delete();
}
@Test
public void test07() {
File file = new File("hello");//相对路径
file.delete();
}
@Test
public void test06() {
File file = new File("d:/1.txt");//绝对路径
file.delete();
}
@Test
public void test05() throws IOException {
File dir = new File("java/dyy");//相对路径
dir.mkdirs();
}
@Test
public void test04() throws IOException {
File dir = new File("hello");//相对路径
dir.mkdir();
}
@Test
public void test03() throws IOException {
File file = new File("/222.txt");//相对路径
file.createNewFile();
}
@Test
public void test02() throws IOException {
File file = new File("22.txt");//相对路径
file.createNewFile();
}
@Test
public void test01() throws IOException {
File file = new File("d:/2.txt");
file.createNewFile();
}
}
4 如何获取某个目录的所有下一级(包括下一级的下一级)?
思路:先获取第一级,然后如果第一级中有目录,继续获取它的下一级。
如何实现?
我们可以声明一个方法,获取某个目录的下一级,然后得到它的下一级之后,再对下一级递归调用当前的方法。
5 如何计算某个文件夹的大小?
思路:把文件夹中每一级的文件的大小累加起来。
如何实现?
定义一个方法,可以累加某个文件夹的下一级的大小。如果它的下一级仍然是文件夹,那么再递归调用当前方法。
6 删除非空目录?
思路:逐级删除,先把目录的下一级删除,然后才能删除自己。
如何实现?
声明一个方法,可以删除某个目录的下一级如果它的下一级仍然是目录,再继续调用当前方法。 谨慎选择测试目录!!!
IO流
1、IO代表什么?
I:input 输入
O:output 输出
Java中用于数据的输入和输出的API。
io:第一代
nio:第二代
2、IO的特点:
(1)单向的
输入流:只能读数据
输出流:只能写数据
(2)阻塞式
3、IO流的分类
(1)按照方向分:
输入流和输出流
(2)按处理数据的方式:
字节流和字符流
字节流:以字节为单位,适用于处理任意类型的数据。
任意类型:字符、数字、图片、视频、音频等
字符流:以字符为单位,仅仅适用于纯文本数据
哪些文件是纯文本?
.txt,.java,.html,.css,.js,....
(3)按照角色不同
节点流和处理流
节点流:与数据的端点(起点和终点)直接连接的IO流,比如:FileInputStream等,ByteArrayInputStream等,
处理流:是指给其他IO流增加辅助功能用的,或者说用于包装其他IO流的IO流,即不能单独使用。
例如:BufferedInputStream等,DataInputStream等,ObjectInputStream等
增加缓冲功能(提高效率) 对其他数据类型做处理用 对对象做处理的
4、四个抽象基类(超类、抽象类)
InputStream:字节输入流系列的父类
OutputStream:字节输出流系列的父类
Reader:字符输入流系列的父类
Writer:字符输出流系列的父类
IO流类型的实现类:
读写文件系列的IO流:
FileInputStream(路径)
FileOutputStream(路径)
FileReader(路径)
FileWriter(路径)
读写文件系列的IO流的包装流:(装饰器模式)
BufferedInputStream(接收FileInputStream)
BufferedOutputStream(接收FileOutputStream)
BufferedReader(接收FileReader)
BufferedWriter(接收FileWriter)
FileInputStream:用于读文件,以字节的方式,适用于任何类型的文件。 但是,如果要在控制台显示文件的内容,只能以文本文件为例。
将字节读取转换为字符读取的转换流
InputStreamReader(接收InputStream)
OutputStreamWriter(接收OutStream)
数据流(游戏数据等,存取自定义的文件类型)(变量到文件)
DataInputStream:(接收InputStream)
DataOutputStream:(接收OutStream)
对象流 (对象到文件、网络)
ObjectOutputStream(接收OutStream)
ObjectInputStream(接收InputStream)
打印流
PrintStream(可以被BufferedWriter包装)
Scanner(可以被BufferedReader包装)
(1)FileInputStream:
(1)int read():一次读取1个字节,如果已经到达流末尾,返回-1,否则返回读取的内容的字节值
(2)int read(byte[] b):一次读取多个字节,读取的字节放到b数组中,如果已经到达流末尾,返回-1,否则返回本次读取的字节数量。
(3)int read(byte[] b,int off,int len):一次读取多个字节,读取的字节放到b数组中,如果已经到达流末尾,返回-1,否则返回本次读取的字节数量。
(3)和(2)不同的是,(2)的话一次最多读取b.length个,(3)的话一次最多读取len个。(2)的话,读取完的数据是从b数组的[0]开始存储(3)的话,读取完的数据是从b数组的[off]开始存储
public class TestFileInputStream {
@Test
public void test04() throws IOException {
//(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
FileInputStream fis = new FileInputStream("d:/1.txt");
//(2)开始读取
byte[] data = new byte[3];
int len;
StringBuilder s = new StringBuilder();//可变字符序列
while((len = fis.read(data))!=-1){
s.append(new String(data,0, len));
}
System.out.println(s);
//(3)关闭
fis.close();
}
@Test
public void test03() throws IOException {
//(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
FileInputStream fis = new FileInputStream("d:/1.txt");
//(2)开始读取
byte[] data = new byte[3];
int len;
while((len = fis.read(data))!=-1){
System.out.println("本次读取:" + len + "个字节");
System.out.println("本次读取的字节内容:" + Arrays.toString(data));
System.out.println("本次读取的字符内容:" + new String(data,0,len));
/*
把字节数组的内容转为字符-->解码
String(byte[] bytes) :用整个字节数组的内容构建字符串
String(byte[] bytes, int offset, int length) :用部分字节数组的内容构建字符串,从bytes[offset]开始,取length个字节
*/
}
//(3)关闭
fis.close();
}
@Test
public void test02() throws IOException {
//(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
FileInputStream fis = new FileInputStream("d:/1.txt");
//(2)开始读取
byte[] data = new byte[3];
System.out.println(fis.read(data));//3
System.out.println(fis.read(data));//1
System.out.println(fis.read(data));//-1
//(3)关闭
fis.close();
}
//FileNotFoundException是IOException的子类
@Test
public void test01() throws IOException {
//(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
FileInputStream fis = new FileInputStream("d:/1.txt");
//(2)开始读取
System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
//(3)关闭IO流
//因为IO流操作仅仅依赖JVM是办不到的,是需要依赖OS(操作系统)来协助的。
//IO操作除了相应的对象要在JVM中占内存,还需要占用OS的相应内存
//所以我们用完之后,需要告诉OS释放对应内存。
//JVM的内存由GC会自动回收。
fis.close();
}
}
(2)FileOutputStream:
用于输出数据到文件,以字节的方式。适用于任意类型的文件。
说明:在当前程序中,暂时以文本数据演示。
OutputStream系列的方法:
(1)write(int b):一次输出一个字节
(2)write(byte[] b):一次输出整个字节数组的内容
(3)write(byte[] b, int off, int len):一次输出部分字节数中的内容,从b[off]开始,len个字节。
public class TestFileOutputStream {
@Test
public void test04() throws IOException {
//(1)创建FileOutputStream流,参数是
FileOutputStream fos = new FileOutputStream("d:/2.txt", true);//如果文件不存在,会自动创建
//如果文件存在,就在后面追加内容
//文件存在,会自动覆盖
//(2)输出内容 " xiao gao",接在2.txt现在内容的后面
/*
byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
*/
fos.write(" xiao gao".getBytes());
//(3)关闭
fos.close();
}
@Test
public void test03() throws IOException {
File file = new File("d:/2.txt");
file.createNewFile();//如果存在,就不创建
}
@Test
public void test02() throws IOException {
//(1)创建FileOutputStream流,参数是
FileOutputStream fos = new FileOutputStream("d:/2.txt");//如果文件不存在,会自动创建
//文件存在,会自动覆盖
//(2)输出内容 "hi"
/*
byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
*/
fos.write("hi".getBytes());
//(3)关闭
fos.close();
}
@Test
public void test01() throws IOException {
//(1)创建FileOutputStream流,参数是
FileOutputStream fos = new FileOutputStream("d:/2.txt");//如果文件不存在,会自动创建
//(2)输出内容 "hello"
/*
byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
*/
fos.write("hello".getBytes());
//(3)关闭
fos.close();
}
}
(3)FileReader:
用于读取纯文本文件,以字符的方式Reader系列的IO流的方法:
(1)int read() :一次读取一个字符,如果到达流末尾返回-1,否则返回该字符的Unicode编码值
(2)int read(char[] cbuf):一次多个字符,放到cbuf数组中,从[0]开始存储,如果到达流末尾返回-1,否则返回本次读取的字符数。最多一次读取cbuf.length个
(3)int read(char[] cbuf, int off, int len)一次多个字符,放到cbuf数组中,从[off]开始存储,如果到达流末尾返回-1,否则返回本次读取的字符数,最多一次读取len个
FileWriter:用于输出数据到纯文本,以字符的方式
public class TestFileReader {
@Test
public void test01() throws IOException {
//(1)创建FileReader对象,参数是指定从哪个文件读取
FileReader fr = new FileReader("d:/2.txt");
//(2)读取
int len;
char[] data = new char[10];
while((len = fr.read(data)) != -1){
System.out.print(new String(data,0,len));
}
//(3)关闭
fr.close();
}
}
(4)FileWriter:
(1) void write(int c):一次写一个字符
(2) void write(char[] cbuf):一次写整个字符数组
(3)void write(char[] cbuf, int off, int len)一次写部分字符数组,从[off]开始写len个
(4)void write(String str):一次写一个字符串
(5)void write(String str, int off, int len) :一次写字符串的一部分,从[off]开始写len个
public class TestFileWriter {
@Test
public void test02() throws IOException {
//(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
FileWriter fw = new FileWriter("d:/1.txt",true);
//(2)写数据 "hello"
fw.write("hello");
//(3)关闭
fw.close();
}
@Test
public void test01() throws IOException {
//(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
FileWriter fw = new FileWriter("d:/1.txt");
//(2)写数据 "hello"
fw.write("hello");
//(3)关闭
fw.close();
}
}
需求:实现复制文件的基础版
public class Files {
/**
* 复制文件
* @param srcFilePathName String 源文件路径名
* @param destFilePathName String 目标文件路径名
*/
public static void copyFile(String srcFilePathName, String destFilePathName) throws IOException {
//判断srcFilePathName是否是文件夹或者不存在
File srcFile = new File(srcFilePathName);
if(!srcFile.exists()){
throw new FileNotFoundException(srcFilePathName + "源文件不存在");
}
if(!srcFile.isFile()){
throw new IOException(srcFilePathName +"不是一个文件");
}
File destFile = new File(destFilePathName);
if(destFile.isDirectory()){
//把srcFilePathName文件复制到destFilePathName文件夹中
destFilePathName = destFilePathName + "/" + srcFile.getName();
//destFilePathName是目标文件夹的路径
//srcFile.getName():是源文件的名称
//destFilePathName + "/" + srcFile.getName():新文件的路径名
}
//(1)先创建FileInputStream的对象,用于读取源文件的数据
FileInputStream fis = new FileInputStream(srcFilePathName);
//(2)再创建FileOutputStream的对象,用于输出数据到目标文件
FileOutputStream fos = new FileOutputStream(destFilePathName);
//(3)一边读一边写
int len;
byte[] data = new byte[1024];//1KB
//fis.read(data)把数据从fis读取到data中
while((len = fis.read(data))!=-1){
//把数据从data中输出到fos中
fos.write(data, 0, len);
// fos.write(data);//如果最后一次读取的字节数少于data.length,会导致上次读取的数据重复写到文件中
}
//(4)关闭
fis.close();
fos.close();
}
}
package com.dyy.io;
import org.junit.Test;
import java.io.IOException;
public class TestFiles {
@Test
public void test02() throws IOException {
Files.copyFile("D:\\day0720_01集合框架集图.avi","java");
}
@Test
public void test01() throws IOException {
Files.copyFile("D:\\dyy\\javaee\\JavaSE20210622\\video\\day0720_01集合框架集图.avi","d:\\day0720_01集合框架集图.avi");
}
}
(5)转换流:OutputStreamWriter
OutputStreamWriter:用于将字节流类型的IO流转为字符流类型的IO流, 数据是从字符类型的数据转为字节类型的数据输出。
OutputStreamWriter是一个处理流,用来包装其他IO流,依赖于其他IO流。
OutputStreamWriter(OutputStream out):仍然使用平台默认的编码
OutputStreamWriter(OutputStream out, String charsetName):可以指定编码
什么情况下用它?
当我们在输出数据时,程序中的编码与文件的编码不一致,那么可以使用它进行处理。
public class TestOutputStreamWriter {
@Test
public void test03() throws IOException {
//(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
FileOutputStream fos = new FileOutputStream("d:/1.txt",true);
//(2)写数据 "hello"
fos.write("hello".getBytes("GBK"));
//(3)关闭
fos.close();
}
@Test
public void test02() throws IOException {
//(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
FileOutputStream fos = new FileOutputStream("d:/1.txt",true);
//(2)使用OutputStreamWriter进行包装
OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
//从IO流的类型转换看,是不是像 把fos字节流类型,转为osw字符流
//(2)写数据 "hello"
//先把数据"hello"写到osw字符流中,以字符的方式,然后 oswIO流再把数据 处理(编码)写到fos,以字节的方式,最后到达"d:/1.txt"
osw.write("hello");
//(3)关闭
osw.close();
fos.close();
}
@Test
public void test01() throws IOException {
//(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
FileWriter fw = new FileWriter("d:/1.txt",true);//不支持编码的转换
//默认是以程序的编码输出的,例如当前程序是UTF-8
//(2)写数据 "hello"
fw.write("hello");
//(3)关闭
fw.close();
}
}
(6)转换流:InputStreamReader
InputStreamReader:用于将字节流类型的IO流转为字符流类型的IO流, 数据是从字节类型的数据转为字符类型的数据输入。
InputStreamReader也是处理流,用于包装其他IO流。
InputStreamReader(InputStream in):用默认编码处理
InputStreamReader(InputStream in, String charsetName) :指定编码处理
转换流:InputStreamReader和OutputStreamWriter
public class TestInputStreamReader {
@Test
public void test03() throws IOException {
FileInputStream fis = new FileInputStream("d:/1.txt");
InputStreamReader isr = new InputStreamReader(fis, "GBK");
char[] data = new char[10];
isr.read(data);
System.out.println(new String(data));
isr.close();
fis.close();
}
@Test
public void test02() throws IOException {
FileInputStream fis = new FileInputStream("d:/1.txt");
byte[] data = new byte[10];
fis.read(data);
System.out.println(new String(data, "GBK"));
fis.close();
}
@Test
public void test01() throws IOException {
FileReader fr = new FileReader("d:/1.txt");
char[] data = new char[10];
fr.read(data);
System.out.println(new String(data));
fr.close();
}
}
(7)缓冲流(BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter)
缓冲IO流:
BufferedInputStream:在其他字节输入流的基础上增加缓冲功能
BufferedOutputStream:在其他字节输出流的基础上增加缓冲功能
BufferedReader:在其他字符输入流的基础上增加缓冲功能
BufferedWriter:在其他字符输出流的基础上增加缓冲功能
以BufferedInputStream为例,说明他们是缓冲流,也是处理流,需要依赖其他IO流。
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
为什么使用缓冲IO流就能加快运行?
缓冲流内部有一个缓冲区(本质就是一个数组),这个数组的大小/长度默认是8192。
当我们从文件读取数据时,会先把数据读取到缓冲流的缓冲区中,然后当缓冲区满的时候,或者我们调用flush()或close()时,会把缓冲区的数据一次读取过来。或者这么说,原来的话,从文件读取的话,byte[] data,一次从文件读取data.length(例如1024个字节),现在的话,从文件先缓冲8192个字节在缓冲区中,然后我们用byte[] data,从缓冲区中一次取1024个字节。从文件读取的话,是从硬盘到JVM 内存,从缓冲区读取的话,是从JVM内存到JVM内存。
public class TestBuffered {
@Test
public void test02()throws IOException {
long start = System.currentTimeMillis();
Files.copyFile("d:/day0720_01集合框架集图.avi","d:/3.avi");
long end = System.currentTimeMillis();
System.out.println("耗时:" + (start - end));//1508
}
@Test
public void test01()throws IOException {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("d:/day0720_01集合框架集图.avi");
FileOutputStream fos = new FileOutputStream("d:/2.avi");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] data = new byte[1024];
int len;
while((len = bis.read(data)) != -1){
bos.write(data, 0, len);
}
bis.close();
fis.close();
bos.close();
fos.close();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (start - end));//301
}
}
问题:不使用缓冲流,自己把byte[] data数组定义的大一点,是不是也可以很快呢?
答案:肯定的。但是不可能无限大的,系统分配给JVM的内存是有限的。
(8)数据流(DataInputStream,DataOutputStream)
DataInputStream:
DataOutputStream:
一对,用DataOutputStream写,用DataInputStream读,并且读的顺序要与写的顺序一致。
需求:要将程序中的许多变量的值输出到文件中,下次读取时,再给同样类型的变量赋值。
场景:单机游戏 玩的过程中,临时需要退出,需要将一些游戏的状态保存,下次从当前的状态继续玩。
DataOutputStream(OutputStream out) 也是处理流(包装流,装饰流),需要依赖其他IO流
DataOutputStream输出的数据不是纯文本的,所以建议不要用.txt这样的纯文本文件存储,可以用.dat等类型的文件保存。 .dat是我们自己随便写的一个文件后缀名。
为什么可以随便写? 因为这个文件不是由其他程序/软件来读取的,它是由我们的另一段Java程序读取。
public class TestDataStream {
@Test
public void test04() throws IOException {
FileInputStream fis = new FileInputStream("d:/game.dat");
DataInputStream dis = new DataInputStream(fis);
String role = dis.readUTF();
int age = dis.readInt();
char gender = dis.readChar();
double power = dis.readDouble();
boolean b = dis.readBoolean();
System.out.println("role = " + role);
System.out.println("age = " + age);
System.out.println("gender = " + gender);
System.out.println("power = " + power);
System.out.println("b = " + b);
dis.close();
fis.close();
}
@Test
public void test03() throws IOException {
//游戏角色
String role = "小高";
int age = 250;
char gender = '妖';
double energy = 89.5;
boolean isLiveable = false;
FileOutputStream fos = new FileOutputStream("d:/game.dat");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeUTF(role);
dos.writeInt(age);
dos.writeChar(gender);
dos.writeDouble(energy);
dos.writeBoolean(isLiveable);
dos.close();
fos.close();
}
@Test
public void test02() throws IOException {
FileReader fr = new FileReader("d:/game.txt");
// String role = fr.read();//读取几个字符是角色名
// int age = fr.read();//读取几个字符是年龄
}
@Test
public void test01() throws IOException {
//游戏角色
String role = "小高";
int age = 250;
char gender = '妖';
double energy = 89.5;
boolean isLiveable = false;
//使用FileWriter输出
FileWriter fw = new FileWriter("d:/game.txt");
fw.write(role);
fw.write(age);
fw.write(gender);
// fw.write(energy);//错误
// fw.write(isLiveable);//错误
fw.write(energy+"");
fw.write(isLiveable+"");
// 除非进行类型的转换,转换成String,下次读取时,非常麻烦,没法处理
//如果要这么处理,也可以一个变量的值占一行,恢复时,按行处理
fw.close();
}
}
(9)对象流
ObjectOutputStream:把对象转为字节序列,这个过程称为“序列化”
ObjectInputStream:把字节序列中是数据“重构”或“还原”成Java对象,这个过程称为“反序列化”
什么情况下使用? 当我们Java程序中,想要直接输出“对象”到文件、或者发送“对象”到网络中等情况时,就可以使用对象IO流。
注意:
①要通过ObjectOutputStream流输出的对象的类型必须实现java.io.Serializable接口。否则会发生java.io.NotSerializableException:不支持序列化异常Serializable接口是一个标识型接口,没有抽象方法的。只是标识类型的作用,就像Cloneable接口一样。
在底层中,要被克隆的对象,或者说当我们调用某个对象的clone方法时,JVM会检查这个对象是否属于Cloneable类型,只有属于这个类型的,才能帮你做克隆。
在底层中,要被序列化的对象,或者说当我们调用ObjectOutputStream的writeObject方法时,JVM会检测这个对象是否属于Serializable类型,只有属于这个类型的,才能帮你做序列化。
比喻:出国时候,上岸/下机时,会有人检测你是否拥有“中华人民共和国的护照”, 如果有,那么就正常通关,否则就要遣回。
②通过ObjectOutputStream流输出的数据到达文件后,我们也看不懂,因为里面不是纯文本,它是给另一个段Java程序看的。
③在反序列化过程中,可能会遇到ClassNotFoundException异常
④结论:凡是实现Serializable接口,都要加一个private static final long serialVersionUID。
当我们序列化完成之后,对所序列化的对象的类型(例如:Student)做了修改,然后再去运行反序列化程序时,就报:java.io.InvalidClassException,类无效异常。
例如:
java.io.InvalidClassException:
com.dyy.io.Student; local class incompatible:
stream classdesc serialVersionUID = -355407171577198872,
local class serialVersionUID = -6337391953310496088
因为我们Student类实现了Serializable接口,每次编译Student类,编译器会自动给这个类的.class标记一个序列化版本ID(serialVersionUID),只要重新编译(即修改了类),serialVersionUID就会改变。这样做的目的是,防止我们序列化对象之后,对类做大的改动,影响我们对象的反序列化。
例如:原来有name,age,分别是String和int类型,后面把这两个的类型给变了,这样的话,如果没有机制阻止这种情况的话,是有安全问题的。一律修改都不允许。
如何解决呢?
我们刚才的修改操作(加了无参构造),并不会影响对象的反序列化的安全问题。我们希望这样的修改操作,被允许,那么怎么办?可以给这个类(例如:Student类)加一个序列化版本ID(serialVersionUID)。
序列化版本ID(serialVersionUID)要求是:
A:long类型
B:static和final
为什么? static表示静态的,所有对象共享的。
final表示最终的,不能修改的
C:通常也会是private
说明:如果是在所有序列化之前,就加了serialVersionUID,那么这个值是多少都可以,通常我们会在源码中看到1L。
⑤是不是对象中所有的属性都要序列化?
A:对象的属性参与序列化,都要实现Serializable接口。(基本数据类型除外)
B:静态的变量是不会序列化的
为什么?
因为我们序列化是针对“对象”,也就是说,要保存/持久化的是对象的状态(非静态的变量),和类变量无关。
因为类变量是所有对象“共享的”,不应该由其中一个对象来“保存”。
C:如果某个成员变量的值不想要序列化,可以加一个transient来修饰,否则实例变量都会序列化。
transient:瞬时的,易变化的。
举例:
学生类型对象
//学生类
public class Student implements Serializable {
private static final long serialVersionUID = -355407171577198872L;
private String name;
private int age;
private transient String password; //密码
private Teacher teacher; //班主任
private static String country;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age, String password, Teacher teacher) {
this.name = name;
this.age = age;
this.password = password;
this.teacher = teacher;
}
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;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
Student.country = country;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
", teacher=" + teacher +
", country=" + country +
'}';
}
}
//
public class TestObjectIO {
@Test
public void test04() throws IOException, ClassNotFoundException {
//读取stu2.dat文件中的Student对象
//要从文件读取,需要:FileInputStream
FileInputStream fis = new FileInputStream("stu2.dat");//相对路径
//要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
Object object = ois.readObject();
System.out.println(object);
//关闭
ois.close();
fis.close();
}
@Test
public void test03() throws IOException {
//创建学生对象
Student stu = new Student("张三",78,"123456", new Teacher());
Student.setCountry("中国");//静态变量
// stu.setCountry("中国");
//要把stu对象直接保存到 stu2.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu2.dat");//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出stu对象
oos.writeObject(stu);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
@Test
public void test02() throws IOException, ClassNotFoundException {
//读取stu.dat文件中的Student对象
//要从文件读取,需要:FileInputStream
FileInputStream fis = new FileInputStream("stu.dat");//相对路径
//要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
Object object = ois.readObject();
System.out.println(object);
//关闭
ois.close();
fis.close();
}
@Test
public void test01() throws IOException {
//创建学生对象
Student stu = new Student("张三",78);
//要把stu对象直接保存到 stu.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu.dat");//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出stu对象
oos.writeObject(stu);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
}
问题:如何序列化多个对象
当我们序列化不止一个对象,怎么办?
问题:
使用追加模式,先后输出了两个对象,
在反序列化时,连续读取两个对象时,发生了java.io.StreamCorruptedException: invalid type code: AC异常。
这个异常的意思时,无效的编码标识。
因为我们每次序列化完成之后,会在后面加一个“序列化结束标识”,
那么我们如果使用追加模式,会在“序列化结束标识”后面追加新的对象信息。
在反序列化时,读完一个对象,再读第二个对象时,它先遇到的是“序列化结束标识”,而不是一个对象的信息, 所以认为invalid type code: AC
如何解决?
通过我们把多个对象是用“容器”保存,然后输出“容器”,
下次如果还要再加新对象,会先把原来的数据反序列化,在反序列化的容器中,添加新对象,然后再序列化。
public class TestManyObjects {
@Test
public void test08() throws IOException, ClassNotFoundException {
//读取stu3.dat文件中的Student对象
//要从文件读取,需要:FileInputStream
FileInputStream fis = new FileInputStream("stu4.dat");//相对路径
//要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
Object object1 = ois.readObject();
System.out.println(object1);
//关闭
ois.close();
fis.close();
}
@Test
public void test07() throws IOException, ClassNotFoundException {
//创建学生对象
Student stu = new Student("李四",96,"123456", new Teacher());
//先把原来的读取回来
FileInputStream fis = new FileInputStream("stu4.dat");//相对路径
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
list.add(stu);
ois.close();
fis.close();
//要把stu对象直接保存到 stu3.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu4.dat");//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出list对象
oos.writeObject(list);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
@Test
public void test06() throws IOException {
//创建学生对象
Student stu = new Student("张三",78,"123456", new Teacher());
ArrayList<Student> list = new ArrayList<>();
list.add(stu);
//要把list对象直接保存到 stu4.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu4.dat",true);//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出list对象
oos.writeObject(list);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
@Test
public void test05() throws IOException, ClassNotFoundException {
//读取stu3.dat文件中的Student对象
//要从文件读取,需要:FileInputStream
FileInputStream fis = new FileInputStream("stu3.dat");//相对路径
//要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
Object object1 = ois.readObject();
System.out.println(object1);
Object object2 = ois.readObject();
System.out.println(object2);
//关闭
ois.close();
fis.close();
}
@Test
public void test04() throws IOException {
//创建学生对象
Student stu = new Student("李四",96,"123456", new Teacher());
//要把stu对象直接保存到 stu3.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu3.dat",true);//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出stu对象
oos.writeObject(stu);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
@Test
public void test03() throws IOException {
//创建学生对象
Student stu = new Student("张三",78,"123456", new Teacher());
//要把stu对象直接保存到 stu3.dat
//要输出到文件:FileOutputStream
FileOutputStream fos = new FileOutputStream("stu3.dat");//相对路径
//因为要直接输出对象,所以需要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出stu对象
oos.writeObject(stu);//把对象转为字节序列输出
//如果stu对象的类型Student没有实现java.io.Serializable接口
//那么就会把java.io.NotSerializableException:不支持序列化异常
//关闭
oos.close();
fos.close();
}
}
补充:Externalizable接口
问题:除了Serializable接口之外,还可以实现什么接口进行序列化?
答案:java.io.Externalizable接口
这个Externalizable接口有两个抽象方法,
-
void readExternal(ObjectInput in)
-
void writeExternal(ObjectOutput out)
这两个抽象方法需要重写。
作用是定义,序列化时,
(1)哪些属性序列化,包括static和transient也可以手动序列化,但是不建议这么做
(2)顺序
关于哪些属性序列化和反序列化,由程序员自己定。
Externalizable接口有一个要求,被序列化的对象的类型必须包含“无参构造”
public class Chinese implements Externalizable {
private static final long serialVersionUID = 1L;
private static String county;
private String name;
private transient int age;
public Chinese() {
}
public Chinese(String name, int age) {
this.name = name;
this.age = age;
}
public static String getCounty() {
return county;
}
public static void setCounty(String county) {
Chinese.county = county;
}
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 void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(county);
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
county = in.readUTF();
name = in.readUTF();
age = in.readInt();
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestExternalizable {
@Test
public void test02() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("chinese.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Chinese.setCounty("中华人民共和国");
System.out.println(ois.readObject());
System.out.println(Chinese.getCounty());
ois.close();
fis.close();
}
@Test
public void test01() throws IOException {
Chinese chinese = new Chinese("张三",23);
Chinese.setCounty("中国");
FileOutputStream fos = new FileOutputStream("chinese.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(chinese);
oos.close();
fos.close();
}
}
(10)打印流
PrintStream: PrintWriter:学习web时,服务器端给客户端返回消息时,response响应对象.getWriter得到的就是它
System.out对象就是PrintStream类型的。
PrintStream中重载了很多print和println方法,用于输出信息。 细节: (1)println()有无参形式 print(x)没有无参形式 (2)print和println方法支持char[]
public class TestPrintStream {
@Test
public void test03(){
int[] arr = {1,2,3,4};
System.out.println(arr);//[I@5d6f64b1
// 自动调用对象的toString方法,默认的toString方法就是返回 对象的运行时类型@对象的hashCode值
char[] letters = {'a','b','c'};
System.out.println(letters);//abc
}
@Test
public void test02(){
System.out.println();
// System.out.print();//报错
}
@Test
public void test01(){
PrintStream ps = System.out;
ps.println(1);
ps.println(1.0);
ps.println("hello");
}
}
(11)System类中的IO流
System类中有三个IO流对象:
System.in:InputStream
System.out:PrintStream
System.err:PrintStream
疑问1:在System类中三个常量对象的声明如下:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
in,out,err对象是final修饰,不能修改的。
看代码,它们已经初始化为null,后面怎么弄的?
我们所说的final修饰的常量不能修改是针对Java语法来说的。
System类中in,out,err对象的创建是由C来完成的。
在System类初始化时:
private static native void registerNatives();
static {
registerNatives();//在这个本地方法中,完成了对in,out,err对象的初始化
}
疑问2:在System类中,提供了如下三个set方法
setIn
setOut
setErr
疑问的点,在Java中final修饰的成员变量,是没有set方法。
回答:这些set方法最终的实现,是由C完成的。
疑问3:既然有set方法,说明可以改,即可以重定向
public class TestSystem {
public static void main(String[] args) throws FileNotFoundException {
//重定向System.out输出到d:/1.txt
PrintStream old = System.out;
System.setOut(new PrintStream("d:/1.txt"));
System.out.println("hello");
System.out.println("world");
System.setOut(old);
System.out.println("java");
}
}
class MyData{
private static final String str = null;
/* MyData(){
str = "hello";//不能修改
}
static{
str = "world";//不能修改
}*/
public static String getStr() {
return str;
}
}
补充:按行读和按行写
按行写:
(1)PrintStream
(2)BufferedWriter
(3)自己在内容后面加"\r\n"
按行读:
(1)Scanner
(2)BufferedReader
public class TestLine {
@Test
public void test04() throws IOException {
FileReader fr = new FileReader("d:/3.txt");
BufferedReader br = new BufferedReader(fr);
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
fr.close();
}
@Test
public void test03() throws FileNotFoundException {
InputStream in = System.in;//保存原来的in对象,可以方便重定向回来
System.setIn(new FileInputStream("d:/2.txt"));
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
scanner.close();
System.setIn(in);//重定向回键盘输入
}
@Test
public void test02() throws IOException {
//从键盘输入内容,按行输出到d:/3.txt
Scanner input = new Scanner(System.in);
FileWriter fw = new FileWriter("d:/3.txt");
BufferedWriter bw = new BufferedWriter(fw);
while(true){
System.out.print("请输入内容:");
String line = input.nextLine();
if("stop".equalsIgnoreCase(line)){
break;
}
bw.write(line);
bw.newLine();//换行
}
bw.close();
fw.close();
input.close();
}
@Test
public void test01() throws FileNotFoundException {
//从键盘输入内容,按行输出到d:/2.txt
Scanner input = new Scanner(System.in);
PrintStream ps = new PrintStream("d:/2.txt");
while(true){
System.out.print("请输入内容:");
String line = input.nextLine();
if("stop".equalsIgnoreCase(line)){
break;
}
ps.println(line);
}
ps.close();
input.close();
}
}
补充:流关闭的问题
问题:IO流出现很多层包装时,关闭IO流是否有顺序问题?
部分的IO流包装会出现问题。
有没有统一的关闭IO流的公式:
(1)方法一:先关外层,再关里面的
FileWriter fw = new FileWriter("d:/3.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw是外层,fw是内层
关闭顺序:
bw.close()
fw.close();
(2)Java中IO流有改进,只要关闭最外层的IO流,即可。
public class TestClose {
@Test
public void test05() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("d:/3.txt"));
bw.write("hello");
bw.close();
}
@Test
public void test04() throws IOException {
FileWriter fw = new FileWriter("d:/3.txt");
BufferedWriter bw = new BufferedWriter(fw);
//BufferedWriter包装FileWriter
//BufferedWriter依赖FileWriter
bw.write("hello");
fw.close();//先关被依赖的fw
bw.close();//后关闭外层的包装IO流
//运行上面的代码,发生java.io.IOException: Stream closed
//因为BufferedWriter中的数据是先写到缓冲区中,
//只有在(1)缓冲区满了,(2)调用flush或close方法时,才会刷出数据
//此时如果我们bw.close()要刷数据到文件时,需要用到fw流,但是它已关闭
}
}
补充:新的try...catch
JDK1.7之后,给我们提供了一种新的try...catch形式
语法格式:
try( 需要自动关闭的IO流的声明和初始化){
其他的IO流操作代码
}catch(异常类型 e){
处理异常的代码
}
try...catch新结构除了支持IO流的自动关闭,还支持其他的资源类的关闭,但是必须要求该资源类实现Closeable接口。
public class TestTryCatch {
public static void copyFile2(String srcFilePathName, String destFilePathName) {
try (
FileInputStream fis = new FileInputStream(srcFilePathName);
FileOutputStream fos = new FileOutputStream(destFilePathName);
){
//(3)一边读一边写
int len;
byte[] data = new byte[1024];//1KB
//fis.read(data)把数据从fis读取到data中
while((len = fis.read(data))!=-1){
//把数据从data中输出到fos中
fos.write(data, 0, len);
}
} catch (IOException e) {
e.printStackTrace();//打印异常
//如果想告知调用者这里发生异常,然后需要对方处理
// throw new RuntimeException(e);//把编译时异常IOException包装为运行时异常扔出去
}
}
public static void copyFile(String srcFilePathName, String destFilePathName) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//(1)先创建FileInputStream的对象,用于读取源文件的数据
fis = new FileInputStream(srcFilePathName);
//(2)再创建FileOutputStream的对象,用于输出数据到目标文件
fos = new FileOutputStream(destFilePathName);
//(3)一边读一边写
int len;
byte[] data = new byte[1024];//1KB
//fis.read(data)把数据从fis读取到data中
while((len = fis.read(data))!=-1){
//把数据从data中输出到fos中
fos.write(data, 0, len);
// fos.write(data);//如果最后一次读取的字节数少于data.length,会导致上次读取的数据重复写到文件中
}
} catch (IOException e) {
e.printStackTrace();
} finally{
//(4)关闭
try {
if(fis!=null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}