繁体   English   中英

Java-将Scanner for file与Scanner结合起来以进行用户输入

[英]Java - combine Scanner for file with Scanner for user input

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


这是我和我的CS教授合起来的一个:

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

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

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

这两个相关的类是net.darkglass.Appmanagernet.darkglass.arasql.command.ExecuteCommand

此问题是由于AppManager中的ln 51转发和ExecuteCommand中的ln 56转发共同引起的。 不兼容的Scanner循环管理用户输入,而Scanner循环管理逐行读取文件则不兼容。 结果,我和我的教授都无法想出将Scanner的两种情况组合为一种方法的方法。

换句话说,这两种结构在一天结束时极其相似,并且可能应该是相同的,但是我们无法找到比当前状况更糟的方法。

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

一些快速观察:

  • 我在我的代码中指出了事情开始变得有点生气了。 也就是说,在直觉上感觉错了。 这是在ExecuteCommand中发生的,因为这是要编写的两个命令中的第二个。

  • 该代码大致遵循解释器设计模式 我的母语是Pythonic和/或C ++。 一些习惯用法和处理方式无疑会反映出这一点。

  • 我的教授很清楚,我至少打算发布这个问题,并且和我一样好奇和激动。 当他解决该项目以确保它可行并且需要多长时间时,他遇到了同样的绊脚石,找不到他满意的解决方案。

  • 编辑:上下文很重要; 请在指定的位置弹出这两个文件,然后再检查一下。 最终发生的事情是,这两种扫描器几乎是相同的,但是由于两种I / O在Java中的工作方式,一种仅用于文件,另一种仅用于用户输入。 从表面上看,我突然意识到这个问题听起来比实际问题要复杂得多。 (是的,任何使用扫描仪的方法都可以解析字符串,而不管其来源如何,但是这里的情况更多是关于在两个不同的源上使用相同的扫描仪,这主要是由于使用方式)。


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());
    }
}

他们都简单地扫描输入并按照输入内容进行操作。 将这两种方法合并为一个需要什么? (是的,它既快速又混乱;它至少可以说明要点和基本评论。)

我认为这可以解决您的问题 这基本上是StdIn类,该类取自Robert Sedgewick和Kevin Wayne StdIn普林斯顿算法,第四版 我在他们的Coursera MOOC 算法第一部分中使用了该类,以从文件或cmd中读取输入。

编辑
将上述类与来自同一存储库的In合并。

编辑2
In类中,您有两种方法:

/**
     * 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 这是一堂课。 不管使用哪种构造函数,都可以调用相同的方法。

我将从主代码中分离出输入,因此您有一个Source接口,可以轮询一个命令,然后有2个具体实现。

  • FileSource文件读取的FileSource
  • InteractiveSource一个向用户显示提示并返回他们返回的文本

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

这样的好处是,如果您想添加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());
}

专用方法仍然包含loop语句,但这没什么不对,因为循环不同的。

还有,也可以将差异移出原始代码,而不是普通代码,例如

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 = "";这样的初始化程序的变量声明String test = ""; 没有 ,它通过与参考为空字符串,它获取被随后的覆盖初始化变量浪费资源“预储备的东西的空间” test = cin.nextLine(); 无论如何。 另外, test = test.concat(" "); test = test.concat(cin.nextLine()); test = test.concat(" "); test = test.concat(cin.nextLine()); 不必要地创建中间字符串实例,其中更简单的test = test + " " + cin.nextLine(); 使用构建器免费编译为代码。

但是最后,如果您停止忽略Scanner的强大功能,这些操作将过时。 此类不仅是提供现有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)"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM