使用扫描仪读取CSV()

时间:2013-01-11 08:28:44

标签: java csv java.util.scanner

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

    }
}

所以这是控制台中的结果:

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

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

8 个答案:

答案 0 :(得分:139)

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

我已经在网上看到了数百种CSV解析器和所谓的教程

几乎每个人都弄错了!

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

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

  1. 可以在值
  2. 附近引用字符
  3. 可以包含其他引用字符而不是"
  4. 甚至可以有其他引用字符而不是"和'
  5. 根本没有引用字符
  6. 甚至可以在某些值上引用字符而在其他值上没有
  7. 可以包含其他分隔符,和;
  8. 可以在分隔符和(引用的)值之间有空格
  9. 可以有其他字符集而不是ascii
  10. 每行应该具有相同数量的值,但并不总是
  11. 可以包含空字段,引用:"foo","","bar"或不:"foo",,"bar"
  12. 可以包含值中的换行符
  13. 如果
  14. 未分隔
  15. ,则
  16. 不能包含值中的换行符
  17. 无法包含值之间的换行符
  18. 如果正确转义,
  19. 可以在值中包含分隔符。
  20. 不使用反斜杠来转义分隔符,但是......
  21. 使用引号字符本身来逃避它,例如Frodo's Ring将为'Frodo''s Ring'
  22. 可以在值的开头或结尾添加引号字符,或者甚至只包含字符("foo""", """bar", """"
  23. 甚至可以在未引用的值中包含引用的字符;这个没有转义
  24. 如果您认为这显然不是问题,那么请再想一想。我已经看到这些项目的每一个都被错误地执行了。即使在主要软件包中也是如此。 (例如Office-Suites,CRM Systems)

    有很好的正确开箱即用的CSV读者和作者:

    如果你坚持自己写作至少阅读(非常短)RFC for CSV

答案 1 :(得分:39)

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|

答案 2 :(得分:8)

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(",")解决方案)将无法正确处理包含,个字符的带引号的标识符。

答案 3 :(得分: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的作者还发布了performance comparison,仅比较了这三个。

答案 4 :(得分:0)

通过此分隔符拆分nextLine(): (?=([^\"]*\"[^\"]*\")*[^\"]*$)")

答案 5 :(得分:0)

我已经看到许多由代码不处理引号(“),引号内的换行符和引号内的引号引起的生产问题;例如:“他说”“ this”“”应解析为:他说“ 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]

答案 6 :(得分:-1)

如果您绝对必须使用扫描仪,则必须通过其useDelimiter(...)方法设置其分隔符。否则,它将默认使用所有空格作为其分隔符。尽管已经说过,但更好 - 使用CSV库,因为这是他们最擅长的。

例如,此分隔符将在逗号上分割,包含或不包含空格:

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

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

答案 7 :(得分:-2)

好吧,我在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()+"|");
         }}
    }
}