Je lis des données à partir d'un fichier qui comporte, malheureusement, deux types de codage de caractères.Problème de mise en mémoire tampon InputStreamReader
Il y a un en-tête et un corps. L'en-tête est toujours en ASCII et définit le jeu de caractères dans lequel le corps est codé.
L'en-tête n'est pas de longueur fixe et doit être parcouru par un analyseur pour déterminer son contenu/sa longueur.
Le fichier peut également être assez volumineux, je dois donc éviter de mettre tout le contenu en mémoire. J'ai donc commencé avec un seul InputStream. Je l'emballe d'abord avec un InputStreamReader avec ASCII et décode l'en-tête et extrait le jeu de caractères pour le corps. Tout bon.
Ensuite, je crée un nouveau InputStreamReader avec le jeu de caractères correct, le dépose sur le même InputStream et commence à essayer de lire le corps.
Malheureusement, il semble que javadoc confirme que InputStreamReader peut choisir de lire en avance pour des raisons d'efficacité. Donc, la lecture de l'en-tête mâche tout/tout le corps.
Est-ce que quelqu'un a des suggestions pour contourner ce problème? Est-ce que créer un CharsetDecoder manuellement et nourrir dans un octet à la fois mais une bonne idée (peut-être enveloppé dans une implémentation de lecteur personnalisé?)
Merci d'avance.
EDIT: Ma dernière solution consistait à écrire un InputStreamReader sans tampon pour m'assurer que je puisse analyser l'en-tête sans mâcher une partie du corps. Bien que ce ne soit pas terriblement efficace, j'enveloppe le InputStream brut avec un BufferedInputStream donc ce ne sera pas un problème.
// An InputStreamReader that only consumes as many bytes as is necessary
// It does not do any read-ahead.
public class InputStreamReaderUnbuffered extends Reader
{
private final CharsetDecoder charsetDecoder;
private final InputStream inputStream;
private final ByteBuffer byteBuffer = ByteBuffer.allocate(1);
public InputStreamReaderUnbuffered(InputStream inputStream, Charset charset)
{
this.inputStream = inputStream;
charsetDecoder = charset.newDecoder();
}
@Override
public int read() throws IOException
{
boolean middleOfReading = false;
while (true)
{
int b = inputStream.read();
if (b == -1)
{
if (middleOfReading)
throw new IOException("Unexpected end of stream, byte truncated");
return -1;
}
byteBuffer.clear();
byteBuffer.put((byte)b);
byteBuffer.flip();
CharBuffer charBuffer = charsetDecoder.decode(byteBuffer);
// although this is theoretically possible this would violate the unbuffered nature
// of this class so we throw an exception
if (charBuffer.length() > 1)
throw new IOException("Decoded multiple characters from one byte!");
if (charBuffer.length() == 1)
return charBuffer.get();
middleOfReading = true;
}
}
public int read(char[] cbuf, int off, int len) throws IOException
{
for (int i = 0; i < len; i++)
{
int ch = read();
if (ch == -1)
return i == 0 ? -1 : i;
cbuf[ i ] = (char)ch;
}
return len;
}
public void close() throws IOException
{
inputStream.close();
}
}
Peut-être que je me trompe, mais depuis le moment où je pensais que ce fichier peut avoir qu'un seul type de codage en même temps. – Roman
@Roman: Vous pouvez faire tout ce que vous voulez avec des fichiers; ce ne sont que des séquences d'octets. Vous pouvez donc écrire un tas d'octets qui sont censés être interprétés comme ASCII, puis écrire un paquet plus d'octets censés être interprétés comme UTF-16, et encore plus d'octets censés être interprétés comme UTF-32. Je ne dis pas que c'est une bonne idée, bien que le cas d'utilisation de l'OP soit certainement raisonnable (vous devez avoir une * certaine * façon d'indiquer ce que l'encodage d'un fichier utilise, après tout). –
@Mike Q - Bonne idée de InputStreamReaderUnbuffered. Je suggère une réponse séparée - elle mérite l'attention :) –