Se vuoi semplicemente sapere se i set sono uguali, il equals
metodo su AbstractSet
è implementato più o meno come di seguito:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return containsAll(c);
}
Nota come ottimizza i casi comuni in cui:
- i due oggetti sono gli stessi
- l'altro oggetto non è affatto un insieme, e
- le dimensioni dei due set sono diverse.
Dopodiché, containsAll(...)
tornerà false
non appena troverà un elemento nell'altro set che non è anche in questo set. Ma se tutti gli elementi sono presenti in entrambi i set, sarà necessario testarli tutti.
La performance nel caso peggiore si verifica quindi quando i due set sono uguali ma non gli stessi oggetti. Tale costo è tipicamente O(N)
o O(NlogN)
dipende dall'implementazione di this.containsAll(c)
.
E ottieni prestazioni quasi peggiori se i set sono grandi e differiscono solo in una piccola percentuale degli elementi.
AGGIORNARE
Se sei disposto a investire tempo in un'implementazione di set personalizzato, esiste un approccio che può migliorare il caso "quasi lo stesso".
L'idea è che devi pre-calcolare e memorizzare nella cache un hash per l'intero set in modo da poter ottenere il valore hashcode corrente del set in O(1)
. Quindi puoi confrontare il codice hash per i due set come un'accelerazione.
Come potresti implementare un codice hash del genere? Ebbene, se il codice hash impostato fosse:
- zero per un insieme vuoto e
- lo XOR di tutti i codici hash degli elementi per un insieme non vuoto,
quindi potresti aggiornare a buon mercato l'hashcode memorizzato nella cache del set ogni volta che aggiungi o rimuovi un elemento. In entrambi i casi, devi semplicemente XOR il codice hash dell'elemento con il codice hash impostato corrente.
Naturalmente, ciò presuppone che i codici hash degli elementi siano stabili mentre gli elementi sono membri di insiemi. Presume inoltre che la funzione hashcode delle classi di elementi dia una buona diffusione. Questo perché quando i due codici hash impostati sono gli stessi devi ancora ricorrere al O(N)
confronto di tutti gli elementi.
Potresti portare questa idea un po 'oltre ... almeno in teoria.
ATTENZIONE - Questo è altamente speculativo. Un "esperimento mentale", se vuoi.
Supponiamo che la tua classe di elementi set abbia un metodo per restituire un checksum crittografico per l'elemento. Ora implementa i checksum del set XORing dei checksum restituiti per gli elementi.
Cosa ci fa guadagnare?
Bene, se assumiamo che non stia succedendo nulla di subdolo, la probabilità che due elementi di insieme disuguali abbiano lo stesso checksum di N bit è 2 -N . E anche la probabilità che 2 insiemi disuguali abbiano gli stessi checksum a N bit è 2 -N . Quindi la mia idea è che puoi implementare equals
come:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return checksums.equals(c.checksums);
}
In base alle ipotesi di cui sopra, questo ti darà la risposta sbagliata solo una volta ogni 2 -N . Se si imposta N abbastanza grande (ad esempio 512 bit) la probabilità di una risposta sbagliata diventa trascurabile (ad esempio circa 10 -150 ).
Lo svantaggio è che il calcolo dei checksum crittografici per gli elementi è molto costoso, soprattutto all'aumentare del numero di bit. Quindi hai davvero bisogno di un meccanismo efficace per memorizzare i checksum. E questo potrebbe essere problematico.
E l'altro aspetto negativo è che una probabilità di errore diversa da zero può essere inaccettabile, non importa quanto piccola sia la probabilità. (Ma se questo è il caso ... come gestisci il caso in cui un raggio cosmico capovolge un bit critico? O se capovolge simultaneamente lo stesso bit in due istanze di un sistema ridondante?)