【Java32】缓冲流/转换流,序列化流/打印流,Properties


1.标准的IO流异常处理

字符流用于读写文本文件。字节流outputStream(内存->硬盘:写)也可以但没字符流FileWriter封装度高。如下是四个抽象类的其他子类,BufferedOs后面Os是outputStream简写。

OutputStreamWriter是Writer的子类,是FileWriter的父类,没FileWriter封装程度高,暴露了os(字节流)和charset(编码表)。
在这里插入图片描述

实际开发a.txt(已存在)和b.txt路径由用户指定会发生异常,IO不能直接抛,程序会崩,所以自己处理try catch。不在try中编译异常就是前句IO异常如下红色。
在这里插入图片描述
如下改进就在try外面int i = 0;定义+赋值。同理FileInputStream fis = null;
在这里插入图片描述

package com.itheima01.exception;
import java.io.*;
/*
* 不论什么类型文件复制都用 字节流!!! 理由:1.没有编解码就不会产生乱码, 用字符流有乱码的风险                              			       *                                         2.没有编解码复制文件更快。
*     
* 如果用字符流复制:GBK编码,utf-8解码,所以在读取时就会产生乱码【如果用字节流复制:读取和写入都没有编解码, 直接就是字节数据的搬运】
*       1. 读取 : a.txt(GBK编码)        (解码: 二进制 -> 字符)  
*                   中 -> (GBK)20013        20013 ->  ? (utf-8解码)
*       2. 写入 : b.txt   (编码: 字符 -> 二进制)  
*                    (上面的问号)?  -> 10000 (utf-8编码),应该为20013而不是10000 
*       
*    # 异常处理
*       try{
*               可能出现异常的代码
*          }catch(Exception e){
*          }
*/
public class ExceptionDemo {
    
    
    public static void main(String[] args)  {
    
     //之前图省事直接在main这行throws IOException
        // 外边定义,里面赋值 // 每个变量在使用的时候,必须有值        
        FileInputStream fis = null;
        FileOutputStream fos = null ;
        try {
    
     
			//int i=1/0;  //下面fis没有赋上值,所以外面定义时赋null值
            fis = new FileInputStream("a.txt"); //刚指定完a.txt,有个程序将a.txt干掉了,出问题:文件找不到,所以IO不能直接抛			
            fos = new FileOutputStream("b.txt");

            int length = -1;
            byte[] buffer = new byte[1024];
            while((length = fis.read(buffer)) != -1){
    
    
                fos.write(buffer,0,length);
            }       
            
//问题: 如下两行io流用完要及时释放, 如果上面读写的过程中出了异常,下面这段代码就不会执行了,就像水龙头没关,直接跳到catch里
            // 解决: finally代码块。try中定义的fis,catch和finally都不会访问到,所以要定义全局变量
//            fos.close();
//            fis.close();
        } catch (IOException e) {
    
     //出异常砸盆子,水龙头却没关
            e.printStackTrace();
            
        } finally{
    
     //最常见就在这里
            if(fos != null){
    
    //避免空指针,fos有可能为空(try中值没赋上),空对象调方法产生空指针异常
                try {
    
    
                    fos.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
            if(fis != null){
    
    
                try {
    
    
                    fis.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

2.其他的异常处理方式

如上代码冗余

package com.itheima01.exception;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
// 多态: 简化代码  所有的IO流都实现了 Closeable 接口 (因为所有的IO流有close方法),选中Closeable按ctrl+h
public class ExceptionDemo02 {
    
    
    public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        FileOutputStream fos = null ;
        try {
    
    
            fis = new FileInputStream("a.txt");
            fos = new FileOutputStream("b.txt");
            int length = -1;
            byte[] buffer = new byte[1024];
            while((length = fis.read(buffer)) != -1){
    
    
                fos.write(buffer,0,length);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();

//1111111111111111111111111111111111111111111111111111111111111111111            
        } finally{
    
    
//            release(fos);
            release(fos,fis); //多态
        }
    }
    private static void release(Closeable... ios) {
    
    
        for (Closeable io : ios) {
    
     //遍历数组
            if(io != null){
    
     //避免空指针
                try {
    
    
                    io.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}
package com.itheima01.exception;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* JDK7 特性:
*       try...with resource
*       try( 要释放的IO流对象A,B) {
*           当这里的代码执行完, A B流就会被自动释放掉(不需要手动调用close)
*       }
*       特点: A,B 默认被final所修饰
*/
public class ExceptionDemo03 {
    
    
    public static void main(String[] args) {
    
    
        try(FileInputStream fis = new FileInputStream("a.txt");
            FileOutputStream fos = new FileOutputStream("b.txt")) {
    
                
            int length = -1;
            byte[] buffer = new byte[1024];
            while((length = fis.read(buffer)) != -1){
    
    
                fos.write(buffer,0,length);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

3.字节缓冲流

package com.itheima02.buffer;
import java.io.*;
/*
*  字节缓冲流:1. public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
		         public BufferedInputStream(InputStream in, int size)
	     	  2. public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
*     字节缓冲流 底层: 字节流 + 缓存区(默认8k)
*              目的: 用空间(内存)换时间 (提高效率)
*              方法: 构造方法注意一下,需要手动传入一个字节流对象。其他方法都跟父类一样
*                   80k(一级)  -> 1k(二级)写入到硬盘
*/
public class ByteDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        FileInputStream is = new FileInputStream("a.txt"); //注意是is不是fis
        FileOutputStream os = new FileOutputStream("b.txt");        
        //如下字节缓存流
        BufferedInputStream fis = new BufferedInputStream(is,80*1024);//字符默认8k,这个可以手动调 
        BufferedOutputStream fos = new BufferedOutputStream(os);

//11111111111111111111111111111111111111111111111111111111111111111111111111111111        
        int length = -1;
        byte[] buffer = new byte[1024]; //这里1kb【二级】最好和上面80k【一级】一致,这样不会有空间浪费问题
        while((length = fis.read(buffer)) != -1){
    
    
            fos.write(buffer,0,length);
        }
        fos.close();
        fis.close();
    }
}

4.字符缓冲流

在这里插入图片描述

package com.itheima02.buffer;
import java.io.*;
/*
* 字符缓冲流:1. 构造
			    1. public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
			    2. public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
		 2. 特有方法
			1. BufferedReader: public String readLine() : 读一行文字。
			2. BufferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
*/
public class CharDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
        BufferedReader br = new BufferedReader(new FileReader("a.txt")); //BufferedReader(字符流),BufferedReader也默认8kb
//        String line = br.readLine();
//        System.out.println(line);

        String line = null;
        while((line = br.readLine()) != null){
    
    
            System.out.println(line); //有几行打印几行
        }        
        br.close(); //关高级流,会默认将低级流也会一并关掉
        
//11111111111111111111111111111111111111111111111111111111111111111111111111111      
        BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
        bw.write("一句话");
        bw.write(System.lineSeparator()); //换行
        bw.write("一段话");
        bw.newLine(); //换行,底层就是上面System.lineSeparator()
        bw.write("一翻话");
        bw.close();
    }
}

5.案例_出师表

在这里插入图片描述

package com.itheima02.buffer;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/*
*   需求: 将a.txt中的内容,按照每行的序号进行排序 b.txt
*       1. 先把a.txt中的数据都读出来
*       2. 一次读一行, 进行切割  number -> 语句
* 
*       方案A
*       3. 放HashMap <Integer,String> map
*       4. for i 1-9 (i = key) //不需要排序,按序号取出并输出
*           value = map.get(key)
* 
*       方案B
*        3. 放TreeMap<Integer,String>  //TreeMap的key会自动排序的,Integer类型会按自然顺序排,底层是treeset比较器默认自动升序。
*        4. 遍历打印
*/
public class OutTeacherWatchDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
//        method01();
        TreeMap<Integer, String> map = new TreeMap<>();
        BufferedReader br = new BufferedReader(new FileReader("a.txt"));
        String line = null;
        while((line = br.readLine()) != null){
    
    
            String[] split = line.split("\\.");
            Integer number = Integer.parseInt(split[0]);
            String content = split[1];
            map.put(number,content);
        }
        br.close();

        BufferedWriter bw = new BufferedWriter(new FileWriter("c.txt"));        
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet(); //直接遍历treeMap,输出即可
        for (Map.Entry<Integer, String> entry : entrySet) {
    
    
            Integer key = entry.getKey();
            String value = entry.getValue();
            bw.write(key + "." + value);
            bw.newLine();
        }
        bw.close();
    }

//111111111111111111111111111111111111111111111111111111111111111111111111111111111111111       
    private static void method01() throws IOException {
    
      //方案A
        HashMap<Integer, String> map = new HashMap<>();         
        BufferedReader br = new BufferedReader(new FileReader("a.txt")); //这是读取a.txt的流
        String line = null;
        while((line = br.readLine()) != null){
    
                    
            String[] split = line.split("\\."); // 注意: \\. 表示一个. (正则表达式)               
            Integer number = Integer.parseInt(split[0]); //前面是数字                
            String content = split[1]; //后面是内容
            map.put(number,content);
        }
        br.close();
     
        BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); //这是输出文本的流        
        for (int i = 1; i <= 9; i++) {
    
     // 遍历1~9 -> 指的就是上面的number
            String content = map.get(i);
            bw.write(i +"." + content);
            bw.newLine(); //添加一个换行符
        }
        bw.close();
        System.out.println("复制完成");
    }
}

6.转换流

package com.itheima03.transfer;
import java.io.*;
/*
*   案例: 系统文件(GBK 编码)  ->  程序(utf-8 解码)
*        乱码: 编解码所使用的编码表不一致。 解决: 将编码和解码编码表一致
* 
*      方案A: (最常用)
*       系统文件GBK -> UTF-8
*       FileReader 流 搞定
* 
*      方案B:
*        程序默认编码表改成GBK (一般不会这么做!!!)
*        FileReader 流 搞定
* 
*       方案C:
*          将IO流的解码字符集 指定为gbk , 但是程序还是utf-8
*      InputStreamReader
*           (在开发中一般也不用, 开发中的涉及的程序和资料都是UTF-8)
*/
public class Demo01 {
    
    
    public static void main(String[] args) throws IOException {
    
    
//  FileReader fr = new FileReader("c:/test/a.txt"); //FileReader底层默认编码表utf-8 //与a.txt的GBK会乱码
        FileInputStream fis = new FileInputStream("c:/test/a.txt"); //a.txt里内容:写一句话
        InputStreamReader fr = new InputStreamReader(fis, "gbk");
        
        int content = -1;
        while((content = fr.read()) != -1){
    
    
            System.out.println((char)content); 
        }
        fr.close();
    }
}

在这里插入图片描述

7.案例_文本编码转换

package com.itheima03.transfer;
import java.io.*;
/*
*   练习:转换文件编码
		1. 将GBK编码的文本文件,转换为UTF-8编码的文本文件。
	   分析:
	        1. 	读GBK编码文件 : 用GBK编码的字符输入流
	            1. FileReader : FileInputStream + utf-8 默认 (不行)
	            2. InputStreamReader :  FileInputStream + gbk 指定 (可以)
	           
	        2. 写utf-8编码文件 : 用utf-8编码的字符输出流
	            1. FileWriter : FileoutputStream + utf-8 默认 (可以)
	            2. OutputStreamWriter : FileoutputStream + utf-8 指定 (可以)
	            两个都可以, 用 FileWriter (简单)
*/
public class Demo02 {
    
    
    public static void main(String[] args) throws IOException {
    
    
        InputStreamReader isr
                = new InputStreamReader(new FileInputStream("c:/test/a.txt"), "gbk");
//        FileWriter fw = new FileWriter("c:/test/b.txt"); //用下面也可以
        OutputStreamWriter osw
                = new OutputStreamWriter(new FileOutputStream("c:/test/b.txt"), "utf-8");

//111111111111111111111111111111111111111111111111111111111111111111111111                
        int length = -1;
        char[] buffer = new char[1024]; //用的字符流,不是byte[]
        while((length = isr.read(buffer)) != -1){
    
    
            osw.write(buffer,0,length);
        }
        osw.close();
        isr.close(); //运行后产生b.txt utf-8 9字节 , a.txt gbk 6字节
    }
}

8.序列化流

package com.itheima04.serial;
import java.io.*;
/*
*   文本编码: 字符 -> 字节也叫二进制 (目的保存数据)
* 
*   序列化(serializable) : 对象/数据结构 -> 二进制 (目的将对象直接保存在硬盘上) 存档
*               Base64 编码等 和utf-8和gbk没有关系
* 
*   反序列化 : 二进制 -> 对象 (将硬盘上的数据 读到 内存中形成对象) 读档
*               Base64 解码
* 
        public ObjectOutputStream(OutputStream out) : 序列化流
        public ObjectInputStream(InputStream in) :  反序列化流
        
    总结: 一个类要允许被序列化:
            1. 首先: 实现Serializable接口
            2. 其次: 这个类要设置唯一的serialVersionUID (String类源码里也有)        
  补丁:transient : 瞬态 (关键字),用这个关键字修饰的属性,不允许序列化/反序列化,如密码不希望被保存
*/
public class Demo01 {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
    
//        method01(); //序
        method2();  //反序
    }

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    private static void method2() throws IOException, ClassNotFoundException {
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));        
        Object s1 = ois.readObject();// 反序列化 //具体是什么对象, 设计成Object
//        Student s1 = (Student) ois.readObject(); //可以,同上行输出一样
        System.out.println(s1); //Student{name'张三',age=18}
        ois.close();
    }

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 
    private static void method01() throws IOException {
    
    
        Student s = new Student("张三", 18); //这对象在内存中
//      System.out.println(s.toString()); //打印出:Student{name="张三"},age=18},把对象进行字符串化(比如记录文件的信息,不记录内容)保存,保存起来再转化成对象需要读出来后取出值再新建一个对象。麻烦

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt")); //输出流new一下就产生一文件 
        oos.writeObject(s); // 序列化 //存档
        oos.close();
    }
}
package com.itheima04.serial;
import java.io.Serializable;
/*
*      序列化相关的两个异常之一:
*           1. java.io.NotSerializableException : 无法序列化异常
*               java规定: 某个类的对象 要可以被序列化, 必须实现一个接口 Serializable
*               Serializable(标识接口) : 没有任何内容的接口 -> 授权 ,允许被人家保存
* 
*           2. java.io.InvalidClassException:  无效的类异常
*             保存的时候(Student :没有int number) 现在读取的时候(Student : 有int number)
*             解决: 搞一个身份证 (这个类不论如何改变,始终JVM都能识别)
*             JVM 识别一个类: serialVersionUID(不同的类,这个id不可以一样的)
*/
public class Student implements Serializable{
    
    
    private final static long serialVersionUID = 1L;  //长整型  //需要method01重新保存
    private String name;
    private transient int age; //age不希望暴露,int默认为0且永远0 //Student{name'张三',age=0}
    private String password;
    int number;
    int year;
    public Student(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    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;
    }
}

9.打印流(最后一个流)

package com.itheima05.print;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;

public class PrintDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
//        method();

//111111111111111111111111111111111111111111111111111111111111111111111111111111111
      /*
      *系统打印流: 输出在控制台不是在硬盘上(控制台也是内存,关了就没了)(特殊),与字节输出流(输出硬盘上)不一样。
      *public final static PrintStream out = null; //在java中, 用final修饰,并且赋值为null, 还调用out方法竟然没有空指针 !!!
      *在java中赋值为null是无效,在JVM底层,这个流是C语言赋值的 (JNI),这样可以直接控制操作系统界面如控制台
      *private static native void setOut0(PrintStream out);  本地方法 (底层调用C)
      */
//    System.setOut(new PrintStream("print.txt")); //与下行同时运行,yyyyyyyyy打印到print.txt,控制台改到print.txt
        System.out.println("yyyyyyyyy"); //控制台 //out是PrintStream类型
        
//        int i=1/0;
        System.err.println("哈哈"); // 红色 (一般 底层打印错误信息)
    }

//1111111111111111111111111111111111111111111111111111111111111111111111111111
    private static void method() throws IOException {
    
    
        PrintStream ps = new PrintStream("print.txt"); //(硬盘上的.txt) //实际上new了一个FileOutputStream
        ps.write(97);
        ps.write("xxx".getBytes());  //对应下面ps.print
        ps.write(System.lineSeparator().getBytes());  //对应ps.println    
            
        ps.print("xxx"); //底层调用.write
        ps.println("xxx");
        ps.close();
    }
}

10.Properties

package com.itheima06.properties;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
/*
* Properties : 是一个Map (放弃了泛型)
*       0. 父类 :Hashtable就是HashMap
*       Hashtable : 哈希表 (线程同步: 线程安全,但是效率低,大部分方法都加了synchronized关键字)(淘汰)
*       HashMap : 哈希表 (线程不同步 : 不安全效率高。真的有线程并发问题,自己可以加同步代码块,锁范围越小越好)
* 
*       1. 厉害的地方: 可以跟IO流相互合作
*          组织数据结构:把Map结构的数据存到硬盘,把硬盘中map结构读取到内存    
*  
		2. 方法:
            1. setProperty(String key, String value):调用 Hashtable 的方法 相当于put()
            2. stringPropertyNames()返回此属性列表中的键集  相当于keySet()
       (上面方法用不到,因为不会用Property保存数据,用Property读配置文件,以下重要!!!)
            3. getProperty(String key)用指定的键在此属性列表中搜索属性
            4. load(InputStream) 和load(Reader):把指定流所对应的文件中的数据,读取出来,保存到Propertie集合中
            开发中的应用: 读取配置文件
*/
public class PropertiesDemo {
    
    
    public static void main(String[] args) throws IOException {
    
    
//        method();

        //1. 创建了Properties对象(Map对象)
        Properties p = new Properties(); //集合都在java.util包下
        
        //2. 跟IO流合作 (读取map结构的数据 -> 数据以Map结构封装p对象)
        p.load(new FileInputStream("note.properties")); //note.properties里写了name=zs age=18 ..。note.txt也可以。 //以前读法:字符流一次读一行
        
//        Object name = p.get("name"); //返回object不好操作
//        Object age = p.get("age"); //常用下面api
        String name = p.getProperty("name"); //网页,控制台,其他文件数据读到程序里都是字符串
        String age = p.getProperty("age"); //就算是数字,读进来也是字符串
        System.out.println(name + "," +  age);//用p.get时不能写成(name+age),因为两个object不能相加,所以中间加一个字符串",",底层会调用toString方法
	}

//111111111111111111111111111111111111111111111111111111111111111111111111111111
    private static void method() {
    
    
        Properties map = new Properties(); //Map<Object,Object> map = new Properties();
        map.put(1,"张三");
        map.put(2,"李四");
        System.out.println(map); //{2=李四,1=张三}
    }
}

"读或写"中间没有"或"就是复制,纯文本读或写用字符流好,不是复制文件。写用OutputStream/Writer即存档。
在这里插入图片描述
B站/知乎/微信公众号:码农编程录
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43435675/article/details/108426320
今日推荐