Làm thế nào để sử dụng Java HashMap hiệu quả

Khôi Linh, Phạm Thu Trang| 02/12/2018 13:49
Theo dõi ICTVietnam trên

HashMap là một trong những cấu trúc dữ liệu được sử dụng nhiều nhất mà chúng tôi sử dụng trong lập trình Java hàng ngày.

Kết quả hình ảnh cho How to Use Java HashMap Effectively

Trong bài viết này, chúng tôi sẽ giải thích HashMap là gì, cấu trúc của nó, lý do và cách sử dụng HashMap trong Java một cách hiệu quả. Trước khi bạn thưởng thức các kỹ thuật chuyên sâu của HashMap, trước tiên hãy xem một ví dụ.

Bạn có một cửa hàng tạp hóa và có rất nhiều mặt hàng trong cửa hàng của bạn. Tên và giá của các sản phẩm này khác nhau. Đương nhiên sẽ khó nhớ tất cả các sản phẩm này. Nếu bạn chọn cách viết tất cả các sản phẩm này vào trong một cuốn sổ ghi chép, thì bất cứ khi nào bạn bán sản phẩm, bạn sẽ phải xem sổ ghi chép để tìm hiểu về giá của nó, v.v.

Nếu tên của sản phẩm không được viết theo thứ tự bảng chữ cái trong sổ ghi chép, thì bạn sẽ phải mất một khoảng thời gian để tìm ra đúng sản phẩm mình muốn. Trong lớp thuật toán, bạn có thể đã học được rằng điều này có độ phức tạp của O (n). Nhưng nếu tên được sắp xếp theo thứ tự bảng chữ cái, thì việc tìm kiếm nhị phân có thể được sử dụng và sẽ chỉ mất thời gian ít (log n). Bạn biết rằng O (log n) mất ít thời gian hơn O (n).

Mặc dù O (log n) mất ít thời gian hơn đáng kể, nhưng vẫn là mất một khoảng thời gian nhất định. Điều bạn mong muốn tốt nhất chắc chắn là nó sẽ không mất thời gian nào cả. Đối với trường hợp này, có lẽ, bạn có thể ghi nhớ tên và giá của tất cả các sản phẩm và cho biết giá ngay khi người mua nói ra tên sản phẩm.

Kịch bản chính xác này có thể được giải quyết bằng cách sử dụng HashMap. Chúng ta có thể gắn tên của sản phẩm và giá của chúng trong HashMap và HashMap có thể ngay lập tức đưa ra giá trị sản phẩm bằng một từ khóa mà không mất thời gian.

Bây giờ, hãy định nghĩa HashMap là gì. HashMapis là cấu trúc dữ liệu thông qua từ khóa và giá trị, theo thời gian thực, O (1) đều phức tạp cho cả hoạt động giao và nhận.

Hãy xem một ví dụ:

//create a map in java 11

var productPrice = new HashMap();

//or in java 8

Map productPrice = new HashMap<>();

// add value

productPrice.put("Rice", 6.9);

productPrice.put("Flour", 3.9);

productPrice.put("Sugar", 4.9);

productPrice.put("Milk", 3.9);

productPrice.put("Egg", 1.9);

Bây giờ, chúng ta cần truy cập giá bằng từ khóa (tên sản phẩm) từ HashMap. Như vậy, chúng ta có một phương thức có tên get ().

//get value

Double egg = productPrice.get("Egg");

System.out.println("The price of Egg is: " egg);

Bây giờ chúng tôi đã trình bày các khái niệm cơ bản về HashMap, hãy đi sâu về cách chúng tôi có thể sử dụng nó.

In tất cả các từ khóa và giá trị từ HashMap

Có một số cách để in tất cả các từ khóa và giá trị từ hashmap. Hãy xem từng cái một:

1. Chúng tôi muốn in tất cả các từ khóa:

Set keys = productPrice.keySet();

//print all the keys

for (String key : keys) {

System.out.println(key);

}

Hoặc là

keys.forEach(key -> System.out.println(key));

Tôi thích mỗi vòng lặp với biểu thức lambda vì nó ngắn gọn hơn.

2. Chúng tôi muốn in tất cả các giá trị sau:

Collection values = productPrice.values();

values.forEach(value -> System.out.println(value));

3. Chúng tôi muốn in tất cả các từ khóa và giá trị, như được hiển thị bên dưới:

Set<>> entries = productPrice.entrySet();

for (Map.Entry entry : entries) {

System.out.print("key: " entry.getKey());

System.out.println(", Value: " entry.getValue());

}

Hoặc, chúng ta có thể sử dụng biểu thức lambda cho mỗi từ khóa và giá trị, bằng cách ngắn gọn hơn:

productPrice.forEach((key, value) -> {

System.out.print("key: " key);

System.out.println(", Value: " value);

});

Chúng tôi muốn biết liệu đã tồn tại một từ khóa chưa

Thông thường, trước tiên chúng ta muốn kiểm tra xem một từ khóa đã tồn tại hay chưa. Dựa trên thông tin, chúng ta phải đưa ra quyết định. Ví dụ, lập trình động phải được ghi nhớ là một trong những kỹ thuật mà chúng ta sử dụng thường xuyên nhất. Hãy xem một ví dụ.

Chuỗi Fibonacci là một chuỗi phổ biến khi chúng ta sử dụng trong trường hợp chứng minh hàm đệ quy. Công thức của loạt chuỗi này như sau:

f(n)  = f(n-1) f(n-2)

Tuy nhiên, trong ví dụ này, cùng một giá trị có thể được tính toán lặp đi lặp lại, có thể dễ dàng ngăn chặn nếu chúng ta sử dụng kỹ thuật memoization.

import java.math.BigInteger;

import java.util.HashMap;

import java.util.Map;

public class Fibonacci {

    private Map memoizeHashMap = new HashMap<>();

    {

        memoizeHashMap.put(0, BigInteger.ZERO);

        memoizeHashMap.put(1, BigInteger.ONE);

        memoizeHashMap.put(2, BigInteger.ONE);

    }

    private BigInteger fibonacci(int n) {

        if (memoizeHashMap.containsKey(n)) {

            return memoizeHashMap.get(n);

        } else {

            BigInteger result = fibonacci(n - 1).add(fibonacci(n - 2));

            memoizeHashMap.put(n, result);

            return result;

        }

    }

    public static void main(String[] args) {

        Fibonacci fibonacci = new Fibonacci();

        for (int i = 0; i < 100; i ) {

            System.out.println(fibonacci.fibonacci(i));

        }

    }

}

Trong chuỗi mã này, đầu tiên chúng tôi kiểm tra xem liệu một số Fibonacci đã có sẵn trong memorize HashMap hay không. Nếu nó đã có sẵn, chúng ta không cần phải tính toán, chúng ta chỉ có thể lấy nó bằng cách sử dụng phím. Nếu không, chúng ta sẽ sử dụng cách tính toán.

Đoạn mã trên sẽ tạo ra 100 số Fibonacci nhanh hơn.

Lưu ý: phương thức put () thay thế giá trị bằng một từ khóa nếu từ khóa đã tồn tại trong HashMap.

Bây giờ, chúng ta hãy xem xét một số phương pháp có sẵn trong HashMap có thể làm cho cuộc sống của chúng ta dễ dàng.

So sánh: computeIfAbsent () với puIfAbsent ()

Bây giờ, hãy xem lại phương thức fibonacci () từ đoạn mã trước đó một lần nữa. Chúng ta có thể làm cho nó ngắn hơn và gọn hơn nếu chúng ta sử dụng phương thức computeIfAbsent () thay cho hàm containsKey ().

private BigInteger fibonacci(int n) {

return memoizeHashMap.computeIfAbsent(n,

                (key) -> fibonacci(n - 1).add(fibonacci(n - 2)));

}

Phương thức này lấy hai đối số. Một là từ khóa và một là một giao diện chức năng, rồi lại một từ khóa và lần lượt, là một giá trị. Ý tưởng là, nếu từ khóa tồn tại trong Hashmap, nó sẽ trả về giá trị đã có. Nếu không, nó sẽ tính toán giá trị và thêm nó vào Hashmap và sau đó trả về giá trị đúng. Điều này làm cho toàn bộ mã đơn giản hơn và ngắn hơn nhiều.

Tuy nhiên, có một phương thức khác có tên putIfAbsent () có giá trị trực tiếp.

productPrice.putIfAbsent("Fish", 4.5);

Sự khác nhau giữa putIfAbsent () và computeIfAbsent ()

Hàm computeIfAbsent () lấy nhân chức năng liên kết từ khóa và giá, được sử dụng để lấy giá trị nếu từ khóa bị thiếu. Mặt khác, putIfAbsent () lấy giá trị trực tiếp. Vì vậy, trong từng trường hợp, giá trị của từ khóa xuất phát từ một phương thức và nếu phương thức là giá trị đắt nhất, thì hàm computeIfAbsent () sẽ không tính giá trị trừ khi không tìm thấy từ khóa, trong đó putIfAbsent () sẽ tính giá trị trực tiếp.

var theKey = "Fish";       

//Even though if the key is present, the callExpensiveMethodToFindValue will get called

productPriceMap.putIfAbsent(theKey, callExpensiveMethodToFindValue(theKey));

//The callExpensiveMethodToFindValue will never get called if key is already present

productPriceMap.computeIfAbsent(theKey, key -> callExpensiveMethodToFindValue(key));

So sánh: compute () với computeIfPresent ()

Tương tự, HashMap có các phương thức có tên là compute () và computeIfPresent ().

Chúng ta đều biết Oracle đã tặng Java EE cho Quỹ Eclipse, và do đó, nó được đổi tên thành Jakarta EE. Giả sử chúng ta muốn viết chương trình mà chúng ta cung cấp các bài viết về chủ đề này và tính toán tần suất của các từ được chọn để xem tần suất mọi người đề cập đến những từ này.

import java.util.HashMap;

import java.util.Map;

public class WordFrequencyFinder {

    private Map map = new HashMap<>();

    {

        map.put("Java", 0);

        map.put("Jakarta", 0);

        map.put("Eclipse", 0);

    }

    public void read(String text) {

        for (String word : text.split(" ")) {

            if (map.containsKey(word)) {

                Integer value = map.get(word);

                map.put(word, value);

            }

        }

    }

}

Đoạn mã trên quá đơn giản. Đầu tiên chúng tôi kiểm tra xem từ khóa đã tồn tại trên Hashmap hay chưa. Nếu nó tồn tại, chúng ta có thể nhận được giá trị và cập nhật nó với một gia số. Đây là cách viết mã cũ.

Java 8 mang đến một phương pháp tốt hơn được gọi là computeIfPresent (). Hãy viết lại đoạn mã trên bằng phương thức này.

public void read(String text) {

  for (String word : text.split(" ")) {

    map.computeIfPresent(word, (String key, Integer value) -> value);

  }

}

Mã trên đây đẹp hơn nhiều. Phương thức computeIfPresent () lấy từ khóa và một hàm remapping, trong đó, lần lượt tính toán giá trị chỉ khi từ khóa có mặt. Vì vậy, hàm remapping chỉ được sử dụng nếu từ khóa có trong hashmap, nếu không thì sẽ không có.

Ngoài ra, chúng ta có một phương thức khác có tên là compute (), có các đối số tương tự như computeIfPresent (). Tuy nhiên, nó tính toán chức năng remapping và nó không quan trọng nếu từ khóa có phải là hiện tại hay không. Dựa trên đầu ra của hàm remapping, nó thêm giá trị vào Hashmap.

getOrDefault ()

Trong một số trường hợp, từ khóa chúng ta xem xét có một hashmap không tồn tại. Tuy nhiên, chúng ta vẫn muốn có một giá trị và chúng ta không muốn thay đổi Hashmap. Trong những trường hợp như vậy, chúng ta có thể sử dụng phương thức methodgetOrDefault () rất hữu ích.

productPriceMap.getOrDefault("Fish", 29.4);

Kết luận

HashMap giúp việc truy cập dữ liệu dễ dàng hơn và nhanh hơn. Và Java 8 đã giới thiệu các biểu thức lambda làm cho cuộc sống của chúng ta trở nên đơn giản hơn nhiều. Chúng ta có thể sử dụng biểu thức lambda để làm cho mã của chúng ta ngắn gọn hơn.

Nổi bật Tạp chí Thông tin & Truyền thông
Đừng bỏ lỡ
Làm thế nào để sử dụng Java HashMap hiệu quả
POWERED BY ONECMS - A PRODUCT OF NEKO