长生的梦呓


  • 归档
  • 算法
  • 基础
  • 关于

  • 分类
  • 日志
  • Servlet
  • Archive
  • 数据结构
  • IO 流

  • 标签
  • 友链
  • MyBatis
  • About
  • Spring 5
  • Java SE
  • Java EE
  • Algorithms
  • 新特性
  • 位运算技巧

  • 搜索
内网穿透 项目实战 数据库 MySQL 安卓踩坑 开发工具 设计模式 Enum 枚举 Linux MyBatis-plus JSON IDEA Transactions AOP IO 流 DP IoC 与 DI 位运算技巧 工具类 学习技巧 Git JDK 排序 Spring Boot Spring MVC Spring Framework MyBatis Log4J Regex Jsoup JDBC 数据结构 递推 递归 算法 Servlet与JSP 小难 中等 简单

Java 快速输入输出

发表于 2020-04-07 | 1 | 阅读次数 286

前言

最近刷算法题又遇到Java超时的问题了,重新研究一下 Java 的输入与输出。总结了下Java的输入输出。

直接说总结吧:Java 的输入输出,只要套上缓冲流(BufferdXxx开头的)速度就会变得高效(更快)。

如果你不愿意看全文具体的分析,你可以直接跳转到 ==一、工具类== 中,我封装了快速输入的工具类,同时举例的快速输出的使用,这样以后会用起来更加简单一些。

如果你愿意整篇文章看一遍,并且IDE打开源码跟着看,我相信你应该也会有收获的。

一、工具类

1.快速输入

StreamTokenizer只能接收数字或字母,如果输入除空格和回车以外的字符(如:~!@#$%^&*()_+{}:<>?)无法识别,会显示null。同时如果要求输出的是字符,你的输入却是数字开头,那么字符会变成null。要求是输入数字你却输出了字母,那么数字会变成0。

        // 创建分词器输入流
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        // 转到下一个标记
        in.nextToken();
        // 输入字符
        String str = in.sval;
        // 转到下一个标记
        in.nextToken();
        // 输入数字
        double num = in.nval;

静态工具类,直接调用方法就可以了。

    /** 快速输入类 */
    static class Reader {
        static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        /** 获取下一段文本 */
        static String next() throws IOException {
            in.nextToken();
            return in.sval;
        }
        /** 获取数字 */
        static int nextInt() throws IOException {
            in.nextToken();
            return (int)in.nval;
        }
        static double nextDouble() throws IOException {
            in.nextToken();
            return in.nval;
        }
    }

2.快速输入

使用这个不会出现上面,字符为null的情况。

写题的时候推荐这样,读入一行后使用String的split()方法按空格分割。如果需要是数字就用Integer.parseInt()方法转换一下。

        // 创建输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        // 输入一行
        String line = in.readLine();
        // 按空格分割
        String[] split = line.split(" ");

你也可以使用下面这个静态类,直接放到你的类中做内部类,然后类名调用方法就可以了。

备注:StringTokenizer是由于兼容性原因而保留的遗留类,在新代码中不鼓励使用它。

    /** 快速输入类 */
    static class Reader {
        static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        static StringTokenizer tokenizer = new StringTokenizer("");
        /** 获取下一段文本 */
        static String next() throws IOException {
            while ( ! tokenizer.hasMoreTokens() ) {
                tokenizer = new StringTokenizer(reader.readLine());
            }
            return tokenizer.nextToken();
        }
        /** 获取数字 */
        static int nextInt() throws IOException {
            return Integer.parseInt(next());
        }
        static double nextDouble() throws IOException {
            return Double.parseDouble(next());
        }
    }

3.快速输出示例

输出比较大的时候可以用下面这种方式。

// 创建一个输出
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
// 输入
out.println("hello");
// 刷新(需要刷新才能显示)
out.flush();
// 关闭流(不用了就关闭,不然占内存,这是一个好习惯,个人电脑没关系,开发工作时候记得关)
out.close();

二、Java快速输入

1.Scanner 输入

这个类在util包下,属于一个工具。

优点:输入是最简单与方便的(有很多方法可以用)

缺点:输入的效率很低。在数据量大的时候,容易出现超时(Time Limit Exceeded)问题。

使用:

Scanner sc = new Scanner(System.in);
String next = sc.next();
int i = sc.nextInt();

扩展:System.in

“标准”输入流。 该流已经打开,准备提供输入数据。 通常,该流对应于键盘输入或由主机环境或用户指定的另一个输入源。

System.in 该流是对应键盘输入(或另外输入源)。这个常常作为参数,传入到类里。

2.BufferedReader 输入(主要)

从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。

这个类在io包下。主要带有缓冲区,可以提高输入效率。查看源码可以看到默认字符缓冲大小(defaultCharBufferSize)为8192字节(8KB),默认值足够大,可用于大多数用途。你也可以单独进行设置。

优点:输入效率高。

缺点:方法比较少,常用的就一个readLine()方法用于读入,处理的时候常用String类的split()方法进行分割字符。用着比较麻烦。

使用:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line = b.readLine();

扩展:InputStreamReader

InputStreamReader是从字节流到字符流的桥:它读取字节,并使用指定的charset将其解码为字符 。

传入的InputStreamReader类用于读入字节,然后解码成我们可以理解的字符。

3.StreamTokenizer 分词器

StreamTokenizer类接收输入流并将其解析为“令牌”,允许一次读取一个令牌。 解析过程由表和多个可以设置为各种状态的标志来控制。 流标记器可以识别标识符,数字,引用的字符串和各种注释样式。

创建一个解析给定字符流的tokenizer(分词器)。主要用来分隔字符串。(我们上面用BufferedReader 不是比较麻烦,可以用这个解决)

优点:输入字符和数字比BufferedReader要方便。

缺点:特殊字符会显示为null

使用:

        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        // 下一个分词
        in.nextToken();
        // 读入一个字符串和一个数字并打印
        System.out.println(in.sval);
        in.nextToken();
        System.out.println(in.nval);
        // 循环读入字符串(下一个分词不是不是行末尾时)
        while (in.nextToken() != in.TT_EOL) {
            System.out.println(in.nval);
        }

注意:如果使用这个进行输入时in.sval,如果输入的是不是英文字母或中文,则会显示为null(输入数字或"~!@#$%^&*()_+{}:<>?"是null)。

参考:https://blog.csdn.net/qq_40693171/article/details/81433637

三、Java 快速输出

1.System.out.println()

我们经常使用的是以下这句打印输出,
System.out返回的是一个PrintStream类型,该类型的对象调用方法时,比如print()会直接进行显示,我们看源码可以知道它内部调用了flush() 方法,所以我们不需要手动的去调用flush()方法。

备注:PrintStream对象中,调用的打印方法是BufferedWriter对象的write(String str)方法(BufferedWriter对象的默认字符缓冲大小defaultCharBufferSize为8192)。

System.out.println("hello");

2.PrintWriter 不带缓冲流

我翻看了很多别人写的快速输出,我发现他们常用的是这个,用作输出。但是下面这种声明,经过测试(数据比较大),我发现效率和第一种差别不大。

备注:跟源码可以看到,PrintWriter类如果传入的是OutputStreamWriter类型,那么使用的输出类为默认Writer抽象类的write(String str)方法。而Writer类写入缓冲大小WRITE_BUFFER_SIZE仅为1024。

PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
out.print("hello");
out.flush();

3.PrintWriter 加上缓冲流。

后来我查看了JDK API 文档看到了这段话。发现,发现加上缓冲流之后才能输出的更快一些。

备注:跟源码可以看到,PrintWriter类如果传入的是BufferedWriter类型,那么使用的为传入的BufferedWriter类(该类继承Writer类)。调用的是BufferedWriter类的write(String s, int off, int len)方法。

一般来说,Writer将其输出立即发送到底层字符或字节流。 除非需要提示输出,否则建议将BufferedWriter包装在其write()操作可能很昂贵的Writer上,例如FileWriters和OutputStreamWriters。

例如, PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); >

将缓冲PrintWriter的输出到文件。 没有缓冲,每次调用print()方法都会使字符转换为字节,然后立即写入文件,这可能非常低效。

        // 输出缓冲流(输出字节流转输出字符流(输出字节流))
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
        out.print("hello");
        out.flush();

4.测试

4.1 测试思路

给定一个文本,包含 ==一百万个"0123456789"== 字符串。在依次使用上面的3中方式,每个循环打印 ==100== 次,求耗时平均值。

4.2 测试结果
测试方法消耗时间(平均毫秒)
System.out.println()方法704
PrintWriter 不带缓冲流725
PrintWriter 加上缓冲流665
4.3 测试代码

测试代码:

import java.io.*;

/**
 * @author ChangSheng
 * @date 2020-04-07
 */
public class TestPrint {
    public static void main(String[] args) throws IOException {
        // 文本数据
        String text = getText();

        // 1. 测试 System.out.print
         test01(text);
        // 2. 测试 PrintWriter 不带缓冲流
        // test02(text);
        // 3. 测试 PrintWriter 加上缓冲流
        // test03(text);

    }

    /** 测试 System.out.print */
    static void test01(String text) {
        int n = 10;
        long sum = 0;
        for (int i = 0; i < n; i++) {
            long start = getTime();
            System.out.println(text);
            long end = getTime();
            sum += end - start;
        }
        System.out.print("平均所用毫秒数:" + (sum / n));
    }

    /** 测试 PrintWriter 不带缓冲流 */
    static void test02(String text) {
        int n = 10;
        long sum = 0;
        for (int i = 0; i < n; i++) {
            PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
            long start = getTime();
            out.print(text);
            out.flush();
            long end = getTime();
            sum += end - start;
        }
        System.out.print("平均所用毫秒数:" + (sum / n));
    }

    /** 测试 PrintWriter 加上缓冲流 */
    static void test03(String text) {
        int n = 10;
        long sum = 0;
        for (int i = 0; i < n; i++) {
            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
            long start = getTime();
            out.print(text);
            out.flush();
            long end = getTime();
            sum += end - start;
        }
        System.out.print("平均所用毫秒数:" + (sum / n));
    }

    static long getTime() {
        return System.currentTimeMillis();
    }

    /** 获取10_000_000次的"0123456789"字符文本 */
    static String getText() {
        int n = 10_000_000;
        StringBuffer text = new StringBuffer();
        for (int i = 0; i < n; i++) {
            text.append("0123456789");
        }
        return text.toString();
    }
}

5.总结

5.1 测试总结

PrintWriter 不带缓冲流(最慢) < System.out.println()方法(其次) < PrintWriter 加上缓冲流(最快)

5.2 原因

参照我上面三种使用时的备注,可以知道:

  • 第一种System.out.println()方法,使用的是BufferedWriter(字符缓存大小8192defaultCharBufferSize),但每次都会刷新(耗时)
  • 第二种PrintWriter 不带缓冲流方法,使用的是抽象类Writer的方法(写入缓存大小仅WRITE_BUFFER_SIZE1024),虽然不会每次刷新,但是缓冲比较小。
  • 第三种PrintWriter 加上缓冲流方法,使用的也是BufferedWriter,但是不用每次都刷新,所以是最快的。

四、扩展

附上IO流导图。

图片参考自:https://www.cnblogs.com/l2rf/p/5320010.html

  • 本文作者: 长生的梦呓
  • 本文链接: https://shengjava.com/archives/java快速输入输出
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 内网穿透 # 项目实战 # 数据库 # MySQL # 安卓踩坑 # 开发工具 # 设计模式 # Enum # 枚举 # Linux # MyBatis-plus # JSON # IDEA # Transactions # AOP # IO 流 # DP # IoC 与 DI # 位运算技巧 # 工具类 # 学习技巧 # Git # JDK # 排序 # Spring Boot # Spring MVC # Spring Framework # MyBatis # Log4J # Regex # Jsoup # JDBC # 数据结构 # 递推 # 递归 # 算法 # Servlet与JSP # 小难 # 中等 # 简单
【ZOJ】3960.What Kind of Friends Are You
【Spring】(6)静态代理
  • 文章目录
  • 站点概览
长生的梦呓

长生的梦呓

110 日志
39 分类
40 标签
RSS
E-mail CSDN
Creative Commons
Links
  • CSDN 地址
  • waltz26
  • Ryan Wang's Blog
  • JohnNiang's Blog
  • 廖雪峰
  • 菜鸟教程
© 2021 长生的梦呓
浙ICP备20005262号-1