【问题标题】:What's the fastest way to read from System.in in Java?在 Java 中从 System.in 读取的最快方法是什么?
【发布时间】:2011-08-13 06:30:14
【问题描述】:

我在使用Scanner(System.in) 时从标准中读取由空格或换行符分隔的一堆整数。

在 Java 中有没有更快的方法来做到这一点?

【问题讨论】:

  • 每秒需要读入几百万个整数?如果你有不到几百万,我不会太担心。
  • 我在编程比赛中遇到了这个问题。您会得到数千个问题实例,每个实例都有数千个数字,这并不罕见(以确保您不会逃脱复杂性较差的解决方案)。
  • 是的,这是针对我正在阅读数千行代码的编程竞赛,我注意到 C++ 中的 cin 比 Java 中的 Scanner 快得多,并且想知道是否存在替代方案。跨度>
  • 未来的搜索者也应该看看这个帖子:stackoverflow.com/questions/691184/…

标签: java optimization inputstream stdin


【解决方案1】:

在 Java 中有更快的方法吗?

是的。扫描仪相当慢(至少根据我的经验)。

如果您不需要验证输入,我建议您将流包装在 BufferedInputStream 中并使用类似String.split / Integer.parseInt 的东西。


一个小比较:

使用此代码读取 17 兆字节(4233600 个数字)

Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
    sum += scanner.nextInt();

占用我的机器 3.3 秒。而这个sn-p

BufferedReader bi = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = bi.readLine()) != null)
    for (String numStr: line.split("\\s"))
        sum += Integer.parseInt(numStr);

花了 0.7 秒

通过进一步弄乱代码(使用String.indexOf / String.substring 迭代line),您可以很容易地将其缩短到大约0.1 秒,但我想我已经回答了您的问题,但我没有想把它变成一些代码高尔夫。

【讨论】:

  • 但是你最好有充分的理由在你的代码中添加这样的混乱。
  • String.split 不如 StringTokenizer 快。要获得最有效的代码,请使用 StringTokenizer
【解决方案2】:

我创建了一个小的 InputReader 类,它的工作原理与 Java 的 Scanner 类似,但在速度上胜过它许多数量级,事实上,它也胜过 BufferedReader。这是一个条形图,显示了我创建的 InputReader 类从标准输入读取不同类型数据的性能:

以下是使用 InputReader 类查找来自 System.in 的所有数字之和的两种不同方法:

int sum = 0;
InputReader in = new InputReader(System.in);

// Approach #1
try {

    // Read all strings and then parse them to integers (this is much slower than the next method).
    String strNum = null;
    while( (strNum = in.nextString()) != null )
        sum += Integer.parseInt(strNum);

} catch (IOException e) { }

// Approach #2
try {

    // Read all the integers in the stream and stop once an IOException is thrown
    while( true ) sum += in.nextInt();

} catch (IOException e) { }

【讨论】:

  • 不幸的是,InputReader.readLine() 使用 OpenJKD8 并没有明显快于 BufferedReader.readLine()。缓冲区大小为 2048,流长度为 2.5 MB。但更糟糕的是:代码中断 (UTF-8) 字符编码。
【解决方案3】:

如果你从竞争编程的角度来问,如果提交速度不够快,那就是 TLE。
然后您可以检查以下方法以从 System.in 中检索字符串。 我从 java 中最好的编码器之一(竞争网站)中获取了

private String ns()
{
    int b = skip();
    StringBuilder sb = new StringBuilder();
    while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
        sb.appendCodePoint(b);
        b = readByte();
    }
    return sb.toString();
}`

【讨论】:

    【解决方案4】:

    StringTokenizer 是一种读取由标记分隔的字符串输入的更快方法。

    查看下面的示例以读取以空格分隔的整数字符串并存储在 arraylist 中,

    String str = input.readLine(); //read string of integers using BufferedReader e.g. "1 2 3 4"
    List<Integer> list = new ArrayList<>();
    StringTokenizer st = new StringTokenizer(str, " ");
    while (st.hasMoreTokens()) {
        list.add(Integer.parseInt(st.nextToken()));
    } 
    

    【讨论】:

      【解决方案5】:

      您可以逐位读取System.in。看看这个答案:https://stackoverflow.com/a/2698772/3307066

      我在这里复制代码(几乎没有修改)。基本上,它读取整数,由任何非数字分隔。 (感谢原作者。)

      private static int readInt() throws IOException {
          int ret = 0;
          boolean dig = false;
          for (int c = 0; (c = System.in.read()) != -1; ) {
              if (c >= '0' && c <= '9') {
                  dig = true;
                  ret = ret * 10 + c - '0';
              } else if (dig) break;
          }
          return ret;
      }
      

      在我的问题中,这段代码大约是。比使用 StringTokenizer 快​​ 2 倍,后者已经比 String.split(" ") 快。 (问题涉及读取 100 万个整数,每个整数最多 100 万个。)

      【讨论】:

        【解决方案6】:

        从编程的角度来看,这个定制的 Scan 和 Print 类比 Java 内置的 Scanner 和 BufferedReader 类要好得多。

        import java.io.InputStream;
        import java.util.InputMismatchException;
        import java.io.IOException;
        
        public class Scan
        {
        
        private byte[] buf = new byte[1024];
        
        private int total;
        private int index;
        private InputStream in;
        
        public Scan()
        {
            in = System.in;
        }
        
        public int scan() throws IOException
        {
        
            if(total < 0)
                throw new InputMismatchException();
        
            if(index >= total)
            {
                index = 0;
                total = in.read(buf);
                if(total <= 0)
                    return -1;
            }
        
            return buf[index++];
        }
        
        
        public int scanInt() throws IOException
        {
        
            int integer = 0;
        
            int n = scan();
        
            while(isWhiteSpace(n))   /*  remove starting white spaces   */
                n = scan();
        
            int neg = 1;
            if(n == '-')
            {
                neg = -1;
                n = scan();
            }
        
            while(!isWhiteSpace(n))
            {
        
                if(n >= '0' && n <= '9')
                {
                    integer *= 10;
                    integer += n-'0';
                    n = scan();
                }
                else
                    throw new InputMismatchException();
            }
        
            return neg*integer;
        }
        
        
        public String scanString()throws IOException
        {
            StringBuilder sb = new StringBuilder();
        
            int n = scan();
        
            while(isWhiteSpace(n))
                n = scan();
        
            while(!isWhiteSpace(n))
            {
                sb.append((char)n);
                n = scan();
            }
        
            return sb.toString();
        }
        
        
        public double scanDouble()throws IOException
        {
            double doub=0;
            int n=scan();
            while(isWhiteSpace(n))
            n=scan();
            int neg=1;
            if(n=='-')
            {
                neg=-1;
                n=scan();
            }
            while(!isWhiteSpace(n)&& n != '.')
            {
                if(n>='0'&&n<='9')
                {
                    doub*=10;
                    doub+=n-'0';
                    n=scan();
                }
                else throw new InputMismatchException();
            }
            if(n=='.')
            {
                n=scan();
                double temp=1;
                while(!isWhiteSpace(n))
                {
                    if(n>='0'&&n<='9')
                    {
                        temp/=10;
                        doub+=(n-'0')*temp;
                        n=scan();
                    }
                    else throw new InputMismatchException();
                }
            }
            return doub*neg;
        }
        
        public boolean isWhiteSpace(int n)
        {
            if(n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1)
                return true;
        
            return false;
        }
        
        public void close()throws IOException
        {
            in.close();
        }
        }
        

        而自定义的Print类可以如下

        import java.io.BufferedWriter;
        import java.io.IOException;
        import java.io.OutputStreamWriter;
        
        public class Print
        {
        private BufferedWriter bw;
        
        public Print()
        {
            this.bw = new BufferedWriter(new OutputStreamWriter(System.out));
        }
        
        
        public void print(Object object)throws IOException
        {
            bw.append("" + object);
        }
        
        public void println(Object object)throws IOException
        {
            print(object);
            bw.append("\n");
        }
        
        
        public void close()throws IOException
        {
            bw.close();
        }
        
        }
        

        【讨论】:

          【解决方案7】:

          您可以使用 BufferedReader 来读取数据

          BufferedReader inp = new BufferedReader(new InputStreamReader(System.in));
            int t = Integer.parseInt(inp.readLine());
            while(t-->0){
              int n = Integer.parseInt(inp.readLine());
              int[] arr = new int[n];
              String line = inp.readLine();
              String[] str = line.trim().split("\\s+");
              for(int i=0;i<n;i++){
                arr[i] = Integer.parseInt(str[i]);
              }
          

          对于打印使用 StringBuffer

              StringBuffer sb = new StringBuffer();
              for(int i=0;i<n;i++){
                        sb.append(arr[i]+" "); 
                      }
              System.out.println(sb);
          

          【讨论】:

            【解决方案8】:

            这里是完整版的快速阅读器和编写器。我也使用了缓冲。

            import java.io.*;
            import java.util.*;
            
            
            public class FastReader {
                private static StringTokenizer st;
                private static BufferedReader in;
                private static PrintWriter pw;
            
            
                public static void main(String[] args) throws IOException {
            
                    in = new BufferedReader(new InputStreamReader(System.in));
                    pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
                    st = new StringTokenizer("");
            
                    pw.close();
                }
            
                private static int nextInt() throws IOException {
                    return Integer.parseInt(next());
                }
                private static long nextLong() throws IOException {
                    return Long.parseLong(next());
                }
                private static double nextDouble() throws IOException {
                    return Double.parseDouble(next());
                }
                private static String next() throws IOException {
                    while(!st.hasMoreElements() || st == null){
                        st = new StringTokenizer(in.readLine());
                    }
                    return st.nextToken();
                }
            }
            

            【讨论】:

              【解决方案9】:

              一次又一次地从磁盘读取会使扫描程序变慢。我喜欢使用 BufferedReader 和 Scanner 的组合来获得两全其美的效果。即 BufferredReader 的速度和扫描仪丰富易用的 API。

              Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
              

              【讨论】:

                猜你喜欢
                • 2011-10-26
                • 1970-01-01
                • 1970-01-01
                • 2011-08-11
                • 1970-01-01
                • 2013-06-08
                • 2019-09-13
                • 2013-10-24
                • 2016-02-14
                相关资源
                最近更新 更多