Problema com nomes de arquivos acentuados

Bom dia,

Executando

        File f1 = new File(path);
        String[] files = f1.list();

Eu obtenho um String[] contendo arquivos (e diretórios) contidos no diretório informado na variável path. Gostaria de submeter individualmente cada posição desse array a um método qualquer.
O problema que eu estou tendo aqui é que dentro do diretório informado (variável path) encontram-se alguns arquivos com nomes acentuados e os caracteres acentuados do arquivo estão sendo armazenados no array como “?”.

Exemplo:

O arquivo Observación.doc está sendo armazenado como Observaci?n.doc.

Quando tento executar alguma operação sobre o arquivo informado nessa posição do array, recebo um FileNotFoundException, por q esse caracter foi substituído e não existe nenhum arquivo chamado “Observaci?n.doc”.

Alguém sabe como posso resolver isso?

Obrigado

Normalmente não se acentua nomes de arquivos, pois pode causar erros.

Isso é uma “não-solução”. OK, eu também não gosto de nomes de arquivos com espaços, mas ele tem um problema a resolver.

Eu também não gosto de acentuar nomes de arquivos, mas o sistema de arquivos que o programa analiza não está sob o meu controle e eu não tenho como impedir os usuários de criarem arquivos dessa forma.

Tentei replicar aqui, mas está tudo certo. Com certeza é problema de encoding no SO, mas a JVM deveria tratar disso.

Já tentou usar o listfiles() no lugar de list()? Ele retorna os arquivos direto, e não os nomes.

pelo jeito, a codificação do sistema operacional está diferente da máquina virtual.
como vc tem uma array de string, pode pegar cada item e colocar a codificação correta. Na maioria dos casos é mudar de ISO-8859-1 para UTF-8 ou vice versa.

tem na api por getBytes(String charsetName) ou pelo construtor String(byte[] bytes, String charsetName).

parace q com os streams vc pode jogar os bytes da string em vez do objeto String direto q ai não da problema. Mas existem casos tb que vc não quer trabalhar com streams, como por exemplo apenas manipular o nome de arquivos.

O que é mais estranho é que não consegui replicar o seu problema. Eu tenho o seguinte programa:

import java.util.*;
import java.io.*;
import java.security.*;
import java.nio.*;
import java.nio.channels.*;
import java.math.*;

class AcharArquivosIguais {
    public AcharArquivosIguais() {
    }
    public void listarArquivos (File diretorio, Set<File> arquivos) {
        File[] listagem = diretorio.listFiles();
        for (File f : listagem) {
            if (f.isDirectory() && !f.getName().equals (".") && !f.getName().equals ("..")) {
                listarArquivos (f, arquivos);
            } else {
                arquivos.add (f);
            }
        }
    }
    private static byte[] buffer = new byte[1024*1024];
    private static String hashFile (File arq) {
        String s = "error";
        try {
            MessageDigest dgst = MessageDigest.getInstance ("SHA1");
            FileInputStream fis = new FileInputStream (arq); 
            int nBytes;
            dgst.reset();
            while ((nBytes = fis.read (buffer)) > 0) {
                dgst.update (buffer, 0, nBytes);
            }
            byte[] bytes = dgst.digest();
            fis.close();
            BigInteger bd = new BigInteger (bytes);
            s = bd.toString (16);
        } catch (NoSuchAlgorithmException ex) {
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return s;
    }
    public void acharArquivosIguais (Collection<File> arquivos) {
	Map<String, List><File>> hash2file = new TreeMap<String, List><File>>();
        for (File f : arquivos) {
            System.err.print ("\r" + f.getName());
            String hash = hashFile (f);
            List<File> files;
            if (!hash2file.containsKey (hash)) {
                files = new ArrayList<File>();
                hash2file.put (hash, files);
            } else {
                files = hash2file.get (hash);
            }
            files.add (f);
        }
        System.out.println ();
        // Agora vamos ver, nessa lista, que arquivos têm mais de uma entrada.
        boolean arquivosRepetidos = false;
        for (Map.Entry<String, List><File>> h2f : hash2file.entrySet()) {
            if (h2f.getValue().size() > 1) {
                arquivosRepetidos = true;
                System.out.println ("---");
                System.out.println (h2f.getValue().size() + " arquivos com o hash " + h2f.getKey() + ":");
                System.out.println ("---");
                for (File f : h2f.getValue()) {
                    System.out.println ("    " + f);
                }
            }
        }
        if (!arquivosRepetidos) {
            System.out.println ("Não foram encontrados arquivos repetidos.");
        }
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println ("Sintaxe: java -cp . AcharArquivosIguais diretorio");
            System.exit (1);
        }
        AcharArquivosIguais aai = new AcharArquivosIguais();
        Set<File> arquivos = new TreeSet<File>();
        System.err.println ("Procurando os arquivos...");
        aai.listarArquivos (new File (args[0]), arquivos);
        System.err.println ("Lendo os arquivos...");
        aai.acharArquivosIguais (arquivos);
    }
}

Eu rodei o programa acima em um diretório contendo arquivos com nomes contendo letras acentuadas e espaços, e ele não me lançou nenhuma IOException referente a arquivos não encontrados. Será que é alguma outra coisa (por exemplo, você guardou os nomes em um banco de dados, e depois tentou pegar o nome com acentos - que veio errado, por algum motivo - e tentou localizar o arquivo?)

Bom… vamos por partes (Jack Estripador)

Para simplificar (ou piorar mais ainda) o problema, apresento abaixo o método dito… problemático:

    public static ArrayList<String> getTree(String path) {
        ArrayList<String> output = new ArrayList();
        if (path.charAt(path.length() - 1) != '/') {
            path = path + "/";
        }
        File f1 = new File(path);
        File type_check;
        String[] files = f1.list();
        for (int i = 0; i < files.length; i++) {
            if (!files[i].matches(".snapshot")) { // RETIRAR ISTO
                type_check = new File(path + files[i]);
                if (type_check.isDirectory()) {
                    output.addAll(getTree(path + files[i]));
                } else {
                    output.add(path + files[i]);
                }
            }
        }
        return output;
    }

Como podem observar, é um método simples que retorna de forma recursiva a estrutura de diretórios abaixo do caminho informado na variável path. Cada posição do array de saída será utilizada como parâmetro em um outro método qualquer. Como pode-se observar, esses dados não passaram por nenhum banco de dados.

Realmente o problema está parecendo mesmo ser de codificação conforme o que já foi sugerido aqui.

Observei que não consigo exibir bytes acima de 127. Entretanto entre 0 e 127, não possuo caracteres como “ç, á, é, í, ó, ú, ã, etc” o que me leva a pensar que esses devem ter valores superiores a 127. segue o exemplo (Usando getBytes como também já foi sugerido):

        String teste="Teste"+String.valueOf((char)140);
        byte[] bs=teste.getBytes();
        System.out.println(bs[bs.length-1]);

O número exibido na última linha deveria ser 140, entretanto o número 63 (endereço do caracter “?”) é o que de fato aparece.

Como faço para mudar a codificação do sistema para resolver esse problema?

Eu não trabalharia com String e sim com java.io.File. Seu código poderia ser assim:

public static List<File> getTree (File path) {
    List<File> output = new ArrayList<File>();
    File[] files = path.listFiles();
    for (String file : files) {
        if (file.isDirectory()) {
            if (!".".equals(file.getName()) && !"..".equals(file.getName()) {
                output.addAll (getTree (file));
            }
        } else {
            output.add (file);
        }
    }
    return output;
}

Você precisa adaptar outras partes do seu programa para poder usar o método acima.

thingol,

Adaptei o método que você sugeriu ao meu programa e também adaptei os métodos restantes para trabalharem com “File” no lugar de “String”, entretanto o erro persiste.

    public static List<File> getTree(File path) {
        List<File> output = new ArrayList<File>();
        File[] files = path.listFiles();
        for (File file : files) {
            if (!file.getName().matches(".snapshot")) {
                if (file.isDirectory()) {
                    output.addAll(getTree(file));
                } else {
                    output.add(file);
                }
            }
        }
        return output;
    }
    public static String getMD5Checksum(File file) throws Exception {
        byte[] b = createChecksum(file);
        String result = "";
        for (int i = 0; i < b.length; i++) {
            result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
        }
        return result;
    }
    public static byte[] createChecksum(File file) throws Exception {
        InputStream fis = new FileInputStream(file);
        byte[] buffer = new byte[1024];
        MessageDigest complete = MessageDigest.getInstance("MD5");
        int numRead;
        do {
            numRead = fis.read(buffer);
            if (numRead > 0) {
                complete.update(buffer, 0, numRead);
            }
        } while (numRead != -1);
        fis.close();
        return complete.digest();
    }

Cada posição da lista obtida pelo método getTree() é submetida ao método getMD5Checksum() que por sua vez chama o método createChecksum().
O programa funciona muito bem até chegar ao arquivo /u/s0b4/2d/filed/préreq.txt, quando a seguinte exception ocorre:


java.io.FileNotFoundException: /u/s0b4/2d/filed/pr?req.txt (No such file or directory)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(FileInputStream.java:106)
        at treecomp.TreeComp.createChecksum(TreeComp.java:92)
        at treecomp.TreeComp.getMD5Checksum(TreeComp.java:109)
        at treecomp.TreeComp.startComp(TreeComp.java:124)
        at treecomp.frm_main.jButton1ActionPerformed(frm_main.java:97)
        at treecomp.frm_main.access$000(frm_main.java:16)
        at treecomp.frm_main$1.actionPerformed(frm_main.java:45)
        at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
        at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
        at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)

        at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
        at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
        at java.awt.Component.processMouseEvent(Component.java:6038)
        at javax.swing.JComponent.processMouseEvent(JComponent.java:3265)
        at java.awt.Component.processEvent(Component.java:5803)
        at java.awt.Container.processEvent(Container.java:2058)
        at java.awt.Component.dispatchEventImpl(Component.java:4410)
        at java.awt.Container.dispatchEventImpl(Container.java:2116)
        at java.awt.Component.dispatchEvent(Component.java:4240)
        at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4322)
        at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3986)
        at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3916)
        at java.awt.Container.dispatchEventImpl(Container.java:2102)
        at java.awt.Window.dispatchEventImpl(Window.java:2429)
        at java.awt.Component.dispatchEvent(Component.java:4240)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)

Observe que eu não usei String em lugar nenhum, mas mesmo assim o programa substitui o “é” por “?”.
Parece que inevitávelmente terei que resolver o problema da codificação, o problema maior é que eu não faço num idéia de por onde começar. :frowning:
Podes me ajudar com isso?

Obrigado

Com um nome de arquivo estranho assim (/u/s0b4) isso é um AIX, Solaris, o quê? Talvez haja alguma sutileza ou bug mesmo.

linux

Falar “linux” é igual a falar “Windows”. Dá uma idéia aproximada mas não muito boa, se for algum problema ambiental.

Qual Linux? (Fedora, Ubuntu, SuSE etc.?)
Qual a versão do Java que você está usando? poste a informação de “java -version”
Qual a variável de ambiente que determina que linguagem está sendo usada? (echo $LANG)

Linux:

Red Hat Enterprise Linux WS release 3.8 (Final)
Linux es000482 2.4.21-47.ELsmp #1 SMP Tue Aug 1 08:12:32 EDT 2006 x86_64 x86_64 x86_64 GNU/Linux

Java:

java version “1.6.0_06”
Java™ SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot™ Server VM (build 10.0-b22, mixed mode)

Variável LANG:

C

Hum… LANG é C? Será que mudando para um outro valor (como

export LANG=pt_BR.UTF-8

antes de rodar seu programa ajudaria alguma coisa?

Após mudar o valor da variável, a primeira coisa que percebi é que o NetBeans ficou em português.
Porém o problema permanece, apesar de agora no lugar de “?” é exibido um quadrado no lugar dos caracteres não reconhecidos.
Outra coisa que notei é que fazendo aquele mesmo teste:

        String teste = "Teste" + String.valueOf((char)140);
        byte[] bs = teste.getBytes();
        System.out.println(bs[bs.length - 1]);

outros valores diferente de 63 (?) estão sendo retornados sempre que eu mudo o valor de char para algo acima de 127.

Que valor que Charset.defaultCharset() retorna?

Sabe dizer o tipo do sistema de arquivos em que esses arquivos se encontram? Como ele está montado? Normalmente sistemas em FAT precisam ter suporte ao code page 437 (CP437) e estarem montados dessa forma.

O kernel também precisa ter suporte a esse encoding, espero que o da Red Hat já tenha.

A variável de ambiente LC_ALL normalmente tem que ficar em pt_BR. Outra opção para LANG é pt_BR.ISO-8859-1.

Dica: crie arquivos com acentos em um outro diretório que não seja esse /u/blablabla que você mencionou. Pelo nome, dá a impressão que foi montado via NFS ou outro sistema remoto. Crie em um diretório local na sua máquina Linux, e veja se dá o mesmo problema.

Bruno,

Configurando a variável LANG para pt_BR.ISO-8859-1 a coisa funcionou!

Agradeço a todos pela ajuda!