TỔ CHỨC LƯU TRỮ DỮ LIỆU VỚI NGÔN NGỮ JAVA

10:51

I. Cách tổ chức lưu trữ dữ liệu của một chương trình Java trên bộ nhớ máy tính:

Khi một chương trình Java được thực thi, nó sẽ yêu cầu hệ điều hành cấp phát một không gian trên bộ nhớ để lưu trữ toàn bộ dữ liệu và thông tin của nó.

Sau đó, nó sẽ chia vùng không gian đó thành 4 vùng nhớ (memory segment) để lưu trữ. Để dễ hiểu thì các bạn xem hình ảnh bên dưới nhé:

    Vùng nhớ code (code segment), theo như tên gọi của nó, tất cả mã chương trình (machine code) được lưu ở đây khi chương trình được thực thi.
    + Vùng nhớ data (data segment), đây là nơi lưu trữ những dữ liệu chung của chương trình như các biến static, constant,… những biến dữ liệu mà được sinh ra khi chương trình bắt đầu thực thi và chỉ được giải phóng khi chương trình kết thúc.
    + Vùng nhớ stack (stack segment), đây sẽ là nơi lưu trữ các biến thuộc nhóm kiểu dữ liệu cơ sở (primitive data type như là boolean, int, char,…) và địa chỉ của ô nhớ (memory address).
Tại sao gọi là vùng nhớ stack? Bởi vì dữ liệu ở đây chia theo các nhóm gọi là stack frame, và mỗi stack frame sẽ được lưu vào vùng nhớ stack theo cơ chế Last-in-first-out (LIFO) như một stack.

Vậy, stack frame (SF) là gì? Nó là nơi lưu trữ toàn bộ các biến của một phương thức (method), mỗi phương thức được thực thi sẽ tạo ra một SF. Xem ví dụ bên dưới để hiểu rõ hơn nhé :P

Ở chương trình dưới ta có 3 method: methodA(), methodB() và main:
public static void methodA(){
int a = 10;
System.out.println(a);
}
public static void methodB(){
int b = 20;
System.out.println(b);
methodA();
}
public static void main(String[] args){
int c = 30;
methodB();
}
Khi chương trình này thực thi, nó sẽ tạo ra 3 SF và push vào vùng nhớ stack theo thứ tự nó được gọi: main, methodB, methodA.


Với phương thức main() ta có biến c = 30 nên giá trị 30 này sẽ được lưu trữ ở SF main, và biến c này cũng sẽ có phạm vi sử dụng trong phương thức main() mà thôi. (Xem hình bên)

Tương tự với các phương thức methodB() và methodA(), ta cũng sẽ có các biến b và a tương ứng. Biến b sẽ được lưu trữ ở SF methodB và chỉ có phạm vi sử dụng ở đó, tương tự cho biến a cũng sẽ được lưu trữ ở SF methodA và chỉ có phạm vi sử dụng ở đây thôi nhé.

Khi methodA() thực thi xong, SF methodA sẽ được pop ra khỏi vùng nhớ stack, biến a (với giá trị là 10) đồng thời cũng được giải phóng. Sau đó SF methodB sẽ được pop, biến b được giải phóng và cuối cùng là tới SF main và biến c.

Vùng nhớ heap (heap segment), đây là nơi lưu trữ tất cả các đối tượng (object) được sinh ra trong thời gian thực thi chương trình (run time).



Với vùng nhớ Code và Data, khi chương trình thực thi sẽ cấp phát một không gian có kích thước không đổi.

Còn đối với vùng nhớ Stack và Heap, kích thước của nó sẽ thay đổi (hoặc mở rộng khi tạo thêm biến hoặc đối tượng, hoặc thu hẹp khi một biến hoặc đối tượng được giải phóng) và nếu cần mở rộng thêm, nó sẽ lấy không gian từ Free memory. (Xem lại hình đầu tiên nhé :P )

Ngoài ra, trong bộ nhớ máy tính, mỗi ô nhớ sẽ được đánh địa chỉ để xác định chính xác vị trí của nó trong bộ nhớ. Vì vậy trong mỗi ô nhớ sẽ bao gồm 2 thành phần là Value và Address như hình vẽ.

II. Kiểu dữ liệu:
1. Kiểu dữ liệu nguyên thủy:

Kiểu dữ liệu Độ dài theo số bit Phạm vi biểu diễn giá trị Mô tả
byte 8 -128 đến 127 Số liệu kiểu byte là một loại điển hình dùng để lưu trữ một giá tri bằng một byte. Chúng được sử dụng rộng rãi khi xử lý một file văn bản
char 16 ‘\u0000’ to ’u\ffff ’ Kiểu Char sử dụng để lưu tên hoặc các dữ liệu ký tự .Ví dụ tên ngườI lao động
boolean 1 “True” hoặc “False” Dữ liệu boolean dùng để lưu các giá trị “Đúng” hoặc “sai” Ví dụ : Người lao đông có đáp ứng được yêu cầu của công ty hay không ?
short 16 -32768 đến 32767 Kiểu short dùng để lưu các số có giá trị nhỏ dưới 32767.Ví dụ số lượng người lao động.
Int 32 -2,147,483,648 đến +2,147,483,648 Kiểu int dùng để lưu một số có giá trị lớn đến 2,147,483,648.Ví dụ tổng lương mà công ty phải trả cho nhân viên.
long 64 -9,223,372,036’854,775,808 đến +9,223,372,036’854,775,808 Kiểu long được sử dụng để lưu một số cố giá trị rất lớn đến 9,223,372,036’854,775,808 .Ví dụ dân số của một nước
float 32 -3.40292347E+38 đến +3.40292347E+38 Kiểu float dùng để lưu các số thập phân đến 3.40292347E+38 Ví dụ : giá thành sản phẩm
double 64 -1,79769313486231570E+308 đến +1,79769313486231570E+308 Kiểu double dùng để lưu các số thập phân có giá trị lớn đến1,79769313486231570E+308 Ví dụ giá trị tín dụng của ngân hàng nhà nước.
  • Tất cả các biến thuộc kiểu dữ liệu tham chiếu đều được lưu trên vùng nhớ stack.
  • Ex: int a = 10;
  • Giải thích: int a là cấp phát một ô nhớ 4 byte (kiểu int có độ dài 4 byte) trên vùng nhớ stack. Ô nhớ gồm có Value và địa chỉ.
  • A = 10 gán value cho ô nhớ là 10.
2. Kiểu dữ liệu tham chiếu:
Kiểu dữ liệu Mô tả
Mảng (Array) Tập hợp các dữ liệu cùng kiểu. Ví dụ : tên sinh viên
Lớp (Class) Tập hợp các biến và các phương thức.Ví dụ : lớp “Sinhviên” chứa toàn bộ các chi tiết của một sinh viên và các phương thức thực thi trên các chi tiết đó.
Giao diện (Interface) Là một lớp trừu tượng được tạo ra cho phép cài đặt đa thừa kế trong Java.
  • Kiểu dữ liệu tham chiếu:những biến thuộc kiểu dữ liệu tham chiếu (hay biến tham chiếu) sẽ được lưu tại vùng nhớ stack và đối tượng sinh ra (sau toán tử new) sẽ được lưu tại vùng nhớ heap. Giá trị của biến tham chiếu chính là địa chỉ của đối tượng được sinh ra đó. (Biến được lưu tại vùng nhớ stack, giá trị là địa chỉ của một đối tượngđược lưu tại vùng nhớ heap).
  • Example:
  • String a = newString(“Java”);
Tương tự ở trên, chúng ta cùng chia nhỏ câu lệnh trên dể dễ hình dung các bước mà chương trình sẽ thực thi trong câu lệnh đó nhé!
  • Đầu tiên là String a: Cấp phát một ô nhớ trên vùng nhớ stack, ô nhớ này chính là biến tham chiếu a.
  • newString(): Cấp phát một ô nhớ trên vùng nhớ heap, ô nhớ này là một đối tượng kiểu String, việc cấp ô nhớ này do toán tử new thực hiện.
  • String(“Java”): Gán giá trị “Java” cho ô nhớ trên vùng nhớ heap.
  • a = new String(“Java”): Gán địa chỉ của đối tượng trên vùng nhớ heapcho value của ô nhớ trên vùng nhớ stack(biến tham chiếu a).
Như ví dụ trên, ta thấy biến tham chiếu a tương tự như con trỏ (pointer) trong ngôn ngữ C/C++, nó được lưu tại vùng nhớ stack và tham chiếu đến địa chỉ của một đối tượng được tạo ra trên vùng nhớ heap.

A.      SỰ KHÁC NHAU GIỮA KIỂU DỮ LIỆU NGUYÊN THỦY VÀ KIỂU DỮ LIỆU THAM CHIẾU:

Kiểu:
  • Primitive Data Type (kiểu dữ liệu cơ sở):bao gồm các kiểu dữ liệu byte, short, int, long, float, double, boolean, char.
  • Reference Data Type (kiểu dữ liệu tham chiếu):bao gồm các kiểu dữ liệu còn lại Array, String, Class, Interface.
Lưu trữ:
  • Primitive Data Type:Biến được lưu tại vùng nhớ stack, giá trị là các kiểu dữ liệu cơ sở.
  • Reference Data Type:Biến được lưu tại vùng nhớ stack, giá trị là địa chỉ của một đối tượngđược lưu tại vùng nhớ heap.
⇒ Khi thực hiện việc gán giá trị, so sánh bằng toán tử ==, truyền tham số vào phương thức hay lấy dữ liệu trả về từ hàm thì giá trị của biến được truyền vào:
  • với biến là kiểu dữ liệu cơ sở giá trị của nó chính là giá trị mà đã được truyền cho nó
  • với biến kiểu dữ liệu tham chiếu, giá trị của nó là địa chỉ của một đối tượng nào đó, hoặc là null.
Để hiểu rõ hơn, chúng ta cùng đi qua các ví dụ bên dưới nào!!! :P
Gán giá trị:Khi thực hiện phép gán, giá trị của biến sẽ được copy đến biến mới.
  • Primitive Data Type:Ví dụ:
    int a = 10;
    int b = a;
    Sau khi thực thi 2 câu lệnh này, b sẽ có giá trị là 10. Xem hình minh họa:
    int a = 10; a được gán giá trị là 10.
    int b = a; b được gán giá trị của a (10).
  • Reference Data Type:Ví dụ:
    String a = new String(“Java”);
    String b = a;
    Sau khi thực hiện 2 câu lệnh này, biến tham chiếu b sẽ có giá trị là địa chỉ của đối tượng”Java”. Xem hình minh họa:
    String a = new String(“Java”); Biến tham chiếu a có giá trị là địa chỉ của đối tượng “Java”. (ở đây là 50000).
    String b = a; Giá trị 50000 của biến tham chiếu a được copy sang biến tham chiếu b ⇒ cả a và b đều tham chiếu đến cùng một đối tượng “Java”.

B.      SO SÁNH BẰNG TOÁN TỬ == :

Khi so sánh bằng toán tử ==, giá trị của biến sẽ được so sánh.
  • Primitive Data Type:Ví dụ:
    int a = 10;
    int b = 10;
    System.out.println(a == b);
    Chương trình sẽ hiển thị kết quả là true.
    Mô tả các bước thực thi chương trình:int a = 10; a được gán giá trị là 10
    int b = 10; b cũng được giá trị là 10
    Khi so sánh (a == b) tương đương với so sánh(10 == 10) nên phép toán sẽ trả về true.
  • Reference Data Type:Ví dụ:
    String a = new String(“Java”);
    String b = new String(“Java”);
    System.out.println(a == b);
    Chương trình sẽ hiển thị kết quả là false. Why?? Cùng đi nào!!! :P
    Ở đây do chúng ta sử dụng 2 lần toán tử new nên chương trình sẽ tạo ra 2 đối tượng “Java” trên vùng nhớ heap. Do đó, 2 biến tham chiếu a và b sẽ tham chiếu đến 2 đối tượng “Java” khác nhau có địa chỉ lần lượt là 50000 và 50008.
    String a = new String(“Java”); a có giá trị là 50000.
    String b = new String(“Java”); b có giá trị là 50008.
    Khi so sánh (a == b) tương đương với so sánh(50000 == 50008) nên phép toán sẽ trả vềfalse.
    Truyền tham số:Khi truyền tham số vào một phương thức, chương trình sẽ copy giá trị đã truyền đó vào một biến khác có phạm vi trong phương thức đó để sử dụng. Xem ví dụ để dễ hiểu hơn nhé… :v
  • Primitive data type (truyền tham trị):Ví dụ: Ta có chương trình:
    public static void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;
    }
    public static void main(String[] args){
    int x = 10;
    int y = 20;
    swap(x, y);
    System.out.println(x + “-” + y);
    }
    Sau khi thực thi chương trình, kết quả sẽ là 10-20.
    Mô tả các bước thực thi chương trình:Khi vào phương thức main(), chương trình sẽ tạo ra 1 stack frame (SF) và push nó vào vùng nhớ stack.
    int x = 10;
    int y = 20;
    2 biến trên được khai báo trong phương thức main() và là biến kiểu cơ sở nên nó sẽ được cấp phát trong SF main.
    swap(x, y);
    Sau khi gọi phương thức swap, chương trình sẽ tạo ra một SF khác cho phương thức này và push nó vào vùng nhớ stack. Vì phương thức swap có 2 đối số là a và b, nên khi gọi đến phương thức này nó sẽ tạo ra 2 biến a và b tương ứng, sau đó nó sẽ copy giá trị của biến x và y từ SF main vào biến a và b của SF swap.
    int temp = a;
    Sau khi thực hiện câu lệnh này, chương trình sẽ tạo ra một biến temp nằm trong SF swap (vì biến temp được khai báo trong phương thức swap). Sau đó nó sẽ copy giá trị của biến a sang biến temp.
    a = b;
    Sau khi thực hiện câu lệnh này, chương trình sẽ copy giá trị của biến b vào biến a.
    b = temp;
    tương tự, giá trị của biến temp sẽ được copy vào biến b.Sau khi ra khỏi phương thức swap, SF swap được pop ra khỏi vùng nhớ stack, các biến a, b,temp đồng thời được giải phóng.
    System.out.println(x + “-“ + y);
    Do vậy, sau khi thực hiện câu lệnh này, kết quả vẫn là 10-20.
  • Reference Data Type (truyền tham chiếu):Ví dụ: Ta có chương trình:
    public class MyInteger{
    public int num;
    public MyInteger(int num){
    this.num = num;
    }
    }
    public static void swap(MyInteger a, MyInteger b){
    int temp = a.num;
    a.num = b.num;
    b.num = temp.num;
    }
    public static void main(String[] args){
    MyInteger x = new MyInteger (10);
    MyInteger y = new MyInteger (20);
    swap(x, y);
    System.out.println(x.num + “-” + y.num);
    }
    Sau khi thực thi chương trình, kết quả sẽ là 20-10. Tại sao lại như vậy? Cùng xem mô tả chi tiết bên dưới nhé…
    Mô tả các bước thực thi chương trình:
    Khi vào phương thức main, chương trình sẽ tạo ra 1 SF và push nó vào vùng nhớ stack.
    MyInteger x = new MyInteger (10);
    MyInteger y = new MyInteger (20);
    Chương trình sẽ cấp phát 2 đối tượng MyIntegertrên vùng nhớ heap và 2 ô nhớ trên vùng nhớ stack để lưu trữ địa chỉ của 2 đối tượng trên. (Giống như con trỏ trên C/C++, các biến tham chiếu x và y sẽ trỏ đến địa chỉ của các đối tượng chứa giá trị 10 và 20 trên vùng nhớ heap)
    swap(x, y);
    Khi phương thức swap được gọi, một SF mới được push vào vùng nhớ stack. Phương thức swap chứa các đối số a, b kiểu MyInteger (kiểu tham chiếu) nên trong SF swap sẽ cấp phát 2 biến a, b tương ứng (kiểu tham chiếu) và sau đó sẽ copy giá trị của các biến x và y vào các biến a và b.
    Các biến x và y tham chiếu đến đối tượng 10 và 20trên vùng nhớ heap, nên sau khi copy, các biến avà b cũng tham chiếu đến đối tượng 10 và 20 đó. Tại thời điểm này, đối tượng 10 sẽ được tham chiếu bởi biến x và a, còn đối tượng 20 sẽ được tham chiếu bởi biến y và b.
    int temp = a.num;
    Sau khi câu lệnh này thực hiện, chương trình sẽ tạo ra một biến temp trong SF swap (vì biến tempđược khai báo trong phương thức swap) và copy giá trị num của đối tượng mà biến a đang tham chiếu vào biến temp.
    a.num = b.num;
    Sau khi câu lệnh này thực hiện, chương trình sẽ copy giá trị num của đối tượng mà biến b đang tham chiếu đến vào biến num của đối tượng mà biến a đang tham chiếu đến.
    b.num = temp.num;
    Sau khi câu lệnh này được thực hiện, chương trình sẽ copy giá trị của biến temp vào biến num của đối tượng mà biến b đang tham chiếu đến.
    Sau khi ra khỏi phương thức swap, SF swap được pop ra khỏi vùng nhớ stack, các biến a, b,temp đồng thời được giải phóng.
    System.out.println(x.num + “-” + y.num);
    Tại thời điểm này, biến x đang tham chiếu đến đối tượng có giá trị của num là 20 và y tham chiếu đến đối tượng có giá trị num là 10. Do đó, kết quả hiển thị sẽ là 20-10. Thật tuyệt đúng không nào? :P
    Ở trên là phương thức swap kinh điển đã được nhiều người dùng để ví dụ cho con trỏ trong C/C++ và bây giờ là biến tham chiếu trong Java có cơ chế tương tự như một biến con trỏ.Giá trị trả về:
    Tương tự như những phần trên, giá trị trả về của một phương thức chính là giá trị của biến đượcreturn.
  • Với các biến kiểu cơ sở, phương thức sẽ trả về giá trị mà biến đó đang chứa, một giá trị kiểu cơ sở.
  • Với các biến kiểu tham chiếu, phương thức cũng sẽ trả về giá trị mà biến đó đang chứa, nhưng giá trị bây giờ là một địa chỉ của một đối tượng mà biến đó đang tham chiếu đến.
III. Kiểu dữ liệu:
Biến địa phương:
  • Biến địa phương được khai báo trong phương thức, hàm dựng, hoặc là khối lệnh.
  • Biến địa phương được tạo khi các phương thức, hàm dựng, hoặc là khối lênh được gọi và biến sẽ bị hủy khi thoát khỏi phương thức, hàm dựng, hoặc là khối lệnh.
  • Không thể sử dụng các Bổ từ truy cập (Access Modifiers) cho các biến địa phương.
  • Biến địa phương chỉ có tầm vực hoạt động (tồn tại) trong phương thức, hàm dựng hoặc các khối lệnh.
  • Biến địa phương được lưu trữ ngầm ở tầng stack.
  • Không có giá trị khởi tạo mặc định cho các biến địa phương nên các biến địa phương phải được gán giá trị trước khi dung (khi khai báo biến địa phương ta phải khởi tạo giá trị ban đầu cho nó).
  • Ex: int a = 10;
  • Giải thích: int a là cấp phát một ô nhớ 4 byte (kiểu int có độ dài 4 byte) trên vùng nhớ stack. Ô nhớ gồm có Value và địa chỉ.

  • A = 10 gán value cho ô nhớ là 10.

Biến thể hiện:

  • Biến thể hiện được cài khai báo bên trong lớp nhưng bên ngoài bất kỳ phương thức, hàm dựng hay khối lệnh nào.
  • Biến thể hiện được lưu trữ trên heap.
  • Biến thể hiện được khởi tạo khi một đối tượng được khởi tạo với từ khóa “new” và bị hủy khi một đối tượng bị hủy.
  • Một biến thể hiện lưu trữ giá trị mà được tham chiếu bởi nhiều phương thức, hàm dựng hay khối lệnh.
  • Biến thể hiện được khai báo bên trong lớp, trước khi hoặc sau khi được dùng.
  • Bổ từ truy cập (Access modifiers) có thể được dùng khi khai báo biến thể hiện.
  • Biến thể hiện khả dụng đối với tất cả các phương thức, hàm dựng hoặc là khối lệnh trong lớp.
  • Biến thể hiện có giá trị mặc định. Nếu là kiểu số thì giá trị mặc định là 0, kiểu Boolean là false, và đối tượng là null. Giá trị có thể khởi tạo khi khai báo hoặc ta thường khởi tạo trong hàm dựng.
  • Biến thể hiện có thể được truy cập trực tiếp bằng cách gọi chúng bên trong lớp.
  • Example:
  • Public class Person{
  • Public String name;
  • }
Biến của lớp (biến tĩnh):
  • Biến của lớp còn được gọi là biến tĩnh, chúng được khai báo với từ khóa static trong lớp, nhưng bên ngoài các phương thức, hàm dựng hoặc các khối lệnh.
  • Chỉ có một bản sao (copy) của mỗi biến tĩnh trong một lớp, bất kể bao nhiêu đối tượng được tạo ra từ nó.
  • Biến tĩnh ít được sử dụng, nó thường được sử dụng để khai báo hằng số (constant). Hằng số là các biến được khai báo là: public / private final static… Biến hằng số không bao giờ thay đổi giá trị của nó khác với giá trị khởi tạo ban đầu.
  • Biến tĩnh được lưu trữ trong bộ nhớ tĩnh (data segment).
  • Biến tĩnh được tạo ra khi chương trình bắt đầu và bị phá hủy khi chương trình kết thúc.
  • Độ khả dụng tương tự như các biến thể hiện. Tuy nhiên, các biến tĩnh thường được khai báo là public để ta có thể dùng chúng ở những lớp khác.
  • Giá trị mặc định là tương tự như các biến thể hiện. Đối với các số giá trị mặc định là 0, đối với boolean thì là false, và với đối tượng là null. Giá trị có thể được chỉ định khi khai báo hoặc trong các hàm dựng. Ngoài ra các giá trị có thể được chỉ định trong khối khởi tạo tĩnh (bạn sẽ gặp sau này).
  • Biến tĩnh có thể được truy cập bằng cách gọi với tên lớp.
  • Cú pháp : Tên_lớp.Tên_biến_tĩnh.
  • Khi khai báo biến tĩnh là public static final, thì bạn nên đặt tên biến (hằng số) là CHỮ_HOA.
  • Chỉ phụ thuộc vào lớp mà không phụ thuộc vào đối tượng.
  • Biến static chỉ khởi tạo một lần khi chương trình bắt đầu thực thi.
  • Truy xuất: Biến static có thể truy xuất trực tiếp bằng tên class mà không cần bất kỳ đối tượng nào.
  • Khi bạn new nhiều đối tượng A thì biến static trong các đối tượng A có giá trị giống nhau. Trong khi biến non static bị trả về 0 mỗi khi new đối tượng mới.
IV. Hàm:

  • Hàm static:
  • Chỉ phụ thuộc vào lớp mà không phụ thuộc vào đối tượng
  • Phương thức static chỉ có thể truy cập vào các thành phần static mà không thể truy cập vào các thành phần không static
  • Phương thức static có thể truy cập trực tiếp bằng tên class mà không cần bất kỳ đối tượng nào.
  • Hàm static được sử dụng khi cần có một lớp tiện ích dùng chung (Util) trong chương trình. Việc tạo một lớp Util rồi trực tiếp tham chiếu đến hàm cần dùng thông qua class làm cho thiết kế tốt hơn so với việc phải khởi tạo một đối tượng rồi dùng hàm tham chiếu từ đối tượng đó. Vì đôi khi việc khởi tạo đối tượng lại tốn chi phí lớn, ảnh hưởng tới performance.
  • Vậy cũng có thể dễ dàng đoán được rằng khi chạy ứng dụng thì thằng static đã được tạo ra mà không cần phải khởi tạo.

You Might Also Like

0 nhận xét

Popular Posts

Like us on Facebook

Flickr Images

Subscribe