text:
1.
Ẩn thanh tiêu đề:
C1:
ActionBar actionBar = getActionBar();
actionBar.hide();
C2:
this.requestWindowFeature(Window.FEATURE_NO_TITLE); (trên setContentView)
C3:
thêm thuộc tính : android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen vào activity trong Manifest
Nguoixanh tihon
Thứ Hai, ngày 17 tháng 2 năm 2014
Thứ Bảy, ngày 11 tháng 1 năm 2014
Java - Exception
1. Một số thuật ngữ:
- Exception: Một sự kiện "ngoại lệ" - ngoài mong muốn và làm cho luồng thực thi bị ngắt quãng, thay đổi, chấm dứt.
- Exception object: Khi exception xảy ra, runtime system sẽ sinh ra 1 đối tượng tương ứng với lỗi đấy (trong java người ta xây dựng sẵn các lớp của Exception - là các lớp con của java.lang.Throwable - các đối tượng này là các instance của các lớp). Các đối tượng này (object) chứa thông tin của các exception.
- Throwing an exception: Hành động tạo object và xử lý (gửi đi) trong runtime system.
- Call stack: Khi exception được throw, runtime system sẽ tìm kiếm các cách thức xử lý exception. Thứ tự tìm kiếm là các method theo thứ tự ngược với thứ tự gọi phương thức (nghĩa là tìm từ trên xuống):
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
}
throw new IOException ("File not found");
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
} finally {
if (sc != null)
sc.close();
}
public static void readFile(String args[]) throws IOException
{
Scanner sc = null;
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null) // không bắt buộc
throw new IOException ("File not found"); // không bắt buộc
while (sc.hasNext()) {
log(sc.next());
}
}
Đây là cách 2 mà chúng ta nói tới. Khi đó, hàm gọi tói phương thức readFile (ví dụ là main) phải như thế này:
public static void main(String args[])
{
try {
readFile(args);
}
catch (IOException e) {
log("Error: " + e.getMessage());
}
}
(Nói chung là trước sau gì cũng phải catch Exception)
6. Class tự tạo để xử lý Exception
Đôi khi ta hay gặp câu lệnh:
throw new MyException(); // MyException là user-define class
Cũng khá đơn giản, bạn chỉ việc tạo thêm class MyException:
class MyException extends NegativeArraySizeException
{
MyException() {
super("MyException occur !");
}
}
public static void test(String args[]) throws MyException
{
int i = -1;
if (i < 0)
throw new MyException();
}
public static void main(String args[])
{
try {
test(args);
}
catch (MyException e) {
log("Error: " + e);
}
}
- Exception: Một sự kiện "ngoại lệ" - ngoài mong muốn và làm cho luồng thực thi bị ngắt quãng, thay đổi, chấm dứt.
- Exception object: Khi exception xảy ra, runtime system sẽ sinh ra 1 đối tượng tương ứng với lỗi đấy (trong java người ta xây dựng sẵn các lớp của Exception - là các lớp con của java.lang.Throwable - các đối tượng này là các instance của các lớp). Các đối tượng này (object) chứa thông tin của các exception.
- Throwing an exception: Hành động tạo object và xử lý (gửi đi) trong runtime system.
- Call stack: Khi exception được throw, runtime system sẽ tìm kiếm các cách thức xử lý exception. Thứ tự tìm kiếm là các method theo thứ tự ngược với thứ tự gọi phương thức (nghĩa là tìm từ trên xuống):
Hình ảnh này gọi là Call stack
- Exception handler: Đoạn code mà sẽ xử lý exception.
- Catching an exception: Mỗi exception handler xử lý 1 loại exception khác nhau, một khi runtime system tìm thấy 1 handler phù hợp để xử lý, hành động đó gọi là catch
Nếu không tìm ra handler tương ứng, chương trình sẽ dừng lại.
2. Các loại exception:
Cos 3 loại:
a. Checked exception: Là exception mà có thể dự đoán được và khôi phục được, ví dụ các lỗi kiểm tra file có tồn tại không?...
b. Error: Lỗi bên ngoài chương trình, hay liên quan đến JVM, không dự đoán hay phục hồi được. Ví dụ: không đọc được file do phần cứng, hệ điều hành có vấn đề, JVM không tìm thấy platform class...
c. Runtime exception: Lỗi bên trong chương trình nhưng không dự đoán hay phục hồi được. Như các lỗi logic, gọi sai API...
3. Catch or Specify Requirement
Là 1 thủ tục yêu cầu chỉ định kiểm tra exception và tạo exception handler.
Đây là 1 thủ tục bắt buộc trong Java, với các checked exception, lập trình viên bắt buộc phải thực hiện thì mới biên dịch được.
Chỉ có loại Checked exception là bắt buộc phải thực hiện thủ tục này, 2 loại còn lại (unchecked) không cần.
Cách thức thực hiện:
Phần code xảy ra exception có thể ngầm định hoặc chỉ định rõ với câu lệnh:
throw new SomeException()
Và phần code này buộc phải:
1. Hoặc nằm trong khối try ... catch
2. Hoặc nằm trong method có mệnh đề throws SomeException đi kèm.
4. Khối try ... catch
Hãy xem xét ví dụ sau:
public static void readFile(String args[]){
Scanner sc = null;try {
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
}
}
Đoạn code:if (sc == null)
throw new IOException ("File not found");
không có cũng được (có thể ngầm định).
Trong khối try là các lệnh có thể xảy ra exception và khi xảy ra, runtime system sẽ tìm handler theo thứ tự:
- FileReader -> không có
- method readFile -> có 1 handler phù hợp (IOException) -> thực thi lệnh trong catch.
Chúng ta đôi khi thấy 1 dạng khối có thêm finally như vầy:
public static void readFile(String args[]){
Scanner sc = null;try {
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
} finally {
if (sc != null)
sc.close();
}
}
Trong khối finally thường là lệnh dọn dẹp, nó được thực hiện ngay khi khối lệnh try thoát ra và catch phù hợp nếu được tìm thấy (try --> catch phù hợp --> finaaly)
5. Throws statement:
Hãy xem ví dụ sau:
{
Scanner sc = null;
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null) // không bắt buộc
throw new IOException ("File not found"); // không bắt buộc
while (sc.hasNext()) {
log(sc.next());
}
}
Đây là cách 2 mà chúng ta nói tới. Khi đó, hàm gọi tói phương thức readFile (ví dụ là main) phải như thế này:
public static void main(String args[])
{
try {
readFile(args);
}
catch (IOException e) {
log("Error: " + e.getMessage());
}
}
(Nói chung là trước sau gì cũng phải catch Exception)
6. Class tự tạo để xử lý Exception
Đôi khi ta hay gặp câu lệnh:
throw new MyException(); // MyException là user-define class
Cũng khá đơn giản, bạn chỉ việc tạo thêm class MyException:
class MyException extends NegativeArraySizeException
{
MyException() {
super("MyException occur !");
}
}
public static void test(String args[]) throws MyException
{
int i = -1;
if (i < 0)
throw new MyException();
}
public static void main(String args[])
{
try {
test(args);
}
catch (MyException e) {
log("Error: " + e);
}
}
Java - I/O cơ bản
I/O là viết tắt của Input / Output, và được hiểu là tất cả những thao tác liên quan đến dữ liệu vào ra của 1 chương trình.
I - I/O Stream
Trong Java, có 1 thuật ngữ cơ bản là I/O Stream. Được hiểu là dòng dữ liệu có nguồn, có đích, mà có thể xuất phát từ (hay đi tới) bất cứ 1 file trên đĩa nào, hay là thiết bị, chương trình bên ngoài, hay là mảng bộ nhớ.
Các loại dữ liệu có thể được chứa trong Stream cũng rất đa dạng: byte stream, character stream, primitive data stream hay object stream.
Hầu hết các lớp làm việc với I/O Stream nằm trong gói java.io
1. Byte stream:
Đây là loại stream được dùng để xử lý từng byte 8-bit một.
Các lớp liên quan thuộc lớp InputStream và OutputStream. Có rất nhiều lớp xử lý byte stream và chúng ta sẽ demo lớp FileInputStream và FileOutputStream (các lớp khác tương tự):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\input.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\output.txt";
public static void myByteStream(String args[]) throws IOException
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(INPUTFILE);
fos = new FileOutputStream(OUTPUTFILE);
int c;
while((c = fis.read()) != -1)
{
fos.write(c);
}
} finally {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
Thông thường ít khi chúng ta dùng đến byte stream trong các chương trình thông thường do chúng xử lý từng byte một, không cần thiết phải level thấp đến vậy :)
2. Character Stream:
Java sử dụng quy ước UNICODE cho việc lưu trữ ký tự. Character stream tự động chuyển đổi internal format sang bộ ký tự địa phương.
Các lớp xử lý Character stream trực thuộc lớp Reader và Writer. Tương tự phần trên sau đây chúng ta sẽ demo các lớp FileReader và FileWriter:
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
public static void myCharacterStream(String args[]) throws IOException
{
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(INPUTFILE);
fw = new FileWriter(OUTPUTFILE);
int c;
while((c = fr.read()) != -1)
{
fw.write(c);
}
} finally {
if (fr != null) {
fr.close();
}
if (fw != null) {
fw.close();
}
}
}
Character stream sử dụng byte stream để giao tiếp với phần cứng I/O, trong khi sử dụng các "cầu nối" để chuyển từ byte sang character (với charset được chỉ định). Ví dụ:
FileReader sử dụng FileInputStream và cầu nối InputStreamReader
Có 1 cách thức khác để đọc dữ liệu từ Character stream, đó là đọc theo dòng:
public static void myCharacterStream2(String args[]) throws IOException
{
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new FileReader(INPUTFILE));
pw = new PrintWriter(new FileWriter(OUTPUTFILE));
String l;
while((l = br.readLine()) != null)
{
pw.println(l);
}
} finally {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
}
}
3. Buffered stream
Buffered là 1 thuật ngữ chỉ việc lưu tạm dữ liệu đến 1 thời điểm nào đó thì mới thao tác, do đó nó giảm thiểu số thao tác phải dùng.
Buffered được sử dụng để wrap các unbuffered stream tương ứng. Ví dụ:
BufferedReader brr = new BufferedReader(new FileReader(input.txt)); // Character stream
BufferedInputStream bris = new BufferedInputStream(new FileInputStream(input.txt)); // Byte stream
4. Scanning
Đây là khái niệm chuyển dữ liệu từ input sang các token (những mảnh dữ liệu được phân tách từ 1 chuỗi dữ liệu ban đầu - kiểu như 1 string phân thành cách từ bởi đấu cách).
Chúng ta xem demo sau:
public static void myScanning(String args[]) throws IOException
{
Scanner sc = null;
double sum = 0;
try {
sc = new Scanner(new BufferedReader(new FileReader(INPUTFILE)));
while (sc.hasNext()) {
if (sc.hasNextDouble()) {
sum += sc.nextDouble();
} else {
sc.next();
}
}
} finally {
if (sc != null) {
sc.close();
}
}
log((Double.toString(sum)));
}
Scanner dùng phương thức sau để định ký tự phân cách
5. I/O from Console
Standard stream gồm System.in, System.out, System.err tương ứng với 3 cổng I/O kinh điển trong bất cứ HĐH nào (các bạn tự hiểu)
Cả 3 đều là byte character nhưng System.out, System.err được định nghĩa bởi PrintStream, nên được hỗ trợ chuyển đổi thành character. Với System.in, để sử dụng, ta cần dùng cầu nối để chuyển thành character stream:
Console I/O: Hỗ trợ 1 số tính năng mà standard stream không có. Để sử dụng, bạn bất buộc phải gọi
System.console():
Ta xét ví dụ sau:
public static void myPassword(String args[])
{
Console c = System.console();
if (c == null) {
log("Console not permit !");
System.exit(1);
}
String login = c.readLine("Enter Username: ");
char[] pass = c.readPassword("Enter Password: ");
c.format("You just entered: ");
c.format("User: "+login+"%n");
c.format("Pass: "+(new String(pass)));
}
6. Data Stream
Java hỗ trợ thao tác I/O với binary data : bolean, byte, char, short, int, long, float, double (primitive data type).
Data stream được hỗ trợ bởi các class implement các giao diện DataInput và DataOutput. Sau đây chúng ta khảo sát DataInputStrean và DataOutputStream.
static final String dataFile = "D:\\Programing\\Java\\Proj\\invoicedata";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
public static void myDataStream(String args[]) throws IOException
{
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i++) {
dos.writeDouble(prices[i]);
dos.writeInt(units[i]);
dos.writeUTF(descs[i]);
}
} finally {
if (dos != null)
dos.close();
}
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
double price = 0;
int unit = 0;
String desc = null;
try {
while(true) {
price = dis.readDouble();
unit = dis.readInt();
desc = dis.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price, unit, desc);
}
} catch (EOFException e) {
log("End.");
}
} finally {
if (dis != null)
dis.close();
}
}
7. Object stream
Object stream hỗ trợ I/O của các reference object (đồng thời cả primitive data). Các lớp Object stream là ObjectInputStream và ObjectOutputStream. Các lớp này thực ra implement các interface ObjectInput và ObjectOutput (là subinterface của DataInput và DataOutput).
Một điều phải lưu ý là để writeObject 1 Object nào đó chứa reference tới các Object khác, rõ ràng nó cũng write luôn cả những Object thêm vào đó. Quá trình readObject cũng tương tự.
Chúng ta xem ví dụ sau đây:
public static void myObjectStream(String args[]) throws IOException
{
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices2.length; i++) {
oos.writeObject(prices[i]);
oos.writeInt(units[i]);
oos.writeUTF(descs[i]);
}
} finally {
if (oos != null) {
oos.close();
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
BigDecimal price2;
int unit = 0;
String desc = null;
try {
while(true) {
price2 = new BigDecimal((Double)ois.readObject());
unit = ois.readInt();
desc = ois.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price2, unit, desc);
}
} catch (EOFException e) {
log("End.");
} catch (ClassNotFoundException e) {
log("Error: "+ e.toString());
}
} finally {
if (ois != null) {
ois.close();
}
}
}
II - File I/O
Có thể coi file I/O là 1 trường hợp của I/O mà bởi tính quan trọng và phổ biến, nó được tách ra phần riêng và có những gói xử lý riêng. Tất cả các gói, class nằm dưới java.nio.file
Đây là những gói MỚI - NEW IO (nio) không phải để thay thế java.io mà đơn giản là mở rộng nó.
(Tất nhiên, như chúng đã xem phần đầu, các thao tác với file I/O cũng được xử lý trong java.io bởi nhiều lớp khác nhau, nhưng nói chung khi vào trận thì với file, bạn nên dùng mấy thứ được chuyên biệt hóa này :))
Chúng ta sẽ bắt đầu với Path và các thứ liên quan
1. Path
Đây là 1 khái niệm quen thuộc. Chúng ta có lớp Path để xử lý các thao tác với nó.
Tạo 1 path mới, ví dụ:
Path p1 = Paths.get("/home/go/to/file");
Path p2 = Paths.get("C:\\go\\to\\file");
Path p3 = Paths.get(URI.create("file:///home/go/to/file")); // cần import java.net
Path p4 = Paths.get(System.getProperty("user.home"), "go", "to", "file");
Lấy thông tin về path:
public static void myPath(String args[]) throws IOException
{
Path p1 = Paths.get("C:\\go\\to\\file");
log("toString(): " + p1.toString());
log("getFileName(): " + p1.getFileName());
log("getName(0): " + p1.getName(0));
log("getNameCount(): " + p1.getNameCount());
log("subpath(0,2): " + p1.subpath(0,2));
log("getParent(): " + p1.getParent());
log("getRoot(): " + p1.getRoot());
}
Chuyển đổi path
Hàm toUri chuyển path thành String mà có thể dùng trên các trình duyệt
Hàm toAbsolutePath chuyển đường dẫn tương đối thành tuyệt đối và 1 số thao tác như:
Hàm toRealPath kiểm tra và trả về dường dẫn thực của file có tồn tại
- Nếu true được gửi vào và file system cho phép symbolic links, nó sẽ trả về đường dẫn thật của file (resolves symbolic link)
- Nếu là path tương đối, chuyển thành tuyệt đối
- Nếu path có chứa thành phần dư thừa (".", "directory/.."), nó loại bỏ các thành phần này
Path p1 = Paths.get("C:\\go\\to\\..\\file");
Path p2 = p1.toRealPath();
Nối path
Rất đơn giản
Path p1 = Paths.get("C:\\go\\to");
Path p2 = p1.resolve("file");
Tạo path từ 2 path đã cho
Path p1 = Paths.get("C:\\go");
Path p1 = Paths.get("C:\\go\\to\\file");
Path p3 = p1.relativize(p2); // p3 = "to\\file"
Path p4 = p2.relativize(p1); // p4 = "..\\.."
So sánh 2 path
phương thức equals được dùng để kiểm tra 2 path có giống nhau không.
2. File
Chúng ta sẽ làm quen với một vài thuật ngữ:
Giải phóng tài nguyên hệ thống:
Nhiều tài nguyên được sử dụng bởi các hàm API như stream, chanel,... được kế thừa hay mở rộng từ interface java.io.Closable. Việc giải phóng sau khi sử dụng rất quan trọng, thông thường chúng ta gọi phương thức close(). Tuy nhiên, chúng ta có thể cho chúng tự động close với cú pháp try-with-resources.
Bất kỳ lớp nào implement java.lang.AutoCloseable đề có thể dùng được với try-with-resources:
try (BufferedReader br = BufferedReader(new FileReader(file))) {
...
} catch (IOException e) {
...
}
Atomic Operation
Những thao tác không thể ngắt quãng, chia nhỏ nếu không muốn bị fails. Ví dụ lệnh move file.
Method chaining
Nhiều trường hợp chúng ta gọi 1 method, method này trả về 1 đối tượng nào đó, và ta gọi tiếp method 2 sử dụng kết quả này, ... Ví dụ:
String value = Charset.defaultCharset().decode(buf).toString();
Glob
Glob pattern khá giống regular express, nó được xem như là String và được dùng để match một String khác
Kiểm tra sự tồn tại:
Files.exists(path) và Files.notExists(path). Chú ý Files.notExists(path) khác với !Files.exists(path)
Kiểm tra sự truy cập
Files.isReadable(path)
Files.isWritable(path)
Files.isExcutable(path)
Kiểm tra 2 path có dẫn tới cùng 1 file
Files.isSameFile(path, path)
4. Một số thao tác file / thư mục
Delete:
Files.delete(path);
Copy:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.copy(p1, p2); // nếu p2 chưa có
Files.copy(p1, p2, REPLACE_EXISTING); // nếu p2 đã có sẵn + cần import static java.nio.file.StandardCopyOption.*;
Nếu p1 là symbolic link thì chỉ có target file được copy, nếu muốn chỉ copy symbolic link thì:
Files.copy(p1, p2, NOFOLLOW_LINKS);
Cũng có thể copy từ/đến 1 InputStream, ví dụ:
Files.copy(new FileInputStream(p1.toString()), p2);
Chú ý rằng khi copy thư mục, chỉ thư mục được copy, các file bên trong thì không được.
Move:
Một chú ý với lệnh move là nếu move 1 thư mục có chứa nội dung bên trong, lệnh move chỉ được phép nếu thư mục đó được phép move mà không cần move cả nội dung bên trong. Riêng với UNIX, trong cùng partition, thư mục được phép move ngay khi nó có chứa nội dung bên trong
import static java.nio.file.StandardCopyOption.*;
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.move(p1, p2); // nếu p2 chưa có
Files.move(p1, p2, ATOMIC_MOVE); // nếu hệ thống cho phép, lệnh move với ATOMIC_MOVE sẽ đảm bảo quá trình move được ưu tiên để hoàn thành mà không bị ngắt quãng, lỗi.
5. File attributes
Có nhiều phương thức và lớp liên quan đến việc lấy attributes và thiết lập attributes file.
Trong java.nio.file.Files cũng có 1 số phương thức lấy attributes như:
size
isDirectory
isRegularFile
...
Tuy nhiên, để lấy nhiều attributes cùng lúc mà dùng từng phương thức như trên thì không tốt cho hiệu suất, vì vậy, ta có phương thức: readAttributes (đọc thêm tài liệu về các phương thức này)
Đối với từng hệ thống khác nhau, chúng ta có những loại attributes khác nhau, vì vậy, java xếp theo các nhóm.
Các attributes cơ bản, ta có java.nio.file.attribute.BasicFileAttributes
Với Windows file, ta có java.nio.file.attribute.DosFileAttributes
Với POSIX file, java.nio.files.attribute.PosixFileAttributes
...
Ta xét ví dụ sau:
public static void myFileAttributes(String args[]) throws IOException
{
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\outputSmall.txt");
BasicFileAttributes bfa = Files.readAttributes(p1, BasicFileAttributes.class);
DosFileAttributes dfa = Files.readAttributes(p1, DosFileAttributes.class);
log("creationTime: " + bfa.creationTime());
log("lastModifiedTime: " + bfa.lastModifiedTime());
log("size: " + bfa.size());
log("isHidden: " + dfa.isHidden());
long curTime = System.currentTimeMillis();
FileTime ft = FileTime.fromMillis(curTime);
Files.setLastModifiedTime(p1, ft);
log("after edit - lastModifiedTime: " + bfa.lastModifiedTime());
}
Ở đây có 1 cái bẫy: log("after edit - lastModifiedTime: " + bfa.lastModifiedTime()); --> sẽ in ra thông số giống như lúc chưa setLastModifiedTime ! Vì ta sử dụng biến bfa - nó đã lấy thông tin tất cả các attributes từ trước, bởi vậy, muốn cập nhật thì không thể dùng lại bfa
File Store attributes
Chúng ta xét ví dụ sau để hiểu các lấy file store:
import java.nio.file.FileStore;
FileStore fs = Files.getFileStore(p1);
log("getTotalSpace: " + fs.getTotalSpace()/1024);
log("getUnallocatedSpace: " + fs.getUnallocatedSpace()/1024);
log("get used: " + (fs.getTotalSpace() - fs.getUnallocatedSpace())/1024);
log("getUsableSpace: " + fs.getUsableSpace()/1024);
6. File reading, writing, creating
Có nhiều phương thức, class để làm việc này tùy theo trường hợp, mức độ
Đối với Small file:
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\inputSmall.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\outputSmall.txt";
public static void myFileOperation_SmallFile(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
byte[] fileArrBytes;
fileArrBytes = Files.readAllBytes(p1);
log(String.valueOf(fileArrBytes));
Files.write(p2, fileArrBytes, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
}
Đối với file lớn - dùng buffer:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.*;
public static void myFileOperation_Buffered(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
Charset charset = Charset.forName("UTF-8");
// or Charset charset = StandardCharsets.UTF_8;
String sLine = null;
try (BufferedReader readerBuf = Files.newBufferedReader(p1, charset);
BufferedWriter writerBuf = Files.newBufferedWriter(p2, charset, WRITE, APPEND)){
while ((sLine = readerBuf.readLine()) != null) {
log(sLine);
writerBuf.write(sLine, 0, sLine.length());
writerBuf.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Dùng I/O Stream kết hợp với buffered
public static void myFileOperation_Stream(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
String sLine;
try (InputStream in = Files.newInputStream(p1);
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
OutputStream ou = Files.newOutputStream(p2, WRITE, APPEND);
BufferedWriter bou = new BufferedWriter(new OutputStreamWriter(ou));) {
while ((sLine = bin.readLine()) != null) {
log(sLine);
bou.write(sLine, 0, sLine.length());
bou.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Liệt kê các thư mục gốc
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
FileSystem dfFst = FileSystems.getDefault(); // get default File system
Iterable<Path> dirs = dfFst.getRootDirectories();
// or Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
for (Path d : dirs) {
log(d.toString());
}
Tạo thư mục:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\testDir\\subTestDir"); // có thể tạo nhiều dir nhiều level
Files.createDirectories(p1);
Liệt kê nội dung thư mục
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p1, "*.txt")) {
for (Path p : stream) {
log(p.getFileName());
}
} catch (DirectoryIteratorException e) {
e.printStackTrace();
}
Chú ý rằng trong: Files.newDirectoryStream(path, filter) thì filter nếu không có nó sẽ liệt kê tất cả. Nếu cần điều kiện để liệt kê, filter là dạng Glob String như trên.
Walking the File Tree
Để duyệt nội dung xuyên qua các thư mục khác nhau, hay 1 hoạt động cụ thể với 1 nhóm file trong 1 cây thư mục... chúng ta sẽ dùng tới các lớp và phương thức là implement của interface: FileVisitor
FileVisitor là 1 interface quy định cụ thể các hành vi cần thiết tại các điểm quan trọng trong quá trình "walking file tree":
- khi một tập tin được truy cập,
- trước khi một thư mục được truy cập,
- sau khi một thư mục được truy cập,
- hoặc khi thất bại xảy ra.
Giao diện có bốn phương pháp tương ứng với những tình huống.
Nếu chúng ta không cần triển khai 4 tình huống trên, thay vì implement FileVisitor, chúng ta sẽ extends SimpleFileVisitor:
import java.nio.file.FileVisitResult;
import java.nio.file.SimpleFileVisitor;
class PrintFiles extends SimpleFileVisitor<Path> {
/**
This is extended class for print all files in specified directory
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
Calc.log("Symbolic link: " + file.toString());
} else if (attr.isRegularFile()) {
Calc.log("Regular file: " + file.toString());
} else {
Calc.log("Other: " + file.toString());
}
Calc.log(" (" + attr.size() + "bytes)");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) {
Calc.log("Dir: " + dir.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
System.err.println(e);
return FileVisitResult.CONTINUE;
}
}
class Calc
{
public static void myDirectory(String args[]) throws IOException
{
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\testDir");
PrintFiles pf = new PrintFiles();
Files.walkFileTree(p2, pf);
}
public static void main(String args[])
{
try {
myDirectory(args);
}
catch (IOException e) {
log("Error: " + e);
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
}
I - I/O Stream
Trong Java, có 1 thuật ngữ cơ bản là I/O Stream. Được hiểu là dòng dữ liệu có nguồn, có đích, mà có thể xuất phát từ (hay đi tới) bất cứ 1 file trên đĩa nào, hay là thiết bị, chương trình bên ngoài, hay là mảng bộ nhớ.
Các loại dữ liệu có thể được chứa trong Stream cũng rất đa dạng: byte stream, character stream, primitive data stream hay object stream.
Hầu hết các lớp làm việc với I/O Stream nằm trong gói java.io
1. Byte stream:
Đây là loại stream được dùng để xử lý từng byte 8-bit một.
Các lớp liên quan thuộc lớp InputStream và OutputStream. Có rất nhiều lớp xử lý byte stream và chúng ta sẽ demo lớp FileInputStream và FileOutputStream (các lớp khác tương tự):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\input.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\output.txt";
public static void myByteStream(String args[]) throws IOException
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(INPUTFILE);
fos = new FileOutputStream(OUTPUTFILE);
int c;
while((c = fis.read()) != -1)
{
fos.write(c);
}
} finally {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
Thông thường ít khi chúng ta dùng đến byte stream trong các chương trình thông thường do chúng xử lý từng byte một, không cần thiết phải level thấp đến vậy :)
2. Character Stream:
Java sử dụng quy ước UNICODE cho việc lưu trữ ký tự. Character stream tự động chuyển đổi internal format sang bộ ký tự địa phương.
Các lớp xử lý Character stream trực thuộc lớp Reader và Writer. Tương tự phần trên sau đây chúng ta sẽ demo các lớp FileReader và FileWriter:
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
public static void myCharacterStream(String args[]) throws IOException
{
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(INPUTFILE);
fw = new FileWriter(OUTPUTFILE);
int c;
while((c = fr.read()) != -1)
{
fw.write(c);
}
} finally {
if (fr != null) {
fr.close();
}
if (fw != null) {
fw.close();
}
}
}
Character stream sử dụng byte stream để giao tiếp với phần cứng I/O, trong khi sử dụng các "cầu nối" để chuyển từ byte sang character (với charset được chỉ định). Ví dụ:
FileReader sử dụng FileInputStream và cầu nối InputStreamReader
Có 1 cách thức khác để đọc dữ liệu từ Character stream, đó là đọc theo dòng:
public static void myCharacterStream2(String args[]) throws IOException
{
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new FileReader(INPUTFILE));
pw = new PrintWriter(new FileWriter(OUTPUTFILE));
String l;
while((l = br.readLine()) != null)
{
pw.println(l);
}
} finally {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
}
}
3. Buffered stream
Buffered là 1 thuật ngữ chỉ việc lưu tạm dữ liệu đến 1 thời điểm nào đó thì mới thao tác, do đó nó giảm thiểu số thao tác phải dùng.
Buffered được sử dụng để wrap các unbuffered stream tương ứng. Ví dụ:
BufferedReader brr = new BufferedReader(new FileReader(input.txt)); // Character stream
BufferedInputStream bris = new BufferedInputStream(new FileInputStream(input.txt)); // Byte stream
4. Scanning
Đây là khái niệm chuyển dữ liệu từ input sang các token (những mảnh dữ liệu được phân tách từ 1 chuỗi dữ liệu ban đầu - kiểu như 1 string phân thành cách từ bởi đấu cách).
Chúng ta xem demo sau:
public static void myScanning(String args[]) throws IOException
{
Scanner sc = null;
double sum = 0;
try {
sc = new Scanner(new BufferedReader(new FileReader(INPUTFILE)));
while (sc.hasNext()) {
if (sc.hasNextDouble()) {
sum += sc.nextDouble();
} else {
sc.next();
}
}
} finally {
if (sc != null) {
sc.close();
}
}
log((Double.toString(sum)));
}
Scanner dùng phương thức sau để định ký tự phân cách
s.useDelimiter()
5. I/O from Console
Standard stream gồm System.in, System.out, System.err tương ứng với 3 cổng I/O kinh điển trong bất cứ HĐH nào (các bạn tự hiểu)
Cả 3 đều là byte character nhưng System.out, System.err được định nghĩa bởi PrintStream, nên được hỗ trợ chuyển đổi thành character. Với System.in, để sử dụng, ta cần dùng cầu nối để chuyển thành character stream:
InputStreamReader cin = new InputStreamReader(System.in);
Console I/O: Hỗ trợ 1 số tính năng mà standard stream không có. Để sử dụng, bạn bất buộc phải gọi
System.console():
Ta xét ví dụ sau:
public static void myPassword(String args[])
{
Console c = System.console();
if (c == null) {
log("Console not permit !");
System.exit(1);
}
String login = c.readLine("Enter Username: ");
char[] pass = c.readPassword("Enter Password: ");
c.format("You just entered: ");
c.format("User: "+login+"%n");
c.format("Pass: "+(new String(pass)));
}
6. Data Stream
Java hỗ trợ thao tác I/O với binary data : bolean, byte, char, short, int, long, float, double (primitive data type).
Data stream được hỗ trợ bởi các class implement các giao diện DataInput và DataOutput. Sau đây chúng ta khảo sát DataInputStrean và DataOutputStream.
static final String dataFile = "D:\\Programing\\Java\\Proj\\invoicedata";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
public static void myDataStream(String args[]) throws IOException
{
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i++) {
dos.writeDouble(prices[i]);
dos.writeInt(units[i]);
dos.writeUTF(descs[i]);
}
} finally {
if (dos != null)
dos.close();
}
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
double price = 0;
int unit = 0;
String desc = null;
try {
while(true) {
price = dis.readDouble();
unit = dis.readInt();
desc = dis.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price, unit, desc);
}
} catch (EOFException e) {
log("End.");
}
} finally {
if (dis != null)
dis.close();
}
}
7. Object stream
Object stream hỗ trợ I/O của các reference object (đồng thời cả primitive data). Các lớp Object stream là ObjectInputStream và ObjectOutputStream. Các lớp này thực ra implement các interface ObjectInput và ObjectOutput (là subinterface của DataInput và DataOutput).
Một điều phải lưu ý là để writeObject 1 Object nào đó chứa reference tới các Object khác, rõ ràng nó cũng write luôn cả những Object thêm vào đó. Quá trình readObject cũng tương tự.
Chúng ta xem ví dụ sau đây:
public static void myObjectStream(String args[]) throws IOException
{
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices2.length; i++) {
oos.writeObject(prices[i]);
oos.writeInt(units[i]);
oos.writeUTF(descs[i]);
}
} finally {
if (oos != null) {
oos.close();
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
BigDecimal price2;
int unit = 0;
String desc = null;
try {
while(true) {
price2 = new BigDecimal((Double)ois.readObject());
unit = ois.readInt();
desc = ois.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price2, unit, desc);
}
} catch (EOFException e) {
log("End.");
} catch (ClassNotFoundException e) {
log("Error: "+ e.toString());
}
} finally {
if (ois != null) {
ois.close();
}
}
}
II - File I/O
Có thể coi file I/O là 1 trường hợp của I/O mà bởi tính quan trọng và phổ biến, nó được tách ra phần riêng và có những gói xử lý riêng. Tất cả các gói, class nằm dưới java.nio.file
Đây là những gói MỚI - NEW IO (nio) không phải để thay thế java.io mà đơn giản là mở rộng nó.
(Tất nhiên, như chúng đã xem phần đầu, các thao tác với file I/O cũng được xử lý trong java.io bởi nhiều lớp khác nhau, nhưng nói chung khi vào trận thì với file, bạn nên dùng mấy thứ được chuyên biệt hóa này :))
Chúng ta sẽ bắt đầu với Path và các thứ liên quan
1. Path
Đây là 1 khái niệm quen thuộc. Chúng ta có lớp Path để xử lý các thao tác với nó.
Tạo 1 path mới, ví dụ:
Path p1 = Paths.get("/home/go/to/file");
Path p2 = Paths.get("C:\\go\\to\\file");
Path p3 = Paths.get(URI.create("file:///home/go/to/file")); // cần import java.net
Path p4 = Paths.get(System.getProperty("user.home"), "go", "to", "file");
Lấy thông tin về path:
public static void myPath(String args[]) throws IOException
{
Path p1 = Paths.get("C:\\go\\to\\file");
log("toString(): " + p1.toString());
log("getFileName(): " + p1.getFileName());
log("getName(0): " + p1.getName(0));
log("getNameCount(): " + p1.getNameCount());
log("subpath(0,2): " + p1.subpath(0,2));
log("getParent(): " + p1.getParent());
log("getRoot(): " + p1.getRoot());
}
Chuyển đổi path
Hàm toUri chuyển path thành String mà có thể dùng trên các trình duyệt
Hàm toAbsolutePath chuyển đường dẫn tương đối thành tuyệt đối và 1 số thao tác như:
Hàm toRealPath kiểm tra và trả về dường dẫn thực của file có tồn tại
- Nếu true được gửi vào và file system cho phép symbolic links, nó sẽ trả về đường dẫn thật của file (resolves symbolic link)
- Nếu là path tương đối, chuyển thành tuyệt đối
- Nếu path có chứa thành phần dư thừa (".", "directory/.."), nó loại bỏ các thành phần này
Path p1 = Paths.get("C:\\go\\to\\..\\file");
Path p2 = p1.toRealPath();
Nối path
Rất đơn giản
Path p1 = Paths.get("C:\\go\\to");
Path p2 = p1.resolve("file");
Tạo path từ 2 path đã cho
Path p1 = Paths.get("C:\\go");
Path p1 = Paths.get("C:\\go\\to\\file");
Path p3 = p1.relativize(p2); // p3 = "to\\file"
Path p4 = p2.relativize(p1); // p4 = "..\\.."
So sánh 2 path
phương thức equals được dùng để kiểm tra 2 path có giống nhau không.
2. File
Chúng ta sẽ làm quen với một vài thuật ngữ:
Giải phóng tài nguyên hệ thống:
Nhiều tài nguyên được sử dụng bởi các hàm API như stream, chanel,... được kế thừa hay mở rộng từ interface java.io.Closable. Việc giải phóng sau khi sử dụng rất quan trọng, thông thường chúng ta gọi phương thức close(). Tuy nhiên, chúng ta có thể cho chúng tự động close với cú pháp try-with-resources.
Bất kỳ lớp nào implement java.lang.AutoCloseable đề có thể dùng được với try-with-resources:
try (BufferedReader br = BufferedReader(new FileReader(file))) {
...
} catch (IOException e) {
...
}
Atomic Operation
Những thao tác không thể ngắt quãng, chia nhỏ nếu không muốn bị fails. Ví dụ lệnh move file.
Method chaining
Nhiều trường hợp chúng ta gọi 1 method, method này trả về 1 đối tượng nào đó, và ta gọi tiếp method 2 sử dụng kết quả này, ... Ví dụ:
String value = Charset.defaultCharset().decode(buf).toString();
Glob
Glob pattern khá giống regular express, nó được xem như là String và được dùng để match một String khác
- An asterisk,
*, matches any number of characters (including none).
- Two asterisks,
**, works like
*but crosses directory boundaries. This syntax is generally used for matching complete paths.
- A question mark,
?, matches exactly one character.
- Braces specify a collection of subpatterns. For example:
{sun,moon,stars}matches "sun", "moon", or "stars".
{temp*,tmp*}matches all strings beginning with "temp" or "tmp".
-
- Square brackets convey a set of single characters or, when the hyphen character (
-) is used, a range of characters. For example:
[aeiou]matches any lowercase vowel.
[0-9]matches any digit.
[A-Z]matches any uppercase letter.
[a-z,A-Z]matches any uppercase or lowercase letter.
*,
?, and
\match themselves.
-
- All other characters match themselves.
- To match
*,
?, or the other special characters, you can escape them by using the backslash character,
\. For example:
\\matches a single backslash, and
\?matches the question mark.
Kiểm tra sự tồn tại:
Files.exists(path) và Files.notExists(path). Chú ý Files.notExists(path) khác với !Files.exists(path)
Kiểm tra sự truy cập
Files.isReadable(path)
Files.isWritable(path)
Files.isExcutable(path)
Kiểm tra 2 path có dẫn tới cùng 1 file
Files.isSameFile(path, path)
4. Một số thao tác file / thư mục
Delete:
Files.delete(path);
Copy:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.copy(p1, p2); // nếu p2 chưa có
Files.copy(p1, p2, REPLACE_EXISTING); // nếu p2 đã có sẵn + cần import static java.nio.file.StandardCopyOption.*;
Nếu p1 là symbolic link thì chỉ có target file được copy, nếu muốn chỉ copy symbolic link thì:
Files.copy(p1, p2, NOFOLLOW_LINKS);
Cũng có thể copy từ/đến 1 InputStream, ví dụ:
Files.copy(new FileInputStream(p1.toString()), p2);
Chú ý rằng khi copy thư mục, chỉ thư mục được copy, các file bên trong thì không được.
Move:
Một chú ý với lệnh move là nếu move 1 thư mục có chứa nội dung bên trong, lệnh move chỉ được phép nếu thư mục đó được phép move mà không cần move cả nội dung bên trong. Riêng với UNIX, trong cùng partition, thư mục được phép move ngay khi nó có chứa nội dung bên trong
import static java.nio.file.StandardCopyOption.*;
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.move(p1, p2); // nếu p2 chưa có
Files.move(p1, p2, ATOMIC_MOVE); // nếu hệ thống cho phép, lệnh move với ATOMIC_MOVE sẽ đảm bảo quá trình move được ưu tiên để hoàn thành mà không bị ngắt quãng, lỗi.
5. File attributes
Có nhiều phương thức và lớp liên quan đến việc lấy attributes và thiết lập attributes file.
Trong java.nio.file.Files cũng có 1 số phương thức lấy attributes như:
size
isDirectory
isRegularFile
...
Tuy nhiên, để lấy nhiều attributes cùng lúc mà dùng từng phương thức như trên thì không tốt cho hiệu suất, vì vậy, ta có phương thức: readAttributes (đọc thêm tài liệu về các phương thức này)
Đối với từng hệ thống khác nhau, chúng ta có những loại attributes khác nhau, vì vậy, java xếp theo các nhóm.
Các attributes cơ bản, ta có java.nio.file.attribute.BasicFileAttributes
Với Windows file, ta có java.nio.file.attribute.DosFileAttributes
Với POSIX file, java.nio.files.attribute.PosixFileAttributes
...
Ta xét ví dụ sau:
public static void myFileAttributes(String args[]) throws IOException
{
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\outputSmall.txt");
BasicFileAttributes bfa = Files.readAttributes(p1, BasicFileAttributes.class);
DosFileAttributes dfa = Files.readAttributes(p1, DosFileAttributes.class);
log("creationTime: " + bfa.creationTime());
log("lastModifiedTime: " + bfa.lastModifiedTime());
log("size: " + bfa.size());
log("isHidden: " + dfa.isHidden());
long curTime = System.currentTimeMillis();
FileTime ft = FileTime.fromMillis(curTime);
Files.setLastModifiedTime(p1, ft);
log("after edit - lastModifiedTime: " + bfa.lastModifiedTime());
}
Ở đây có 1 cái bẫy: log("after edit - lastModifiedTime: " + bfa.lastModifiedTime()); --> sẽ in ra thông số giống như lúc chưa setLastModifiedTime ! Vì ta sử dụng biến bfa - nó đã lấy thông tin tất cả các attributes từ trước, bởi vậy, muốn cập nhật thì không thể dùng lại bfa
File Store attributes
Chúng ta xét ví dụ sau để hiểu các lấy file store:
import java.nio.file.FileStore;
FileStore fs = Files.getFileStore(p1);
log("getTotalSpace: " + fs.getTotalSpace()/1024);
log("getUnallocatedSpace: " + fs.getUnallocatedSpace()/1024);
log("get used: " + (fs.getTotalSpace() - fs.getUnallocatedSpace())/1024);
log("getUsableSpace: " + fs.getUsableSpace()/1024);
6. File reading, writing, creating
Có nhiều phương thức, class để làm việc này tùy theo trường hợp, mức độ
Đối với Small file:
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\inputSmall.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\outputSmall.txt";
public static void myFileOperation_SmallFile(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
byte[] fileArrBytes;
fileArrBytes = Files.readAllBytes(p1);
log(String.valueOf(fileArrBytes));
Files.write(p2, fileArrBytes, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
}
Đối với file lớn - dùng buffer:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.*;
public static void myFileOperation_Buffered(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
Charset charset = Charset.forName("UTF-8");
// or Charset charset = StandardCharsets.UTF_8;
String sLine = null;
try (BufferedReader readerBuf = Files.newBufferedReader(p1, charset);
BufferedWriter writerBuf = Files.newBufferedWriter(p2, charset, WRITE, APPEND)){
while ((sLine = readerBuf.readLine()) != null) {
log(sLine);
writerBuf.write(sLine, 0, sLine.length());
writerBuf.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Dùng I/O Stream kết hợp với buffered
public static void myFileOperation_Stream(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
String sLine;
try (InputStream in = Files.newInputStream(p1);
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
OutputStream ou = Files.newOutputStream(p2, WRITE, APPEND);
BufferedWriter bou = new BufferedWriter(new OutputStreamWriter(ou));) {
while ((sLine = bin.readLine()) != null) {
log(sLine);
bou.write(sLine, 0, sLine.length());
bou.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Liệt kê các thư mục gốc
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
FileSystem dfFst = FileSystems.getDefault(); // get default File system
Iterable<Path> dirs = dfFst.getRootDirectories();
// or Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
for (Path d : dirs) {
log(d.toString());
}
Tạo thư mục:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\testDir\\subTestDir"); // có thể tạo nhiều dir nhiều level
Files.createDirectories(p1);
Liệt kê nội dung thư mục
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p1, "*.txt")) {
for (Path p : stream) {
log(p.getFileName());
}
} catch (DirectoryIteratorException e) {
e.printStackTrace();
}
Chú ý rằng trong: Files.newDirectoryStream(path, filter) thì filter nếu không có nó sẽ liệt kê tất cả. Nếu cần điều kiện để liệt kê, filter là dạng Glob String như trên.
Walking the File Tree
Để duyệt nội dung xuyên qua các thư mục khác nhau, hay 1 hoạt động cụ thể với 1 nhóm file trong 1 cây thư mục... chúng ta sẽ dùng tới các lớp và phương thức là implement của interface: FileVisitor
FileVisitor là 1 interface quy định cụ thể các hành vi cần thiết tại các điểm quan trọng trong quá trình "walking file tree":
- khi một tập tin được truy cập,
- trước khi một thư mục được truy cập,
- sau khi một thư mục được truy cập,
- hoặc khi thất bại xảy ra.
Giao diện có bốn phương pháp tương ứng với những tình huống.
Nếu chúng ta không cần triển khai 4 tình huống trên, thay vì implement FileVisitor, chúng ta sẽ extends SimpleFileVisitor:
import java.nio.file.FileVisitResult;
import java.nio.file.SimpleFileVisitor;
class PrintFiles extends SimpleFileVisitor<Path> {
/**
This is extended class for print all files in specified directory
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
Calc.log("Symbolic link: " + file.toString());
} else if (attr.isRegularFile()) {
Calc.log("Regular file: " + file.toString());
} else {
Calc.log("Other: " + file.toString());
}
Calc.log(" (" + attr.size() + "bytes)");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) {
Calc.log("Dir: " + dir.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
System.err.println(e);
return FileVisitResult.CONTINUE;
}
}
class Calc
{
public static void myDirectory(String args[]) throws IOException
{
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\testDir");
PrintFiles pf = new PrintFiles();
Files.walkFileTree(p2, pf);
}
public static void main(String args[])
{
try {
myDirectory(args);
}
catch (IOException e) {
log("Error: " + e);
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
}
Thứ Ba, ngày 07 tháng 1 năm 2014
Java - Những mảnh kiến thức nhỏ
- Class không có constructor thì compiler lấy constructor mặc định - không đối số, các thuộc tính gán giá trị mặc định.
Nếu lớp con không có constructor, compiler gọi 1 constructor mặc định, đồng thời gọi luôn constructor không đối số của lớp cha (lúc này nếu lớp cha có 1 constructor có đối số thì sẽ gây lỗi, thà không có luôn để compiler ban phát miễn phí constructor mặc định)
- Access level of class:
+ Outer class chỉ có thể là public (visible với mọi class) hoặc package private (ngầm định - không có từ khóa public - visible trong package). Điều tương tự với Interface.
+ Ở phạm vị member cũng tương tự, có thêm private - visible trong nội bộ class, protected - visible trong package và các lớp con (khác package).
+ Interface cũng chỉ có thể là public hay package, không thể private hay protected
+ Interface chỉ chứa thành viên public hoặc package private
+ Outer class hoạt động gần giống static, trong khi đó inner class thì không.
- Phân biệt Parameter và Argument
Parameter là tham số lúc khai báo/ định nghĩa method
Argument là giá trị thực truyền vào khi gọi method
- Trong java, không thể truyền method như là 1 parameter tới method khác, thay vào đó, ta có thể truyền đối tượng, sau đó gọi method sau.
- Khi hết sử dụng, tất cả các reference đến 1 object đều phải đặt về null thì mới có thể giải phóng object.
- Một method dùng 1 tên Class làm giá trị trả về, thì giá trị trả về phải là đối tượng của lớp đó, hoặc LỚP CON của lớp đó. Nếu dùng Interface như giá trị trả về, method đó phải trả về đối tượng của lớp implement Interface kia.
- Biến (thuộc tính) static của 1 class ngoài việc được gọi không cần thông qua đối tượng, còn 1 điều đáng lưu ý: Tất cả các lời gọi đến biến đó (từ gọi bằng [ClassName].[Biến static] đến việc gọi qua các đối tượng [DoiTuong1].[Biến static], [DoiTuong2].[Biến static]...) đều trỏ đến 1 vị trí duy nhất trên bộ nhớ Vì vậy, 1 thay đổi sẽ là thay đổi chung trên các đối tượng khác nhau (nếu có).
Các biến static còn được gọi là class variable, biến không static gọi là instance variable
- Nested class:
+ Chia 2 loại, static nested class và inner class (non-static)
++ Static nested class: (SNC)
- Giống như static field, SNC không truy cập được bất cứ phần tử instance của lớp cha, nếu muốn phải thông qua object, với các phần tử này, SNC truy cập y như các top-level class khác.
++ Inner class (IC):
- Truy cập các phần tử của lớp cha như mọi thành phần khác.
- Không thể chứa bất kỳ static member nào
- IC có thể chia thành 2 loại nhỏ:
+ Local class:
- Là class nằm trong 1 block,
- Có thể truy cập thành viên của lớp ngoài (không nằm trong block) thông qua: [Lớp outer].[Thành viên]
- Nếu Local class nằm trong static method thì nó chỉ truy cập được các thành viên static của lớp ngoài mà thôi (nhưng ngay cả khi đó, local class vẫn là non-static nhé, nó luôn luôn là vậy :)
- Truy cập các biến local (bên trong khối block) chỉ khi các biến này là final. Tuy vậy, từ SE 8 trở lên, local class có thể gọi trực tiếp parameter của method chứa nó.
- Không thể chứa bất kỳ static member nào. Trường hợp duy nhất là constant (constant là biến kiểu primitive hoặc String được khai báo và khởi tạo với compile-time constant expression - là một chuỗi hoặc một biểu thức số học có thể được xác định giá trị ở thời gian biên dịch)
+ Anonymous class:(AC)
- Xem ví dụ sau:
class Demo {
...
public class CHello { // Inner class
private String sMsg = "hello";
public void display(String aMsg) {
Calc.log(aMsg);
}
}
public void FHello() {
CHello annClass = new CHello() { // go to ANONYMOUS class - extends CHello
@Override
public void display(String aMsg) {
Calc.log(aMsg);
Calc.log(aMsg);
}
};
annClass.display("hello");
}
public static void main(String args[])
{
Demo ds = new Demo();
ds.FHello();
}
- AC được xem là 1 phần của biểu, như đã thấy trên ví dụ, nó kết thúc với ";"
- AC đi với việc extends 1 class hay implement 1 interface nào đó
- Trong AC, chí có thể là lệnh khai báo, method, chứ không thể chứa 1 statements nào.
- Trong AC, bạn không thể tạo constructor
- Các đặc điểm khác giống với Local class
- Enum:
Dạng đơn giản:
public enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
Hay phức tạp hơn:
enum EnumDemo { // Enum type class
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private double mass;
private double radius;
private EnumDemo(double aMass, double aRadius) {
mass = aMass;
radius = aRadius;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
}
...
public static void main(String args[])
{
EnumDemo ed = EnumDemo.MERCURY;
if (ed.equals(EnumDemo.MERCURY)) {
log("Mercury !");
log("Mass: " + EnumDemo.MERCURY.getMass());
log("Radius: " + ed.getRadius());
//EnumDemo.MERCURY = EnumDemo.EARTH; // Error
}
for (EnumDemo e : EnumDemo.values()) {
switch (e) {
case EARTH:
log(e + ": " + e.getMass() + " - " + e.getRadius() + " - We are here !");
break;
default:
log(e + ": " + e.getMass() + " - " + e.getRadius());
}
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
Có thể coi là 1 dạng class/ interface đặc biệt với 1 số đặc điểm sau:
+ Có thể định nghĩa constructor, method, variable...
+ Như ví dụ trên, bạn có thể gán các giá trị với từng constant của Enum, trong trường hợp này bạn phải khai báo biến và constructor vì MERCURY(3.303e+23, 2.4397e6) thực chất gọi 1 constructor 2 đối số double để gán giá trị. Constructor phải là private. Chúng ta không dùng new để tạo instance cho enum.
+ Mỗi constant đều ngầm định là static final, vì vậy, lệnh sau sẽ sai:
EnumDemo.MERCURY = EnumDemo.EARTH; // Error
+ Như chúng ta thấy, ta có thể dùng switch .. case để làm việc với Enum.
+ Enum ngầm định extends lớp java.lang.Enum, vậy nên nó không thể extends thêm class nào khác. Đồng thời nó có sẵn nhiều method như toString(), values() như ta đã sử dụng trong ví dụ.
+ Chúng ta cũng hoàn toàn có thể Override các method như toString()
- Vài thứ về interface và inheritance:
+ Giả sử ta có interface InterfaceDemo và class ClassDemo implements interface này.
Chú ý về việc dùng interface như 1 kiểu dữ liệu sau:
ClassDemo cd = new ClassDemo();
InterfaceDemo id = (InterfaceDemo)cd;
khi đó cd bắt buộc phải là object của lớp implements Interface này.
+ Ta có thể mở rộng 1 interface đã có bằng cách viết 1 interface khác extends nó (không nên thêm cập nhật vào interface cũ vì sẽ khiến các class implement phải triển khai thêm):
- field ngầm định luôn là public static final,
- method ngầm định là public abstract
+ Modifier - khả năng truy cập của các method Override luôn >= các method được override trong superclass.
+ Overloading - nạp chồng (giống tên method, khác signature) và Override - ghi đè (giống signature) không thể thể cùng xảy ra trên 1 method ở subclass
+ Override method ở subclass có thể có kiểu giá trị trả về là subtype của kiểu trả về của method được override superclass, khi đó gọi là covariant return type
+ Chúng ta xem ví dụ sau về triển khai 1 interaface:
- java.lang.Object class - superclass of all - một vài method:
+ clone():
- Nếu object không thuộc lớp implements của Cloneable interface thì sẽ báo lỗi: CloneNotSupportedException.
- Nếu muốn dùng được, hoặc override, hoặc thêm "implements Cloneable" vào class mà ta sẽ dùng,
- Đối với các object có tham chiếu các object khác, ta phải override method này để nó clone cả object ngoài kia.
+ equals()
- Chỉ so sánh đúng với primitive data type, reference data type gần như phải override lại,
- Khi override equals(), cần override cả hashCode()
+ finalize()
- Không dựa vào hàm này để cleanup object
- Method nào được gọi bởi constructor, nên đặt với final để nó "cố định" !
- Abstract class:
+ Tương tự interface, ngoại trừ:
- Có thể chứa các field không phải final, static
- Có 1 hay nhiều abstract method (chỉ ở dạng khai báo - chưa implement - giống interface)
- Có thể có hoặc không method được implement (cho phép các subclass kế thừa 1 số method định sẵn giống nhau, còn các method khác thì implement các kiểu khác nhau, linh hoạt hơn interface)
- Được dùng trong trường hợp chia sẻ với lớp con nhiều method (đã implemt) và có 1 số method khác nhau (abstract method)
+ Các subclass phải implment hết đống abstract đó, nếu không chính các subclass cũng phải là abstract
- Generics:
+ Chú ý rằng:
Integer là lớp con của Number, nhưng
Box<Integer> thì KHÔNG phải là lớp con của Box<Number>. Do đó:
Number number = new Number();
Integer integer = new Integer();
number = integer; // OK
integer = number; // NO
Box<Number> boxNum = new Box();
Box<Integer> boxInt = new Box();
boxNum = boxInt; // NO
boxTest(boxInt); // NO
Chú ý điều sau:
ArrayList<?> : là supertype của ArrayList<Integer>, ArrayList<String>,...
Trong khi ArrayList<Object> thì không phải. Do vậy, với method sau:
Nếu lớp con không có constructor, compiler gọi 1 constructor mặc định, đồng thời gọi luôn constructor không đối số của lớp cha (lúc này nếu lớp cha có 1 constructor có đối số thì sẽ gây lỗi, thà không có luôn để compiler ban phát miễn phí constructor mặc định)
- Access level of class:
+ Outer class chỉ có thể là public (visible với mọi class) hoặc package private (ngầm định - không có từ khóa public - visible trong package). Điều tương tự với Interface.
+ Ở phạm vị member cũng tương tự, có thêm private - visible trong nội bộ class, protected - visible trong package và các lớp con (khác package).
+ Interface cũng chỉ có thể là public hay package, không thể private hay protected
+ Interface chỉ chứa thành viên public hoặc package private
+ Outer class hoạt động gần giống static, trong khi đó inner class thì không.
- Phân biệt Parameter và Argument
Parameter là tham số lúc khai báo/ định nghĩa method
Argument là giá trị thực truyền vào khi gọi method
- Trong java, không thể truyền method như là 1 parameter tới method khác, thay vào đó, ta có thể truyền đối tượng, sau đó gọi method sau.
- Khi hết sử dụng, tất cả các reference đến 1 object đều phải đặt về null thì mới có thể giải phóng object.
- Một method dùng 1 tên Class làm giá trị trả về, thì giá trị trả về phải là đối tượng của lớp đó, hoặc LỚP CON của lớp đó. Nếu dùng Interface như giá trị trả về, method đó phải trả về đối tượng của lớp implement Interface kia.
- Biến (thuộc tính) static của 1 class ngoài việc được gọi không cần thông qua đối tượng, còn 1 điều đáng lưu ý: Tất cả các lời gọi đến biến đó (từ gọi bằng [ClassName].[Biến static] đến việc gọi qua các đối tượng [DoiTuong1].[Biến static], [DoiTuong2].[Biến static]...) đều trỏ đến 1 vị trí duy nhất trên bộ nhớ Vì vậy, 1 thay đổi sẽ là thay đổi chung trên các đối tượng khác nhau (nếu có).
Các biến static còn được gọi là class variable, biến không static gọi là instance variable
- Nested class:
+ Chia 2 loại, static nested class và inner class (non-static)
++ Static nested class: (SNC)
- Giống như static field, SNC không truy cập được bất cứ phần tử instance của lớp cha, nếu muốn phải thông qua object, với các phần tử này, SNC truy cập y như các top-level class khác.
++ Inner class (IC):
- Truy cập các phần tử của lớp cha như mọi thành phần khác.
- Không thể chứa bất kỳ static member nào
- IC có thể chia thành 2 loại nhỏ:
+ Local class:
- Là class nằm trong 1 block,
- Có thể truy cập thành viên của lớp ngoài (không nằm trong block) thông qua: [Lớp outer].[Thành viên]
- Nếu Local class nằm trong static method thì nó chỉ truy cập được các thành viên static của lớp ngoài mà thôi (nhưng ngay cả khi đó, local class vẫn là non-static nhé, nó luôn luôn là vậy :)
- Truy cập các biến local (bên trong khối block) chỉ khi các biến này là final. Tuy vậy, từ SE 8 trở lên, local class có thể gọi trực tiếp parameter của method chứa nó.
- Không thể chứa bất kỳ static member nào. Trường hợp duy nhất là constant (constant là biến kiểu primitive hoặc String được khai báo và khởi tạo với compile-time constant expression - là một chuỗi hoặc một biểu thức số học có thể được xác định giá trị ở thời gian biên dịch)
+ Anonymous class:(AC)
- Xem ví dụ sau:
class Demo {
...
public class CHello { // Inner class
private String sMsg = "hello";
public void display(String aMsg) {
Calc.log(aMsg);
}
}
public void FHello() {
CHello annClass = new CHello() { // go to ANONYMOUS class - extends CHello
@Override
public void display(String aMsg) {
Calc.log(aMsg);
Calc.log(aMsg);
}
};
annClass.display("hello");
}
public static void main(String args[])
{
Demo ds = new Demo();
ds.FHello();
}
- AC được xem là 1 phần của biểu, như đã thấy trên ví dụ, nó kết thúc với ";"
- AC đi với việc extends 1 class hay implement 1 interface nào đó
- Trong AC, chí có thể là lệnh khai báo, method, chứ không thể chứa 1 statements nào.
- Trong AC, bạn không thể tạo constructor
- Các đặc điểm khác giống với Local class
- Enum:
Dạng đơn giản:
public enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
Hay phức tạp hơn:
enum EnumDemo { // Enum type class
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private double mass;
private double radius;
private EnumDemo(double aMass, double aRadius) {
mass = aMass;
radius = aRadius;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
}
...
public static void main(String args[])
{
EnumDemo ed = EnumDemo.MERCURY;
if (ed.equals(EnumDemo.MERCURY)) {
log("Mercury !");
log("Mass: " + EnumDemo.MERCURY.getMass());
log("Radius: " + ed.getRadius());
//EnumDemo.MERCURY = EnumDemo.EARTH; // Error
}
for (EnumDemo e : EnumDemo.values()) {
switch (e) {
case EARTH:
log(e + ": " + e.getMass() + " - " + e.getRadius() + " - We are here !");
break;
default:
log(e + ": " + e.getMass() + " - " + e.getRadius());
}
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
Có thể coi là 1 dạng class/ interface đặc biệt với 1 số đặc điểm sau:
+ Có thể định nghĩa constructor, method, variable...
+ Như ví dụ trên, bạn có thể gán các giá trị với từng constant của Enum, trong trường hợp này bạn phải khai báo biến và constructor vì MERCURY(3.303e+23, 2.4397e6) thực chất gọi 1 constructor 2 đối số double để gán giá trị. Constructor phải là private. Chúng ta không dùng new để tạo instance cho enum.
+ Mỗi constant đều ngầm định là static final, vì vậy, lệnh sau sẽ sai:
EnumDemo.MERCURY = EnumDemo.EARTH; // Error
+ Như chúng ta thấy, ta có thể dùng switch .. case để làm việc với Enum.
+ Enum ngầm định extends lớp java.lang.Enum, vậy nên nó không thể extends thêm class nào khác. Đồng thời nó có sẵn nhiều method như toString(), values() như ta đã sử dụng trong ví dụ.
+ Chúng ta cũng hoàn toàn có thể Override các method như toString()
- Vài thứ về interface và inheritance:
+ Giả sử ta có interface InterfaceDemo và class ClassDemo implements interface này.
Chú ý về việc dùng interface như 1 kiểu dữ liệu sau:
ClassDemo cd = new ClassDemo();
InterfaceDemo id = (InterfaceDemo)cd;
khi đó cd bắt buộc phải là object của lớp implements Interface này.
+ Ta có thể mở rộng 1 interface đã có bằng cách viết 1 interface khác extends nó (không nên thêm cập nhật vào interface cũ vì sẽ khiến các class implement phải triển khai thêm):
public interface DoItPlus extends DoIt { boolean didItWork(int i, double x, String s); }+ Trnog interface:
- field ngầm định luôn là public static final,
- method ngầm định là public abstract
+ Modifier - khả năng truy cập của các method Override luôn >= các method được override trong superclass.
+ Overloading - nạp chồng (giống tên method, khác signature) và Override - ghi đè (giống signature) không thể thể cùng xảy ra trên 1 method ở subclass
+ Override method ở subclass có thể có kiểu giá trị trả về là subtype của kiểu trả về của method được override superclass, khi đó gọi là covariant return type
+ Chúng ta xem ví dụ sau về triển khai 1 interaface:
- java.lang.Object class - superclass of all - một vài method:
+ clone():
- Nếu object không thuộc lớp implements của Cloneable interface thì sẽ báo lỗi: CloneNotSupportedException.
- Nếu muốn dùng được, hoặc override, hoặc thêm "implements Cloneable" vào class mà ta sẽ dùng,
- Đối với các object có tham chiếu các object khác, ta phải override method này để nó clone cả object ngoài kia.
+ equals()
- Chỉ so sánh đúng với primitive data type, reference data type gần như phải override lại,
- Khi override equals(), cần override cả hashCode()
+ finalize()
- Không dựa vào hàm này để cleanup object
- Method nào được gọi bởi constructor, nên đặt với final để nó "cố định" !
- Abstract class:
+ Tương tự interface, ngoại trừ:
- Có thể chứa các field không phải final, static
- Có 1 hay nhiều abstract method (chỉ ở dạng khai báo - chưa implement - giống interface)
- Có thể có hoặc không method được implement (cho phép các subclass kế thừa 1 số method định sẵn giống nhau, còn các method khác thì implement các kiểu khác nhau, linh hoạt hơn interface)
- Được dùng trong trường hợp chia sẻ với lớp con nhiều method (đã implemt) và có 1 số method khác nhau (abstract method)
+ Các subclass phải implment hết đống abstract đó, nếu không chính các subclass cũng phải là abstract
- Generics:
+ Chú ý rằng:
Integer là lớp con của Number, nhưng
Box<Integer> thì KHÔNG phải là lớp con của Box<Number>. Do đó:
Number number = new Number();
Integer integer = new Integer();
number = integer; // OK
integer = number; // NO
Box<Number> boxNum = new Box();
Box<Integer> boxInt = new Box();
boxNum = boxInt; // NO
public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK
Box<Number> box = new Box<Number>(); box.add(new Integer(10)); // OK box.add(new Double(10.1)); // OKNhưng:
public void boxTest(Box<Number> n) { /* ... */ }Box<Integer> boxInt = new Box<Integer>();
boxTest(boxInt); // NO
Chú ý điều sau:
ArrayList<?> : là supertype của ArrayList<Integer>, ArrayList<String>,...
Trong khi ArrayList<Object> thì không phải. Do vậy, với method sau:
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }Không chấp nhận List<Integer>, List<String>..., nhưng
public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }thì chấp hết
Thứ Hai, ngày 06 tháng 1 năm 2014
Java - Concurrency - Đa luồng
Giống như nhiều ngôn ngữ khác, java cũng hỗ trợ đa luồng, đa nhiệm, 2 đơn vị căn bản là process và thread.
Nếu như 1 process sở hữu cho mình 1 môi trường thực thi riêng, bao gồm bộ nhớ, tài nguyên, thì thread, lại chia sẻ tài nguyên, bộ nhớ của process chứa nó.
Nói chung process và thread là những khái niệm khá cơ bản trong vấn đề đa nhiệm, vì vậy bạn nên tìm hiểu về nền tảng và sau đó hãy xem xét đến môi trường của java.
Ngay cả CPU 1 core thì cũng đảm nhận đa nhiệm, nhiều process, thread cùng lúc được, vì CPU biết các chia thời gian xử lý thành những phần nhỏ gọi là slicing và xen lẫn các thread vào các slicing đó.
Một chương trình có thể có nhiều process liên kết với nhau qua IPC (pipes, sockets,...), 1 process có ít nhất 1 thread, khi chúng ta tạo 1 ứng dụng đơn giản nhất thì mặc định chúng ta có main thread, main thread có thể tạo các thread mới...
Chúng ta sẽ chủ yếu nói về Thread.
1. Thread Object:
a. Định nghĩa và khởi động 1 thread:
Có 2 cách sau định nghĩa và khởi động 1 process:
a1. Implements Runnable interface: Chúng ta phải Override run() method
a2. Extends Thread class: Chúng ta cũng phải Override run() method
Chúng ta sẽ dùng cách thứ 1 vì Runnable Object có thể subclass của nhiều class khác ngoài Thread.
b. Sleep 1 thread:
Làm cho thread suspend 1 khoảng thời gian được chỉ định. Để sử dụng được chúng ta phải làm thủ tục throw/catch InterruptedException. Hãy xem 1 ví dụ:
c. Interrup
Dừng thực thi công việc hiện tại để làm việc khác, tuy nhiên, đa phần là terminal thread.
Để thread bị interrupt hoạt động đúng thì thread cần "Hỗ trợ Interruption":
- Đối với thread thực thi thường xuyên các method gây InterruptedException (như sleep, join,...) thì cần return từ run() method ngay khi catch Exception này.
- Đối với những thread lâu không thực thi các method gây InterruptedException, cần định kỳ kiểm tra và return như thế này:
hay
d. Join
t.join() hay t.join(miliseconds) có nghĩa là thread hiện tại (thread gọi method này) sẽ tạm ngưng và chỉ trở lại khi thread t thực thi xong (hoặc 1 khoảng thời gian nếu chỉ định số miliseconds)
2. Synchronization
Các thread có thể chia sẻ với nhau quyền truy cập đến các field cũng như là các object được reference.tới. Việc này mang lại hiệu quả nhưng nhiều khi gây nên nhiều lỗi như thread interference hay memory consistency error.
Chúng ta có thể dùng Synchronization để giải quyết các vấn đề trên (thực ra việc này cũng có thể gây ra lỗi thread contention - gây chậm chương trình, các thread liên quan)
a. Thread Interference:
Đây là lỗi khi nhiều thread cùng truy cập và xử lý với cùng 1 đối tượng trong cùng khoảng thời gian, dẫn tói các thread gây can thiệp lẫn nhau không mong muốn. Ví dụ:
Bạn có tưởng tượng được gì nếu 2 thread A và B cùng gọi và xử lý 1 Counter object, thread A gọi increment() method, thread B gọi decrement() method, sau đó cả 2 gọi value() method để in kết quả.
Nếu xuất phát ban đầu là c=0, thì:
b. Memory consistency error:
Giả sử ta có:
c = 0;
#1: c++; thread A
#2: System.out.println(c); thread B
Vấn đề là, không phải lúc nào thread A cũng thực hiện lệnh của mình trước thread B, và khi đó, mọi ý đồ của coder sẽ bị phá vỡ.
Lỗi này được nói chung là: các thread khác nhau có cách nhìn không tương xứng mà lẽ ra phải có với cùng 1 dữ liệu.
Có thể tránh lỗi này với thủ tục gọi là happens-before relationship.
c. Happens-before relationship:
Trước hết chúng ta cần hiểu rằng, thứ tự của các dòng lệnh trong mã nguồn chương trình hoàn toàn có thể bị đảo lộn, sắp xếp lại bởi compiler, JVM hay processer, điều này để dảm bảo tính hiệu quả và yêu cầu từ các nhà phát triển khác nhau.
Như vậy, nguy cơ về chuyện 1 lệnh ở 1 thread thực thi nhưng với những dữ liệu chưa được cập nhật đúng cách trên bộ nhớ - do nó không "nhìn thấy" kết quả từ lênh trước theo như ý đồ của coder (phần trên đã nói). Để khắc phục điều này, thủ tục happens-before relationship được đề xướng.
Nếu A và B được gọi trong cùng thread, chúng ta luôn được đảm bảo chúng sẽ được gọi theo thứ tự nhu trong mã nguồn.
Nếu khác thread, A happens-before relationship với B, đảm bảo rằng A sẽ được gọi trước B hay nói cách khác B nhìn thấy kết quả từ A.
Nhưng chúng ta phải chú ý 1 điều, A và B ở đây không phải 1 chỉ lệnh, mà là 1 action, bao gồm các thao tác: Đọc/ viết các biến, lock/ unlock monitor, starting/ joining thread.
Sở dĩ như vậy vì 1 lệnh đôi khi không cần thiết phải giữ đúng thứ tự, nếu điều đó không ảnh hưởng đến kết quả, ví dụ:
x = 3; // (1)
y = 4; // (2)
...
Ở đây lệnh (1) và (2) có thể đổi thứ tự được.
Synchronization là 1 trong nhiều cách thực hiện happens-before relationship
d. Synchronization:
Như các vấn đề đã nói trên, chúng ta cần đảm bảo rằng tại 1 thời điểm chỉ 1 thread được truy cập 1 đối tượng, thread này thực hiện xong đến lượt thread khác.
Có 2 cách chính: Syn. method và Syn. statement:
d1. Syn. method:
Như ta thấy, chỉ cần thêm keyword: synchronized trước method là được. Với cách này ta có 2 hệ quả sau:
- Đảm bảo rằng, trong 1 thời điểm, 1 synchronized method của 1 đối tượng chỉ được thực hiện bởi 1 thread (các thread khác sẽ bị block nếu cùng nhắm tới method này, cho đến khi thread kia hoàn thành)
- Sau khi 1 synchronized method exit, nó tự động tạo happens-before relationship với bất cứ method được gọi theo sau nào của cùng đối tượng.
Chú ý constructor không synchronized được và cũng không cần (vì với 1 đối tượng thì nó chỉ được gọi 1 lần bởi 1 thread)
d2. Synchronized statement (block):
Trước hết chúng ta nói qua về lock hay monitor. Mỗi object có cho mình 1 lock, 1 thread muốn làm việc với object này phải "own" được lock của nó. Trước khi thực thi với object, thread phải acquire lock, sau khi thực hiện xong, thread release lock này. Trong khi 1 thread đang own 1 object clock, các thread khác phải đợi cho đến khi thread đó nhả ra (release).
Synchronized statement khác Synchronized method ở chỗ Synchronized statement chỉ định cụ thể object.
Cơ chế của việc này như sau: khi nhận thấy có từ khóa Synchronized, compiler làm cho các khối lệnh làm thủ tục own lock của object được chỉ định, như vậy ta đảm bảo được tính đồng bộ hóa cho các thread với đối tượng này. Ví dụ:
class Callme {
void call(String msg) {
synchronized(this) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
//synchronized(target) { // synchronized block
target.call(msg);
//}
}
}
public class Synch {
public static void main(String args[]) {
Callme myTarget = new Callme();
Caller ob1 = new Caller(myTarget, "Hello");
Caller ob2 = new Caller(myTarget, "Synchronized");
Caller ob3 = new Caller(myTarget, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
3. Một số vấn đề với multithread:
a. Deadlock:
Sửu dụng từ khóa synchronized để đồng bộ hóa cũng gây ra những vấn đề ngoài ý muốn. Deadlock là vấn đề khi 2 hay nhiều thread đợi lẫn nhau, dẫn đến vô thời hạn, thread đợi lẫn nhau vì thread A đang giữ lock của đối tượng a, và có 1 method nào đấy muốn truy cập đến đối tượng b được lock bởi thread B và thế là thread A phải đợi thread B làm xong phần việc và nhả b ra, rủi thay thread B cũng cần truy cập qua a (ngược lại), và thế là 2 thread đợi nhau trong... vô vọng.
Ví dụ 1:
public class MyDeadlock {
private static MyDeadlock a = new MyDeadlock();
private static MyDeadlock b = new MyDeadlock();
public synchronized void testFunc(MyDeadlock friend) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside testFunc !");
System.out.println(threadName + ": Prepare to get friend cross...");
try { // cause to asynchronization if not use synchronized keyword
Thread.sleep(2000);
} catch(InterruptedException e) {
System.out.println(threadName + " is interrupted !");
}
friend.cross();
}
public synchronized void cross() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside croos !");
}
static class MyRunnable implements Runnable {
public void run() {
a.testFunc(b); // by additional thread (t)
}
}
public static void main(String... args) {
Thread t = new Thread(new MyRunnable(), "DEMO THREAD");
t.start();
b.testFunc(a); // by Main thread
}
}
Hoặc ví dụ 2:
Ở ví dụ 2, nếu thread A thực thi oneMethod(), threadB thực thi anotherMethod(). Chúng ta hãy tưởng tượng khi thread A gọi oneMethod(), nó sẽ lock cacheLock đầu tiên, thread B gọi anotherMethod(), nó sẽ lock tableLock. Sau đó, thread A đòi lock tiếp tableLock, tương tự thread B cũng đòi lock tiếp cacheLock (theo thứ tự trong mỗi method). Và deadlock xảy ra.
Ví dụ 3:
Khắc phục/ tránh deadlock:
Trong vd 2 và 3, để ý nếu ta biết cách xếp đặt thứ tự lock các đối tượng của các thread giống nhau thì sẽ giải quyết được.
ví dụ 3: sửa method transfer():
Một phương pháp nữa là giảm scope giữa các phương thức đồng bộ (hạn chế việc 2 các method này ở các lớp khác nhau,...) xuống tối đa có thể.
b. Starvation:
Một thread không thể truy cập tài nguyên, biến... do bị chiếm bởi 1 thread tham lam khác
c.Livelock:
Một vấn đề nữa là tình trạng 2 hay nhiều thread cùng truy cập đến 1 tài nguyên, biến,... dùng chung, khác với deadlock - các thread bị block, không có sự hoạt động, trong livelock, các thread luôn thay đổi trạng thái mặc dù không có sự tiến triển nào của chương trình.
Nếu như 1 process sở hữu cho mình 1 môi trường thực thi riêng, bao gồm bộ nhớ, tài nguyên, thì thread, lại chia sẻ tài nguyên, bộ nhớ của process chứa nó.
Nói chung process và thread là những khái niệm khá cơ bản trong vấn đề đa nhiệm, vì vậy bạn nên tìm hiểu về nền tảng và sau đó hãy xem xét đến môi trường của java.
Ngay cả CPU 1 core thì cũng đảm nhận đa nhiệm, nhiều process, thread cùng lúc được, vì CPU biết các chia thời gian xử lý thành những phần nhỏ gọi là slicing và xen lẫn các thread vào các slicing đó.
Một chương trình có thể có nhiều process liên kết với nhau qua IPC (pipes, sockets,...), 1 process có ít nhất 1 thread, khi chúng ta tạo 1 ứng dụng đơn giản nhất thì mặc định chúng ta có main thread, main thread có thể tạo các thread mới...
Chúng ta sẽ chủ yếu nói về Thread.
1. Thread Object:
a. Định nghĩa và khởi động 1 thread:
Có 2 cách sau định nghĩa và khởi động 1 process:
a1. Implements Runnable interface: Chúng ta phải Override run() method
public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) {
HelloRunnable run = new HelloRunnable();
Thread t = new Thread(run, "HELLO THREAD !");
t.start(); } }
a2. Extends Thread class: Chúng ta cũng phải Override run() method
public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) {
HelloThread t = new HelloThread();
t.start(); } }
Chúng ta sẽ dùng cách thứ 1 vì Runnable Object có thể subclass của nhiều class khác ngoài Thread.
b. Sleep 1 thread:
Làm cho thread suspend 1 khoảng thời gian được chỉ định. Để sử dụng được chúng ta phải làm thủ tục throw/catch InterruptedException. Hãy xem 1 ví dụ:
public class SleepMessages { public static void main(String args[]) throws InterruptedException { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; for (int i = 0; i < importantInfo.length; i++) { //Pause for 4 seconds Thread.sleep(4000); //Print a message System.out.println(importantInfo[i]); } } }
c. Interrup
Dừng thực thi công việc hiện tại để làm việc khác, tuy nhiên, đa phần là terminal thread.
Để thread bị interrupt hoạt động đúng thì thread cần "Hỗ trợ Interruption":
- Đối với thread thực thi thường xuyên các method gây InterruptedException (như sleep, join,...) thì cần return từ run() method ngay khi catch Exception này.
public void run() {
for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds try { Thread.sleep(4000); } catch (InterruptedException e) { // We've been interrupted: no more messages. return; } // Print a message System.out.println(importantInfo[i]); }
}
- Đối với những thread lâu không thực thi các method gây InterruptedException, cần định kỳ kiểm tra và return như thế này:
if (Thread.interrupted()) { // We've been interrupted: no more crunching. return; }
hay
if (Thread.interrupted()) { throw new InterruptedException(); }
d. Join
t.join() hay t.join(miliseconds) có nghĩa là thread hiện tại (thread gọi method này) sẽ tạm ngưng và chỉ trở lại khi thread t thực thi xong (hoặc 1 khoảng thời gian nếu chỉ định số miliseconds)
2. Synchronization
Các thread có thể chia sẻ với nhau quyền truy cập đến các field cũng như là các object được reference.tới. Việc này mang lại hiệu quả nhưng nhiều khi gây nên nhiều lỗi như thread interference hay memory consistency error.
Chúng ta có thể dùng Synchronization để giải quyết các vấn đề trên (thực ra việc này cũng có thể gây ra lỗi thread contention - gây chậm chương trình, các thread liên quan)
a. Thread Interference:
Đây là lỗi khi nhiều thread cùng truy cập và xử lý với cùng 1 đối tượng trong cùng khoảng thời gian, dẫn tói các thread gây can thiệp lẫn nhau không mong muốn. Ví dụ:
class Counter { private int c = 0; public void increment() { c++; } public void decrement() { c--; } public int value() { return c; } }
Bạn có tưởng tượng được gì nếu 2 thread A và B cùng gọi và xử lý 1 Counter object, thread A gọi increment() method, thread B gọi decrement() method, sau đó cả 2 gọi value() method để in kết quả.
Nếu xuất phát ban đầu là c=0, thì:
- Thread A: Nhận giá trị từ c.
- Thread B: Nhận giá trị từ c.
- Thread A: Increment giá trị mới nhận; kết qủa là 1.
- Thread B: Decrement giá trị mới nhận; kết quả là -1.
- Thread A: Lưu giá trị vào c; c = 1.
- Thread B: Lưu giá trị vào c; c = -1.
b. Memory consistency error:
Giả sử ta có:
c = 0;
#1: c++; thread A
#2: System.out.println(c); thread B
Vấn đề là, không phải lúc nào thread A cũng thực hiện lệnh của mình trước thread B, và khi đó, mọi ý đồ của coder sẽ bị phá vỡ.
Lỗi này được nói chung là: các thread khác nhau có cách nhìn không tương xứng mà lẽ ra phải có với cùng 1 dữ liệu.
Có thể tránh lỗi này với thủ tục gọi là happens-before relationship.
c. Happens-before relationship:
Trước hết chúng ta cần hiểu rằng, thứ tự của các dòng lệnh trong mã nguồn chương trình hoàn toàn có thể bị đảo lộn, sắp xếp lại bởi compiler, JVM hay processer, điều này để dảm bảo tính hiệu quả và yêu cầu từ các nhà phát triển khác nhau.
Như vậy, nguy cơ về chuyện 1 lệnh ở 1 thread thực thi nhưng với những dữ liệu chưa được cập nhật đúng cách trên bộ nhớ - do nó không "nhìn thấy" kết quả từ lênh trước theo như ý đồ của coder (phần trên đã nói). Để khắc phục điều này, thủ tục happens-before relationship được đề xướng.
Nếu A và B được gọi trong cùng thread, chúng ta luôn được đảm bảo chúng sẽ được gọi theo thứ tự nhu trong mã nguồn.
Nếu khác thread, A happens-before relationship với B, đảm bảo rằng A sẽ được gọi trước B hay nói cách khác B nhìn thấy kết quả từ A.
Nhưng chúng ta phải chú ý 1 điều, A và B ở đây không phải 1 chỉ lệnh, mà là 1 action, bao gồm các thao tác: Đọc/ viết các biến, lock/ unlock monitor, starting/ joining thread.
Sở dĩ như vậy vì 1 lệnh đôi khi không cần thiết phải giữ đúng thứ tự, nếu điều đó không ảnh hưởng đến kết quả, ví dụ:
x = 3; // (1)
y = 4; // (2)
...
Ở đây lệnh (1) và (2) có thể đổi thứ tự được.
Synchronization là 1 trong nhiều cách thực hiện happens-before relationship
d. Synchronization:
Như các vấn đề đã nói trên, chúng ta cần đảm bảo rằng tại 1 thời điểm chỉ 1 thread được truy cập 1 đối tượng, thread này thực hiện xong đến lượt thread khác.
Có 2 cách chính: Syn. method và Syn. statement:
d1. Syn. method:
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
Như ta thấy, chỉ cần thêm keyword: synchronized trước method là được. Với cách này ta có 2 hệ quả sau:
- Đảm bảo rằng, trong 1 thời điểm, 1 synchronized method của 1 đối tượng chỉ được thực hiện bởi 1 thread (các thread khác sẽ bị block nếu cùng nhắm tới method này, cho đến khi thread kia hoàn thành)
- Sau khi 1 synchronized method exit, nó tự động tạo happens-before relationship với bất cứ method được gọi theo sau nào của cùng đối tượng.
Chú ý constructor không synchronized được và cũng không cần (vì với 1 đối tượng thì nó chỉ được gọi 1 lần bởi 1 thread)
d2. Synchronized statement (block):
Trước hết chúng ta nói qua về lock hay monitor. Mỗi object có cho mình 1 lock, 1 thread muốn làm việc với object này phải "own" được lock của nó. Trước khi thực thi với object, thread phải acquire lock, sau khi thực hiện xong, thread release lock này. Trong khi 1 thread đang own 1 object clock, các thread khác phải đợi cho đến khi thread đó nhả ra (release).
Synchronized statement khác Synchronized method ở chỗ Synchronized statement chỉ định cụ thể object.
public class Counter { private int count = 0; public void increment() { synchronized (this) { count++; } } public int getCount() { synchronized (this) { return count; } } }
Cơ chế của việc này như sau: khi nhận thấy có từ khóa Synchronized, compiler làm cho các khối lệnh làm thủ tục own lock của object được chỉ định, như vậy ta đảm bảo được tính đồng bộ hóa cho các thread với đối tượng này. Ví dụ:
class Callme {
void call(String msg) {
synchronized(this) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
//synchronized(target) { // synchronized block
target.call(msg);
//}
}
}
public class Synch {
public static void main(String args[]) {
Callme myTarget = new Callme();
Caller ob1 = new Caller(myTarget, "Hello");
Caller ob2 = new Caller(myTarget, "Synchronized");
Caller ob3 = new Caller(myTarget, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
3. Một số vấn đề với multithread:
a. Deadlock:
Sửu dụng từ khóa synchronized để đồng bộ hóa cũng gây ra những vấn đề ngoài ý muốn. Deadlock là vấn đề khi 2 hay nhiều thread đợi lẫn nhau, dẫn đến vô thời hạn, thread đợi lẫn nhau vì thread A đang giữ lock của đối tượng a, và có 1 method nào đấy muốn truy cập đến đối tượng b được lock bởi thread B và thế là thread A phải đợi thread B làm xong phần việc và nhả b ra, rủi thay thread B cũng cần truy cập qua a (ngược lại), và thế là 2 thread đợi nhau trong... vô vọng.
Ví dụ 1:
public class MyDeadlock {
private static MyDeadlock a = new MyDeadlock();
private static MyDeadlock b = new MyDeadlock();
public synchronized void testFunc(MyDeadlock friend) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside testFunc !");
System.out.println(threadName + ": Prepare to get friend cross...");
try { // cause to asynchronization if not use synchronized keyword
Thread.sleep(2000);
} catch(InterruptedException e) {
System.out.println(threadName + " is interrupted !");
}
friend.cross();
}
public synchronized void cross() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside croos !");
}
static class MyRunnable implements Runnable {
public void run() {
a.testFunc(b); // by additional thread (t)
}
}
public static void main(String... args) {
Thread t = new Thread(new MyRunnable(), "DEMO THREAD");
t.start();
b.testFunc(a); // by Main thread
}
}
Hoặc ví dụ 2:
public static Object cacheLock = new Object(); public static Object tableLock = new Object(); ... public void oneMethod() { synchronized (cacheLock) { synchronized (tableLock) { doSomething(); } } } public void anotherMethod() { synchronized (tableLock) { synchronized (cacheLock) { doSomethingElse(); } } }
Ở ví dụ 2, nếu thread A thực thi oneMethod(), threadB thực thi anotherMethod(). Chúng ta hãy tưởng tượng khi thread A gọi oneMethod(), nó sẽ lock cacheLock đầu tiên, thread B gọi anotherMethod(), nó sẽ lock tableLock. Sau đó, thread A đòi lock tiếp tableLock, tương tự thread B cũng đòi lock tiếp cacheLock (theo thứ tự trong mỗi method). Và deadlock xảy ra.
Ví dụ 3:
// File Name ThreadSafeBankAccount.java public class ThreadSafeBankAccount { private double balance; private int number; public ThreadSafeBankAccount(int num, double initialBalance) { balance = initialBalance; number = num; } public int getNumber() { return number; } public double getBalance() { return balance; } public void deposit(double amount) { synchronized(this) { double prevBalance = balance; try { Thread.sleep(4000); }catch(InterruptedException e) {} balance = prevBalance + amount; } } public void withdraw(double amount) { synchronized(this) { double prevBalance = balance; try { Thread.sleep(4000); }catch(InterruptedException e) {} balance = prevBalance - amount; } } } // File Name LazyTeller.java public class LazyTeller extends Thread { private ThreadSafeBankAccount source, dest; public LazyTeller(ThreadSafeBankAccount a, ThreadSafeBankAccount b) { source = a; dest = b; } public void run() { transfer(250.00); } public void transfer(double amount) { System.out.println("Transferring from " + source.getNumber() + " to " + dest.getNumber()); synchronized(source) { Thread.yield(); synchronized(dest) { System.out.println("Withdrawing from " + source.getNumber()); source.withdraw(amount); System.out.println("Depositing into " + dest.getNumber()); dest.deposit(amount); } } } } public class DeadlockDemo { public static void main(String[] args) { System.out.println("Creating two bank accounts..."); ThreadSafeBankAccount checking = new ThreadSafeBankAccount(101, 1000.00); ThreadSafeBankAccount savings = new ThreadSafeBankAccount(102, 5000.00); System.out.println("Creating two teller threads..."); Thread teller1 = new LazyTeller(checking, savings); Thread teller2 = new LazyTeller(savings, checking); System.out.println("Starting both threads..."); teller1.start(); teller2.start(); } }Khi teller1 gọi transfer(), nó lock source - chính là checking, cùng lúc teller2 gọi transfer() và lock dest - tức saving. Ngay sau khi lock checking, teller1 đòi lock cả saving, ngược lại teller2 lại đòi lock checking (nghiệp vụ transfer đòi hỏi có source và dest để chuyển tiền !). Và thế là deadlock.
Khắc phục/ tránh deadlock:
Trong vd 2 và 3, để ý nếu ta biết cách xếp đặt thứ tự lock các đối tượng của các thread giống nhau thì sẽ giải quyết được.
public static Object cacheLock = new Object(); public static Object tableLock = new Object(); ... public void oneMethod() { synchronized (cacheLock) { synchronized (tableLock) { doSomething(); } } } public void anotherMethod() {
synchronized (cacheLock) {
synchronized (tableLock) { doSomethingElse(); } } }
ví dụ 3: sửa method transfer():
public void transfer(double amount) { System.out.println("Transferring from " + source.getNumber() + " to " + dest.getNumber()); ThreadSafeBankAccount first, second; if(source.getNumber() < dest.getNumber()) { first = source; second = dest; } else { first = dest; second = source; } synchronized(first) { Thread.yield(); synchronized(second) { System.out.println("Withdrawing from " + source.getNumber()); source.withdraw(amount); System.out.println("Depositing into " + dest.getNumber()); dest.deposit(amount); } } }(dùng first và second làm trung gian để đảm bảo 2 thread luôn lock object có getName() trả về nhỏ hơn - first, trước).
Một phương pháp nữa là giảm scope giữa các phương thức đồng bộ (hạn chế việc 2 các method này ở các lớp khác nhau,...) xuống tối đa có thể.
b. Starvation:
Một thread không thể truy cập tài nguyên, biến... do bị chiếm bởi 1 thread tham lam khác
c.Livelock:
Một vấn đề nữa là tình trạng 2 hay nhiều thread cùng truy cập đến 1 tài nguyên, biến,... dùng chung, khác với deadlock - các thread bị block, không có sự hoạt động, trong livelock, các thread luôn thay đổi trạng thái mặc dù không có sự tiến triển nào của chương trình.
Đăng ký: Bài đăng (Atom)

markdown:
1.
Ẩn thanh tiêu đề:
C1:
ActionBar actionBar = getActionBar();
actionBar.hide();
C2:
this.requestWindowFeature(Window.FEATURE_NO_TITLE); (trên setContentView)
C3:
thêm thuộc tính : android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen vào activity trong Manifest
# Nguoixanh tihon
## Thứ Hai, ngày 17 tháng 2 năm 2014
## Thứ Bảy, ngày 11 tháng 1 năm 2014
###
[Java - Exception](http://nguoixanhtihon.blogspot.com/2014/01/java-exception.html)
1. Một số thuật ngữ:
- Exception: Một sự kiện "ngoại lệ" - ngoài mong muốn và làm cho luồng thực thi bị ngắt quãng, thay đổi, chấm dứt.
- Exception object: Khi exception xảy ra, runtime system sẽ sinh ra 1 đối tượng tương ứng với lỗi đấy (trong java người ta xây dựng sẵn các lớp của Exception - là các lớp con của java.lang.Throwable - các đối tượng này là các instance của các lớp). Các đối tượng này (object) chứa thông tin của các exception.
- Throwing an exception: Hành động tạo object và xử lý (gửi đi) trong runtime system.
- Call stack: Khi exception được throw, runtime system sẽ tìm kiếm các cách thức xử lý exception. Thứ tự tìm kiếm là các method theo thứ tự ngược với thứ tự gọi phương thức (nghĩa là tìm từ trên xuống):
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
}
throw new IOException ("File not found");
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
} finally {
if (sc != null)
sc.close();
}
public static void readFile(String args[]) throws IOException
{
Scanner sc = null;
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null) // không bắt buộc
throw new IOException ("File not found"); // không bắt buộc
while (sc.hasNext()) {
log(sc.next());
}
}
Đây là cách 2 mà chúng ta nói tới. Khi đó, hàm gọi tói phương thức readFile (ví dụ là main) phải như thế này:
public static void main(String args[])
{
try {
readFile(args);
}
catch (IOException e) {
log("Error: " + e.getMessage());
}
}
(Nói chung là trước sau gì cũng phải catch Exception)
6. Class tự tạo để xử lý Exception
Đôi khi ta hay gặp câu lệnh:
throw new MyException(); // MyException là user-define class
Cũng khá đơn giản, bạn chỉ việc tạo thêm class MyException:
class MyException extends NegativeArraySizeException
{
MyException() {
super("MyException occur !");
}
}
public static void test(String args[]) throws MyException
{
int i = -1;
if (i < 0)
throw new MyException();
}
public static void main(String args[])
{
try {
test(args);
}
catch (MyException e) {
log("Error: " + e);
}
}
- Exception: Một sự kiện "ngoại lệ" - ngoài mong muốn và làm cho luồng thực thi bị ngắt quãng, thay đổi, chấm dứt.
- Exception object: Khi exception xảy ra, runtime system sẽ sinh ra 1 đối tượng tương ứng với lỗi đấy (trong java người ta xây dựng sẵn các lớp của Exception - là các lớp con của java.lang.Throwable - các đối tượng này là các instance của các lớp). Các đối tượng này (object) chứa thông tin của các exception.
- Throwing an exception: Hành động tạo object và xử lý (gửi đi) trong runtime system.
- Call stack: Khi exception được throw, runtime system sẽ tìm kiếm các cách thức xử lý exception. Thứ tự tìm kiếm là các method theo thứ tự ngược với thứ tự gọi phương thức (nghĩa là tìm từ trên xuống):
Hình ảnh này gọi là Call stack
- Exception handler: Đoạn code mà sẽ xử lý exception.
- Catching an exception: Mỗi exception handler xử lý 1 loại exception khác nhau, một khi runtime system tìm thấy 1 handler phù hợp để xử lý, hành động đó gọi là catch
Nếu không tìm ra handler tương ứng, chương trình sẽ dừng lại.
2. Các loại exception:
Cos 3 loại:
a. Checked exception: Là exception mà có thể dự đoán được và khôi phục được, ví dụ các lỗi kiểm tra file có tồn tại không?...
b. Error: Lỗi bên ngoài chương trình, hay liên quan đến JVM, không dự đoán hay phục hồi được. Ví dụ: không đọc được file do phần cứng, hệ điều hành có vấn đề, JVM không tìm thấy platform class...
c. Runtime exception: Lỗi bên trong chương trình nhưng không dự đoán hay phục hồi được. Như các lỗi logic, gọi sai API...
3. Catch or Specify Requirement
Là 1 thủ tục yêu cầu chỉ định kiểm tra exception và tạo exception handler.
Đây là 1 thủ tục bắt buộc trong Java, với các checked exception, lập trình viên bắt buộc phải thực hiện thì mới biên dịch được.
Chỉ có loại Checked exception là bắt buộc phải thực hiện thủ tục này, 2 loại còn lại (unchecked) không cần.
Cách thức thực hiện:
Phần code xảy ra exception có thể ngầm định hoặc chỉ định rõ với câu lệnh:
throw new SomeException()
Và phần code này buộc phải:
1. Hoặc nằm trong khối try ... catch
2. Hoặc nằm trong method có mệnh đề throws SomeException đi kèm.
4. Khối try ... catch
Hãy xem xét ví dụ sau:
public static void readFile(String args[]){
Scanner sc = null;try {
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
}
}
Đoạn code:if (sc == null)
throw new IOException ("File not found");
không có cũng được (có thể ngầm định).
Trong khối try là các lệnh có thể xảy ra exception và khi xảy ra, runtime system sẽ tìm handler theo thứ tự:
- FileReader -> không có
- method readFile -> có 1 handler phù hợp (IOException) -> thực thi lệnh trong catch.
Chúng ta đôi khi thấy 1 dạng khối có thêm finally như vầy:
public static void readFile(String args[]){
Scanner sc = null;try {
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null)
throw new IOException ("File not found");
while (sc.hasNext()) {
log(sc.next());
}
} catch (IOException e) {
log("Error: " + e.getMessage());
} finally {
if (sc != null)
sc.close();
}
}
Trong khối finally thường là lệnh dọn dẹp, nó được thực hiện ngay khi khối lệnh try thoát ra và catch phù hợp nếu được tìm thấy (try --> catch phù hợp --> finaaly)
5. Throws statement:
Hãy xem ví dụ sau:
{
Scanner sc = null;
sc = new Scanner(new BufferedReader(new FileReader("abc.txt")));
if (sc == null) // không bắt buộc
throw new IOException ("File not found"); // không bắt buộc
while (sc.hasNext()) {
log(sc.next());
}
}
Đây là cách 2 mà chúng ta nói tới. Khi đó, hàm gọi tói phương thức readFile (ví dụ là main) phải như thế này:
public static void main(String args[])
{
try {
readFile(args);
}
catch (IOException e) {
log("Error: " + e.getMessage());
}
}
(Nói chung là trước sau gì cũng phải catch Exception)
6. Class tự tạo để xử lý Exception
Đôi khi ta hay gặp câu lệnh:
throw new MyException(); // MyException là user-define class
Cũng khá đơn giản, bạn chỉ việc tạo thêm class MyException:
class MyException extends NegativeArraySizeException
{
MyException() {
super("MyException occur !");
}
}
public static void test(String args[]) throws MyException
{
int i = -1;
if (i < 0)
throw new MyException();
}
public static void main(String args[])
{
try {
test(args);
}
catch (MyException e) {
log("Error: " + e);
}
}
###
[Java - I/O cơ bản](http://nguoixanhtihon.blogspot.com/2014/01/java-io-co-ban.html)
I/O là viết tắt của Input / Output, và được hiểu là tất cả những thao tác liên quan đến dữ liệu vào ra của 1 chương trình.
Trong Java, có 1 thuật ngữ cơ bản là I/O Stream. Được hiểu là dòng dữ liệu có nguồn, có đích, mà có thể xuất phát từ (hay đi tới) bất cứ 1 file trên đĩa nào, hay là thiết bị, chương trình bên ngoài, hay là mảng bộ nhớ.
Các loại dữ liệu có thể được chứa trong Stream cũng rất đa dạng: byte stream, character stream, primitive data stream hay object stream.
Hầu hết các lớp làm việc với I/O Stream nằm trong gói java.io
1. Byte stream:
Đây là loại stream được dùng để xử lý từng byte 8-bit một.
Các lớp liên quan thuộc lớp InputStream và OutputStream. Có rất nhiều lớp xử lý byte stream và chúng ta sẽ demo lớp FileInputStream và FileOutputStream (các lớp khác tương tự):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\input.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\output.txt";
public static void myByteStream(String args[]) throws IOException
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(INPUTFILE);
fos = new FileOutputStream(OUTPUTFILE);
int c;
while((c = fis.read()) != -1)
{
fos.write(c);
}
} finally {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
Thông thường ít khi chúng ta dùng đến byte stream trong các chương trình thông thường do chúng xử lý từng byte một, không cần thiết phải level thấp đến vậy :)
2. Character Stream:
Java sử dụng quy ước UNICODE cho việc lưu trữ ký tự. Character stream tự động chuyển đổi internal format sang bộ ký tự địa phương.
Các lớp xử lý Character stream trực thuộc lớp Reader và Writer. Tương tự phần trên sau đây chúng ta sẽ demo các lớp FileReader và FileWriter:
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
public static void myCharacterStream(String args[]) throws IOException
{
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(INPUTFILE);
fw = new FileWriter(OUTPUTFILE);
int c;
while((c = fr.read()) != -1)
{
fw.write(c);
}
} finally {
if (fr != null) {
fr.close();
}
if (fw != null) {
fw.close();
}
}
}
Character stream sử dụng byte stream để giao tiếp với phần cứng I/O, trong khi sử dụng các "cầu nối" để chuyển từ byte sang character (với charset được chỉ định). Ví dụ:
FileReader sử dụng FileInputStream và cầu nối InputStreamReader
Có 1 cách thức khác để đọc dữ liệu từ Character stream, đó là đọc theo dòng:
public static void myCharacterStream2(String args[]) throws IOException
{
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new FileReader(INPUTFILE));
pw = new PrintWriter(new FileWriter(OUTPUTFILE));
String l;
while((l = br.readLine()) != null)
{
pw.println(l);
}
} finally {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
}
}
3. Buffered stream
Buffered là 1 thuật ngữ chỉ việc lưu tạm dữ liệu đến 1 thời điểm nào đó thì mới thao tác, do đó nó giảm thiểu số thao tác phải dùng.
Buffered được sử dụng để wrap các unbuffered stream tương ứng. Ví dụ:
BufferedReader brr = new BufferedReader(new FileReader(input.txt)); // Character stream
BufferedInputStream bris = new BufferedInputStream(new FileInputStream(input.txt)); // Byte stream
4. Scanning
Đây là khái niệm chuyển dữ liệu từ input sang các token (những mảnh dữ liệu được phân tách từ 1 chuỗi dữ liệu ban đầu - kiểu như 1 string phân thành cách từ bởi đấu cách).
Chúng ta xem demo sau:
public static void myScanning(String args[]) throws IOException
{
Scanner sc = null;
double sum = 0;
try {
sc = new Scanner(new BufferedReader(new FileReader(INPUTFILE)));
while (sc.hasNext()) {
if (sc.hasNextDouble()) {
sum += sc.nextDouble();
} else {
sc.next();
}
}
} finally {
if (sc != null) {
sc.close();
}
}
log((Double.toString(sum)));
}
Scanner dùng phương thức sau để định ký tự phân cách
5. I/O from Console
Standard stream gồm System.in, System.out, System.err tương ứng với 3 cổng I/O kinh điển trong bất cứ HĐH nào (các bạn tự hiểu)
Cả 3 đều là byte character nhưng System.out, System.err được định nghĩa bởi PrintStream, nên được hỗ trợ chuyển đổi thành character. Với System.in, để sử dụng, ta cần dùng cầu nối để chuyển thành character stream:
Console I/O: Hỗ trợ 1 số tính năng mà standard stream không có. Để sử dụng, bạn bất buộc phải gọi
System.console():
Ta xét ví dụ sau:
public static void myPassword(String args[])
{
Console c = System.console();
if (c == null) {
log("Console not permit !");
System.exit(1);
}
String login = c.readLine("Enter Username: ");
char[] pass = c.readPassword("Enter Password: ");
c.format("You just entered: ");
c.format("User: "+login+"%n");
c.format("Pass: "+(new String(pass)));
}
6. Data Stream
Java hỗ trợ thao tác I/O với binary data : bolean, byte, char, short, int, long, float, double (primitive data type).
Data stream được hỗ trợ bởi các class implement các giao diện DataInput và DataOutput. Sau đây chúng ta khảo sát DataInputStrean và DataOutputStream.
static final String dataFile = "D:\\Programing\\Java\\Proj\\invoicedata";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
public static void myDataStream(String args[]) throws IOException
{
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i++) {
dos.writeDouble(prices[i]);
dos.writeInt(units[i]);
dos.writeUTF(descs[i]);
}
} finally {
if (dos != null)
dos.close();
}
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
double price = 0;
int unit = 0;
String desc = null;
try {
while(true) {
price = dis.readDouble();
unit = dis.readInt();
desc = dis.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price, unit, desc);
}
} catch (EOFException e) {
log("End.");
}
} finally {
if (dis != null)
dis.close();
}
}
7. Object stream
Object stream hỗ trợ I/O của các reference object (đồng thời cả primitive data). Các lớp Object stream là ObjectInputStream và ObjectOutputStream. Các lớp này thực ra implement các interface ObjectInput và ObjectOutput (là subinterface của DataInput và DataOutput).
Một điều phải lưu ý là để writeObject 1 Object nào đó chứa reference tới các Object khác, rõ ràng nó cũng write luôn cả những Object thêm vào đó. Quá trình readObject cũng tương tự.
Chúng ta xem ví dụ sau đây:
public static void myObjectStream(String args[]) throws IOException
{
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices2.length; i++) {
oos.writeObject(prices[i]);
oos.writeInt(units[i]);
oos.writeUTF(descs[i]);
}
} finally {
if (oos != null) {
oos.close();
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
BigDecimal price2;
int unit = 0;
String desc = null;
try {
while(true) {
price2 = new BigDecimal((Double)ois.readObject());
unit = ois.readInt();
desc = ois.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price2, unit, desc);
}
} catch (EOFException e) {
log("End.");
} catch (ClassNotFoundException e) {
log("Error: "+ e.toString());
}
} finally {
if (ois != null) {
ois.close();
}
}
}
Có thể coi file I/O là 1 trường hợp của I/O mà bởi tính quan trọng và phổ biến, nó được tách ra phần riêng và có những gói xử lý riêng. Tất cả các gói, class nằm dưới java.nio.file
Đây là những gói MỚI - NEW IO (nio) không phải để thay thế java.io mà đơn giản là mở rộng nó.
(Tất nhiên, như chúng đã xem phần đầu, các thao tác với file I/O cũng được xử lý trong java.io bởi nhiều lớp khác nhau, nhưng nói chung khi vào trận thì với file, bạn nên dùng mấy thứ được chuyên biệt hóa này :))
Chúng ta sẽ bắt đầu với Path và các thứ liên quan
1. Path
Đây là 1 khái niệm quen thuộc. Chúng ta có lớp Path để xử lý các thao tác với nó.
Tạo 1 path mới, ví dụ:
Path p1 = Paths.get("/home/go/to/file");
Path p2 = Paths.get("C:\\go\\to\\file");
Path p3 = Paths.get(URI.create("file:///home/go/to/file")); // cần import java.net
Path p4 = Paths.get(System.getProperty("user.home"), "go", "to", "file");
Lấy thông tin về path:
public static void myPath(String args[]) throws IOException
{
Path p1 = Paths.get("C:\\go\\to\\file");
log("toString(): " + p1.toString());
log("getFileName(): " + p1.getFileName());
log("getName(0): " + p1.getName(0));
log("getNameCount(): " + p1.getNameCount());
log("subpath(0,2): " + p1.subpath(0,2));
log("getParent(): " + p1.getParent());
log("getRoot(): " + p1.getRoot());
}
Chuyển đổi path
Hàm toUri chuyển path thành String mà có thể dùng trên các trình duyệt
Hàm toAbsolutePath chuyển đường dẫn tương đối thành tuyệt đối và 1 số thao tác như:
Hàm toRealPath kiểm tra và trả về dường dẫn thực của file có tồn tại
- Nếu true được gửi vào và file system cho phép symbolic links, nó sẽ trả về đường dẫn thật của file (resolves symbolic link)
- Nếu là path tương đối, chuyển thành tuyệt đối
- Nếu path có chứa thành phần dư thừa (".", "directory/.."), nó loại bỏ các thành phần này
Path p1 = Paths.get("C:\\go\\to\\..\\file");
Path p2 = p1.toRealPath();
Nối path
Rất đơn giản
Path p1 = Paths.get("C:\\go\\to");
Path p2 = p1.resolve("file");
Tạo path từ 2 path đã cho
Path p1 = Paths.get("C:\\go");
Path p1 = Paths.get("C:\\go\\to\\file");
Path p3 = p1.relativize(p2); // p3 = "to\\file"
Path p4 = p2.relativize(p1); // p4 = "..\\.."
So sánh 2 path
phương thức equals được dùng để kiểm tra 2 path có giống nhau không.
2. File
Chúng ta sẽ làm quen với một vài thuật ngữ:
Giải phóng tài nguyên hệ thống:
Nhiều tài nguyên được sử dụng bởi các hàm API như stream, chanel,... được kế thừa hay mở rộng từ interface java.io.Closable. Việc giải phóng sau khi sử dụng rất quan trọng, thông thường chúng ta gọi phương thức close(). Tuy nhiên, chúng ta có thể cho chúng tự động close với cú pháp try-with-resources.
Bất kỳ lớp nào implement java.lang.AutoCloseable đề có thể dùng được với try-with-resources:
try (BufferedReader br = BufferedReader(new FileReader(file))) {
...
} catch (IOException e) {
...
}
Atomic Operation
Những thao tác không thể ngắt quãng, chia nhỏ nếu không muốn bị fails. Ví dụ lệnh move file.
Method chaining
Nhiều trường hợp chúng ta gọi 1 method, method này trả về 1 đối tượng nào đó, và ta gọi tiếp method 2 sử dụng kết quả này, ... Ví dụ:
String value = Charset.defaultCharset().decode(buf).toString();
Glob
Glob pattern khá giống regular express, nó được xem như là String và được dùng để match một String khác
Kiểm tra sự tồn tại:
Files.exists(path) và Files.notExists(path). Chú ý Files.notExists(path) khác với !Files.exists(path)
Kiểm tra sự truy cập
Files.isReadable(path)
Files.isWritable(path)
Files.isExcutable(path)
Kiểm tra 2 path có dẫn tới cùng 1 file
Files.isSameFile(path, path)
4. Một số thao tác file / thư mục
Delete:
Files.delete(path);
Copy:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.copy(p1, p2); // nếu p2 chưa có
Files.copy(p1, p2, REPLACE_EXISTING); // nếu p2 đã có sẵn + cần import static java.nio.file.StandardCopyOption.*;
Nếu p1 là symbolic link thì chỉ có target file được copy, nếu muốn chỉ copy symbolic link thì:
Files.copy(p1, p2, NOFOLLOW_LINKS);
Cũng có thể copy từ/đến 1 InputStream, ví dụ:
Files.copy(new FileInputStream(p1.toString()), p2);
Chú ý rằng khi copy thư mục, chỉ thư mục được copy, các file bên trong thì không được.
Move:
Một chú ý với lệnh move là nếu move 1 thư mục có chứa nội dung bên trong, lệnh move chỉ được phép nếu thư mục đó được phép move mà không cần move cả nội dung bên trong. Riêng với UNIX, trong cùng partition, thư mục được phép move ngay khi nó có chứa nội dung bên trong
import static java.nio.file.StandardCopyOption.*;
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.move(p1, p2); // nếu p2 chưa có
Files.move(p1, p2, ATOMIC_MOVE); // nếu hệ thống cho phép, lệnh move với ATOMIC_MOVE sẽ đảm bảo quá trình move được ưu tiên để hoàn thành mà không bị ngắt quãng, lỗi.
5. File attributes
Có nhiều phương thức và lớp liên quan đến việc lấy attributes và thiết lập attributes file.
Trong java.nio.file.Files cũng có 1 số phương thức lấy attributes như:
size
isDirectory
isRegularFile
...
Tuy nhiên, để lấy nhiều attributes cùng lúc mà dùng từng phương thức như trên thì không tốt cho hiệu suất, vì vậy, ta có phương thức: readAttributes (đọc thêm tài liệu về các phương thức này)
Đối với từng hệ thống khác nhau, chúng ta có những loại attributes khác nhau, vì vậy, java xếp theo các nhóm.
Các attributes cơ bản, ta có java.nio.file.attribute.BasicFileAttributes
Với Windows file, ta có java.nio.file.attribute.DosFileAttributes
Với POSIX file, java.nio.files.attribute.PosixFileAttributes
...
Ta xét ví dụ sau:
public static void myFileAttributes(String args[]) throws IOException
{
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\outputSmall.txt");
BasicFileAttributes bfa = Files.readAttributes(p1, BasicFileAttributes.class);
DosFileAttributes dfa = Files.readAttributes(p1, DosFileAttributes.class);
log("creationTime: " + bfa.creationTime());
log("lastModifiedTime: " + bfa.lastModifiedTime());
log("size: " + bfa.size());
log("isHidden: " + dfa.isHidden());
long curTime = System.currentTimeMillis();
FileTime ft = FileTime.fromMillis(curTime);
Files.setLastModifiedTime(p1, ft);
log("after edit - lastModifiedTime: " + bfa.lastModifiedTime());
}
Ở đây có 1 cái bẫy: log("after edit - lastModifiedTime: " + bfa.lastModifiedTime()); --> sẽ in ra thông số giống như lúc chưa setLastModifiedTime ! Vì ta sử dụng biến bfa - nó đã lấy thông tin tất cả các attributes từ trước, bởi vậy, muốn cập nhật thì không thể dùng lại bfa
File Store attributes
Chúng ta xét ví dụ sau để hiểu các lấy file store:
import java.nio.file.FileStore;
FileStore fs = Files.getFileStore(p1);
log("getTotalSpace: " + fs.getTotalSpace()/1024);
log("getUnallocatedSpace: " + fs.getUnallocatedSpace()/1024);
log("get used: " + (fs.getTotalSpace() - fs.getUnallocatedSpace())/1024);
log("getUsableSpace: " + fs.getUsableSpace()/1024);
6. File reading, writing, creating
Có nhiều phương thức, class để làm việc này tùy theo trường hợp, mức độ
Đối với Small file:
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\inputSmall.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\outputSmall.txt";
public static void myFileOperation_SmallFile(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
byte[] fileArrBytes;
fileArrBytes = Files.readAllBytes(p1);
log(String.valueOf(fileArrBytes));
Files.write(p2, fileArrBytes, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
}
Đối với file lớn - dùng buffer:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.*;
public static void myFileOperation_Buffered(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
Charset charset = Charset.forName("UTF-8");
// or Charset charset = StandardCharsets.UTF_8;
String sLine = null;
try (BufferedReader readerBuf = Files.newBufferedReader(p1, charset);
BufferedWriter writerBuf = Files.newBufferedWriter(p2, charset, WRITE, APPEND)){
while ((sLine = readerBuf.readLine()) != null) {
log(sLine);
writerBuf.write(sLine, 0, sLine.length());
writerBuf.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Dùng I/O Stream kết hợp với buffered
public static void myFileOperation_Stream(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
String sLine;
try (InputStream in = Files.newInputStream(p1);
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
OutputStream ou = Files.newOutputStream(p2, WRITE, APPEND);
BufferedWriter bou = new BufferedWriter(new OutputStreamWriter(ou));) {
while ((sLine = bin.readLine()) != null) {
log(sLine);
bou.write(sLine, 0, sLine.length());
bou.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Liệt kê các thư mục gốc
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
FileSystem dfFst = FileSystems.getDefault(); // get default File system
Iterable<Path> dirs = dfFst.getRootDirectories();
// or Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
for (Path d : dirs) {
log(d.toString());
}
Tạo thư mục:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\testDir\\subTestDir"); // có thể tạo nhiều dir nhiều level
Files.createDirectories(p1);
Liệt kê nội dung thư mục
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p1, "*.txt")) {
for (Path p : stream) {
log(p.getFileName());
}
} catch (DirectoryIteratorException e) {
e.printStackTrace();
}
Chú ý rằng trong: Files.newDirectoryStream(path, filter) thì filter nếu không có nó sẽ liệt kê tất cả. Nếu cần điều kiện để liệt kê, filter là dạng Glob String như trên.
Walking the File Tree
Để duyệt nội dung xuyên qua các thư mục khác nhau, hay 1 hoạt động cụ thể với 1 nhóm file trong 1 cây thư mục... chúng ta sẽ dùng tới các lớp và phương thức là implement của interface: FileVisitor
FileVisitor là 1 interface quy định cụ thể các hành vi cần thiết tại các điểm quan trọng trong quá trình "walking file tree":
- khi một tập tin được truy cập,
- trước khi một thư mục được truy cập,
- sau khi một thư mục được truy cập,
- hoặc khi thất bại xảy ra.
Giao diện có bốn phương pháp tương ứng với những tình huống.
Nếu chúng ta không cần triển khai 4 tình huống trên, thay vì implement FileVisitor, chúng ta sẽ extends SimpleFileVisitor:
import java.nio.file.FileVisitResult;
import java.nio.file.SimpleFileVisitor;
class PrintFiles extends SimpleFileVisitor<Path> {
/**
This is extended class for print all files in specified directory
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
Calc.log("Symbolic link: " + file.toString());
} else if (attr.isRegularFile()) {
Calc.log("Regular file: " + file.toString());
} else {
Calc.log("Other: " + file.toString());
}
Calc.log(" (" + attr.size() + "bytes)");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) {
Calc.log("Dir: " + dir.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
System.err.println(e);
return FileVisitResult.CONTINUE;
}
}
class Calc
{
public static void myDirectory(String args[]) throws IOException
{
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\testDir");
PrintFiles pf = new PrintFiles();
Files.walkFileTree(p2, pf);
}
public static void main(String args[])
{
try {
myDirectory(args);
}
catch (IOException e) {
log("Error: " + e);
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
}
**I - I/O Stream**
Trong Java, có 1 thuật ngữ cơ bản là I/O Stream. Được hiểu là dòng dữ liệu có nguồn, có đích, mà có thể xuất phát từ (hay đi tới) bất cứ 1 file trên đĩa nào, hay là thiết bị, chương trình bên ngoài, hay là mảng bộ nhớ.
Các loại dữ liệu có thể được chứa trong Stream cũng rất đa dạng: byte stream, character stream, primitive data stream hay object stream.
Hầu hết các lớp làm việc với I/O Stream nằm trong gói java.io
1. Byte stream:
Đây là loại stream được dùng để xử lý từng byte 8-bit một.
Các lớp liên quan thuộc lớp InputStream và OutputStream. Có rất nhiều lớp xử lý byte stream và chúng ta sẽ demo lớp FileInputStream và FileOutputStream (các lớp khác tương tự):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\input.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\output.txt";
public static void myByteStream(String args[]) throws IOException
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(INPUTFILE);
fos = new FileOutputStream(OUTPUTFILE);
int c;
while((c = fis.read()) != -1)
{
fos.write(c);
}
} finally {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
Thông thường ít khi chúng ta dùng đến byte stream trong các chương trình thông thường do chúng xử lý từng byte một, không cần thiết phải level thấp đến vậy :)
2. Character Stream:
Java sử dụng quy ước UNICODE cho việc lưu trữ ký tự. Character stream tự động chuyển đổi internal format sang bộ ký tự địa phương.
Các lớp xử lý Character stream trực thuộc lớp Reader và Writer. Tương tự phần trên sau đây chúng ta sẽ demo các lớp FileReader và FileWriter:
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;
public static void myCharacterStream(String args[]) throws IOException
{
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(INPUTFILE);
fw = new FileWriter(OUTPUTFILE);
int c;
while((c = fr.read()) != -1)
{
fw.write(c);
}
} finally {
if (fr != null) {
fr.close();
}
if (fw != null) {
fw.close();
}
}
}
Character stream sử dụng byte stream để giao tiếp với phần cứng I/O, trong khi sử dụng các "cầu nối" để chuyển từ byte sang character (với charset được chỉ định). Ví dụ:
FileReader sử dụng FileInputStream và cầu nối InputStreamReader
Có 1 cách thức khác để đọc dữ liệu từ Character stream, đó là đọc theo dòng:
public static void myCharacterStream2(String args[]) throws IOException
{
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new FileReader(INPUTFILE));
pw = new PrintWriter(new FileWriter(OUTPUTFILE));
String l;
while((l = br.readLine()) != null)
{
pw.println(l);
}
} finally {
if (br != null) {
br.close();
}
if (pw != null) {
pw.close();
}
}
}
3. Buffered stream
Buffered là 1 thuật ngữ chỉ việc lưu tạm dữ liệu đến 1 thời điểm nào đó thì mới thao tác, do đó nó giảm thiểu số thao tác phải dùng.
Buffered được sử dụng để wrap các unbuffered stream tương ứng. Ví dụ:
BufferedReader brr = new BufferedReader(new FileReader(input.txt)); // Character stream
BufferedInputStream bris = new BufferedInputStream(new FileInputStream(input.txt)); // Byte stream
4. Scanning
Đây là khái niệm chuyển dữ liệu từ input sang các token (những mảnh dữ liệu được phân tách từ 1 chuỗi dữ liệu ban đầu - kiểu như 1 string phân thành cách từ bởi đấu cách).
Chúng ta xem demo sau:
public static void myScanning(String args[]) throws IOException
{
Scanner sc = null;
double sum = 0;
try {
sc = new Scanner(new BufferedReader(new FileReader(INPUTFILE)));
while (sc.hasNext()) {
if (sc.hasNextDouble()) {
sum += sc.nextDouble();
} else {
sc.next();
}
}
} finally {
if (sc != null) {
sc.close();
}
}
log((Double.toString(sum)));
}
Scanner dùng phương thức sau để định ký tự phân cách
s.useDelimiter()
5. I/O from Console
Standard stream gồm System.in, System.out, System.err tương ứng với 3 cổng I/O kinh điển trong bất cứ HĐH nào (các bạn tự hiểu)
Cả 3 đều là byte character nhưng System.out, System.err được định nghĩa bởi PrintStream, nên được hỗ trợ chuyển đổi thành character. Với System.in, để sử dụng, ta cần dùng cầu nối để chuyển thành character stream:
InputStreamReader cin = new InputStreamReader(System.in);
Console I/O: Hỗ trợ 1 số tính năng mà standard stream không có. Để sử dụng, bạn bất buộc phải gọi
System.console():
Ta xét ví dụ sau:
public static void myPassword(String args[])
{
Console c = System.console();
if (c == null) {
log("Console not permit !");
System.exit(1);
}
String login = c.readLine("Enter Username: ");
char[] pass = c.readPassword("Enter Password: ");
c.format("You just entered: ");
c.format("User: "+login+"%n");
c.format("Pass: "+(new String(pass)));
}
6. Data Stream
Java hỗ trợ thao tác I/O với binary data : bolean, byte, char, short, int, long, float, double (primitive data type).
Data stream được hỗ trợ bởi các class implement các giao diện DataInput và DataOutput. Sau đây chúng ta khảo sát DataInputStrean và DataOutputStream.
static final String dataFile = "D:\\Programing\\Java\\Proj\\invoicedata";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
public static void myDataStream(String args[]) throws IOException
{
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i++) {
dos.writeDouble(prices[i]);
dos.writeInt(units[i]);
dos.writeUTF(descs[i]);
}
} finally {
if (dos != null)
dos.close();
}
DataInputStream dis = null;
try {
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
double price = 0;
int unit = 0;
String desc = null;
try {
while(true) {
price = dis.readDouble();
unit = dis.readInt();
desc = dis.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price, unit, desc);
}
} catch (EOFException e) {
log("End.");
}
} finally {
if (dis != null)
dis.close();
}
}
7. Object stream
Object stream hỗ trợ I/O của các reference object (đồng thời cả primitive data). Các lớp Object stream là ObjectInputStream và ObjectOutputStream. Các lớp này thực ra implement các interface ObjectInput và ObjectOutput (là subinterface của DataInput và DataOutput).
Một điều phải lưu ý là để writeObject 1 Object nào đó chứa reference tới các Object khác, rõ ràng nó cũng write luôn cả những Object thêm vào đó. Quá trình readObject cũng tương tự.
Chúng ta xem ví dụ sau đây:
public static void myObjectStream(String args[]) throws IOException
{
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices2.length; i++) {
oos.writeObject(prices[i]);
oos.writeInt(units[i]);
oos.writeUTF(descs[i]);
}
} finally {
if (oos != null) {
oos.close();
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
BigDecimal price2;
int unit = 0;
String desc = null;
try {
while(true) {
price2 = new BigDecimal((Double)ois.readObject());
unit = ois.readInt();
desc = ois.readUTF();
System.out.format("Price: %.2f$ - Unit: %d - Desc: %s%n", price2, unit, desc);
}
} catch (EOFException e) {
log("End.");
} catch (ClassNotFoundException e) {
log("Error: "+ e.toString());
}
} finally {
if (ois != null) {
ois.close();
}
}
}
**II - File I/O**
Có thể coi file I/O là 1 trường hợp của I/O mà bởi tính quan trọng và phổ biến, nó được tách ra phần riêng và có những gói xử lý riêng. Tất cả các gói, class nằm dưới java.nio.file
Đây là những gói MỚI - NEW IO (nio) không phải để thay thế java.io mà đơn giản là mở rộng nó.
(Tất nhiên, như chúng đã xem phần đầu, các thao tác với file I/O cũng được xử lý trong java.io bởi nhiều lớp khác nhau, nhưng nói chung khi vào trận thì với file, bạn nên dùng mấy thứ được chuyên biệt hóa này :))
Chúng ta sẽ bắt đầu với Path và các thứ liên quan
1. Path
Đây là 1 khái niệm quen thuộc. Chúng ta có lớp Path để xử lý các thao tác với nó.
Tạo 1 path mới, ví dụ:
Path p1 = Paths.get("/home/go/to/file");
Path p2 = Paths.get("C:\\go\\to\\file");
Path p3 = Paths.get(URI.create("file:///home/go/to/file")); // cần import java.net
Path p4 = Paths.get(System.getProperty("user.home"), "go", "to", "file");
Lấy thông tin về path:
public static void myPath(String args[]) throws IOException
{
Path p1 = Paths.get("C:\\go\\to\\file");
log("toString(): " + p1.toString());
log("getFileName(): " + p1.getFileName());
log("getName(0): " + p1.getName(0));
log("getNameCount(): " + p1.getNameCount());
log("subpath(0,2): " + p1.subpath(0,2));
log("getParent(): " + p1.getParent());
log("getRoot(): " + p1.getRoot());
}
Chuyển đổi path
Hàm toUri chuyển path thành String mà có thể dùng trên các trình duyệt
Hàm toAbsolutePath chuyển đường dẫn tương đối thành tuyệt đối và 1 số thao tác như:
Hàm toRealPath kiểm tra và trả về dường dẫn thực của file có tồn tại
- Nếu true được gửi vào và file system cho phép symbolic links, nó sẽ trả về đường dẫn thật của file (resolves symbolic link)
- Nếu là path tương đối, chuyển thành tuyệt đối
- Nếu path có chứa thành phần dư thừa (".", "directory/.."), nó loại bỏ các thành phần này
Path p1 = Paths.get("C:\\go\\to\\..\\file");
Path p2 = p1.toRealPath();
Nối path
Rất đơn giản
Path p1 = Paths.get("C:\\go\\to");
Path p2 = p1.resolve("file");
Tạo path từ 2 path đã cho
Path p1 = Paths.get("C:\\go");
Path p1 = Paths.get("C:\\go\\to\\file");
Path p3 = p1.relativize(p2); // p3 = "to\\file"
Path p4 = p2.relativize(p1); // p4 = "..\\.."
So sánh 2 path
phương thức equals được dùng để kiểm tra 2 path có giống nhau không.
2. File
Chúng ta sẽ làm quen với một vài thuật ngữ:
Giải phóng tài nguyên hệ thống:
Nhiều tài nguyên được sử dụng bởi các hàm API như stream, chanel,... được kế thừa hay mở rộng từ interface java.io.Closable. Việc giải phóng sau khi sử dụng rất quan trọng, thông thường chúng ta gọi phương thức close(). Tuy nhiên, chúng ta có thể cho chúng tự động close với cú pháp try-with-resources.
Bất kỳ lớp nào implement java.lang.AutoCloseable đề có thể dùng được với try-with-resources:
try (BufferedReader br = BufferedReader(new FileReader(file))) {
...
} catch (IOException e) {
...
}
Atomic Operation
Những thao tác không thể ngắt quãng, chia nhỏ nếu không muốn bị fails. Ví dụ lệnh move file.
Method chaining
Nhiều trường hợp chúng ta gọi 1 method, method này trả về 1 đối tượng nào đó, và ta gọi tiếp method 2 sử dụng kết quả này, ... Ví dụ:
String value = Charset.defaultCharset().decode(buf).toString();
Glob
Glob pattern khá giống regular express, nó được xem như là String và được dùng để match một String khác
- An asterisk,
*, matches any number of characters (including none).
- Two asterisks,
**, works like
*but crosses directory boundaries. This syntax is generally used for matching complete paths.
- A question mark,
?, matches exactly one character.
- Braces specify a collection of subpatterns. For example:
{sun,moon,stars}matches "sun", "moon", or "stars".
{temp*,tmp*}matches all strings beginning with "temp" or "tmp".
-
- Square brackets convey a set of single characters or, when the hyphen character (
-) is used, a range of characters. For example:
[aeiou]matches any lowercase vowel.
[0-9]matches any digit.
[A-Z]matches any uppercase letter.
[a-z,A-Z]matches any uppercase or lowercase letter.
*,
?, and
\match themselves.
-
- All other characters match themselves.
- To match
*,
?, or the other special characters, you can escape them by using the backslash character,
\. For example:
\\matches a single backslash, and
\?matches the question mark.
Kiểm tra sự tồn tại:
Files.exists(path) và Files.notExists(path). Chú ý Files.notExists(path) khác với !Files.exists(path)
Kiểm tra sự truy cập
Files.isReadable(path)
Files.isWritable(path)
Files.isExcutable(path)
Kiểm tra 2 path có dẫn tới cùng 1 file
Files.isSameFile(path, path)
4. Một số thao tác file / thư mục
Delete:
Files.delete(path);
Copy:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.copy(p1, p2); // nếu p2 chưa có
Files.copy(p1, p2, REPLACE_EXISTING); // nếu p2 đã có sẵn + cần import static java.nio.file.StandardCopyOption.*;
Nếu p1 là symbolic link thì chỉ có target file được copy, nếu muốn chỉ copy symbolic link thì:
Files.copy(p1, p2, NOFOLLOW_LINKS);
Cũng có thể copy từ/đến 1 InputStream, ví dụ:
Files.copy(new FileInputStream(p1.toString()), p2);
Chú ý rằng khi copy thư mục, chỉ thư mục được copy, các file bên trong thì không được.
Move:
Một chú ý với lệnh move là nếu move 1 thư mục có chứa nội dung bên trong, lệnh move chỉ được phép nếu thư mục đó được phép move mà không cần move cả nội dung bên trong. Riêng với UNIX, trong cùng partition, thư mục được phép move ngay khi nó có chứa nội dung bên trong
import static java.nio.file.StandardCopyOption.*;
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\output.txt");
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\output1.txt");
Files.move(p1, p2); // nếu p2 chưa có
Files.move(p1, p2, ATOMIC_MOVE); // nếu hệ thống cho phép, lệnh move với ATOMIC_MOVE sẽ đảm bảo quá trình move được ưu tiên để hoàn thành mà không bị ngắt quãng, lỗi.
5. File attributes
Có nhiều phương thức và lớp liên quan đến việc lấy attributes và thiết lập attributes file.
Trong java.nio.file.Files cũng có 1 số phương thức lấy attributes như:
size
isDirectory
isRegularFile
...
Tuy nhiên, để lấy nhiều attributes cùng lúc mà dùng từng phương thức như trên thì không tốt cho hiệu suất, vì vậy, ta có phương thức: readAttributes (đọc thêm tài liệu về các phương thức này)
Đối với từng hệ thống khác nhau, chúng ta có những loại attributes khác nhau, vì vậy, java xếp theo các nhóm.
Các attributes cơ bản, ta có java.nio.file.attribute.BasicFileAttributes
Với Windows file, ta có java.nio.file.attribute.DosFileAttributes
Với POSIX file, java.nio.files.attribute.PosixFileAttributes
...
Ta xét ví dụ sau:
public static void myFileAttributes(String args[]) throws IOException
{
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\outputSmall.txt");
BasicFileAttributes bfa = Files.readAttributes(p1, BasicFileAttributes.class);
DosFileAttributes dfa = Files.readAttributes(p1, DosFileAttributes.class);
log("creationTime: " + bfa.creationTime());
log("lastModifiedTime: " + bfa.lastModifiedTime());
log("size: " + bfa.size());
log("isHidden: " + dfa.isHidden());
long curTime = System.currentTimeMillis();
FileTime ft = FileTime.fromMillis(curTime);
Files.setLastModifiedTime(p1, ft);
log("after edit - lastModifiedTime: " + bfa.lastModifiedTime());
}
Ở đây có 1 cái bẫy: log("after edit - lastModifiedTime: " + bfa.lastModifiedTime()); --> sẽ in ra thông số giống như lúc chưa setLastModifiedTime ! Vì ta sử dụng biến bfa - nó đã lấy thông tin tất cả các attributes từ trước, bởi vậy, muốn cập nhật thì không thể dùng lại bfa
File Store attributes
Chúng ta xét ví dụ sau để hiểu các lấy file store:
import java.nio.file.FileStore;
FileStore fs = Files.getFileStore(p1);
log("getTotalSpace: " + fs.getTotalSpace()/1024);
log("getUnallocatedSpace: " + fs.getUnallocatedSpace()/1024);
log("get used: " + (fs.getTotalSpace() - fs.getUnallocatedSpace())/1024);
log("getUsableSpace: " + fs.getUsableSpace()/1024);
6. File reading, writing, creating
Có nhiều phương thức, class để làm việc này tùy theo trường hợp, mức độ
Đối với Small file:
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
final static String INPUTFILE = "D:\\Programming\\Java\\Proj\\inputSmall.txt";
final static String OUTPUTFILE = "D:\\Programming\\Java\\Proj\\outputSmall.txt";
public static void myFileOperation_SmallFile(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
byte[] fileArrBytes;
fileArrBytes = Files.readAllBytes(p1);
log(String.valueOf(fileArrBytes));
Files.write(p2, fileArrBytes, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
}
Đối với file lớn - dùng buffer:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.file.Files;
import static java.nio.file.StandardOpenOption.*;
public static void myFileOperation_Buffered(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
Charset charset = Charset.forName("UTF-8");
// or Charset charset = StandardCharsets.UTF_8;
String sLine = null;
try (BufferedReader readerBuf = Files.newBufferedReader(p1, charset);
BufferedWriter writerBuf = Files.newBufferedWriter(p2, charset, WRITE, APPEND)){
while ((sLine = readerBuf.readLine()) != null) {
log(sLine);
writerBuf.write(sLine, 0, sLine.length());
writerBuf.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Dùng I/O Stream kết hợp với buffered
public static void myFileOperation_Stream(String args[]) throws IOException
{
Path p1 = Paths.get(INPUTFILE);
Path p2 = Paths.get(OUTPUTFILE);
String sLine;
try (InputStream in = Files.newInputStream(p1);
BufferedReader bin = new BufferedReader(new InputStreamReader(in));
OutputStream ou = Files.newOutputStream(p2, WRITE, APPEND);
BufferedWriter bou = new BufferedWriter(new OutputStreamWriter(ou));) {
while ((sLine = bin.readLine()) != null) {
log(sLine);
bou.write(sLine, 0, sLine.length());
bou.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Liệt kê các thư mục gốc
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
FileSystem dfFst = FileSystems.getDefault(); // get default File system
Iterable<Path> dirs = dfFst.getRootDirectories();
// or Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
for (Path d : dirs) {
log(d.toString());
}
Tạo thư mục:
Path p1 = Paths.get("D:\\Programming\\Java\\Proj\\testDir\\subTestDir"); // có thể tạo nhiều dir nhiều level
Files.createDirectories(p1);
Liệt kê nội dung thư mục
try (DirectoryStream<Path> stream = Files.newDirectoryStream(p1, "*.txt")) {
for (Path p : stream) {
log(p.getFileName());
}
} catch (DirectoryIteratorException e) {
e.printStackTrace();
}
Chú ý rằng trong: Files.newDirectoryStream(path, filter) thì filter nếu không có nó sẽ liệt kê tất cả. Nếu cần điều kiện để liệt kê, filter là dạng Glob String như trên.
Walking the File Tree
Để duyệt nội dung xuyên qua các thư mục khác nhau, hay 1 hoạt động cụ thể với 1 nhóm file trong 1 cây thư mục... chúng ta sẽ dùng tới các lớp và phương thức là implement của interface: FileVisitor
FileVisitor là 1 interface quy định cụ thể các hành vi cần thiết tại các điểm quan trọng trong quá trình "walking file tree":
- khi một tập tin được truy cập,
- trước khi một thư mục được truy cập,
- sau khi một thư mục được truy cập,
- hoặc khi thất bại xảy ra.
Giao diện có bốn phương pháp tương ứng với những tình huống.
Nếu chúng ta không cần triển khai 4 tình huống trên, thay vì implement FileVisitor, chúng ta sẽ extends SimpleFileVisitor:
import java.nio.file.FileVisitResult;
import java.nio.file.SimpleFileVisitor;
class PrintFiles extends SimpleFileVisitor<Path> {
/**
This is extended class for print all files in specified directory
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
Calc.log("Symbolic link: " + file.toString());
} else if (attr.isRegularFile()) {
Calc.log("Regular file: " + file.toString());
} else {
Calc.log("Other: " + file.toString());
}
Calc.log(" (" + attr.size() + "bytes)");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) {
Calc.log("Dir: " + dir.toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
System.err.println(e);
return FileVisitResult.CONTINUE;
}
}
class Calc
{
public static void myDirectory(String args[]) throws IOException
{
Path p2 = Paths.get("D:\\Programming\\Java\\Proj\\testDir");
PrintFiles pf = new PrintFiles();
Files.walkFileTree(p2, pf);
}
public static void main(String args[])
{
try {
myDirectory(args);
}
catch (IOException e) {
log("Error: " + e);
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
}
## Thứ Ba, ngày 07 tháng 1 năm 2014
###
[Java - Những mảnh kiến thức nhỏ](http://nguoixanhtihon.blogspot.com/2014/01/java-nhung-manh-kien-thuc-nho.html)
- Class không có constructor thì compiler lấy constructor mặc định - không đối số, các thuộc tính gán giá trị mặc định.
Nếu lớp con không có constructor, compiler gọi 1 constructor mặc định, đồng thời gọi luôn constructor không đối số của lớp cha (lúc này nếu lớp cha có 1 constructor có đối số thì sẽ gây lỗi, thà không có luôn để compiler ban phát miễn phí constructor mặc định)
- Access level of class:
+ Outer class chỉ có thể là public (visible với mọi class) hoặc package private (ngầm định - không có từ khóa public - visible trong package). Điều tương tự với Interface.
+ Ở phạm vị member cũng tương tự, có thêm private - visible trong nội bộ class, protected - visible trong package và các lớp con (khác package).
+ Interface cũng chỉ có thể là public hay package, không thể private hay protected
+ Interface chỉ chứa thành viên public hoặc package private
+ Outer class hoạt động gần giống static, trong khi đó inner class thì không.
- Phân biệt Parameter và Argument
Parameter là tham số lúc khai báo/ định nghĩa method
Argument là giá trị thực truyền vào khi gọi method
- Trong java, không thể truyền method như là 1 parameter tới method khác, thay vào đó, ta có thể truyền đối tượng, sau đó gọi method sau.
- Khi hết sử dụng, tất cả các reference đến 1 object đều phải đặt về null thì mới có thể giải phóng object.
- Một method dùng 1 tên Class làm giá trị trả về, thì giá trị trả về phải là đối tượng của lớp đó, hoặc LỚP CON của lớp đó. Nếu dùng Interface như giá trị trả về, method đó phải trả về đối tượng của lớp implement Interface kia.
- Biến (thuộc tính) static của 1 class ngoài việc được gọi không cần thông qua đối tượng, còn 1 điều đáng lưu ý: Tất cả các lời gọi đến biến đó (từ gọi bằng [ClassName].[Biến static] đến việc gọi qua các đối tượng [DoiTuong1].[Biến static], [DoiTuong2].[Biến static]...) đều trỏ đến
Các biến static còn được gọi là class variable, biến không static gọi là instance variable
- Nested class:
+ Chia 2 loại, static nested class và inner class (non-static)
++ Static nested class: (SNC)
- Giống như static field, SNC không truy cập được bất cứ phần tử instance của lớp cha, nếu muốn phải thông qua object, với các phần tử này, SNC truy cập y như các top-level class khác.
++ Inner class (IC):
- Truy cập các phần tử của lớp cha như mọi thành phần khác.
- Không thể chứa bất kỳ static member nào
- IC có thể chia thành 2 loại nhỏ:
+ Local class:
- Là class nằm trong 1 block,
- Có thể truy cập thành viên của lớp ngoài (không nằm trong block) thông qua: [Lớp outer].[Thành viên]
- Nếu Local class nằm trong static method thì nó chỉ truy cập được các thành viên static của lớp ngoài mà thôi (nhưng ngay cả khi đó, local class vẫn là non-static nhé, nó luôn luôn là vậy :)
- Truy cập các biến local (bên trong khối block) chỉ khi các biến này là final. Tuy vậy, từ SE 8 trở lên, local class có thể gọi trực tiếp parameter của method chứa nó.
- Không thể chứa bất kỳ static member nào. Trường hợp duy nhất là constant (constant là biến kiểu primitive hoặc String được khai báo và khởi tạo với compile-time constant expression - là một chuỗi hoặc một biểu thức số học có thể được xác định giá trị ở thời gian biên dịch)
+ Anonymous class:(AC)
- Xem ví dụ sau:
class Demo {
...
public class CHello { // Inner class
private String sMsg = "hello";
public void display(String aMsg) {
Calc.log(aMsg);
}
}
public void FHello() {
CHello annClass = new CHello() { // go to ANONYMOUS class - extends CHello
@Override
public void display(String aMsg) {
Calc.log(aMsg);
Calc.log(aMsg);
}
};
annClass.display("hello");
}
public static void main(String args[])
{
Demo ds = new Demo();
ds.FHello();
}
- AC được xem là 1 phần của biểu, như đã thấy trên ví dụ, nó kết thúc với ";"
- AC đi với việc extends 1 class hay implement 1 interface nào đó
- Trong AC, chí có thể là lệnh khai báo, method, chứ không thể chứa 1 statements nào.
- Trong AC, bạn không thể tạo constructor
- Các đặc điểm khác giống với Local class
- Enum:
Dạng đơn giản:
public enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
Hay phức tạp hơn:
enum EnumDemo { // Enum type class
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private double mass;
private double radius;
private EnumDemo(double aMass, double aRadius) {
mass = aMass;
radius = aRadius;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
}
...
public static void main(String args[])
{
EnumDemo ed = EnumDemo.MERCURY;
if (ed.equals(EnumDemo.MERCURY)) {
log("Mercury !");
log("Mass: " + EnumDemo.MERCURY.getMass());
log("Radius: " + ed.getRadius());
//EnumDemo.MERCURY = EnumDemo.EARTH; // Error
}
for (EnumDemo e : EnumDemo.values()) {
switch (e) {
case EARTH:
log(e + ": " + e.getMass() + " - " + e.getRadius() + " - We are here !");
break;
default:
log(e + ": " + e.getMass() + " - " + e.getRadius());
}
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
Có thể coi là 1 dạng class/ interface đặc biệt với 1 số đặc điểm sau:
+ Có thể định nghĩa constructor, method, variable...
+ Như ví dụ trên, bạn có thể gán các giá trị với từng constant của Enum, trong trường hợp này bạn phải khai báo biến và constructor vì MERCURY(3.303e+23, 2.4397e6) thực chất gọi 1 constructor 2 đối số double để gán giá trị. Constructor phải là private. Chúng ta không dùng new để tạo instance cho enum.
+ Mỗi constant đều ngầm định là static final, vì vậy, lệnh sau sẽ sai:
EnumDemo.MERCURY = EnumDemo.EARTH; // Error
+ Như chúng ta thấy, ta có thể dùng switch .. case để làm việc với Enum.
+ Enum ngầm định extends lớp java.lang.Enum, vậy nên nó không thể extends thêm class nào khác. Đồng thời nó có sẵn nhiều method như toString(), values() như ta đã sử dụng trong ví dụ.
+ Chúng ta cũng hoàn toàn có thể Override các method như toString()
- Vài thứ về interface và inheritance:
+ Giả sử ta có interface InterfaceDemo và class ClassDemo implements interface này.
Chú ý về việc dùng interface như 1 kiểu dữ liệu sau:
ClassDemo cd = new ClassDemo();
InterfaceDemo id = (InterfaceDemo)cd;
khi đó cd bắt buộc phải là object của lớp implements Interface này.
+ Ta có thể mở rộng 1 interface đã có bằng cách viết 1 interface khác extends nó (không nên thêm cập nhật vào interface cũ vì sẽ khiến các class implement phải triển khai thêm):
- field ngầm định luôn là public static final,
- method ngầm định là public abstract
+ Modifier - khả năng truy cập của các method Override luôn >= các method được override trong superclass.
+ Overloading - nạp chồng (giống tên method, khác signature) và Override - ghi đè (giống signature) không thể thể cùng xảy ra trên 1 method ở subclass
+ Override method ở subclass có thể có kiểu giá trị trả về là subtype của kiểu trả về của method được override superclass, khi đó gọi là
+ Chúng ta xem ví dụ sau về triển khai 1 interaface:
- java.lang.Object class - superclass of all - một vài method:
+ clone():
- Nếu object không thuộc lớp implements của Cloneable interface thì sẽ báo lỗi: CloneNotSupportedException.
- Nếu muốn dùng được, hoặc override, hoặc thêm "implements Cloneable" vào class mà ta sẽ dùng,
- Đối với các object có tham chiếu các object khác, ta phải override method này để nó clone cả object ngoài kia.
+ equals()
- Chỉ so sánh đúng với primitive data type, reference data type gần như phải override lại,
- Khi override equals(), cần override cả hashCode()
+ finalize()
- Không dựa vào hàm này để cleanup object
- Method nào được gọi bởi constructor, nên đặt với final để nó "cố định" !
- Abstract class:
+ Tương tự interface, ngoại trừ:
- Có thể chứa các field không phải final, static
- Có 1 hay nhiều abstract method (chỉ ở dạng khai báo - chưa implement - giống interface)
- Có thể có hoặc không method được implement (cho phép các subclass kế thừa 1 số method định sẵn giống nhau, còn các method khác thì implement các kiểu khác nhau, linh hoạt hơn interface)
- Được dùng trong trường hợp chia sẻ với lớp con nhiều method (đã implemt) và có 1 số method khác nhau (abstract method)
+ Các subclass phải implment hết đống abstract đó, nếu không chính các subclass cũng phải là abstract
- Generics:
+ Chú ý rằng:
Integer là lớp con của Number, nhưng
Box<Integer> thì KHÔNG phải là lớp con của Box<Number>. Do đó:
Number number = new Number();
Integer integer = new Integer();
number = integer; // OK
integer = number; // NO
Box<Number> boxNum = new Box();
Box<Integer> boxInt = new Box();
boxNum = boxInt; // NO
Chú ý điều sau:
ArrayList<?> : là supertype của ArrayList<Integer>, ArrayList<String>,...
Trong khi ArrayList<Object> thì không phải. Do vậy, với method sau:
Nếu lớp con không có constructor, compiler gọi 1 constructor mặc định, đồng thời gọi luôn constructor không đối số của lớp cha (lúc này nếu lớp cha có 1 constructor có đối số thì sẽ gây lỗi, thà không có luôn để compiler ban phát miễn phí constructor mặc định)
- Access level of class:
+ Outer class chỉ có thể là public (visible với mọi class) hoặc package private (ngầm định - không có từ khóa public - visible trong package). Điều tương tự với Interface.
+ Ở phạm vị member cũng tương tự, có thêm private - visible trong nội bộ class, protected - visible trong package và các lớp con (khác package).
+ Interface cũng chỉ có thể là public hay package, không thể private hay protected
+ Interface chỉ chứa thành viên public hoặc package private
+ Outer class hoạt động gần giống static, trong khi đó inner class thì không.
- Phân biệt Parameter và Argument
Parameter là tham số lúc khai báo/ định nghĩa method
Argument là giá trị thực truyền vào khi gọi method
- Trong java, không thể truyền method như là 1 parameter tới method khác, thay vào đó, ta có thể truyền đối tượng, sau đó gọi method sau.
- Khi hết sử dụng, tất cả các reference đến 1 object đều phải đặt về null thì mới có thể giải phóng object.
- Một method dùng 1 tên Class làm giá trị trả về, thì giá trị trả về phải là đối tượng của lớp đó, hoặc LỚP CON của lớp đó. Nếu dùng Interface như giá trị trả về, method đó phải trả về đối tượng của lớp implement Interface kia.
- Biến (thuộc tính) static của 1 class ngoài việc được gọi không cần thông qua đối tượng, còn 1 điều đáng lưu ý: Tất cả các lời gọi đến biến đó (từ gọi bằng [ClassName].[Biến static] đến việc gọi qua các đối tượng [DoiTuong1].[Biến static], [DoiTuong2].[Biến static]...) đều trỏ đến
**1 vị trí duy nhất**trên bộ nhớ Vì vậy, 1 thay đổi sẽ là thay đổi chung trên các đối tượng khác nhau (nếu có).
Các biến static còn được gọi là class variable, biến không static gọi là instance variable
- Nested class:
+ Chia 2 loại, static nested class và inner class (non-static)
++ Static nested class: (SNC)
- Giống như static field, SNC không truy cập được bất cứ phần tử instance của lớp cha, nếu muốn phải thông qua object, với các phần tử này, SNC truy cập y như các top-level class khác.
++ Inner class (IC):
- Truy cập các phần tử của lớp cha như mọi thành phần khác.
- Không thể chứa bất kỳ static member nào
- IC có thể chia thành 2 loại nhỏ:
+ Local class:
- Là class nằm trong 1 block,
- Có thể truy cập thành viên của lớp ngoài (không nằm trong block) thông qua: [Lớp outer].[Thành viên]
- Nếu Local class nằm trong static method thì nó chỉ truy cập được các thành viên static của lớp ngoài mà thôi (nhưng ngay cả khi đó, local class vẫn là non-static nhé, nó luôn luôn là vậy :)
- Truy cập các biến local (bên trong khối block) chỉ khi các biến này là final. Tuy vậy, từ SE 8 trở lên, local class có thể gọi trực tiếp parameter của method chứa nó.
- Không thể chứa bất kỳ static member nào. Trường hợp duy nhất là constant (constant là biến kiểu primitive hoặc String được khai báo và khởi tạo với compile-time constant expression - là một chuỗi hoặc một biểu thức số học có thể được xác định giá trị ở thời gian biên dịch)
+ Anonymous class:(AC)
- Xem ví dụ sau:
class Demo {
...
public class CHello { // Inner class
private String sMsg = "hello";
public void display(String aMsg) {
Calc.log(aMsg);
}
}
public void FHello() {
CHello annClass = new CHello() { // go to ANONYMOUS class - extends CHello
@Override
public void display(String aMsg) {
Calc.log(aMsg);
Calc.log(aMsg);
}
};
annClass.display("hello");
}
public static void main(String args[])
{
Demo ds = new Demo();
ds.FHello();
}
- AC được xem là 1 phần của biểu, như đã thấy trên ví dụ, nó kết thúc với ";"
- AC đi với việc extends 1 class hay implement 1 interface nào đó
- Trong AC, chí có thể là lệnh khai báo, method, chứ không thể chứa 1 statements nào.
- Trong AC, bạn không thể tạo constructor
- Các đặc điểm khác giống với Local class
- Enum:
Dạng đơn giản:
public enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}
Hay phức tạp hơn:
enum EnumDemo { // Enum type class
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private double mass;
private double radius;
private EnumDemo(double aMass, double aRadius) {
mass = aMass;
radius = aRadius;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
}
...
public static void main(String args[])
{
EnumDemo ed = EnumDemo.MERCURY;
if (ed.equals(EnumDemo.MERCURY)) {
log("Mercury !");
log("Mass: " + EnumDemo.MERCURY.getMass());
log("Radius: " + ed.getRadius());
//EnumDemo.MERCURY = EnumDemo.EARTH; // Error
}
for (EnumDemo e : EnumDemo.values()) {
switch (e) {
case EARTH:
log(e + ": " + e.getMass() + " - " + e.getRadius() + " - We are here !");
break;
default:
log(e + ": " + e.getMass() + " - " + e.getRadius());
}
}
}
public static void log(Object aMsg){
System.out.println(String.valueOf(aMsg));
}
Có thể coi là 1 dạng class/ interface đặc biệt với 1 số đặc điểm sau:
+ Có thể định nghĩa constructor, method, variable...
+ Như ví dụ trên, bạn có thể gán các giá trị với từng constant của Enum, trong trường hợp này bạn phải khai báo biến và constructor vì MERCURY(3.303e+23, 2.4397e6) thực chất gọi 1 constructor 2 đối số double để gán giá trị. Constructor phải là private. Chúng ta không dùng new để tạo instance cho enum.
+ Mỗi constant đều ngầm định là static final, vì vậy, lệnh sau sẽ sai:
EnumDemo.MERCURY = EnumDemo.EARTH; // Error
+ Như chúng ta thấy, ta có thể dùng switch .. case để làm việc với Enum.
+ Enum ngầm định extends lớp java.lang.Enum, vậy nên nó không thể extends thêm class nào khác. Đồng thời nó có sẵn nhiều method như toString(), values() như ta đã sử dụng trong ví dụ.
+ Chúng ta cũng hoàn toàn có thể Override các method như toString()
- Vài thứ về interface và inheritance:
+ Giả sử ta có interface InterfaceDemo và class ClassDemo implements interface này.
Chú ý về việc dùng interface như 1 kiểu dữ liệu sau:
ClassDemo cd = new ClassDemo();
InterfaceDemo id = (InterfaceDemo)cd;
khi đó cd bắt buộc phải là object của lớp implements Interface này.
+ Ta có thể mở rộng 1 interface đã có bằng cách viết 1 interface khác extends nó (không nên thêm cập nhật vào interface cũ vì sẽ khiến các class implement phải triển khai thêm):
public interface DoItPlus extends DoIt { boolean didItWork(int i, double x, String s); }+ Trnog interface:
- field ngầm định luôn là public static final,
- method ngầm định là public abstract
+ Modifier - khả năng truy cập của các method Override luôn >= các method được override trong superclass.
+ Overloading - nạp chồng (giống tên method, khác signature) và Override - ghi đè (giống signature) không thể thể cùng xảy ra trên 1 method ở subclass
+ Override method ở subclass có thể có kiểu giá trị trả về là subtype của kiểu trả về của method được override superclass, khi đó gọi là
**covariant return type**
+ Chúng ta xem ví dụ sau về triển khai 1 interaface:
- java.lang.Object class - superclass of all - một vài method:
+ clone():
- Nếu object không thuộc lớp implements của Cloneable interface thì sẽ báo lỗi: CloneNotSupportedException.
- Nếu muốn dùng được, hoặc override, hoặc thêm "implements Cloneable" vào class mà ta sẽ dùng,
- Đối với các object có tham chiếu các object khác, ta phải override method này để nó clone cả object ngoài kia.
+ equals()
- Chỉ so sánh đúng với primitive data type, reference data type gần như phải override lại,
- Khi override equals(), cần override cả hashCode()
+ finalize()
- Không dựa vào hàm này để cleanup object
- Method nào được gọi bởi constructor, nên đặt với final để nó "cố định" !
- Abstract class:
+ Tương tự interface, ngoại trừ:
- Có thể chứa các field không phải final, static
- Có 1 hay nhiều abstract method (chỉ ở dạng khai báo - chưa implement - giống interface)
- Có thể có hoặc không method được implement (cho phép các subclass kế thừa 1 số method định sẵn giống nhau, còn các method khác thì implement các kiểu khác nhau, linh hoạt hơn interface)
- Được dùng trong trường hợp chia sẻ với lớp con nhiều method (đã implemt) và có 1 số method khác nhau (abstract method)
+ Các subclass phải implment hết đống abstract đó, nếu không chính các subclass cũng phải là abstract
- Generics:
+ Chú ý rằng:
Integer là lớp con của Number, nhưng
Box<Integer> thì KHÔNG phải là lớp con của Box<Number>. Do đó:
Number number = new Number();
Integer integer = new Integer();
number = integer; // OK
integer = number; // NO
Box<Number> boxNum = new Box();
Box<Integer> boxInt = new Box();
boxNum = boxInt; // NO
public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK
Box<Number> box = new Box<Number>(); box.add(new Integer(10)); // OK box.add(new Double(10.1)); // OKNhưng:
public void boxTest(Box<Number> n) { /* ... */ }
`Box<Integer> boxInt = new Box<Integer>();` `boxTest(boxInt); // NO`
Chú ý điều sau:
ArrayList<?> : là supertype của ArrayList<Integer>, ArrayList<String>,...
Trong khi ArrayList<Object> thì không phải. Do vậy, với method sau:
public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }Không chấp nhận List<Integer>, List<String>..., nhưng
public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }thì chấp hết
## Thứ Hai, ngày 06 tháng 1 năm 2014
###
[Java - Concurrency - Đa luồng](http://nguoixanhtihon.blogspot.com/2014/01/java-concurrency-luong.html)
Giống như nhiều ngôn ngữ khác, java cũng hỗ trợ đa luồng, đa nhiệm, 2 đơn vị căn bản là process và thread.
Nếu như 1 process sở hữu cho mình 1 môi trường thực thi riêng, bao gồm bộ nhớ, tài nguyên, thì thread, lại chia sẻ tài nguyên, bộ nhớ của process chứa nó.
Nói chung process và thread là những khái niệm khá cơ bản trong vấn đề đa nhiệm, vì vậy bạn nên tìm hiểu về nền tảng và sau đó hãy xem xét đến môi trường của java.
Ngay cả CPU 1 core thì cũng đảm nhận đa nhiệm, nhiều process, thread cùng lúc được, vì CPU biết các chia thời gian xử lý thành những phần nhỏ gọi là slicing và xen lẫn các thread vào các slicing đó.
Một chương trình có thể có nhiều process liên kết với nhau qua IPC (pipes, sockets,...), 1 process có ít nhất 1 thread, khi chúng ta tạo 1 ứng dụng đơn giản nhất thì mặc định chúng ta có main thread, main thread có thể tạo các thread mới...
Chúng ta sẽ chủ yếu nói về Thread.
a. Định nghĩa và khởi động 1 thread:
Có 2 cách sau định nghĩa và khởi động 1 process:
a1. Implements Runnable interface: Chúng ta phải Override run() method
a2. Extends Thread class: Chúng ta cũng phải Override run() method
Chúng ta sẽ dùng cách thứ 1 vì Runnable Object có thể subclass của nhiều class khác ngoài Thread.
b. Sleep 1 thread:
Làm cho thread suspend 1 khoảng thời gian được chỉ định. Để sử dụng được chúng ta phải làm thủ tục throw/catch InterruptedException. Hãy xem 1 ví dụ:
c. Interrup
Dừng thực thi công việc hiện tại để làm việc khác, tuy nhiên, đa phần là terminal thread.
Để thread bị interrupt hoạt động đúng thì thread cần "Hỗ trợ Interruption":
- Đối với thread thực thi thường xuyên các method gây InterruptedException (như sleep, join,...) thì cần return từ run() method ngay khi catch Exception này.
- Đối với những thread lâu không thực thi các method gây InterruptedException, cần định kỳ kiểm tra và return như thế này:
hay
d. Join
t.join() hay t.join(miliseconds) có nghĩa là thread hiện tại (thread gọi method này) sẽ tạm ngưng và chỉ trở lại khi thread t thực thi xong (hoặc 1 khoảng thời gian nếu chỉ định số miliseconds)
2. Synchronization
Các thread có thể chia sẻ với nhau quyền truy cập đến các field cũng như là các object được reference.tới. Việc này mang lại hiệu quả nhưng nhiều khi gây nên nhiều lỗi như thread interference hay memory consistency error.
Chúng ta có thể dùng Synchronization để giải quyết các vấn đề trên (thực ra việc này cũng có thể gây ra lỗi thread contention - gây chậm chương trình, các thread liên quan)
a. Thread Interference:
Đây là lỗi khi nhiều thread cùng truy cập và xử lý với cùng 1 đối tượng trong cùng khoảng thời gian, dẫn tói các thread gây can thiệp lẫn nhau không mong muốn. Ví dụ:
Bạn có tưởng tượng được gì nếu 2 thread A và B cùng gọi và xử lý 1 Counter object, thread A gọi increment() method, thread B gọi decrement() method, sau đó cả 2 gọi value() method để in kết quả.
Nếu xuất phát ban đầu là c=0, thì:
b. Memory consistency error:
Giả sử ta có:
c = 0;
#1: c++; thread A
#2: System.out.println(c); thread B
Vấn đề là, không phải lúc nào thread A cũng thực hiện lệnh của mình trước thread B, và khi đó, mọi ý đồ của coder sẽ bị phá vỡ.
Lỗi này được nói chung là: các thread khác nhau có cách nhìn không tương xứng mà lẽ ra phải có với cùng 1 dữ liệu.
Có thể tránh lỗi này với thủ tục gọi là happens-before relationship.
c. Happens-before relationship:
Trước hết chúng ta cần hiểu rằng, thứ tự của các dòng lệnh trong mã nguồn chương trình hoàn toàn có thể bị đảo lộn, sắp xếp lại bởi compiler, JVM hay processer, điều này để dảm bảo tính hiệu quả và yêu cầu từ các nhà phát triển khác nhau.
Như vậy, nguy cơ về chuyện 1 lệnh ở 1 thread thực thi nhưng với những dữ liệu chưa được cập nhật đúng cách trên bộ nhớ - do nó không "nhìn thấy" kết quả từ lênh trước theo như ý đồ của coder (phần trên đã nói). Để khắc phục điều này, thủ tục happens-before relationship được đề xướng.
Nếu A và B được gọi trong cùng thread, chúng ta luôn được đảm bảo chúng sẽ được gọi theo thứ tự nhu trong mã nguồn.
Nếu khác thread, A happens-before relationship với B, đảm bảo rằng A sẽ được gọi trước B hay nói cách khác B nhìn thấy kết quả từ A.
Nhưng chúng ta phải chú ý 1 điều, A và B ở đây không phải 1 chỉ lệnh, mà là 1 action, bao gồm các thao tác: Đọc/ viết các biến, lock/ unlock monitor, starting/ joining thread.
Sở dĩ như vậy vì 1 lệnh đôi khi không cần thiết phải giữ đúng thứ tự, nếu điều đó không ảnh hưởng đến kết quả, ví dụ:
x = 3; // (1)
y = 4; // (2)
...
Ở đây lệnh (1) và (2) có thể đổi thứ tự được.
Synchronization là 1 trong nhiều cách thực hiện happens-before relationship
d. Synchronization:
Như các vấn đề đã nói trên, chúng ta cần đảm bảo rằng tại 1 thời điểm chỉ 1 thread được truy cập 1 đối tượng, thread này thực hiện xong đến lượt thread khác.
Có 2 cách chính: Syn. method và Syn. statement:
d1. Syn. method:
Như ta thấy, chỉ cần thêm keyword: synchronized trước method là được. Với cách này ta có 2 hệ quả sau:
- Đảm bảo rằng, trong 1 thời điểm, 1 synchronized method của 1 đối tượng chỉ được thực hiện bởi 1 thread (các thread khác sẽ bị block nếu cùng nhắm tới method này, cho đến khi thread kia hoàn thành)
- Sau khi 1 synchronized method exit, nó tự động tạo happens-before relationship với bất cứ method được gọi theo sau nào của cùng đối tượng.
Chú ý constructor không synchronized được và cũng không cần (vì với 1 đối tượng thì nó chỉ được gọi 1 lần bởi 1 thread)
d2. Synchronized statement (block):
Trước hết chúng ta nói qua về lock hay monitor. Mỗi object có cho mình 1 lock, 1 thread muốn làm việc với object này phải "own" được lock của nó. Trước khi thực thi với object, thread phải acquire lock, sau khi thực hiện xong, thread release lock này. Trong khi 1 thread đang own 1 object clock, các thread khác phải đợi cho đến khi thread đó nhả ra (release).
Synchronized statement khác Synchronized method ở chỗ Synchronized statement chỉ định cụ thể object.
Cơ chế của việc này như sau: khi nhận thấy có từ khóa Synchronized, compiler làm cho các khối lệnh làm thủ tục own lock của object được chỉ định, như vậy ta đảm bảo được tính đồng bộ hóa cho các thread với đối tượng này. Ví dụ:
class Callme {
void call(String msg) {
synchronized(this) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
//synchronized(target) { // synchronized block
target.call(msg);
//}
}
}
public class Synch {
public static void main(String args[]) {
Callme myTarget = new Callme();
Caller ob1 = new Caller(myTarget, "Hello");
Caller ob2 = new Caller(myTarget, "Synchronized");
Caller ob3 = new Caller(myTarget, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
3. Một số vấn đề với multithread:
a. Deadlock:
Sửu dụng từ khóa synchronized để đồng bộ hóa cũng gây ra những vấn đề ngoài ý muốn. Deadlock là vấn đề khi 2 hay nhiều thread đợi lẫn nhau, dẫn đến vô thời hạn, thread đợi lẫn nhau vì thread A đang giữ lock của đối tượng a, và có 1 method nào đấy muốn truy cập đến đối tượng b được lock bởi thread B và thế là thread A phải đợi thread B làm xong phần việc và nhả b ra, rủi thay thread B cũng cần truy cập qua a (ngược lại), và thế là 2 thread đợi nhau trong... vô vọng.
Ví dụ 1:
public class MyDeadlock {
private static MyDeadlock a = new MyDeadlock();
private static MyDeadlock b = new MyDeadlock();
public synchronized void testFunc(MyDeadlock friend) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside testFunc !");
System.out.println(threadName + ": Prepare to get friend cross...");
try { // cause to asynchronization if not use synchronized keyword
Thread.sleep(2000);
} catch(InterruptedException e) {
System.out.println(threadName + " is interrupted !");
}
friend.cross();
}
public synchronized void cross() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside croos !");
}
static class MyRunnable implements Runnable {
public void run() {
a.testFunc(b); // by additional thread (t)
}
}
public static void main(String... args) {
Thread t = new Thread(new MyRunnable(), "DEMO THREAD");
t.start();
b.testFunc(a); // by Main thread
}
}
Hoặc ví dụ 2:
Ở ví dụ 2, nếu thread A thực thi oneMethod(), threadB thực thi anotherMethod(). Chúng ta hãy tưởng tượng khi thread A gọi oneMethod(), nó sẽ lock cacheLock đầu tiên, thread B gọi anotherMethod(), nó sẽ lock tableLock. Sau đó, thread A đòi lock tiếp tableLock, tương tự thread B cũng đòi lock tiếp cacheLock (theo thứ tự trong mỗi method). Và deadlock xảy ra.
Ví dụ 3:
Khắc phục/ tránh deadlock:
Trong vd 2 và 3, để ý nếu ta biết cách xếp đặt thứ tự lock các đối tượng của các thread giống nhau thì sẽ giải quyết được.
ví dụ 3: sửa method transfer():
Một phương pháp nữa là giảm scope giữa các phương thức đồng bộ (hạn chế việc 2 các method này ở các lớp khác nhau,...) xuống tối đa có thể.
b. Starvation:
Một thread không thể truy cập tài nguyên, biến... do bị chiếm bởi 1 thread tham lam khác
c.Livelock:
Một vấn đề nữa là tình trạng 2 hay nhiều thread cùng truy cập đến 1 tài nguyên, biến,... dùng chung, khác với deadlock - các thread bị block, không có sự hoạt động, trong livelock, các thread luôn thay đổi trạng thái mặc dù không có sự tiến triển nào của chương trình.
Nếu như 1 process sở hữu cho mình 1 môi trường thực thi riêng, bao gồm bộ nhớ, tài nguyên, thì thread, lại chia sẻ tài nguyên, bộ nhớ của process chứa nó.
Nói chung process và thread là những khái niệm khá cơ bản trong vấn đề đa nhiệm, vì vậy bạn nên tìm hiểu về nền tảng và sau đó hãy xem xét đến môi trường của java.
Ngay cả CPU 1 core thì cũng đảm nhận đa nhiệm, nhiều process, thread cùng lúc được, vì CPU biết các chia thời gian xử lý thành những phần nhỏ gọi là slicing và xen lẫn các thread vào các slicing đó.
Một chương trình có thể có nhiều process liên kết với nhau qua IPC (pipes, sockets,...), 1 process có ít nhất 1 thread, khi chúng ta tạo 1 ứng dụng đơn giản nhất thì mặc định chúng ta có main thread, main thread có thể tạo các thread mới...
Chúng ta sẽ chủ yếu nói về Thread.
**1. Thread Object:**
a. Định nghĩa và khởi động 1 thread:
Có 2 cách sau định nghĩa và khởi động 1 process:
a1. Implements Runnable interface: Chúng ta phải Override run() method
public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) {
HelloRunnable run = new HelloRunnable();
Thread t = new Thread(run, "HELLO THREAD !");
t.start(); } }
a2. Extends Thread class: Chúng ta cũng phải Override run() method
public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) {
HelloThread t = new HelloThread();
t.start(); } }
Chúng ta sẽ dùng cách thứ 1 vì Runnable Object có thể subclass của nhiều class khác ngoài Thread.
b. Sleep 1 thread:
Làm cho thread suspend 1 khoảng thời gian được chỉ định. Để sử dụng được chúng ta phải làm thủ tục throw/catch InterruptedException. Hãy xem 1 ví dụ:
public class SleepMessages { public static void main(String args[]) throws InterruptedException { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; for (int i = 0; i < importantInfo.length; i++) { //Pause for 4 seconds Thread.sleep(4000); //Print a message System.out.println(importantInfo[i]); } } }
c. Interrup
Dừng thực thi công việc hiện tại để làm việc khác, tuy nhiên, đa phần là terminal thread.
Để thread bị interrupt hoạt động đúng thì thread cần "Hỗ trợ Interruption":
- Đối với thread thực thi thường xuyên các method gây InterruptedException (như sleep, join,...) thì cần return từ run() method ngay khi catch Exception này.
public void run() {
for (int i = 0; i < importantInfo.length; i++) { // Pause for 4 seconds try { Thread.sleep(4000); } catch (InterruptedException e) { // We've been interrupted: no more messages.
return;} // Print a message System.out.println(importantInfo[i]); }
}
- Đối với những thread lâu không thực thi các method gây InterruptedException, cần định kỳ kiểm tra và return như thế này:
if (Thread.interrupted()) { // We've been interrupted: no more crunching. return; }
hay
if (Thread.interrupted()) { throw new InterruptedException(); }
d. Join
t.join() hay t.join(miliseconds) có nghĩa là thread hiện tại (thread gọi method này) sẽ tạm ngưng và chỉ trở lại khi thread t thực thi xong (hoặc 1 khoảng thời gian nếu chỉ định số miliseconds)
2. Synchronization
Các thread có thể chia sẻ với nhau quyền truy cập đến các field cũng như là các object được reference.tới. Việc này mang lại hiệu quả nhưng nhiều khi gây nên nhiều lỗi như thread interference hay memory consistency error.
Chúng ta có thể dùng Synchronization để giải quyết các vấn đề trên (thực ra việc này cũng có thể gây ra lỗi thread contention - gây chậm chương trình, các thread liên quan)
a. Thread Interference:
Đây là lỗi khi nhiều thread cùng truy cập và xử lý với cùng 1 đối tượng trong cùng khoảng thời gian, dẫn tói các thread gây can thiệp lẫn nhau không mong muốn. Ví dụ:
class Counter { private int c = 0; public void increment() { c++; } public void decrement() { c--; } public int value() { return c; } }
Bạn có tưởng tượng được gì nếu 2 thread A và B cùng gọi và xử lý 1 Counter object, thread A gọi increment() method, thread B gọi decrement() method, sau đó cả 2 gọi value() method để in kết quả.
Nếu xuất phát ban đầu là c=0, thì:
- Thread A: Nhận giá trị từ c.
- Thread B: Nhận giá trị từ c.
- Thread A: Increment giá trị mới nhận; kết qủa là 1.
- Thread B: Decrement giá trị mới nhận; kết quả là -1.
- Thread A: Lưu giá trị vào c; c = 1.
- Thread B: Lưu giá trị vào c; c = -1.
b. Memory consistency error:
Giả sử ta có:
c = 0;
#1: c++; thread A
#2: System.out.println(c); thread B
Vấn đề là, không phải lúc nào thread A cũng thực hiện lệnh của mình trước thread B, và khi đó, mọi ý đồ của coder sẽ bị phá vỡ.
Lỗi này được nói chung là: các thread khác nhau có cách nhìn không tương xứng mà lẽ ra phải có với cùng 1 dữ liệu.
Có thể tránh lỗi này với thủ tục gọi là happens-before relationship.
c. Happens-before relationship:
Trước hết chúng ta cần hiểu rằng, thứ tự của các dòng lệnh trong mã nguồn chương trình hoàn toàn có thể bị đảo lộn, sắp xếp lại bởi compiler, JVM hay processer, điều này để dảm bảo tính hiệu quả và yêu cầu từ các nhà phát triển khác nhau.
Như vậy, nguy cơ về chuyện 1 lệnh ở 1 thread thực thi nhưng với những dữ liệu chưa được cập nhật đúng cách trên bộ nhớ - do nó không "nhìn thấy" kết quả từ lênh trước theo như ý đồ của coder (phần trên đã nói). Để khắc phục điều này, thủ tục happens-before relationship được đề xướng.
Nếu A và B được gọi trong cùng thread, chúng ta luôn được đảm bảo chúng sẽ được gọi theo thứ tự nhu trong mã nguồn.
Nếu khác thread, A happens-before relationship với B, đảm bảo rằng A sẽ được gọi trước B hay nói cách khác B nhìn thấy kết quả từ A.
Nhưng chúng ta phải chú ý 1 điều, A và B ở đây không phải 1 chỉ lệnh, mà là 1 action, bao gồm các thao tác: Đọc/ viết các biến, lock/ unlock monitor, starting/ joining thread.
Sở dĩ như vậy vì 1 lệnh đôi khi không cần thiết phải giữ đúng thứ tự, nếu điều đó không ảnh hưởng đến kết quả, ví dụ:
x = 3; // (1)
y = 4; // (2)
...
Ở đây lệnh (1) và (2) có thể đổi thứ tự được.
Synchronization là 1 trong nhiều cách thực hiện happens-before relationship
d. Synchronization:
Như các vấn đề đã nói trên, chúng ta cần đảm bảo rằng tại 1 thời điểm chỉ 1 thread được truy cập 1 đối tượng, thread này thực hiện xong đến lượt thread khác.
Có 2 cách chính: Syn. method và Syn. statement:
d1. Syn. method:
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
Như ta thấy, chỉ cần thêm keyword: synchronized trước method là được. Với cách này ta có 2 hệ quả sau:
- Đảm bảo rằng, trong 1 thời điểm, 1 synchronized method của 1 đối tượng chỉ được thực hiện bởi 1 thread (các thread khác sẽ bị block nếu cùng nhắm tới method này, cho đến khi thread kia hoàn thành)
- Sau khi 1 synchronized method exit, nó tự động tạo happens-before relationship với bất cứ method được gọi theo sau nào của cùng đối tượng.
Chú ý constructor không synchronized được và cũng không cần (vì với 1 đối tượng thì nó chỉ được gọi 1 lần bởi 1 thread)
d2. Synchronized statement (block):
Trước hết chúng ta nói qua về lock hay monitor. Mỗi object có cho mình 1 lock, 1 thread muốn làm việc với object này phải "own" được lock của nó. Trước khi thực thi với object, thread phải acquire lock, sau khi thực hiện xong, thread release lock này. Trong khi 1 thread đang own 1 object clock, các thread khác phải đợi cho đến khi thread đó nhả ra (release).
Synchronized statement khác Synchronized method ở chỗ Synchronized statement chỉ định cụ thể object.
public class Counter { private int count = 0; public void increment() {
synchronized (this) {count++; } } public int getCount() { synchronized (this) {return count; } } }
Cơ chế của việc này như sau: khi nhận thấy có từ khóa Synchronized, compiler làm cho các khối lệnh làm thủ tục own lock của object được chỉ định, như vậy ta đảm bảo được tính đồng bộ hóa cho các thread với đối tượng này. Ví dụ:
class Callme {
void call(String msg) {
synchronized(this) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
//synchronized(target) { // synchronized block
target.call(msg);
//}
}
}
public class Synch {
public static void main(String args[]) {
Callme myTarget = new Callme();
Caller ob1 = new Caller(myTarget, "Hello");
Caller ob2 = new Caller(myTarget, "Synchronized");
Caller ob3 = new Caller(myTarget, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}
3. Một số vấn đề với multithread:
a. Deadlock:
Sửu dụng từ khóa synchronized để đồng bộ hóa cũng gây ra những vấn đề ngoài ý muốn. Deadlock là vấn đề khi 2 hay nhiều thread đợi lẫn nhau, dẫn đến vô thời hạn, thread đợi lẫn nhau vì thread A đang giữ lock của đối tượng a, và có 1 method nào đấy muốn truy cập đến đối tượng b được lock bởi thread B và thế là thread A phải đợi thread B làm xong phần việc và nhả b ra, rủi thay thread B cũng cần truy cập qua a (ngược lại), và thế là 2 thread đợi nhau trong... vô vọng.
Ví dụ 1:
public class MyDeadlock {
private static MyDeadlock a = new MyDeadlock();
private static MyDeadlock b = new MyDeadlock();
public synchronized void testFunc(MyDeadlock friend) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside testFunc !");
System.out.println(threadName + ": Prepare to get friend cross...");
try { // cause to asynchronization if not use synchronized keyword
Thread.sleep(2000);
} catch(InterruptedException e) {
System.out.println(threadName + " is interrupted !");
}
friend.cross();
}
public synchronized void cross() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Inside croos !");
}
static class MyRunnable implements Runnable {
public void run() {
a.testFunc(b); // by additional thread (t)
}
}
public static void main(String... args) {
Thread t = new Thread(new MyRunnable(), "DEMO THREAD");
t.start();
b.testFunc(a); // by Main thread
}
}
Hoặc ví dụ 2:
public static Object cacheLock = new Object(); public static Object tableLock = new Object(); ... public void oneMethod() { synchronized (cacheLock) { synchronized (tableLock) { doSomething(); } } } public void anotherMethod() { synchronized (tableLock) { synchronized (cacheLock) { doSomethingElse(); } } }
Ở ví dụ 2, nếu thread A thực thi oneMethod(), threadB thực thi anotherMethod(). Chúng ta hãy tưởng tượng khi thread A gọi oneMethod(), nó sẽ lock cacheLock đầu tiên, thread B gọi anotherMethod(), nó sẽ lock tableLock. Sau đó, thread A đòi lock tiếp tableLock, tương tự thread B cũng đòi lock tiếp cacheLock (theo thứ tự trong mỗi method). Và deadlock xảy ra.
Ví dụ 3:
// File Name ThreadSafeBankAccount.java public class ThreadSafeBankAccount { private double balance; private int number; public ThreadSafeBankAccount(int num, double initialBalance) { balance = initialBalance; number = num; } public int getNumber() { return number; } public double getBalance() { return balance; } public void deposit(double amount) { synchronized(this) { double prevBalance = balance; try { Thread.sleep(4000); }catch(InterruptedException e) {} balance = prevBalance + amount; } } public void withdraw(double amount) { synchronized(this) { double prevBalance = balance; try { Thread.sleep(4000); }catch(InterruptedException e) {} balance = prevBalance - amount; } } } // File Name LazyTeller.java public class LazyTeller extends Thread { private ThreadSafeBankAccount source, dest; public LazyTeller(ThreadSafeBankAccount a, ThreadSafeBankAccount b) { source = a; dest = b; } public void run() { transfer(250.00); } public void transfer(double amount) { System.out.println("Transferring from " + source.getNumber() + " to " + dest.getNumber()); synchronized(source) { Thread.yield(); synchronized(dest) { System.out.println("Withdrawing from " + source.getNumber()); source.withdraw(amount); System.out.println("Depositing into " + dest.getNumber()); dest.deposit(amount); } } } } public class DeadlockDemo { public static void main(String[] args) { System.out.println("Creating two bank accounts..."); ThreadSafeBankAccount checking = new ThreadSafeBankAccount(101, 1000.00); ThreadSafeBankAccount savings = new ThreadSafeBankAccount(102, 5000.00); System.out.println("Creating two teller threads..."); Thread teller1 = new LazyTeller(checking, savings); Thread teller2 = new LazyTeller(savings, checking); System.out.println("Starting both threads..."); teller1.start(); teller2.start(); } }Khi teller1 gọi transfer(), nó lock source - chính là checking, cùng lúc teller2 gọi transfer() và lock dest - tức saving. Ngay sau khi lock checking, teller1 đòi lock cả saving, ngược lại teller2 lại đòi lock checking (nghiệp vụ transfer đòi hỏi có source và dest để chuyển tiền !). Và thế là deadlock.
Khắc phục/ tránh deadlock:
Trong vd 2 và 3, để ý nếu ta biết cách xếp đặt thứ tự lock các đối tượng của các thread giống nhau thì sẽ giải quyết được.
public static Object cacheLock = new Object(); public static Object tableLock = new Object(); ... public void oneMethod() { synchronized (cacheLock) { synchronized (tableLock) { doSomething(); } } } public void anotherMethod() {
synchronized (cacheLock) {
synchronized (tableLock) { doSomethingElse(); } } }
ví dụ 3: sửa method transfer():
public void transfer(double amount) { System.out.println("Transferring from " + source.getNumber() + " to " + dest.getNumber()); ThreadSafeBankAccount first, second; if(source.getNumber() < dest.getNumber()) { first = source; second = dest; } else { first = dest; second = source; } synchronized(first) { Thread.yield(); synchronized(second) { System.out.println("Withdrawing from " + source.getNumber()); source.withdraw(amount); System.out.println("Depositing into " + dest.getNumber()); dest.deposit(amount); } } }(dùng first và second làm trung gian để đảm bảo 2 thread luôn lock object có getName() trả về nhỏ hơn - first, trước).
Một phương pháp nữa là giảm scope giữa các phương thức đồng bộ (hạn chế việc 2 các method này ở các lớp khác nhau,...) xuống tối đa có thể.
b. Starvation:
Một thread không thể truy cập tài nguyên, biến... do bị chiếm bởi 1 thread tham lam khác
c.Livelock:
Một vấn đề nữa là tình trạng 2 hay nhiều thread cùng truy cập đến 1 tài nguyên, biến,... dùng chung, khác với deadlock - các thread bị block, không có sự hoạt động, trong livelock, các thread luôn thay đổi trạng thái mặc dù không có sự tiến triển nào của chương trình.
Đăng ký: