Java (四)I/O系统

 

1  输入输出

1.1  输入(Input)

  输入是指可以让程序从外部系统获得数据(核心含义是“”,读取外部数据)。

  例如:

  • 读取硬盘上的文件内容到程序。播放器打开一个视频文件。
  • 读物网络上某个位置的内容到程序。浏览器输入网之后,打开该网址对应的网页内容。
  • 读取数据库系统的数据到程序。

1.2  输入(Output)

  输出是指程序输出数据给外部系统从而可以操作外部系统(核心含义是“”,将数据写出到外部系统)。

  例如:

  • 将数据写到硬盘中。编辑一个word文档后,将内容写到硬盘上进行保存。
  • 将数据写到数据库中。注册一个网站会员,实际就是后台程序向数据库写入一条记录。

1.3  数据源

  数据源分为:源设备、目标设备。

  • 源设备:为程序提供数据,一般对应输入流。
  • 目标设备:程序数据的目的地,一半对应输出流。

2  I/O流

2.1  Java中流的概念细分

2.1.1  按流的方向分类

  • 输入流:数据源到程序。
  • 输出流:程序到目的地。

2.1.2  按处理的数据单元分类

  • 字节流:以字节为单位获取数据。命名上以 Stream 结尾的流一般是字节流。
  • 字符流:以字符为单位以字符为单位获取数据。命名上以 Reader/Writer 结尾的流一般是字符流。

2.1.3  按处理对象不同分类

  • 节点流:可以直接从数据源或目的地读写数据。
  • 处理流:不直接连接到数据源或目的地。通过对其他流的处理提高程序性能,也叫包装流。
    • 处理流又分为:缓冲流、转换流、数据流。

  (个人理解:实际上就是对已形成的流进行了包装设计。)

3  常用类

1. InputStream / OutputStream:

  字节流的抽象类。

2. Reader / Writer:

  字符流的抽象类。

3. FileInputStream / FileOutputStream:

  节点流:以字节为单位直接操作“文件”。

【例3-1】使用 FileInputStream 读取数据

  首先在该工程的FileTest目录下创建一个文本文件“fan.txt”,在文件中输入内容 “Hello World”。

package com.ZZF.io;

import java.io.*;

public class FISDemo {
    public static void main(String[] args) {
        int temp;                  //定义一个int类型的变量temp,记住每次读取的一个字节
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("FileTest/fan.txt");
            //创建一个文件字节输入流
            while (true) {
                temp = fis.read();     //变量temp记住读取的每一个字节
                if (temp == -1) {
                    break;          //如果读取的字节为-1,跳出while循环
                }
                System.out.print(temp + "\t");     //输出temp
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fis != null) 
                    fis.close();    //关闭
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果如下图所示:

  这个例子中,创建的字节流 FileInputStream 通过 read() 方法将指定目录文件 “FileTest\fan.txt” 中的数据读取并打印。之所以输出数字是因为硬盘上的文件是以字节的形式存在的。需要注意的是,“Hello World” 中,空格也占一个字节,其对应的ASCII码值是32。一旦遇到 IO 异常,IO 流的 close() 方法将无法得到执行,流对象所占用的系统资源将得不到释放,因此,为了保证 IO 流的 close() 方法必须执行,通常将关闭流的操作写在 finally 代码块中。

【例3-2】使用FileOutputStream输出数据

package com.ZZF.io;

import java.io.*;

public class FOSDemo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        String str = "张小凡的博客";
        byte[] b = str.getBytes();
        try {
            fos = new FileOutputStream("FileTest/fan1.txt");
            for (int i = 0; i < b.length; i++) {
                fos.write(b[i]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

程序运行后,会在指定目录 “FileTest” 下生成一个新的文本文件 “fan1.txt”,打开文件会看到如下图所示的内容:

  需要注意的是,如果通过 FileOutputStream 向一个已经存在的文件中写入数据,那么该文件中的数据首先会被清空,再写入新数据。若希望在已存在的文件内容之后追加新内容,则可使用 FileOutputStream 构造函数 FileOutputStream(String fileName, boolean append) 来创建文件输出流对象,并把 append 参数值设为true。对例3-2 中关键代码进行如下修改:

fos = new FileOutputStream("FileTest/fan1.txt", true);

4. ByteArrayInputStream / ByteArrayOutputStream:

  节点流:以字节为单位直接操作“字节数组对象”。

【例3-3】简单测试 ByteArrayInputStream 的使用

package com.ZZF.io;

import java.io.*;

public class TestByteArray {
    public static void main(String[] args) {
        byte[] b = "Hello World".getBytes();    //将字符串转换成字节数组
        test(b);
    }

    public static void test(byte[] bytes) {
        ByteArrayInputStream bais = null;
        StringBuilder sb = new StringBuilder();
        int temp = 0;
        int num = 0;
        //该构造方法的参数是一个字节数组,这个字节数组就是数据源
        try {
            bais = new ByteArrayInputStream(bytes);
            while ((temp = bais.read()) != -1) {
                sb.append((char) temp);
                num++;
            }
            System.out.println(sb);
            System.out.println("读取的字节数:" + num);
        } finally {
            try {
                if (bais != null) {
                    bais.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

输入结果如下图所示:

  ByteArrayInputStream 和 ByteArrayOutputStream 经常用在需要流和数组之间转化的情况。

  其实,FileInputStream 是把文件当做数据源,ByteArrayInputStream 则是把内存中的 “某个字节数组对象” 当做数据源。

5. ObjectInputStream / ObjectOutputStream:

  处理流:以字节为单位直接操作“对象”。

6. DataInputStream / DataOutputStream:

  处理流:以字节为单位直接操作“基本数据类型与字符串类型”。

【例3-4】DataInputStream 和 DataOutputStream 的使用

package com.ZZF.io;

import java.io.*;

public class TestDataStream {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        DataInputStream dis = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;

        try {
            fis = new FileInputStream("FileTest/fan1.txt");
            fos = new FileOutputStream("FileTest/fan1.txt");
            //使用数据流对缓冲流进行包装,新增缓冲功能
            dos = new DataOutputStream(new BufferedOutputStream(fos));
            dis = new DataInputStream(new BufferedInputStream(fis));
            //将如下数据写入到文件中
            dos.writeChar('a');
            dos.writeInt(1);
            dos.writeUTF("张小凡的博客");
            //手动刷新缓冲区,将流中的数据写入到文件中
            dos.flush();
            //直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
            System.out.println("char: " + dis.readChar());
            System.out.println("int: " + dis.readInt());
            System.out.println("String: " + dis.readUTF());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(dos!=null){
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(dis!=null){
                    dis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos!=null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fis!=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果如下图所示:

  存在的问题是,打开所操作的文件时会发现,文本中出现乱码或者不显示 int 类型的数字1。网上进行查阅之后,说是writeInt底层使用的是位操作,实际操作的是字符。可以正确读取就可以了。

7. FileReader / FileWriter

  节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。

【例3-5】使用FileReader读取文件

package com.ZZF.io;

import java.io.*;

public class FRDemo {
    public static void main(String[] args) {
        FileReader fr = null;       //创建一个FileReader对象来读取文件中的字符
        int temp;                   //定义一个变量用于记录
        try {
            fr = new FileReader("FileTest/fan.txt");
            while ((temp = fr.read()) != -1) {
                System.out.print((char)temp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try{
                if (fr != null) {
                    fr.close();     //关闭文件读取流,释放资源         
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果如下图所示:

  字符输入流的 read() 方法返回的是int类型的值,要想获得字符就需要进行强制类型转换。

【例3-6】使用FileWriter将字符写入文件

  在已有的 “fan.txt” 文件中换行并写入 “你好,世界!”。

package com.ZZF.io;

import java.io.*;

public class FWDemo {
    public static void main(String[] args) {
        FileWriter fw = null;       //创建一个FileWriter对象用于向文件中写入数据
        String str = "你好,世界!";

        try {
            fw = new FileWriter("FileTest/fan.txt",true);
            fw.write("\r\n");
            fw.write(str);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try{
                if (fw != null) {
                    fw.close();     //关闭写入流,释放资源
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

打开 “fan.txt” 文件会看到如下图所示内容:

 

8. BufferedReader / BufferedWriter

        处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。

【例3-7】使用 BufferedReader/BufferedWriter对文件内容进行复制

package com.ZZF.io;

import java.io.*;

public class BRWDemo {
    public static void main(String[] args) {
        // 注:处理文本文件时,实际开发中可以用如下写法,简单高效!!
        FileReader fr = null;
        FileWriter fw = null;
        BufferedReader br = null;
        BufferedWriter bw = null;
        String temp;
        try {
            fr = new FileReader("FileTest/src.txt");
            fw = new FileWriter("FileTest/des.txt");
            //使用缓冲字符流进行包装
            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);
            //BufferedReader提供了更方便的readLine()方法,直接按行读取文本
            //br.readLine()方法的返回值是一个字符串对象,即文本中的一行内容
            while ((temp = br.readLine()) != null) {
                //将读取的一行字符串写入文件中
                bw.write(temp);
                //下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意

  • readLine()方法是BufferedReader特有的方法,可以对文本文件进行更加方便的读取操作。
  • 写入一行后要记得使用newLine()方法换行。

 

9. BufferedInputStream / BufferedOutputStream

        处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高读写效率。

【例3-8】使用 BufferedInputStream/BufferedOutputStream 实现文件的复制

package com.ZZF.io;

import java.io.*;

public class BIOSDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        int temp;
      
try { fis = new FileInputStream("FileTest/src.txt"); bis = new BufferedInputStream(fis); fos = new FileOutputStream("FileTest/des.txt"); bos = new BufferedOutputStream(fos); while ((temp = bis.read()) != -1) { bos.write(temp); } } catch (Exception e) { e.printStackTrace(); } finally { //注意:增加处理流后,注意流的关闭顺序“后开的先关闭” try { if (bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (bis != null) { bis.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (fos != null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

10. InputStreamReader / OutputStreamWriter

        处理流:将字节流对象转化成字符流对象。

  System.in是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。

       而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。

11. PrintStream

        处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。

装饰器模式

  装饰器模式可以动态的把新的职责添加到对象上,在扩展性方面比通过继承实现扩展更富有弹性。这里关键点是“动态”,也就是运行时;而继承在编译的时候已经确定了。

  这种设计模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

 

 

 

 

 

总结:

  未进行示例演示的类查阅参考资料获取。

  初学Java,如有错误和不足,恳请指正。

参考资料

https://www.sxt.cn/Java_jQuery_in_action/ten-iqtechnology.html

 

posted @ 2019-05-06 20:51  张小凡I4CU  阅读(252)  评论(0编辑  收藏  举报