poprawny hashCode

0

Czesc, mam do Was 2 pytania:

mam 2 HashMap<Integer, TestObj> o nazwach A i B. Jedna trzyma stare wartosci (A), druga nowe (B). I teraz jak zostanie utworzona nowa to musze sprawdzic czy ktorys z jej elementow istnieje w starej. TestObj jest to Entity. Wyglada mniej wiecej tak:

 

@Entity
public class TestObj implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String field1;
    private int field2;
    private String field3;
    private Date field4;

//gettery i settery

}

pomyslalam ze najprosciej bedzie jesli te zaimplementuje hasCode dla tej klasy i w mapach A i B jako klucz bede trzymac wlasnie ten hashCode. Pozniej bedzie mi latwo sprawdzic czy jakies elementy A istnieja w B. W moim przypadku 2 obiekty klasy TestObj sa rowne jesli wszystkie ich pola (co do wartosci) sa rowne, wiec w tym przypadku powinny zwracac ten sam hashCode.

Mysle sobie jak poprawnie zaimplementowac to hashCode. Czy wystarczyloby mi cos takiego, np:

 

int hash = field1 + field2.hashCode() + field3 + field4.hashCode();

? Moim zdaniem tak, ale sie zastanawiam czy przy tak prostej implementacji moge sie jednak gdzies naciac?

Drugie pytanie. Tyczy sie plikow - zalozmy ze kopiuje sobie plik do katalogu tmp. Sprawdzajac jego lastModificationDate otrzymuje naprzemian: CreateDate, ModificationDate oraz AccessedDate (sprawdzalam poprzez prawy myszy - Properties). Czy ktos z Was wie moze dlaczego?

pzdr,
misty

0
  • Każde sensowne IDE ma generator hashCode i equals, który na pewno będzie dużo lepszy od tego co przedstawiłaś.
  • mapa za hashCode do obiektu? Po co? Użyj kolekcji typu Set.

Zakładając, że A i B to zbiory (Set) to:
A.containsAll(B) sprawdza czy B zawiera się w całości A
A.removeAll(B) = A - B
A.retainAll(B) = iloczyn (część wspólna) zbiorów A i B
A.addAll(B) = suma zbiorów A i B
Collections.disjoint(A, B) sprawdza czy A i B zawierają wspólny element.

0

dzieki!!

0
  1. Oczywiście, że na haszach możesz się naciąć. Przede wszystkim zakładasz (chyba), że każde pole odwołuje się do poprawnego obiektu, co nie musi być prawdą w każdym momencie.
    Pewnym ułatwieniem jest wprowadzona w Javie 7 klasa Objects, która załatwia problem z żonglowaniem nullami. Poza tym upraszcza pisanie poprawnych metod hashCode jak i equals:
//require: Java 7

@Override public int hashCode()
{
	return Objects.hash(id, field1, field2, field3, field4);
}

@Override public boolean equals(Object otherObj)
{
	if(otherObj == null || getClass() != otherObj.getClass())
		return false;
	if(this == otherObj) //this object
		return true;
	@SuppressWarnings("unchecked")
		final TestObj other = (TestObj)otherObj;
	return this.id == other.id
		&& Objects.equals(this.field1, other.field1) //or deepEquals
		&& this.field2 == other.field2
		&& Objects.equals(this.field3, other.field3) //or deepEquals
		&& Objects.equals(this.field4, other.field4); //or deepEquals
}

Przy okazji polepsza to czytelność kodu takich metod. W wypadku hashCode() argumenty id i field2 są zamieniane na obiekty z racji autoboxa, ale gdyby nie miało być to robione (autobox ma pewien narzut na wydajność), to wydajniejsze byłoby:

@Override public int hashCode()
{
	int hash = Objects.hash(field1, field3, field4);
	return  (hash << 5) - hash + id + field2;
}

ps. A jeżeli już naprawdę przyciśnie Cię kwestia wydajności (i na dodatek tablic), to poniżej jest ciekawy kod ogólnej metody nieco wydajniejszej niż referencyjna z biblioteki standardowej:

private static int hashCode(Object object)
{
	final Class<?> cls = object.getClass();
	if(cls.isArray())
		if(cls.getComponentType().isPrimitive()) //primitive table
			switch(cls.getComponentType().getName())
			{
			case "boolean": return Arrays.hashCode((boolean[])object);
			case "char": return Arrays.hashCode((char[])object);
			case "byte": return Arrays.hashCode((byte[])object);
			case "short": return Arrays.hashCode((short[])object);
			case "int": return Arrays.hashCode((int[])object);
			case "long": return Arrays.hashCode((long[])object);
			case "float": return Arrays.hashCode((float[])object);
			case "double": return Arrays.hashCode((double[])object);
			}
		else
		{
			final Object[] array = (Object[])object;
			int hash = 0;
			for(int i = 0; i < array.length; ++i)
				//used this hashcode (recurrent)
				hash = (hash << 5) - hash + hashCode(array[i]);
			return hash;
		}
	return Objects.hashCode(object);
}
  1. Jak sprawdzasz jego "lastModificationCode"?
    Bo jest kilka sposobów, a jeszcze Java 7 dodatkowo dorzuciła sporo nowego w sprawie atrybutów plików.
0

Gołe XORy są złe, bo w wielu scenariuszach powodują masę kolizji w HashMapie, a przez co wydajność drastycznie spada.

HashMapa (jak i TreeMapa i pewnie sporo innych rodzajów map) działa poprawnie tylko wtedy, jeżeli obiekty-klucze w niej nie są mutowane - inaczej struktura się rozpieprza. A więc logicznym krokiem byłoby nawet stworzenie niemutowalnych kluczy. W takim przypadku hashCode można sobie zapisać w dodatkowym polu np typu int - wartość 0 oznacza nieobliczony hashCode, a inne wartości oznaczają obliczony (nota bene: tak działa hashCode w klasie String).

0

hej,
dzieki za kolejne przyklady! 7mki niestety nie mam. Wlasciwie to wygenerowany hashCode przez eclipse mi wystarczy.

2. Jak sprawdzasz jego "lastModificationCode"?
Jak pisalam wczesniej 7mki nie mam (nie moge uzyc). A sprawdzam po prostu: file.lastModified();

0

To przelatywanie przez trzy daty dostępu przy pomocy lastModified jest ekstra fjuczerem na Twojej implementacji Javy (nie opisuje tego dokumentacja). Normalnie powinna być tam tylko data ostatniej modyfikacji i nic więcej. Możesz to oczywiście wykorzystać. Ale przy migracji na nowszą Javę (która kiedyś pewnie nastąpi) taki kod i tak pewnie pójdzie do śmietnika (jak nie ma w dokumentacji, to może przestać istnieć fizycznie). Głównie dlatego, że w nowszej edycji rozwiązano to kompleksowo przez atrybuty pliku i nie trzeba będzie się wspierać starym nieudokumentowanym mykiem. Java po przejęciu przez Oracle dostała sporego kopa - stare interfejsy są ostatnio regularnie czyszczone i używane tylko jako mosty do nowego softu.

ps. Co do tych drobnych ułatwień do haszy w siódemce, to są one naprawdę kosmetyczne (choć skracają pisaninę):

public final class Objects {
    public static int hash(Object... values) {
        return Arrays.hashCode(values); //1.5
    }

    public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
//...
}
0

hmm, przeciez to jest w dokumentacji:

http://docs.oracle.com/javase/6/docs/api/java/io/File.html#lastModified()

ogolnie tak jak sobie patrze to plik ma 3 daty: Created, Modified oraz Accessed (prawym na plik->Properties). Ja sie spodziewalam ze wywolanie lastModified() zawsze zwroci mi date Modified. Ale tak nie jest. Use-case wyglada tak - kopiuje sobie plik do katalogu tmp na ktorym jest odpowiedni listener. Windows sam z siebie wysyla wtedy 4 sygnaly (1 typu create oraz 3 modificate - troche bez sensu, ale kazde OS robi to po swojemu). I jak teraz na te zdarzenia wypisuje sobie file.lastModified() to zamiast daty Modified, otrzymuje kolejno:
Created
Modified
Accessed
Accessed

Co jest dla mnie bez sensu bo w dokumentacji jest bykiem napisane:
A long value representing the time the file was last modified, measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970),

wiec dlaczego nie zwraca mi za kazdym razem Modified?

pzdr,
misty

0

Obsługa plików w Javie była (jest?) nędzna i mocno przestarzała. To co było prawdziwe pod javową obsługą plików w Windows XP mogło przestać być prawdziwe pod Vistą lub Win7. Obsługa plików nadal jest przestarzała bo w najnowszej edycji ledwo dorównano (ustandaryzowano?) możliwości jakie oferował kilka lat temu XP. Być może dlatego, że te takie możliwości dostały nie tak dawno temu wszelkie inne "linuksowato-uniksowate" systemy (co by nie mówić, to M$ nadal jest jakimś rozpowszechniaczem nowych pomysłów jeżeli chodzi o system - może czasem głupich, nietrafionych, zerżniętych z ogryzka, ale jednak).

Pytanie pomocnicze. Na jakim systemie to sprawdzasz?

0

teraz na xp. Docelowo bedzie dzialac na linuxie, ale nie wiem jeszcze na jakim. Z tego co mi wiadomo to linux nie wysyla na takie zdarzenia kilku sygnalow. Niemniej jednak - spodziewalabym sie ze ta metoda bedzie mi zwracac zawsze date Modification, tak jak jest w api napisane. Jakos nie widze tutaj zaleznosci od systemu operacyjnego (tzn chodzi mi o to ze zwraca rozne daty). Ale moze sie myle. Chcialam sobie uzyc tego atrybutu do sprawdzania czy plik zostal zmodyfikowany (tzn chcialam uzyc length() oraz tej metody lastModified()) ale widze ze nie ma sensu namniejszego isc ta droga skoro takie "kwiatki" wychodza.
Porownuje wiec pliki sprawdzajac znak po znaku. Innej mozliwosci w tym momencie nie widze.

pzdr,
misty

0

Ta metoda zawsze mi zwracała wyłącznie datę modyfikacji. Też na XP. Na Linuksach jest tylko data modyfikacji, więc tam nie powinno być problemu. Dlatego pytałem się o system.
Podrzuć tu jakiś krótki kawałek kodu tak jak to robisz, to będzie można sprawdzić czy te kwiatki w ten sam sposób wychodzą. Być może związane jest to jakoś z momentem lub wątkiem, w którym "pytasz".

0

Tylko ze ja nie robie nic specjalnego. Rejestruje sobie tego mojego "watchera" na danym katalogu i jak przyjdzie zdarzenie "MODIFY" to wypisywalam sobie date modyfikacji. Mysle ze ten kod nie wnosi nic specyficznego:


        while (watch) {

            try {
                signal = watchService.take();

            } catch (InterruptedException e) {
                logger.error(e);
            }

            /** get list of events from the key */
            List<WatchEvent<?>> events = signal.pollEvents();
            /** reset must be called to allow the key to be reported again by the watch service */
            signal.reset();

            for (WatchEvent event : events) {

                if (event.kind() == StandardWatchEventKind.ENTRY_MODIFY) {
                File file = new File(watchedDir + "/" + ((Path) event.context()).toString());
    logger.info(file.lastModified());
}
 

1 użytkowników online, w tym zalogowanych: 0, gości: 1