web-dev-qa-db-fra.com

Filtre (recherche et remplacement) Array d'octets dans une intrigue

J'ai une intrigue qui prend le fichier HTML comme paramètre d'entrée. Je dois obtenir les octets du flux d'entrée.

J'ai une chaîne: "XYZ". J'aimerais convertir cette chaîne au format d'octet et vérifier si la chaîne correspond à la chaîne de la séquence d'octets que j'ai obtenue à partir de l'introuvable. S'il y a alors, je dois remplacer la correspondance avec la séquence bye pour une autre chaîne.

Y a-t-il quelqu'un qui pourrait m'aider avec ça? J'ai utilisé Regex pour trouver et remplacer. Cependant, trouver et remplacer le flux d'octets, je ne suis pas au courant.

Auparavant, j'utilise JSOUP pour analyser HTML et remplacer la chaîne, cependant en raison de certains problèmes d'encodage UTF, le fichier semble apparaître corrompu lorsque je le fais.

TL; DR: Ma question est la suivante :

Est un moyen de trouver et de remplacer une chaîne au format d'octet dans une intrusion crue dans Java?

20
user471450

Je ne sais pas que vous avez choisi la meilleure approche pour résoudre votre problème.

Cela dit, je n'aime pas (et avoir la politique à ne pas dire) répondre aux questions avec "ne" pas "alors voilà ...

Regardez - FilterInputStream .

De la documentation:

Un filtreInputStream contient un autre flux d'entrée, qu'il utilise comme source de base de données transformant éventuellement la transformation des données en cours de route ou en fournissant des fonctionnalités supplémentaires.


C'était un exercice amusant de l'écrire. Voici un exemple complet pour vous:

import Java.io.*;
import Java.util.*;

class ReplacingInputStream extends FilterInputStream {

    LinkedList<Integer> inQueue = new LinkedList<Integer>();
    LinkedList<Integer> outQueue = new LinkedList<Integer>();
    final byte[] search, replacement;

    protected ReplacingInputStream(InputStream in,
                                   byte[] search,
                                   byte[] replacement) {
        super(in);
        this.search = search;
        this.replacement = replacement;
    }

    private boolean isMatchFound() {
        Iterator<Integer> inIter = inQueue.iterator();
        for (int i = 0; i < search.length; i++)
            if (!inIter.hasNext() || search[i] != inIter.next())
                return false;
        return true;
    }

    private void readAhead() throws IOException {
        // Work up some look-ahead.
        while (inQueue.size() < search.length) {
            int next = super.read();
            inQueue.offer(next);
            if (next == -1)
                break;
        }
    }

    @Override
    public int read() throws IOException {    
        // Next byte already determined.
        if (outQueue.isEmpty()) {
            readAhead();

            if (isMatchFound()) {
                for (int i = 0; i < search.length; i++)
                    inQueue.remove();

                for (byte b : replacement)
                    outQueue.offer((int) b);
            } else
                outQueue.add(inQueue.remove());
        }

        return outQueue.remove();
    }

    // TODO: Override the other read methods.
}

Exemple d'utilisation

class Test {
    public static void main(String[] args) throws Exception {

        byte[] bytes = "hello xyz world.".getBytes("UTF-8");

        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);

        byte[] search = "xyz".getBytes("UTF-8");
        byte[] replacement = "abc".getBytes("UTF-8");

        InputStream ris = new ReplacingInputStream(bis, search, replacement);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int b;
        while (-1 != (b = ris.read()))
            bos.write(b);

        System.out.println(new String(bos.toByteArray()));

    }
}

Compte tenu des octets pour la chaîne "Hello xyz world" Il imprime:

Hello abc world
29
aioobe

J'avais besoin de quelque chose comme ça aussi et a décidé de rouler ma propre solution au lieu d'utiliser l'exemple ci-dessus par @aioobe. Regardez le code . Vous pouvez tirer la bibliothèque de Maven Central ou simplement copier le code source.

Voici comment vous l'utilisez. Dans ce cas, j'utilise une instance imbriquée pour remplacer deux motifs deux fosses FIX DOS et MAC.

new ReplacingInputStream(new ReplacingInputStream(is, "\n\r", "\n"), "\r", "\n");

Voici le code source complet:

/**
 * Simple FilterInputStream that can replace occurrances of bytes with something else.
 */
public class ReplacingInputStream extends FilterInputStream {

    // while matching, this is where the bytes go.
    int[] buf=null;
    int matchedIndex=0;
    int unbufferIndex=0;
    int replacedIndex=0;

    private final byte[] pattern;
    private final byte[] replacement;
    private State state=State.NOT_MATCHED;

    // simple state machine for keeping track of what we are doing
    private enum State {
        NOT_MATCHED,
        MATCHING,
        REPLACING,
        UNBUFFER
    }

    /**
     * @param is input
     * @return nested replacing stream that replaces \n\r (DOS) and \r (MAC) line endings with UNIX ones "\n".
     */
    public static InputStream newLineNormalizingInputStream(InputStream is) {
        return new ReplacingInputStream(new ReplacingInputStream(is, "\n\r", "\n"), "\r", "\n");
    }

    /**
     * Replace occurances of pattern in the input. Note: input is assumed to be UTF-8 encoded. If not the case use byte[] based pattern and replacement.
     * @param in input
     * @param pattern pattern to replace.
     * @param replacement the replacement or null
     */
    public ReplacingInputStream(InputStream in, String pattern, String replacement) {
        this(in,pattern.getBytes(StandardCharsets.UTF_8), replacement==null ? null : replacement.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * Replace occurances of pattern in the input.
     * @param in input
     * @param pattern pattern to replace
     * @param replacement the replacement or null
     */
    public ReplacingInputStream(InputStream in, byte[] pattern, byte[] replacement) {
        super(in);
        Validate.notNull(pattern);
        Validate.isTrue(pattern.length>0, "pattern length should be > 0", pattern.length);
        this.pattern = pattern;
        this.replacement = replacement;
        // we will never match more than the pattern length
        buf = new int[pattern.length];
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        // copy of parent logic; we need to call our own read() instead of super.read(), which delegates instead of calling our read
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;

    }

    @Override
    public int read(byte[] b) throws IOException {
        // call our own read
        return read(b, 0, b.length);
    }

    @Override
    public int read() throws IOException {
        // use a simple state machine to figure out what we are doing
        int next;
        switch (state) {
        case NOT_MATCHED:
            // we are not currently matching, replacing, or unbuffering
            next=super.read();
            if(pattern[0] == next) {
                // clear whatever was there
                buf=new int[pattern.length]; // clear whatever was there
                // make sure we start at 0
                matchedIndex=0;

                buf[matchedIndex++]=next;
                if(pattern.length == 1) {
                    // edgecase when the pattern length is 1 we go straight to replacing
                    state=State.REPLACING;
                    // reset replace counter
                    replacedIndex=0;
                } else {
                    // pattern of length 1
                    state=State.MATCHING;
                }
                // recurse to continue matching
                return read();
            } else {
                return next;
            }
        case MATCHING:
            // the previous bytes matched part of the pattern
            next=super.read();
            if(pattern[matchedIndex]==next) {
                buf[matchedIndex++]=next;
                if(matchedIndex==pattern.length) {
                    // we've found a full match!
                    if(replacement==null || replacement.length==0) {
                        // the replacement is empty, go straight to NOT_MATCHED
                        state=State.NOT_MATCHED;
                        matchedIndex=0;
                    } else {
                        // start replacing
                        state=State.REPLACING;
                        replacedIndex=0;
                    }
                }
            } else {
                // mismatch -> unbuffer
                buf[matchedIndex++]=next;
                state=State.UNBUFFER;
                unbufferIndex=0;
            }
            return read();
        case REPLACING:
            // we've fully matched the pattern and are returning bytes from the replacement
            next=replacement[replacedIndex++];
            if(replacedIndex==replacement.length) {
                state=State.NOT_MATCHED;
                replacedIndex=0;
            }
            return next;
        case UNBUFFER:
            // we partially matched the pattern before encountering a non matching byte
            // we need to serve up the buffered bytes before we go back to NOT_MATCHED
            next=buf[unbufferIndex++];
            if(unbufferIndex==matchedIndex) {
                state=State.NOT_MATCHED;
                matchedIndex=0;
            }
            return next;

        default:
            throw new IllegalStateException("no such state " + state);
        }
    }

    @Override
    public String toString() {
        return state.name() + " " + matchedIndex + " " + replacedIndex + " " + unbufferIndex;
    }

}
4
Jilles van Gurp

L'approche suivante fonctionnera mais je ne sais pas la taille de l'impact sur la performance.

  1. Enveloppez le InputStream avec un InputStreamReader,
  2. enveloppez le InputStreamReader avec un FilterReader qui remplace les chaînes, puis
  3. enveloppez le FilterReader avec un ReaderInputStream.

Il est crucial de choisir le codage approprié, sinon le contenu du flux sera corrompu.

Si vous souhaitez utiliser des expressions régulières pour remplacer les chaînes, vous pouvez utiliser streamflyer , un outil de mien, une alternative pratique à FilterReader. Vous trouverez un exemple pour les flux d'octets sur la page Web de Streamflyer. J'espère que cela t'aides.

4
rwitzel

Il n'y a pas de fonctionnalité intégrée pour la recherche-and-remplacer sur des flux d'octets (InputStream).

Et, une méthode pour compléter cette tâche efficacement et correctement n'est pas immédiatement évidente. J'ai mis en place l'algorithme Boyer-Moore pour les ruisseaux et cela fonctionne bien, mais cela a pris un certain temps. Sans algorithme comme celui-ci, vous devez recourir à une approche de force brute où vous recherchez le motif à partir de chaque position du flux, qui peut être lent.

Même si vous décodez le code HTML comme texte, L'utilisation d'une expression régulière pour correspondre aux motifs peut être une mauvaise idée, car HTML n'est pas une langue "régulière".

Donc, même si vous avez rencontré des difficultés, je vous suggère de poursuivre votre approche initiale de l'analyse du HTML en tant que document. Pendant que vous rencontrez des problèmes avec le codage du personnage, il sera probablement plus facile, à long terme, de résoudre la solution appropriée à la longue solution que ce sera de la mauvaise solution de jury.

2
erickson

J'ai proposé cette simple pièce de code lorsque je devais servir un fichier de modèle dans un servlet remplaçant un certain mot clé par une valeur. Il devrait être assez rapide et bas sur la mémoire. Ensuite, utilisez des flux de canette, je suppose que vous pouvez l'utiliser pour toutes sortes de choses.

/ Jc

public static void replaceStream(InputStream in, OutputStream out, String search, String replace) throws IOException
{
    replaceStream(new InputStreamReader(in), new OutputStreamWriter(out), search, replace);
}

public static void replaceStream(Reader in, Writer out, String search, String replace) throws IOException
{
    char[] searchChars = search.toCharArray();
    int[] buffer = new int[searchChars.length];

    int x, r, si = 0, sm = searchChars.length;
    while ((r = in.read()) > 0) {

        if (searchChars[si] == r) {
            // The char matches our pattern
            buffer[si++] = r;

            if (si == sm) {
                // We have reached a matching string
                out.write(replace);
                si = 0;
            }
        } else if (si > 0) {
            // No match and buffered char(s), empty buffer and pass the char forward
            for (x = 0; x < si; x++) {
                out.write(buffer[x]);
            }
            si = 0;
            out.write(r);
        } else {
            // No match and nothing buffered, just pass the char forward
            out.write(r);
        }
    }

    // Empty buffer
    for (x = 0; x < si; x++) {
        out.write(buffer[x]);
    }
}
1
J.C

J'avais besoin d'une solution à cela, mais j'ai trouvé les réponses ci-dessus dans la mémoire de la mémoire et/ou de la CPU. La solution ci-dessous surpasse de manière significative les autres ici en ces termes en fonction de l'analyse comparative simple.

Cette solution est particulièrement efficace en matière de mémoire, n'entrant aucun coût mesurable même avec> gb flux.

Cela dit, il ne s'agit pas d'une solution zéro-CPU-COÛT. Les frais généraux de la CPU/Temps-Time sont probablement raisonnables pour tous les scénarios les plus exigeants/sensibles aux ressources, mais les frais généraux sont réels et doivent être pris en compte lors de l'évaluation de la valeur de l'emploi de cette solution dans un contexte donné.

Dans mon cas, notre taille maximale du fichier du monde réel que nous traitons est d'environ 6 Mo, où nous voyons une latence ajoutée d'environ 170 ms avec des remplacements de 44 URL. Ceci est destiné à un proxy inverse basé sur ZUUU sur AWS ECS avec une seule partager CPU (1024). Pour la plupart des fichiers (moins de 100 ko), la latence ajoutée est sous-milliseconde. Sous la concurrence élevée (et donc la Convention de la CPU), la latence ajoutée pourrait augmenter, mais nous sommes actuellement en mesure de traiter des centaines de fichiers simultanément sur un seul nœud sans impact de la latence humainement notable.

La solution que nous utilisons:

import Java.io.IOException;
import Java.io.InputStream;

public class TokenReplacingStream extends InputStream {

    private final InputStream source;
    private final byte[] oldBytes;
    private final byte[] newBytes;
    private int tokenMatchIndex = 0;
    private int bytesIndex = 0;
    private boolean unwinding;
    private int mismatch;
    private int numberOfTokensReplaced = 0;

    public TokenReplacingStream(InputStream source, byte[] oldBytes, byte[] newBytes) {
        assert oldBytes.length > 0;
        this.source = source;
        this.oldBytes = oldBytes;
        this.newBytes = newBytes;
    }

    @Override
    public int read() throws IOException {

        if (unwinding) {
            if (bytesIndex < tokenMatchIndex) {
                return oldBytes[bytesIndex++];
            } else {
                bytesIndex = 0;
                tokenMatchIndex = 0;
                unwinding = false;
                return mismatch;
            }
        } else if (tokenMatchIndex == oldBytes.length) {
            if (bytesIndex == newBytes.length) {
                bytesIndex = 0;
                tokenMatchIndex = 0;
                numberOfTokensReplaced++;
            } else {
                return newBytes[bytesIndex++];
            }
        }

        int b = source.read();
        if (b == oldBytes[tokenMatchIndex]) {
            tokenMatchIndex++;
        } else if (tokenMatchIndex > 0) {
            mismatch = b;
            unwinding = true;
        } else {
            return b;
        }

        return read();

    }

    @Override
    public void close() throws IOException {
        source.close();
    }

    public int getNumberOfTokensReplaced() {
        return numberOfTokensReplaced;
    }

}
1
rees