- 1 1. Những gì bạn sẽ học trong bài viết này (Kết luận nhanh)
- 2 2. “Đầu vào chuẩn” trong Java là gì?
- 3 3. Các cách thường dùng để đọc đầu vào chuẩn trong Java (Tổng quan)
- 4 4. Đọc Dữ Liệu Đầu Vào Tiêu Chuẩn với Scanner (Từ Cơ Bản Đến Mẹo Thực Tế)
- 4.1 4.1 Đọc Một Dòng Văn Bản (nextLine)
- 4.2 4.2 Đọc Số (nextInt, nextLong, v.v.)
- 4.3 4.3 Cạm Bẫy Nổi Tiếng: nextInt() Theo Sau Bởi nextLine()
- 4.4 4.4 Cách khắc phục vấn đề nextLine()
- 4.5 4.5 Đọc các giá trị cách nhau bằng dấu cách
- 4.6 4.6 Đọc cho đến khi hết đầu vào (EOF)
- 4.7 4.7 Tại sao Scanner chậm với đầu vào lớn
- 4.8 4.8 Khi nào nên dùng Scanner
- 4.9 4.9 Những gì sẽ tới tiếp theo
- 5 5. Đọc đầu vào chuẩn với BufferedReader (Nhanh và Đáng tin cậy)
- 5.1 5.1 Ví dụ tối thiểu: Đọc một dòng duy nhất
- 5.2 5.2 Đọc nhiều dòng cho đến EOF
- 5.3 5.3 Chuyển đổi chuỗi thành số
- 5.4 5.4 Xử lý đầu vào cách nhau bằng dấu cách
- 5.5 5.5 Đọc các Dòng Có Các Giá Trị Cách Nhau Bằng Khoảng Trắng Lặp Lại
- 5.6 5.6 Xử lý IOException
- 5.7 5.7 Tại sao BufferedReader nhanh
- 5.8 5.8 Scanner vs BufferedReader Tóm tắt
- 5.9 5.9 Những gì sẽ đến tiếp
- 6 6. Nhập nhanh cho lập trình thi đấu (FastScanner)
- 7 7. Cách Chọn Phương Pháp Đầu Vào Phù Hợp (Hướng Dẫn Quyết Định Nhanh)
- 8 8. Các Lỗi Phổ Biến và Khắc Phục Sự Cố
- 8.1 8.1 Đầu Vào Có Vẻ Bị Bỏ Qua Hoặc Thiếu
- 8.2 8.2 Chương Trình Chờ Vô Tận Để Nhập
- 8.3 8.3 NumberFormatException Khi Phân Tích Số
- 8.4 8.4 split() Tạo Ra Kết Quả Không Mong Đợi
- 8.5 8.5 Chương Trình Quá Chậm (Vượt Giới Hạn Thời Gian)
- 8.6 8.6 Nhầm Lẫn Về Các Ngoại Lệ Được Kiểm Tra (IOException)
- 8.7 8.7 Vấn Đề Mã Hóa Ký Tự
- 8.8 8.8 Danh Sách Kiểm Tra Xử Lý Nhanh
- 8.9 8.9 Những Gì Sắp Đến
- 9 9. Các Câu Hỏi Thường Gặp (FAQ)
- 9.1 9.1 Cái Nào Tốt Hơn: Scanner hay BufferedReader?
- 9.2 9.2 Liệu Scanner Có Thật Sự Chậm Không?
- 9.3 9.3 Tại Sao nextLine() Trả Về Chuỗi Rỗng?
- 9.4 9.4 Nên Dùng split() Hay StringTokenizer?
- 9.5 9.5 Làm Thế Nào Để Đọc Đầu Vào Cho Đến EOF?
- 9.6 9.6 Tôi Có Thể Dùng FastScanner Trong Ứng Dụng Thực Tế Không?
- 9.7 9.7 Tôi Luôn Phải Xử Lý Ngoại Lệ Không?
- 9.8 9.8 Đầu Vào Nhanh, Nhưng Đầu Ra Chậm. Tôi Nên Làm Gì?
- 9.9 9.9 Những Gì Sắp Đến
- 10 10. Tổng Kết Cuối Cùng
1. Những gì bạn sẽ học trong bài viết này (Kết luận nhanh)
Có một vài cách để xử lý đầu vào chuẩn trong Java, nhưng ý tưởng chính rất đơn giản:
Chọn phương pháp nhập dựa trên mục đích của bạn.
Bạn không cần giải pháp nhanh nhất hay phức tạp nhất ngay từ đầu.
Bài viết này giải thích đầu vào chuẩn của Java từng bước, để bạn có thể hiểu rõ khi nào và tại sao sử dụng mỗi cách tiếp cận.
Chúng ta sẽ đề cập đến nhập liệu trong Java ở ba mức độ thực tiễn:
- Dành cho người mới bắt đầu và các chương trình nhỏ :
Scanner - Dành cho đầu vào lớn và hiệu năng ổn định :
BufferedReader - Dành cho lập trình thi đấu và đầu vào rất lớn :
FastScanner
1.1 Bạn nên dùng cái nào? (Scanner vs BufferedReader vs Fast Input)
Nếu bạn đang vội, phần này một mình đã đủ để quyết định.
1) Học Java hoặc xử lý đầu vào nhỏ → Scanner
Trường hợp sử dụng điển hình: wp:list /wp:list
- Hướng dẫn Java
- Bài tập trường học
- Công cụ dòng lệnh nhỏ
Ưu điểm: wp:list /wp:list
- Rất dễ viết
- Dễ đọc và hiểu
Nhược điểm: wp:list /wp:list
- Chậm khi kích thước đầu vào trở nên lớn
Sử dụng Scanner khi:
- Kích thước đầu vào nhỏ
- Độ dễ đọc quan trọng hơn hiệu năng
2) Đầu vào lớn hoặc hiệu năng ổn định → BufferedReader
Trường hợp sử dụng điển hình: wp:list /wp:list
- Ứng dụng thực tế
- Xử lý hàng loạt
- Bài tập lập trình với nhiều dòng đầu vào
Ưu điểm: wp:list /wp:list
- Nhanh hơn nhiều so với Scanner
- Hành vi dự đoán được và ổn định
Nhược điểm: wp:list /wp:list
- Bạn phải tự phân tích số và token
BufferedReader là lựa chọn tốt khi:
- Kích thước đầu vào lớn
- Hiệu năng quan trọng
- Bạn muốn kiểm soát toàn bộ quá trình xử lý đầu vào
3) Lập trình thi đấu hoặc đầu vào khổng lồ → FastScanner
Trường hợp sử dụng điển hình: wp:list /wp:list
- Lập trình thi đấu
- Các vấn đề có thời gian giới hạn rất chặt chẽ
Ưu điểm: wp:list /wp:list
- Rất nhanh
Nhược điểm: wp:list /wp:list
- Khó đọc
- Khó gỡ lỗi
- Không phù hợp cho phát triển ứng dụng thông thường
FastScanner phù hợp khi:
- Kích thước đầu vào khổng lồ
- Scanner hoặc BufferedReader gây lỗi thời gian
1.2 Ai là đối tượng của bài viết này
Bài viết này được thiết kế cho một dải rộng các nhà phát triển Java.
Người mới bắt đầu
- Muốn hiểu đầu vào chuẩn là gì
- Cần giải thích rõ ràng về
Scanner - Thường bị nhầm lẫn bởi các lỗi liên quan đến đầu vào
Người học trung cấp
- Đã biết Scanner
- Muốn hiểu tại sao nó chậm
- Cần cách đáng tin cậy để xử lý đầu vào lớn
Lập trình viên thi đấu
- Cần kỹ thuật nhập nhanh
- Muốn mẫu thực tế cho các cuộc thi
1.3 Bạn sẽ có thể làm gì sau khi đọc
Khi kết thúc bài viết, bạn sẽ có thể:
- Hiểu thực sự gì là đầu vào chuẩn của Java (
System.in) - Chọn phương pháp nhập đúng cho mỗi tình huống
- Sử dụng
Scannermà không gặp các bẫy thường gặp - Đọc đầu vào lớn hiệu quả với
BufferedReader - Xử lý đầu vào lập trình thi đấu bằng các kỹ thuật nhanh
- Gỡ lỗi các vấn đề thường gặp liên quan đến đầu vào một cách tự tin
1.4 Điều gì sẽ đến tiếp theo
Trong phần tiếp theo, chúng ta sẽ giải thích đầu vào chuẩn thực sự là gì trong Java, bắt đầu từ System.in.
Hiểu nền tảng này sẽ làm cho sự khác biệt giữa Scanner, BufferedReader và các phương pháp nhập nhanh trở nên rõ ràng hơn.
2. “Đầu vào chuẩn” trong Java là gì?
Trước khi học cách sử dụng Scanner hoặc BufferedReader, quan trọng là phải hiểu đầu vào chuẩn thực sự là gì trong Java.
Nhiều sự nhầm lẫn liên quan đến nhập liệu xuất phát từ việc bỏ qua khái niệm cơ bản này.
2.1 Vai trò của đầu vào chuẩn (System.in)
Trong Java, đầu vào chuẩn là nguồn dữ liệu mặc định mà một chương trình đọc khi nó khởi chạy.
Nguồn này được biểu diễn bằng đối tượng sau:
System.in
Các điểm chính về System.in:
- Nó đại diện cho một luồng đầu vào được hệ điều hành cung cấp
- Kiểu của nó là
InputStream - Java tự nó không quyết định nguồn đầu vào đến từ đâu
Nói cách khác, System.in chỉ đơn giản là một luồng dữ liệu, không phải một “API bàn phím”.
2.2 Đầu vào chuẩn không luôn luôn là bàn phím
Một hiểu lầm rất phổ biến là:
Đầu vào chuẩn = đầu vào bàn phím
Điều này chỉ đúng một phần.
Khi bạn chạy một chương trình như sau:
java Main
đầu vào chuẩn được kết nối với bàn phím.
Tuy nhiên, bạn cũng có thể chạy nó như sau:
java Main < input.txt
Trong trường hợp này:
- Đầu vào đến từ một tệp
- Không phải từ bàn phím
- Nhưng Java vẫn đọc nó qua
System.in
Từ quan điểm của chương trình, không có sự khác biệt nào.
Đây là lý do tại sao đầu vào chuẩn thường được mô tả là:
“Bất kỳ dữ liệu nào được đưa vào chương trình khi chạy”
2.3 Tại sao System.in khó sử dụng trực tiếp
Mặc dù System.in mạnh mẽ, nhưng nó không thuận tiện để sử dụng trực tiếp.
Lý do rất đơn giản:
System.inđọc byte thôNó không hiểu: wp:list /wp:list
- Dòng
- Số
- Khoảng trắng
- Mã hoá văn bản
Ví dụ:
InputStream in = System.in;
Ở mức này, bạn chỉ làm việc với các byte, không phải các giá trị có ý nghĩa.
Đó là lý do Java cung cấp các lớp bao bọc để chuyển đổi đầu vào thô thành dữ liệu có thể sử dụng.
2.4 Các lớp xử lý đầu vào trong Java
Quá trình xử lý đầu vào của Java có thể hiểu như một cấu trúc lớp.
[ Input source (keyboard, file, pipe) ]
↓
System.in (InputStream)
↓
Input helper classes
├ Scanner
├ BufferedReader
└ Fast input implementations
Mỗi lớp có một trách nhiệm rõ ràng:
System.in wp:list /wp:list
- Luồng byte cấp thấp
Scanner wp:list /wp:list
Nhập dựa trên token dễ dàng (chậm nhưng đơn giản)
BufferedReader wp:list /wp:list
Nhập dựa trên dòng nhanh
FastScanner wp:list /wp:list
Nhập số tập trung vào hiệu năng
Hiểu cấu trúc này giải thích tại sao có nhiều phương pháp nhập liệu.
2.5 Tại sao Java có nhiều phương pháp nhập liệu
Java được sử dụng trong nhiều bối cảnh khác nhau:
- Giáo dục
- Hệ thống doanh nghiệp
- Công cụ dòng lệnh
- Lập trình thi đấu
Mỗi bối cảnh có các ưu tiên khác nhau:
- Dễ sử dụng
- Độ dễ đọc
- Hiệu năng
- Ổn định
Vì vậy, Java không ép buộc một “phương pháp nhập tốt nhất” duy nhất.
Thay vào đó, nó cung cấp nhiều công cụ cho các nhu cầu khác nhau.
2.6 Những gì sẽ tới tiếp theo
Trong phần tiếp theo, chúng ta sẽ bắt đầu sử dụng Scanner, cách thân thiện nhất cho người mới bắt đầu để đọc đầu vào chuẩn.
Bạn sẽ học:
- Cách đọc chuỗi
- Cách đọc số
- Những bẫy thường gặp khiến người mới bối rối
Điều này sẽ chuẩn bị cho bạn việc hiểu các phương pháp nhập nhanh hơn sau này.
3. Các cách thường dùng để đọc đầu vào chuẩn trong Java (Tổng quan)
Bây giờ bạn đã hiểu đầu vào chuẩn là gì, hãy xem ba cách chính để đọc nó trong Java.
Trước khi đi sâu vào chi tiết mã, quan trọng là nhìn thấy bức tranh tổng thể.
Mỗi phương pháp tồn tại vì một lý do, và chọn đúng sẽ tiết kiệm thời gian và giảm bớt phiền toái.
3.1 Scanner: Lựa chọn dễ nhất và thân thiện nhất cho người mới bắt đầu
Scanner thường là lớp nhập liệu đầu tiên mà người mới học Java tiếp cận.
Scanner sc = new Scanner(System.in);
Với một dòng duy nhất này, bạn có thể dễ dàng đọc:
- Chuỗi
- Số nguyên
- Số thực
Các tính năng chính của Scanner
Pros wp:list /wp:list
- Rất dễ viết và hiểu
- Đọc giá trị trực tiếp dưới dạng kiểu Java
- Rộng rãi trong các hướng dẫn và sách giáo trình
Cons wp:list /wp:list
Chậm đối với đầu vào lớn
- Có một số hành vi khó hiểu mà người mới thường gặp
Khi nào Scanner là lựa chọn tốt
- Học Java
- Chương trình nhỏ
- Yêu cầu nhập liệu đơn giản
Nếu mục tiêu của bạn là hiểu cú pháp và logic Java, Scanner là một điểm khởi đầu tuyệt vời.
3.2 BufferedReader: Nhanh và Đáng Tin Cậy cho Các Ứng Dụng Thực Tế
BufferedReader là lựa chọn tiêu chuẩn khi kích thước đầu vào hoặc hiệu năng trở nên quan trọng.
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
Không giống như Scanner, BufferedReader tập trung vào đọc toàn bộ dòng một cách hiệu quả.
Các tính năng chính của BufferedReader
Ưu điểm wp:list /wp:list
- Nhanh hơn nhiều so với Scanner
- Hành vi dự đoán được và ổn định
- Thích hợp cho lượng dữ liệu lớn
Nhược điểm wp:list /wp:list
Cần phải phân tích thủ công
- Mã hơi phức tạp hơn
Khi nào BufferedReader là lựa chọn tốt
- Ứng dụng thực tế
- Xử lý hàng loạt
- Các bài toán lập trình có nhiều dòng đầu vào
BufferedReader thường được coi là lựa chọn chuyên nghiệp mặc định.
3.3 Đầu vào Nhanh (FastScanner): Dành cho Lập Trình Thi Đấu
Trong lập trình thi đấu, ngay cả BufferedReader cũng có thể quá chậm.
Để giải quyết vấn đề này, nhiều nhà phát triển sử dụng các lớp đầu vào nhanh tùy chỉnh, thường được gọi là FastScanner.
Đặc điểm của đầu vào nhanh
- Đọc byte thô bằng bộ đệm
- Tránh tạo đối tượng không cần thiết
- Chuyển đổi số một cách thủ công
Ưu và nhược điểm
Ưu điểm wp:list /wp:list
- Siêu nhanh
- Lý tưởng cho kích thước đầu vào khổng lồ
Nhược điểm wp:list /wp:list
Khó đọc
- Khó gỡ lỗi
- Không phù hợp cho các ứng dụng thông thường
Khi nào nên dùng đầu vào nhanh
- Lập trình thi đấu
- Giới hạn thời gian rất chặt chẽ
- Kích thước đầu vào khổng lồ
Trong phát triển phần mềm bình thường, đầu vào nhanh hiếm khi cần thiết.
3.4 Bảng So Sánh Nhanh
| Method | Ease of Use | Speed | Typical Use |
|---|---|---|---|
| Scanner | Very high | Low | Learning, small programs |
| BufferedReader | Medium | High | Real applications |
| FastScanner | Low | Very high | Competitive programming |
Bảng này một mình đã có thể giúp bạn quyết định công cụ nào nên sử dụng.
3.5 Cách Quyết Định Khi Bạn Không Chắc
Nếu bạn không chắc phương pháp nào nên chọn, hãy theo quy tắc này:
Đây có phải là để học tập hoặc một chương trình nhỏ không? wp:list /wp:list
Có → Scanner 2. Đầu vào có lớn hoặc hiệu năng quan trọng không? wp:list /wp:list
Có → BufferedReader 3. Đây có phải là một bài toán lập trình thi đấu không? wp:list /wp:list
Có → FastScanner
Hầu hết các trường hợp thực tế đều được giải quyết hoàn hảo bằng BufferedReader.
3.6 Những Gì Sắp Đến
Trong phần tiếp theo, chúng ta sẽ tập trung vào cách sử dụng Scanner một cách đúng đắn.
Bạn sẽ học:
- Cách đọc chuỗi
- Cách đọc số
- Những lỗi thường gặp khi dùng Scanner và cách tránh chúng
Điều này sẽ giúp bạn viết mã xử lý đầu vào chính xác ngay từ đầu.
4. Đọc Dữ Liệu Đầu Vào Tiêu Chuẩn với Scanner (Từ Cơ Bản Đến Mẹo Thực Tế)
Trong phần này, chúng ta sẽ tập trung vào Scanner, cách thân thiện nhất cho người mới bắt đầu để đọc dữ liệu đầu vào tiêu chuẩn trong Java.
Chúng tôi không chỉ sẽ chỉ cách sử dụng mà còn giải thích tại sao một số vấn đề xảy ra, để bạn có thể tránh các lỗi phổ biến.
4.1 Đọc Một Dòng Văn Bản (nextLine)
Trường hợp sử dụng đơn giản nhất là đọc một dòng văn bản đầy đủ.
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
System.out.println(line);
nextLine()đọc mọi thứ cho tới ký tự xuống dòng- Các khoảng trắng trong dòng được giữ nguyên
Ví dụ đầu vào:
Hello Java World
Kết quả:
Hello Java World
Phương pháp này lý tưởng khi bạn muốn đọc câu hoặc văn bản tự do.
4.2 Đọc Số (nextInt, nextLong, v.v.)
Một trong những ưu điểm lớn nhất của Scanner là nó có thể đọc số trực tiếp.
int n = sc.nextInt();
long x = sc.nextLong();
double d = sc.nextDouble();
Nếu đầu vào là:
42
Giá trị 42 sẽ được lưu dưới dạng int mà không cần chuyển đổi thủ công.
Tại sao điều này tiện lợi
- Không cần
Integer.parseInt - Giảm mã lặp lại
- Dễ hiểu cho người mới bắt đầu
4.3 Cạm Bẫy Nổi Tiếng: nextInt() Theo Sau Bởi nextLine()
Đây là một trong những lỗi phổ biến nhất liên quan đến Scanner.
int n = sc.nextInt();
String s = sc.nextLine(); // often becomes empty
Tại sao lại xảy ra điều này?
nextInt()chỉ đọc số- Ký tự xuống dòng (
\n) còn lại trong bộ đệm đầu vào nextLine()đọc ký tự xuống dòng còn lại đó
Kết quả, s trở thành một chuỗi rỗng.
4.4 Cách khắc phục vấn đề nextLine()
Giải pháp tiêu chuẩn là tiêu thụ ký tự xuống dòng còn lại.
int n = sc.nextInt();
sc.nextLine(); // consume newline
String s = sc.nextLine();
Mẫu này rất phổ biến và đáng ghi nhớ.
Ngoài ra, bạn có thể tránh hoàn toàn việc trộn nextInt() và nextLine() bằng cách đọc mọi thứ dưới dạng chuỗi.
4.5 Đọc các giá trị cách nhau bằng dấu cách
Scanner tự động coi dấu cách và ký tự xuống dòng là dấu phân tách.
Đầu vào:
10 20 30
Mã:
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
Cách này hoạt động mà không cần bất kỳ logic bổ sung nào.
Chi phí ẩn
- Scanner nội bộ sử dụng biểu thức chính quy
- Điều này làm cho nó linh hoạt, nhưng chậm hơn đối với đầu vào lớn
4.6 Đọc cho đến khi hết đầu vào (EOF)
Đôi khi bạn cần đọc đầu vào cho đến khi không còn dữ liệu.
while (sc.hasNext()) {
int value = sc.nextInt();
System.out.println(value);
}
Hoặc đối với đầu vào dựa trên dòng:
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(line);
}
Mẫu này hữu ích cho việc đọc file và các hệ thống chấm bài trực tuyến.
4.7 Tại sao Scanner chậm với đầu vào lớn
Scanner chậm không phải vì nó “xấu”, mà vì nó được thiết kế cho sự an toàn và linh hoạt.
Các nguyên nhân bao gồm:
- Phân tích biểu thức chính quy
- Chuyển đổi kiểu tự động
- Kiểm tra đầu vào kỹ lưỡng
Đối với đầu vào nhỏ, chi phí này không đáng kể.
Đối với đầu vào lớn, nó trở thành vấn đề hiệu năng nghiêm trọng.
4.8 Khi nào nên dùng Scanner
Scanner là lựa chọn tốt khi:
- Bạn đang học Java
- Kích thước đầu vào nhỏ
- Độ dễ đọc của mã quan trọng hơn tốc độ
Nếu hiệu năng trở thành mối quan ngại, đã đến lúc chuyển sang BufferedReader.
4.9 Những gì sẽ tới tiếp theo
Trong phần tiếp theo, chúng ta sẽ giới thiệu BufferedReader, giải pháp tiêu chuẩn cho việc nhập nhanh và đáng tin cậy.
Bạn sẽ học:
- Nhập dựa trên dòng
- Phân tích số thủ công
- Xử lý hiệu quả đầu vào lớn
5. Đọc đầu vào chuẩn với BufferedReader (Nhanh và Đáng tin cậy)
Trong phần này, chúng ta chuyển sang BufferedReader, được sử dụng rộng rãi trong các chương trình Java thực tế và các thử thách lập trình.
So với Scanner, nó yêu cầu một chút mã nhiều hơn, nhưng mang lại hiệu năng và kiểm soát tốt hơn rất nhiều.
5.1 Ví dụ tối thiểu: Đọc một dòng duy nhất
Cấu hình cơ bản cho BufferedReader như sau:
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
String line = br.readLine();
System.out.println(line);
readLine()đọc đầu vào dòng theo dòng- Nó trả về
nullkhi không còn đầu vào (EOF)
Hành vi rõ ràng này giúp việc gỡ lỗi dễ dàng hơn nhiều so với Scanner.
5.2 Đọc nhiều dòng cho đến EOF
Khi số lượng dòng đầu vào không biết trước, mẫu này là tiêu chuẩn:
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
Tại sao mẫu này hoạt động tốt
nullchỉ rõ ràng kết thúc đầu vào- Không cần kiểm tra thêm
- Rất phổ biến trong việc đọc file và các hệ thống chấm bài trực tuyến
5.3 Chuyển đổi chuỗi thành số
Khác với Scanner, BufferedReader luôn đọc chuỗi, vì vậy việc chuyển đổi số phải thực hiện thủ công.
int n = Integer.parseInt(br.readLine());
long x = Long.parseLong(br.readLine());
double d = Double.parseDouble(br.readLine());
Lưu ý quan trọng
- Nếu đầu vào không phải là số hợp lệ, sẽ xảy ra
NumberFormatException - Điều này buộc bạn phải chính xác về định dạng đầu vào
Mặc dù ban đầu có thể bất tiện, nhưng nó dẫn đến các chương trình dự đoán được hơn.
5.4 Xử lý đầu vào cách nhau bằng dấu cách
Các giá trị cách nhau bằng dấu cách rất phổ biến.
Đầu vào:
10 20 30
Phương pháp 1: Sử dụng split (Ưu tiên Độ dễ đọc)
String[] parts = br.readLine().split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
int c = Integer.parseInt(parts[2]);
- Dễ hiểu
- Phù hợp với đầu vào nhỏ đến trung bình
- Chậm hơn một chút do sử dụng biểu thức chính quy
Phương pháp 2: Sử dụng StringTokenizer (Ưu tiên Hiệu năng)
StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
- Nhanh hơn
split - Rất phổ biến trong lập trình thi đấu
- Vẫn hoàn toàn hợp lệ trong Java hiện đại
5.5 Đọc các Dòng Có Các Giá Trị Cách Nhau Bằng Khoảng Trắng Lặp Lại
Ví dụ đầu vào:
3
10 20
30 40
50 60
Mã:
int n = Integer.parseInt(br.readLine());
for (int i = 0; i < n; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
System.out.println(x + y);
}
Mẫu này xuất hiện thường xuyên trong:
- Các bài kiểm tra lập trình
- Các bài toán thuật toán
- Các công cụ xử lý dữ liệu

5.6 Xử lý IOException
Các phương thức của BufferedReader ném các ngoại lệ đã kiểm tra, vì vậy bạn phải xử lý chúng.
Cách tiếp cận đơn giản (học / thi đấu):
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
}
Cách tiếp cận kiểu sản xuất:
try {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
} catch (IOException e) {
e.printStackTrace();
}
Đối với người mới bắt đầu, cách tiếp cận đầu tiên thường là đủ.
5.7 Tại sao BufferedReader nhanh
BufferedReader nhanh vì:
- Nó đọc dữ liệu theo các khối lớn (đệm)
- Tránh việc phân tích biểu thức chính quy
- Cho phép bạn kiểm soát cách dữ liệu được diễn giải
Tóm lại, nó tách biệt:
- Đọc
- Phân tích
Thiết kế này là điểm khác biệt chính so với Scanner.
5.8 Scanner vs BufferedReader Tóm tắt
| Feature | Scanner | BufferedReader |
|---|---|---|
| Ease of use | Very high | Medium |
| Speed | Low | High |
| Line-based input | Weak | Excellent |
| Numeric parsing | Automatic | Manual |
| Real-world usage | Limited | Very common |
5.9 Những gì sẽ đến tiếp
Trong phần tiếp theo, chúng ta sẽ khám phá các kỹ thuật nhập nhanh được sử dụng trong lập trình thi đấu.
Bạn sẽ học:
- Tại sao ngay cả BufferedReader cũng có thể quá chậm
- Cách hoạt động của nhập nhanh về mặt khái niệm
- Một triển khai FastScanner thực tế
6. Nhập nhanh cho lập trình thi đấu (FastScanner)
Trong lập trình thi đấu, kích thước đầu vào có thể lớn đến mức ngay cả BufferedReader cũng trở thành nút thắt.
Phần này giải thích tại sao cần nhập nhanh, cách nó hoạt động, và khi nào bạn nên (và không nên) sử dụng nó.
6.1 Tại sao Scanner gây lỗi thời gian giới hạn
Các đề bài lập trình thi đấu thường có:
- Hàng trăm ngàn hoặc hàng triệu số
- Giới hạn thời gian nghiêm ngặt (1–2 giây)
- Môi trường thực thi hạn chế
Scanner thực hiện nhiều kiểm tra nội bộ:
- Phân tích biểu thức chính quy
- Chuyển đổi kiểu tự động
- Kiểm tra toàn diện
Các tính năng này hữu ích cho độ an toàn—nhưng tốn kém về hiệu năng.
Do đó, Scanner thường gây ra TLE (Time Limit Exceeded) trong các cuộc thi.
6.2 Khi BufferedReader vẫn chưa đủ
BufferedReader nhanh hơn nhiều so với Scanner, nhưng nó vẫn:
- Tạo các đối tượng
String - Tách chuỗi thành các token
- Phân tích số từ văn bản
Khi đầu vào cực kỳ lớn, những bước này một mình có thể quá chậm.
Điều này dẫn đến một cách tiếp cận khác:
Đọc byte thô và chuyển đổi số một cách thủ công
6.3 Cách hoạt động của nhập nhanh (về mặt khái niệm)
Các kỹ thuật nhập nhanh thường tuân theo quy trình này:
- Đọc một khối byte lớn một lần
- Lưu chúng vào bộ đệm byte
- Bỏ qua các ký tự không cần thiết (khoảng trắng, xuống dòng)
- Chuyển đổi các chữ số trực tiếp thành số
- Tránh tạo các đối tượng không cần thiết
Điều này giảm thiểu:
- Phân bổ bộ nhớ
- Thu gom rác
- Chi phí CPU
6.4 Triển khai FastScanner thực tế
Dưới đây là một FastScanner tối thiểu và thực tế phù hợp cho các cuộc thi.
static class FastScanner {
private final InputStream in = System.in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
private int readByte() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
int nextInt() throws IOException {
int c;
do {
c = readByte();
} while (c <= ' ');
boolean negative = false;
if (c == '-') {
negative = true;
c = readByte();
}
int value = 0;
while (c > ' ') {
value = value * 10 + (c - '0');
c = readByte();
}
return negative ? -value : value;
}
}
Ví dụ sử dụng:
FastScanner fs = new FastScanner();
int n = fs.nextInt();
Cách tiếp cận này cực kỳ nhanh cho đầu vào số lớn.
6.5 Những Hạn Chế Quan Trọng Của FastScanner
Đầu vào nhanh không phải là giải pháp phổ quát.
Nhược điểm:
- Khó đọc và bảo trì
- Khó gỡ lỗi
- Không phù hợp cho đầu vào chứa nhiều văn bản
- Quá mức cần thiết cho hầu hết các ứng dụng
Chỉ sử dụng FastScanner khi cần thiết, thường là trong các cuộc thi.
6.6 Tóm Tắt Phương Pháp Đầu Vào
- Scanner → Học tập, đầu vào nhỏ
- BufferedReader → Ứng dụng thực tế, đầu vào lớn
- FastScanner → Chỉ dành cho lập trình thi đấu
Việc chọn công cụ đơn giản nhất đáp ứng nhu cầu hiệu suất của bạn luôn là tốt nhất.
6.7 Những Gì Sẽ Đến Tiếp Theo
Trong phần tiếp theo, chúng ta sẽ tóm tắt cách chọn phương pháp đầu vào phù hợp bằng một hướng dẫn quyết định đơn giản.
7. Cách Chọn Phương Pháp Đầu Vào Phù Hợp (Hướng Dẫn Quyết Định Nhanh)
Đến nay, bạn đã thấy nhiều cách xử lý đầu vào chuẩn trong Java.
Phần này giúp bạn quyết định nhanh chóng và tự tin phương pháp nào nên sử dụng trong các tình huống thực tế.
7.1 Dành Cho Học Tập Và Các Chương Trình Nhỏ
Khuyến nghị: Scanner
Tại sao Scanner hoạt động tốt ở đây
- Dễ đọc và viết
- Ít mã lặp lại
- Phù hợp với hầu hết các hướng dẫn dành cho người mới bắt đầu
Các tình huống điển hình:
- Học các kiến thức cơ bản về Java
- Công cụ dòng lệnh nhỏ
- Bài tập với kích thước đầu vào hạn chế
Scanner sc = new Scanner(System.in); int n = sc.nextInt();
Nếu chương trình của bạn chỉ đọc một lượng đầu vào nhỏ, Scanner hoàn toàn ổn.
7.2 Dành Cho Đầu Vào Lớn Và Hiệu Suất Ổn Định
Khuyến nghị: BufferedReader
Tại sao BufferedReader là lựa chọn chuyên nghiệp mặc định
- Nhanh và có thể dự đoán
- Hoạt động tốt với đầu vào lớn
- Dễ kiểm soát logic phân tích đầu vào
Các tình huống điển hình:
- Ứng dụng thực tế
- Công việc hàng loạt
- Các bài toán lập trình với nhiều dòng đầu vào
BufferedReader br = new BufferedReader( new InputStreamReader(System.in) );
Nếu bạn không chắc chắn và hiệu suất quan trọng, BufferedReader là lựa chọn an toàn nhất.
7.3 Dành Cho Lập Trình Thi Đấu Và Kích Thước Đầu Vào Cực Lớn
Khuyến nghị: FastScanner
Tại sao FastScanner tồn tại
- Được thiết kế để tránh TLE
- Xử lý đầu vào số lớn hiệu quả
Các tình huống điển hình:
- Các cuộc thi lập trình thi đấu
- Các bài toán với giới hạn thời gian rất nghiêm ngặt
- Bộ dữ liệu cực lớn
FastScanner fs = new FastScanner(); int n = fs.nextInt();
Ngoài lập trình thi đấu, cách tiếp cận này thường không cần thiết.
7.4 Luồng Quyết Định Đơn Giản
Khi còn nghi ngờ, hãy theo logic này:
- Đây có phải là học tập hoặc kịch bản nhỏ không? → Sử dụng
Scanner - Đầu vào có lớn hoặc hiệu suất có quan trọng không? → Sử dụng
BufferedReader - Đây có phải là bài toán thi đấu với giới hạn thời gian nghiêm ngặt không? → Sử dụng
FastScanner
Trong thực tế, hầu hết các chương trình Java rơi vào bước 2.
7.5 Những Hiểu Lầm Phổ Biến
“Phương pháp nhanh nhất luôn là tốt nhất”
Điều này là sai.
- Đầu vào nhanh hơn làm giảm khả năng đọc
- Mã phức tạp tăng rủi ro lỗi
- Bảo trì trở nên khó khăn hơn
Always prefer the phương pháp đơn giản nhất đáp ứng yêu cầu của bạn.
“Scanner không bao giờ nên được sử dụng”
Cũng sai.
Scanner là một công cụ học tập tuyệt vời và hoàn toàn hợp lệ cho các nhiệm vụ nhỏ.
7.6 Phần Tiếp Theo
Trong phần tiếp theo, chúng ta sẽ xem xét các lỗi phổ biến và mẹo khắc phục sự cố liên quan đến đầu vào chuẩn của Java.
Bạn sẽ học:
- Tại sao đầu vào hoạt động không mong đợi
- Cách sửa các lỗi phân tích phổ biến
- Cách chẩn đoán các vấn đề hiệu suất
8. Các Lỗi Phổ Biến và Khắc Phục Sự Cố
Ngay cả khi bạn hiểu các nguyên tắc cơ bản của đầu vào chuẩn Java, các sai lầm nhỏ có thể gây ra lỗi khó hiểu hoặc vấn đề hiệu suất.
Phần này thu thập các vấn đề phổ biến nhất, giải thích tại sao chúng xảy ra, và cho thấy cách sửa chúng.
8.1 Đầu Vào Có Vẻ Bị Bỏ Qua Hoặc Thiếu
Triệu Chứng
- Một biến chuỗi trở nên rỗng
- Đầu vào bị bỏ qua mà không chờ đầu vào người dùng
Nguyên Nhân Thường Gặp
Điều này thường xảy ra khi trộn nextInt() (hoặc các phương thức tương tự) với nextLine() trong Scanner.
int n = sc.nextInt();
String s = sc.nextLine(); // becomes empty
Giải Pháp
Tiêu thụ dòng mới còn lại trước khi gọi nextLine().
int n = sc.nextInt();
sc.nextLine(); // consume newline
String s = sc.nextLine();
Hoặc, sử dụng BufferedReader và xử lý phân tích thủ công.
8.2 Chương Trình Chờ Vô Tận Để Nhập
Triệu Chứng
- Chương trình không kết thúc
- Nộp bài cho thẩm phán trực tuyến không bao giờ hoàn thành
Nguyên Nhân Thường Gặp
- Chương trình mong đợi nhiều đầu vào hơn được cung cấp
- EOF (kết thúc đầu vào) không được xử lý đúng cách
Giải Pháp
Sử dụng các vòng lặp đầu vào nhận biết EOF.
String line;
while ((line = br.readLine()) != null) {
// process line
}
Luôn kiểm tra kép thông số định dạng đầu vào.
8.3 NumberFormatException Khi Phân Tích Số
Triệu Chứng
- Chương trình sập khi chuyển đổi chuỗi thành số
int n = Integer.parseInt(line);
Nguyên Nhân Thường Gặp
- Khoảng trắng đầu hoặc cuối
- Dòng trống
- Ký tự không mong đợi trong đầu vào
Giải Pháp
Làm sạch đầu vào trước khi phân tích.
line = line.trim();
int n = Integer.parseInt(line);
Cũng xác minh rằng định dạng đầu vào khớp với kỳ vọng của bạn.
8.4 split() Tạo Ra Kết Quả Không Mong Đợi
Triệu Chứng
- Số lượng token sai
- Chuỗi rỗng trong mảng kết quả
Nguyên Nhân Thường Gặp
Nhiều khoảng trắng giữa các giá trị.
String[] parts = line.split(" ");
Giải Pháp
Sử dụng biểu thức chính quy xử lý nhiều khoảng trắng.
String[] parts = line.trim().split("\\s+");
Điều này hoạt động cho một hoặc nhiều khoảng trắng, tab, hoặc ngắt dòng.
8.5 Chương Trình Quá Chậm (Vượt Giới Hạn Thời Gian)
Triệu Chứng
- Chương trình hoạt động cục bộ nhưng thất bại trong cuộc thi
- Thời gian thực thi vượt quá giới hạn
Nguyên Nhân Thường Gặp
- Sử dụng
Scannercho đầu vào lớn - Sử dụng quá mức
split() - Các cuộc gọi
System.out.println()thường xuyên
Giải Pháp
- Chuyển sang
BufferedReader - Sử dụng
StringTokenizerthay vìsplit - Xuất hàng loạt sử dụng
StringBuildervàPrintWriter
Thường thì, tốc độ đầu vào là nút thắt cổ chai thực sự, không phải thuật toán.
8.6 Nhầm Lẫn Về Các Ngoại Lệ Được Kiểm Tra (IOException)
Triệu Chứng
- Lỗi biên dịch liên quan đến
IOException - Không chắc chắn nơi thêm
try-catch
Giải Pháp Đơn Giản (Học Tập / Cuộc Thi)
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)
);
}
Giải Pháp Sản Xuất
Sử dụng các khối try-catch đúng cách và xử lý lỗi một cách khéo léo.
8.7 Vấn Đề Mã Hóa Ký Tự
Triệu Chứng
- Văn bản không phải tiếng Anh xuất hiện bị hỏng
- Ký tự không mong đợi trong đầu vào
Nguyên Nhân
Không khớp giữa mã hóa đầu vào và mã hóa mặc định của Java.
Giải Pháp
Chỉ định mã hóa một cách rõ ràng.
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in, "UTF-8")
);
Điều này đặc biệt quan trọng khi đọc tệp hoặc đầu vào đa ngôn ngữ.
8.8 Danh Sách Kiểm Tra Xử Lý Nhanh
Khi đầu vào hoạt động không như mong đợi, hãy kiểm tra các mục sau:
- Mã của bạn có khớp chính xác với định dạng đầu vào không?
- Bạn có xử lý đúng các ký tự xuống dòng và khoảng trắng không?
- Bạn có kết hợp các phương thức của
Scannermột cách an toàn không? - Kích thước đầu vào có quá lớn đối với
Scannerkhông? - Bạn có xử lý EOF một cách đúng đắn không?
Việc kiểm tra có hệ thống các điểm này sẽ giải quyết hầu hết các vấn đề.
8.9 Những Gì Sắp Đến
Trong phần tiếp theo, chúng tôi sẽ trả lời các câu hỏi thường gặp (FAQ) về đầu vào chuẩn của Java.
Điều này sẽ giúp làm rõ những nghi ngờ còn lại và củng cố các thực hành tốt nhất.
9. Các Câu Hỏi Thường Gặp (FAQ)
Phần này trả lời những câu hỏi phổ biến nhất liên quan đến đầu vào chuẩn của Java, đặc biệt là những câu hỏi mà người mới bắt đầu và các nhà phát triển trung cấp thường đặt ra.
9.1 Cái Nào Tốt Hơn: Scanner hay BufferedReader?
Tùy vào mục đích của bạn.
Sử dụng
Scannernếu: wp:list /wp:list- Bạn đang học Java
- Kích thước đầu vào nhỏ
- Độ dễ đọc quan trọng hơn hiệu năng
Sử dụng
BufferedReadernếu: wp:list /wp:listKích thước đầu vào lớn
- Hiệu năng quan trọng
- Bạn muốn hành vi dự đoán được
Nếu bạn không chắc, BufferedReader thường là lựa chọn an toàn hơn về lâu dài.
9.2 Liệu Scanner Có Thật Sự Chậm Không?
Có, đối với đầu vào lớn.
Scanner được thiết kế cho:
- An toàn
- Linh hoạt
- Dễ sử dụng
Những tính năng này khiến nó chậm hơn khi xử lý một lượng lớn dữ liệu.
Đối với đầu vào nhỏ, sự khác biệt là không đáng kể.
9.3 Tại Sao nextLine() Trả Về Chuỗi Rỗng?
Điều này xảy ra khi nextLine() được gọi sau nextInt() hoặc các phương thức tương tự.
Lý do:
nextInt()không tiêu thụ ký tự xuống dòngnextLine()đọc ký tự xuống dòng còn lại
Giải pháp:
sc.nextLine(); // consume newline
Hoặc tránh kết hợp các phương thức nhập dựa trên token và dựa trên dòng.
9.4 Nên Dùng split() Hay StringTokenizer?
Sử dụng
split()khi: wp:list /wp:list- Đầu vào nhỏ
- Độ dễ đọc quan trọng
Sử dụng
StringTokenizerkhi: wp:list /wp:listĐầu vào lớn
- Hiệu năng quan trọng
Trong lập trình thi đấu, StringTokenizer vẫn được sử dụng rộng rãi.
9.5 Làm Thế Nào Để Đọc Đầu Vào Cho Đến EOF?
Với BufferedReader:
String line;
while ((line = br.readLine()) != null) {
// process input
}
Với Scanner:
while (sc.hasNext()) {
// process input
}
Xử lý EOF là phổ biến trong nhập tệp và các hệ thống chấm điểm trực tuyến.
9.6 Tôi Có Thể Dùng FastScanner Trong Ứng Dụng Thực Tế Không?
Không được khuyến nghị.
FastScanner:
- Khó đọc
- Khó bảo trì
- Chỉ được tối ưu cho các cuộc thi
Đối với các ứng dụng thực tế, BufferedReader là sự cân bằng tốt nhất giữa tốc độ và tính rõ ràng.
9.7 Tôi Luôn Phải Xử Lý Ngoại Lệ Không?
- Đối với việc học và các cuộc thi:
public static void main(String[] args) throws Exceptionlà chấp nhận được. Đối với mã sản xuất: wp:list /wp:list
- Sử dụng
try-catchthích hợp - Xử lý lỗi một cách rõ ràng
- Sử dụng
9.8 Đầu Vào Nhanh, Nhưng Đầu Ra Chậm. Tôi Nên Làm Gì?
Tối ưu hoá đầu ra cũng như đầu vào.
- Tránh gọi
System.out.println()quá thường xuyên - Sử dụng
StringBuilder - Dùng
PrintWritercho đầu ra có bộ đệm
Hiệu năng nhập và xuất nên được tối ưu cùng nhau.
9.9 Những Gì Sắp Đến
Trong phần cuối, chúng tôi sẽ tóm tắt mọi thứ và nhắc lại các kết luận chính.
10. Tổng Kết Cuối Cùng
Trong bài viết này, chúng tôi đã khám phá đầu vào chuẩn của Java từ các khái niệm cơ bản đến các kỹ thuật nâng cao được sử dụng trong lập trình thi đấu.
Hãy kết thúc bằng cách xem lại những điểm quan trọng nhất.
10.1 Ba Bài Học Chính
- Đầu vào nhỏ hoặc mục đích học →
Scanner - Đầu vào lớn hoặc ứng dụng thực tế →
BufferedReader - Lập trình thi đấu và kích thước đầu vào cực lớn →
FastScanner
Việc chọn công cụ phù hợp cho từng tình huống quan trọng hơn rất nhiều so với việc luôn dùng phương pháp nhanh nhất ở mọi nơi.
10.2 Cách Ngừng Bối Rối Khi Xử Lý Input
Hầu hết sự bối rối về input trong Java xuất phát từ việc không hiểu:
- Tại sao có nhiều phương pháp input khác nhau
- Những đánh đổi mà mỗi phương pháp mang lại
- Khi nào hiệu năng thực sự quan trọng
Khi bạn xem việc xử lý input như một lựa chọn thiết kế, sự bối rối sẽ biến mất.
10.3 Lộ Trình Học Tập Được Đề Xuất Cho Người Mới Bắt Đầu
Nếu bạn mới học Java, hãy theo trình tự sau:
- Học cách nhập dữ liệu bằng
Scanner - Chuyển sang
BufferedReaderđể có hiệu năng và kiểm soát tốt hơn - Học các kỹ thuật nhập nhanh chỉ khi bạn tham gia lập trình thi đấu
Lộ trình này giúp bạn vừa tự tin vừa hình thành thói quen đúng đắn.
10.4 Mẹo Thực Tế Khi Áp Dụng Trong Dự Án Thực Tế
- Luôn xác nhận định dạng của dữ liệu đầu vào
- Cẩn thận với các ký tự xuống dòng và khoảng trắng
- Tối ưu cả input và output đồng thời
- Nghi ngờ tốc độ input nếu hiệu năng kém
Những thói quen đơn giản này ngăn ngừa phần lớn lỗi liên quan đến input.
10.5 Bước Tiếp Theo
Để nâng cao kỹ năng Java sau khi đã thành thạo input tiêu chuẩn, bạn có thể học thêm:
- Tối ưu hoá output tiêu chuẩn (
PrintWriter,StringBuilder) - Các nguyên tắc cơ bản của xử lý ngoại lệ
- Các cấu trúc dữ liệu Collections (
List,Map) kết hợp với input - Thiết kế input/output cho các thuật toán
Input tiêu chuẩn của Java có vẻ đơn giản, nhưng đó là một kỹ năng cốt lõi hỗ trợ mọi chương trình Java mà bạn viết.
Việc thành thạo nó sẽ giúp mã của bạn chạy nhanh hơn, sạch hơn và đáng tin cậy hơn.

