Java - 将文件扫描仪与用户输入扫描仪相结合

Java - combine Scanner for file with Scanner for user input

Edit2:请参阅下面的一些示例代码,这是留下来的,因为人们确实以原始形式回复了这个问题。


这是我和我的 CS 教授的合集:

我们有一项作业要求学生为 SQL 的修改子集编写基本命令界面。并不重要,但提供上下文。

要求说明命令可以在文件中或在命令提示符下输入。扫描仪对此似乎很自然,相当明显。

所以对于这个问题,我将引用我正在进行的学生项目的提交: https://github.com/greysondn/fallcorp/tree/fe5f2a317ff3f3206e7dd318cb50f9f67519b02b

两个相关的类是net.darkglass.Appmanager and net.darkglass.arasql.command.ExecuteCommand

问题出现在 AppManager 中的 ln 51 转发和 ExecuteCommand 中的 ln 56 转发的组合。 Scanner 管理用户输入的循环和 Scanner 管理逐行读取文件的循环不兼容;因此,我和我的教授都无法理解将 Scanner 的两种情况组合成一种方法的方法。

换句话说,这两种结构归根结底非常相似,应该是一模一样的,但我们找不到不比现在更糟的地方.

有没有一种方法可以编写扫描程序,使其既适用于用户输入又适用于用户输入的文件输入?

一些快速观察:


Edit2:经过一些评论,这里是演示核心问题的某种形式的代码。

public void doFile()
{
    // set scanner up against some URI; this is messy but it's a
    // "point of the matter" thing
    Scanner cin = new Scanner(aFile);

    // read over file
    while (cin.hasNextLine())
    {
        // this is actually a lot more complicated, but ultimately we're
        // just doing whatever the next line says
        doWhatItSays(cin.nextLine());
    }
}

public void doREPL()
{
    // set scanner up against user input - this is the actual line
    Scanner cin = new Scanner(System.in);


    Boolean continueRunning = true;

    while(continueRunning)
    {
        // pretty print prompt
        System.out.println("");
        System.out.print("$> ");

        // This, like before, is a lot more complicated, but ultimately
        // we just do whatever it says. (One of the things it may say
        // to do is to set continueRunning to false.)
        doWhatItSays(cin.nextLine());
    }
}

他们都只是简单地扫描一个输入并按照它说的去做;将这两种方法合并为一种方法需要什么? (是的,它快速而混乱;它至少得到了重点和基本评论。)

我认为this will solve your problem。这基本上是 StdIn class 摘自普林斯顿大学 算法,第 4 版 ,作者是 Robert Sedgewick 和 Kevin Wayne。我在他们的 Coursera MOOC 算法第一部分 中使用了这个 class 从文件或命令中读取输入。

编辑
结合上面提到的classwith In class from the same repository

编辑 2
In class 你有两种方法:

/**
     * Initializes an input stream from a file.
     *
     * @param  file the file
     * @throws IllegalArgumentException if cannot open {@code file}
     * @throws IllegalArgumentException if {@code file} is {@code null}
     */
    public In(File file) {
        if (file == null) throw new IllegalArgumentException("file argument is null");
        try {
            // for consistency with StdIn, wrap with BufferedInputStream instead of use
            // file as argument to Scanner
            FileInputStream fis = new FileInputStream(file);
            scanner = new Scanner(new BufferedInputStream(fis), CHARSET_NAME);
            scanner.useLocale(LOCALE);
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + file, ioe);
        }
    }

/**
     * Initializes an input stream from a given {@link Scanner} source; use with 
     * {@code new Scanner(String)} to read from a string.
     * <p>
     * Note that this does not create a defensive copy, so the
     * scanner will be mutated as you read on. 
     *
     * @param  scanner the scanner
     * @throws IllegalArgumentException if {@code scanner} is {@code null}
     */
    public In(Scanner scanner) {
        if (scanner == null) throw new IllegalArgumentException("scanner argument is null");
        this.scanner = scanner;
    }

你根据用户输入调用正确的方法(假设你检查第一个参数是否是一个文件,这很容易做到)然后你有很多其他方法,比如 hasNextLinereadChar。这一切都合而为一 class。无论您将使用哪个构造函数,都可以调用相同的方法。

我会把输入从主要代码中分离出来,所以你有一个接口 Source 轮询一个命令,然后有 2 个具体实现。

  • FileSource 只是从文件中读取
  • InteractiveSource 向用户显示提示和 return 他们 return
  • 的文本

您可以将其中之一传递给您的 class,无论它从何处获取命令,它都以相同的方式使用它。

这样做的好处是,如果您想添加 NetworkSourceUISource,您只需要编写一个新的 Source 实现即可。

这种方式还可以让您干净地关闭系统,因为每个 Source 都可以有一个方法 keepGoing(),您可以在命令之间调用该方法来查看系统是否应该关闭,对于文件来说,它会在什么时候关闭没有更多的行,输入可能是在他们输入 exit

之后

看来你把问题想的太多了Scanner,这与问题无关。如果你有匹配到 99% 的代码,直接的解决方案是将通用代码单独移动到一个方法中,并保留两个小的专用方法:

public void doFile() {
    try(Scanner cin = new Scanner(aFile)) {
        // read over file
        while (cin.hasNextLine()) {
            commonLoopBody(cin);
        }
    }
}

public void doREPL() {
    // set scanner up against user input - this is the actual line
    Scanner cin = new Scanner(System.in);
    boolean continueRunning = true;

    while(continueRunning) {
        // pretty print prompt
        System.out.printf("%n$> ");
        commonLoopBody(cin);
    }
}

private void commonLoopBody(Scanner cin) {
    // this is actually a lot more complicated, but ultimately we're
    // just doing whatever the next line says
    doWhatItSays(cin.nextLine());
}

专用方法仍然包含循环语句,但这并没有错,因为循环 不同。

不过,还有一种方法是将差异从原始代码中移出,而不是从通用代码中移出,例如

public void doFile() {
    try(Scanner cin = new Scanner(aFile)) {
        commonLoop(cin, cin::hasNextLine, ()->{});
    }
}

public void doREPL() {
    boolean continueRunning = true;
    commonLoop(new Scanner(System.in),()->continueRunning,()->System.out.printf("%n$> "));
}

private void commonLoop(Scanner cin, BooleanSupplier runCondition, Runnable beforeCommand){
    while(runCondition.getAsBoolean()) {
        beforeCommand.run();
        // this is actually a lot more complicated, but ultimately we're
        // just doing whatever the next line says
        doWhatItSays(cin.nextLine());
    }
}

在这个具体的例子中,将循环语句移动到公共代码中没有任何优势,但在某些情况下,在框架中进行循环维护会提供优势,Stream API 是举个例子……


也就是说,查看您的具体代码,似乎存在根本性的误解。在 Java 中,String 对象是不可变的,在 String 上调用 concat 会创建一个新实例。因此,带有 String test = ""; 这样的初始化程序的变量声明确实 而不是 “为事物预先保留了 space”,它通过使用引用初始化变量来浪费资源到一个空字符串,无论如何都会被随后的 test = cin.nextLine(); 覆盖。此外,test = test.concat(" "); test = test.concat(cin.nextLine()); 不必要地创建中间字符串实例,其中更简单的 test = test + " " + cin.nextLine(); 使用构建器免费编译为代码。

但最终,如果您不再忽视 Scanner 的强大功能,这些操作就会过时。 class 不仅仅是提供现有 BufferedReader.readLine() 功能的另一种方式,它是一种模式匹配工具,允许在流输入上使用正则表达式引擎。

如果您想用分号分隔命令,请使用分号作为分隔符,而不是读取必须手动连接的行。替换多行命令的换行符和删除注释也可以通过单个模式替换操作来完成。例如

static String EXAMPLE_INPUT =
   "a single line command;\n"
 + "-- a standalone comment\n"
 + "a multi\n"
 + "line\n"
 + "-- embedded comment\n"
 + "command;\n"
 + "multi -- line\n"
 + "command with double minus;\n"
 + "and just a last command;";

public static void main(String[] args) {
    Scanner s = new Scanner(EXAMPLE_INPUT).useDelimiter(";(\R|\Z)");
    while(s.hasNext()) {
        String command = s.next().replaceAll("(?m:^--.*)?+(\R|\Z)", " ");
        System.out.println("Command: "+command);
    }
}

将打印

Command: a single line command 
Command:  a multi line  command 
Command: multi -- line command with double minus 
Command: and just a last command 

如果要保留结果中的分号,可以将分隔符从
";(\R|\Z)""(?<=;)(\R|\Z)".