Tags:

Đọc ghi file CSV trong java

File CSV (Comma Separated Values) là một loại định dạng văn bản trong đó các giá trị được ngăn cách bởi dấu phẩy. CSV file thường được dùng để lưu trữ các thông tin có kích thước nhỏ như danh bạ, đia chỉ, etc.

File CSV có thể mở và thao tác trên các phần mềm văn bản như notepad, textEdit, etc hoặc các phần mềm bảng tính như Excel. Các dữ liệu của từng column tương ứng sẽ được ngăn cách nhau bởi dấu phẩy, chúng ta có thể sử dụng các class của java.io để đọc file CSV như BufferedReader, BufferedInputStream etc. 

Note:

  • Nếu dữ liệu trong một column chứa dấu [,] phải thêm cặp dấu [“”] vào đầu và cuối column để phân biệt. Ví dụ “9th, at Terrace plc” => 9th, at Terrace plc
  • Nếu dữ liệu chứa cặp dấu “”(1) thì phải thêm cặp dấu “” bao ngoài(1) và bao ở đầu và cuối colum .  Ví dụ “Joan “”the bone”” => Joan “”the bone”

Cho file addresses.csv với nội dung sau

John,Doe,120 jefferson st.,Riverside, NJ
Jack,McGinnis,220 hobo Av.,Phila, PA
"John ""Da Man""",Repici,120 Jefferson St.,Riverside, NJ
Stephen,Tyler,"7452 Terrace ""At the Plaza"" road",SomeTown,SD
,Blankman,,SomeTown, SD
"Joan ""the bone"", Anne",Jet,"9th, at Terrace plc",Desert City,CO

Nguồn: https://people.sc.fsu.edu/~jburkardt/data/csv/csv.html

Các column lần lượt là LastName, FirstName, Street, District, City. Đọc dữ liệu từ file addresses.csv cho thực hiện các yêu cầu sau:

  1. Xuất tất cả các thông tin của từng người ra màn hình.
  2. Cho biết những người có địa chỉ District tại SomeTown.

Đầu tiên mình sẽ thiết kế Address class để lưu trữ thông tin từng dòng.

public class Address {
    private String lastName;
    private String firstName;
    private String street;
    private String district;
    private String city;

    public Address(String lastName, String firstName, String street, String district, String city) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.street = street;
        this.district = district;
        this.city = city;
    }

    public String getLastName() {
        return lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getStreet() {
        return street;
    }

    public String getDistrict() {
        return district;
    }

    public String getCity() {
        return city;
    }
}

Tiếp đến xây dựng hàm xử lý chuỗi để trả về các thông tin tương ứng.

Ý tưởng

  1. Khởi tạo một Stack stack, StringBuilder str, Arraylist result.
  2. Duyệt qua từng ký tự ch của từng dòng
    • Nếu ch == ‘\”‘ 
      • Nếu str.length() > 0 và stack.size() % 2 = 0 => nuối ký tự chstr.
      • Thêm ch vào stack.
    • Ngược lại nếu ch == ‘,’ và stack.size % 2 == 0
      • Thêm chuỗi str vào kết quả trả về result.
      • Làm rỗng stackstr.
    • Ngược lại nếu ch == ‘,’ và stack.size % 2 != 0
      • Nuối ch vào str.
    • Ngược lại nuối ch vào str.
  3. Thêm chuỗi str được sử lý cuối cùng vào result.
  4. Trả về kết quả
public static List getAddressInfor(String line) {
        List<String> result = new ArrayList<>();
        Stack<Character> stack = new Stack<>();
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < line.length(); i++) {
            char ch = line.charAt(i);
            if (ch == '\"') {
                if (str.length() > 0 && stack.size() % 2 == 0)
                    str.append(ch);
                stack.push(ch);
            } else if (ch == ',' && stack.size() % 2 == 0) {
                result.add(str.toString());
                stack.clear();
                str = new StringBuilder();
            } else if (ch == ',' && stack.size() % 2 != 0) {
                str.append(ch);
            } else {
                str.append(ch);
            }
        }
        result.add(str.toString());
        return result;

    }

Đọc file CSV với BufferedReader

BufferedReader đọc dữ liệu theo luồng ký tự sử dụng cơ chế bộ nhớ đệm nhầm tăng hiệu suất đọc file. Với BufferedReader chúng ta có thể đọc từng dòng với readLine() method sau đó xử lý chuỗi để có được kết quả.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        String path = "./src/addresses.csv";
        List<Address> addresses = new ArrayList<>();

        try (BufferedReader bir = new BufferedReader(new FileReader(path))) {
            String line = bir.readLine();
            while (line != null) {
                List<String> result = getAddressInfor(line);
                addresses.add(new Address(result.get(0).trim(), result.get(1).trim(), result.get(2).trim(), result.get(3).trim(), result.get(4).trim()));
                line = bir.readLine();
            }
            System.out.println("SHOW ALL");
            System.out.println("----------------------------------------");
            addresses.forEach(address -> {
                System.out.println("Last name: " + address.getLastName());
                System.out.println("First name: " + address.getFirstName());
                System.out.println("Street: " + address.getStreet());
                System.out.println("District: " + address.getDistrict());
                System.out.println("City: " + address.getCity());
                System.out.println("----------------------------------------");
            });

            System.out.println("\n\n\n");
            System.out.println("DISTRICT IS SomeTown");

            addresses.stream().filter(address -> address.getDistrict().equals("SomeTown"))
                    .forEach(address -> {
                        System.out.println("Last name: " + address.getLastName());
                        System.out.println("First name: " + address.getFirstName());
                        System.out.println("Street: " + address.getStreet());
                        System.out.println("District: " + address.getDistrict());
                        System.out.println("City: " + address.getCity());
                        System.out.println("----------------------------------------");
                    });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static List getAddressInfor(String line) {
        List<String> result = new ArrayList<>();
        Stack<Character> stack = new Stack<>();
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < line.length(); i++) {
            char ch = line.charAt(i);
            if (ch == '\"') {
                if (str.length() > 0 && stack.size() % 2 == 0)
                    str.append(ch);
                stack.push(ch);
            } else if (ch == ',' && stack.size() % 2 == 0) {
                result.add(str.toString());
                stack.clear();
                str = new StringBuilder();
            } else if (ch == ',' && stack.size() % 2 != 0) {
                str.append(ch);
            } else {
                str.append(ch);
            }
        }
        result.add(str.toString());
        return result;

    }
}

Output:

SHOW ALL
—————————————-
Last name: John
First name: Doe
Street: 120 jefferson st.
District: Riverside
City: NJ
—————————————-
Last name: Jack
First name: McGinnis
Street: 220 hobo Av.
District: Phila
City: PA
—————————————-
Last name: John “Da Man”
First name: Repici
Street: 120 Jefferson St.
District: Riverside
City: NJ
—————————————-
Last name: Stephen
First name: Tyler
Street: 7452 Terrace “At the Plaza” road
District: SomeTown
City: SD
—————————————-
Last name:
First name: Blankman
Street:
District: SomeTown
City: SD
—————————————-
Last name: Joan “the bone”, Anne
First name: Jet
Street: 9th, at Terrace plc
District: Desert City
City: CO
—————————————-


DISTRICT IS SomeTown
Last name: Stephen
First name: Tyler
Street: 7452 Terrace “At the Plaza” road
District: SomeTown
City: SD
—————————————-
Last name:
First name: Blankman
Street:
District: SomeTown
City: SD
—————————————-

Ghi File với BufferedWriter

BufferedWriter giúp chúng ta giảm thiểu số lượng thao tác I/O với cơ chết sử dụng bộ nhớ đệm. Các column trong file CSV được ngăn cách nhau bởi dấu phẩy nên chúng ta có thể dễ dàng thao tác như ghi một file text.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<List<String>> rows = Arrays.asList(
                Arrays.asList("Jean", "author", "Java"),
                Arrays.asList("David", "editor", "Python"),
                Arrays.asList("Scott", "editor", "Node.js")
        );

        Writer out;
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.csv"))) {

            bw.write("Name");
            bw.write(",");
            bw.write("Role");
            bw.write(",");
            bw.write("Topic");
            bw.newLine();

            for (List<String> rowData : rows) {
                bw.write(String.join(",", rowData));
                bw.newLine();
            }


        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Lời kết

Chúng ta có nhiều cách để đọc file CSV vì bản chất nó cũng như file text, chỉ cần xử lý chuỗi một chút là có được kết quả mong muốn. Trong thực tế đôi khi chúng ta sẽ gặp các file dạng text nhưng lại có cách bố trí tương tự như file CSV như là ký tự ngắt giữa column là | thay cho , trong CSV.

0 0 votes
Article Rating
Subscribe
Notify of
guest
2 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
2
0
Would love your thoughts, please comment.x
()
x