Najbardziej efektywny sposób sprawdzania, czy ArrayList zawiera obiekt w Javie

Mam Arraylistę obiektów w Javie. Obiekty mają cztery pola, z których dwa uznałbym za równe innemu obiektowi. Szukam najskuteczniejszego sposobu, biorąc pod uwagę te dwa pola, aby sprawdzić, czy tablica zawiera ten obiekt.

Kluczem Jest to, że te klasy są generowane na podstawie obiektów XSD, więc nie mogę modyfikować samych klas, aby nadpisać .equals.

Czy Jest jakiś lepszy sposób niż tylko przeszukiwanie i ręczne porównywanie dwóch pól dla każdy przedmiot, a następnie złamanie po znalezieniu? To wygląda na bałagan, szukając lepszego sposobu.

Edit: ArrayList pochodzi od odpowiedzi SOAP, która nie jest zamieniana na obiekty.

Author: Svante, 2009-02-18

12 answers

To zależy od tego, jak wydajny trzeba rzeczy być. Po prostu iteracja nad listą w poszukiwaniu elementu spełniającego określony warunek to O( n), ale tak samo jest z ArrayList.Zawiera, jeśli można zaimplementować metodę Equals. Jeśli nie robisz tego w pętlach lub pętlach wewnętrznych, to podejście jest prawdopodobnie w porządku.

Jeśli naprawdę potrzebujesz bardzo wydajnych prędkości wyszukiwania za wszelką cenę, musisz zrobić dwie rzeczy:

  1. obejść fakt, że Klasa jest generowany: napisz Klasa adaptera, która może zawijać wygenerowaną klasę i which implementation equals () based na tych dwóch polach (zakładając, że są publiczne). Nie zapomnij również zaimplementuj hashCode() (*)
  2. owiń każdy obiekt tym adapterem i włóż to do HashSet. HashSet.contains () ma stałą czas dostępu, tzn. O(1) zamiast O (n).

Oczywiście, budowa tego HashSet nadal ma koszt O (n). Zyskasz tylko wtedy, gdy koszt budowy HashSet jest nieistotny w porównaniu do całkowitego kosztu wszystkich kontroli contains (), które musisz wykonać. Taka jest próba zbudowania listy bez duplikatów.


* () implementacja hashCode () najlepiej wykonać za pomocą operatora xor ' ING ( ^ ) hashCodes tych samych pól, których używasz do implementacji równości (ale pomnóż przez 31, aby zmniejszyć prawdopodobieństwo, że XOR da 0)
 98
Author: Wim Coenen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:27

Możesz użyć komparatora z wbudowanymi metodami Javy do sortowania i wyszukiwania binarnego. Załóżmy, że masz klasę taką jak ta, gdzie A i b są polami, których chcesz użyć do sortowania:

class Thing { String a, b, c, d; }

Zdefiniowałbyś swój komparator:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

Następnie posortuj listę:

Collections.sort(list, comparator);

I na koniec wykonaj wyszukiwanie binarne:

int i = Collections.binarySearch(list, thingToFind, comparator);
 36
Author: Fabian Steeg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 22:58:03

Biorąc pod uwagę twoje ograniczenia, utknąłeś z wyszukiwaniem brute force (lub tworzeniem indeksu, jeśli wyszukiwanie będzie powtarzane). Czy możesz rozwinąć jakiś sposób generowania ArrayList -- Być może jest tam jakaś przestrzeń do poruszania się.

Jeśli szukasz ładniejszego kodu, rozważ użycie klas Apache Commons Collections, w szczególności CollectionUtils.find () , dla gotowego cukru składniowego:

ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...

Object found = CollectionUtils.find(haystack, new Predicate() {
   public boolean evaluate(Object input) {
      return needleField1.equals(input.field1) && 
             needleField2.equals(input.field2);
   }
});
 10
Author: Michael Brewer-Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 22:41:22

Jeśli lista jest posortowana , możesz użyć wyszukiwania binarnego. Jeśli nie, to nie ma lepszego sposobu.

Jeśli robisz to często, prawie na pewno warto byłoby posortować listę za pierwszym razem. Ponieważ nie możesz modyfikować klas, musisz użyć Comparator do sortowania i wyszukiwania.

 6
Author: Michael Myers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 00:15:30

Nawet jeśli metoda equals byłaby porównując te dwa pola, to logicznie, byłby to ten sam kod, co robi się to ręcznie. OK, to może być "bałagan", ale to nadal prawidłowa odpowiedź

 4
Author: oxbow_lakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 22:22:23

Jeśli jesteś użytkownikiem mojego ForEach DSL , można to zrobić za pomocą zapytania Detect.

Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query) 
    each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();
 4
Author: akuhn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-01 19:19:48

czy jest lepszy sposób niż tylko przeplatanie i ręczne porównywanie dwóch pól dla każdego obiektu, a następnie łamanie po znalezieniu? To wygląda na bałagan, szukając lepszego sposobu.

Jeśli twoim problemem jest konserwacja, możesz zrobić to, co Fabian Steeg zasugerował (to właśnie bym zrobił), chociaż prawdopodobnie nie jest to "najbardziej wydajne" (ponieważ najpierw musisz posortować tablicę, a następnie wykonać wyszukiwanie binarne), ale z pewnością najczystsze i lepszą opcją.

Jeśli naprawdę zależy ci na wydajności, możesz utworzyć niestandardową implementację listy, która używa pola w Twoim obiekcie jako hash i używa Hashmapy jako pamięci masowej. Ale pewnie to by było za dużo.

Następnie musisz zmienić miejsce, w którym wypełniasz dane z ArrayList na YourCustomList.

Jak:

 List list = new ArrayList();

 fillFromSoap( list );

Do:

 List list = new MyCustomSpecialList();

 fillFromSoap( list );

Implementacja byłaby podobna do następującej:

class MyCustomSpecialList extends AbstractList  { 
    private Map<Integer, YourObject> internalMap;

    public boolean add( YourObject o ) { 
         internalMap.put( o.getThatFieldYouKnow(), o );
    }

    public boolean contains( YourObject o ) { 
        return internalMap.containsKey( o.getThatFieldYouKnow() );
    }

}

Prawie podobnie jak HashSet, problem polega na tym, że HashSet opiera się na dobrej implementacji metody hashCode, której prawdopodobnie nie masz. Zamiast tego używasz jako hash "to pole, które znasz" , które sprawia, że jeden obiekt jest równy drugiemu.

Oczywiście implementacja listy od zera o wiele trudniejsza niż mój fragment powyżej, dlatego mówię, że sugestia Fabian Steeg byłaby lepsza i łatwiejsza do wdrożenia ( chociaż coś takiego byłoby bardziej efektywne )

Powiedz nam, co zrobiłeś na końcu.
 2
Author: OscarRyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:30:58

Może lista nie jest tym, czego potrzebujesz.

Może TreeSet byłby lepszym pojemnikiem. Otrzymujesz o (log N) wstawianie i pobieranie oraz uporządkowaną iterację (ale nie pozwoli na duplikaty).

LinkedHashMap może być jeszcze lepszy dla Twojego przypadku użycia, sprawdź też to.

 2
Author: Ben Hardy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 02:01:17

Budowanie HashMap tych obiektów na podstawie wartości pola jako klucza może być opłacalne z punktu widzenia wydajności, np.]}

 1
Author: Rocket Surgeon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 22:30:19

Jeśli musisz przeszukiwać wiele razy na tej samej liście, zbudowanie indeksu może się opłacić.

Powtórz raz i zbuduj Hashmapę z wartością równą, której szukasz jako klucz i odpowiednim węzłem jako wartością. Jeśli potrzebujesz wszystkich zamiast kogokolwiek o podanej wartości, niech mapa ma typ wartości listy i zbuduje całą listę w początkowej iteracji.

Należy pamiętać, że należy zmierzyć przed zrobieniem tego, ponieważ narzut budynku indeksu może przyciemnienie tylko przemieszcza się aż do znalezienia oczekiwanego węzła.

 1
Author: Thorbjørn Ravn Andersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 22:35:44

Istnieją trzy podstawowe opcje:

1) Jeśli wydajność pobierania jest najważniejsza i jest to praktyczne, użyj postaci tabeli hash zbudowanej raz(i zmienionej jako / jeśli Lista się zmieni).

2) Jeśli lista jest wygodnie posortowana lub jest to praktyczne, aby ją sortować i o (log n) pobieranie jest wystarczające, Sortuj I Szukaj.

3) Jeśli O(n) pobieranie jest wystarczająco szybkie lub jeśli manipulowanie/utrzymywanie struktury danych lub alternatywy jest niepraktyczne, iteratuj listę.

Zanim napiszesz kod bardziej złożony niż prosta iteracja nad listą, warto przemyśleć kilka pytań.

  • Dlaczego potrzebne jest coś innego? (Czas) wydajność? Elegancja? Konserwacja? Ponowne użycie? Wszystkie z tych powodów są w porządku, osobno lub razem, ale mają wpływ na rozwiązanie.

  • Jak bardzo masz kontrolę nad daną strukturą danych? Czy możesz wpłynąć na to, jak jest zbudowany? Udało się później?

  • Co to jest cykl życia struktury danych (i obiektów bazowych)? Czy jest zbudowany na raz i nigdy się nie zmienił, czy jest bardzo dynamiczny? Czy Twój kod może monitorować (a nawet zmieniać) jego cykl życia?

  • Czy istnieją inne ważne ograniczenia, takie jak pamięć footprintu? Czy informacje o duplikatach mają znaczenie? Itd.

 1
Author: Jeremy Rishel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-17 23:32:07

Powiedziałbym, że najprostszym rozwiązaniem byłoby zawinięcie obiektu i delegowanie wywołania contains do kolekcji zawiniętej klasy. Jest to podobne do komparatora, ale nie zmusza cię do sortowania wynikowej kolekcji, możesz po prostu użyć ArrayList.zawiera().

public class Widget {
        private String name;
        private String desc;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }



    public abstract class EqualsHashcodeEnforcer<T> {

        protected T wrapped;

        public T getWrappedObject() {
            return wrapped;
        }

        @Override
        public boolean equals(Object obj) {
            return equalsDelegate(obj);
        }

        @Override
        public int hashCode() {
            return hashCodeDelegate();
        }

        protected abstract boolean equalsDelegate(Object obj);

        protected abstract int hashCodeDelegate();
    }


    public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {

        @Override
        protected boolean equalsDelegate(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == getWrappedObject()) {
                return true;
            }
            if (obj.getClass() != getWrappedObject().getClass()) {
                return false;
            }
            Widget rhs = (Widget) obj;

            return new EqualsBuilder().append(getWrappedObject().getName(),
                    rhs.getName()).append(getWrappedObject().getDesc(),
                    rhs.getDesc()).isEquals();
        }

        @Override
        protected int hashCodeDelegate() {

            return new HashCodeBuilder(121, 991).append(
                    getWrappedObject().getName()).append(
                    getWrappedObject().getDesc()).toHashCode();
        }

    }
 0
Author: jonathan.cone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-18 00:49:02