43

我需要一个 java 中的比较器,它与 sql 'like' 运算符具有相同的语义。例如:

myComparator.like("digital","%ital%");
myComparator.like("digital","%gi?a%");
myComparator.like("digital","digi%");

应该评估为真,并且

myComparator.like("digital","%cam%");
myComparator.like("digital","tal%");

应该评估为假。有什么想法如何实现这样的比较器,或者有人知道具有相同语义的实现吗?这可以使用正则表达式来完成吗?

4

17 回答 17

36

.* 将匹配正则表达式中的任何字符

我认为java语法是

"digital".matches(".*ital.*");

对于单个字符匹配,只需使用一个点。

"digital".matches(".*gi.a.*");

并匹配一个实际的点,将其转义为斜线点

\.
于 2009-05-22T15:16:15.007 回答
24

是的,这可以通过正则表达式来完成。请记住,Java 的正则表达式与 SQL 的“like”有不同的语法。不是“ %”,而是“ .*”,而不是“ ?”,而是“ .”。

有点棘手的是,您还必须转义 Java 视为特殊的任何字符。由于您试图使其类似于 SQL,我猜它^$[]{}\不应该出现在正则表达式字符串中。但是在进行任何其他替换之前,您必须将“ .”替换为“”。\\.编辑: 通过用“ ”和“ ”Pattern.quote(String)包围字符串来转义所有内容,这将导致表达式中的所有内容都被视为文字(根本没有通配符)。所以你绝对不想使用它。)\Q\E

此外,正如 Dave Webb 所说,您还需要忽略大小写。

考虑到这一点,以下是它的示例:

public static boolean like(String str, String expr) {
    expr = expr.toLowerCase(); // ignoring locale for now
    expr = expr.replace(".", "\\."); // "\\" is escaped to "\" (thanks, Alan M)
    // ... escape any other potentially problematic characters here
    expr = expr.replace("?", ".");
    expr = expr.replace("%", ".*");
    str = str.toLowerCase();
    return str.matches(expr);
}
于 2009-05-22T15:26:02.740 回答
21

正则表达式是最通用的。但是,一些 LIKE 函数可以在没有正则表达式的情况下形成。例如

String text = "digital";
text.startsWith("dig"); // like "dig%"
text.endsWith("tal"); // like "%tal"
text.contains("gita"); // like "%gita%"
于 2009-07-19T13:30:41.510 回答
14

我能找到的每个 SQL 参考都说“任何单个字符”通配符是下划线 ( _),而不是问号 ( ?)。这稍微简化了一些事情,因为下划线不是正则表达式元字符。Pattern.quote()但是,由于mmyers给出的原因,您仍然无法使用。当我以后可能想编辑正则表达式时,我有另一种方法来转义正则表达式。有了这些,like()方法变得非常简单:

public static boolean like(final String str, final String expr)
{
  String regex = quotemeta(expr);
  regex = regex.replace("_", ".").replace("%", ".*?");
  Pattern p = Pattern.compile(regex,
      Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
  return p.matcher(str).matches();
}

public static String quotemeta(String s)
{
  if (s == null)
  {
    throw new IllegalArgumentException("String cannot be null");
  }

  int len = s.length();
  if (len == 0)
  {
    return "";
  }

  StringBuilder sb = new StringBuilder(len * 2);
  for (int i = 0; i < len; i++)
  {
    char c = s.charAt(i);
    if ("[](){}.*+?$^|#\\".indexOf(c) != -1)
    {
      sb.append("\\");
    }
    sb.append(c);
  }
  return sb.toString();
}

如果您真的想使用?通配符,最好的办法是将其从quotemeta()方法中的元字符列表中删除。替换其转义形式 -- replace("\\?", ".")-- 并不安全,因为原始表达式中可能存在反斜杠。

这给我们带来了真正的问题:大多数 SQL 风格似乎都支持[a-z][^j-m]or形式的字符类[!j-m],它们都提供了一种转义通配符的方法。后者通常通过ESCAPE关键字来完成,它允许您每次定义不同的转义字符。可以想象,这使事情变得相当复杂。转换为正则表达式可能仍然是最好的选择,但解析原始表达式会困难得多——事实上,您要做的第一件事就是将LIKE-like 表达式本身的语法形式化。

于 2009-07-20T11:32:23.580 回答
7

要在 java 中实现 sql 的 LIKE 函数,您不需要正则表达式它们可以如下获得:

String text = "apple";
text.startsWith("app"); // like "app%"
text.endsWith("le"); // like "%le"
text.contains("ppl"); // like "%ppl%"
于 2017-06-24T04:49:32.693 回答
3

Java 字符串具有 .startsWith() 和 .contains() 方法,它们可以帮助您完成大部分工作。对于更复杂的事情,您必须使用正则表达式或编写自己的方法。

于 2009-05-22T15:18:00.993 回答
3

你可以'%string%'转向contains()'string%'转向startsWith()'%string"'转向endsWith()

您还应该在不区分大小写的情况下toLowerCase()同时在字符串和模式上运行LIKE

不知道你会如何处理'%string%other%',除非使用正则表达式。

如果您使用正则表达式:

于 2009-05-22T15:20:08.930 回答
3
public static boolean like(String toBeCompare, String by){
    if(by != null){
        if(toBeCompare != null){
            if(by.startsWith("%") && by.endsWith("%")){
                int index = toBeCompare.toLowerCase().indexOf(by.replace("%", "").toLowerCase());
                if(index < 0){
                    return false;
                } else {
                    return true;
                }
            } else if(by.startsWith("%")){
                return toBeCompare.endsWith(by.replace("%", ""));
            } else if(by.endsWith("%")){
                return toBeCompare.startsWith(by.replace("%", ""));
            } else {
                return toBeCompare.equals(by.replace("%", ""));
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

可能会帮助你

于 2013-01-12T14:31:35.973 回答
2

ComparatorComparable接口在这里可能不适用。它们处理排序,并返回符号或 0 的整数。您的操作是关于查找匹配项并返回真/假。那不一样。

于 2009-08-16T19:11:51.300 回答
2

Apache Cayanne ORM 有一个“内存评估

它可能不适用于未映射的对象,但看起来很有希望:

Expression exp = ExpressionFactory.likeExp("artistName", "A%");   
List startWithA = exp.filterObjects(artists); 
于 2009-07-20T20:30:06.127 回答
2

http://josql.sourceforge.net/有你需要的。寻找 org.josql.expressions.LikeExpression。

于 2011-03-01T19:32:42.633 回答
1

我不完全了解贪婪的问题,但如果它适合你,试试这个:

public boolean like(final String str, String expr)
  {
    final String[] parts = expr.split("%");
    final boolean traillingOp = expr.endsWith("%");
    expr = "";
    for (int i = 0, l = parts.length; i < l; ++i)
    {
      final String[] p = parts[i].split("\\\\\\?");
      if (p.length > 1)
      {
        for (int y = 0, l2 = p.length; y < l2; ++y)
        {
          expr += p[y];
          if (i + 1 < l2) expr += ".";
        }
      }
      else
      {
        expr += parts[i];
      }
      if (i + 1 < l) expr += "%";
    }
    if (traillingOp) expr += "%";
    expr = expr.replace("?", ".");
    expr = expr.replace("%", ".*");
    return str.matches(expr);
}
于 2009-07-19T12:42:53.880 回答
1

我有类似的要求,通过一些修改可能会有所帮助,这里是代码:

package codeSamplesWithoutMaven;

公共类 TestLikeInJava {

public static void main(String[] args) {
    String fromDb = "erick@gmail.com";
    String str1 = "*gmail*";
    String str2 = "*erick";
    String str3 = "*rick";
    String str4 = "*.com";
    String str5 = "erick*";
    String str6 = "ck@gmail*";
    System.out.println(belongsToStringWithWildcards(str1, fromDb));
    System.out.println(belongsToStringWithWildcards(str2, fromDb));
    System.out.println(belongsToStringWithWildcards(str3, fromDb));
    System.out.println(belongsToStringWithWildcards(str4, fromDb));
    System.out.println(belongsToStringWithWildcards(str5, fromDb));
    System.out.println(belongsToStringWithWildcards(str6, fromDb));
}

private static Boolean belongsToStringWithWildcards(String strToTest, String targetStr) {
    Boolean result = Boolean.FALSE;
    int type = 0; //1:start, 2:end, 3:both
    if (strToTest.startsWith("*") && strToTest.endsWith("*")) {
        type = 3;
    } else {
        if (strToTest.startsWith("*")) {
            type = 1;
        } else if (strToTest.endsWith("*")) {
            type = 2;
        }
    }
    System.out.println("strToTest " + strToTest + " into " + targetStr + " type " + type);
    strToTest = strToTest.replaceAll("[*]", "");
    System.out.println("strToTest " + strToTest + " into " + targetStr + " type " + type);
    switch (type) {
        case 1: result = targetStr.endsWith(strToTest);  
                break;
        case 2: result = targetStr.startsWith(strToTest);
                break;
        case 3: result = targetStr.contains(strToTest);
                break;
    }
    return result;
}

}

于 2022-02-22T17:53:29.357 回答
0

查看https://github.com/hrakaroo/glob-library-java

它是 Java 中的一个零依赖库,用于进行 glob(和类似 sql)类型的比较。在大型数据集上,它比转换为正则表达式要快。

基本语法

MatchingEngine m = GlobPattern.compile("dog%cat\%goat_", '%', '_', GlobPattern.HANDLE_ESCAPES);
if (m.matches(str)) { ... }
于 2019-12-30T04:43:43.453 回答
0

这是我对此的看法,它在 Kotlin 中,但可以毫不费力地转换为 Java:

val percentageRegex = Regex("""(?<!\\)%""")
val underscoreRegex = Regex("""(?<!\\)_""")

infix fun String.like(predicate: String): Boolean {

    //Split the text by every % not preceded by a slash.
    //We transform each slice before joining them with .* as a separator.
    return predicate.split(percentageRegex).joinToString(".*") { percentageSlice ->

        //Split the text by every _ not preceded by a slash.
        //We transform each slice before joining them with . as a separator.
        percentageSlice.split(underscoreRegex).joinToString(".") { underscoreSlice ->

            //Each slice is wrapped in "Regex quotes" to ignore all
            // the metacharacters they contain.
            //We also remove the slashes from the escape sequences
            // since they are now treated literally.
            Pattern.quote(
                underscoreSlice.replace("\\_", "_").replace("\\%", "%")
            )
        }

    }.let { "^$it$" }.toRegex().matches(this@like)
}

它可能不是这里所有解决方案中性能最好的,但它可能是最准确的。

它忽略除 % 和 _ 之外的所有其他正则表达式元字符,并且还支持用斜杠转义它们。

于 2021-02-14T12:57:00.243 回答
0
public static boolean like(String source, String exp) {
        if (source == null || exp == null) {
            return false;
        }

        int sourceLength = source.length();
        int expLength = exp.length();

        if (sourceLength == 0 || expLength == 0) {
            return false;
        }

        boolean fuzzy = false;
        char lastCharOfExp = 0;
        int positionOfSource = 0;

        for (int i = 0; i < expLength; i++) {
            char ch = exp.charAt(i);

            // 是否转义
            boolean escape = false;
            if (lastCharOfExp == '\\') {
                if (ch == '%' || ch == '_') {
                    escape = true;
                    // System.out.println("escape " + ch);
                }
            }

            if (!escape && ch == '%') {
                fuzzy = true;
            } else if (!escape && ch == '_') {
                if (positionOfSource >= sourceLength) {
                    return false;
                }

                positionOfSource++;// <<<----- 往后加1
            } else if (ch != '\\') {// 其他字符,但是排查了转义字符
                if (positionOfSource >= sourceLength) {// 已经超过了source的长度了
                    return false;
                }

                if (lastCharOfExp == '%') { // 上一个字符是%,要特别对待
                    int tp = source.indexOf(ch);
                    // System.out.println("上一个字符=%,当前字符是=" + ch + ",position=" + position + ",tp=" + tp);

                    if (tp == -1) { // 匹配不到这个字符,直接退出
                        return false;
                    }

                    if (tp >= positionOfSource) {
                        positionOfSource = tp + 1;// <<<----- 往下继续

                        if (i == expLength - 1 && positionOfSource < sourceLength) { // exp已经是最后一个字符了,此刻检查source是不是最后一个字符
                            return false;
                        }
                    } else {
                        return false;
                    }
                } else if (source.charAt(positionOfSource) == ch) {// 在这个位置找到了ch字符
                    positionOfSource++;
                } else {
                    return false;
                }
            }

            lastCharOfExp = ch;// <<<----- 赋值
            // System.out.println("当前字符是=" + ch + ",position=" + position);
        }

        // expr的字符循环完了,如果不是模糊的,看在source里匹配的位置是否到达了source的末尾
        if (!fuzzy && positionOfSource < sourceLength) {
            // System.out.println("上一个字符=" + lastChar + ",position=" + position );

            return false;
        }

        return true;// 这里返回true
    }
Assert.assertEquals(true, like("abc_d", "abc\\_d"));
        Assert.assertEquals(true, like("abc%d", "abc\\%%d"));
        Assert.assertEquals(false, like("abcd", "abc\\_d"));

        String source = "1abcd";
        Assert.assertEquals(true, like(source, "_%d"));
        Assert.assertEquals(false, like(source, "%%a"));
        Assert.assertEquals(false, like(source, "1"));
        Assert.assertEquals(true, like(source, "%d"));
        Assert.assertEquals(true, like(source, "%%%%"));
        Assert.assertEquals(true, like(source, "1%_"));
        Assert.assertEquals(false, like(source, "1%_2"));
        Assert.assertEquals(false, like(source, "1abcdef"));
        Assert.assertEquals(true, like(source, "1abcd"));
        Assert.assertEquals(false, like(source, "1abcde"));

        // 下面几个case很有代表性
        Assert.assertEquals(true, like(source, "_%_"));
        Assert.assertEquals(true, like(source, "_%____"));
        Assert.assertEquals(true, like(source, "_____"));// 5个
        Assert.assertEquals(false, like(source, "___"));// 3个
        Assert.assertEquals(false, like(source, "__%____"));// 6个
        Assert.assertEquals(false, like(source, "1"));

        Assert.assertEquals(false, like(source, "a_%b"));
        Assert.assertEquals(true, like(source, "1%"));
        Assert.assertEquals(false, like(source, "d%"));
        Assert.assertEquals(true, like(source, "_%"));
        Assert.assertEquals(true, like(source, "_abc%"));
        Assert.assertEquals(true, like(source, "%d"));
        Assert.assertEquals(true, like(source, "%abc%"));
        Assert.assertEquals(false, like(source, "ab_%"));

        Assert.assertEquals(true, like(source, "1ab__"));
        Assert.assertEquals(true, like(source, "1ab__%"));
        Assert.assertEquals(false, like(source, "1ab___"));
        Assert.assertEquals(true, like(source, "%"));

        Assert.assertEquals(false, like(null, "1ab___"));
        Assert.assertEquals(false, like(source, null));
        Assert.assertEquals(false, like(source, ""));
于 2019-01-30T09:18:02.167 回答
-3

好的,这是一个奇怪的解决方案,但我认为仍然应该提到它。

我们可以利用任何数据库中已有的现有实现,而不是重新创建类似的机制!

(唯一的要求是,您的应用程序必须能够访问任何数据库)。

每次只需运行一个非常简单的查询,根据同类比较的结果返回真或假。然后执行查询,直接从数据库中读取答案!

对于 Oracle 数据库:

SELECT
CASE 
     WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
     ELSE 'false'
 END test
FROM dual 

对于 MS SQL 服务器

SELECT
CASE 
     WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
     ELSE 'false'
END test

您所要做的就是将“StringToSearch”和“LikeSequence”替换为绑定参数并设置您要检查的值。

于 2018-05-22T08:29:42.843 回答