Quét
(Scanning)

Trong lập trình Java, việc xử lý dữ liệu đầu vào và định dạng dữ liệu đầu ra là những tác vụ quan trọng và phổ biến. Quét (Scanning) liên quan đến việc đọc và phân tích dữ liệu từ các nguồn đầu vào khác nhau như bàn phím, tệp, hoặc luồng mạng, để trích xuất thông tin cần thiết. Định dạng (Formatting), ngược lại, là quá trình định dạng dữ liệu để hiển thị hoặc ghi vào các đầu ra, giúp thông tin trở nên dễ hiểu và trình bày đẹp mắt.

Java cung cấp các lớp và phương thức mạnh mẽ như Scanner và Formatter để thực hiện các thao tác quét và định dạng một cách hiệu quả và tiện lợi. Những công cụ này không chỉ hỗ trợ trong việc xử lý dữ liệu văn bản mà còn cho phép làm việc với các kiểu dữ liệu số, ngày tháng, và nhiều định dạng phức tạp khác.

Trong phần này, chúng ta sẽ khám phá chi tiết cách sử dụng các lớp này, các phương thức chính, cũng như các tình huống thường gặp khi làm việc với quét và định dạng trong Java.

Lớp Scanner (Class Scanner)

Quét dữ liệu liên quan đến việc đọc và phân tích dữ liệu từ các nguồn khác nhau, chẳng hạn như từ bàn phím, tập tin, hoặc chuỗi. Trong Java, lớp Scanner được sử dụng để thực hiện công việc này.

Lớp Scanner: Lớp này cho phép bạn quét dữ liệu từ các nguồn khác nhau như System.in, các tệp, hoặc các chuỗi. Nó có thể phân tích và trích xuất các loại dữ liệu khác nhau như số nguyên, số thực, và chuỗi.

Scanner là một lớp trong Java được sử dụng để phân tích dữ liệu đầu vào thành các token riêng lẻ và chuyển đổi chúng thành các kiểu dữ liệu tương ứng.

Cách hoạt động của Scanner

Phân tách dữ liệu thành token: Scanner chia nhỏ dữ liệu đầu vào thành các token dựa trên một mẫu phân cách (delimiter) được thiết lập. Mặc định, mẫu phân cách là khoảng trắng, bao gồm dấu cách, tab, và xuống dòng. Điều này cho phép Scanner dễ dàng phân biệt giữa các phần khác nhau của dữ liệu đầu vào.

Chuyển đổi token thành kiểu dữ liệu: Sau khi dữ liệu được phân tách thành các token, Scanner cung cấp nhiều phương thức next khác nhau để chuyển đổi các token này thành các kiểu dữ liệu tương ứng. Ví dụ:

↳ nextInt() để lấy số nguyên (int).

↳ nextLong() để lấy số nguyên dài (long).

↳ next() để lấy chuỗi ký tự (String).

Bằng cách này, Scanner giúp bạn dễ dàng đọc và chuyển đổi dữ liệu từ các nguồn đầu vào khác nhau như bàn phím, tệp tin, hoặc luồng mạng.

Số bản địa hóa (Localized numbers)

Trong lớp Scanner của Java, localized numbers là các số được định dạng theo quy ước địa phương (locale) của một quốc gia hoặc khu vực cụ thể. Khi sử dụng Scanner để đọc các số được định dạng theo kiểu địa phương, nó có thể nhận diện và chuyển đổi các số này theo đúng định dạng đó.

Cách hoạt động của Localized Numbers trong Scanner:

Locale: là một đối tượng trong Java đại diện cho một ngôn ngữ, quốc gia, hoặc khu vực cụ thể. Ví dụ, Locale.US đại diện cho Hoa Kỳ, Locale.FRANCE đại diện cho Pháp. Các quốc gia khác nhau có thể có cách định dạng số khác nhau, chẳng hạn:

↳ Ở Hoa Kỳ: 1,234.56 (dấu phẩy cho phần ngàn, dấu chấm cho phần thập phân).

↳ Ở Đức: 1.234,56 (dấu chấm cho phần ngàn, dấu phẩy cho phần thập phân).

↳ Sử dụng Localized Numbers: Khi bạn thiết lập một Scanner để sử dụng một locale cụ thể, nó sẽ đọc các số theo định dạng của locale đó. Bạn có thể thiết lập locale cho Scanner bằng phương thức useLocale(Locale locale).

Các định dạng localized (theo địa phương) được xác định bởi các tham số sau trong Java, dựa trên các đối tượng DecimalFormat (df) và DecimalFormatSymbols (dfs). Đây là các tham số chi tiết:

↳ LocalGroupSeparator là dấu phân cách hàng nghìn, như dấu phẩy trong "1,000" hoặc dấu chấm trong "1.000". Phương thức lấy giá trị: dfs.getGroupingSeparator().

↳ LocalDecimalSeparator là dấu chấm thập phân, như dấu chấm trong "3.14" hoặc dấu phẩy trong "3,14". Phương thức lấy giá trị: dfs.getDecimalSeparator().

↳LocalPositivePrefix và LocalPositiveSuffix là các chuỗi ký tự xuất hiện trước và sau một số dương, có thể là chuỗi rỗng (ví dụ: "USD 100"). Phương thức lấy giá trị: df.getPositivePrefix() và df.getPositiveSuffix().

↳ LocalNegativePrefix và LocalNegativeSuffix là các chuỗi ký tự xuất hiện trước và sau một số âm, thường thấy ở những nơi sử dụng dấu ngoặc kép hoặc dấu âm (ví dụ: "(100)" hoặc "-100"). Phương thức lấy giá trị: df.getNegativePrefix() và df.getNegativeSuffix().

↳ LocalNaN là chuỗi đại diện cho các giá trị không phải là số, thường được dùng trong tính toán dấu phẩy động khi kết quả không xác định (ví dụ: 0/0). Phương thức lấy giá trị: dfs.getNaN().

↳ LocalInfinity là chuỗi đại diện cho vô cực, khi một phép tính dẫn đến kết quả lớn vô hạn. Phương thức lấy giá trị: dfs.getInfinity().

Các chuỗi có thể được phân tích (parsed) thành số bởi một đối tượng của lớp này được xác định theo ngữ pháp của biểu thức chính quy (regular-expression grammar) dưới đây:

↳ NonAsciiDigit: Đại diện cho một ký tự không phải ASCII, nhưng vẫn là một chữ số. Nếu Character.isDigit(c) trả về true cho một ký tự c, thì ký tự đó được coi là một chữ số hợp lệ trong ngữ cảnh này.

↳ Non0Digit: Đại diện cho bất kỳ chữ số nào từ 1 đến chữ số lớn nhất trong hệ cơ số hiện tại (radix) hoặc một chữ số không phải ASCII.

↳ Digit: Đại diện cho bất kỳ chữ số nào từ 0 đến chữ số lớn nhất trong hệ cơ số hoặc một chữ số không phải ASCII.

↳ GroupedNumeral: Đại diện cho một con số có dấu phân tách nhóm, như dấu phẩy trong "1,000". Nó bắt đầu với một chữ số khác 0, sau đó là một đến hai chữ số nữa, rồi các nhóm ba chữ số được phân cách bởi dấu phân tách nhóm của địa phương.

↳ Numeral: Đại diện cho một chuỗi các chữ số (như "1234") hoặc một số có dấu phân tách nhóm.

↳ Integer: Đại diện cho một số nguyên, có thể có dấu cộng hoặc trừ phía trước, hoặc được bao quanh bởi tiền tố/hậu tố dương/âm của địa phương.

↳ DecimalNumeral: Đại diện cho một số có thể có dấu thập phân theo sau bởi các chữ số.

↳ Exponent: Đại diện cho số mũ trong ký hiệu khoa học, như "e+2" trong "1.23e+2".

↳ Decimal: Đại diện cho một số thập phân, có thể bao gồm dấu, một số thập phân, và tùy chọn một số mũ.

↳ HexFloat: Đại diện cho một số dấu chấm động (floating-point) trong hệ thập lục phân, như "0x1.0p0".

↳ NonNumber: Đại diện cho các giá trị đặc biệt không phải số như NaN hoặc Infinity, bao gồm các phiên bản được định dạng theo địa phương.

↳ SignedNonNumber: Đại diện cho một giá trị không phải số, có thể được thêm dấu hoặc bao quanh bởi tiền tố/hậu tố dương/âm của địa phương.

↳ Float: Đại diện cho một số dấu chấm động, có thể là một số thập phân, một số dấu chấm động hệ thập lục phân, hoặc một giá trị không phải số có dấu.

Khai báo lớp Scanner trong Java

Để sử dụng lớp Scanner, bạn cần import gói java.io bạn cần thêm câu lệnh import vào đầu file Java của mình. Gói này cung cấp các lớp và giao diện để thực hiện các hoạt động nhập xuất (I/O) trong Java.

Cú pháp câu lệnh import:

Cú pháp

import java.util.Scanner;

Cú pháp khai báo lớp Scanner:

Cú pháp

public final class Scanner
extends Object
implements Iterator<String>, Closeable

Dưới đây là giải thích chi tiết về cú pháp khai báo này:

↳ public: Lớp Scanner có thể được truy cập từ bất kỳ đâu trong mã nguồn.

↳ final: Lớp Scanner không thể được kế thừa. Bạn không thể tạo một lớp con từ lớp Scanner.

↳ class Scanner: Đây là khai báo của một lớp có tên là Scanner.

↳ extends Object: Lớp Scanner kế thừa từ lớp cơ sở Object. Trong Java, mọi lớp đều trực tiếp hoặc gián tiếp kế thừa từ lớp Object.

↳ implements Iterator<String>: Lớp Scanner thực hiện giao diện Iterator với kiểu dữ liệu là String. Điều này cho phép bạn sử dụng Scanner trong các vòng lặp (loop) như một bộ lặp (iterator) để truy cập từng token (chuỗi) trong dữ liệu đầu vào.

↳ implements Closeable: Lớp Scanner thực hiện giao diện Closeable, cho phép bạn đóng đối tượng Scanner khi không còn sử dụng, giải phóng tài nguyên liên quan đến luồng dữ liệu mà Scanner đang đọc.

Các Constructor của lớp Scanner

Lớp Scanner cung cấp nhiều constructor khác nhau để tạo đối tượng và thiết lập nguồn dữ liệu đầu vào:

↳ Scanner(File source): Tạo một Scanner để đọc dữ liệu từ file được chỉ định.

↳ Scanner(File source, String charsetName): Tạo một Scanner để đọc dữ liệu từ file được chỉ định với bộ mã ký tự (charset) đã chỉ định.

↳ Scanner(InputStream source): Tạo một Scanner để đọc dữ liệu từ luồng đầu vào được chỉ định.

↳ Scanner(InputStream source, String charsetName): Tạo một Scanner để đọc dữ liệu từ luồng đầu vào được chỉ định với bộ mã ký tự (charset) đã chỉ định.

↳ Scanner(Path source): Tạo một Scanner để đọc dữ liệu từ file được chỉ định bằng đối tượng Path.

↳ Scanner(Path source, String charsetName): Tạo một Scanner để đọc dữ liệu từ file được chỉ định bằng đối tượng Path với bộ mã ký tự (charset) đã chỉ định.

↳ Scanner(Readable source): Tạo một Scanner để đọc dữ liệu từ bất kỳ đối tượng nào thực hiện giao diện Readable.

↳ Scanner(ReadableByteChannel source): Tạo một Scanner để đọc dữ liệu từ kênh byte đọc được (ReadableByteChannel).

↳ Scanner(ReadableByteChannel source, String charsetName): Tạo một Scanner để đọc dữ liệu từ kênh byte đọc được với bộ mã ký tự (charset) đã chỉ định.

↳ Scanner(String source): Tạo một Scanner để đọc dữ liệu từ một chuỗi ký tự.

Các phương thức của lớp Scanner

Các phương thức trong Scanner cung cấp nhiều cách để đọc và phân tích dữ liệu từ một nguồn đầu vào. Bạn có thể sử dụng chúng để tách dữ liệu thành các token, chuyển đổi các token này thành các kiểu dữ liệu tương ứng như số nguyên, số thực, hoặc chuỗi ký tự. Dưới đây là danh sách tất cả các phương thức của lớp Scanner trong Java:

↳ void close(): Đóng scanner và giải phóng tài nguyên.

↳ Pattern delimiter(): Trả về mẫu phân cách đang được sử dụng bởi scanner.

↳ String findInLine(Pattern pattern): Tìm kiếm lần xuất hiện tiếp theo của mẫu đã chỉ định, bỏ qua delimiter.

↳ String findInLine(String pattern): Tìm kiếm lần xuất hiện tiếp theo của một mẫu được tạo từ chuỗi đã chỉ định, bỏ qua delimiter.

↳ String findWithinHorizon(Pattern pattern, int horizon): Tìm kiếm lần xuất hiện tiếp theo của mẫu đã chỉ định trong phạm vi nhất định.

↳ String findWithinHorizon(String pattern, int horizon): Tìm kiếm lần xuất hiện tiếp theo của một mẫu được tạo từ chuỗi đã chỉ định, bỏ qua delimiter, trong phạm vi nhất định.

↳ boolean hasNext(): Trả về true nếu còn token tiếp theo trong đầu vào.

↳ boolean hasNext(Pattern pattern): Trả về true nếu token tiếp theo khớp với mẫu đã chỉ định.

↳ boolean hasNext(String pattern): Trả về true nếu token tiếp theo khớp với mẫu được tạo từ chuỗi đã chỉ định.

↳ boolean hasNextBigDecimal(): Trả về true nếu token tiếp theo có thể được phân tích thành BigDecimal.

↳ boolean hasNextBigInteger(): Trả về true nếu token tiếp theo có thể được phân tích thành BigInteger trong cơ số mặc định.

↳ boolean hasNextBigInteger(int radix): Trả về true nếu token tiếp theo có thể được phân tích thành BigInteger trong cơ số đã chỉ định.

↳ boolean hasNextBoolean(): Trả về true nếu token tiếp theo có thể được phân tích thành giá trị boolean.

↳ boolean hasNextByte(): Trả về true nếu token tiếp theo có thể được phân tích thành giá trị byte trong cơ số mặc định.

↳ boolean hasNextByte(int radix): Trả về true nếu token tiếp theo có thể được phân tích thành giá trị byte trong cơ số đã chỉ định.

↳ boolean hasNextDouble(): Trả về true nếu token tiếp theo có thể được phân tích thành số thực dấu chấm động kép.

↳ boolean hasNextFloat(): Trả về true nếu token tiếp theo có thể được phân tích thành số thực dấu chấm động đơn.

↳ boolean hasNextInt(): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên trong cơ số mặc định.

↳ boolean hasNextInt(int radix): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên trong cơ số đã chỉ định.

↳ boolean hasNextLine(): Trả về true nếu còn dòng tiếp theo trong đầu vào.

↳ boolean hasNextLong(): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên dài trong cơ số mặc định.

↳ boolean hasNextLong(int radix): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên dài trong cơ số đã chỉ định.

↳ boolean hasNextShort(): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên ngắn trong cơ số mặc định.

↳ boolean hasNextShort(int radix): Trả về true nếu token tiếp theo có thể được phân tích thành số nguyên ngắn trong cơ số đã chỉ định.

↳ IOException ioException(): Trả về ngoại lệ IOException cuối cùng được ném bởi nguồn dữ liệu của Scanner.

↳ Locale locale(): Trả về locale của Scanner.

↳ MatchResult match(): Trả về kết quả khớp của phép tìm kiếm gần nhất được thực hiện bởi Scanner.

↳ String next(): Trả về token tiếp theo.

↳ String next(Pattern pattern): Trả về token tiếp theo nếu khớp với mẫu đã chỉ định.

↳ String next(String pattern): Trả về token tiếp theo nếu khớp với mẫu được tạo từ chuỗi đã chỉ định.

↳ BigDecimal nextBigDecimal(): Phân tích token tiếp theo thành số thập phân lớn.

↳ BigInteger nextBigInteger(): Đọc token tiếp theo dưới dạng số nguyên lớn.

↳ BigInteger nextBigInteger(int radix): Đọc token tiếp theo dưới dạng số nguyên lớn trong cơ số đã chỉ định.

↳ boolean nextBoolean(): Đọc token tiếp theo dưới dạng giá trị boolean.

↳ byte nextByte(): Đọc token tiếp theo dưới dạng giá trị byte.

↳ byte nextByte(int radix): Đọc token tiếp theo dưới dạng giá trị byte trong cơ số đã chỉ định.

↳ double nextDouble(): Đọc token tiếp theo dưới dạng số thực dấu chấm động kép.

↳ float nextFloat(): Đọc token tiếp theo dưới dạng số thực dấu chấm động đơn.

↳ int nextInt(): Đọc token tiếp theo dưới dạng số nguyên.

↳ int nextInt(int radix): Đọc token tiếp theo dưới dạng số nguyên trong cơ số đã chỉ định.

↳ long nextLong(): Đọc token tiếp theo dưới dạng số nguyên dài.

↳ long nextLong(int radix): Đọc token tiếp theo dưới dạng số nguyên dài trong cơ số đã chỉ định.

↳ short nextShort(): Đọc token tiếp theo dưới dạng số nguyên ngắn.

↳ short nextShort(int radix): Đọc token tiếp theo dưới dạng số nguyên ngắn trong cơ số đã chỉ định.

↳ String nextLine(): Đọc toàn bộ dòng tiếp theo.

↳ void remove(): Phương thức này không được hỗ trợ trong Scanner.

↳ Scanner reset(): Đặt lại trạng thái của Scanner.

↳ Scanner skip(Pattern pattern): Bỏ qua các ký tự khớp với mẫu đã chỉ định.

↳ Scanner skip(String pattern): Bỏ qua các ký tự khớp với chuỗi đã chỉ định.

↳ String toString(): Trả về chuỗi biểu diễn của Scanner.

↳ Scanner useDelimiter(Pattern pattern): Thiết lập mẫu phân cách mới.

↳ Scanner useDelimiter(String pattern): Thiết lập mẫu phân cách mới từ chuỗi đã chỉ định.

↳ Scanner useLocale(Locale locale): Thiết lập locale cho Scanner.

↳ Scanner useRadix(int radix): Thiết lập cơ số mặc định cho Scanner.

Dưới đây là một số ví dụ cơ bản về cách sử dụng lớp Scanner trong Java để đọc dữ liệu từ các nguồn khác nhau và phân tích chúng.

Ví dụ 1: Đọc dữ liệu từ bàn phím

Ví dụ: Example.java

import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Đọc một chuỗi
        System.out.print("Nhập tên của bạn: ");
        String name = scanner.nextLine();
        System.out.println("Chào, " + name + "!");
        
        // Đọc một số nguyên
        System.out.print("Nhập tuổi của bạn: ");
        int age = scanner.nextInt();
        System.out.println("Bạn " + age + " tuổi.");
        
        scanner.close();
    }
}

Kết quả của chương trình là

Nhập tên của bạn: hello
Chào, hello!
Nhập tuổi của bạn: 12
Bạn 12 tuổi.

Ví dụ 2: Đọc dữ liệu từ tập tin

Giả sử chúng ta có một file văn bản tên là Example.txt chứa nội dung là

Tập tin: Example.txt

Ví dụ 2: Đọc Dữ liệu Từ Tập Tin.

Ví dụ: Example.java

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

public class Example {
    public static void main(String[] args) {
        try {
            File file = new File("Example.txt");
            Scanner scanner = new Scanner(file);
            
            // Đọc từng dòng trong tập tin
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
            
            scanner.close();
        } catch (FileNotFoundException e) {
            System.out.println("Tập tin không tìm thấy.");
        }
    }
}

Kết quả của chương trình là

Ví dụ 2: Đọc Dữ liệu Từ Tập Tin.

Ví dụ 3: Đọc dữ liệu từ chuỗi

Ví dụ: Example.java

import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        String data = "123 45.67 Hello";
        Scanner scanner = new Scanner(data);
        
        // Đọc một số nguyên
        int number = scanner.nextInt();
        System.out.println("Số nguyên: " + number);
        
        // Đọc một số thực
        double decimal = scanner.nextDouble();
        System.out.println("Số thực: " + decimal);
        
        // Đọc một chuỗi
        String text = scanner.next();
        System.out.println("Chuỗi: " + text);
        
        scanner.close();
    }
}

Kết quả của chương trình là

Số nguyên: 123
Số thực: 45.67
Chuỗi: Hello

Ví dụ 4: Đọc dữ liệu với Delimiter

Ví dụ: Example.java

import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        String data = "Java,Python,C++";
        Scanner scanner = new Scanner(data);
        scanner.useDelimiter(","); // Thiết lập dấu phân cách
        
        // Đọc từng token được phân cách bởi dấu phẩy
        while (scanner.hasNext()) {
            String token = scanner.next();
            System.out.println("Ngôn ngữ lập trình: " + token);
        }
        
        scanner.close();
    }
}

Kết quả của chương trình là

Ngôn ngữ lập trình: Java
Ngôn ngữ lập trình: Python
Ngôn ngữ lập trình: C++

Ví dụ 5: Đọc dữ liệu và tính tổng

Ví dụ: Example.java

import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        String data = "10 20 30 40 50";
        Scanner scanner = new Scanner(data);
        
        int sum = 0;
        while (scanner.hasNextInt()) {
            sum += scanner.nextInt();
        }
        
        System.out.println("Tổng: " + sum);
        
        scanner.close();
    }
}

Kết quả của chương trình là

Tổng: 150

Các ví dụ này minh họa cách sử dụng lớp Scanner để đọc và phân tích dữ liệu từ nhiều nguồn khác nhau, bao gồm bàn phím, tập tin, chuỗi và sử dụng các dấu phân cách khác nhau.

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”