wake-up-neo.com

Wie implementiere ich einen SQL-ähnlichen LIKE-Operator in Java?

Ich brauche einen Komparator in Java, der die gleiche Semantik hat wie der SQL-Operator "like" . Zum Beispiel:

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

sollte wahr sein, und

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

sollte falsch bewerten. Haben Sie Ideen, wie Sie einen solchen Komparator implementieren können, oder kennt jemand eine Implementierung mit derselben Semantik? Kann dies mit einem regulären Ausdruck erfolgen?

35
Chris

. * stimmt mit beliebigen Zeichen in regulären Ausdrücken überein

Ich denke die Java-Syntax wäre

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

Und für die Übereinstimmung eines einzelnen Zeichens verwenden Sie einfach einen einzelnen Punkt.

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

Und um einen tatsächlichen Punkt abzugleichen, lassen Sie ihn als Schrägpunkt frei

\.
31
Bob

Ja, dies kann mit einem regulären Ausdruck erfolgen. Beachten Sie, dass die regulären Ausdrücke von Java eine andere Syntax haben als "Like" von SQL. Anstelle von "%" hätten Sie ".*" und anstelle von "?" hätten Sie ".".

Was es etwas komplizierter macht, ist, dass Sie auch alle Zeichen, die Java als etwas Besonderes behandelt, entziehen müssen. Da Sie versuchen, dies analog zu SQL zu machen, schätze ich, dass ^$[]{}\ nicht in der Regex-Zeichenfolge erscheinen sollte. Sie müssen jedoch "." durch "\\." ersetzen, bevor Sie andere Ersetzungen vornehmen. (Edit:Pattern.quote(String) entgeht alles, indem der String mit "\Q" und "\E" umgeben wird. Dadurch wird alles im Ausdruck als Literal behandelt (überhaupt keine Platzhalter).definitiv möchte es nicht verwenden.)

Außerdem, wie Dave Webb sagt, müssen Sie auch den Fall ignorieren.

In diesem Sinne sehen Sie hier ein Beispiel, wie es aussehen könnte:

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);
}
20
Michael Myers

Reguläre Ausdrücke sind am vielseitigsten. Einige LIKE-Funktionen können jedoch ohne reguläre Ausdrücke gebildet werden. z.B.

String text = "digital";
text.startsWith("Dig"); // like "Dig%"
text.endsWith("tal"); // like "%tal"
text.contains("gita"); // like "%gita%"
16
Peter Lawrey

Jede SQL-Referenz, die ich finden kann, besagt, dass der Platzhalter "Beliebige Zeichen" der Unterstrich (_) und nicht das Fragezeichen (?) ist. Das vereinfacht die Sache etwas, da der Unterstrich kein Regex-Metazeichen ist. Sie können jedoch Pattern.quote() aus den von mmyers angegebenen Gründen nicht verwenden. Ich habe hier eine andere Methode, um Regex zu umgehen, wenn ich sie später bearbeiten möchte. Damit ist die like()-Methode ziemlich einfach:

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();
}

Wenn Sie wirklich ? für den Platzhalter verwenden möchten, besteht die beste Möglichkeit darin, ihn aus der Liste der Metazeichen der quotemeta()-Methode zu entfernen. Das Escape-Formular - replace("\\?", ".") - zu ersetzen, ist nicht sicher, da der ursprüngliche Ausdruck möglicherweise umgekehrte Schrägstriche enthält.

Das bringt uns zu den wirklichen Problemen: Die meisten SQL-Varianten scheinen Zeichenklassen in den Formularen [a-z] und [^j-m] oder [!j-m] zu unterstützen, und sie bieten alle eine Möglichkeit, Platzhalterzeichen zu umgehen. Letzteres wird normalerweise mit einem ESCAPE-Schlüsselwort ausgeführt, mit dem Sie jedes Mal ein anderes Escape-Zeichen definieren können. Wie Sie sich vorstellen können, macht dies die Sache ziemlich kompliziert. Das Konvertieren in einen regulären Ausdruck ist wahrscheinlich immer noch die beste Option, aber das Parsen des ursprünglichen Ausdrucks wird viel schwieriger sein. In der Tat müssen Sie zunächst die Syntax der LIKE-ähnlichen Ausdrücke selbst formalisieren.

11
Alan Moore

Um LIKE-Funktionen von SQL in Java zu implementieren, benötigen Sie keinen regulären Ausdruck in

String text = "Apple";
text.startsWith("app"); // like "app%"
text.endsWith("le"); // like "%le"
text.contains("ppl"); // like "%ppl%"
4
Mithun Adhikari

Sie könnten '%string%' auf contains() , 'string%' auf startsWith() und '%string"' auf endsWith() umstellen.

Sie sollten toLowerCase() sowohl für den String als auch für das Muster ausführen, da LIKE case-insenstive ist.

Nicht sicher, wie Sie mit '%string%other%' umgehen würden, außer mit einem regulären Ausdruck.

Wenn Sie reguläre Ausdrücke verwenden:

3
Dave Webb
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;
    }
}

kann dir helfen

2
Krishnendu

Java-Strings verfügen über die Methoden .startsWith () und .contains (), die Ihnen den besten Weg zeigen. Für alles, was komplizierter ist, müsste man Regex verwenden oder eine eigene Methode schreiben.

2
job

http://josql.sourceforge.net/ hat was Sie brauchen. Suchen Sie nach org.josql.expressions.LikeExpression.

2
Rich MacDonald

Apache Cayanne ORM verfügt über eine " In-Memory-Auswertung

Es funktioniert möglicherweise nicht für nicht zugeordnete Objekte, sieht jedoch vielversprechend aus:

Expression exp = ExpressionFactory.likeExp("artistName", "A%");   
List startWithA = exp.filterObjects(artists); 
2
OscarRyz

ich weiß es nicht genau über das gierige Thema, aber versuchen Sie es, wenn es für Sie funktioniert:

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);
}
1
tommyL

Die Schnittstellen Comparator und Comparable sind hier wahrscheinlich nicht anwendbar. Sie befassen sich mit dem Sortieren und geben Ganzzahlen mit entweder einem Vorzeichen oder 0 zurück. Bei Ihrer Operation müssen Sie Übereinstimmungen finden und true/false zurückgeben. Das ist anders.

1
John O
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, ""));
0
test love