【Java从入门到入土】 IO流

Java IO流

1、什么是IO流

文件通常是由一连串的字节或字符构成,组成文件的字节序列 称为 字节流 ,组成文件的字符序列称为字符流

Java 中根据流的方向可以分为输入流输出流
I:Input,输入流是将文件或其它输入设备的数据加载到内存的过程
O:Output,输出流恰恰相反,是将内存中的数据保存到文件或其它输出设备

简单来说,通过IO流可以完成对硬盘文件的读和写

2、流的分类

2.1 按照流的方向进行分类

以内存作为参照物,往内存中去叫做输入(Input)读(Read),从内存中出来叫做输出(Output)写(Write)

2.2 按照读取数据方式的不同来进行分类

1)按照字节的方式读取数据

一次读取一个字节,等同于一次读取8个二进制位,这种流是万能的,什么类型的文件都可以读取,包括文本文件、图片、声音文件、视频文件等。

2)按照字符的方式读取数据

一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取图片、声音、视频等文件,只能读取纯文本文件,连word文件都无法读取

综上所述,流的分类:
输入流、输出流

字节流、字符流

举个例子,假设我们有一个文件file1.txt,里面的内容是:a中国bc张三fe

采用字节流的话是这样读的:
第一次读:一个字节,正好读到’a’
第二次读:一个字节,正好读到’中’字符的一半
第三次读:一个字节,正好读到’中’字符的另外一半

采用字符流的话是这样读的:
第一次读:‘a’ 字符('a’字符在windows系统中占用1个字节)
第二次读:‘中’ 字符('中’字符在windows系统中占用2个字节)

3、流的四大家族

怎么学好IO流?
Java中的IO流都已经写好了(在java.io.*包下),因此程序员不需要关心具体怎么去实现,我们最主要的还是掌握如何使用就好,掌握在java中已经提供了哪些流,每个流的特点是什么,每个流对象的常用方法有哪些以及如何使用?

我们要学会怎么new流对象?
我们要学会调用流对象的方法,知道哪个方法是读,哪个方法是写

下面首先介绍一下IO流的四大家族:

1)四大家族的首领:

  • java.io.InputStream 字节输入流
  • java.io.OutputStream 字节输出流
  • java.io.Reader 字符输入流
  • java.io.Writer 字符输出流

2)四大家族的首领都是抽象类(abstract class)

3)所有的流都实现了 java.io.Closeable 接口,都是可关闭的,都有close()这个方法

4)所有的输出流都实现了 java.io.Flushable 接口,都是可刷新的,都有flush()方法

注意:

​ 流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会占用很多资源,因此在后面的学习和使用中要养成好习惯,用完流一定要关闭!同时,输出流在最终输出之后,也要记得使用flush()方法刷新一下,这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)(注意:如果没有flush()可能会导致数据丢失)

4、常用的IO流

java.io包下需要掌握的16个流:

⭐️文件专属:

  • java.io.FileInputStream(重点掌握)
  • java.io.FileOutputStream(重点掌握)
  • java.io.FileReader
  • java.io.FileWriter

⭐️转换流:(将字节流转换成字符流)

  • java.io.InputStreamReader 主要是将字节输入流转换成字符输入流
  • java.io.OutputStreamWriter 主要是将字节输出流转换成字符输出流

⭐️缓冲流:(缓冲流主要是为了提高效率而存在的,它可以减少物理读取次数)

  • java.io.BufferedReader
  • java.io.BufferedWriter
  • java.io.BufferedInputStream
  • java.io.BufferedOutputStream

⭐️数据流专属:

  • java.io.DataInputStream
  • java.io.DataOutputStream

⭐️标准输出流:

  • java.io.PrintWriter
  • java.io.PrintStream(掌握)

⭐️对象流专属:

  • java.io.ObjectInputStream(掌握)
  • java.io.ObjectOutputStream(掌握)

总结:以Writer和Reader结尾的叫字符流,以Stream结尾的叫字节流!

4.1 文件专属

4.1.1 FileInputStream读文件(字节输入流)

FileInputStream:文件字节输入流,万能的,任何类型的文件都可以采用这个流来读取

首先我们在E:\01javaSE\test这个目录下新建一个test.txt文件,然后在IDEA中编写测试代码

package com.java.javase.IO1;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest01 {
    public static void main(String[] args) {
        // 创建文件字节输入流对象
        FileInputStream fis=null;
        try {
            // 文件路径:E:\01javaSE\test(IDEA会自动把\编成\\,因为java中\表示转义)
            fis=new FileInputStream("E:\\01javaSE\\test\\test.txt");
            // 开始读
            while(true){
                // 调用read()方法,返回值是读取的数据所对应的ASCII码,例如读取ab,则打印出的结果是97 98
                // 当没有数据可以读取时,返回值是-1
                int readDate=fis.read();
                if(readDate==-1){
                    break;
                }
                System.out.print(readDate+" ");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally语句块中要确保流最后被关闭,并且被关闭的前提是流不为空(流为空也没必要关!避免空指针异常)
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意点:程序执行结束后,在finally语句块中确保流一定被关闭,并且关闭流的前提是流不为空,因为流是null的时候没必要关闭,这样做可以避免空指针异常!

并且上面代码中的while循环有点冗余,因此可以改造一下上面代码中的while循环:

int readDate=0;
while((readDate=fis.read())!=-1){
    System.out.print(readDate+" ");
}

改进以后我们再来分析这个程序存在哪些缺点?

一次读取一个字节(byte),这样内存和硬盘交互太频繁,会浪费大量的时间和资源在交互上面,因此我们需要对其进行改进,使其可以一次读取多个字节

read(byte[] b)方法,从此输入流中将最多b.length个字节的数据读入一个byte数组中,下面我们就使用这个方法对之前的程序进行改造,减少硬盘和内存的交互,提高程序的执行效率

package com.java.javase.IO1;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest01 {
    public static void main(String[] args) {
        // 创建文件字节输入流对象
        FileInputStream fis=null;
        try {
            // 文件路径:E:\01javaSE\test(IDEA会自动把\编成\\,因为java中\表示转义)
            fis=new FileInputStream("E:\\01javaSE\\test\\test.txt");
            byte[]bytes = new byte[4];
            int readCount = 0;
            // 开始读
            while((readCount= fis.read(bytes))!=-1){
                System.out.print(new String(bytes,0,readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally语句块中要确保流最后被关闭,并且被关闭的前提是流不为空(流为空也没必要关!避免空指针异常)
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileInputStream类的其它常用方法:

  • int available() 返回一个int类型的值,表示流当中剩余的没有读到的字节数量,可以在未读取之前获取总字节数量,这样就可以很方便的确定用于读取的byte数组的长度,但是这种方式不太适合太大的文件,因为byte[]数组不能太大!
  • long skip() 跳过几个字节不读

第一个方法演示

package com.java.javase.IO1;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis=null;
        try{
            fis=new FileInputStream("E:\\01javaSE\\test\\test.txt");
            System.out.println("总字节数量:"+fis.available());
            int readByte=fis.read();
            System.out.println("还剩下多少个字节没有读: "+fis.available());
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}catch (IOException e){
    		e.printStackTrace();
		}finally {
    		if(fis!=null){
        		try {
           	 		fis.close();
       	 		} catch (IOException e) {
            		e.printStackTrace();
        		}
    		}
		}
	}
}

第二个方法演示

package com.java.javase.IO1;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis=null;
        try{
            fis=new FileInputStream("E:\\01javaSE\\test\\test.txt");
            fis.skip(3);
            System.out.println(fis.read());

    	}catch(FileNotFoundException e){
        	e.printStackTrace();
    	}catch (IOException e){
        	e.printStackTrace();
    	}finally {
      	  	if(fis!=null){
            	try {
                	fis.close();
            	} catch (IOException e) {
                	e.printStackTrace();
            	}
        	}
    	}
	}
}
4.1.2 FileOutputStream写文件(字节输出流)

FileOutputStream:文件字节输出流,负责写

package com.java.javase.IO1;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest01 {
    public static void main(String[] args) {
        FileOutputStream fos=null;
        try{
            // 工程Project的根就是IDEA的默认当前路径
            // append:true的作用是以追加的方式在文件末尾写入,不会清空源文件内容
            fos=new FileOutputStream("chapter6/src/com/java/javase/IO1/IODocument",true);
            byte[]bytes={97,98,99,100};
            // 将byte数组全部写出
            fos.write(bytes);
        	fos.flush();                       // 写完之后,最后一定要刷新
    	}catch(FileNotFoundException e){
        	e.printStackTrace();
    	}catch(IOException e){
        	e.printStackTrace();
    	}finally{
        	if(fos!=null){
            	try {
                	fos.close();
            	} catch (IOException e) {
                	e.printStackTrace();
                }
             }
    	}
	}
}

我们可以指定只写byte数组的一部分

package com.java.javase.IO1;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest01 {
    public static void main(String[] args) {
        FileOutputStream fos=null;
        try{
            // 工程Project的根就是IDEA的默认当前路径
            // append:true的作用是以追加的方式在文件末尾写入,不会清空源文件内容
            fos=new FileOutputStream("chapter6/src/com/java/javase/IO1/IODocument",true);
            byte[]bytes={97,98,99,100};
            // 将byte数组的一部分写出(数组,起始下标,长度)
            fos.write(bytes,0,2);

            fos.flush();                       // 写完之后,最后一定要刷新
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

写入中文字符串

String s1="我是一个大学生!";
byte[]bs=s1.getBytes();
fos.write(bs);
4.1.3 文件的复制
  • 使用FileInputStream+FileOutputStream完成文件的拷贝
  • 拷贝的过程应该是一边读,一边写
  • 使用以上的字节流拷贝文件的时候,文件类型是随意的
package com.java.javase.IO1;
import java.io.*;

public class Copy1 {
    public static void main(String[] args) {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try {
            fis=new FileInputStream("chapter6/src/com/java/javase/IO1/IODocument");
            fos=new FileOutputStream("E:\\01javaSE\\test\\test.txt",true);
            // 一次拷贝1MB
            byte[]bytes=new byte[1024*1024];
            int readCount=0;
            while((readCount=fis.read(bytes))!=-1){
                fos.write(bytes,0,readCount);
            }
            fos.flush();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.1.4 FileReader读文件(字符输入流)
  • 文件字符输入流,按照字符的方式去读取,只能读取普通文本
  • 读取文本内容时,比较方便和快捷
  • 读取时使用的是char数组
package com.java.javase.IO2;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest01 {
    public static void main(String[] args) {
        FileReader fr=null;
        try{
            fr=new FileReader("E:\\01javaSE\\test\\test.txt");
            char[]chars=new char[4];
            int readCount=0;
            while((readCount=fr.read(chars))!=-1){
                System.out.println(new String(chars,0,readCount));
            }

        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(fr!=null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
4.1.5 FileWriter写文件(字符输出流)
  • 文件字符输出流,FileReader的兄弟,负责写
  • 只能输出普通文本
package com.java.javase.IO2;

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest01 {
    public static void main(String[] args) {
        FileWriter fw=null;
        try{
            // 创建文件字符输出流对象,并且指定写的方式是追加
            fw=new FileWriter("E:\\01javaSE\\test\\test.txt",true);
            char[]chars={'1','我','是','中','国','人'};
            fw.write(chars);

            // 输出流刷新
            fw.flush();
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(fw!=null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

写入换行符

fw.write("\n");
fw.write("HelloWorld");

4.2 缓冲流专属

4.2.1 BufferedReader 读文件
  • 带有缓冲区的字符输入流
  • 构造方法只能传入字符流
  • 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲
package com.java.javase.IO;

import java.io.BufferedReader;
import java.io.FileReader;

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{
        // 创建一个使用默认大小输入缓冲区的缓冲字符输入流
        FileReader reader=new FileReader("E:\\01javaSE\\test\\test.txt");
        // 当一个流的构造方法中需要一个流的时候,这个被传入的流叫做: 节点流
        // 外部负责包装的流叫做包装流,也称做处理流
        // 就当前这个程序来说,FileReader就是一个节点流,BufferedReader就是包装流/处理流
        BufferedReader br=new BufferedReader(reader);

        // 读取一个文本行(不带有换行符),返回值是一个字符串,当读取到文件末尾没有数据可以读取时,返回值为null
        String s=null;
        while ((s=br.readLine())!=null){
            System.out.println(s);
        }
     
        // 对于包装流来说,只需要关闭最外层的流就行,里面的节点流会自动关闭
        br.close();
    }
}

在上面使用构造方法创建BufferedReader对象的时候,构造方法中只能传入字符流,那如果我要传入的是FileInputStream文件字节输入流呢?答案肯定是不行的,肯定会报错!怎么解决呢?可以通过转换流转换

4.2.2 InputStreamReader转换流
package com.java.javase.IO;

import java.io.*;

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{
        // BufferedReader的构造方法中只能传入字符流,但是这里我想要传入一个字节流
        FileInputStream in=new FileInputStream("E:\\01javaSE\\test\\test.txt");

        // 通过转换流转换,将字节流转换为字符流
        // in在这里是一个节点流,reader是包装流
        InputStreamReader reader=new InputStreamReader(in);
     
        // 这个构造方法只能传一个字符流,不能传字节流,但是上面我们已经通过转换流转换了
        // reader在这里是一个节点流,br是包装流
        BufferedReader br=new BufferedReader(reader);
     
        String line=null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
     
        // 关闭最外层
        br.close();
    }
}

将代码合并的写法

package com.java.javase.IO;

import java.io.*;

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{
        BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("E:\\01javaSE\\test\\test.txt")));

        String line=null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
     
        // 关闭最外层
        br.close();
    }
}
4.2.3 BufferedWriter读文件
  • 带有缓冲区的字符输出流
  • 用法与BufferedReader相似
package com.java.javase.IO;

import java.io.*;

public class BufferedWriterTest {
    public static void main(String[] args) throws Exception{
        // 以追加的方式写入
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\01javaSE\\test\\test.txt",true)));

        bw.write("today is Monday!");
     
        // 刷新
        bw.flush();
        // 关闭最外层
        bw.close();
    }
}

4.3 数据流专属

4.3.1 DataOutputStream

这个流可以将数据连同数据的类型一并写入文件(该文件不是普通的文本文档)

package com.java.javase.IO;

import java.io.DataOutputStream;
import java.io.FileOutputStream;
// DataOutputStream写的文件,只能使用DataOutputStream去读,并且读的时候需要提前知道写入的顺序
// 读的顺序需要和写的顺序一致,才可以正常取出数据

public class DataOutputStreamTest01 {
    public static void main(String[] args)throws Exception {
        DataOutputStream dos=new DataOutputStream(new FileOutputStream("E:\\01javaSE\\test\\test1.txt"));
        byte b=100;
        short s=200;
        int i=10;
        long f=44L;
        float g=3.0F;
        double d=3.14;
        boolean sex=false;
        char c='a';
        

        // 将数据以及数据的类型一并写入文件当中
        dos.writeByte(b);
        dos.writeShort(s);
        dos.writeInt(i);
        dos.writeLong(f);
        dos.writeFloat(g);
        dos.writeDouble(d);
        dos.writeBoolean(sex);
        dos.writeChar(c);
     
        dos.flush();
        dos.close();
    }

}

当我们使用记事本打开写入数据的文件时,会出现乱码,无法正常显示文件(如下图),如果要正常读取我们用DataOutputStream写入的数据,就要用到下面要讲的DataInputStream

4.3.2 DataInputStream 数据字节输入流
  • DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候你需要提前知道写入的顺序
  • 读的顺序需要和写的顺序一致,才可以正常取出数据
package com.java.javase.IO;

import java.io.DataInputStream;
import java.io.FileInputStream;
// DataInputStream ————数据字节输入流

public class DataInputStreamTest01 {
    public static void main(String[] args)throws Exception {
        DataInputStream dis =new DataInputStream(new FileInputStream("E:\\01javaSE\\test\\test1.txt"));
        byte a=dis.readByte();
        short b=dis.readShort();
        int c=dis.readInt();
        long d=dis.readLong();
        float e=dis.readFloat();
        double f=dis.readDouble();
        boolean g=dis.readBoolean();
        char h=dis.readChar();

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
        System.out.println(e);
        System.out.println(f);
        System.out.println(g);
        System.out.println(h);
    }
}

输出结果

100
200
10
44
3.0
3.14
false
a

4.4 PrintStream标准输出流

标准的字节输出流
默认输出到控制台

package com.java.javase.IO;

import java.io.PrintStream;

public class PrintStreamTest01 {
    public static void main(String[] args) {
        // 联合起来写
        System.out.println("hello world!");

        // 分开写
        PrintStream ps=System.out;
        ps.println("helo zhangsan");
        ps.println(100);
     
        // 标准输出流不需要手动关闭
    }

}

System这个类中有这样一个属性

因此System.out实际上代表一个PrintStream对象

改变标准输出流的方向

package com.java.javase.IO;


import java.io.FileOutputStream;
import java.io.PrintStream;

public class PrintStreamTest01 {
    public static void main(String[] args) throws Exception{
        // 改变标准输出流的方向,使其输出不再指向控制台,而指向我们指定的文件,需要用到System类的setOut()方法
        PrintStream printStream=new PrintStream(new FileOutputStream("E:\\01javaSE\\test\\test1.txt"));
        System.setOut(printStream);
        System.out.println("hello world");
        System.out.println(999999999);
    }
}

一个好用的日志工具的编写

package com.java.javase.IO;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
    日志工具
 */
public class LogUtil {
    public static void log(String msg) throws Exception {
        try{
            // 指向一个日志文件
            PrintStream out=new PrintStream(new FileOutputStream("E:\\01javaSE\\test\\log.txt",true));
            // 改变输出方向
            System.setOut(out);
            // 当前时间格式化
            Date nowTime=new Date();
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime=sdf.format(nowTime);
            // 输出到日志文件
            System.out.println(strTime+" : "+msg);
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }
    }
}

测试类

package com.java.javase.IO;

public class Test {
    public static void main(String[] args) throws Exception{
        LogUtil.log("用户开始登录!");
        LogUtil.log("用户登陆中!");
        LogUtil.log("用户退出登录!");
    }
}

4.5 File类

  • File类和四大家族没有关系,所以File类不能完成文件的读和写
  • File对象表示文件和目录路径名的抽象表示形式
  • E:\01javaSE\test 是一个File对象
  • E:\01javaSE\test\log.txt 也是一个File对象
  • 一个File对象有可能对应的是目录,也可能是文件

我们需要掌握File类中常用的方法

package com.java.javase.IO;

import java.io.File;

public class FileTest01 {
    public static void main(String[] args) throws Exception{
        // 创建一个File对象
        File f1=new File("E:\\01javaSE\\test\\log1.txt");

        // 判断是否存在
        System.out.println(f1.exists());
     
        // 如果E:\01javaSE\test不存在,则以文件的形式创建出来
        if(!f1.exists()){
            // 以文件形式新建
            f1.createNewFile();
        }
    }

}

创建多重目录

package com.java.javase.IO;

import java.io.File;

public class FileTest01 {
    public static void main(String[] args) throws Exception{
        // 创建一个File对象
        File f1=new File("E:/01javaSE/test/a/b/c/d");
        if (!f1.exists()){
            // 以多重目录的形式创建
            f1.mkdirs();
        }
    }
}

获取文件的父路径

package com.java.javase.IO;

import java.io.File;

public class FileTest01 {
    public static void main(String[] args) throws Exception {
        // 创建一个File对象
        File f1 = new File("E:\\01javaSE\\test\\log.txt");
        // 获取文件的父路径
        String parentPath = f1.getParent();
        System.out.println(parentPath);
    }
}

获取当前目录下的所有子文件

package com.java.javase.IO;
import java.io.File;

/**

 * File中的listFiles方法
   */
public class FileTest02 {
	public static void main(String[] args) {
       	File f1=new File("E:\\01javaSE\\test");
       		// 获取当前目录下所有的子文件
       		File[] files=f1.listFiles();
       		for(File file:files){
           		System.out.println(file.getAbsolutePath());
       		}
   	}
}

其它常用方法

package com.java.javase.IO;


import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileTest02 {
    public static void main(String[] args) {
        File f1=new File("E:\\01javaSE\\test\\log.txt");
        // 获取文件名
        System.out.println("文件名:"+f1.getName());

        // 判断是否是一个目录
        System.out.println(f1.isDirectory());
     
        // 判断是否是一个文件
        System.out.println(f1.isFile());
     
        // 获取文件最后一次修改时间
        // 这个毫秒数是从1970年到当前时间的总毫秒数
        long haoMiao=f1.lastModified();
     
        // 将总毫秒数转换成日期
        Date time=new Date(haoMiao);
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime=sdf.format(time);
        System.out.println(strTime);
     
        // 获取文件大小(多少字节)
        System.out.println(f1.length());
    }

}

练习:拷贝目录

package com.java.javase.IO;

import java.io.*;

public class Copy {
    public static void main(String[] args) {
        // 拷贝源
        String pathName1="E:\\01javaSE\\02-JavaSE进阶版";
        File srcFile=new File(pathName1);

        // 拷贝目标
        String pathName2="E:\\01javaSE\\dest";
        File destFile=new File(pathName2);
     
        // 调用方法拷贝
        copyDir(pathName1,pathName2,srcFile);
    }
     
    /**
     * 拷贝目录
     * @param srcFile 拷贝源
     */
    private static void copyDir(String pathName1,String pathName2,File srcFile) {
        // 获取拷贝源下面的所有File对象(可能是文件也可能是目录)
        File[] files=srcFile.listFiles();
        if (files==null){
            return;
        }
        for(File file:files){
            // 第一种情况:如果遇到的是一个文件则拷贝
            if(file.isFile()){
                FileInputStream fis=null;
                FileOutputStream fos=null;
                try {
                    int n=pathName1.length();
                    String resultPath=pathName2+file.getAbsolutePath().substring(n);
                    System.out.println(resultPath);
                    File f1=new File(resultPath);
                    if (!f1.exists()){
                        f1.createNewFile();
                    }
                    fis=new FileInputStream(file.getAbsolutePath());
                    fos=new FileOutputStream(resultPath,true);
                    // 一次拷贝1MB
                    byte[]bytes=new byte[1024*1024];
                    int readCount=0;
                    while((readCount=fis.read(bytes))!=-1){
                        fos.write(bytes,0,readCount);
                    }
                    fos.flush();
     
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }catch (IOException e){
                    e.printStackTrace();
                }finally {
                    if(fis!=null){
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(fos!=null){
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
     
            // 第二种情况:如果遇到的是一个文件夹则递归遍历,并且要创建相应的文件夹
            // 创建文件夹目录
            if(file.isDirectory()){
                int n=pathName1.length();
                // 拿到目标目录
                /**
                 * E:\01javaSE\dest\a
                 * E:\01javaSE\dest\a\b
                 * E:\01javaSE\dest\a\b\c
                 * E:\01javaSE\dest\a\b\c\d
                 */
                String resultPath=pathName2+file.getAbsolutePath().substring(n);
                File f1=new File(resultPath);
                if (!f1.exists()){
                    f1.mkdirs();
                }
                // 递归调用
                copyDir(pathName1,pathName2,file);
            }
     
        }
    }
}

4.6 ObjectInputStream和ObjectOutputStream

在介绍ObjectInputStream和ObjectOutputStream之前,我们先来看一下什么是序列化和反序列化

ObjectOutputStream是负责序列化的,ObjectInputStream是负责反序列化的

首先准备一个学生类

package com.java.javase.xuLieHua;

import java.io.Serializable;

public class Student implements Serializable {
    private  int no;
    private String name;

    public Student() {
    }
     
    public Student(int no, String name) {
        this.no = no;
        this.name = name;
    }
     
    public int getNo() {
        return no;
    }
     
    public void setNo(int no) {
        this.no = no;
    }
     
    public String getName() {
        return name;
    }
     
    public void setName(String name) {
        this.name = name;
    }
     
    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

}

如果你希望某一个属性不参与序列化,可以在该属性前加关键字 transient (表示游离的,不参与序列化)

private  int no;
private transient String name;

序列化

package com.java.javase.xuLieHua;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
// java.io.NotSerializableException: Student对象不支持序列化!
// 参与序列化和反序列化的对象必须实现Serializable接口————只是一个标志接口,起到标识的作用
// Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号
public class ObjectOutputStreamTest01 {
    public static void main(String[] args) throws Exception{
        Student s=new Student(110,"张三");

        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\01javaSE\\test\\xuliehua.txt"));
        oos.writeObject(s);
     
        oos.flush();
        oos.close();
    }

}

反序列化

package com.java.javase.xuLieHua;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
// 反序列化

public class ObjectInputStreamTest01 {
    public static void main(String[] args)throws Exception {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\01javaSE\\test\\xuliehua.txt"));
        Object obj=ois.readObject();
        System.out.println(obj.toString());
        ois.close();
    }
}

一次性序列化多个对象

package com.java.javase.xuLieHua;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
// 一次性序列化多个对象
// 可以将对象放到集合当中,序列化集合
// 参与序列化的集合以及集合中的元素都需要实现java.io.Serializable接口

public class ObjectOutputStreamTest02 {
    public static void main(String[] args) throws Exception{
        List<Student> studentList=new ArrayList<>();
        studentList.add(new Student(110,"lisi"));
        studentList.add(new Student(111,"zhangsan"));
        studentList.add(new Student(112,"wangwu"));
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\01javaSE\\test\\xuliehua.txt"));
        oos.writeObject(studentList);

        oos.flush();
        oos.close();
    }

}

与之对应的,一次性反序列化多个对象

package com.java.javase.xuLieHua;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;

public class ObjectInputStreamTest02 {
    public static void main(String[] args)throws Exception {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\01javaSE\\test\\xuliehua.txt"));
        List<Student> studentList=(List<Student>)ois.readObject();
        for(Student student:studentList){
            System.out.println(student);
        }

        ois.close();
    }

}

关于序列化版本号的理解

  • 当我们首次编写好一个学生类,使用序列化手段将对象存储到硬盘中,在该过程中,Java虚拟机看到Serializable接口之后,会自动帮我们生成一个序列化版本号
  • 过了很多很多年,Student这个类的源码改动了(比如增加了某个属性),源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件
  • class文件再次运行的时候,java虚拟机生成的序列化版本号也会发生相应的改变
  • 这时再次运行反序列化操作的时候就会报出如下错误

因此序列化版本号有什么作用呢?可以用来区分类

Java语言中区分类的机制:

​ 1、首先通过类名进行对比,如果类名不一样,则肯定不是同一个类
​ 2、如果类名一样,再根据序列化版本号进行区分

自动生成序列化版本号的缺点:

​ 一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类

结论:

​ 凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号,这样,以后即使这个类改变了,但是版本号不变,java虚拟机会认为是同一个类

private static final long serialVersionUID=1L;

4.7 IO+Properties的联合使用

IO流:文件的读和写

Properties:是一个集合,key和value都是String类型

新建一个classinfo.properties配置文件

name=lisi
password=123456

编写读取代码

package com.java.javase.IO;
// IO+Properties的联合使用
// 配置文件中的内容格式是:
// key1=value1
// key2=value2
// key3=value3
import java.io.FileReader;
import java.util.Properties;

public class IoPropertiesTest01 {
    public static void main(String[] args) throws Exception{
       // 新建一个输入流对象
       FileReader reader=new FileReader("E:\\02javadaima\\chapter6\\src\\com\\java\\javase\\IO\\classinfo.properties");
       // 新建一个Map集合
       Properties pro=new Properties();
       // 调用Properties对象的load方法将文件中的数据加载到Map集合中
       pro.load(reader);

       // 通过key来获取value
       String a=pro.getProperty("name");
       System.out.println(a);
    }

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖虎不秃头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值