oruji.github.io
oruji.github.ioPersian Tutorials
ویرایش: 1396/11/14 22:22
A A

نحوه خواندن و پردازش (parse) فایل CSV در جاوا

در این آموزش از کتاب برخط جاوا، نحوه و چگونگی خواندن و parse فایل های CSV را مرور خواهیم کرد. CSV مخفف Comma-Separated-Values می باشد و یک فایل معمولی متنی است، که داده ها در آن به صورت ستون به ستون ذخیره شده و توسط یک جدا کننده (به طور معمول کاما ",") از هم جدا می شوند.

نمونه محتوی CSV

1,US,United States 2,MY,Malaysia 3,AU,Australia

یا

"1","US","United States" "2","MY","Malaysia" "3","AU","Australia"

نکته

در یک فایل CSV، به طور معمول دو مشکل وجود دارد:

  • مورد اول اینکه درون مقادیر جدا شده با کاما، خود کاراکتر کاما نیز وجود داشته باشد:
  • "aaa","b,bb","ccc"
  • دوم اینکه فیلدها هرکدام درون دابل کتیشن باشند، درون خود فیلد نیز کاراکتر دابل کتیشن وجود داشته باشد. در این مورد باید قبل از کاراکتر کتیشن یک کتیشن دیگر نیز قرار داد.
  • "aaa","b""bb","ccc"

    در این آموزش، ما سه مثال برای خواندن، پردازش (parse) و چاپ مقادیر فایل CSV در جاوا را نشان خواهیم داد.

    1. راهکار ساده برای پردازش و parse یک فایل CSV با قالب بندی ساده.
    2. راهکار پیشرفته برای پردازش و parse فایل CSV با قالب بندی پیچیده (فیلد های دارای کاما یا دابل کتیشن).
    3. راهکار Thirtd Party، مثال OpenCSV.

    1. راهکار ساده

    در صورتیکه مطمئن هستید فایل های CSV شما حاوی کاما و دابل کتیشن نمی باشند، تنها کافی است از split() برای پردازش و parse فایل CSV استفاده کنید.

    1.1 فایل ساده CSV

    "1.0.0.0","1.0.0.255","16777216","16777471","AU","Australia" "1.0.1.0","1.0.3.255","16777472","16778239","CN","China" "1.0.4.0","1.0.7.255","16778240","16779263","AU","Australia" "1.0.8.0","1.0.15.255","16779264","16781311","CN","China" "1.0.16.0","1.0.31.255","16781312","16785407","JP","Japan" "1.0.32.0","1.0.63.255","16785408","16793599","CN","China" "1.0.64.0","1.0.127.255","16793600","16809983","JP","Japan" "1.0.128.0","1.0.255.255","16809984","16842751","TH","Thailand"

    1.2 هیچ جادویی وجود ندارد، تنها فایل متنی بالا را خوانده، و با جدا کننده کاما جدا می کنیم:

    package org.amoozesh.csv; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class CSVReader { public static void main(String[] args) { String csvFile = "/Users/oruji/csv/country.csv"; BufferedReader br = null; String line = ""; String cvsSplitBy = ","; try { br = new BufferedReader(new FileReader(csvFile)); while ((line = br.readLine()) != null) { // use comma as separator String[] country = line.split(cvsSplitBy); System.out.println("Country [code= " + country[4] + " , name=" + country[5] + "]"); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

    1.3 برای JDK 7 و نسخه های بالاتر، از try-resources استفاده کنید.

    package org.amoozesh.csv; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class CSVReader { public static void main(String[] args) { String csvFile = "/Users/oruji/csv/country.csv"; String line = ""; String cvsSplitBy = ","; try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) { while ((line = br.readLine()) != null) { // use comma as separator String[] country = line.split(cvsSplitBy); System.out.println("Country [code= " + country[4] + " , name=" + country[5] + "]"); } } catch (IOException e) { e.printStackTrace(); } } }

    خروجی

    Country [code= "AU" , name="Australia"] Country [code= "CN" , name="China"] Country [code= "AU" , name="Australia"] Country [code= "CN" , name="China"] Country [code= "JP" , name="Japan"] Country [code= "CN" , name="China"] Country [code= "JP" , name="Japan"] Country [code= "TH" , name="Thailand"]

    2. راهکار پیشرفته

    این راهکار مشکل وجود کاراکتر کاما و دابل کتیشن درون فیلدها را حل می کند، و همچنین جدا کننده های سفارشی (به غیر از کاما) را نیز پشتیبانی می کند. مثال پردازش CSV زیر و تست case های Junit آن را بر درک بیشتر مرور کنید.

    نکته

    دوباره تکرار می کنیم، در صورتیکه کاراکتر دابل کتیشن (") درون یک فیلد بود، شما باید یک کتیشن دیگر قبل از آن قرار دهید:

    "aaa","b""bb","ccc"

    2.1 فایل CSV دیگری را در زیر وجود دارد

    10,AU,Australia 11,AU,Aus""tralia "12","AU","Australia" "13","AU","Aus""tralia" "14","AU","Aus,tralia"

    2.2 پردازش فایل فوق

    package org.amoozesh.utils; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class CSVUtils { private static final char DEFAULT_SEPARATOR = ','; private static final char DEFAULT_QUOTE = '"'; public static void main(String[] args) throws Exception { String csvFile = "/Users/oruji/csv/country2.csv"; Scanner scanner = new Scanner(new File(csvFile)); while (scanner.hasNext()) { List line = parseLine(scanner.nextLine()); System.out.println("Country [id= " + line.get(0) + ", code= " + line.get(1) + " , name=" + line.get(2) + "]"); } scanner.close(); } public static List parseLine(String cvsLine) { return parseLine(cvsLine, DEFAULT_SEPARATOR, DEFAULT_QUOTE); } public static List parseLine(String cvsLine, char separators) { return parseLine(cvsLine, separators, DEFAULT_QUOTE); } public static List parseLine(String cvsLine, char separators, char customQuote) { List result = new ArrayList<>(); //if empty, return! if (cvsLine == null && cvsLine.isEmpty()) { return result; } if (customQuote == ' ') { customQuote = DEFAULT_QUOTE; } if (separators == ' ') { separators = DEFAULT_SEPARATOR; } StringBuffer curVal = new StringBuffer(); boolean inQuotes = false; boolean startCollectChar = false; boolean doubleQuotesInColumn = false; char[] chars = cvsLine.toCharArray(); for (char ch : chars) { if (inQuotes) { startCollectChar = true; if (ch == customQuote) { inQuotes = false; doubleQuotesInColumn = false; } else { //Fixed : allow "" in custom quote enclosed if (ch == '\"') { if (!doubleQuotesInColumn) { curVal.append(ch); doubleQuotesInColumn = true; } } else { curVal.append(ch); } } } else { if (ch == customQuote) { inQuotes = true; //Fixed : allow "" in empty quote enclosed if (chars[0] != '"' && customQuote == '\"') { curVal.append('"'); } //double quotes in column will hit this! if (startCollectChar) { curVal.append('"'); } } else if (ch == separators) { result.add(curVal.toString()); curVal = new StringBuffer(); startCollectChar = false; } else if (ch == '\r') { //ignore LF characters continue; } else if (ch == '\n') { //the end, break! break; } else { curVal.append(ch); } } } result.add(curVal.toString()); return result; } }

    خروجی

    Country [id= 10, code= AU , name=Australia] Country [id= 11, code= AU , name=Aus"tralia] Country [id= 12, code= AU , name=Australia] Country [id= 13, code= AU , name=Aus"tralia] Country [id= 14, code= AU , name=Aus,tralia]

    3.3 unit test زیر را مرور کنید، این مثال مشکل کاما و دابل کتیشن را تست میکند:

    package org.amoozesh.csv; import org.amoozesh.utils.CSVUtils; import org.hamcrest.core.IsNull; import org.junit.Test; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; public class CSVUtilsTest { @Test public void test_no_quote() { String line = "10,AU,Australia"; List result = CSVUtils.parseLine(line); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Australia")); } @Test public void test_no_quote_but_double_quotes_in_column() throws Exception { String line = "10,AU,Aus\"\"tralia"; List result = CSVUtils.parseLine(line); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Aus\"tralia")); } @Test public void test_double_quotes() { String line = "\"10\",\"AU\",\"Australia\""; List result = CSVUtils.parseLine(line); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Australia")); } @Test public void test_double_quotes_but_double_quotes_in_column() { String line = "\"10\",\"AU\",\"Aus\"\"tralia\""; List result = CSVUtils.parseLine(line); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Aus\"tralia")); } @Test public void test_double_quotes_but_comma_in_column() { String line = "\"10\",\"AU\",\"Aus,tralia\""; List result = CSVUtils.parseLine(line); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Aus,tralia")); } }

    3.4 unit test دیگری را که در زیر آمده است مرور کنید، این مثال جدا کننده و دابل کتیشن سفارشی را تست می کند.

    package org.amoozesh.csv; import org.amoozesh.utils.CSVUtils; import org.hamcrest.core.IsNull; import org.junit.Test; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; public class CSVUtilsTestCustom { @Test public void test_custom_separator() { String line = "10|AU|Australia"; List result = CSVUtils.parseLine(line, '|'); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Australia")); } @Test public void test_custom_separator_and_quote() { String line = "'10'|'AU'|'Australia'"; List result = CSVUtils.parseLine(line, '|', '\''); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Australia")); } @Test public void test_custom_separator_and_quote_but_custom_quote_in_column() { String line = "'10'|'AU'|'Aus|tralia'"; List result = CSVUtils.parseLine(line, '|', '\''); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Aus|tralia")); } @Test public void test_custom_separator_and_quote_but_double_quotes_in_column() { String line = "'10'|'AU'|'Aus\"\"tralia'"; List result = CSVUtils.parseLine(line, '|', '\''); assertThat(result, IsNull.notNullValue()); assertThat(result.size(), is(3)); assertThat(result.get(0), is("10")); assertThat(result.get(1), is("AU")); assertThat(result.get(2), is("Aus\"tralia")); } }

    مثال OpenCSV

    در صورتیکه با مثال های فوق راحت نیستید، از کتابخانه هایی مانند OpenCSV می توانید استفاده کنید.

    3.1 Maven

    <dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>3.8</version> </dependency>

    3.2 فایل CSV

    10,AU,Australia 11,AU,Aus""tralia "12","AU","Australia" "13","AU","Aus""tralia" "14","AU","Aus,tralia"

    3.2 مثال OpenCSV برای پردازش و parse فایل CSV بالا

    package org.amoozesh.csv; import com.opencsv.CSVReader; import java.io.FileReader; import java.io.IOException; public class CSVReaderExample { public static void main(String[] args) { String csvFile = "/Users/oruji/csv/country3.csv"; CSVReader reader = null; try { reader = new CSVReader(new FileReader(csvFile)); String[] line; while ((line = reader.readNext()) != null) { System.out.println("Country [id= " + line[0] + ", code= " + line[1] + " , name=" + line[2] + "]"); } } catch (IOException e) { e.printStackTrace(); } } }

    خروجی

    Country [id= 10, code= AU , name=Australia] Country [id= 11, code= AU , name=Aus"tralia] Country [id= 12, code= AU , name=Australia] Country [id= 13, code= AU , name=Aus"tralia] Country [id= 14, code= AU , name=Aus,tralia]

    منابع

    1. The Only Class You Need for CSV Files