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?
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.
}
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
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;
}
}
L'approche suivante fonctionnera mais je ne sais pas la taille de l'impact sur la performance.
InputStream
avec un InputStreamReader
,InputStreamReader
avec un FilterReader
qui remplace les chaînes, puisFilterReader
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.
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.
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]);
}
}
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;
}
}