Leitura/Escrita de Bytes para um Arquivo Usando Apenas Java.IO

0

Pergunta

Como podemos escrever uma matriz de bytes para um arquivo (e lê-lo novamente a partir do arquivo) em Java?

Sim, todos nós sabemos que já existem muitas perguntas como essa, mas eles ficam muito confuso e subjetiva, devido ao fato de que há muitas maneiras de realizar essa tarefa.

Então, vamos reduzir o escopo da pergunta:

Domínio:

  • Android / Java

O que queremos:

  • Rápido (tanto quanto possível)
  • Bug-free (em um rigidamente meticuloso forma)

O que não estamos fazendo:

  • Bibliotecas de terceiros
  • Todas as bibliotecas que exigem Android API mais tarde do que 23 (Marshmallow)

(Então, que as regras do Apache Commons, o Google Guava, Java.nio, e deixa-nos com o bom e velho' de Java.io)

O que precisamos:

  • Matriz de bytes é sempre exatamente o mesmo (conteúdo e tamanho) depois de passar a escrever-então-processo de leitura
  • Método de gravação apenas necessita de dois argumentos: o Ficheiro de arquivo e byte[] dados
  • Leia método retorna um byte[] e exige apenas um argumento: Arquivo do arquivo

No meu caso em particular, estes métodos são privados (não uma biblioteca) e NÃO se responsabiliza pelo seguinte, (mas se você quiser criar uma solução universal que se aplica a um público mais amplo, vá para ele):

  • Thread-safety (ficheiro não será acessado por mais do que um processo de uma só vez)
  • Arquivo que está sendo nulo
  • Arquivo apontando para o local inexistente
  • A falta de permissões no arquivo de localização
  • Matriz de bytes a ser muito grande
  • Matriz de bytes a ser nulo
  • Lidar com qualquer "index", "comprimento", ou "acrescentar" argumentos/capacidades

Então... estamos em busca do definitivo à prova de bala código que as pessoas no futuro pode assumir é seguro de usar porque a sua resposta tem lotes de votos e não existem comentários que dizem, "Que pode falhar se o..."

Isso é o que eu tenho até agora:

Bytes De Gravação Para O Arquivo:

private void writeBytesToFile(final File file, final byte[] data) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
    }

Os Bytes De Leitura De Arquivo:

private byte[] readBytesFromFile(final File file) {
        RandomAccessFile raf;
        byte[] bytesToReturn = new byte[(int) file.length()];
        try {
            raf = new RandomAccessFile(file, "r");
            raf.readFully(bytesToReturn);
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
        return bytesToReturn;
}

Pelo que tenho lido, as possíveis Exceções são:

FileNotFoundException : Estou certo de que isso não deve acontecer tão longo como o caminho do arquivo a ser fornecida foi obtido usando o Android ferramentas internas e/ou se o aplicativo foi testado corretamente?

IOException : eu realmente não sei o que poderia causar isso... mas eu estou supondo que não há nenhuma maneira em torno dela se faz.

Então, com isso em mente... estes métodos podem ser aprimorados ou substituídos, e, em caso afirmativo, com que?

android arrays file java
2021-11-23 02:58:43
2

Melhor resposta

6

Parece que estes vão ser núcleo de utilitário/biblioteca de métodos, que deve ser executado no Android API de 23 ou mais tarde.

Relativas a métodos de biblioteca, acho que é melhor não fazer suposições sobre como as aplicações irão utilizar estes métodos. Em alguns casos, as aplicações podem querer receber marcada IOExceptions (porque os dados de um arquivo deve existir para que o aplicativo funcione), em outros casos, as aplicações podem não importa mesmo se os dados não estão disponíveis (como os dados de um arquivo é somente de cache, que também está disponível a partir de uma fonte primária).

Quando se trata de operações de e/S, nunca há uma garantia de que as operações será bem-sucedida (por exemplo, usuário de cair o telefone no banheiro). A biblioteca deve refletir e dar a aplicação de uma escolha sobre como lidar com os erros.

Para otimizar o desempenho de I/O sempre assumir o "caminho feliz" e pegar erros para descobrir o que deu errado. Isto é contra-intuitivo para a programação normal, mas essencial para lidar com o armazenamento de I/O. Por exemplo, apenas verificando se um arquivo existe antes de ler a partir de um arquivo podem tornar sua aplicação duas vezes mais lento de todos estes tipos de e/S de ações acumula-se rapidamente para atrasar a sua aplicação. Apenas suponha que o arquivo existe e se você receber um erro, apenas, em seguida, verifique se o arquivo existe.

Assim as ideias, as principais funções poderá ter esta aparência:

public static void writeFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileOutputStream out = new FileOutputStream(f)) {
        out.write(data);
    }
}

public static int readFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileInputStream in = new FileInputStream(f)) {
        return in.read(data); 
    }
}

Notas sobre a implementação:

  • Os métodos também podem jogar de tempo de execução, excepções, como NullPointerExceptions - esses métodos nunca vai ser "livre de erros".
  • Eu não acho que é necessária a memória intermédia/queria que os métodos acima, uma vez que apenas uma chamada nativa é feito (ver também aqui).
  • O aplicativo agora também tem a opção de ler apenas o início de um arquivo.

Para tornar mais fácil para um aplicativo de leitura de um arquivo, um método adicional pode ser adicionado. Mas lembre-se que é até a biblioteca para detectar eventuais erros e relatório-los para o aplicativo, uma vez que a própria aplicação pode não detectar esses erros.

public static byte[] readFile(File f) throws FileNotFoundException, IOException {
    int fsize = verifyFileSize(f);
    byte[] data = new byte[fsize];
    int read = readFile(f, data);
    verifyAllDataRead(f, data, read);
    return data;
}

private static int verifyFileSize(File f) throws IOException {
    long fsize = f.length();
    if (fsize > Integer.MAX_VALUE) {
        throw new IOException("File size (" + fsize + " bytes) for " + f.getName() + " too large.");
    }
    return (int) fsize;
}

public static void verifyAllDataRead(File f, byte[] data, int read) throws IOException {
    if (read != data.length) {
        throw new IOException("Expected to read " + data.length 
                + " bytes from file " + f.getName() + " but got only " + read + " bytes from file.");
    }
}

Esta implementação adiciona outro ponto oculto de falha: OutOfMemory, no ponto onde a nova matriz de dados é criado.

Para acomodar as aplicações mais, métodos adicionais podem ser adicionados para ajudar com diferentes cenário. Por exemplo, digamos que o aplicativo realmente não quer lidar com verificada exceções:

public static void writeFileData(File f, byte[] data) {
    try {
        writeFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
}

public static byte[] readFileData(File f) {
    try {
        return readFile(f);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return null;
}

public static int readFileData(File f, byte[] data) {
    try {
        return readFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return -1;
}

private static void fileExceptionToRuntime(Exception e) {
    if (e instanceof RuntimeException) { // e.g. NullPointerException
        throw (RuntimeException)e;
    }
    RuntimeException re = new RuntimeException(e.toString());
    re.setStackTrace(e.getStackTrace());
    throw re;
}

O método fileExceptionToRuntime é uma implementação mínima, mas ele mostra que a idéia aqui.

A biblioteca pode também ajudar um aplicativo para solucionar problemas quando um erro ocorrer. Por exemplo, um método de canReadFile(File f) poderia verificar se um arquivo existe e é legível e não é muito grande. O aplicativo pode chamar uma função depois de um arquivo de leitura falha e verificar as razões mais comuns por que um arquivo não pode ser lido. O mesmo pode ser feito para escrever para um ficheiro.

2021-11-28 22:59:55

Apreciar o útil e informativo resposta. Eu estou colocando-os juntos em um projeto para ver se consigo entender melhor. Qual é a razão para alterar a readBytes assinatura do método, a partir do que eu tinha? (seu leva um byte[] como um dos args e retorna int). Também é o último bloco de código se destina a ser parte da biblioteca ou a aplicação?
Nerdy Bunz

também não a linha "return (int) f.comprimento();" crash desde f.o comprimento é maior que o Inteiro.MAX_VALUE?
Nerdy Bunz

@NerdyBunz Sobre a última pergunta: não, "downcasting" não dá um erro e, neste caso, uma IOException for lançada quando o fsize o valor é muito grande. Além disso, eu deveria ter re-usado fsize lá (desde f.length() resulta em uma operação de e/S).
vanOekel

Sobre a primeira pergunta: todo ele é destinado a ser parte da biblioteca. Meu byte[] readFile(File f) é semelhante ao seu byte[] readBytesFromFile(final File file). Meu byte[] readFileData(File f) método é um exemplo de como você pode personalizar estas funções ainda mais. Eu tinha dificuldade em descobrir quais os métodos para expor (publice manter oculto (privatee eu penso que é uma pergunta que só você pode responder: quais os métodos que você deseja que o aplicativo para usar sem ser restritivas para a aplicação?
vanOekel
3

Embora você não pode usar bibliotecas de terceiros, você ainda pode ler o seu código e aprender com a sua experiência. No Google Guava por exemplo, você costuma ler um arquivo, em bytes, como este:

FileInputStream reader = new FileInputStream("test.txt");
byte[] result = ByteStreams.toByteArray(reader);

O núcleo implementação desta é toByteArrayInternal. Antes de chamar este procedimento, você deve verificar:

  • Um não nulo arquivo é passado (NullPointerException)
  • O arquivo existe (FileNotFoundException)

Depois disso, ele é reduzido a manipulação de um InputStream e este, onde IOExceptions vêm. Quando a leitura de sequências de um monte de coisas fora do controlo da sua aplicação pode dar errado (setores defeituosos e outros problemas de hardware, mal-funcionamento do airbag, OS direitos de acesso) e se manifestam com uma IOException.

Estou copiando aqui a implementação:

private static final int BUFFER_SIZE = 8192;

/** Max array length on JVM. */
private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8;

private static byte[] toByteArrayInternal(InputStream in, Queue<byte[]> bufs, int totalLen)
      throws IOException {
    // Starting with an 8k buffer, double the size of each successive buffer. Buffers are retained
    // in a deque so that there's no copying between buffers while reading and so all of the bytes
    // in each new allocated buffer are available for reading from the stream.
    for (int bufSize = BUFFER_SIZE;
        totalLen < MAX_ARRAY_LEN;
        bufSize = IntMath.saturatedMultiply(bufSize, 2)) {
      byte[] buf = new byte[Math.min(bufSize, MAX_ARRAY_LEN - totalLen)];
      bufs.add(buf);
      int off = 0;
      while (off < buf.length) {
        // always OK to fill buf; its size plus the rest of bufs is never more than MAX_ARRAY_LEN
        int r = in.read(buf, off, buf.length - off);
        if (r == -1) {
          return combineBuffers(bufs, totalLen);
        }
        off += r;
        totalLen += r;
      }
    }

    // read MAX_ARRAY_LEN bytes without seeing end of stream
    if (in.read() == -1) {
      // oh, there's the end of the stream
      return combineBuffers(bufs, MAX_ARRAY_LEN);
    } else {
      throw new OutOfMemoryError("input is too large to fit in a byte array");
    }
  }

Como você pode ver mais do que a lógica tem a ver com a leitura do arquivo em blocos. Este é lidar com situações, onde você não sabe o tamanho do InputStream, antes de iniciar a leitura. No seu caso, você só precisa ler os arquivos e você deve ser capaz de saber o comprimento de antemão, portanto, esta complexidade pode ser evitado.

A seleção é de OutOfMemoryException. Em Java padrão, o limite é muito grande, porém no Android, ele vai ser um valor muito menor. Você deve verificar, antes de tentar ler o arquivo de que há memória suficiente disponível.

2021-11-26 13:42:23

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................