2015-10-18 1 views
5

Vous savez que vous pouvez utiliser AsynchronousFileChannel pour lire un fichier entier à une chaîne:Comment utiliser AsynchronousFileChannel pour lire à un StringBuffer efficace

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ); 
      long len = fileChannel.size(); 

      ReadAttachment readAttachment = new ReadAttachment(); 
      readAttachment.byteBuffer = ByteBuffer.allocate((int) len); 
      readAttachment.asynchronousChannel = fileChannel; 

      CompletionHandler<Integer, ReadAttachment> completionHandler = new CompletionHandler<Integer, ReadAttachment>() { 

       @Override 
       public void completed(Integer result, ReadAttachment attachment) { 

        String content = new String(attachment.byteBuffer.array()); 
        try { 
         attachment.asynchronousChannel.close(); 
        } catch (IOException e) { 
         e.printStackTrace(); 
        } 
        completeCallback.accept(content); 
       } 

       @Override 
       public void failed(Throwable exc, ReadAttachment attachment) { 
        exc.printStackTrace(); 
        exceptionError(errorCallback, completeCallback, String.format("error while reading file [%s]: %s", path, exc.getMessage())); 
       } 
      }; 

      fileChannel.read(
        readAttachment.byteBuffer, 
        0, 
        readAttachment, 
        completionHandler); 

Supposons que maintenant, je ne veux pas affecter un ensemble ByteBuffer , mais lisez ligne par ligne. Je pourrais utiliser un ByteBuffer de largeur fixe et continuer à rappeler read plusieurs fois, en copiant et en ajoutant toujours à un StringBuffer jusqu'à ce que je n'obtienne pas à une nouvelle ligne ... Ma seule préoccupation est: parce que l'encodage du dossier que je lis peut être multi octet par caractère (quelque chose UTF), il peut arriver que les octets lus se terminent par un caractère incomplet. Comment puis-je m'assurer que je convertis les bons octets en chaînes et que je ne gâche pas l'encodage?

MISE À JOUR: réponse est dans le commentaire de la réponse sélectionnée, mais il pointe essentiellement à CharsetDecoder.

+3

Ne pas utiliser async E/S pour lire les lignes. Ce n'est tout simplement pas approprié. Vous pouvez lire des millions de lignes par seconde avec 'BufferedReader.readLine().' – EJP

+0

J'ai besoin d'une opération non bloquante! – gotch4

+0

Alors pourquoi utilisez-vous des E/S asynchrones? Ce n'est pas non-bloquant. C'est un troisième paradigme, après blocage et non-blocage. Mais pourquoi pensez-vous que vous ne pouvez pas utiliser les E/S bloquantes en premier lieu? – EJP

Répondre

1

Si vous avez un séparateur ASCII clair dans votre cas (\ n), vous ne devez pas vous soucier de la chaîne incomplète car ce caractère est mappé sur singlebyte (et vice versa).

Il suffit donc de rechercher '\ n' octet dans votre entrée et de lire et convertir n'importe quoi avant en String. Boucle jusqu'à ce que plus aucune nouvelle ligne ne soit trouvée. Ensuite, compactez le tampon et réutilisez-le pour la lecture suivante. Si vous ne trouvez pas de nouvelle ligne, vous devrez allouer un tampon plus grand, copier le contenu de l'ancien et seulement ensuite appeler à nouveau la lecture.

EDIT: Comme mentionné dans le commentaire, vous pouvez passer le ByteBuffer à CharsetDecoder à la volée et le traduire en CharBuffer (puis ajouter à StringBuilder ou à tout ce qui est la solution préférée).

+0

De toute façon je dois stocker une ligne entière comme tampon d'octets ... Oublions un instant que j'ai affaire à des lignes ... Et que ma mémoire tampon est limitée (les lignes peuvent être très longues). Comment ferions-nous? – gotch4

+1

Vous pouvez utiliser http://docs.oracle.com/javase/7/docs/api/java/nio/charset/CharsetDecoder.html#decode(java.nio.ByteBuffer,%20java.nio.CharBuffer,%20boolean) pour convertir l'entrée à la volée. Vous devrez toujours gérer le tampon car il pourrait contenir des caractères restants entre les lectures. –

+0

Parfait! Merci, pensez à mettre à jour la réponse – gotch4

0

Essayez Scanner:

Scanner sc = new Scanner(FileChannel.open(filePath, StandardOpenOption.READ)); 
    String line = sc.readLine(); 

FileChannel est InterruptibleChannel

+0

encore, je n'ai pas besoin d'interruptibilité, je dois commencer la lecture et un rappel plus tard ... – gotch4