Java:替换流、数组、文件等中的字符串。

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
有时,您需要替换流、数组、文件或大字符串中的字符串或令牌。

您可以使用String.replace()方法,但对于大量数据和大量替换,这表现不佳。为什么?

String.replace()方法创建一个新的String实例——它是应用替换后的原始String的副本。如果String的大小为1mb,那么最终将得到两个各为1mb的字符串。如果你必须执行5次替换,你必须调用replace() 5次,每次都是在最后一个replace()调用返回的字符串上,像这样:

image.png

结果将是原始字符串的5个副本和原始数据的总内存消耗量的5倍。你可以想象,这种方法性能很差,而且扩展效果不太好。使用String.replace()方法的O符号是:

O(N * M)
复制代码

...其中N =字符串的大小,M = 要执行的替换数。

令牌重现阅读器

我将在这里提出一种名为TokenReplacingReader的不同、更可扩展的解决方案,而不是使用String.replace()方法。首先,我将解释它在理论上是如何工作的,然后在本文末尾向您提供工作代码。

TokenReplacingReader从标准java.io.Reader读取字符数据。

然后,您的应用程序通过TokenReplacingReader读取数据。您的应用程序从TokenReplacingReader读取的数据将是TokenReplacingReader使用的从Reader读取的数据,所有令牌都被新值替换。如果您需要将数据写入磁盘或某些输出流,您的应用程序必须自己编写。

TokenReplacingReader在表单${tokenName}的数据中找到令牌时,它会调用anITokenResolver以获取要插入字符流而不是令牌的值。

ITokenResolver是一个您可以自己实现的接口。因此,您自己的令牌解析器可以从适合您的应用程序的任何地方查找令牌值——Map、数据库、JNDI目录等。令牌名称(不含附件${})将传递给ITokenResolver.resolveToken(String tokenName)方法。

TokenReplacingReader本身就是javajava.io.Reader的子类,因此任何可以使用aReaderReader类都可以使用TokenReplacingReader

下面的图表显示了“TokenReplacingReader”是如何工作的:

image.png

令牌复制阅读器使用示例

以下是如何使用TokenReplacingReader示例:

image.png

输入String中的两个令牌

token1{token1}和

{token2}将被value1和JJ ROCKS!!这些值由MapTokenResolver返回(ITokenResolver实现通过在Map中查找来解析值)。

以下是一些其他示例,演示如何使用TokenReplacingReader替换字符流、数组、文件和大字符串中的令牌。

image.png

TokenReplacingReader性能

TokenReplacingReader使用的内存不如String.replace()方法多。数据在读取时被修改,因此所有数据都会复制一次(但不再复制)。由于数据是逐个字符复制的,因此内存消耗量不会比您正在读取的数据的缓冲区/流大多少。

令牌替换的速度取决于您对ITokenResolver接口的实现。

TokenReplacingReaderO符号是:

O(N + M)
复制代码

...其中N是要替换令牌的数据大小,M是替换的数量。

这比String.replace()方法的O(N * M)更快。

更多用途

您可以创建TokenReplacingReader的变体,该变体可以将XML实体(例如&)替换为单个字符值。或者创建一个类似于脚本的小语言作为令牌,它可以在令牌中获取参数,调用可重用函数等。只有想象力才能为您可以使用这种令牌替换机制设置限制。

此外,由于TokenReplacingReader是java.io。Reader,它从Reader本身获取字符,你可以用其他java.io链接它。Reader或InputStreams做其他事情(如解压缩、解密、从UTF-8、UTF-16转换等)

令牌复制阅读器代码

这是TokenReplacingReader及其接口ITokenResolver的代码。

注意:
并非所有方法都实现了。仅足以向您展示TokenReplacingReader的工作原理。您可以自己实施其余部分(如果您需要的话)。

public class TokenReplacingReader extends Reader {

  protected PushbackReader pushbackReader   = null;
  protected ITokenResolver tokenResolver    = null;
  protected StringBuilder  tokenNameBuffer  = new StringBuilder();
  protected String         tokenValue       = null;
  protected int            tokenValueIndex  = 0;

  public TokenReplacingReader(Reader source, ITokenResolver resolver) {
    this.pushbackReader = new PushbackReader(source, 2);
    this.tokenResolver  = resolver;
  }

  public int read(CharBuffer target) throws IOException {
    throw new RuntimeException("Operation Not Supported");
  }

  public int read() throws IOException {
    if(this.tokenValue != null){
      if(this.tokenValueIndex < this.tokenValue.length()){
        return this.tokenValue.charAt(this.tokenValueIndex++);
      }
      if(this.tokenValueIndex == this.tokenValue.length()){
        this.tokenValue = null;
        this.tokenValueIndex = 0;
      }
    }

    int data = this.pushbackReader.read();
    if(data != '$') return data;

    data = this.pushbackReader.read();
    if(data != '{'){
      this.pushbackReader.unread(data);
      return '$';
    }
    this.tokenNameBuffer.delete(0, this.tokenNameBuffer.length());

    data = this.pushbackReader.read();
    while(data != '}'){
      this.tokenNameBuffer.append((char) data);
      data = this.pushbackReader.read();
    }

    this.tokenValue = this.tokenResolver
      .resolveToken(this.tokenNameBuffer.toString());

    if(this.tokenValue == null){
      this.tokenValue = "${"+ this.tokenNameBuffer.toString() + "}";
    }
    if(this.tokenValue.length() == 0){
        return read();
    }
    return this.tokenValue.charAt(this.tokenValueIndex++);


  }

  public int read(char cbuf[]) throws IOException {
    return read(cbuf, 0, cbuf.length);
  }

  public int read(char cbuf[], int off, int len) throws IOException {
    int charsRead = 0;
    for(int i=0; i<len; i++){
        int nextChar = read();
        if(nextChar == -1) {
            if(charsRead == 0){
                charsRead = -1;
            }
            break;
        }
        charsRead = i + 1;
        cbuf[off + i] = (char) nextChar;
      }
    return charsRead;
  }

  public void close() throws IOException {
    this.pushbackReader.close();
  }

  public long skip(long n) throws IOException {
    throw new RuntimeException("Operation Not Supported");
  }

  public boolean ready() throws IOException {
    return this.pushbackReader.ready();
  }

  public boolean markSupported() {
    return false;
  }

  public void mark(int readAheadLimit) throws IOException {
    throw new RuntimeException("Operation Not Supported");
  }

  public void reset() throws IOException {
    throw new RuntimeException("Operation Not Supported");
  }
}
复制代码

image.png

以下是在Map中查找令牌值的ITokenResolver实现示例。

image.png