【问题标题】:Should a Scanner only be instantiated only once? if that's the case why so?一个 Scanner 应该只被实例化一次吗?如果是这样,为什么会这样?
【发布时间】:2021-04-06 17:43:49
【问题描述】:

我知道我在这里很困难,但我似乎无法理解为什么我们不能只创建两次 Scanner 类的实例。我会添加一个示例以防万一。

import java.util.Scanner;

public class Nope
{
    public static void main(String[] args)
    {
        System.out.println("What's your name?");
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        
        System.out.println("Welcome " + name + "!");
        scanner.close();
        
        // Now 
        System.out.println("where you do live?");
        Scanner sc = new Scanner(System.in);
        String country = sc.nextLine();
        
        System.out.println("That's a lovely place");
        sc.close();
        
    }
}

我得到一个看起来像这样的运行时错误

What's your name?
Kate
Welcome Kate!
Exception in thread "main" where you do live?
java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
    at Nope.main(Nope.java:17)

我知道再次创建同一个类的新对象是没有意义的,鼓励冗余。但我只是觉得如果我知道原因,我的头脑就会清醒了,你不也是这么想的吗?

'java.util.NoSuchElementException: No line found'和人们说扫描仪不可克隆是什么意思。

PS:我故意关闭了我的第一个扫描仪并创建了一个新对象只是为了理解这个问题。

【问题讨论】:

  • 虽然我不知道有任何应用程序需要同时使用 2 台扫描仪,但它确实有效。您的示例中的问题是,您在使用第二个扫描仪之前关闭了第一个扫描仪。您需要级联第二个扫描仪。特别是,这意味着所有扫描仪必须按照创建它们的相反顺序关闭。使用自动关闭机制是最安全的方法。请参阅页面下方的示例...

标签: java java.util.scanner nosuchelementexception


【解决方案1】:

您应该只为每个输入流创建一个Scanner。除其他外,扫描仪会提前读取,因此将消耗比实际返回的输入更多的输入。 (这是它如何知道的,例如输入是否hasNextInt()等等。)

如果您有多个输入流(例如处理多个文件),创建多个扫描器是完全明智的,但System.in 应该只有一个扫描器使用它。

【讨论】:

    【解决方案2】:

    这里实际上发生了两件不同的事情。

    1. 您应该为每个输入来源创建一个Scanner。例如,一个Scanner 用于每个不同的输入文件,一个用于System.in,一个用于每个不同的套接字输入流。

      原因是(正如 Chrylis 指出的那样)是Scanner 的各种方法在扫描仪的输入源上提前读取。如果字符未被操作消耗,则它们不会被放回输入源。相反,它们由Scanner 缓冲,并保留以供下一个Scanner 操作使用。因此,如果您有两个扫描仪试图从同一个输入源读取数据,其中一个可能会窃取用于另一个的输入。

      这就是为什么在System.in 上打开多个Scanner 对象不好的真正原因。不是您提出的“冗余”论点。一点冗余从根本上来说并没有错……尤其是如果它简化了应用程序。但是扫描器竞争输入可能会导致意外的行为/错误。

    2. 第二个问题是当你close() 一个Scanner 时也会关闭输入源。

      在您的情况下,这意味着您正在关闭System.in。然后您将创建第二个Scanner 以读取(现已关闭)System.in

      当您尝试使用Scanner 来读取已关闭的System.in 时,会导致NoSuchElementException

    因此,如果您没有在第一个 Scanner 上调用 close(),那么您的代码可能会起作用,但这取决于您对第一个 Scanner 执行的操作顺序.


    人们说Scanner 不可克隆。

    他们是正确的。

    【讨论】:

      【解决方案3】:

      这很简单,您应该只为每个输入创建一个扫描器。 Scanner 使用 nextLine() 方法逐行读取。最后检查条件 hasNext() 以便找出参数。

      试试这个

      import java.util.Scanner;
      
      public class Nope{
          public static void main(String[] args) {
              Scanner scanner = new Scanner(System.in);
              System.out.println("What's your name?");
              String name = scanner.nextLine();
      
              System.out.println("Welcome " + name + "!");
              // Now
              System.out.println("where you do live?");
              String country = scanner.nextLine();
      
              System.out.println(country +" Is a lovely place");
              if(!scanner.hasNext()){
              scanner.close();
              }
          }
      }
      

      输出喜欢

      
      What's your name?
      xyz
      Welcome xyz!
      where you do live?
      rjk
      rjk Is a lovely place
      
      

      【讨论】:

        【解决方案4】:

        这个答案的重点是 close() 操作以及为什么没有选项可以从 System.in 再次读取,如果扫描仪实例之前关闭了它,因为最佳答案已经给出了正确的信息。只是好奇。


        Scanner

        当一个 Scanner 关闭时,它会关闭它的输入源,如果源 实现 Closeable 接口。 没有外部的扫描仪对于多线程使用是不安全的 同步。

        • 您应该为要读取的每个源创建一个 Scanner 实例。
        • 如果你必须共享同一个实例,你应该实现同步机制,因为它不是线程安全的。
        • 正如其他答案已经指出的那样,close() 是一个“危险”操作。

        System.in close()

        假设 System.in 被指定为来源。

        这是来自InputStreamReader的关闭方法

        public void close() throws IOException
        {
            synchronized (lock)
            {
               // Makes sure all intermediate data is released by the decoder.
               if (decoder != null)
                  decoder.reset();
               if (in != null)
                  in.close();
               in = null;
               isDone = true;
               decoder = null;
             }
        }
        

        引用 System.in 的变量是名为 in 的变量。

        对此InputStream 执行两个操作除了空检查):


        1. in.close()

        This does nothing at all: System.in 的类 (InputStream) 只留下继承的 close() 方法的空实现(来自 Closeable

        /**
         * Closes this stream. Concrete implementations of this class should free
         * any resources during close. This implementation does nothing.
         *
         */
        public void close() throws IOException {
            /* empty */
        }
        

        javadocs都没有隐藏真相

        InputStream的close方法什么都不做


        2. in = null

        这是您无法再次阅读 System.in 的真正原因。将其设置为 null 将无法使用新的 Scanner 从此源进行任何进一步的读取尝试。


        但是...为什么它会抛出 NoSuchElementException 而不是 NullPointerException

        Scanner 的初始化不会在创建其 Reader 实例的步骤中失败。这是因为InputStream 被包装成一个新的BufferedInputStream。所以lock对象在Scanner的Reader初始化时不为空:

        public InputStreamReader(InputStream in) 
        {
            super(in);  
            this.in = in;
            ...
        }
        

        .

        protected Reader(Object lock) 
        {
            if (lock == null) {    
                throw new NullPointerException();   
            }
            this.lock = lock;
        }
        

        您将能够从System.in 创建第二个Scanner 实例,而不会引发任何异常;由于InputStream 被包装到一个新的BufferedInputStream 实例中,lock 对象不为空并通过过滤器。但是内部的InputStreamSystem.in,确实是空的,从它在之前的close()操作中被设置为空的那一刻起:

        这是ScannerSystem.in的第二次初始化中的lock对象。扫描器仍然不知道会出现什么问题,因为它的初始化是成功的(由于包装了BufferedInputStream)并且仍然相信它的InputStream 是有效的。

        但在第一次尝试从System.in 再次读取时,会发生这种情况:

        public String nextLine() {
            if (hasNextPattern == linePattern())
                return getCachedResult();
            clearCaches();
        
            String result = findWithinHorizon(linePattern, 0);
            if (result == null) /* result is null, as there's no source */
               throw new NoSuchElementException("No line found");
        
             (...)
         }
        

        那是Scanner 注意到终于有些事情进展不顺利的那一刻。 findWithinHorizon 的结果将返回 null,因为无法找到来源。

        由于之前在 close() 操作中将 System.in 设置为 null,因此在尝试从第二个 Scanner 实例读取时出现错误:NoSuchElementException

        【讨论】:

          【解决方案5】:

          Scanner 实现 AutoCloseable 接口
          不要单独调用close,而是使用autocloseable mechanism

          public class Nope
          {
            public static void main(String[] args)
            {
              System.out.println("What's your name?");
              try( Scanner scanner = new Scanner(System.in) ) {
                String name = scanner.nextLine();
          
                System.out.println("Welcome " + name + "!");
          
                // Now 
                System.out.println("where you do live?");
                try( Scanner sc = new Scanner(System.in) ) {
                  String country = sc.nextLine();
          
                  System.out.println("That's a lovely place");
                }
              }
            }
          }
          

          现在一切正常,退出 try-with-resources 块时会自动调用 close...

          【讨论】:

            猜你喜欢
            • 2014-09-29
            • 2016-01-08
            • 2016-04-01
            • 2018-11-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-03-13
            相关资源
            最近更新 更多