我需要Java中的比较器,该比较器的语义与sql"赞"运算符相同。
例如:
myComparator.like("digital","%ital%");
myComparator.like("digital","%gi?a%");
myComparator.like("digital","digi%");
应该评估为真,并且
myComparator.like("digital","%cam%");
myComparator.like("digital","tal%");
应该评估为假。 关于如何实现这样的比较器的任何想法,或者有人知道具有相同语义的实现吗? 可以使用正则表达式吗?
请参阅Apache Cayenne项目中的RegexUtil#sqlPatternToRegex(String)。
。*将匹配正则表达式中的任何字符
我认为Java语法是
"digital".matches(".*ital.*");
对于单个字符匹配,只需使用单个点。
"digital".matches(".*gi.a.*");
为了匹配实际的点,请将其转为斜线点
\.
是的,谢谢!但是,如果单词inst如此简单,例如"%dig%",并且字符串需要一些escping键?已经有东西了吗?怎么样 ? ?
我为问号运算符编辑了答案。我对您的其余评论感到有些困惑。您是说要使用sql语法显示字符串,并且要按原样对其求值?如果是这种情况,我认为您将需要手动替换为sql语法。
如果用作搜索模式的字符串也包含诸如(或)的分组字符怎么办?其他字符怎么需要转义?
我认为这取决于您允许多少选择。
只是要注意。*是贪婪的(。*?可能更合适)。我不认为正则表达式中的。*与SQL中的%语义完全相同。
很好,请参阅以下问题进行爆炸stackoverflow.com/questions/255815/
是的,可以使用正则表达式来完成。请记住,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);
}
是否存在一种方法,可以在Java正则表达式中转义具有特殊含义的每个字符?
是的,Pattern.quote(java.sun.com/javase/6/docs/api/java/util/regex/)可以做到。出于某种原因,我认为这可能会导致问题,但是现在我不知道为什么不将其包括在答案中。
哦,是的,现在我记得了。这是因为 ?是一个特殊的正则表达式字符,因此在替换它之前将其转义。我想我们可以改为使用Pattern.quote,然后使用expr = expr.replace(" \?","。");
第三行应显示为replace(".","\\.");
你是对的。我应该在发布之前先在点上对其进行测试。
您还可以添加expr = expr.replaceAll("(?,因为"\_"可以在SQL中转义,因此在这种情况下不应替换为"."。 (对于一个字符,我使用了_而不是?。)
另外,对于%,此替换会更好:expr = expr.replaceAll("(?
正则表达式是最通用的。但是,某些LIKE函数可以不使用正则表达式而形成。例如
String text ="digital";
text.startsWith("dig"); // like"dig%"
text.endsWith("tal"); // like"%tal"
text.contains("gita"); // like"%gita%"
我可以找到的每个SQL参考都说"任何单个字符"通配符是下划线(_),而不是问号(?)。因为下划线不是正则表达式元字符,所以这简化了一些事情。但是,由于mmyers给出的原因,您仍然不能使用Pattern.quote()。当我以后想编辑正则表达式时,这里还有另一种转义正则表达式的方法。这样,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]或[!j-m]形式的字符类,并且它们都提供了一种转义通配符的方法。后者通常是通过ESCAPE关键字完成的,该关键字使您每次都可以定义一个不同的转义符。可以想象,这使事情复杂化了很多。转换为正则表达式可能仍然是最好的选择,但是解析原始表达式会更加困难-实际上,您要做的第一件事就是形式化LIKE表达式本身的语法。
是的,你是对的。我比我更喜欢您的解决方案。
如果(s == null)抛出新的IllegalArgumentException("字符串不能为null");否则if(s.isEmpty())返回";
要在Java中实现sql的LIKE函数,您不需要在
它们可以通过以下方式获得:
String text ="apple";
text.startsWith("app"); // like"app%"
text.endsWith("le"); // like"%le"
text.contains("ppl"); // like"%ppl%"
这本质上只是多年前发布的现有答案的重复。
该答案已经发布
您可以将'%string%'更改为contains(),将'string%'更改为startsWith(),将'%string"'更改为endsWith()。
您还应该在字符串和模式上同时运行toLowerCase(),因为LIKE是区分大小写的。
除了使用正则表达式外,不确定如何处理'%string%other%'。
如果您使用正则表达式:
在替换%字符之前,请给字符串加引号
注意LIKE字符串中的转义字符
什么是abot"%this%string%"?分割%符号,遍历数组,然后检查每个条目?我认为这可以做得更好...
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;
}
}
可能会帮助你
http://josql.sourceforge.net/具有您所需要的。查找org.josql.expressions.LikeExpression。
Apache Cayanne ORM具有"内存中的评估"
它可能不适用于未映射的对象,但看起来很有希望:
Expression exp = ExpressionFactory.likeExp("artistName","A%");
List startWithA = exp.filterObjects(artists);
您知道休眠是否支持此功能吗?我的意思是,使用这样的表达式过滤当前内存中的对象?
Java字符串具有.startsWith()和.contains()方法,它们将使您获得更多收益。对于更复杂的事情,您必须使用正则表达式或编写自己的方法。
比较器和可比较接口在这里可能不适用。它们处理排序,并返回符号或0的整数。您的操作是查找匹配项,并返回true / false。那不一样。
欢迎您为运营商建议一个更好的名称。我不喜欢批评家,没有改进的建议,顺便说一句。
我不完全了解贪婪的问题,但是如果它对您有用,请尝试以下操作:
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);
}
您的内部split()和循环会替换任何?带点的顺序-我不明白。为什么要选择该序列,而只用一个点代替它,就像一个单独的问号?
它取代了?与。因为?是单个任意字符的占位符。我知道 \\\?看起来很奇怪,但是我进行了测试,并且对于我的测试来说似乎可行。
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++;// <<
} 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,""));
好的,这是一个奇怪的解决方案,但我认为仍然应该提到它。
无需重新创建类似的机制,我们可以利用任何数据库中已有的现有实现!
(仅要求您的应用程序必须有权访问任何数据库)。
每次只运行一个非常简单的查询,根据同类比较的结果返回true或false。 然后执行查询,并直接从数据库中读取答案!
对于Oracle数据库:
SELECT
CASE
WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
ELSE 'false'
END test
FROM dual
对于MS SQL Server
SELECT
CASE
WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
ELSE 'false'
END test
您所要做的就是用绑定参数替换" StringToSearch"和" LikeSequence",并设置要检查的值。