Java 文件 IO 详解(下)

本文深入探讨Java文件IO系统的处理流、转换流等高级特性,包括PrintStream、InputStreamReader及RandomAccessFile等类的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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多个类,但却很有规律。 我们来看一看:

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter
缓冲流BufferInputStreamBufferOutputStreamBufferReaderBufferWriter
装换流InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream
抽象基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataInputStreamDataOutputStream

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

      通常情况下:如果输入/输出的内容是文本内容,则考虑使用处理流,如果进行输入/输出是二进制文件,那么使用节点流。能用记事本打开的称为文本文件,否则为二进制文件。
      字符流还有一个好处就是可以把字符串当做是一个物理节点,用于从字符串读取内容,获将内容写入字符串(用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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值