47

我的 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

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

4

8 回答 8

159

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

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

几乎每个人都搞错了!

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

请记住 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 读取器和写入器:

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

于 2014-07-25T08:01:11.617 回答
46
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|
于 2013-01-11T08:32:51.100 回答
10

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(",")的带引号的标识符。,

于 2013-01-11T08:32:54.737 回答
1

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

于 2018-03-08T10:18:11.217 回答
1

我同意 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 的作者还发布了仅比较这三者的性能比较

于 2018-07-05T10:10:37.460 回答
0

我见过很多由于代码不处理引号 (")、引号内的换行符和引号内的引号引起的生产问题;例如:"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]
于 2020-05-07T11:47:12.690 回答
-1

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

例如,此定界符将以逗号分隔,带或不带周围的空格:

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

有关更多信息,请查看java.util.Scanner API

于 2013-01-11T08:32:28.327 回答
-3

好吧,我在 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()+"|");
         }}
    }
}
于 2016-01-23T15:08:41.557 回答