【问题标题】:Read CSV with Scanner()使用 Scanner() 读取 CSV
【发布时间】:2012-12-25 19:18:49
【问题描述】:

我的 csv 正在被读入 System.out,但我注意到任何带有空格的文本都会被移到下一行(作为返回 \n)

我的 csv 是这样开始的:

first,last,email,address 1, address 2
john,smith,blah@blah.com,123 St. Street,
Jane,Smith,blech@blech.com,4455 Roger Cir,apt 2

运行我的应用程序后,任何带有空格(地址 1)的单元格都会被扔到下一行。

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class main {

    public static void main(String[] args) {
        // -define .csv file in app
        String fileNameDefined = "uploadedcsv/employees.csv";
        // -File class needed to turn stringName to actual file
        File file = new File(fileNameDefined);

        try{
            // -read from filePooped with Scanner class
            Scanner inputStream = new Scanner(file);
            // hashNext() loops line-by-line
            while(inputStream.hasNext()){
                //read single line, put in string
                String data = inputStream.next();
                System.out.println(data + "***");

            }
            // after loop, close scanner
            inputStream.close();


        }catch (FileNotFoundException e){

            e.printStackTrace();
        }

    }
}

这是控制台中的结果:

第一个,最后一个,电子邮件,地址 1、地址 2 约翰,史密斯,blah@blah.com,123 英石。 街道, Jane,Smith,blech@blech.com,4455 罗杰 环, apt 2

我是否错误地使用了扫描仪?

【问题讨论】:

标签: java csv java.util.scanner


【解决方案1】:

如果您绝对必须使用 Scanner,则必须通过其useDelimiter(...) 方法设置其分隔符。否则它将默认使用所有空白作为其分隔符。尽管如前所述更好——使用 CSV 库,因为这是他们最擅长的。

例如,此定界符会以逗号分隔,有或没有环绕空格:

scanner.useDelimiter("\\s*,\\s*");

请查看java.util.Scanner API 了解更多信息。

【讨论】:

    【解决方案2】:
    scanner.useDelimiter(",");
    

    这应该可行。

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.util.Scanner;
    
    
    public class TestScanner {
    
        public static void main(String[] args) throws FileNotFoundException {
            Scanner scanner = new Scanner(new File("/Users/pankaj/abc.csv"));
            scanner.useDelimiter(",");
            while(scanner.hasNext()){
                System.out.print(scanner.next()+"|");
            }
            scanner.close();
        }
    
    }
    

    对于 CSV 文件:

    a,b,c d,e
    1,2,3 4,5
    X,Y,Z A,B
    

    输出是:

    a|b|c d|e
    1|2|3 4|5
    X|Y|Z A|B|
    

    【讨论】:

    • 我认为它不是分隔换行符是否正确?即它正在读取“e\n1”和“5\nX”作为单个标记?大概需要一个更复杂的正则表达式来分隔逗号和换行符。 (当然,即使这样,它仍然无法将带引号的字符串保持在一起,因此通常不适合 CSV。)
    • 我们可以使用 Scanner.hasNextLine() 和 nextLine() 方法获取单行数据,然后用上面的程序进行处理。
    • 如果分隔符 (,) 是值的一部分,则此解决方案将不起作用。这个答案是错误的。
    • 从未提及支持将分隔符作为数据的一部分。术语“逗号分隔值”是不言自明的。没有提到根据 RFC-4180 或 Excel CSV 变体需要“封装器”。
    • 这会很好用,直到你得到一个值,比如"Commas can go in values, too!"
    【解决方案3】:

    Scanner.next() 不读取换行符,而是读取下一个标记,由空格分隔(默认情况下,如果 useDelimiter() 未用于更改分隔符模式)。要读取一行,请使用Scanner.nextLine()

    一旦您阅读了一行,您就可以使用String.split(",") 将该行分隔为多个字段。这可以识别不包含所需数量的字段的行。使用useDelimiter(","); 将忽略文件的基于行的结构(每行由逗号分隔的字段列表组成)。例如:

    while (inputStream.hasNextLine())
    {
        String line = inputStream.nextLine();
        String[] fields = line.split(",");
        if (fields.length >= 4) // At least one address specified.
        {
            for (String field: fields) System.out.print(field + "|");
            System.out.println();
        }
        else
        {
            System.err.println("Invalid record: " + line);
        }
    }
    

    如前所述,建议使用 CSV 库。一方面,这个(和useDelimiter(",") 解决方案)将无法正确处理包含, 字符的带引号的标识符。

    【讨论】:

      【解决方案4】:

      请停止编写错误的 CSV 解析器!

      我在网上看过数百个 CSV 解析器和所谓的教程

      几乎每个人都搞错了!

      这不会是一件坏事,因为它不会影响我,但是尝试编写 CSV readers 并弄错的人倾向于编写 CSV writers , 也。并且把他们弄错了。而这些我必须为其编写解析器。

      请记住 CSV(按不那么明显的顺序递增):

      1. 值周围可以有引号字符
      2. 可以有除“之外的其他引号字符
      3. 甚至可以使用除 " 和 ' 以外的其他引号字符
      4. 不能有引号字符
      5. 甚至可以在某些值上使用引号字符,而在其他值上则没有
      6. 可以有除 , 和 ; 以外的其他分隔符
      7. 分隔符和(引用的)值之间可以有空格
      8. 可以有除 ascii 以外的其他字符集
      9. 应该在每行中具有相同数量的值,但并不总是
      10. 可以包含空字段,引用:"foo","","bar" 或不引用:"foo",,"bar"
      11. 值中可以包含换行符
      12. 如果没有分隔符,则不能在值中包含换行符
      13. 不能在值之间包含换行符
      14. 如果正确转义,值中可以包含分隔符
      15. 不使用反斜杠来转义分隔符,但是...
      16. 使用引用字符本身来转义它,例如Frodo's Ring 将是 'Frodo''s Ring'
      17. 可以在值的开头或结尾包含引号字符,甚至可以作为唯一字符 ("foo""", """bar", """")
      18. 甚至可以在未引用的值中包含引用的字符;这个没有转义

      如果您认为这显然不是问题,请再想一想。我已经看到每一个这些项目都被错误地实现了。即使在主要软件包中。 (例如办公套件、CRM 系统)

      现成的 CSV 读取器和写入器运行良好且工作正常:

      如果您坚持自己编写,请至少阅读(非常短的)RFC for CSV

      【讨论】:

      • 太对了!这应该是公认的答案:不要!使用支持引号、换行符等的真正解析器。感谢 Scheintod 阻止某些人这样做。
      • 在很多情况下,如果我们谈论的是 CSV 文件,我们可以争论。
      • 我同意@JanBodnar 的观点……第 2、3、4 和 5 点是错误的。 CSV 根据定义只能有 1 个分隔符,一个逗号。否则,它只是行和列中的杂乱数据,可能有也可能没有文件扩展名 .csv。 9 无关紧要。只需使用动态数据结构。任何谈论 " 和 ' 的内容都与数据捕获无关,尽管可以在数据清理步骤中删除。您实际上只是在谈论一个被视为 .txt 文件的 .csv 文件。没有软件包可以正确解释这一点,因为你不知道 CSV 文件是什么。不过底部链接很好。
      • 你说的有一部分是对的,我在这些方面的措辞很差。 “引用字符”将是正确的词,而不是“分隔符”。我可能会在喝完咖啡后改变它。但是,虽然您是对的,100% 正确的 csv 在现实中会有一个逗号作为字段分隔符,但您会遇到各种各样的它们。我看到分号比逗号更频繁,并且遇到了事件制表符和空格。我所知道的所有好的 csv 库都可以解析这些,因为这是他们的工作:解析其他程序生成的 csv。 (但我什至遇到过解析器无法解析的“.csv”文件)
      • @TurnipEntropy "CSV" 通常但不精确地用作通过引用处理值中的分隔符的文件的通用术语。相反,在值中转义分隔符的另一种方法通常称为“TEXT”。例如,Postgres 将这些术语用于其 COPY 命令的批量导入/导出的输入格式。所以说文件必须使用“,”作为分隔符才能成为 csv 符合 RFC 和桌面计算世界的用法,但对于数据库和数据传输世界来说太窄了。
      【解决方案5】:

      好吧,我在 NetBeans 8.1 中编写代码:

      首先:创建一个新项目,选择 Java 应用程序并命名您的项目。

      然后在公开课后修改你的代码,如下所示:

      /**
       * @param args the command line arguments
       * @throws java.io.FileNotFoundException
       */
      public static void main(String[] args) throws FileNotFoundException {
          try (Scanner scanner = new Scanner(new File("C:\\Users\\YourName\\Folder\\file.csv"))) {
               scanner.useDelimiter(",");
               while(scanner.hasNext()){
                   System.out.print(scanner.next()+"|");
               }}
          }
      }
      

      【讨论】:

        【解决方案6】:

        用这个分隔符分割 nextLine(): (?=([^\"]*\"[^\"]*\")*[^\"]*$)").

        【讨论】:

          【解决方案7】:

          我同意 Scheintod 的观点,即使用现有的 CSV 库是一个从一开始就符合 RFC-4180 的好主意。除了提到的 OpenCSV 和 Oster Miller,还有一系列其他的 CSV 库。如果你对性能感兴趣,可以看看uniVocity/csv-parsers-comparison。这表明

          使用 JDK 6、7、8 或 9 始终是最快的。该研究在这三个中没有发现任何 RFC 4180 兼容性问题。 OpenCSV 和 Oster Miller 的速度大约是它们的两倍。

          我与作者没有任何关联,但关于 uniVocity CSV 解析器,由于其作者与该解析器的作者相同,该研究可能存在偏见。

          需要注意的是,SimpleFlatMapper 的作者还发布了 performance comparison 仅比较这三个。

          【讨论】:

            【解决方案8】:

            我见过很多由于代码不处理引号(")、引号内的换行符和引号内的引号引起的生产问题;例如:"he said ""this""" 应该被解析为:he said "this "

            就像前面提到的那样,许多 CSV 解析示例只是读取一行,然后用分隔符分隔该行。这是相当不完整和有问题的。

            对于我以及可能喜欢购买(或使用其他人的代码并处理他们的依赖项)的人来说,我开始着手经典的文本解析编程,这对我很有用:

            /**
             * Parse CSV data into an array of String arrays. It handles double quoted values.
             * @param is input stream
             * @param separator
             * @param trimValues
             * @param skipEmptyLines
             * @return an array of String arrays
             * @throws IOException
             */
            public static String[][] parseCsvData(InputStream is, char separator, boolean trimValues, boolean skipEmptyLines)
                throws IOException
            {
                ArrayList<String[]> data = new ArrayList<String[]>();
                ArrayList<String> row = new ArrayList<String>();
                StringBuffer value = new StringBuffer();
                int ch = -1;
                int prevCh = -1;
                boolean inQuotedValue = false;
                boolean quoteAtStart = false;
                boolean rowIsEmpty = true;
                boolean isEOF = false;
            
                while (true)
                {
                    prevCh = ch;
                    ch = (isEOF) ? -1 : is.read();
            
                    // Handle carriage return line feed
                    if (prevCh == '\r' && ch == '\n')
                    {
                        continue;
                    }
                    if (inQuotedValue)
                    {
                        if (ch == -1)
                        {
                            inQuotedValue = false;
                            isEOF = true;
                        }
                        else
                        {
                            value.append((char)ch);
            
                            if (ch == '"')
                            {
                                inQuotedValue = false;
                            }
                        }
                    }
                    else if (ch == separator || ch == '\r' || ch == '\n' || ch == -1)
                    {
                        // Add the value to the row
                        String s = value.toString();
            
                        if (quoteAtStart && s.endsWith("\""))
                        {
                            s = s.substring(1, s.length() - 1);
                        }
                        if (trimValues)
                        {
                            s = s.trim();
                        }
                        rowIsEmpty = (s.length() > 0) ? false : rowIsEmpty;
                        row.add(s);
                        value.setLength(0);
            
                        if (ch == '\r' || ch == '\n' || ch == -1)
                        {
                            // Add the row to the result
                            if (!skipEmptyLines || !rowIsEmpty)
                            {
                                data.add(row.toArray(new String[0]));
                            }
                            row.clear();
                            rowIsEmpty = true;
            
                            if (ch == -1)
                            {
                                break;
                            }
                        }
                    }
                    else if (prevCh == '"')
                    {
                        inQuotedValue = true;
                    }
                    else
                    {
                        if (ch == '"')
                        {
                            inQuotedValue = true;
                            quoteAtStart = (value.length() == 0) ? true : false;
                        }
                        value.append((char)ch);
                    }
                }
                return data.toArray(new String[0][]);
            }
            

            单元测试:

            String[][] data = parseCsvData(new ByteArrayInputStream("foo,\"\",,\"bar\",\"\"\"music\"\"\",\"carriage\r\nreturn\",\"new\nline\"\r\nnext,line".getBytes()), ',', true, true);
            for (int rowIdx = 0; rowIdx < data.length; rowIdx++)
            {
                System.out.println(Arrays.asList(data[rowIdx]));
            }
            

            生成输出:

            [foo, , , bar, "music", carriage
            return, new
            line]
            [next, line]
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-10-31
              • 1970-01-01
              • 2017-01-18
              • 2014-05-21
              • 2021-01-04
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多