JavaSE核心API--I/O流

java.io.FileOutputStream等

1)理论讲解:缓冲字节输出流的缓冲区问题

代码演示:

    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"))) {
        String str = "我是隔壁的泰山,抓住爱情的藤蔓.";
        byte[] data = str.getBytes();
        bos.write(data);
        /*
         * void flush() 缓冲流的flush方法用于将缓冲区中已经缓存的数据一次性写出。
         * 频繁调用flush方法会降低写效率,但是可以保证写出的即时性(根据实际需求酌情调用)。
         */
        bos.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("写入完毕!");

2)理论讲解:缓冲字符输入流

java.io.BufferedReader 缓冲字符输入流

块读操作,提高读取字符效率,并且提供了按行读取字符串的操作。

代码演示:

    /** 将当前源代码按行读取出来并输出到控制台 */
    try (InputStreamReader isr = new InputStreamReader(new FileInputStream("src/io/BufferedReaderDemo.java"))) {
        BufferedReader br = new BufferedReader(isr);
        /*
         * String readLine() 
         * 缓冲字符输入流提供的该方法可以连续读取若干字符直到读取了换行符为止,
         * 然后将换行符之前的内容以一个字符串形式返回。
         * 返回的字符串不含有最后的换行符。 
         * 若返回值为null,表示读取了文件末尾。
         */
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        br.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

3)理论讲解:缓冲流

缓冲流是一对高级流,作用是加快读写效率。这使得我们在读写数据时无论用随机读写还是块读写都可以保证效率。
实际上缓冲流会将我们的读写最终统一转换为块读写提高的效率

代码演示:

    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_cp.jpg"))) {
        int d = -1;
        while ((d = bis.read()) != -1) {
            bos.write(d);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("复制完毕!");

4)理论讲解:使用文件输入流读取文件数据

代码演示:

    FileInputStream fis
        = new FileInputStream("fos.txt");
    byte[] data = new byte[200];
    int len = fis.read(data);
    System.out.println("实际读取到了:"+len+"个字节");

    String str = new String(data,0,len,"UTF-8");
    System.out.println(str);

    fis.close();

5)理论讲解:I/O流

IO java标准的输入与输出
java提供了统一的标准的输入与输出操作,用于与外界设备进行交互数据。

其中:
输入流:用于从外界读取数据到程序中的流。(读操作)
输出流:用于将程序中的数据发送到外界的流。(写操作)

java.io.InputStream:
所有字节输入流的超类,定义了所有输入流都具备的读取字节的方法。但本身InputStream是抽象类,不可以实例化。

java.io.OutputStream:
所有字节输出流的超类,定义了写出数据的方法。

流读写数据采取的模式为:顺序读写,也就是说无论读还是写,都是一次性的,不可以回退。

java将流划分为了两类:
  节点流:又叫低级流,是实际连接程序与另一端的"管道",负责实际读写数据的流。读写一定是建立在节点流的基础上进行的。
  处理流:又叫高级流,高级流不可以独立存在,必须连接在其他流上,这样当数据流经当前高级流时会对数据进行加工处理,这样可以简化对数据加工的操作。   

用一组高级流进行串联操作,最终连接到某个低级流上,完成对读写数据的流水线式加工。这样的操作称为:流连接(流连接是学习IO的精髓,要掌握。)


文件流
文件流是一对低级流,用于读写文件数据的流。
java.io.FileOutputStream
java.io.FileInputStream

文件流与RandomAccessFile对比:
文件流是基于java标准IO进行读写数据的,所以对文件数据是顺序读写形式。
RAF是基于指针的随机读写形式。可以操作指针对文件任意位置进行编辑(读写)。

文件流可以基于流连接,串联若干高级流完成更复杂的读写数据操作。RAF很多操作都需要自行完成。

代码演示:

    /*
     * 向fos.txt文件中写入数据
     * 常见构造方法:
     * FileOutputStream(String path)
     * FileOutputStream(File file)
     * 以上两种创建模式为覆盖写模式,即:创建流的时候
     * 若该文件已经存在,流会现将该文件数据清除。然后才开始新的写操作。
     * 
     * FileOutputStream(String path,boolean append)
     * FileOutputStream(File file,boolean append)
     * 以上两种模式为追加写操作,即:文件数据都保留,会将通过该流写入的数据继续追加到文件末尾。
     * 
     */
    FileOutputStream fos
//      = new FileOutputStream("fos.txt");
        = new FileOutputStream("fos.txt",true);

//  String str = "我可以接受你的所有,所有小脾气。";
//  byte[] data = str.getBytes("UTF-8");
//  fos.write(data);
//  str = "我可以带你去吃很多,很多好东西。";
//  data = str.getBytes("UTF-8");
//  fos.write(data);

    String str = "回手,掏~";
    byte[] data = str.getBytes("UTF-8");
    fos.write(data);

    System.out.println("写出完毕!");

    fos.close();

6)理论讲解:转换输入流

转换输入流
java.io.InputStreamReader

代码演示:

    FileInputStream fis = new FileInputStream("osw.txt");       
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    /*
     * int read()
     * 字符流的read方法一次是读取一个字符的,所以虽然
     * 返回值是int型,但实际是一个char值。
     * 若返回的int值为-1时,表示文件末尾.
     */
    int d = -1;
    while((d = isr.read())!=-1) {
        System.out.print((char)d);
    }
    isr.close();

7)理论讲解:转换流

java将流按照读写单位划分为两类:
1:字节流,读写以字节为最小单位
2:字符流,读写以字符(unicode)为最小单位。实际上底层本质还是读写字节,但是字节与字符的转换操作有字符流自行完成。
字符流只适合读写文本数据!
java.io.Reader是所有字符输入流的超类
java.io.Writer是所有字符输出流的超类

转换流(是一对高级流):
java.io.InputStreamReader
java.io.OutputStreamWriter
他们在流连接中使用字符流完成字符读写操作时是非常重要的一环,但是通常不直接操作这两个流  

java中提供了很多功能更丰富的字符流,但是字符流都有一个
共同点:不能直接连接在字节流上
大部分常用的低级流都是字节流(比如文件流),这会导致在流连接中我们要使用字符流读写文本数据时无法直接与字节流连接。
因此我们要使用转换流,它们是唯一一对可以连接在字节流上的字符流,使用它作为"转换器"使用,可以达到让字符流与字节流连接使用。

代码演示:

    FileOutputStream fos = new FileOutputStream("osw.txt");
    /*
     * 创建转换流是通常会指定第二个参数,该参数为字符集的
     * 名称,这样通过转换流写出的文本数据都会按照指定的字符
     * 集转换为字节。
     * 若不指定,则是按照系统默认字符集,不推荐。
     */
    OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");

//  osw.write('a');

    osw.write("好嗨呦~");
    osw.write("感觉人生已经达到了高潮~");

    System.out.println("写出完毕!");
    osw.close();

8)理论讲解:缓冲字符输出流

java.io.PrintWriter 
常用的缓冲字符输出流,可以按行写出字符串。并且具有自动行刷新功能。
内部常连接java.io.BufferedWriter作为缓冲使用。

代码演示:

    /*
     * PW提供了直接对文件写操作的构造方法 
     * PrintWriter(String fileName) 
     * PrintWriter(File file)
     * 
     * 可以指定字符集 
     * PrintWriter(String fileName,String csn) 
     * PrintWriter(File file,String csn)
     */
    try (PrintWriter pw = new PrintWriter("pw.txt", "UTF-8")) {
        /**对pw.txt文件做写操作*/
        pw.println("hello姐~");
        pw.println("来了老弟~");
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("写入完毕!");

9)理论讲解:在流连接中使用PrintWriter

代码演示:

    try {
        FileOutputStream fos = new FileOutputStream("pw2.txt");// 负责将字节写入文件
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");// 负责将字符转换为字节
        BufferedWriter bw = new BufferedWriter(osw);// 负责块写操作
        PrintWriter pw = new PrintWriter(bw);// 负责自动行刷新
        /** 使用pw对文件进行写操作 */
        pw.println("你好!");
        pw.println("再见!");
        System.out.println("写入完毕!");
        pw.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

10)理论讲解:使用文件流完成文件的复制

使用文件流完成文件的复制 
步骤: 
1:创建一个文件输入流用于读取原文件 
2:创建一个文件输出流用于将数据写入复制文件
3:循环读取原文件的字节写入到复制文件完成复制 4:将两个流关闭。

可参考使用RAF对文件的复制操作。

代码演示:

    long start = System.currentTimeMillis();
    try (FileInputStream fis = new FileInputStream("ykzzldx.mp3");
            FileOutputStream fos = new FileOutputStream("ykzzldx_cp.mp3")) {
        byte[] data = new byte[1024 * 10];
        int len = -1;
        while ((len = fis.read(data)) != -1) {
            fos.write(data, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    long end = System.currentTimeMillis();
    System.out.println("复制完毕!耗时:" + (end - start) + "ms");

11)理论讲解:实现简易记事本功能

使用PW(PrintWriter)实现简易记事本功能 
程序启动后要求输入文件名,然后对文件写操作。 
之后用户输入的每行字符串按行写入文件(换行写)。
当输入exit时程序退出。

自行使用流连接完成PW的创建。

代码演示:

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入文件名:");
    String fileName = scanner.nextLine();
    System.out.println("请开始输入内容,输入exit退出");
    /**
     * 在流连接中使用PW时,可以传入第二个参数,该参数为一个boolean值,当该值为true时,则开启自动行刷新操作。
     * 这时当我们调用PW的println方法写出一行字符串后 会自动flush。(注意:print方法是不会自动行刷新的!)
     */
    try (PrintWriter pw = new PrintWriter(
            new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8")), true)) {
        while (true) {
            String line = scanner.nextLine();
            if ("exit".equals(line)) {
                break;
            }
            pw.println(line);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("再见!");
    scanner.close();

12)附件:使用当前类实例测试对象流的对象读写操作

所有希望被对象流读写的类都必须实现Serializable接口
该接口是一个"签名接口",实现该接口后在源码中不需要重写任何方法。
实际上编译器在编译当前源代码为class文件时发现当前类实现了可序列化接口,
那么会为其自动添加一个方法,用于将当前类实例转换为一组字节。
但是这个方法无需在源代码中被体现了。

代码演示:

public class Person implements Serializable{
   /**
     * 序列化版本号
     */
   private static final long serialVersionUID = 1L;
   private String name;
   private int age;
   private String gender;
   /*
     * 当一个属性被transient关键字修饰后,那么当前类实例在序列化时,该属性值会被忽略。
     * 忽略不必要的属性,可以减少序列化后的字节,做到对象瘦身的效果。减少资源开销。
     */
   private transient String[] otherInfo;

   public Person(String name, int age, String gender, String[] otherInfo) {
        super();
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.otherInfo = otherInfo;
   }

   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;
   }

   public String getGender() {
        return gender;
   }

   public void setGender(String gender) {
        this.gender = gender;
   }

   public String[] getOtherInfo() {
        return otherInfo;
   }

   public void setOtherInfo(String[] otherInfo) {
        this.otherInfo = otherInfo;
   }

   public String toString() {
        return name+","+age+","+gender+","+Arrays.toString(otherInfo);
   }
}

13)理论讲解:对象输入流

代码演示:

    /*
     * 将Person对象从person.obj文件中读取出来
     */
    FileInputStream fis = new FileInputStream("person.obj");
    ObjectInputStream ois = new ObjectInputStream(fis);
    /*
     * 对象输入流的readObject方法会将读取的字节
     * 按照其结构还原回对应的对象。
     * 而这个过程称为:对象反序列化
     */
    Person p = (Person)ois.readObject();
    System.out.println(p);

    ois.close();

14)理论讲解:对象流

对象流
java.io.ObjectOutputStream
java.io.ObjectInputStream

对象流是一对高级流,在流连接中使用它们可以很方便的读写java中的任何对象。

代码演示:

    /*
     * 将一个Person实例写入到文件person.obj中。
     */
    String name = "苍老师";
    String gender = "女";
    int age = 18;
    String[] otherInfo = {"是一名演员","来自霓虹","爱好是写毛笔字","促进中日文化交流"}; 
    Person p = new Person(name, age, gender, otherInfo);
    System.out.println(p);

    FileOutputStream fos = new FileOutputStream("person.obj");      
    ObjectOutputStream oos = new ObjectOutputStream(fos);   
    /*
     * 对象输出流的writeObject方法可以将给定的对象
     * 按照其结构转换为一组字节后通过其连接的流写出。
     * 但是这里有一个前提要求:
     * 写出的对象所属的类必须实现可序列化接口,否则
     * 会抛出异常:NotSerializableException
     */ 
    /*
     * 当前案例中,我们在流连接中连接了两个流:
     * 文件流,对象流
     * 其中:
     * 对象输出流负责将对象按照其结构转换为一组字节
     * 而这个过程称为:对象序列化
     * 
     * 文件输出流负责将字节写入到文件中。
     * 而将数据写入文件的过程称为数据持久化。
     */
    oos.writeObject(p);     
    System.out.println("写出完毕!");
    oos.close();