Back

Java_IO流_2

JavaIO流的处理流与底层分析

JavaIO流2


  1. BufferedReader与BufferedWriter
  2. 使用字节流实现对二进制文件复制
  3. 对象流
  4. 标准输入输出流
  5. 转换流
  6. 打印流
  7. Properties类

BufferedReader与BufferedWriter

  • BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的。

  • 关闭处理流的时候,只需要关闭外层流即可

    演示bufferedReader的使用

    package com.java_io;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    
    public class BufferedReaderTest {
        public static void main(String[] args) throws Exception{
            String path = "E://note.txt";
            //创建BufferedReader对象
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
            //按行读取,提高效率
            String line;
            /*
            * bufferedReader.readLine()是按行读取文件的
            * 当返回null时,表示已读取完毕
            */
            while ((line =bufferedReader.readLine())!=null){
                System.out.println(line);
            }
            bufferedReader.close();
        }
    
    }
    

    image1

  • 需要注意的是,关闭流时只需要关闭BufferedReader。因为底层会自动关闭节点流

    底层代码

        public void close() throws IOException {
            synchronized (lock) {
                if (in == null)
                    return;
                try {
                    in.close();
                } finally {
                    in = null;
                    cb = null;
                }
            }
        }
    

    image2

  • 演示BufferedWriter

    package com.java_io;
    
    import java.io.BufferedWriter;
    import java.io.FileWriter;
    
    public class BufferedWriterTest {
        public static void main(String[] args) throws Exception{
            String path = "E://a.txt";
            //创建对象,把FileWriter的Boolean值置为true可以实现追加功能
            //默认为false,会覆盖原来的内容
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path,true));
    
            bufferedWriter.write("hello,成志恒");
            //插入一个当前系统的换行符
            bufferedWriter.newLine();
            bufferedWriter.write("hello2,成志恒");
            bufferedWriter.newLine();
            bufferedWriter.write("hello3,成志恒");
            bufferedWriter.newLine();
    
            bufferedWriter.close();
        }
    }
    

    image3

  • 使用bufferd进行文件拷贝

    package com.bufferd;
    
    import java.io.*;
    
    public class BufferedCopy_ {
        public static void main(String[] args) {
            String path1 = "e://a.txt";
            String path2 = "e://a1.txt";
            BufferedReader br = null;
            BufferedWriter bw = null;
            String line;
            try {
                br = new BufferedReader(new FileReader(path1));
                bw = new BufferedWriter(new FileWriter(path2));
                while ((line = br.readLine())!=null){
                    bw.write(line);
                    bw.newLine();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(br != null){
                        br.close();
                    }
                    if(bw != null){
                        bw.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    使用说明:

    • BufferedReader和BufferedWriter是按照字符操作的
    • 如果去操作二进制文件(声音,视频,doc,pdf),可能造成文件损坏

使用字节流实现对二进制文件复制

  • BufferedOutputstream和BufferedInputStream结合实现对图片的拷贝

    package com.bufferd;
    
    import com.sun.xml.internal.ws.api.ha.StickyFeature;
    
    import java.io.*;
    
    public class BufferedOutputStream_ {
        public static void main(String[] args) {
            String path1 = "e://567.jpg";
            String path2 = "e://678.jpg";
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
            try {
                bis = new BufferedInputStream(new FileInputStream(path1));
                bos = new BufferedOutputStream(new FileOutputStream(path2));
                byte[] b = new byte[1024];
                int len;
                while((len = bis.read(b))!=-1){
                    bos.write(b,0,len);
                }
                System.out.println("拷贝成功...");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(bis!=null){
                        bis.close();
                    }
                    if (bos!=null){
                        bos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    
  • 字节流可以操作二进制文件,也可以操作文本文件

对象流

  • 序列化和反序列化

    1. 序列化就是在保存数据时,保存数据的值和数据类型
    2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
    3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,改类必须实现两个接口之一:
      • Serializable //这是一个标记接口,没有方法
      • Externalizable //该接口有方法需要实现,一般不使用
  • ObjectOutputStream提供序列化功能

    • 序列化后保存的文件格式不是纯文本的,而是按照序列化的格式来保存

    • 常用的几种基本类型都实现了Serializable接口,所以可以直接保存其数据类型

      image4

    • 如果需要实例化某个类的对象,该类需要实现Serializable

    代码演示

    package com.outputstream;
    
    import java.io.FileOutputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class ObjectOutputStream_ {
        public static void main(String[] args) throws Exception{
            String filePath = "e:\\a.bat";
    
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
    
            oos.write(100);//int -> Integer(实现了Serializable接口)
            oos.writeBoolean(true);//boolean -> Boolean(实现了Serializable接口)
            oos.writeChar('a');//char -> Character(实现了Serializable接口)
            oos.writeUTF("成志恒");//string -> String(实现了Serializable接口)
            oos.writeDouble(9.5);//double -> Double(实现了Serializable接口)
    
            //保存一个dog对象
            oos.writeObject(new Dog("dog",10));
            oos.close();
            System.out.println("保存成功...");
        }
    
    }
    class Dog implements Serializable {
        public Dog(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        private String name;
        private int age;
    }
    
  • ObjectInputStream提供反序列化功能

    • 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
    • 在反序列化的时候,如果需要调用对象(dog)的方法,则需要向下转型,而实现向下转型的关键是当前类可以引用dog类,即在当前类下引用类。

    代码演示

    package com.inputstream_;
    
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    import com.outputstream.Dog;8//引用dog类
    
    
    public class ObjectInputStream_ {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            String filePath = "e:\\a.bat";
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
    
            System.out.println(ois.readInt());
            System.out.println(ois.readBoolean());
            System.out.println(ois.readChar());
            System.out.println(ois.readUTF());
            System.out.println(ois.readDouble());
            Object dog = ois.readObject();
            System.out.println("类型为:"+dog.getClass());
            System.out.println(dog);
    
            //向下转型
            Dog dog1 = (Dog)dog;
            System.out.println(dog1.getAge());
    
            ois.close();
            System.out.println("反序列成功");
        }
    }
    

    image5

  • 注意事项和细节说明

    • 读写顺序要一致

    • 要求实现序列化或反序列化对象,需要实现Serializable接口

    • 序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性

      //SerialVersionUID序列化的版本号,可以提高兼容性
      private static final long serialVersionUID = 1L;
      
    • 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员

      //下面两个属性都不会被序列化
      private static String color;
      private transient String nation;
      
    • 序列化对象时,要求里面属性的类型也需要实现序列化接口

      代码演示

      Master类

      package com.outputstream;
      
      public class Master {
      }
      

      Dog类新增加一个Master类型的属性

      private Master master = new Master();
      

      此时运行ObjectOutputStream_系统会报错

      image6

      如果要解决上述问题,需要Master类实现Serializable接口

      package com.outputstream;
      
      import java.io.Serializable;
      
      public class Master implements Serializable {
      
      }
      

      此时再运行ObjectOutoutStream_,保存成功

      image7

    • 序列化具备可继承性,也就是如果某类已经 实现了序列化,则它的所有子类也已经默认实现了序列化

标准输入输出流

  • System.in(标准输入:键盘)

    • 该流为System类中的public final static InputStream in = null;
    • 编译类型:InputStream
    • 运行类型:BufferedInputStream
  • System.out(标准输出:显示器)

    • 该流为System类中的public final static PrintStream out = null;
    • 编译类型:PrintStream
    • 运行类型:PrintStream
  • 代码演示

    package com.standard;
    
    import java.util.Scanner;
    
    public class OutAndIn {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入数据");
            String next = scanner.next();
            System.out.println("next="+next);
        }
    }
    

    image8

转换流

  • 在读取文件内容时,会默认文件为utf-8方式编码的,一旦文件编码方式改变了,所读取到的信息就会产生乱码,所以有转换流的出现

  • 转换流可以把一个字节流转换成字符流,而字节流读取文件信息可以按照其编码方式来读取,所以能很好解决乱码问题

  • 转换流有InputStreamReader与OutputStreamWriter

    • InputStreamReader:Reader的子类,可以将InputStream包装成Reader

    • OutputStreamWriter:Writer的子类,可以将OutputStream包装成Writer

    • 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流

    • 可以在使用是指定编码格式(比如utf-8,gbk,gb2312等)

      image9

  • 演示使用InputStreamReader转换流解决中文乱码问题

    将字节流FileInputStream转换成字符流InputStreamReader,指定编码gbk/utf-8

    package com.inputstream_;
    
    import java.io.*;
    
    public class InputStreamReader_ {
        public static void main(String[] args) throws IOException {
            String filePath = "e:\\a.txt";
    
            //1.将FieleInputStream转换成InputStreamReader,编码方式为gbk
            InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"gbk");
            //2.将InputStreamReader传入BufferedReader
            BufferedReader br = new BufferedReader(isr);
            //3.读取文件
            String s = br.readLine();
            //4.打印文件内容
            System.out.println(s);
            //5.关闭流
            br.close();
        }
    }
    

    为了减少代码行数,在开发中经常把1和2合在一起写

     BufferedReader br = new BufferedReader(
         new InputStreamReader(
             new FileInputStream(filePath),"gbk"));
    
  • OutStreamWriter同理

    package com.outputstream;
    
    import java.io.BufferedWriter;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    
    public class OutStreamWriter_ {
        public static void main(String[] args) throws IOException {
            String filePath = "e:\\a.txt";
    
            String charSet = "utf-8";
    
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath),charSet);
    
            osw.write("hello,成志恒11");
    
            osw.close();
        }
    }
    

打印流

  • 打印流只有输出流没有输入流

  • PrintStream(字节打印流)

    在默认情况下,PrintStream输出数据的位置是标准输出(显示器)

    演示代码

    package com.print;
    
    import java.io.PrintStream;
    
    public class PrintStream_ {
        public static void main(String[] args) {
            PrintStream printStream = System.out;
    
            printStream.println("hello,word");
    
            printStream.close();
        }
    }
    

    image10

    通过查看Print的底层源码

        public void print(String s) {
            if (s == null) {
                s = "null";
            }
            write(s);
        }
    

    可以知道print底层使用的是write,所以我们可以直接调用write进行打印/输出

    printStream.write("hello,word",getBytes());
    

    所得运行结果跟上述一样。

    另外,通过System.setOut()方法可以将内容输出到指定的的设备上

          String filePath = "e:\\a.txt";
          System.setOut(new PrintStream(filePath));
          System.out.println("hi,word!");
    

    运行结果

    image11

  • PrintWriter(字符字符打印流)

    • PrintWriter使用完之后必须关闭流,否则写入的内容不会刷新

    使用PrintWriter将内容输出到指定设备

    package com.print;
    
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    public class PrintWriter_ {
        public static void main(String[] args) throws IOException {
            PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\a.txt"));
    
            printWriter.print("hello.word!!!");
          //必须关闭流!
            printWriter.close();//相当于flush+关闭流
        }
    }
    

    image12

Properties类

  • Properties类主要用于读取Java的配置文件。

  • 传统的读取配置文件信息的方法

    代码演示

    package com.properties;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class Properties_ {
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
    
            String line = "";
    
            while((line = br.readLine()) != null){
                //利用split将等号前后的内容划分成两个数组
                String[] split = line.split("=");
                System.out.println(split[0]+"值为"+split[1]);
            }
    
    
        }
    }
    

    image13

    如果我们使用传统方法获取指定的ip值,会有很多问题,所以一般使用Properties类去读取配置文件

  • Properties类读取配置文件

    • 该类是专门用于读取配置文件的集合类,配置文件的格式需要按照一下格式编写:

      键=值

      键=值

    • 注意:键值对不需要有空格,值不需要用引号括起来,默认类型为String

    • Properties的常见方法

      • load:加载配置文件的键值对到Priperties对象
      • list:将数据显示到指定设备
      • getProperty(key):根据键获取值
      • setProperty(key,value):设置键值对到Properties对象
      • store:将Properties中的键值对存储到配置文件。在idea中,保存信息到配置文件,如果信息含有中文,会存储为Unicode码
      • http://tool.chinaz.com/tools/unicode/aspx Unicode码查询工具

    代码演示

    package com.properties;
    
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.Properties;
    
    public class Properties_01 {
        public static void main(String[] args) throws IOException {
            //1.创建Properties对象
            Properties properties = new Properties();
            //2.加载指定文件
            properties.load(new FileReader("src\\mysql.properties"));
            //3.把k-v输出到控制台
            properties.list(System.out);
            //4.根据key值获取相对应的value
            String user = properties.getProperty("user");
            System.out.println("用户名="+user);
        }
    }
    

    运行结果

    image14

  • 使用Properties类来创建配置文件

    package com.properties;
    
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Properties;
    
    public class Properties_02 {
        public static void main(String[] args) throws IOException {
            Properties properties = new Properties();
    
            properties.setProperty("charset","utf8");
            properties.setProperty("user","志恒");//中文保存为Unicode码
            properties.setProperty("pwd","11111");
    
            properties.store(new FileWriter("src\\mysql2.properties"),null);
    
            System.out.println("保存成功");
        }
    }
    

    运行结果

    image15

  • 使用Properties来修改文件内容

    • 如果该文件没有key,就会自动创建
    • 如果该文件有key,就会修改
    properties.setProperties("pwd",888888);
    

    image16

  • Properties父类是Hashtable,所以其核心代码底层就是Hashtable 的核心方法

     public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
    
            // Makes sure the key is not already in the hashtable.
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
            Entry<K,V> entry = (Entry<K,V>)tab[index];
            for(; entry != null ; entry = entry.next) {
                if ((entry.hash == hash) && entry.key.equals(key)) {
                    V old = entry.value;
                    entry.value = value;//如果key存在,就替换
                    return old;
                }
            }
    
            addEntry(hash, key, value, index);//如果是新的值,就addEntry
            return null;
        }
    

Built with Hugo
Theme Stack designed by Jimmy