【问题标题】:How can I replace strings in a text in Java?如何在 Java 中替换文本中的字符串?
【发布时间】:2008-12-19 14:15:13
【问题描述】:

在一个项目中,我们的文本文件如下所示:

mv A, R3
mv R2, B
mv R1, R3
mv B, R4
add A, R1
add B, R1
add R1, R2
add R3, R3
add R21, X
add R12, Y
mv X, R2

我需要根据以下替换字符串,但我正在寻找 寻求更通用的解决方案。

R1  => R2
R2  => R3
R3  => R1
R12 => R21
R21 => R12

我知道我可以在 Perl 中做到这一点,下面代码中的 replace() 函数, 但是真正的应用程序是用Java编写的,所以解决方案需要是 在 Java 中也是如此。

#!/usr/bin/perl
use strict;
use warnings;

use File::Slurp qw(read_file write_file);


my %map = (
    R1  => 'R2',
    R2  => 'R3',
    R3  => 'R1',
    R12 => 'R21',
    R21 => 'R12',
);

replace(\%map, \@ARGV);

sub replace {
    my ($map, $files) = @_;

    # Create R12|R21|R1|R2|R3
    # making sure R12 is before R1
    my $regex = join "|",
                sort { length($b) <=> length($a) }
                keys %$map;

    my $ts = time;

    foreach my $file (@$files) {
        my $data = read_file($file);
        $data =~ s/\b($regex)\b/$map{$1}/g;
        rename $file, "$file.$ts";       # backup with current timestamp
        write_file( $file, $data);
    }
}

您对 Java 实现的帮助将不胜感激。

【问题讨论】:

  • 为什么在制作正则表达式之前对键进行排序?
  • 所以 R12 将在 R1 之前匹配(否则 R12 将永远不会匹配),现在代码中也添加了注释。
  • 对它们进行排序并不是真正的解决方案;如果文本包含“R13”,它将更改为“R23”。您希望正则表达式与您告诉它的内容相匹配,仅此而已。在这种情况下,单词边界会这样做: /\b(R1|R2|R3|R12|R21)\b/
  • 你是对的,在正则表达式周围添加 \b 将确保应该单独保留的 R122 与 R12 不匹配并更改为 R212

标签: java regex perl


【解决方案1】:

在过去的两周里,我实际上不得不多次使用这种算法。所以这里是世界上第二冗长的语言......

import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/*
R1  => R2
R2  => R3
R3  => R1
R12 => R21
R21 => R12
*/

String inputString 
    = "mv A, R3\n"
    + "mv R2, B\n"
    + "mv R1, R3\n"
    + "mv B, R4\n"
    + "add A, R1\n"
    + "add B, R1\n"
    + "add R1, R2\n"
    + "add R3, R3\n"
    + "add R21, X\n"
    + "add R12, Y\n"
    + "mv X, R2"
    ;

System.out.println( "inputString = \"" + inputString + "\"" );

HashMap h = new HashMap();
h.put( "R1",  "R2" );
h.put( "R2",  "R3" );
h.put( "R3",  "R1" );
h.put( "R12", "R21" );
h.put( "R21", "R12" );

Pattern      p       = Pattern.compile( "\\b(R(?:12?|21?|3))\\b");
Matcher      m       = p.matcher( inputString );
StringBuffer sbuff   = new StringBuffer();
int          lastEnd = 0;
while ( m.find()) {
    int mstart = m.start();
    if ( lastEnd < mstart ) { 
        sbuff.append( inputString.substring( lastEnd, mstart ));
    }
    String key   = m.group( 1 );
    String value = (String)h.get( key );
    sbuff.append( value );
    lastEnd = m.end();
}
if ( lastEnd < inputString.length() ) { 
    sbuff.append( inputString.substring( lastEnd ));
}

System.out.println( "sbuff = \"" + sbuff + "\"" );

这可以通过这些类进行 Java 化:

import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

interface StringReplacer { 
    public CharSequence getReplacement( Matcher matcher );
}

class Replacementifier { 

    static Comparator keyComparator = new Comparator() { 
         public int compare( Object o1, Object o2 ) {
             String s1   = (String)o1;
             String s2   = (String)o2;
             int    diff = s1.length() - s2.length();
             return diff != 0 ? diff : s1.compareTo( s2 );
         }
    };
    Map replaceMap = null;

    public Replacementifier( Map aMap ) { 
        if ( aMap != null ) { 
            setReplacements( aMap ); 
        }
    }

    public setReplacements( Map aMap ) { 
        replaceMap = aMap;
    }

    private static String createKeyExpression( Map m ) { 
        Set          set = new TreeSet( keyComparator );
        set.addAll( m.keySet());
        Iterator     sit = set.iterator();
        StringBuffer sb  = new StringBuffer( "(" + sit.next());

        while ( sit.hasNext()) { 
            sb.append( "|" ).append( sit.next());
        }
        sb.append( ")" );
        return sb.toString();
    }

    public String replace( Pattern pattern, CharSequence input, StringReplacer replaceFilter ) {
        StringBuffer output  = new StringBuffer();
        Matcher      matcher = pattern.matcher( inputString );
        int          lastEnd = 0;
        while ( matcher.find()) {
            int mstart = matcher.start();
            if ( lastEnd < mstart ) { 
                output.append( inputString.substring( lastEnd, mstart ));
            }
            CharSequence cs = replaceFilter.getReplacement( matcher );
            if ( cs != null ) { 
                output.append( cs );
            }
            lastEnd = matcher.end();
        }
        if ( lastEnd < inputString.length() ) { 
            sbuff.append( inputString.substring( lastEnd ));
        }
    }

    public String replace( Map rMap, CharSequence input ) {
        // pre-condition
        if ( rMap == null && replaceMap == null ) return input;

        Map     repMap = rMap != null ? rMap : replaceMap;
        Pattern pattern  
            = Pattern.compile( createKeyExpression( repMap ))
            ;
        StringReplacer replacer = new StringReplacer() { 
            public CharSequence getReplacement( Matcher matcher ) {
                String key   = matcher.group( 1 );
                return (String)repMap.get( key );
            }
        };
        return replace( pattern, input, replacer ); 
    }
}

【讨论】:

  • 有理由不使用泛型吗? (我可以自己添加它们,但毕竟这是你的答案。)
  • 没有理由。我只是不这样做,因为我们的大部分代码都是在 Java 5 之前维护的。
【解决方案2】:

perl 解决方案的优点是一次性替换所有字符串,有点“事务性”。如果您在 Java 中没有相同的选项(而且我想不出办法),您需要小心替换 R1=>R2,然后 R2=>R3。在这种情况下,R1 和 R2 最终都会被 R3 替换。

【讨论】:

    【解决方案3】:

    使用 Matcher 的低级 API,这是一次性完成此任务的一种不太冗长的方法:appendReplacement()appendTail()

    import java.util.*;
    import java.util.regex.*;
    
    public class Test
    {
      public static void main(String[] args) throws Exception
      {
        String inputString 
          = "mv A, R3\n"
          + "mv R2, B\n"
          + "mv R1, R3\n"
          + "mv B, R4\n"
          + "add A, R1\n"
          + "add B, R1\n"
          + "add R1, R2\n"
          + "add R3, R3\n"
          + "add R21, X\n"
          + "add R12, Y\n"
          + "mv X, R2"
          ;
    
          System.out.println(inputString);
          System.out.println();
          System.out.println(doReplace(inputString));
      }
    
      public static String doReplace(String str)
      {
         Map<String, String> map = new HashMap<String, String>()
         {{
            put("R1", "R2");
            put("R2", "R3");
            put("R3", "R1");
            put("R12", "R21");
            put("R21", "R12");
         }};
    
         Pattern p = Pattern.compile("\\bR\\d\\d?\\b");
         Matcher m = p.matcher(str);
         StringBuffer sb = new StringBuffer();
         while (m.find())
         {
           String repl = map.get(m.group());
           if (repl != null) 
           {
             m.appendReplacement(sb, "");
             sb.append(repl);
           }
         }
         m.appendTail(sb);
         return sb.toString();
      }
    }
    

    请注意,appendReplacement() 处理替换字符串以将 $n 序列替换为来自捕获组的文本,在这种情况下我们不希望这样做。为了避免这种情况,我给它传递了一个空字符串,然后改用 StringBuffer 的append() 方法。

    Elliott Hughes 已发布了该技术的预打包实现here。 (他倾向于引用他编写的其他实用程序类,因此您可能需要在编译之前删除他的main() 方法中的测试。)

    【讨论】:

      【解决方案4】:

      我的建议是在读取文件本身时替换字符串 您可以使用 RandomAccessFile。在逐个字符读取文件时, 您实际上可以检查模式,然后自行进行替换。然后您可以一次将所有内容写入文件。我认为这会为您节省更多时间。

      【讨论】:

      • 这是回答问题的良好开端,但展示您所描述内容的简单示例会改进它。
      【解决方案5】:

      您可以使用 HashMap:

      Map<String, String> map = new HashMap<String, String>();
      map.put("R1", "R2");
      map.put("R2", "R3");
      
      for(String key: map.keySet()) {
        str.replaceAll(key, map.get(key));
      }
      

      replaceAll 也处理正则表达式。

      编辑:正如许多人指出的那样,上述解决方案不起作用,因为它不处理循环替换。所以这是我的第二种方法:

      public class Replacement {
      
          private String newS;
          private String old;
      
          public Replacement(String old, String newS) {
              this.newS = newS;
              this.old = old;
          }
      
          public String getOld() {
              return old;
          }
      
          public String getNew() {
              return newS;
          }
      }
      
      SortedMap<Integer, Replacement> map = new TreeMap<Integer, Replacement>();
      
      map.put(new Integer(1), new Replacement("R2", "R3"));
      map.put(new Integer(2), new Replacement("R1", "R2"));
      
      for(Integer key: map.keySet()) {
         str.replaceAll(map.get(key).getOld(), map.get(key).getNew());
      }
      

      只要您正确订购替换件并防止循环替换件,此方法就有效。有些替换是不可能的:

      R1 -> R2
      R2 -> R3
      R3 -> R1
      

      您必须为这些使用一些“临时”变量:

      R1 -> R@1
      R2 -> R@3
      R3 -> R1
      R@(\d{1}) -> R\1
      

      您可以编写一个库来为您完成所有这些工作。

      【讨论】:

      • 因为 replaceAll() 处理正则表达式,引用它的参数可能是个好主意。
      • 这个解决方案肯定行不通吗?您的第一次迭代将始终将“R1”替换为“R2”,然后您的第二次迭代将始终将“R2”替换为“R3”,包括最初为“R1”的值 - 结果是 R1、R2 和R3 将 *all 最后显示为 R3。
      • 而且由于地图上没有订购保证,这可能发生也可能不会发生,让它变得更加有趣......
      • @Adrian:我和你在一起——我怀疑这个问题中的循环替换是否有效。
      • 另一个问题:Java 字符串是不可变的,所以 str.replaceAll() 创建了一个新字符串,但你正在丢弃它。
      猜你喜欢
      • 1970-01-01
      • 2010-11-17
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      • 2010-12-27
      • 1970-01-01
      • 2021-02-14
      • 2014-06-21
      相关资源
      最近更新 更多