Duvida com HashSet

Minha aplicação possui um HashSet global de strings
este set é alimentado por varias threads, o metodo de inserção de dados é sincronizado.

Porém tenho reparado que algumas strings tem se repetido dentro do set. li que seria preciso alterar o equals() e o hashCode() para fazer uma comparação correta e evitar repetição, mas como eu teria q fazer isso no meu caso ?

aguem pode me dar uma ajuda com exemplo de codigo ?

segue o codigo da minha classe


import java.util.HashSet;
import java.util.Set;

public class Visitar {
	
	private static Set<String> buf = new HashSet<String>();

	
	public static synchronized void adiciona(String li) {

		buf.add(li);

	}// end of 
	
	
	public static synchronized String remove() {

		String l = buf.iterator().next();

		buf.remove(l);

		return l;

	}// end of 
	
	
	public static synchronized int total() {

		return buf.size();

	}// end of  

}// end of class


como eu posso nesse caso alterar o hashcode e o equals para nao haver strings repetidas no meu buffer ?

A classe String já possui uma implementação correta dos métodos equals() e hashCode(). Essa recomendação de implementar equals() e hashCode() seria para quando você utiliza collections de tipos criados por você.

Poderia listar aqui algum exemplo de String que se repetiu no seu HashSet?

Se vc está utilizando um Set em um ambiente concorrente, é melhor você usar a classe ConcurrentHashSet.
Quanto ao caso de Strings se repetirem em um Set é muuuuuuiiiiito estranho, na verdade nem deve ser um erro na sua app, vc deve ter visto errado.

Não é possível um HashSet ter 2 objetos que que são iguais (retornem true em um teste equals)

Fiz uma classe aqui de exemplo, pode usá-la para testar:[code]import java.util.HashSet;
import java.util.Set;

class MyThread extends Thread{

private Set<String> set;

public MyThread(Set<String> set){
	this.set = set;
}

@Override
public void run(){
	for (int i = 0; i < 100; i++){
		set.add("1");
	}
}

}

public class Teste{

private Set<String> set;

{
	set = new HashSet<String>();
}

public Set<String> getSet(){
	return set;
}

public static void main(String[] args){
	Teste t = new Teste();
	for (int i = 0; i < 1000; i++){
		System.out.println(i);
		new MyThread(t.getSet()).start();
	}

	System.out.println(t.set);
}

}[/code]

[quote=mmx]Minha aplicação possui um HashSet global de strings
este set é alimentado por varias threads, o metodo de inserção de dados é sincronizado.

Porém tenho reparado que algumas strings tem se repetido dentro do set. li que seria preciso alterar o equals() e o hashCode() para fazer uma comparação correta e evitar repetição, mas como eu teria q fazer isso no meu caso ?

aguem pode me dar uma ajuda com exemplo de codigo ?[/quote]

Um HashSet não é automaticamente sincronizado como o antigo java.util.Hashtable (que não deve ser usado em código novo). Isso pode estar causando os elementos repetidos no HashSet. Note que não somente a inserção deve ser sincronizada, mas também a consulta.

Normalmente você efetuar a sincronização usando o próprio objeto HashSet, ou então usando Collections.synchronizedSet (embora isso não sincronize o caso especial em que você quer percorrer o HashSet completo, usando iterator()).

http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#synchronizedSet(java.util.Set)

Em um sistema que compramos de um nosso parceiro americano, usa-se ConcurrentHashMap:

http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentHashMap.html

(Você pode usar o ConcurrentHashMap como um HashSet, fazendo com que todos os valores sejam null ; na verdade, se não me engano o HashSet é implementado como um HashMap onde todos os valores são null, e o que importa são apenas as chaves.

Note que o ConcurrentHashMap também não sincroniza o iterator(). Você precisa efetuar manualmente a sincronização, tanto com Collections.synchronizedSet, quanto para ConcurrentHashMap.

vou fazer os testes e pesquisar sobre ConcurrentHashSet, o que tem ocorrido na aplicação e q uma thread retira e processa uma string do buffer e depois insere esta string processada em outro buffer, que tambem é um HashSet, todos os metodos de leitura e escrita sao sincronizados

Entao se uma string ja foi processada, ela nao deveria ser processada novamente, reparei q as threads estavam processando essa string ja processada, mas votarei as pesquisas

mais uma coisa, conforme ja postei acima


public static synchronized String remove() {  
  
        String l = buf.iterator().next();  
  
        buf.remove(l);  
  
        return l;  
  
    }// end of   

esta é a maneira correta de se remover um objeto de um HAshSet, ou este procedimento estaria causando um memory Leak ?

por hora agradeço muito a todos que responderam ! :slight_smile:

up ?

[quote=mmx]mais uma coisa, conforme ja postei acima


public static synchronized String remove() {  
  
        String l = buf.iterator().next();  
  
        buf.remove(l);  
  
        return l;  
  
    }// end of   

esta é a maneira correta de se remover um objeto de um HAshSet, ou este procedimento estaria causando um memory Leak ?

por hora agradeço muito a todos que responderam ! :)[/quote]

Pergunta imbecil número 1 - por que é que você usa um remove sem parâmetros? É que você quer transferir os elementos de um HashSet para outro, imagino que fazendo isto aqui:

Set<String> buf = new HashSet<String>();
Set<String> conj2 = new HashSet<String>();
...
String s;
while ( (s = remove()) != null ) {
    conj2.add (s);
}

Se for isso, é mais fácil (e na verdade até mais seguro) fazer o seguinte (não estou levando em conta os synchronized que porventura sejam necessários) :

Set<String> buf = new HashSet<String>();
Set<String> conj2 = new HashSet<String>();
...
conj2.addAll (buf);
buf.clear();

Ah, agora entendi.
Você quer fazer um “producer-consumer”.
Por que é que é um HashSet e não uma fila normal (como uma ArrayBlockingQueue ? )

Uh, entendi que você não quer realmente ter elementos repetidos. Nesse caso, você pode combinar um ConcurrentHashMap com alguma coisa que permita verificar se alguma thread deva ser acordada para pegar um elemento nesse ConcurrentHashMap.
Tente não pensar em “threads” (que é uma abstração de nível muito baixo, é como se fosse o “goto” do C) e sim em “Executors”.

Estude o pacote java.util.concurrent e veja se alguma estrutura de dados se adequa a seu problema.

Espera um pouquinho. Digamos que você tenha a seguinte sequência de operações:

produtor produz string A -> set: {A}
consumidor consome string - A -> set: {}
produtor produz novamente a string A -> set: {A}
consumidor consome string - que vai ser A, é óbvio

Então a impressão que você tem de que as strings estão sendo novamente processadas pode ser um efeito colateral de os consumidores estarem consumindo tão rapidamente que as strings que já foram processadas não serem encontradas.

Em vez disso, você teria de fazer o seguinte:
a) Ter uma fila onde os produtores inserissem as strings
b) Ter um conjunto de strings já processadas
c) Quando um produtor fosse enfileirar algo, ele primeiramente deveria checar o conjunto de strings já processadas, e se não estiver nesse conjunto, só então enfileirar
d) Quando um consumidor for processar uma string da fila, ele deve inserir a string no conjunto de strings já processadas, e então remover a string da fila (ou ao contrário, dependendo do tipo de processamento que deve ser feito.).

Infelizmente você não pode ter apenas uma estrutura de dados; você tem de ter 2.
Mas como uma única string pode ter referências na primeira e na segunda estrutura, você não está duplicando informações.

Diria que é bem isso que eu estou fazendo

Eu perguntei em cima sobre a maneira correta de remover os dados pq me corrijam se eu estiver errado, uma vez pesquisando sobre memory Leak, li um texto q dizia q em arrayList se vc der remove em um objeto, o array ainda mantem uma referencia para o mesmo, ate se fir utilizado o metodo Clear(), o correto seria entao marcar como NULL a posição do objeto e manda-lo pro coletor de lixo

ai fiquei na duvida se o HasSet tinha mesmo comportamento, pq desempenho e crucial na minha aplicação

Primeiro faça funcionar direito, depois pense em desempenho.

Isto posto, se você souber o que está fazendo, naturalmente a sua solução correta terá bom desempenho também.

De qualquer maneira, essa história de “em um ArrayList, remover um objeto ainda mantém uma referência para o mesmo” é meio esquisita. Duas coisas:

a) Um ArrayList normal não deve ser usado como uma fila - para filas, use alguma classe que implemente a interface Queue;

b) Tanto o método remove (int) quanto o método remove (Object), em java.util.ArrayList, são implementados anulando-se a posição correspondente no array. Deve ser algum antigo bug do ArrayList que foi corrigido no Java 1.2 mas que as pessoas até hoje acham que ainda existe.
Veja o comentário “let gc do its work”.

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    }

sera q passei mesmo esta impressão ? acredite, sei bem o q estou fazendo

funcionar esta funcionando, eu estou vamos dizer otimizando o código

mas creio q ja sei onde esta o erro, vou fazer alguns testes de stress para confirmar a minha hipotese e posto aqui o resultado, todavia agradeço pela atenção