簡體   English   中英

JAVA:查找存儲在hashMap中的對象的最佳性能方法

[英]JAVA : Best performance-wise method to find an object stored in hashMap

我有一堆對象存儲在hashMap<Long,Person>我需要找到具有特定屬性的person對象而不知道它的ID。

例如人類:

public person{
    long id;
    String firstName;
    String lastName;
    String userName;
    String password;
    String address;
    ..
   (around 7-10 attributes in total)
    }

假設我想找到username =“mike”的對象。 是否有任何方法可以找到它而不實際迭代整個哈希映射,如下所示:

for (Map.Entry<Long,Person> entry : map.entrySet()) {
        if(entry.getValue().getUserName().equalsIgnoreCase("mike"));

我在這里找到的答案很古老。

如果你想要速度並且總是在尋找一個特定屬性,那么最好的辦法是創建另一個用該屬性鍵入的“緩存”哈希映射。

對於不到一百萬個條目,占用的內存將是無關緊要的,並且哈希映射查找將比任何其他解決方案快得多。

或者,您可以將所有搜索屬性放入單個映射(即名稱和ID)。 如果您關心碰撞,請使用獨特的內容為密鑰添加前綴。 就像是:

String ID_PREFIX = "^!^ID^!^";
String USERNAME_PREFIX = "^!^USERNAME^!^";
String FIRSTNAME_PREFIX = "^!^FIRSTNAME^!^";
Map<String,Person> personMap = new HashMap<String,Person>();

//add a person
void addPersonToMap(Person person)
{
    personMap.put(ID_PREFIX+person.id, person);
    personMap.put(USERNAME_PREFIX+person.username, person);
    personMap.put(FIRSTNAME_PREFIX+person.firstname, person);
}

//search person
Person findPersonByID(long id)
{
    return personMap.get(ID_PREFIX+id);
}

Person findPersonByUsername(String username)
{
    return personMap.get(USERNAME_PREFIX+username);
}

//or a more generic version:
//Person foundPerson = findPersonByAttribute(FIRSTNAME_PREFIX, "mike");
Person findPersonByAttribute(String attr, String attr_value)
{
    return personMap.get(attr+attr_value);
}

以上假設每個屬性在所有人中都是唯一的。 這可能適用於ID和用戶名,但問題是指定firstname = mike,這不太可能是唯一的。

在這種情況下,你想用列表進行抽象,所以它更像是這樣:

Map<String,List<Person>> personMap = new HashMap<String,List<Person>>();

//add a person
void addPersonToMap(Person person)
{
    insertPersonIntoMap(ID_PREFIX+person.id, person);
    insertPersonIntoMap(USERNAME_PREFIX+person.username, person);
    insertPersonIntoMap(FIRSTNAME_PREFIX+person.firstname, person);
}

//note that List contains no duplicates, so can be called multiple times for the same person.
void insertPersonIntoMap(String key, Person person)
{
    List<Person> personsList = personMap.get(key);
    if(personsList==null)
        personsList = new ArrayList<Person>();
    personsList.add(person);
    personMap.put(key,personsList);
}

//we know id is unique, so we can just get the only person in the list
Person findPersonByID(long id)
{
    List<Person> personList = personMap.get(ID_PREFIX+id);
    if(personList!=null)
        return personList.get(0);

    return null;
}

//get list of persons with firstname
List<Person> findPersonsByFirstName(String firstname)
{
    return personMap.get(FIRSTNAME_PREFIX+firstname);
} 

在那一點上,你真的進入了一個抓包設計,但如果你不期待數百萬條款,它仍然非常有效。

我能想到的最佳性能方法是使用另一個HashMap ,其中鍵是要搜索的屬性,值是對象列表。

對於您的示例,這將是HashMap<String, List<Person>> ,其中鍵是用戶名。 缺點是您必須維護兩張地圖。

注意:我使用List<Person>作為值,因為我們無法保證username在所有用戶中都是唯一的。 這同樣適用於任何其他領域。

例如,要將Person添加到此新地圖,您可以執行以下操作:

Map<String, List<Person>> peopleByUsername = new HashMap<>();

// ...

Person p = ...;

peopleByUsername.computeIfAbsent(
        p.getUsername(), 
        k -> new ArrayList<>())
    .add(p);

然后,返回所有用戶名為joesmith

List<Person> matching = peopleByUsername.get("joesmith");

從易失性地圖中獲取一個或幾個條目
如果您正在操作的地圖可以經常更改,並且您只想獲得一些條目,那么迭代地圖的條目就可以了,因為您需要空間和時間來構建其他結構或對數據進行排序。

從易失性地圖中獲取許多條目
如果您需要從該映射中獲取許多條目,則可以通過首先對條目進行排序(例如,構建列表並對其進行排序)然后使用二進制搜索來獲得更好的性能。 或者,您可以構建一個中間映射,使用您需要搜索的屬性作為其鍵。

但請注意,這兩種方法至少需要時間,因此當您查找許多條目時,這只會產生更好的性能。

從“持久性”地圖中多次獲取條目
如果您的地圖及其估值值沒有變化(或者不經常變化),您可以維護地圖屬性 - >人。 這意味着初始設置和更新附加映射(除非您的數據不會更改)以及一些內存開銷需要一些努力,但以后會大大加快查找速度。 與查找頻率相比,當您執行非常少的“寫入”時,這是一種有價值的方法,如果您可以節省內存開銷(取決於這些映射的大小以及您需要多少內存)。

考慮每個備用密鑰一個哈希映射。 這將具有“高”設置成本,但將導致通過備用密鑰快速檢索。

  1. 使用Long鍵值設置hashmap。
  2. 運行hashmap Person對象並創建第二個hashmap( HashMap<String, Person> ),其中username是密鑰。

也許,同時填充兩個哈希圖。

在您的情況下,您將得到類似HashMap<Long, Person> idKeyedMapHashMap<String, Person> usernameKeyedMap

如果將地圖定義為Map<Object, Person>也可以將所有鍵值放在同一個映射中。 然后,當您添加(id,person)對時,您還需要添加(用戶名,人物)對。 警告,這不是一個偉大的技術。

解決問題的最佳方法是什么?

正如您在答案和評論中看到的,有很多方法可以解決這個問題。

如何使用Map(也許是如何創建它)。 如果Map是從select語句構建的,並且表中的列具有long id值,我們可能會認為我們應該使用HashMap<Long, Person>

查看問題的另一種方法是考慮用戶名也應該是唯一的(即,任何兩個人都不應該共享相同的用戶名)。 因此,將地圖創建為HashMap<String, Person> 使用username作為鍵,Person對象作為值。

使用后者:

Map<String, Person> users = new HashMap<>();
users = retrieveUsersFromDatabase(); // perform db select and build map
String username = "mike";
users.get(username).

這將是在包含Person對象作為其值的Map中檢索要查找的對象的最快方法。

您可以使用以下命令將Hashmap簡單地轉換為List:

List list = new ArrayList(map.values());

現在,您可以輕松地遍歷列表對象。 這樣,您可以在Person類的任何屬性上搜索Hashmap值,而不僅僅是限制firstname。

唯一的缺點是你最終會創建一個列表對象。 但是使用stream api可以進一步改進代碼,將Hashmap轉換為列表並在單個操作中進行迭代,從而節省空間並提高並行流的性能。

可以通過設計和使用適當的Comparator類來完成對值對象的排序和查找。

比較器類:根據特定屬性設計比較器可以按如下方式進行:

class UserComparator implements Comparator<Person>{

    @Override
    public int compare(Person p1, Person p2) {

        return  p1.userName.compareTo(p2.userName);

    }

}

用法:上面設計的比較器可以使用如下:

    HashMap<Long, Person> personMap = new HashMap<Long, Person>();

                        .
                        .
                        .

    ArrayList<Person> pAL = new ArrayList<Person>(personMap.values()); //create list of values

    Collections.sort(pAL,new UserComparator()); // sort the list using comparator

    Person p = new Person(); // create a dummy object

    p.userName="mike"; // Only set the username

    int i= Collections.binarySearch(pAL,p,new UserComparator()); // search the list using comparator

    if(i>=0){

        Person p1 = pAL.get(Collections.binarySearch(pAL,p,new UserComparator())); //Obtain object if username is present

    }else{

        System.out.println("Insertion point: "+ i); // Returns a negative value if username is not present

    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM