Lucene API可以有两种方法进行价格区间搜索。(我们采用第二种方法)
1. 采用RangeFilter过滤器进行价格区间搜索。
从名称上可以很容易地看出来,RangeFilter是用于过滤在一定范围内出现的文档
在JavaDoc中,对它是这样解释的。
A Filter that restricts search results to a range of values in a given field.
中文翻译为:
该Filter用于将检索结果限定在某个给定的Field值的范围内。
具体用法是:
RangeFilter filter = new RangeFilter(“DerivedPrice”,”200”,”500”,true,true);
searcher.search(query,filter);
上述代码表示搜索价格区间(DerivedPrice为索引中价格域)在200到500的结果。
搜索结果为:
300.0,350.0,400.0,4000.0
问题出来了,为什么4000.0也在搜索结果中?
这是因为,Lucene在按照范围搜索时,将字段值都作为字符串,并按照字典顺序对字符串进行排序,由于4排在2和5之间,所以4000.0也出现在搜索结果中。当然排序还会对小数点进行排序,这样将导致搜索结果不是我们想要的结果。
怎么解决?
修改索引补零,即对索引中的价格域DerivedPrice进行补零操作,首先要对所有价格有清晰的整体认识,我们索引中的价格都为200,或200.5或200.00之类的数据,即小数点后最多2位,而且价格最大差不过10万,为保证万无一失我们先将索引原数据中的所有价格*100,这样就去掉了小数点,然后对所有价格不够10位的前面全部补零,这样可以接受的最大价格为100万。
例如:123变为 0000012300
123.5 变为 0000012350
123.55变为0000012355
然后重建索引,再采用我们上述的RangeFilter即可得到我们想要的范围搜索结果了。同时,搜出的结果价格要首先转换为Float,然后除于10才是真正的价格。
优点:可以采用Lucene的内部范围搜索机制,速度快,也省心。
缺点:需要对索引进行修改重建,而且搜出的结果需重新进行价格转换。
2. 借用价格排序方法Sort进行升降序排列,然后采用二分查找法查找价格区间的两个端点,最后取得两个端点之间的值即为落在价格区间的搜索结果。
下面是价格区间封装类PriceRange:
/**
* 价格区间封装类,包括价格区间最小值,最大值和搜索后的升降序排列标示 默认为false升
*序排列,true标示降序排列
*
* @author 路卫杰
* @version 1.0, 2010-7-30
*/
public class PriceRange {
/** 价格区间最小值 */
private float lower;
/** 价格区间最大值 */
private float upper;
/** 价格区间排序方式,默认为false升序,true表示升序 */
private boolean orderFlag = false;
/** 价格区间排序方式,默认为false升序,true表示升序 */
private Sort priceSort;
/** 升序排列 */
public final static boolean ASCENDING = false;
/** 降序排列 */
public final static boolean DESCENDING = true;
/**
* 构造方法
*
* @param lower
* 价格区间最低值
* @param upper
* 价格区间最高值
*/
public PriceRange(float lower, float upper) {
if (lower <= upper) {
this.lower = lower;
this.upper = upper;
} else {
this.lower = upper;
this.upper = lower;
}
this.priceSort = new Sort(new SortField("DerivedPrice", SortField.FLOAT,this.orderFlag));
//下面是本项目的价格Sort初始化方法,为方便交流,上面是Lucene的价格Sort生成方法
//SortBy.getSort(SortBy.PRICE, this.orderFlag);
}
/**
* 构造方法
*
* @param lower
* 价格区间最低值
* @param upper
* 价格区间最高值
* @param orderFlag
* 价格区间搜索排序方法,false为升序,true为降序
*/
public PriceRange(float lower, float upper, boolean orderFlag) {
if (lower <= upper) {
this.lower = lower;
this.upper = upper;
} else {
this.lower = upper;
this.upper = lower;
}
this.orderFlag = orderFlag;
this.priceSort = new Sort(new SortField("DerivedPrice", SortField.FLOAT,this.orderFlag));
//下面是本项目的价格Sort初始化方法,为方便交流,上面是Lucene的价格Sort生成方法
//SortBy.getSort(SortBy.PRICE, this.orderFlag);
}
/**
* 生成价格比lower更高的PriceRange对象
*
* @param lower
* 价格区间最低值
*
* @return 价格比lower更高的PriceRange对象
*/
public static PriceRange More(float lower) {
return new PriceRange(lower, Float.MAX_VALUE);
}
/**
* 生成价格比lower更高的PriceRange对象
*
* @param lower
* 价格区间最低值
* @param orderFlag
* 价格区间搜索排序方法,false为升序,true为降序
*
* @return 价格比lower更高的PriceRange对象
*/
public static PriceRange More(float lower, boolean orderFlag) {
return new PriceRange(lower, Float.MAX_VALUE, orderFlag);
}
/**
* 生成价格比upper更低的PriceRange对象
*
* @param upper
* 价格区间最高值
*
* @return 价格比upper更低的PriceRange对象
*/
public static PriceRange Less(float upper) {
return new PriceRange(Float.MIN_VALUE, upper);
}
/**
* 生成价格比upper更低的PriceRange对象
*
* @param upper
* 价格区间最高值
* @param orderFlag
* 价格区间搜索排序方法,false为升序,true为降序
*
* @return 价格比upper更低的PriceRange对象
*/
public static PriceRange Less(float upper, boolean orderFlag) {
return new PriceRange(Float.MIN_VALUE, upper, orderFlag);
}
public float getLower() {
return lower;
}
public void setLower(float lower) {
this.lower = lower;
}
public float getUpper() {
return upper;
}
public void setUpper(float upper) {
this.upper = upper;
}
public boolean isOrderFlag() {
return orderFlag;
}
public void setOrderFlag(boolean orderFlag) {
this.orderFlag = orderFlag;
}
public Sort getPriceSort() {
return priceSort;
}
public void setPriceSort(Sort priceSort) {
this.priceSort = priceSort;
}
}
下面是对按照价格排序后的结果进行二分查找,查找价格区间两个端点在结果中的位置。
/**
* 二分查找算法,查找Hits对象中DerivedPrice值为value的Document在Hits中的位置
*
* @param hits
* Hits对象
* @param value
* 要查找的值
* @param orderFlag
* 原数组升降序标示,false表示为升序排列,true表示为降序排列
* @param flag
* 如果没有此值,orderFlag为false前提下:true表示取比其大的值的位置,false表示取比其小的值的位置;
* orderFlag为true前提下:true表示取比其小的值的位置,false表示取比其大的值的位置。
*
* @throws IOException
* @throws CorruptIndexException
* @throws NumberFormatException
*
* @return Hits对象中DerivedPrice值为value的Document在Hits中的位置,如果value值不在hits中,若flag为true返回比value大的值的位置,若flag为false返回比value小的值的位置
*/
private int binarySearch(Hits hits, float value, boolean orderFlag,
boolean flag) throws NumberFormatException, CorruptIndexException,
IOException {
int low = 0;
int high = hits.length() - 1;
// 根据orderFlag标示循环进行二分查找
if (orderFlag) {// 原数组为降序情况
while (low <= high) {
int mid = (low + high) / 2;
float midVal = Float.parseFloat(hits.doc(mid).get(
"DerivedPrice"));
if (midVal > value)
low = mid + 1;
else if (midVal < value)
high = mid - 1;
else {
// 查找第一次出现的位置
if (!flag) {
int i = mid - 1;
for (; i > 0; i--) {
if (Float.parseFloat(hits.doc(i)
.get("DerivedPrice")) == value)
continue;
else
break;
}
return ++i;
} else {
int i = mid + 1;
for (; i < hits.length(); i++) {
if (Float.parseFloat(hits.doc(i)
.get("DerivedPrice")) == value)
continue;
else
break;
}
return --i;
}
}
}
// 如果flag为false返回比其大的值的位置,否则返回比其小的值的位置
if (!flag)
return (low < hits.length() ? low : hits.length() - 1);
else
return (high >= 0 ? high : 0);
} else {// 原数组为升序情况
while (low <= high) {
int mid = (low + high) / 2;
float midVal = Float.parseFloat(hits.doc(mid).get(
"DerivedPrice"));
if (midVal < value)
low = mid + 1;
else if (midVal > value)
high = mid - 1;
else {
// 查找第一次出现的位置
if (flag) {
int i = mid - 1;
for (; i > 0; i--) {
if (Float.parseFloat(hits.doc(i)
.get("DerivedPrice")) == value)
continue;
else
break;
}
return ++i;
} else {
int i = mid + 1;
for (; i < hits.length(); i++) {
if (Float.parseFloat(hits.doc(i)
.get("DerivedPrice")) == value)
continue;
else
break;
}
return --i;
}
}
}
// 如果flag为true返回比其大的值的位置,否则返回比其小的值的位置
if (flag)
return (low < hits.length() ? low : hits.length() - 1);
else
return (high >= 0 ? high : 0);
}
}
对二分查找的调用如下:
/** 价格区间最小值在hits中的位置 */
int low = binarySearch(hits, lower, orderFlag, true);
/** 价格区间最大值在hits中的位置 */
int up = binarySearch(hits, upper, orderFlag, false);
// 价格区间是否有结果标志,默认isFlag为true
boolean isFlag = true;
// 如果orderFlag为false,即hits中价格为升序且low>up 或
// 如果orderFlag为true,即原hits中价格为降序且low<up
// 则表明价格区间中没有结果,将isFlag置false
if ((!orderFlag && low > up) || (orderFlag && low < up))
isFlag = false;
int totalRecords = 0;
if (low >= 0 && up >= 0 && isFlag) {
// 如果up小于low,则进行交换
if (up < low) {
int mid = up;
up = low;
low = mid;
}
// 在价格区间的总数目
totalRecords = up - low + 1;
}
// 根据总数目生成Page对象
page = PageUtil.createPage(page, totalRecords);
// 此页的开始记录和结束记录
int start = page.getBeginIndex();
int end = start + page.getEveryPage();
// 如果end大于totalRecords则取totalRecords值
end = (end < totalRecords ? end : totalRecords);
// 获取搜索结果
for (int i = low + start; i < low + end; i++) {
Document doc = hits.doc(i);
OutShow outShow = new OutShow();
outShow.setProduct(doc.get("Product"));
outShow.setUrl(doc.get("Page_url"));
outShow.setDescription(doc.get("Description"));
outShow.setOnHand(doc.get("OnHand"));
outShow.setCharSet(doc.get("Charset"));
outShow.setCrawlertime(doc.get("Crawlertime"));
outShow.setAddress(doc.get("Address"));
outShow.setProvider(doc.get("Provider"));
outShow.setSite(doc.get("Site"));
float credit = Float
.valueOf(doc.get("DerivedCreditPoint") == null ? "0.0"
: doc.get("DerivedCreditPoint"));
outShow.setCreditPoint(credit);
float price1 = Float
.valueOf(doc.get("DerivedPrice") == null ? "0.0"
: doc.get("DerivedPrice"));
outShow.setPrice(price1);
int itemSold = Integer
.valueOf(doc.get("DerivedItemSold") == null ? "0"
: doc.get("DerivedItemSold"));
outShow.setItemSold(itemSold);
outShow.setScore(doc.getBoost());
outShow.setDerivedCatNum(doc.get("DerivedCatNum"));
outShow.setPictureUrl(doc.get("PictureUrl"));
list.add(outShow);
}
}
System.out.println("共搜索到 " + totalRecords + " 个结果!");
}
对PriceRange的调用如下:
PriceRange pRange=new PriceRange(Float.parseFloat("2324"),Float.parseFloat("2340"));
search.search(page,pRange);
优点:不用修改索引,搜出的结果也无需对价格进行转换
缺点:无法在价格区间的搜索的同时对其它字段进行排序。由于本方法使用的是价格排序,然后对结果进行二分查找,所以无法同时对其它字段进行排序。