Java: les chaînes de lecture à partir d'un fichier d'accès aléatoire avec une entrée en mémoire tampon

StackOverflow https://stackoverflow.com/questions/4305094

Question

Je ne l'ai jamais eu des expériences étroites avec l'API Java IO avant et je suis vraiment frustré maintenant. Je trouve difficile de croire comment il est étrange et complexe et à quel point il pourrait être de faire une tâche simple.

Ma tâche: J'ai 2 positions (à partir octet, octet de fin), et pos1 pos2. Je dois lire des lignes entre ces deux octets (y compris le démarrage, pas compris la fin à un) et les utiliser comme UTF8 objets String.

Par exemple, dans la plupart des langages de script, il serait un 1-2-3-liner très simple comme ça (en Ruby, mais il sera essentiellement le même pour Python, Perl, etc.):

f = File.open("file.txt").seek(pos1)
while f.pos < pos2 {
  s = f.readline
  # do something with "s" here
}

Il est rapidement l'enfer avec Java API IO;) En fait, je vois deux façons de lire les lignes (se terminant par \n) à partir de fichiers locaux réguliers:

  • RandomAccessFile a getFilePointer() et seek(long pos), mais il est readLine () lit les chaînes non-UTF8 (et même pas octet tableaux), mais très étranges cordes avec encodage brisé, et il n'a pas de mémoire tampon (qui probablement signifie que chaque appel read*() serait traduit en simple undelying OS read() => assez lent).
  • BufferedReader a une grande méthode readLine(), et il peut même faire une recherche avec skip(long n), mais il n'a aucun moyen de déterminer le nombre même d'octets qui a été déjà lu, ne pas mentionner la position actuelle dans un fichier.

Je l'ai essayé d'utiliser quelque chose comme:

    FileInputStream fis = new FileInputStream(fileName);
    FileChannel fc = fis.getChannel();
    BufferedReader br = new BufferedReader(
            new InputStreamReader(
                    fis,
                    CHARSET_UTF8
            )
    );

... et puis en utilisant fc.position() pour obtenir la position de lecture du fichier en cours et fc.position(newPosition) à un ensemble, mais il ne semble pas fonctionner dans mon cas: ressemble à elle retourne la position d'un pré-remplissage effectué par BufferedReader tampon, ou quelque chose comme ça -. ces compteurs semblent être arrondie en 16K incréments

Dois-je vraiment à mettre en œuvre tout par moi-même, à savoir un fichier l'interface readering qui:

  • me permettre d'obtenir / position définie dans un fichier
  • les opérations de lecture de fichiers tampons
  • permettre la lecture des chaînes UTF8 (ou au moins permettre à des opérations comme « tout lu jusqu'à la prochaine \n »)

Y at-il un moyen plus rapide que sa mise en œuvre moi-même? Suis-je superviser quelque chose?

Était-ce utile?

La solution

import org.apache.commons.io.input.BoundedInputStream

FileInputStream file = new FileInputStream(filename);
file.skip(pos1);
BufferedReader br = new BufferedReader(
   new InputStreamReader(new BoundedInputStream(file,pos2-pos1))
);

Si vous ne se soucient pas de pos2, alors vous avez besoin woundn't Apache Commons IO.

Autres conseils

J'ai écrit ce code pour lire utf-8 en utilisant randomaccessfiles

//File: CyclicBuffer.java
public class CyclicBuffer {
private static final int size = 3;
private FileChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(size);

public CyclicBuffer(FileChannel channel) {
    this.channel = channel;
}

private int read() throws IOException {
    return channel.read(buffer);
}

/**
 * Returns the byte read
 *
 * @return byte read -1 - end of file reached
 * @throws IOException
 */
public byte get() throws IOException {
    if (buffer.hasRemaining()) {
        return buffer.get();
    } else {
        buffer.clear();
        int eof = read();
        if (eof == -1) {
            return (byte) eof;
        }
        buffer.flip();
        return buffer.get();
    }
}
}
//File: UTFRandomFileLineReader.java


public class UTFRandomFileLineReader {
private final Charset charset = Charset.forName("utf-8");
private CyclicBuffer buffer;
private ByteBuffer temp = ByteBuffer.allocate(4096);
private boolean eof = false;

public UTFRandomFileLineReader(FileChannel channel) {
    this.buffer = new CyclicBuffer(channel);
}

public String readLine() throws IOException {
    if (eof) {
        return null;
    }
    byte x = 0;
    temp.clear();

    while ((byte) -1 != (x = (buffer.get())) &amp;&amp; x != '\n') {
        if (temp.position() == temp.capacity()) {
            temp = addCapacity(temp);
        }
        temp.put(x);
    }
    if (x == -1) {
        eof = true;
    }
    temp.flip();
    if (temp.hasRemaining()) {
        return charset.decode(temp).toString();
    } else {
        return null;
    }
}

private ByteBuffer addCapacity(ByteBuffer temp) {
    ByteBuffer t = ByteBuffer.allocate(temp.capacity() + 1024);
    temp.flip();
    t.put(temp);
    return t;
}

public static void main(String[] args) throws IOException {
    RandomAccessFile file = new RandomAccessFile("/Users/sachins/utf8.txt",
            "r");
    UTFRandomFileLineReader reader = new UTFRandomFileLineReader(file
            .getChannel());
    int i = 1;
    while (true) {
        String s = reader.readLine();
        if (s == null)
            break;
        System.out.println("\n line  " + i++);
        s = s + "\n";
        for (byte b : s.getBytes(Charset.forName("utf-8"))) {
            System.out.printf("%x", b);
        }
        System.out.printf("\n");

    }
}
}

Pour @Ken Bloom Un très rapide à partir d'une version Java 7. Note: Je ne pense pas que ce soit la façon la plus efficace, je reçois toujours ma tête NIO.2, Oracle a commencé son tutoriel ici

Notez également que ce n'utilise pas nouvelle syntaxe ARM Java 7 (qui prend en charge la gestion des exceptions pour les ressources à base de fichiers), il ne fonctionnait pas dans la dernière version de OpenJDK que j'ai. Mais si les gens veulent voir la syntaxe, laissez-moi savoir.

/* 
 * Paths uses the default file system, note no exception thrown at this stage if 
 * file is missing
 */
Path file = Paths.get("C:/Projects/timesheet.txt");
ByteBuffer readBuffer = ByteBuffer.allocate(readBufferSize);
FileChannel fc = null;
try
{
    /*
     * newByteChannel is a SeekableByteChannel - this is the fun new construct that 
     * supports asynch file based I/O, e.g. If you declared an AsynchronousFileChannel 
     * you could read and write to that channel simultaneously with multiple threads.
     */
    fc = (FileChannel)file.newByteChannel(StandardOpenOption.READ);
    fc.position(startPosition);
    while (fc.read(readBuffer) != -1)
    {
        readBuffer.rewind();
        System.out.println(Charset.forName(encoding).decode(readBuffer));
        readBuffer.flip();
    }
}

Démarrer avec un RandomAccessFile de read et de l'utilisation ou readFully pour obtenir un tableau d'octets entre pos1 et pos2. Disons que nous avons mis en mémoire les données lues dans une variable nommée rawBytes.

Ensuite, créez votre BufferedReader en utilisant

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))

Ensuite, vous pouvez appeler readLine sur le BufferedReader.

caveat. Celui-ci utilise probablement plus de mémoire que si vous pouviez faire le BufferedReader chercher au bon endroit lui-même, car il tout en mémoire précharge

Je pense que la confusion est causée par l'encodage UTF-8 et la possibilité de caractères à deux octets.

UTF8 ne précise pas le nombre d'octets dans un seul caractère. Je suppose que de votre message que vous utilisez des caractères d'un octet. Par exemple, 412 octets signifierait 411 caractères. Mais si la chaîne utilisait des caractères à deux octets, vous obtiendrez le caractère 206.

Le paquet java.io d'origine ne traitait pas bien avec cette confusion multi-octets. Ainsi, ils ont ajouté plus de classes pour traiter spécifiquement des chaînes. Le package mélange deux types de gestionnaires de fichiers différents (et ils peuvent être source de confusion jusqu'à ce que la nomenclature est triée). flux classes fournissent des données E / S directes sans aucune conversion. Le lecteur Cours convertir des fichiers en chaînes avec un support complet pour les caractères multi-octets. Cela pourrait aider à clarifier une partie du problème.

Puisque vous déclarez que vous utilisez des caractères UTF-8, vous voulez que les cours de lecture. Dans ce cas, je suggère FileReader. La méthode saut () dans FileReader vous permet de passer par des personnages X, puis commencer la lecture du texte. Sinon, je préfère la méthode de lecture surchargée () car il vous permet de saisir tout le texte à un moment donné.

Si vous assumez votre « octets » sont des caractères individuels, essayer quelque chose comme ceci:

FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...

Je suis en retard à la fête, mais je suis tombé sur ce problème dans mon propre projet.

Après beaucoup de traversal Javadocs et Stack Overflow, je pense avoir trouvé une solution simple.

Après avoir demandé à l'endroit approprié dans votre RandomAccessFile, que j'appelle ici raFile, procédez comme suit:

FileDescriptor fd = raFile.getFD();
FileReader     fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);

Ensuite, vous devriez être en mesure d'appeler br.readLine() au contenu de votre cœur, ce qui sera beaucoup plus rapide que d'appeler raFile.readLine().

La seule chose que je ne suis pas est sûr de savoir si les chaînes UTF8 sont gérées correctement.

L'API java IO est très flexible. Malheureusement, parfois la flexibilité le rend bavard. L'idée principale ici est qu'il ya beaucoup de cours d'eau, les écrivains et les lecteurs qui mettent en œuvre wrapper boniment. Par exemple BufferedInputStream enveloppe tout autre InputStream. La même chose est sur les flux de sortie.

La différence entre les flux et les lecteurs / écrivains est que les flux de travail avec des octets tandis que les lecteurs / écrivains travaillent avec des personnages.

Heureusement certains cours d'eau, les écrivains et les lecteurs ont des constructeurs pratiques qui simplifient le codage. Si vous voulez lire le fichier que vous venez de dire

    InputStream in = new FileInputStream("/usr/home/me/myfile.txt");
    if (in.markSupported()) {
        in.skip(1024);
        in.read();
    }

Il est pas si compliqué que tu as peur.

Canaux est quelque chose de différent. Il est une partie de soi-disant « nouvelle IO » ou nio. New IO n'est pas bloqué - il est son principal avantage. Vous pouvez effectuer une recherche dans Internet pour tout « nio java tutoriel » et lire à ce sujet. Mais il est plus compliqué que IO régulière et n'est pas nécessaire pour la plupart des applications.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top