IO流

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。IO流,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

9.IO流
9.1.分类
  • 根据数据的流向分为:输入流和输出流。
    • 输入流 :把数据从其他设备上读取到内存中的流。
    • 输出流 :把数据从内存中写出到 其他设备上的流。
  • 根据数据的类型分为:字节流和字符流。
    • 字节流 :以字节为单位,读写数据的流。
    • 字符流 :以字符为单位,读写数据的流。
  • IO流体系
分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArraylnputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedlnputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedlnputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 lnputStreamReader OutputStreamWriter
对象流 ObjectlnputStream ObjectOutputStream
FilterlnputStream FilterOutputStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
数据流 DatalnputStream DataOutputStream
9.2.字节流
9.2.1.字节介绍
  • 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
9.2.2.字节输出流
  • java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
  • 一连串的 0 1 二进制数据字节序列,以字节为单位,0000 0000八位。
  • 常用方法
    • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    • public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
    • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
    • public abstract void write(int b) :将指定的字节输出流。

    close方法,当完成流的操作时,必须调用此方法,释放系统资源。

9.2.3.FileOutputStream类
  • java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。
  • 构造方法写入文件
    • public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
    • public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。
  • 构造方法续写文件
    • public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的文件。
    • public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。

    true 表示追加数据, false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了

//创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileOutputStream fos = new FileOutputStream(file);
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("b.txt");
    }
}


//常用方法
public class FOSWrite {
    //虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
    //流操作完毕后,必须释放系统资源,调用close方法,千万记得
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("E:\\fos.txt");
        // 写出数据
        fos.write(97); // 写出第1个字节
        fos.write(98); // 写出第2个字节
        fos.write(99); // 写出第3个字节  //abc

        byte[] b1 = "程序员".getBytes();
        // 写出字节数组数据
        fos.write(b1);  //abc程序员
        
        byte[] b2 = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b2,2,2);    //abc黑马程序员cd
        // 关闭资源
        fos.close();
        
        
        FileOutputStream fos1 = new FileOutputStream("E:\\fos.txt"true);
        // 字符串转换为字节数组
        byte[] b3 = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos1.write(b3);  //abc黑马程序员cdcd
        // 关闭资源
        fos1.close();
        
        
        FileOutputStream fos2 = new FileOutputStream("fos.txt");
        // 定义字节数组
        byte[] words = {97,98,99,100,101};
        // 遍历数组
        for (int i = 0; i < words.length; i++) {
            // 写出一个字节
            fos2.write(words[i]);
            // 写出一个换行, 换行符号转成数组写出
            fos2.write("\r\n".getBytes());
        }
        // 关闭资源
        fos2.close();
    }
}
  • 回车符 \r 和换行符 \n :
    • 回车符:回到一行的开头(return)。
    • 换行符:下一行(newline)。
  • 系统中的换行:
    • Windows系统里,每行结尾是回车+换行 ,即 \r\n ;
    • Unix系统里,每行结尾只有 换行 ,即 \n ;
    • Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。
  • 异常处理
public static void main(String[] args) {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream("a.txt");
        out.write("hello world".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally { // 为什么finally? 前面代码有问题 开启的io一定要关闭
        try {
            // 不等于null才关闭
            if (out != null) {
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
9.2.4.字节输入流
  • java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
  • 常用方法
    • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
    • public abstract int read() : 从输入流读取数据的下一个字节。
    • public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
9.2.5.FileInputStream
  • java.io.FileInputStream 类是文件输入流,从文件中读取字节。
  • 构造方法:
    • public FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
    • public FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

    创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出 FileNotFoundException 。

//构造方法
public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
        // 使用文件名称创建流对象
        FileInputStream fos = new FileInputStream("b.txt");
    }
}

//常用方法
public class FISRead {
    public static void main(String[] args) throws IOException{
        
        // 1.读取字节: read 方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1
        // 使用文件名称创建流对象
        FileInputStream fis = new FileInputStream("read.txt");
        // 读取数据,返回一个字节
        int read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        // 读取到末尾,返回‐1
        read = fis.read();
        System.out.println(read);
        // 关闭资源
        fis.close();
        
        // 使用文件名称创建流对象,循环读取
        FileInputStream fis1 = new FileInputStream("read.txt");
        // 定义变量,保存数据
        int b ;
        // 循环读取
        while ((b = fis1.read())!=1) {
            System.out.println((char)b);
        }
        // 关闭资源
        fis.close();
        //虽然读取了一个字节,但是会自动提升为int类型。
        //流操作完毕后,必须释放系统资源,调用close方法,千万记得。
        
        
        
        
        // 2.使用字节数组读取: read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1
        // 使用文件名称创建流对象.
        FileInputStream fis2 = new FileInputStream("read.txt"); // 文件中为abcde
        // 定义变量,作为有效个数
        int len ;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        
        // 循环读取
        //由于最后一次读取时,可能只读取一个字节,数组中,上次读取的数据没有被完全替换,如,文件内容为abc,则最后一次打印cb,所以要通过len,获取有效的字节
        //while ((len= fis.read(b))!=‐1) {
        // 每次读取后,把数组变成字符串打印
            //System.out.println(new String(b));
        //}
        
        // 每次读取后,把数组的有效字节部分,变成字符串打印,len 每次读取的有效字节个数,注意有效二字,而不是真实的数组长度
        // 假设这样一种情形,每次读取1024个字节,而文件有1025个字节,那么
        // 第一轮循环,len的值为1024,第二轮循环,len的值为1,第三轮循环,len的值为-1,判定条件为假,不进入while循环
        while ((len= fis2.read(b))!=1) {
            System.out.println(new String(b,0,len));
        }
        // 关闭资源
        fis.close();
    }
}



//图片复制
public class Copy {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 指定数据源
        FileInputStream fis = new FileInputStream("E:\\test.jpg");
        // 1.2 指定目的地
        FileOutputStream fos = new FileOutputStream("E:\\test_copy.jpg");
        // 2.读写数据
        // 2.1 定义数组
        byte[] b = new byte[1024];
        // 2.2 定义长度
        int len;
        // 2.3 循环读取
        while ((len = fis.read(b))!=1) {
            // 2.4 写出数据
            fos.write(b, 0 , len);
        }
        // 3.关闭资源
        fos.close();
        fis.close();
    }
}



//循环复制某个文件夹里的指定文件
public CopyFile{
    
    //定义一个类变量,解决文件命名问题
    static int i = 0;

    public static void main(String[] args) throws IOException {
        File file = new File("E:\\firstLevel");
        getAllJava(file);

    }

    public static void getAllJava(File dir) throws IOException {
        //调用File对象方法listFiles()获取,加入过滤器
        File[] listFile = dir.listFiles((pathname) -> {
            return pathname.isDirectory() || (pathname.isFile() && pathname.getName().endsWith("java"));
        });

        for (File f : listFile) {
            //判断f表示的路径是不是文件夹
            if (f.isDirectory()) {
                //递归进入文件夹遍历
                getAllJava(f);
            } else {
                System.out.println(f.getAbsoluteFile().toString());
                //目标目录
                File toDir = new File("E:\\b");
                //目标文件
                File toFile = new File(toDir, "" + (++i) + ".java");
                 StringBuilder sb = new StringBuilder(f.getAbsolutePath()).reverse();
                //copyFile(f.getAbsolutePath(), toDir, "E:\\b" + sb.substring(5, 6) + ".java");
                copyFile(f.getAbsolutePath(), toDir, toFile);
            }
        }
    }

    public static void copyFile(String fromFile, File toDirPath, File toFile) throws IOException {
        //字节输入流——读取文件
        FileInputStream in = new FileInputStream(fromFile);
        //如果目标目录不存在,就创建它
        if (!toDirPath.exists()) {
            toDirPath.mkdirs();
        }
        //字节输出流——写入文件
        FileOutputStream out = new FileOutputStream(toFile);
        //把读取到的内容写入新文件
        byte[] bs = new byte[1024];
        int count;
        while ((count = in.read(bs)) != -1) {
            out.write(bs, 0, count);
        }
        //关闭流
        in.close();
        out.close();
    }
}
9.2.6.四种文件复制方式效率
  • 对于某个大小为40MB的文件,复制效率测试如下:
    1. 字节流读写单个字节 运行时间:210546ms
    2. 字节流读写字节数组 运行时间:438ms
    3. 字节流缓冲区读写单个字节 运行时间:2304ms
    4. 字节流缓冲区读写字节数组 运行时间:137ms

    速度可能因不同的电脑而不同,但四种方式整体的效率比较类似

public class Test{
    
    //1.字节流读写单个字节
    public static void copy_1(File src, File desc) {
        try {
            FileInputStream fis = new FileInputStream(src);
            FileOutputStream fos = new FileOutputStream(desc);
            int len = 0;
            while ((len = fis.read()) != -1) {
                fos.write(len);
            }
            fis.close();
            fos.close();
        } catch (IOException ex) {
            System.out.println(ex);
            throw new RuntimeException("文件写入失败");
        }
    }
    
    //2.字节流读写字节数组
    public static void copy_2(File src, File desc) {
        try {
            FileInputStream fis = new FileInputStream(src);
            FileOutputStream fos = new FileOutputStream(desc);
            int len = 0;
            byte[] bt = new byte[1024];
            while ((len = fis.read(bt)) != -1) {
                fos.write(bt,0,len);
            }
            fis.close();
            fos.close();
        } catch (IOException ex) {
            System.out.println(ex);
            throw new RuntimeException("文件写入失败");
        }
    }
    
    //3.字节流缓冲区读写单个字节
    public static void copy_3(File src, File desc) {
        try {
            BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(src));
            BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(desc));
            int len = 0;
            while ((len = bufis.read()) != -1) {
                bufos.write(len);
            }
            bufis.close();
            bufos.close();
        } catch (IOException ex) {
            System.out.println(ex);
            throw new RuntimeException("文件写入失败");
        }
    }
    
    //4.字节流缓冲区读写字节数组
    public static void copy_4(File src, File desc) {
        try {
            BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(src));
            BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(desc));
            int len = 0;
            byte[] bt=new byte[1024];
            while ((len = bufis.read(bt)) != -1) {
                bufos.write(bt,0,len);
            }
            bufis.close();
            bufos.close();
        } catch (IOException ex) {
            System.out.println(ex);
            throw new RuntimeException("文件写入失败");
        }
    }
    
    //测试速度
    public static void main(String[] args) {
        long start=System.currentTimeMillis();
        File src = new File("E:\\software\\kugou8322.exe");
        File desc = new File("d:\\oracle code\\longzhoufeng\\kugou.exe");
        copy_1(src, desc);
        long end=System.currentTimeMillis();
        System.out.println(end-start);
    }
}
9.3.字符流
9.3.1.字符介绍
  • 当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
9.3.2.字符输入流
  • java.io.Reader 抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
  • 一连串的 0 1 二进制数据字符序列,以字符为单位,0000 0000 0000 0000共16位。
  • 常用方法
    • public void close() :关闭此流并释放与此流相关联的任何系统资源。
    • public int read() : 从输入流读取一个字符。
    • public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中 。
9.3.3.FileReader
  • java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
    • 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中UTF-8
    • 字节缓冲区:一个字节数组,用来临时存储字节数据。
  • 构造方法
    • public FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。
    • public FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。

    创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

//构造方法
public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("b.txt");
    }
}

//1.读取字符: read 方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回 -1 ,循环读取
public class FRRead1 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存数据
        int b ;
        // 循环读取
        while ((b = fr.read())!= -1 ) {
            System.out.println((char)b);
        }
        // 关闭资源
        fr.close();
    }
}

//2.使用字符数组读取: read(char[] cbuf) ,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1 
public class FRRead2 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len ;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=1) {
            System.out.println(new String(cbuf));
        }
        // 关闭资源
        fr.close();
    }
}
//2.获取有效的字符改进,代码使用演示
public class FISRead3 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len ;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=1) {
            System.out.println(new String(cbuf,0,len));
        }
        // 关闭资源
        fr.close();
    }
}
9.3.4.字符输出流
  • java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
  • 常用方法
    • public void write(int c) 写入单个字符。
    • public void write(char[] cbuf) 写入字符数组。
    • public abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
    • public void write(String str) 写入字符串。
    • public void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
    • public void flush() 刷新该流的缓冲。
    • public void close() 关闭此流,但要先刷新它。
9.3.5.FileWriter
  • java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
  • 构造方法
    • public FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象。
    • public FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称。

    创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

//构造方法
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("b.txt");
    }
}

//1.写出字符: write(int b) 方法,每次可以写出一个字符数据
public class FWWrite {
    public static void main(String[] args) throws IOException {
    // 使用文件名称创建流对象
    FileWriter fw = new FileWriter("fw.txt");
    // 写出数据
    fw.write(97); // 写出第1个字符
    fw.write('b'); // 写出第2个字符
    fw.write('C'); // 写出第3个字符
    fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
    /*
     【注意】关闭资源时,与FileOutputStream不同。
      如果不关闭,数据只是保存到缓冲区,并未保存到文件。
    */
    // fw.close();
    }
}
// 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
// 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。


//2.因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush 方法了。
//flush :刷新缓冲区,流对象可以继续使用。
//close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
        // 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

//3.写出字符数组 : write(char[] cbuf) 和 write(char[] cbuf, int off, int len) ,每次可以写出字符数组中的数据,用法类似FileOutputStream
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串转换为字节数组
        char[] chars = "程序员".toCharArray();
        // 写出字符数组
        fw.write(chars); // 程序员
        // 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
        fw.write(b,2,2); // 程序
        // 关闭资源
        fos.close();
    }
}


//3.写出字符串: write(String str) 和 write(String str, int off, int len) ,每次可以写出字符串中的数据,更为方便,代码使用演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串
        String msg = 程序员";
        // 写出字符数组
        fw.write(msg); //程序员
        // 写出从索引2开始,2个字节。索引2是'程',两个字节,也就是'程序'。
        fw.write(msg,2,2);
        // 程序
        // 关闭资源
        fos.close();
    }
}


//4.续写和换行:操作类似于FileOutputStream。
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象,可以续写数据
        FileWriter fw = new FileWriter("fw.txt"true);
        // 写出字符串
        fw.write("黑马");
        // 写出换行
        fw.write("\r\n");
        // 写出字符串
        fw.write("程序员");
        // 关闭资源
        fw.close();
    }
}
//字符流,只能操作文本文件,不能操作图片,视频等非文本文件。在读的时候回将字节转换为字符,在转换过程中可能找不到对应的字符们就会用?代替,写出的时候回将字符转换成字节写出去,如果是?直接写出后文件就乱了,不能正常识别了
//当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流

可以这样理解文件复制的过程:

  1. 字符流:二进制数据 – 编码 –> 字符编码表 – 解码 –> 二进制数据
  2. 字节流:二进制数据 –> 二进制数据

问题就是出现在编码和解码的过程中,既然是字符的编码表,那它就是包含所有的字符,但是字符的数量是有限的,这就意味着它不能表示一些超过编码表的字符,因为根本不存在表中。所以,JVM 会使用一些字符进行替换,基本上都是乱码(所以大小会发生变化),而且如果有一个数据恰好是-1,那么读取就会中断,引起数据丢失。

9.3.6.IO异常处理
  • 建议使用 try…catch…finally 代码块,处理异常部分
public class HandleException1 {
    public static void main(String[] args) {
        // 声明变量
        FileWriter fw = null;
        try {
            //创建流对象
            fw = new FileWriter("fw.txt");
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • JDK7的处理
//还可以使用JDK7优化后的 try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

try (创建流对象语句,如果多个,使用';'隔开) {
    // 读写数据
} catch (IOException e) {
    e.printStackTrace();
}


public class HandleException2 {
    public static void main(String[] args) {
        // 创建流对象
        try ( FileWriter fw = new FileWriter("fw.txt"); ) {
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • JDK9的改进
//JDK9中 try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close

//改进前格式:
// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引入方式:创建新的变量保存
try (Resource r1 = resource1;Resource r2 = resource2) {
    // 使用对象
}

//改进后格式:
// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引入方式:直接引入
try (resource1; resource2) {
// 使用对象
}

//代码使用
public class TryDemo {
    public static void main(String[] args) throws IOException {
        // 创建流对象
        final FileReader fr = new FileReader("in.txt");
        FileWriter fw = new FileWriter("out.txt");
        // 引入到try中
        try (fr; fw) {
            // 定义变量
            int b;
            // 读取数据
            while ((b = fr.read())!=1) {
                // 写出数据
                fw.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
9.4.属性集
9.4.1.概述
  • java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时, System.getProperties 方法就是返回一个 Properties 对象。
  • Properties : 是一个做持久化的集合类(可以把代码运行中产生的数据, 写出文件,)
    1. 是HashTable的一个子类
    2. 在使用Properties的时候, 不要使用非Peoperties定义的api (尤其Map, Hashtable不要使用)
    3. 在Properties里面存储数据的时候, 只能存储String类型
9.4.2.Properties类
  • 构造方法
    • public Properties() :创建一个空的属性列表。
  • 基本的存储方法
    • public Object setProperty(String key, String value) : 保存一对属性。
    • public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
    • public Set<String> stringPropertyNames() :所有键的名称的集合。
  • 与流相关的方法
    • public void load(InputStream inStream) : 从字节输入流中读取键值对。
public class ProDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 创建属性集对象
        Properties properties = new Properties();
        // 添加键值对元素
        properties.setProperty("filename", "a.txt");
        properties.setProperty("length", "209385038");
        properties.setProperty("location", "D:\\a.txt");
        // 打印属性集对象
        System.out.println(properties);
        // 通过键,获取属性值
        System.out.println(properties.getProperty("filename"));
        System.out.println(properties.getProperty("length"));
        System.out.println(properties.getProperty("location"));
        // 遍历属性集,获取所有键的集合
        Set<String> strings = properties.stringPropertyNames();
        // 打印键值对
        for (String key : strings ) {
            System.out.println(key+" ‐‐ "+properties.getProperty(key));
        }
    }
}
输出结果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename ‐‐ a.txt
length ‐‐ 209385038
location ‐‐ D:\a.txt
     
    
//参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了
public class ProDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        // 创建属性集对象
        Properties pro = new Properties();
        // 加载文本中信息到属性集
        pro.load(new FileInputStream("read.txt"));
        // 遍历集合并打印
        Set<String> strings = pro.stringPropertyNames();
        for (String key : strings ) {
            System.out.println(key+" ‐‐ "+pro.getProperty(key));
        }
    }
}
//文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
//在IDEA中创建config.property文件,可以在不改变程序的前提下,修改配置文件即可满足程序需求
9.5.缓冲流
9.5.1.概述
  • 缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:
    • 字节缓冲流: BufferedInputStream , BufferedOutputStream
    • 字符缓冲流: BufferedReader , BufferedWriter
  • 缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写减少系统IO次数,从而提高读写的效率。
9.5.2.字节缓冲流
  • 构造方法
    • public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流。
    • public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
  • 要创建缓冲流对象,必须在构造方法中,接收一个普通的相应的输入/输出字节流对象,即缓冲流,是基于一个已有的底层的字节流创建出来的。
  • 看起来缓冲流是通过包装了一层底普通字节流得到的,这种流,我们称为包装流。
  • 关闭流的时候,只需要关闭包装流,不需要关闭底层流,因为包装流会负责自己所包装的底层流。
  • 使用字节缓冲流的时候 如果忘记close,数据就不会刷新到文件当中,必须要使用flush方法强制刷新到底层流close方法会自动执行flush方法。可以不用flush方法,但是一定要close。
9.5.3.字符缓冲流
  • 构造方法
    • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
      • 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
      • 通常,Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用BufferedReader 包装所有其 read() 操作可能开销很高的 Reader(如 FileReader 和 InputStreamReader)。如
        • BufferedReader in = new BufferedReader(new FileReader("foo.in"));
    • public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
      • 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
      • 该类提供了 newLine() 方法,它使用平台自己的行分隔符概念,此概念由系统属性 line.separator 定义。并非所有平台都使用新行符 (’\n’) 来终止各行。因此调用此方法来终止每个输出行要优于直接写入新行符。

    字节缓冲流默认缓冲区大小是8kB = 8192Bytes,如果是字符缓冲流则为16KB,大小可以修改。

  • 特有方法
    • BufferedReader: public String readLine() : 读一行文字。文件末尾,返回null而不是 -1
    • BufferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
//readLine()
public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        // 定义字符串,保存读取的一行文字
        String line = null;
        // 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.print(line);
            System.out.println("‐‐‐‐‐‐");
        }
        // 释放资源
        br.close();
    }
}

//newLine()
public class BufferedWriterDemo throws IOException {
    public static void main(String[] args) throws IOException {
        // 创建流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
        // 写出数据
        // 写出换行
        bw.write("程序");
        bw.newLine();
        bw.write("员");
        bw.newLine();
        // 释放资源
        bw.close();
    }
}
9.5.4.文本排序
/*
    请将文本信息恢复顺序。
    3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉
    以咨之,然后施行,必得裨补阙漏,有所广益。
    8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其
    咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
    4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,
    必能使行阵和睦,优劣得所。
    2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不
    宜偏私,使内外异法也。
    1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外
    者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以
    塞忠谏之路也。
    9.今当远离,临表涕零,不知所言。
    6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣
    以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
    7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。
    今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛
    下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
    5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息
    痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
*/

/*
    案例分析
    1. 逐行读取文本信息。
    2. 解析文本信息到集合中。
    3. 遍历集合,按顺序,写出文本信息。
*/

public class BufferedTest {
    public static void main(String[] args) throws IOException {
        // 创建map集合,保存文本数据,键为序号,值为文字
        HashMap<String, String> lineMap = new HashMap<>();
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
        // 读取数据
        String line = null;
        while ((line = br.readLine())!=null) {
            // 解析文本
            String[] split = line.split("\\.");
            // 保存到集合
            lineMap.put(split[0],split[1]);
        }
        // 释放资源
        br.close();
        // 遍历map集合
        for (int i = 1; i <= lineMap.size(); i++) {
            String key = String.valueOf(i);
            // 获取map中文本
            String value = lineMap.get(key);
            // 写出拼接文本
            bw.write(key+"."+value);
            // 写出换行
            bw.newLine();
        }
        // 释放资源
        bw.close();
    }
}
9.6.其他流
9.6.1.数据流
  • DataInputStream/DataOutputStream
    数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据
public class Test(){
    private static void write() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("E:\\dos.txt"));
        dos.writeByte(1);
        dos.writeShort(20);
        dos.writeInt(300);
        dos.writeLong(4000);
        dos.writeFloat(12.34f);
        dos.writeDouble(12.56);
        dos.writeChar('a');
        dos.writeBoolean(true);
        dos.close();
    }
    private static void read() throws FileNotFoundException, IOException {
        DataInputStream dis = new DataInputStream(
        new FileInputStream("dos.txt"));
        byte b = dis.readByte();
        System.out.println(b);
        short s = dis.readShort();
        System.out.println(s);
        int i = dis.readInt();
        System.out.println(i);
        long l = dis.readLong();
        System.out.println(l);
        float f = dis.readFloat();
        System.out.println(f);
        double d = dis.readDouble();
        System.out.println(d);
        char ch = dis.readChar();
        System.out.println(ch);
        boolean bb = dis.readBoolean();
        System.out.println(bb);
        dis.close();
    }
    public static void main(String[] args) throws IOException {
        write();
        read();
    }
}
9.6.2.打印流
  • java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的。
    • 字节打印流 PrintStream
    • 字符打印流 PrintWriter
  • PrintStream
    • 构造方法:
      • public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
      • public PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建新的PrintWriter。
      • public PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter。
    • 常用方法:
      • public void print(String str): 输出任意类型的数据,
      • public void println(String str): 输出任意类型的数据,自动写入换行操作
    • System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以改变它的流向。
public class PrintDemo {
    public static void main(String[] args) throws IOException {
        // 调用系统的打印流,控制台直接输出97
        System.out.println(97);
        // 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("ps.txt");
        // 设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
        // 调用系统的打印流,ps.txt中输出97
        System.out.println(97);
    }
}
9.6.3.转换流
  • 字符编码和字符集
    • 字符编码:计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
    • 字符编码 Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
    • 字符集:字符集 Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
    • 字节流通过InputStreamReader读取字节,解码为字符,字符流通过OutputStreamReader写出字符,编码为字节。
  • FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
public class ReaderDemo {
    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();
    }
}
输出结果:
���
  • InputStreamReader
    • 转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
    • InputStreamReader使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
    • 构造方法
      • public InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
      • public InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。
    • 常用方法
      • public void close() 关闭该流并释放与之关联的所有资源。
      • public String getEncoding() 返回此流使用的字符编码的名称。
      • public int read() 读取单个字符。
      • public int read(char[] cbuf, int offset, intlength) 将字符读入数组中的某一部分。

    FileReader 对比 InputStreamReader

    1. FileReader创建对象简单,OutputStreamReader繁琐
    2. FileReader只能使用默认字符集,OutputStreamReader可以指定字符集编码
//构造方法
InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。

//指定编码读取
public class ReaderDemo {
    public static void main(String[] args) throws IOException {
        // 定义文件路径,文件为gbk编码
        String FileName = "E:\\file_gbk.txt";
        // 创建流对象,默认UTF8编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
        // 创建流对象,指定GBK编码
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
        // 定义变量,保存字符
        int read;
        // 使用默认编码字符流读取,乱码
        while ((read = isr.read()) !=1) {
            System.out.print((char)read); // ���
        }
        isr.close();
        // 使用指定编码字符流读取,正常解析
        while ((read = isr2.read()) !=1) {
            System.out.print((char)read);// 大家好
        }
        isr2.close();
    }
}
  • OutputStreamWriter
    • 转换流 java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
    • OutputStreamWriter 使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。
    • 构造方法
      • public OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。
      • public OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。
    • 常用方法:
      • public void close() 关闭此流,但要先刷新它。
      • public void flush() 刷新该流的缓冲。
      • public String getEncoding() 返回此流使用的字符编码的名称。
      • public void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
      • public void write(int c) 写入单个字符。
      • public void write(String str, int off, int len) 写入字符串的某一部分

    FileWriter 对比 OutputStreamWriter

    1. FileWriter创建对象简单,OutputStreamWriter繁琐
    2. FileWriter只能使用默认字符集,OutputStreamWriter可以指定字符集编码
//构造方法
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

//指定字符集写出
public class OutputDemo {
    public static void main(String[] args) throws IOException {
        // 定义文件路径
        String FileName = "E:\\out.txt";
        // 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
        osw.write("你好"); // 保存为6个字节
        osw.close();
        // 定义文件路径
        String FileName2 = "E:\\out2.txt";
        // 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 写出数据
        osw2.write("你好");// 保存为4个字节
        osw2.close();
    }
}
  • Example:
//转换文件编码
//将GBK编码的文本文件,转换为UTF-8编码的文本文件。

public class TransDemo {
    public static void main(String[] args) {
        // 1.定义文件路径
        String srcFile = "file_gbk.txt";
        String destFile = "file_utf8.txt";
        // 2.创建流对象
        // 2.1 转换输入流,指定GBK编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
        // 2.2 转换输出流,默认utf8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
        // 3.读写数据
        // 3.1 定义数组
        char[] cbuf = new char[1024];
        // 3.2 定义长度
        int len;
        // 3.3 循环读取
        while ((len = isr.read(cbuf))!=1) {
            // 循环写出
            osw.write(cbuf,0,len);
        }
        // 4.释放资源
        osw.close();
        isr.close();
    }
}
9.6.4.序列化
  • 概念
    • 序列化: 就是将内存中的对象数据,转化为二进制数据,输出到外部设备,即永久保存内存中的数据
    • 反序列化:将外部设备上,永久保存的数据,读取到内存中,还原成对象
    • Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据 、 对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象
  • ObjectOutputStream
    • java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
    • 构造方法
      • public ObjectOutputStream(OutputStream out)
    • 成员方法
      • public void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
    • 序列化操作:
      • 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
      • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。
//序列化操作
public class Employee implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
    public void addressCheck() {
        System.out.println("Address check : " + name + " ‐‐ " + address);
    }
}

//写出对象方法    public final void writeObject (Object obj) : 将指定的对象写出。
public class SerializeDemo{
    public static void main(String [] args) {
        Employee e = new Employee();
        e.name = "zhangsan";
        e.address = "beiqinglu";
        e.age = 20;
        try {
            // 创建序列化流对象
            FileOutputStream fileOut = new FileOutputStream("employee.txt");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            // 写出对象
            out.writeObject(e);
            // 释放资源
            out.close();
            fileOut.close();
            System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
        } catch(IOException i) {
            i.printStackTrace();
        }
    }
}
输出结果:
Serialized data is saved
  • ObjectInputStream
    • ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
    • 构造方法
      • public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
    • 反序列化操作
      1. 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:
        • public final Object readObject () : 读取一个对象。
    public class DeserializeDemo {
        public static void main(String [] args) {
            Employee e = null;
            try {
                // 创建反序列化流
                FileInputStream fileIn = new FileInputStream("employee.txt");
                ObjectInputStream in = new ObjectInputStream(fileIn);
                // 读取一个对象
                e = (Employee) in.readObject();
                // 释放资源
                in.close();
                fileIn.close();
            }catch(IOException i) {
                // 捕获其他异常
                i.printStackTrace();
                return;
            }catch(ClassNotFoundException c) {
                // 捕获类找不到异常
                System.out.println("Employee class not found");
                c.printStackTrace();
                return;
            }
            // 无异常,直接打印输出
            System.out.println("Name: " + e.name);
            // zhangsan
            System.out.println("Address: " + e.address); // beiqinglu
            System.out.println("age: " + e.age); // 0
        }
    }
    

    对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常。

    1. 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出InvalidClassException 异常。发生这个异常的原因如下:

      • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
      • 该类包含未知数据类型
      • 该类没有可访问的无参数构造方法

      Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

    public class Employee implements java.io.Serializable {
        // 加入序列版本号
        private static final long serialVersionUID = 1L;
        public String name;
        public String address;
        // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
        public int eid;
        public void addressCheck() {
            System.out.println("Address check : " + name + " ‐‐ " + address);
        }
    }
    
  • Example:
    • 序列化集合
      1. 将存有多个自定义对象的集合序列化操作,保存到 list.txt 文件中。
      2. 反序列化 list.txt ,并遍历集合,打印对象信息。
    /*
        案例分析
        1. 把若干学生对象 ,保存到集合中。
        2. 把集合序列化。
        3. 反序列化读取时,只需要读取一次,转换为集合类型。
        4. 遍历集合,可以打印所有的学生信息
    */
    
    public class SerTest {
        public static void main(String[] args) throws Exception {
            // 创建 学生对象
            Student student = new Student("老王", "laow");
            Student student2 = new Student("老张", "laoz");
            Student student3 = new Student("老李", "laol");
            ArrayList<Student> arrayList = new ArrayList<>();
            arrayList.add(student);
            arrayList.add(student2);
            arrayList.add(student3);
            // 序列化操作
            // serializ(arrayList);
            // 反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
            // 读取对象,强转为ArrayList类型
            ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
            for (int i = 0; i < list.size(); i++ ){
                Student s = list.get(i);
                System.out.println(s.getName()+"‐‐"+ s.getPwd());
            }
        }
        private static void serializ(ArrayList<Student> arrayList) throws Exception {
            // 创建 序列化流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
            // 写出对象
            oos.writeObject(arrayList);
            // 释放资源
            oos.close();
        }
    }
    

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181081.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!