深入剖析Java输入输出的那些细节

前言

无论是新手入门的命令行输入输出,还是说OJ的读入和输出,离不开基本的IO操作。
但Java本身由于纯粹的面向对象,输入输出不方便,所以我们倒不妨探讨一番。

其实输入输出真实是所有学Java的入门者一定要先去学的,但用的越多越能深入地感受到输入输出或多或少的小问题。正所谓“养兵千日,用兵一时”,想要关键时刻一针见血,就必须在平时做好准备。

Are you ready? OK ~ 那今天我们就来聊聊,我们如何用好Java的输入输出。

看看Python

这叫输入:

n=input()

可以用eval()将输入类型转换成数值类型,用int()转换成整数类型……
如下:

n=eval(input())

这叫输出:

print(n)

看看C++

cin可以输入:

cin>>n;

输入还可以用scanf()效率更高

cout可以输出:

cout<<n;

输出还可以用printf()效率更高

Java输出篇

为什么先谈输出呢?
显然因为简单啊。

普通输出流三种输出方式:

第一种是不换行输出

int n = 1;
System.out.print(n);

第二种是换行输出

System.out.println(n);

第三种是格式化输出

System.out.printf("%d", n);

还有错误输出流,与上述内容类似:

第一种是不换行输出

System.err.print(n);

第二种是换行输出

System.err.println(n);

第三种是格式化输出

System.err.printf("%d", n);

Java输出的一些问题

  1. printf();如果格式化不正确,就会爆java.util.IllegalFormatConversionException异常。
  2. 为什么Eclipse/IDEA这样的IDE在爆异常的时候都可能红字和普通字混合?
    异常是err错误流,普通输出是out普通输出流,可能会在IDE里由于线程的问题而混合在一起。
  3. System.in/System.out/System.err是什么?
    根据下面的源码(java.lang.System),可知分别是InputStream、PrintStream对象,err流和out流是同一个类的不同对象。
     public static final InputStream in;
     public static final PrintStream out;
     public static final PrintStream err;`
    
  4. 打印输出的时候会启动IO,所以不建议直接输出,可以用StringBuilder把答案“组织好”再统一输出。
  5. 循环里每次都cout的时候能不输出最后的空格,不需要额外的调整,但System.out.println()输出的时候没有这种考虑,必须自己处理最后一次的结果。我建议可以用builder.append(i).append(" ");builder.toString().trim();,最后消去末尾的一个空格,即可完成所需要的输出。
  6. 格式化打印指定位数小数是常见操作,可用printf()完成任务目标。比如,System.out.printf("%.5f");就是保留五位小数打印浮点数。
  7. ……(留待补充)

对于第五条,说明一下。我们先写一个测试类,打一下时间戳:

public class PrintTest {
    public static void main(String[] args) {
        int[] array = new int[100000];
        long time1 = System.currentTimeMillis();
        for (int i = 0; i < array.length-1; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println(array[array.length-1]);
        long time2 = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            builder.append(array[i]).append(" ");
        }
        System.out.println(builder.toString().trim());
        long time3 = System.currentTimeMillis();
        System.out.println("策略1的输出时间:" + (time2-time1));
        System.out.println("策略2的输出时间:" + (time3-time2));
    }
}

除去上面的两行输出,会打印:
策略1的输出时间:330
策略2的输出时间:10

显然,用StringBuilder而不是直接一次接一次的System.out.print(),是正解啦。

Java输入篇

记得在学校刚学的时候,第一节课老师就让大家写一下输入输出语句,输出还好,输入就不行了,很多同学不会写。其实我也是在学了很多语法以后才学会的输入啦,下面就来说一说吧。

其实对初学者的一个困难就是输出可以不必import,但输入则不然,需要import java.io.*,或者import java.util.Scanner,可能不那么容易直接理解。

一般比较“出名”的处理方式是java.util.Scanner,这倒确实。
Scanner类可以直接读取一下类型的对象:

  • public String next() → 读取下一个字符串(默认分隔符为Space or Tab or Enter)
  • public String next​(String pattern) → 读取下一个字符串(匹配到的串符合指定的正则表达式)
  • public String next​(Pattern pattern) → 读取下一个字符串(匹配到的串符合指定的正则表达式)
  • public BigDecimal nextBigDecimal() → 读取下一个高精小数(默认十进制)
  • public BigInteger nextBigInteger() → 读取下一个高精整数(默认十进制)
  • public BigInteger nextBigInteger​(int radix) → 读取下一个高精整数(指定进制)
  • public boolean nextBoolean() → 读取下一个布尔值
  • public byte nextByte() → 读取下一个byte整型数值(超容会报错,默认十进制)
  • public byte nextByte​(int radix) → 读取下一个byte整型数值(超容会报错,指定进制)
  • public double nextDouble() → 读取下一个双精度浮点数值(默认十进制)
  • public float nextFloat() → 读取下一个单精度浮点数值(默认十进制)
  • public int nextInt() → 读取下一个int整型数值(超容会报错,默认十进制)
  • public int nextInt​(int radix) → 读取下一个int整型数值(超容会报错,指定进制)
  • public String nextLine() → 读取下一行内容以字符串类型返回(分隔符为Enter)
  • public long nextLong() → 读取下一个long整型数值(超容会报错,默认十进制)
  • public long nextLong​(int radix) → 读取下一个long整型数值(超容会报错,指定进制)
  • public short nextShort() → 读取下一个short整型数值(超容会报错,默认十进制)
  • public short nextShort​(int radix) → 读取下一个short整型数值(超容会报错,指定进制)

看吧,确实“全能”,不过我们在看到Scanner便捷的同时也应该清醒地认识到其性能的低下。
Scanner之所以能如此全能,依赖于Java支持的正则表达式,这点可以自行阅读文档或者源码了解,这里不加详细说明。
我们试想,比如一行这样的数据:
1 2 555 333333 99 243 ……
如果说每一次都用scanner.nextInt(),就会慢太多。
反反复复的IO操作,每次都要判断和处理,很影响效率的。

比如说在洛谷刷算法题的时候,我一般是用Java,但很多次都被卡死性能,不管怎么优化也不行,最后发现问题就在Scanner身上,那怎么处理呢?

答案是换BufferedReader,性能大幅提升。

看下面的代码:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int num = Integer.parseInt(reader.readLine());
int[] record = new int[num];
String[] nums = reader.readLine().split("\\s+");
for (int i = 0; i < num; i++) {
    record[i] = Integer.parseInt(nums[i]);
}
reader.close();

这样一段代码就可以先读一个数值,再读剩下一行里大量的数值。
看似还要开一个String[],但其实都说过了,再慢也慢不过IO,希望大家心里清楚最慢的就是IO了,减少IO是提升性能之道。

不过,在极其苛刻的情况下,Java是没办法完成的,严重TLE+MLE,哪怕你用了byte这样的也不行,所以就得换C++了。毕竟算法题没必要为Java设计性能指标,不达标也只能认了,Java比起C/C++太慢了。

Java输入的一些问题

  1. 性能实在不达标,看不对就换BufferedReader,再不行就换C++。
  2. 切记scanner.nextInt()之后没换行,此时如果读一行scanner.nextLine()可能只读到空字符串——"",导致后续崩盘。所以遇到单行单个数值+单行多数值的情况,就直接先用Integer.parseInt(scanner.nextLine());再用String[] array = scanner.nextLine().split("\s+");,把String[]转成int[]即可(切记不可直接强转,二者毫无关系)。
  3. IO真的是最慢的,千万别多次IO。宁可处理麻烦点也别贪图省代码。
  4. 使用完输入流后记得关闭,这是一个好习惯。用scanner,就写scanner.close(); ,用reader,就写reader.close();
  5. 输入流关闭之后就不能再用了,这点要注意,在最后用完之后关闭就好。
  6. java.util.Scanner不需要处理异常,java.io.BufferedReader需要处理java.io.IOException,要么try…catch…finally,要么throws,不处理是不能通过编译的。当然啦,自动关闭资源的try语句也挺好的。
  7. ……

总结

这里,我们看到了Java基本输入输出的问题,主要针对命令行的键盘输入和屏幕打印输出。
全部内容均源于自己平时的积累和感悟,希望对大家有所帮助吧。
写的东西技术含量倒能不说特别高吧,但我认为很实用,也算尽心尽力了呢。
如果有用的话,是不是应该点个赞再走呢?OrzOrzOrz……
在这里插入图片描述在这里插入图片描述在这里插入图片描述

切记,看似微不足道的东西,可能在关键时刻拌你一跤。积少成多,聚沙成塔,积累每个细节,方能厚积薄发。

再见啦!
在这里插入图片描述

发布了479 篇原创文章 · 获赞 972 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/weixin_43896318/article/details/104216294
今日推荐