Cách xử lý khi nho java

Là một kỹ sư phụ trợ, chúng tôi phải đối mặt với các tình huống xử lý dữ liệu không đồng bộ. Hôm nay chúng ta hãy xem nó được thực hiện như thế nào trong java và nhiều cách khác nhau để làm điều đó.

Thread

Thành phần rất cơ bản nhưng rất mạnh mẽ của Java concurrency là Thread. Luồng của Java thực sự được liên kết với Luồng của hệ điều hành. Cách rất cơ bản để tạo Thread là bằng cách mở rộng nó và ghi đè lên run :

Bắt đầu chuỗi gây ra run() được gọi là.
Bạn có thể yêu cầu; có, Thread có rất nhiều phương thức khác có thể được ghi đè:

  • Trong hầu hết các trường hợp, chúng tôi không muốn ghi đè các phương thức khác của luồng.
  • Khi chúng tôi mở rộng Thread lớp, lớp mở rộng mất khả năng mở rộng hơn nữa vì Java không hỗ trợ nhiều lớp kế thừa.
  • Mỗi luồng có đối tượng riêng khi chúng ta mở rộng nó, và nó không tốt cho sức khỏe bộ nhớ khi có rất nhiều Objects of the extended Thread tạo.

Java giải quyết những vấn đề này với giao diện Runnable. Trong thực tế, Thread có một phương thức được nạp chồng có Runnable.

Runnable

Runnable là một giao diện chỉ có một phương thức: run(). Có, Runnable là một giao diện chức năng và phiên bản của nó có thể được tạo bằng hàm lambda. Tuy nhiên, đó là một cách dễ dàng để làm điều này; đối với những thứ phức tạp, chúng tôi có thể muốn triển khai nó. Xem sự khác biệt ở đây. Đó là tất cả về yêu cầu:

Mặc dù Runnable có một run(), nó không phải là một Luồng mà chỉ là một lớp Java cho đến khi nó được kiểm soát bởi (được chuyển tới) Luồng. Sự bắt đầu của chuỗi làm cho đối tượng có thể chạy được run() được gọi là.

Đúng vậy, Java đã giải quyết được nó trong phiên bản 1.5 và nó có thể gọi là .

Callable<V>

Callable là một giao diện chung. Tại sao? Chạy lệnh này của giá trị trả về như lệnh chung chạy lệnh này. Callable là một giao diện quá chức năng và call() là phương thức duy nhất, một phương thức không đối số ném Exception và trả về giá trị lệnh này chạy chung.

Việc triển khai Callable rất giống với Runnable:

Xem tại đây, cuộc gọi xử lý dữ liệu và trả về một giá trị có thể được thu thập sau khi thực thi. Nhưng có một sự khác biệt lớn trong việc gọi nó? Chúng tôi sử dụng ExecutorService để gọi và Future để nắm giữ kết quả. Hãy nói về lý do tại sao.

Như bạn có thể thấy, không có hành vi nào được kiểm soát khi tạo và chạy các Luồng (Runnable hoặc Callable quá). Chúng ta có thể muốn kiểm soát số lượng luồng đang chạy tại một thời điểm vì mỗi luồng được liên kết với các luồng của hệ điều hành. Số lượng Chủ đề chúng tôi chạy phải ít hơn số lõi CPU có sẵn. Tất cả cùng nhau, Java giải quyết nó bằng cách ExecutorService giao diện.
Trên đây là bài viết thông tin về Lập trình Async trong Java: Phần I.
Vui lòng liên hệ với chúng tôi qua phần bình luận, trong trường hợp bạn có bất kỳ câu hỏi nào liên quan đến hướng dẫn mà chúng tôi đã chia sẻ ở trên.

Các kiểu dữ liệu chuỗi :

Kiểu String :

String là một lớp đối tượng được Java dựng sẵn, dùng để khởi tạo và xử lý các chuỗi văn bản.

Có 2 cách khởi tạo chuỗi:

//cách 1
String name = "Hoang";
//cách 2
String name = new String("Hoang");
  • Cách 1: khởi tạo String ở vùng nhớ StringPool. StringPool là một vùng trong bộ nhớ Heap được Java quy hoạch riêng. Đặc điểm của vùng nhớ này là nhiều biến tham chiếu khác nhau nhưng nếu có cùng giá trị thì nghĩa là cùng tham chiếu tới một đối tượng chuỗi duy nhất, nhờ đó tiết kiệm không gian lưu trữ.
  • Cách 2: khởi tạo String ở vùng nhớ Heap. Mỗi đối tượng được khởi tạo đều được cấp bộ nhớ riêng, nghĩa là dù giá trị giống nhau nhưng vẫn là các đối tượng riêng biệt. Cách khởi tạo 1 thường được dùng vì gõ nhanh, tiết kiệm bộ nhớ, nhưng khởi tạo chậm. Còn cách 2 khởi tạo nhanh hơn, và là cách khởi tạo bắt buộc trong một số trường hợp.

Một số phương thức phổ biến của String :

  • length() : trả về độ dài chuỗi.
  • charAt(i) : trả về ký tự char ở vị trí i của chuỗi.
  • equals("Hoa") : trả về kết quả boolean so sánh xem có giống với chuỗi “Hoa” ko.
  • compareTo("Hoa") : trả về kết quả int là hiệu của 2 ký tự khác nhau đầu tiên ở 2 chuỗi (âm, dương, hoặc bằng 0). Nếu chuỗi sau nằm trong chuỗi trước thì kết quả trả về là số lượng ký tự dôi dư. Phương thức này chủ yếu dùng để sắp xếp các chuỗi theo trật tự từ điển.
  • indexOf("Hoa") : trả về vị trí đầu tiên bắt gặp chuỗi nhỏ trong chuỗi mẹ. Nếu ko tìm thấy thì trả về -1.
  • indexOf("Hoa", 2) : tương tự phương thức trên, bổ sung thêm tham số bắt đầu duyệt chuỗi mẹ từ vị trí nào.
  • lastIndexOf("a") : ngược lại với indexOf(), trả về vị trí cuối cùng gặp được.
  • lastIndexOf("a", 2) : tương tự indexOf().
  • startWith("Ho") : trả về kết quả boolean có bắt đầu bằng chuỗi nhỏ ko.
  • endWith("ng") : ngược lại với startWith().
  • contains("oa") : trả về kết quả boolean có chứa chuỗi nhỏ hay ko.
  • isEmpty() : trả về kết quả boolean mảng có rỗng hay ko.
  • toUpperCase() : trả về chuỗi sau khi chuyển hết ký tự thành hoa hết.
  • toLowerCase() : trả về chuỗi sau khi chuyển hết ký tự thành thường hết.
  • substring(3) : trả về chuỗi con cắt từ vị trí 3 trở đi.
  • substring(3, 5) : trả về chuỗi con cắt từ vị trí 3 đến vị trí 4 (lưu ý nó ko cắt cả vị trí 5 đâu).
  • split(" ") : trả về một mảng các chuỗi con được phân cắt bởi chuỗi ” ” (ví dụ đây là dấu cách).

So sánh chuỗi :

  • str1.equal(str2) : trả về kết quả boolean.
  • str1.compareTo(str2) : trả về kết quả int.
  • str1 == str2 : cách này ko nên dùng, chỉ có thể áp dụng nếu 2 biến String cùng khai báo trong StringPool, vì khi ấy 2 tham chiếu cùng tham chiếu tới 1 đối tượng, do đó phép so sánh bằng này là kiểm tra 2 tham chiếu có cùng chiếu tới 1 đối tượng ko, ko thể áp dụng giữa 1 String ở StringPool với 1 String ở Heap, hay giữa 2 String được khai báo ở Heap.

Kiểu StringBuilder, StringBuffer :

Cũng là các lớp đối tượng do Java dựng sẵn giúp xử lý chuỗi, nhưng khác với String ở chỗ: giá trị thuộc tính của đối tượng String ko bị thay đổi mỗi khi gọi phương thức của chính nó, còn giá trị của đối tượng StringBuilder hoặc StringBuffer bị thay đổi mỗi khi gọi phương thức.

Cách khởi tạo:

StringBuilder name = new StringBuilder("Hoang");
StringBuffer name = new StringBuffer("Hoang");

Sự khác nhau giữa StringBuilder và StringBuffer: cơ bản thì 2 kiểu dữ liệu giống nhau về các phương thức xử lý chuỗi, nhưng khác nhau về mục đích sử dụng. StringBuffer được sử dụng trong các bài toán liên quan đến đa luồng, đồng bộ dữ liệu. Còn trong các bài toán thông thường thì StringBuilder được dùng thường xuyên hơn.

Các phương thức phổ biến của StringBuilder :

  • append("Hau") : nối chuỗi sau vào sau chuỗi trước.
  • deleteCharAt(3) : xóa ký tự ở vị trí 3.
  • delete(3, 5) : xóa một chuỗi các ký tự từ vị trí 3 đến 4.
  • insert(3, "f") : chèn thêm chuỗi nhỏ vào vị trí 3 của chuỗi mẹ.
  • reverse() : đảo ngược chuỗi.
  • toString() : trả về đối tượng String (StringBuilder có gọi được một số phương thức từ String, nhưng tốt nhất tạo một đối tượng String riêng).

Một số kiểu dữ liệu mảng :

Kiểu Array :

Array là một đối tượng biểu diễn một tập hợp hữu hạn các phần tử liền nhau trong bộ nhớ và có chung kiểu dữ liệu. Mảng kiểu Array phải được xác định trước kích thước và các ô nhớ nối tiếp nhau trong vùng nhớ.

Khai báo và khởi tạo:

int mang1[] = new int[3];
double[] mang2 = new double[3];
String mang3[] = new String[] {"An", "Binh", "Hoa"};
int mang4[][] = new int[3][4];

Khi bạn viết “int mang[]”, bạn mới chỉ khai báo mảng, mảng chỉ thực sự được khởi tạo bởi từ khóa “new”. Dòng 1 và 2 là 2 cách viết ký hiệu mảng, bạn dùng cách nào cũng được. Khi chúng ta khởi tạo mảng, mặc định giá trị các phần tử đều bằng 0 hết với kiểu int, bằng 0.0 với kiểu double, bằng null với các kiểu dữ liệu có cấu trúc ví dụ như String. Dòng 3 là cách truyền luôn giá trị cho các phần tử, mảng sẽ tự động xác định kích thước. Dòng 4 là cách khởi tạo mảng 2 chiều.

Cách duyệt mảng :

Dùng for:

for(int i=0; i<mang.length; i++) {
  System.out.println(mang[i]);
}

Dùng for…each:

String mang[] = new String[] {"An", "Binh", "Hoa"};
for(String phanTu : mang) {
  System.out.println(phanTu);
}

Vòng for…each khác for ở chỗ:

  • Biến “phanTu” trả về giá trị của phần tử đó luôn, ko như for phải dùng chỉ số để trả về giá trị phần tử.
  • Chính vì for…each ko dùng chỉ số, nên nó chỉ có khả năng duyệt từ đầu đến cuối, ko tuỳ ý nhảy vị trí được, thích hợp cho nhiệm vụ tìm kiếm, chỉ phải duyệt một lèo đến hết mảng.

Cách duyệt mảng 2 chiều :

for(int i=0; i<mang.length; i++) {
  for(int j=0; j<mang[0].length; j++) {
    //bla bla 
  }
}

Ý tưởng về cách biểu diễn mảng trong Java có khác với C. Trong C, mảng 2 chiều thực chất là mảng 1 chiều mà các hàng nối tiếp nhau, còn trong Java thì coi mọi mảng đều là mảng 1 chiều, mỗi phần tử là một mảng một chiều khác. Do đó “mang.length” trả về số hàng của mảng (lưu ý “length” ở đây là thuộc tính, không phải phương thức “length()” như chuỗi), còn “mang[0].length” trả về số cột của mảng.

Một số phương thức xử lý mảng hữu hiệu trong thư viện Arrays :

Gói thư viện java.util.Arrays chứa một số phương thức xử lý mảng, đem lại tiện ích cho người lập trình thay vì duyệt và xử lý mảng thủ công, ví dụ như:

Arrays.fill(mang, 1) : khởi tạo đồng loạt giá trị “1” cho toàn bộ phần tử của mảng “mang”.

Arrays.toString(mang) : biểu diễn các giá trị của các phần tử mảng thành một chuỗi. Lưu ý chỉ sử dụng ngay được phương thức này với các kiểu dữ liệu nguyên thuỷ, không dùng với các kiểu dữ liệu có cấu trúc (nếu không nó chỉ trả về địa chỉ của các đối tượng). Để áp dụng được với các kiểu dữ liệu cấu trúc, bạn phải ghi đè phương thức toString() trong lớp đối tượng đó.

Arrays.copyOfRange(mang, 1, 4) : trả về một mảng sao chép từ phần tử thứ 1 tới phần tử thứ 4 của mảng “mang”.

Arrays.sort(mang) : sắp xếp các phần tử trong mảng theo thứ tự tăng dần. Chú ý chỉ áp dụng với các kiểu dữ liệu nguyên thuỷ. Đối với các kiểu dữ liệu cấu trúc, bạn phải xây dựng bộ hành vi sắp xếp cho lớp đối tượng. Có 2 cách xây dựng bộ hành vi là Comparable và Comparator (cá nhân mình ưu tiên dùng Comparator).

public class HocSinh implements Comparable<HocSinh> {
  private double diem;

  @Override
  public int compareTo(HocSinh o) {
    double hieu = this.diem - o.diem;
    if(hieu > 0) {
      return 1;
    } else if(hieu < 0) {
      return -1;
    } else {
      return 0;
    }
  }
}
public class QuanLy {
  Comparator<HocSinh> sapXepDiemHSTangDan = new Comparator<HocSinh>() {
    public int compare(HocSinh o1, HocSinh o2) {
      double hieu = o1.diem - o2.diem;
      if(hieu > 0) {
        return 1;
      } else if(hieu < 0) {
        return -1;
      } else {
        return 0;
      }
    }
  }
}

Nếu dùng Comparator thì phương thức sort() phải truyền thêm tham số thứ 2 chính là tên Comparator mà bạn vừa định nghĩa.

Arrays.sort(mang, sapXepDiemHSTangDan);

Sự khác nhau giữa Comparable và Comparator:

  • Comparable được viết ở lớp cấp dưới, còn Comparator thường được viết ở lớp cấp quản lý.
  • Comparable thực chất là một interface, trong đó ghi đè phương thức compareTo(…), nhằm thực hiện so sánh thuộc tính của chính đối tượng này với đối tượng khác. Còn Comparator thực chất là một thuộc tính nằm trong lớp quản lý, thuộc tính này là một đối tượng trong đó có ghi đè phương thức compare(…, …), nhằm thực hiện so sánh thuộc tính của đối tượng 1 với đối tượng 2.

Kiểu ArrayList :

ArrayList là một lớp đối tượng được Java dựng sẵn, khắc phục mặt hạn chế của kiểu Array, đó là biểu diễn được một danh sách mà số lượng phần tử thay đổi được, có thể tuỳ ý thêm, sửa, xoá phần tử khỏi danh sách (lưu ý ArrayList chỉ biểu diễn mảng 1 chiều).

Khai báo:

ArrayList<HocSinh> hocSinh = new ArrayList<HocSinh>();

Các phương thức chính của ArrayList:

  • size() : trả về kích thước của danh sách.
  • add(hs) : thêm nối tiếp phần tử “hs” vào danh sách (“hs” cũng phải thuộc kiểu dữ liệu “HocSinh”).
  • get(i) : lấy giá trị phần tử thứ i.
  • remove(i) : xoá phần tử thứ i.
  • clear() : xoá toàn bộ phần tử khỏi danh sách.
  • isEmpty() : trả về boolean xem danh sách có rỗng ko.
  • indexOf(hs) : trả về vị trí đầu tiên của phần tử trong danh sách.
  • contains(hs) : trả về boolean xem có phần tử này trong danh sách ko.
  • sort(tieuChiSapXep) : tương tự Array, bạn cũng phải xây dựng Comparator để sắp xếp đối với các đối tượng thuộc kiểu dữ liệu cấu trúc.
  • toArray() : trả về một mảng Array.
  • toString() : trả về một chuỗi gồm giá trị của các phần tử. Đối với danh sách có kiểu dữ liệu cấu trúc, bạn phải ghi đè lại phương thức toString() để chỉ dẫn cách nó hiển thị thông tin.

Lưu ý, vì danh sách kiểu ArrayList có khả năng thay đổi kích thước, nên khi bạn đem phương thức size() làm điều kiện thoát vòng lặp for, nó vẫn thay đổi giá trị mỗi lần gọi. Điều này dẫn đến tình trạng khi bạn thực hiện xoá phần tử, ví dụ bạn xoá phần tử thứ 5, phần tử tiếp theo sẽ thụt vào thế chỗ phần tử mất đi, do đó nếu biến i tiếp tục tăng lên 6, bạn sẽ bỏ lỡ mất 1 phần tử. Vì thế bạn cần phải thêm i– để tránh bị nhảy cóc.

for(int i=0; i<danhSach.size(); i++) {
  if(danhSach.get(i)==num) {
    danhSach.remove(i);
    i--;
  }
}

ArrayList chỉ có thể lưu trữ dữ liệu kiểu đối tượng, nên nếu bạn muốn lưu số, bạn phải dùng lớp đối tượng của kiểu dữ liệu đó. Kiểu int có lớp Integer, kiểu double có lớp Double.

ArrayList<Integer> listInt = new ArrayList<Integer>();
listInt.add(1);
listInt.add(2);