Java语言基础(10)文件与IO流

1、File类

File提供了大量的文件操作:删除文件,修改文件,建立目录、列表文件等等。
如何递归读取目录及子目录下的文件:
    @Test
    public void test02(){
        File f = new File("E:\\");
        rec(f);
    }

    public void rec(File f){

        if(f.isFile()){
            System.out.println(f.getAbsolutePath()+File.separator+f.getName());
        }

        if(f.isDirectory()){
            File[] files = f.listFiles();
            if(files!=null){
                for (File file : files) {
                    rec(file);
                }
            }
        }
    }

2、Java流概述

文件通常是由一连串的字节或字符构成,组成文件的字节序列称为字节流,组成文件的字符序列称为字符流。Java中根据流的方向可以分为输入流和输出流。输入流是将文件或其它输入设备的数据加载到内存的过程;输出流恰恰相反,是将内存中的数据保存到文件或其他输出设备,详见下图:

在这里插入图片描述

文件是由字符或字节构成,那么将文件加载到内存或再将文件输出到文件,需要有输入和输出流的支持,那么在Java语言中又把输入和输出流分为了两个,字节输入和输出流,字符输入和输出流,见下表:

在这里插入图片描述

2.1、InputStream(字节输入流)

InputStream是字节输入流,InputStream是一个抽象类,所有继承了InputStream的类都是字节输入流,主要了解以下子类即可:

在这里插入图片描述

主要方法介绍:
返回值方法
voidclose() 关闭此输入流并释放与该流关联的所有系统资源。
intread() 从输入流读取下一个数据字节。
intread(byte[] b) 从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中。
intread(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入字节数组。

2.2、OutputStream(字节输出流)

所有继承了OutputStream都是字节输出流

在这里插入图片描述

主要方法介绍
返回值方法
voidclose() 关闭此输出流并释放与此流有关的所有系统资源。
intflush()刷新此输出流并强制写出所有缓冲的输出字节。
intwrite(byte[] b) 将 b.length 个字节从指定的字节数组写入此输出流。
intwrite(byte[] b, int off, int len) 将指定字节数组中从偏移量 off 开始的 len 个字节写入此输出流。
intwrite(int b) 将指定的字节写入此输出流。

2.3、Reader(字符输入流)

所有继承了Reader都是字符输如流

在这里插入图片描述

主要方法介绍
返回值方法
voidclose() 关闭此输出流并释放与此流有关的所有系统资源。
intread() 读取单个字符。
intread(char[] cbuf) 将字符读入数组。
intread(char[] cbuf, int off, int len) 将字符读入数组的某一部分。

2.4、Writer(字符输出流)

所有继承了Writer都是字符输出流

在这里插入图片描述

主要方法介绍
返回值方法
Writerappend(char c) 将指定字符追加到此 writer。
voidclose() 关闭此流,但要先刷新它。
voidflush() 刷新此流。
voidwrite(char[] cbuf) 写入字符数组。
voidwrite(char[] cbuf, int off, int len) 写入字符数组的某一部分。
voidwrite(int c) 写入单个字符。
voidvoid write(String str) 写入字符串。
voidwrite(String str, int off, int len) 写入字符串的某一部分。

3、文件流

文件流主要分为:文件字节输入流、文件字节输出流、文件字符输入流、文件字符输出流

3.1、FileInputStream(文件字节输入流)

FileInputStream主要按照字节方式读取文件,例如我们准备读取一个文件,该文件的名称为test.txt

在这里插入图片描述

        InputStream in = new FileInputStream("E:/a.txt");

        int c = 0;
        while((c = in.read()) != -1){
            System.out.println(c);
        }

        in.close();
文件可以正确的读取,但是我们的汉字乱码了,原因在于使用了字节输入流,它是一个字节一个字节读取的,而汉字是两个字节,所以读出一个字节就打印,那么汉字是不完整的,所以就乱码了

3.2、FileOutputStream(文件字节输出流)

FileOutputStream主要按照字节方式写文件,例如:我们做文件的复制,首先读取文件,读取后在将该文件另写一份保存到磁盘上,这就完成了备份

在这里插入图片描述

        InputStream in = new FileInputStream("E:/a.txt");
        OutputStream out = new FileOutputStream("E:/aa.txt");

        int c = 0;
        while((c = in.read()) != -1){
            out.write(c);
        }

        in.close();
        out.close();

3.3、FileReader(文件字符输入流)

FileReader是一字符为单位读取文件,也就是一次读取两个字节,如:
        Reader r = new FileReader("E:/a.txt");
        int c = 0;
        while((c = r.read()) != -1){
            char ch = (char)c;
            System.out.println(ch);
        }
        r.close();
【打印指定内容】
Writer w = new FileWriter("E:/aaa.txt",true);
w.write("你好,这是个bug。。。。\n");
w.write("你好,这是个bug。。。。\n");
w.write("你好,这是个bug。。。。\n");
w.write("你好,这是个bug。。。。\n");
w.close();

4、缓冲流

缓冲流主要是为了提高效率而存在的,减少物理读取次数,缓冲流主要有:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter,并且BufferedReader提供了实用方法readLine(),可以直接读取一行,BufferWriter提供了newLine()可以写换行符。

4.1、采用字节缓冲流改造文件复制代码

在这里插入图片描述

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/aaa.txt"));

int b = 0;
while((b = bis.read()) != -1){
    System.out.println(b);
}

bis.close();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/a.txt"));
        BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream("E:/aaa.txt"));
        int b = 0;
        while((b = bis.read()) != -1){
            bout.write(b);
        }

        bout.flush();//手动刷新缓存
        bis.close();
        bout.close();
可以显示的调用flush,flush的含义是刷新缓冲区,也就是将缓存区中的数据写到磁盘上,不再放到内存里了,在执行os.close()时,其实默认执行了os.flush(),我们在这里可以不用显示的调用

4.2、采用字符缓冲流改造文件复制代码

import java.io.*;

public class BufferedReaderTest01 {
	
	public static void main(String[] args) {
		BufferedReader r = null;
		BufferedWriter w = null;
		try {
			r = new BufferedReader(
						new FileReader("c:\\test.txt"));
			w = new BufferedWriter(
						new FileWriter("d:\\test.txt.bak"));
			String s = null;
			while ((s = r.readLine()) != null) {
				w.write(s);
				//w.write("\n");
				//可以采用如下方法换行
				w.newLine();
			}
			System.out.println("文件复制完毕!");
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			try {
				if (r != null)	{
					r.close();	
				}
				if (w != null) {
					//在close前会先调用flush
					w.close();	
				}
			}catch(IOException e) {}	
		}
	} 
}

5、转换流

转换流主要有两个InputStreamReader和OutputStreamWriter
1. InputStreamReader主要是将字节流输入流转换成字符输入流
2. OutputStreamWriter主要是将字节流输出流转换成字符输出流

5.1、InputStreamReader

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("E:/a.txt")));

String str = null;
while((str = br.readLine()) != null){
    System.out.println(str);
}

br.close();

5.2、OutputStreamWriter

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("E:/a.txt")));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/aaa.txt")));
String str = null;
while((str = br.readLine()) != null){
    bw.write(str+"\n");
}

br.close();
bw.close();

6、打印流

打印流主要包含两个:PrintStream和PrintWriter,分别对应字节流和字符流

6.1、完成屏幕打印的重定向

System.out其实对应的就是PrintStream,默认输出到控制台,我们可以重定向它的输出,可以定向到文件,也就是执行System.out.println(“hello”)不输出到屏幕,而输出到文件
【示例代码】
OutputStream os = new FileOutputStream("E:/a.txt",true);
PrintStream p = new PrintStream(os);
p.println("哈哈哈哈哈");
os.close();
OutputStream os = new FileOutputStream("E:/a.txt",true);
PrintWriter pw = new PrintWriter(os);
pw.println("嘿嘿嘿");
pw.close();
os.close();
OutputStream os = new FileOutputStream("E:/a.txt",true);
System.setOut(new PrintStream(os));
System.out.println("abcde哈哈哈哈");
os.close();

6.2、接受屏幕输入

IDEA junit接收控制台输入需要配置

在这里插入图片描述

添加-Deditable.java.test.console=true,然后重启IDEA即可生效

在这里插入图片描述

【示例代码】
System.in可以接收屏幕输入
//IDEA junit控制台输入需要配置
System.out.println("请输入:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
System.out.println("接收到的数据:"+line);
br.close();
【接受循环输入】
//IDEA junit控制台输入需要配置
System.out.println("请输入:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

String line = null;
while(! (line = br.readLine()).equals("") ){
    System.out.println("接收到的数据:"+line);
}
br.close();

7、对象流

对象流可以将Java对象转换成二进制写入磁盘,这个过程通常叫做序列化,并且还可以从磁盘读出完整的Java对象,而这个过程叫做反序列化。
对象流主要包括:ObjectInputStream和ObjectOutputStream

7.1、如何实现序列化和反序列化

如果实现序列化该类必须实现序列化接口java.io.Serializable,该接口没有任何方法,该接口只是一种标记接口,标记这个类是可以序列化的

序列化
import java.util.Date;

public class People {

    private Long id;

    private String name;

    private Date birthday;
    //get/set...
}
People p = new People(1L,"张三",new Date());

ObjectOutput oos = new ObjectOutputStream(new FileOutputStream("E:/p.txt"));
oos.writeObject(p);
oos.close();
不能序列化,对序列化的类是有要求的,这个序列化的类必须实现一个接口Serializable,这个接口没有任何方法声明,它是一个标识接口,如:java中的克隆接口Cloneable,也是起到了一种标识性的作用

序列化

import java.io.Serializable;
import java.util.Date;

public class People implements Serializable {

    private Long id;

    private String name;

    private Date birthday;
}
以上可以完成序列化

反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:/p.txt"));

//反序列化
People person = (People)ois.readObject();
System.out.println(person);
ois.close();

7.2、关于transient关键字

import java.io.Serializable;
import java.util.Date;

public class People implements Serializable {

    private Long id;

    private transient String name;//序列号过程中会忽略该字段

    private Date birthday;
}

7.3、关于serialVersionUID属性

【示例代码】,在person中加入一个成员属性sex,然后在读取person.dat文件

public class People implements Serializable {


    private Long id;

    private transient String name;

    private Date birthday;

    private String sex;
}
*java.io.InvalidClassException: com.test.People; local class incompatible: stream classdesc serialVersionUID = -7100224129210986212, local class serialVersionUID = -6142766582074026610*
错误的原因:在序列化存储Person时,他会为该类生成一个serialVersionUID= -6120276268074674235,而我们在该类中加入了一个sex属性后,那么在使用的时		候他就会为该类生成一个新的serialVersionUID= 1923863382018150382,这个两个UID(-6120276268074674235和1923863382018150382)不同,所以Java认为	是	不兼容的两个类。如果解决呢?
通常在实现序列化的类中增加如下定义:
static final long serialVersionUID = -111111111111111111L;
如果在序列化类中定义了成员域serialVersionUID,系统会把当前serialVersionUID成员域的值作为类的序列号(类的版本号),这样不管你的类如何升级,那么他		的序列号(版本号)都是一样的,就不会产生类的兼容问题。

【代码示例】,解决序列化版本冲突的问题

public class People implements Serializable {
    //加入版本号,防止序列化兼容问题
    private static final long serialVersionUID = 1L;

    private Long id;

    private transient String name;

    private Date birthday;

    private String sex;
}
以上不再出现序列化的版本问题,因为他们有统一的版本号:-111111111111111111L
进一步理解一下serialVersionUID

在这里插入图片描述

【代码示例】,将Person的版本号修改为-111111111111111222L, 该客户端与服务器通信会出现序列化版本兼容问题

java.io.InvalidClassException: com.test.People; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2023)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1873)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2180)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1690)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)
	at com.test.FileTest.test21(FileTest.java:264)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
serialVersionUID就和序列化有关
  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值