存档在 2015年11月

APP后端搜索技术

2015年11月16日

 现在人们的网络生活已经离不开搜索了,遇到不懂的问题,想知道的事情,搜索一下,就知道答案。

在app中,最常见的搜索情景就是搜索用户。只有几百,几千的用户量时,可以直接用用like这样的模糊查询,但是,如果数据有几百万,甚至上千万的时候,一次like查询数据库就堵了。到了一定量级的时候,不得不考虑使用专门的搜索技术。

1.    一个简单的搜索例子

有三行数据:

(1)近2周8成股民亏损超10%。

(2)满仓中国梦。

(3)股民两天亏一套三居。

例如,有个需求,从上面的3行数据中,把包含“股民”这个关键词的数据找出来。

按照一般的做法,就是分别查找上面的每一行数据:

第一行数据从头到尾查找一次,发现有“股民”这个关键词。

第二行数据从头到尾查找一次,没有有“股民”这个关键词。

第三行数据从头到尾查找一次,发现有“股民”这个关键词。

根据查找结果,第一,第三行数据包含“股民”这个关键词。

 

2.    搜索技术的基本原理

按照上面的过程,每次查找,都需要把每行数据从头到尾查一次。

如果需要从上百万,千万的数据中查找一个关键词,读者可以想象一下效率有多低。

我们看一下搜索引擎的例子,在搜索引擎搜索“股民”这个关键词的结果:

在搜索引擎的搜索结果中,是直接显示了所有包含“股民”这个关键字的数据。

它是怎么做到在海量的信息中,快速搜索中包含关键字的信息的呢?

实现搜索的关键,就是分词和倒序索引。

如果我们知道每行数据中包含多少个关键字,然后建立一个映射表,把每个关键字出现在哪行数据中记录下来,搜索就变得很轻松。当知道一个关键字的时候,只需要查找这个映射表,找到这个关键词,根据这个关键词建立的映射关系就能查到包含这个关键词的数据。

知道每行数据中包含多少个关键字的过程,就是分词。这里有个问题,什么是关键字?

关键字,其实就是一个词语或句子,例如,当我有需要的时候,“股民”可以是搜索的关键字,但是,“股”也可以是搜索的关键字,“民”也可以是搜索的关键字。什么是关键字,要看使用者的需求。因此,为了能准确分析出一行数据到底包含多少个关键字,就需要一个包含了所有词语或句子的词典,用来分析数据中有什么关键字。

 

建立一个映射表,把每个关键字出现在哪行数据中记录下来,这个过程就是建倒序搜引。

下面举个实际的例子,看看是怎么分词和建立倒序索引。

还是用回上面举例的三行数据,左边的是数据的编号,右边的是数据的内容。

(1)近2周8成股民亏损超10%。

(2)满仓中国梦。

(3)股民两天亏一套三居。

首先,把分析上面每行数据包含多少个关键词(这里为了简化分词过程,没有把每个汉字或数字当成一个关键词,例如,” 民”应该是个关键词,但为了简化分词,没有当成一个关键词)。

下面根据上面的结果建立一个映射表表2,把每个关键字出现在哪行数据中记录下来

我们很容易得知,“股民”这个关键词在数据1,3中出现过。如果需要知道“中国”这个关键词出现在哪,通过查找表2也很容易得知出现在数据2中。

在这么几行数据中,还不能体验到倒序索引的高效。如果数据量到了上百万,千万,甚至上亿,倒序索引的效率就非常明显了。归根到底,这种数据结构就是为了实现快速搜索也建立的。

再进一步,除了记录关键词出现在哪行数据中,还能记录在某行数据中出现的频率,出现的位置等信息,如果有兴趣继续深入了解搜索引擎的技术,可阅读《这就是搜索引擎:核心技术详解》(张俊林著),这篇文章只是简单介绍搜索引擎的基本原理。

 

3.    常见的开源搜索软件介绍

搜索技术一点都不简单,如果要我们从头开始做,不知道要到哪年哪月才能用给app用上搜索功能。幸好,大牛们已经为我们开源大量的搜索软件,只要我们会使用这些搜索软件提供的api,就能给app后台整合搜索技术。下面简单介绍一下常见的搜索软件。

(1) Lucene

Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。

 

(2) Solr

Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。

 

(3) Elasticsearch

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是第二流行的企业搜索引擎。

 

(4) Sphinx

Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL,PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。Sphinx特别为一些脚本语言设计搜索API接口,如PHP,Python,Perl,Ruby等,同时为MySQL也设计了一个存储引擎插件。

 

(5) Coreseek

Coreseek 是一款中文全文检索/搜索软件,以GPLv2许可协议开源发布,基于Sphinx研发并独立发布,专攻中文搜索和信息处理领域,适用于行业/垂直搜索、论坛/站内搜索、数据库搜索、文档/文献检索、信息检索、数据挖掘等应用场景,用户可以免费下载使用。

Coreseek曾经在本人架构过两个app后台深度使用过,配置简单,性能高效,整合了Sphinx和中文分词,快速完成了搜索模块的开发。但最大的缺点是稳定版不支持实时索引,测试版是支持了,但没在生产环境中用过。

Coreseek有两个核心模块 Indexer和Search。

Indexer: 负责从mysql中拉取数据源,把数据源分词,建立索引

Search:搜索模块

流程:

1.      Indexer模块从mysql中拉取数据

2.      Indexer模块把数据经过中文分词,建立索引

3.      客户端向Search模块发起搜索请求

4.      Seach模块查找索引中的数据

5.      Seach模块得到索引中符合要求的数据的id等数据

6.      把数据返回给客户端

iOS: 知名APP用到的第三方开源框架

2015年11月9日
知名应用程序的设计和技术一直都是开发者需要学习的,同样这些应用所使用的开源框架也是不可忽视的一部分。此前《iOS第三方开源库的吐槽和备忘》中作者ibireme列举了国内多款知名应用所使用的开源框架,并对其中一些框架进行了分析,同样国外开发者@iOSCowboy也在博客中给我们列出了国外多款知名应用使用的开源框架。另外txx’s blog中详细介绍了Facebook Paper使用的第三方库
Instagram
AFNetworking: 适用于iOS和OS X的网络框架。
Appirater: 提醒用户打分。
ASIHTTPRequest:简单使用CFNetwork API封装进行HTTP网络请求,用Objective-C编写,可应用在Mac OSX和iOS开发中。
CocoaHTTPServer: 用于Mac OS X和iOS应用程序的轻量级、可嵌入的HTTP服务器框架。
Cocoa Lumberjack:适用于Mac和iOS的日志框架,集简单、快速、强大以及灵活于一身。
MBProgressHUD: 用多种样式展示半透明的HUD,并带有指示器和标签,自定义功能强大。
PLCrashReporter (Github mirror): 进程内崩溃报告框架。
QSUtilities: 实用工具、控件以及其他辅助类的集合。
SocketRocket: Objective-C WebSocket客户端库。 https://github.com/square/SocketRocket
XBImageFilters:允许实时过滤摄像头拍摄的照片,使用OpenGL ES 2 来快速处理各种图片效果。
Foursquare 
Facebook SDK for iOS: 集成Facebook,构建强大的社交app。
FSNetworking: Foursquare iOS网络库。
kingpin: MapKit/MKAnnotation pin 聚合库,主要用来在地图上面添加锚点。
AFNetworking:适用于iOS和OS X的网络框架。
SKBounceAnimation: CAKeyframeAnimation子类,可快速简单地设置弹动的数量,开始和结束的值,以及创建动画。
DB5: 通过Plist配置文件。
LinkedIn
BlocksKit: blocks工具包。
SDWebImage: 提供一个UIImageVIew类以支持远程加载网络图片。具有缓存管理、异步图片下载等功能,支持GIF动画,使用GCD和ARC。
DTCOreText:文字效果代码类库。在UITextView上实现丰富的文字效果,比如文字大小、颜色、字体、下划线,链接,给文字加上图片、视频,文字任意间距等等。实现类似于CSS网页的文字效果。
Shazam
AudioStreamer:Mac OS X和iPhone上适用的流媒体音频播放器,可播放来自网络上的音乐。.
ColorArt: iTunes 11风格的颜色匹配代码。
objc-geohash: Objective-C GeoHash库,通过经纬度获得哈希表。
FormatterKit: 收集了精心构思的NSFormatter子类。
UIView+Glow: UIView的一个类别,可添加对制作发光视图的支持,以突出屏幕上重要的部分,方便用户与之进行交互。
WEbViewJavascriptBridge: 在使用UIWebView时,它优雅地实现了JS与ios 的ObjC 原生代码之间的互调,支持消息发送、接收、消息处理器的注册与调用以及设置消息处理的回调。
Skype
AFNetworking: 适用于iOS和OS X的网络框架。
Hockey SDK: HockeyApp service官方iOS SDK。
PLCrashReporter (Github mirror): 进程内的崩溃报告框架。
TTTAttributedLabel是一个文字视图开源组件,是UILabel的替代元件,可以以简单的方式展现渲染的属性字符串。另外,还支持链接植入,不管是手动还是使用UIDataDetectorTypes自动把电话号码、事件、地址以及其他信息变成链接。
SDWebImage: 提供一个UIImageVIew类以支持远程加载网络图片。具有缓存管理、异步图片下载等功能,支持GIF动画,使用GCD和ARC。
Cocoa Lumberjack: 适用于Mac和iOS的日志框架,集简单、快速、强大以及灵活于一身。
MWPhotoBrowser: 一个简单的带有栅格视图的iOS照片浏览器,可添加标题和选择多个图片。照片浏览器效果类似iOS原生的照片应用,可显示来自手机的图片或者是网络图片,也可自动从网络下载图片并进行缓存,还可图片进行缩放等。
BlocksKit: Objective-C blocks工具包。
Spotify
FMDB: SQLite API封装库。
MAObjCRuntime:将运行时API封装成ObjC。
Nu: 编程语言。
PLCrashReporter (Github mirror):进程内崩溃报告框架。
SBJSON:Objective-C 实现的一个严格的JSON 解析器和生成器。

ERROR 1045 (28000): Access denied for user ‘root’@’localhost’ (using password: YES) 问题注意点

2015年11月9日

最近新装好的mysql在进入mysql工具时,有错误提示:

# mysql -uroot -p
Enter password:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

使用网上介绍的方法修改root用户的密码:

# mysqladmin -uroot -p password 'newpassword'
Enter password:
mysqladmin: connect to server at 'localhost' failed
error: 'Access denied for user 'root'@'localhost' (using password: YES)'

方法一:特别要注意的是需要到mysql目录下执行./bin/mysqld_safe,其他目录下指定mysqld_safe好像不行

# /etc/init.d/mysql stop
# ./bin/mysqld_safe --user=mysql --skip-grant-tables --skip-networking &
# mysql -u root mysql
mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';
mysql> FLUSH PRIVILEGES;
mysql> quit
# /etc/init.d/mysqld restart
# mysql -uroot -p
Enter password: <输入新设的密码newpassword>

mysql>

方法二:
直接使用/etc/mysql/debian.cnf文件中[client]节提供的用户名和密码:

# mysql -udebian-sys-maint -p
Enter password: <输入[client]节的密码>
mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';
mysql> FLUSH PRIVILEGES;
mysql> quit
# mysql -uroot -p
Enter password: <输入新设的密码newpassword>
mysql>

关于AndroidStudio导入Module出现问题 DefaultGroovyMavenDeployer 的解决方法

2015年11月8日

AndroidStudio在导入Module 时总是会出现各种问题。
比如:导入 GitHub  某一开源项目的 Library 时,经常会出现 类似以下的错误
No such property: XXXXX for class: org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer
这种错误 是由于 作者开发插件 与我们不统一造成的,所以只需要将  插件屏蔽掉就可以了。
由于 Module 只用一个Build.gradle文件来控制它这个module的编译规则,所以,在导入 Module之前,作以下几步就能成功导入:
1. 打开下载好的 Module 的Build.gradle 文件,只保留以下信息即可

apply plugin: 'android-library'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            res.srcDirs = ['res']
        }
    }
}

2. 进行导入的操作
3. 如果导入后,Studio 报错, 提示需要下载   buildTools XXX版本,或者下载 SDK XXX版本。
只需要在Studio的 《Project》模式下,修改 compileSdkVersion 或  buildToolsVersion 版本号为 当前使用的版本即可,如下:
compileSdkVersion 21
buildToolsVersion “21.1.2”

scrollview与listview、gridview嵌套高度冲突解决

2015年11月5日

由于listview,gridview都继承自Scrollview,两个Scrollview不能嵌套,所以在显示的时候会出现奇怪的高度问题。网上说了2种办法:一种是重写listview和gridview。不过这个方法有个问题,如果你的listview或者gridview设置了分割线高度。最后的计算结果会忽略掉分割线的高度,导致现实不全。
第二种方法是写一个工具类对listview的高度进行重算,计算完设置listview的高度,这个方法试过可行,有把分割线也计算进去。不过网上都是 listview的计算。缺少gridview的,因此在这里补充上。由于gridview计算的高度不能直接根据数据总数,需要判断每行显示的个数。以 及最后一行是否全部显示。需要多几个判断。

代码如下:
计算listview高度的代码

public static void setListViewHeightBasedOnChildren(ListView listView) {
                    // 获取ListView对应的Adapter
                    ListAdapter listAdapter = listView.getAdapter();
                    if (listAdapter == null) {
                            // pre-condition
                            return;
                    }
      
                    int totalHeight = 0;
                    for (int i = 0, len = listAdapter.getCount(); i < len; i++) { // listAdapter.getCount()返回数据项的数目
                            View listItem = listAdapter.getView(i, null, listView);
                            listItem.measure(0, 0); // 计算子项View 的宽高
                            totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度
                    }
      
                    ViewGroup.LayoutParams params = listView.getLayoutParams();
                    params.height = totalHeight
                                    + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
                    // listView.getDividerHeight()获取子项间分隔符占用的高度
                    // params.height最后得到整个ListView完整显示需要的高度
                    listView.setLayoutParams(params);
            }

计算gridview高度的代码

public static void setGridViewHeightBasedOnChildren(GridView gridView) {
                    // 获取GridView对应的Adapter
                    ListAdapter listAdapter = gridView.getAdapter();
                    if (listAdapter == null) {
                            return;
                    }
                    int rows;
                    int columns=0;
                    int horizontalBorderHeight=0;
                    Class<?> clazz=gridView.getClass();
                    try {
                            //利用反射,取得每行显示的个数
                            Field column=clazz.getDeclaredField("mRequestedNumColumns");
                            column.setAccessible(true);
                            columns=(Integer)column.get(gridView);
                            //利用反射,取得横向分割线高度
                            Field horizontalSpacing=clazz.getDeclaredField("mRequestedHorizontalSpacing");
                            horizontalSpacing.setAccessible(true);
                            horizontalBorderHeight=(Integer)horizontalSpacing.get(gridView);
                    } catch (Exception e) {
                            // TODO: handle exception
                            e.printStackTrace();
                    }
                    //判断数据总数除以每行个数是否整除。不能整除代表有多余,需要加一行
                    if(listAdapter.getCount()%columns>0){
                            rows=listAdapter.getCount()/columns+1;
                    }else {
                            rows=listAdapter.getCount()/columns;
                    }
                    int totalHeight = 0;
                    for (int i = 0; i < rows; i++) { //只计算每项高度*行数
                            View listItem = listAdapter.getView(i, null, gridView);
                            listItem.measure(0, 0); // 计算子项View 的宽高
                            totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度
                    }
                    ViewGroup.LayoutParams params = gridView.getLayoutParams();
                    params.height = totalHeight+horizontalBorderHeight*(rows-1);//最后加上分割线总高度
                    gridView.setLayoutParams(params);
            }

Error:Execution failed for task ‘:app:dexDebug’.

2015年11月4日

异常Log:

Error:Execution failed for task ‘:app:dexDebug’.
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process ‘command ‘/Library/……/java” finished with non-zero exit value 2

错误原因:

这个异常在Android Studio编译运行的时候出现,原因是项目中引用了重复的jar包,这可能是因为新引入的library module中有和主项目中重复引用的jar包,需要重点检查 Android 兼容包 support-v4support-v7 包,还有一些常用的开源项目,例如Gson,Nineoldandroids…,这些常用的开源项目有可能在你引用的 library 项目中已经被引用过了。