Java 文件 IO 详解(下)

来源:互联网 发布:广告位管理系统源码 编辑:程序博客网 时间:2024/05/21 08:35

Java 文件 IO 详解(下)

    在“Java IO详解(上)”这个文章里面我们明确了输入/输出流的4个基类,并且详细了解了4个访问节点流的用法。接下来我们使用跟简单的处理流来访问文件。
1. 处理流的用法
      使用处理流的思路是:通过处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的 I/O设备、文件交互。
      处理流非常简单,只要流的构造器不是一个物理节点,而是已经存在的流,那这个流就一定是处理流。而节点流都是直接以物理节点作为构造器参数的。下面通过PrintStream处理流来包装OutputStream,感受下处理流的简便:

/** * Created by 杨Sir on 2017/10/30. * 处理流的用法:只需在创建处理流时传入节点流即可 */public class PrintStreamTest {    public static void main(String[] args){        try (                //创建文件输出流,,节点流                FileOutputStream fos = new FileOutputStream("PrintStreamTest.txt");                //创建处理流                PrintStream ps = new PrintStream(fos))        {            //使用 PrintStream执行输出            ps.println("普通字符串");            //直接使用 PrintStream 输出对象            ps.println(new PrintStreamTest());        } catch (IOException e) {            e.printStackTrace();        }    }}

      通常如果需要输出文本内容,都应该将输出流包装为PrintStream后进行输出。使用了处理流包装了底层节点流后,关闭输入/输出资源时只需要关闭上层处理流即可,底层节点流会自动关闭。
2. 输入/输出流体系
      Java 输入/输出流体系提供了40多个类,但却很有规律。 我们来看一看:

分类 字节输入流 字节输出流 字符输入流 字符输出流 抽象基类 InputStream OutputStream Reader Writer 访问文件 FileInputStream FileOutputStream FileReader FileWriter 访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter 访问字符串 StringReader StringWriter 缓冲流 BufferInputStream BufferOutputStream BufferReader BufferWriter 装换流 InputStreamReader OutputStreamWriter 对象流 ObjectInputStream ObjectOutputStream 抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter 打印流 PrintStream PrintWriter 推回输入流 PushbackInputStream PushbackReader 特殊流 DataInputStream DataOutputStream

注:上面的粗体代表节点流,必须直接与物理节点相连;斜体代表抽象基类,无法创建实例。

      通常情况下:如果输入/输出的内容是文本内容,则考虑使用处理流,如果进行输入/输出是二进制文件,那么使用节点流。能用记事本打开的称为文本文件,否则为二进制文件。
      字符流还有一个好处就是可以把字符串当做是一个物理节点,用于从字符串读取内容,获将内容写入字符串(用StringBuffer充当字符串)的功能。我们用程序见证一下字符串作为物理输入/输出节点:

/** * Created by 杨Sir on 2017/10/31. * 使用字符串作为物理节点的字符输入/输出流的用法 * * 以字符串作为物理节点时,使用 StringReader和StringWriter进行访问字符串, * StringWriter写入字符串其实是将字符串写在StringBuffer中,用StringBuffer充当字符串。 */public class StringNodeTest {    public static void main(String[] args) {        String src = "既然4个基类访问文件的节点流比较\n" +                "麻烦,那么我们来看看如何使用处理流操\n" +                "作文件。处理流可以隐藏底层节点流之间\n" +                "的差异,想外提供更加方便的输入输出方法。\n";        char[] buf = new char[32];        int hasRead = 0;        try (                StringReader sr = new StringReader(src))        {            while ((hasRead = sr.read(buf)) > 0) {                System.out.println(new String(buf, 0, hasRead));            }        } catch (IOException e) {            e.printStackTrace();        }        /** StringWriter 底层的部分代码。            public StringWriter(int initialSize) {                buf = new StringBuffer(initialSize);                lock = buf;            }        */        try (                //创建指定的 StringWriter,实际上以一个 StringBuffer 作为输出节点。                StringWriter sw = new StringWriter(20))        {            //调用StringWriter的方法执行输出            sw.write("用处理流来包装节点流,\n");            sw.write("程序通过处理流来进行输入输出,\n");            sw.write("通过节点流与底层的I/O设备进行,\n");            sw.write("如何识别处理流,\n");            System.out.println("-------下面是 sw 字符串节点的内容------");            System.out.println(sw.toString());        }catch (IOException e){            e.printStackTrace();        }    }}
  1. 转换流
          I/O 体系提供了两个转换流将字节流转为字符流,InputStreamReader和OutputStreamWriter;我以获取键盘输入为例介绍下转换流的用法,Java的System.in代表标准输入,这个标准输入流是InputStream类的实例,键盘输入都是文本内容。
/** * Created by 杨Sir on 2017/10/31. * 装换流: InputStreamReader:将字节输出流转换为字符输出流; OutputStreamReader:将字节输出流转换为字符输出流。 * * BufferedReader流具有缓冲的功能,可以逐行读取,以换行符为标致,没有读到换行符,则程序阻塞,直到读到换行符。 */public class KeyinTest {    public static void main(String[] args){        try(                // System.in 标准输入流是InputStream的类的实例。因此可以将 System.in 转换为 Reader对象                InputStreamReader reader = new InputStreamReader(System.in);                //将普通的Reader包装为 BufferedReader                BufferedReader br = new BufferedReader(reader))        {            String line = null;            //采用循环的方式逐行读取            while((line = br.readLine()) != null){                //如果程序读到 "exit",则程序退出                if(line.equals("exit")){                    System.exit(1);                }                //打印读取内容                System.out.println("输入内容为:"+line);            }        }catch(IOException e){            e.printStackTrace();        }    }}

      BufferReader流具有缓冲能力,他还可以一行一行读取文本,以换行符为标志,如果没有读到换行符,则程序阻塞。
4. 推回输入流
       在I/O 体系,有两个流很与众不同,就是PushbackInputStream和 PushbackReader,他们都提供了三个方法:
        void unread(byte[]/char[] buf): 将一个字节或字符数组内容推回到缓冲区,从而允许重复读取刚才的内容。
        void unread(byte[]/char[] buf, int off, int len): 将一个字节/字符数组从off开始,长度为len 字节/字符的内容推回到缓冲区,从而允许重复读取刚刚的内容。
        void unread(int b): 将一个字节或字符内容推回到缓冲区,从而允许重复读取刚才的内容。
      废话不多说,直接看例子:

/** * Created by 杨Sir on 2017/10/31. * 推回输入流:PushbackInputStream 和 PushbackReader,两个推回输入流都带有一个推回缓冲区。推回时会将指定数组内容推回到缓冲区, * 读取时先读取推回到缓冲区中的数据,推回缓冲区内容不够时才从原输入流中读取。推回缓冲区长度默认为 1。 * * 该程序功能:找出 "new FileReader" 字符串,输出该字符串之前的内容 */public class PushbackTest {    public static void main(String[] args){        try(                //创建一个PushbackReader对象,指定推回缓冲区长度为64                PushbackReader pr =                        new PushbackReader(new FileReader("D:\\绝对路径PushbackTest.java"),64))        {            char[] buf = new char[32];            //用以保存上次读取的字符串内容            String lastContent = "";            int hasRead = 0;            //循环读取文件内容            while((hasRead = pr.read(buf))>0){                //将读取内容转换为字符串                String content = new String(buf, 0, hasRead);                int targetIndex = 0;                //将上次读取的字符串和这次的连接起来,查看是否包含目标字符串                if((targetIndex = (lastContent+content).indexOf("new FileReader")) > 0){                    //将本次内容和上次内容以前推回缓冲区                    pr.unread((lastContent+content).toCharArray());                    //重新定义一个长度为 targetIndex的数组                    if(targetIndex > 32){                        buf = new char[targetIndex];                    }                    //再次读取指定长度的内容                    pr.read(buf, 0, targetIndex);                    //打印读取的内容                    System.out.println(new String(buf, 0, targetIndex));                    System.exit(0);                }else{                    //打印上次读取的内容,只要没匹配成功,就打印出上次读取的内容,,                    //匹配成功后只需打印出 匹配成功的那次的上一次到目标字符串之前 的内容即可。                    System.out.println(lastContent);                    lastContent = content;                }            }        }catch (IOException e){            e.printStackTrace();        }    }}
  1. 重定向标准输入/输出
          Java的标准输入/输出分别通过System.in 和 System.out代表,默认情况下是 键盘和显示器。
          重定向标准输入/输出: System类里提供了三个重定向标准输入/输出的方法
            – static void setErr(PrintStream err): 重定向“标准”错误输出流
            – static void setIn(InputStream in): 重定向”标准”输入流
            – static void setOut(OutputStream out): 重定向“标准”输出流。
    例如:重定向标准输出流,将System.out 输出重定向到文件输出
/** * Created by 杨Sir on 2017/10/31. */public class RedirectOut {    public static void main(String[] args){        try(                //一次性创建PrintStream输出流                PrintStream ps = new PrintStream(new FileOutputStream("out.txt")))        {            //将标准输出流重定向到ps输出流,,sout的输出将由控制台转到 ps的输出流            System.setOut(ps);            //向标准输出 输出一个字符串            System.out.println("普通字符串");            //向标准输出 输出一个对象            System.out.println(new RedirectOut());        } catch (FileNotFoundException e) {            e.printStackTrace();        }    }}
  1. RandomAccessFile(任意位置访问文件)
          RandomAccessFile是Java输入输出体系中功能最丰富的文件内容访问类,提供了很多方法访问文件内容,既可以读取文件内容,也可以像文件输出数据,可以是任意位置。它的缺点是只能读写文件,不能读写其他IO节点。
          RandomAccessFile提供了两个方法操作文件记录指针:
            long getFilePointer(): 返回文件记录指针当前的位置。
            void seek(long pos): 将文件记录指针定位到pos位置。
          RandomAccessFile的读写文件的方法完全类似于InputStream和OutputStream的read方法和write方法。
          RandomAccessFile 有两个构造器,一个使用String参数指定文件名,一个使用File参数指定文件本身;除此之外还要指定一个 mode参数,该参数确定RandomAccessFile的访问方式。
             “r”: 以只读的方式打开指定文件。
             “rw”: 以读、写的方式打开指定文件。
             “rws”:以读、写的方式打开指定文件,相对于“rw”,还要求对文件内容或元数据的每个更新都同步写到底层存储设备。
             “rwd”: 以读、写的方式打开指定文件,相对于“rw”,还要求对文件内容的每个更新都同步写到底层存储设备。
          还是来个实例爽快,功能:向指定文件的指定位置后面追加内容,为防止覆盖,先要创建一个临时文件保存指定位置后面的数据,插入要插的内容以后,再将临时文件里的内容追加到插入后的内容的后面:
/** * Created by 杨Sir on 2017/11/1. * 向指定文件、指定位置插入内容,,并且不覆盖指定位置后面的内容 * */public class InsertContent {    public static void main(String[] args) throws IOException {        insert("out.txt", 5,"恩恩haha我是插入的内容\r\n");    }    public static void insert(String filename , long pos , String content) throws IOException {        //创建临时文件        File tmp  = File.createTempFile("emp",null); //默认后缀为 .tmp        tmp.deleteOnExit();        try(                RandomAccessFile raf =                        new RandomAccessFile("D:\\IDEARep\\CrazyJavaLiGang\\"+filename,"rw");                //使用临时文件来保持插入点后的数据                FileInputStream tmpfis = new FileInputStream(tmp);                FileOutputStream tmpfos = new FileOutputStream(tmp);                )        {            raf.seek(pos);            //--------将插入点后的数据保持在临时文件            byte[] buf = new byte[64];            int hasRead = 0;            while((hasRead = raf.read(buf)) > 0){                tmpfos.write(buf, 0, hasRead);            }            //插入内容            raf.seek(pos);            raf.write(content.getBytes());            //追加临时文件中的内容            while((hasRead = tmpfis.read(buf)) > 0 ){                raf.write(buf, 0 ,hasRead);            }        }catch (IOException e){            e.printStackTrace();        }    }}
原创粉丝点击