Solr in Action是本好书,决定复习一遍。

为什么需要搜索引擎,或者说搜索引擎有什么特别的地方,需要在应用中用到它?

搜索引擎有四个主要特征:

1.文本为中心。

当用户需要在文本中查找所需要的信息时,基本上就需要用到搜索引擎了。

2.读多写少
搜索引擎的结果为了读做了很多优化,相应的,写数据就会变得慢一些。当应用读多写少,用搜索引擎是比较合适的,而如果写多读少,则应考虑其它方案。

3.面向文档
搜索引擎的一条记录成为一个文档,这个文档是一个整体,不需要依赖其它信息。

4.灵活的模式
意思是说,引擎中的记录不要求结构都一样,每条记录所具有的字段可以不同

搜索的基本应用:
1.关键词查询
2.相关性排序
相关性排序是搜索引擎区别与其它查询的重要特征,相关性排序也是一个非常重要的研究方向。

Solr是什么?
简单来说,Solr就是Lucene的一个外壳。底层,Solr使用Lucene来索引和查询数据,外层,Solr提供灵活的配置文件,避免像Lucene那样编写代码来定义字段类型。此外,Solr还提供一些功能,如高亮,缓存,分布式等。

为什么选择Solr?
因为Solr在稳定性,可扩展性,容错性三个方面都做的非常出色。

联系作者

最近需要将Solr从1.4升级到4.8,于是需要将索引数据进行升级,而1.4无法直接升级到4.8,需要经过如下转化。从1.4升级到3.6,3.6升级到4.0,4.0升级到4.8。有几个引擎的数据升级很顺利,可是也有那么几个引擎的数据升级过程中出现了错误。

错误都出现在4.0升级到4.8时。调用栈如下:
Caused by: java.lang.IllegalArgumentException: maxValue must be non-negative (got: -1)
at org.apache.lucene.util.packed.PackedInts.bitsRequired(PackedInts.java:1180)
at org.apache.lucene.codecs.lucene41.ForUtil.bitsRequired(ForUtil.java:243)
at org.apache.lucene.codecs.lucene41.ForUtil.writeBlock(ForUtil.java:164)
at org.apache.lucene.codecs.lucene41.Lucene41PostingsWriter.addPosition(Lucene41PostingsWriter.java:368)
at org.apache.lucene.codecs.PostingsConsumer.merge(PostingsConsumer.java:123)
at org.apache.lucene.codecs.TermsConsumer.merge(TermsConsumer.java:164)
at org.apache.lucene.codecs.FieldsConsumer.merge(FieldsConsumer.java:72)
at org.apache.lucene.index.SegmentMerger.mergeTerms(SegmentMerger.java:389)
at org.apache.lucene.index.SegmentMerger.merge(SegmentMerger.java:112)
at org.apache.lucene.index.IndexWriter.mergeMiddle(IndexWriter.java:4132)
at org.apache.lucene.index.IndexWriter.merge(IndexWriter.java:3728)
at org.apache.lucene.index.ConcurrentMergeScheduler.doMerge(ConcurrentMergeScheduler.java:405)
at org.apache.lucene.index.ConcurrentMergeScheduler$MergeThread.run(ConcurrentMergeScheduler.java:482)

看代码后,在PostingsConsumer 120行附近,final int position = postingsEnum.nextPosition();,这个position是负的,所以报错。看这附近的代码,知道是对索引词的在文档中的位置信息进行压缩。可是词在文档中的位置不应该是负的,于是报错。问题是,为什么这里会出现负的位置,只能解释是数据问题。一个解决的办法是跳过为负的位置,如此升级确实成功了,只是不知道有没有什么副作用。

联系作者

在Solr的索引记录里看到,很多HostName是逆序的,如news.qq.com记录成moc.qq.swen, www.qq.com记录成moc.qq.www,moc.qq,finance.qq.com记录成moc.qq.ecnanif。后来才知道,这是为了实现像google那样的site功能.

site功能就是要查找索引中某一域名下的记录。一个实现办法就是实现上面的逆序存储。如此,要找出qq.com下的所有记录只需要用moc.qq.*去比较HostName即可。

联系作者

从扬大数院毕业后,与数学渐行渐远,许多时候,考虑问题都角度渐渐变得工程,也就是直观,而不是从抽象的角度来解决。组里有一个实习生,带来了几道笔试题,思考后,解决了,从解决问题的角度,明显看出,越来越工程化了。

第一题说的是用6种颜色去涂一个立方体,问有几种涂法?

一个明显的解答是6的阶乘,也就是720次,可是其中有很多种涂色,经过旋转后是一样的,所以这是错的。看到这题,立刻想到了魔方,之后想到了骰子,考虑到骰子更加直观,就用骰子。思考之后,其实挺简单的。把1这面朝上,那么1的对面,也就是底面有5种可能的情况。之后再看侧面的情况,对于侧面,固定一面之后,它的对面还有3中可能的情况,之后剩下两个侧面,有2种情况。所以一共有5 x 3 x 2 = 30种情况。这题一个难点是最开始的1这面的选择,以及侧面时,固定一面的选择。对于1这面的选择,是不能算概率的,因为无论怎么排,总是可以把1这面朝上。而对于侧面时固定一面,这固定一面也是不能算概率的,因为无论怎么排,都可以固定一面。

第二题说的是,对于座位编号从1到5的5个人,将他们的座位打乱,每个人都不在自己座位上的情况有几种?

经过上一题的训练后,抽象一下题目 ,对于座位编号从1到n的n个人,将他们的座位打乱,每个人都不在自己座位上的情况有几种
所以对于这题,相当于n为5的情况。依然用上面的类似方法。对于编号1的人,他一共有4种情况不在自己的座位上,假设他占了编号为5的座位。那么对于编号为5的这个人,他有两种情况可以选择,第一种,他占了座位1,则此时还剩三个人,这相当于n为3的情况,计算得到一种有2种可能;第二种情况是5不在座位1上,那么剩下的情况就相当于n=4的情形,计算得到有9中可能。于是最终结果等于4 x (2 + 9) = 44。而从这里也可以得到一个递推公式。设t(n)为人数为n时的可能情形。则t(n) = (n - 1) x (t(n - 1) + t(n - 2)),于是得到一个序列为0 1 2 9 44 265 ….

第三题说的是,一共有27个人想喝饮料,三个空瓶子可以换一瓶饮料,那么一共需要买多少瓶饮料才能保证每个人都能喝到一瓶饮料?

对于这题,立刻想到经典的借瓶子策略,对于这里,即只需要2个空瓶就可以喝一瓶饮料,这是因为当有2个空瓶时,可以向老板借一个空瓶,凑齐三个空瓶换来一瓶饮料,喝完之后,把空瓶换给老板。我想这里也是可以用到这个策略,于是写了一个序列1 2 6 18,等于27。所以最终的答案是18瓶。

联系作者

从QueryComponent可以知道,一个分布式solr请求从发起请求到对响应的结果进行处理会经历许多的stage.

对于分布式普通请求,从private int regularDistributedProcess(ResponseBuilder rb)的实现中可以看到,会经历ResponseBuilder.STAGE_PARSE_QUERY,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS,ResponseBuilder.STAGE_DONE等stage。从private void handleRegularResponses(ResponseBuilder rb, ShardRequest sreq),分布式普通请求有ShardRequest.PURPOSE_GET_TOP_IDS,ShardRequest.PURPOSE_GET_FIELDS两次响应

对于分布式group请求,从private int groupedDistributedProcess(ResponseBuilder rb)的实现中可以看到,则会经历ResponseBuilder.STAGE_PARSE_QUERY,ResponseBuilder.STAGE_TOP_GROUPS,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS,ResponseBuilder.STAGE_DONE等stage。从private void handleGroupedResponses(ResponseBuilder rb, ShardRequest sreq)可以看到,分布式group请求有ShardRequest.PURPOSE_GET_TOP_GROUPS,ShardRequest.PURPOSE_GET_TOP_IDS,ShardRequest.PURPOSE_GET_FIELDS三次响应.

对于不同的请求和响应,有相应的类或者方法来实现。当然也可以自己实现相应的类或者方法来处理。即便是QueryComponent也可以自己定义,只需要实现相应的接口即可。

对于private int regularDistributedProcess(ResponseBuilder rb),一个可能的实现是:

1
2
3
4
5
6
7
8
9
10
private int regularDistributedProcess(ResponseBuilder rb) {
ComponentDistributedStage cdStage = stages.getCDStage(rb.stage);
int nextState = ResponseBuilder.STAGE_DONE;
if (cdStage != null) {
cdStage.distributedProcess(rb, this);
if (stages.containsNextState(rb.stage))
nextState = stages.getNextState(rb.stage);
}
return nextState;
}

这里stages是一个Map,保存相应stage的实现类,父类型为ComponentDistributedStage。具体实现一个stage时,实现相应的接口即可。

联系作者

感觉上,这段代码不贴上来,仿佛欠别人钱似的。趁现在还有些精力,以后很长一段时间都不会接触Sphinx了,赶紧把这件事给做了。
具体为什么这样改,可以看前面的文章。以下修改是基于sphinx-for-chinese-2.2.1-dev-r4311版本,之需要修改sphinx.cpp即可。

在2296行后面添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct CSphWord
{
BYTE m_sAccum[3 * SPH_MAX_WORD_LEN + 3];
int length;
const BYTE *m_pTokenStart;
const BYTE *m_pTokenEnd;
};
class ISphWords
{
public:
int Length () const
{

return m_dData.GetLength();
}

const CSphWord * First () const
{

return m_dData.Begin();
}

const CSphWord * Last () const
{

return &m_dData.Last();
}
void Clean() {
m_dData.Reset();
}

void AddWord ( BYTE * word, int length, const BYTE *start, const BYTE *end)
{

CSphWord & tWord = m_dData.Add();
memcpy(tWord.m_sAccum, word, length);
tWord.length = length;
tWord.m_pTokenStart = start;
tWord.m_pTokenEnd = end;
}

public:
CSphVector<CSphWord> m_dData;
};

在2296行,virtual int GetMaxCodepointLength () const { return m_tLC.GetMaxCodepointLength(); }后面添加如下方法成员:

1
cvirtual BYTE *          ProcessParsedWord();

在2303行,Darts::DoubleArray::result_pair_type m_pResultPair[256];后面添加如下数据成员:

1
2
3
4
5
6
7
8
9
10
11
/*****add by luodongshan for indexer*****/
int totalParsedWordsNum; //总共需要处理的词
int processedParsedWordsNum; //已经处理的词
int isIndexer; //是否开启细粒度分词
bool needMoreParser; //需要更细粒度分词
const char * m_pTempCur;
char m_BestWord[3 * SPH_MAX_WORD_LEN + 3];
int m_iBestWordLength;
ISphWords m_Words;
CSphWord *current;
bool isParserEnd;

在6448行,m_bHasBlend = false;后面添加如下初始化代码:

1
2
3
4
5
6
7
8
9
char *penv = getenv("IS_INDEX");
if (penv != NULL) {
isIndexer = 1;
} else {
isIndexer = 0;
}
needMoreParser = false;
current = NULL;
isParserEnd = false;

在6743后面添加新增方法成员ProcessParsedWord的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template < bool IS_QUERY >
BYTE * CSphTokenizer_UTF8Chinese<IS_QUERY>::ProcessParsedWord() {
for (; current != NULL && current <= m_Words.Last(); ) {
memcpy(m_sAccum, current->m_sAccum, current->length);
m_pTokenStart = current->m_pTokenStart;
m_pTokenEnd = current->m_pTokenEnd;
current++;
return m_sAccum;
}
isParserEnd = false;
m_Words.Clean();
current = NULL;
return NULL;
}

在6785行, bool bGotSoft = false; // hey Beavis he said soft huh huhhuh后面增加如下代码:

1
2
3
if (isIndexer && isParserEnd) { //使用MMSEG分词结束,处理细粒度分词得到的词
return ProcessParsedWord();
}

在6791行, int iNum;后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/***add by dengsl 2014/06/24****/
if(isIndexer && needMoreParser) { //对最优匹配进行细粒度分词
while (m_pTempCur < m_BestWord + m_iBestWordLength) {
if(processedParsedWordsNum == totalParsedWordsNum) { //此位置的前缀词已处理完,跳到下一位置
size_t minWordLength = m_pResultPair[0].length;
for(int i = 1; i < totalParsedWordsNum; i++) {
if(m_pResultPair[i].length < minWordLength) {
minWordLength = m_pResultPair[i].length;
}
}
m_pTempCur += minWordLength;
m_pText=(Darts::DoubleArray::key_type *)(m_pCur + (m_pTempCur - m_BestWord));
iNum = m_tDa.commonPrefixSearch(m_pText, m_pResultPair, 256, m_pBufferMax-(m_pCur+(m_pTempCur-m_BestWord)));
totalParsedWordsNum = iNum;
processedParsedWordsNum = 0;
} else {
iWordLength = m_pResultPair[processedParsedWordsNum].length;
processedParsedWordsNum++;
if (m_pTempCur == m_BestWord && iWordLength == m_iBestWordLength) {
continue;
}
memcpy(m_sAccum, m_pText, iWordLength);
m_sAccum[iWordLength] = '\0';
if( 3 * SPH_MAX_WORD_LEN + 3 >= iWordLength + 2) {
m_sAccum[iWordLength + 1] = '\0';
if(m_pTokenEnd == m_pBufferMax) { //是结尾,保存结尾符标志
m_sAccum[iWordLength + 1] = 1;
}
}
m_Words.AddWord(m_sAccum, iWordLength + 2, m_pCur + (m_pTempCur - m_BestWord), m_pCur + (m_pTempCur - m_BestWord) + iWordLength);
}
}
m_pCur += m_iBestWordLength;
needMoreParser = false;
iWordLength = 0;
current = const_cast< CSphWord * > ( m_Words.First() );
}
/***add end by dengsl 2014/06/24****/

在6832行,iNum = m_tDa.commonPrefixSearch(m_pText, m_pResultPair, 256, m_pBufferMax-m_pCur);后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/***add by dengsl 2014/06/24****/
if(isIndexer && iNum > 1) {
m_iBestWordLength=getBestWordLength(m_pText, m_pBufferMax-m_pCur);
memcpy(m_sAccum, m_pText, m_iBestWordLength);
m_sAccum[m_iBestWordLength]='\0';
m_pTokenStart = m_pCur;
m_pTokenEnd = m_pCur + m_iBestWordLength;

totalParsedWordsNum = iNum;
needMoreParser = true;
processedParsedWordsNum = 0;
memcpy(m_BestWord, m_pText, m_iBestWordLength);
m_BestWord[m_iBestWordLength]='\0';
m_pTempCur = m_BestWord;
if( 3 * SPH_MAX_WORD_LEN + 3 >= m_iBestWordLength + 2) {
m_sAccum[m_iBestWordLength + 1] = '\0';
if(m_pTokenEnd == m_pBufferMax) { //是结尾,保存结尾符标志
m_sAccum[m_iBestWordLength + 1] = 1;
}
}
return m_sAccum;
}
/***add by dengsl 2014/06/24****/

在6903行,将

1
return NULL;

修改为

1
2
3
/* dengsl */
isParserEnd = true;
return ProcessParsedWord();

在6914行,将

1
2
if_const ( IS_BLEND && !BlendAdjust ( pCur ) )
return NULL;

修改成:

1
2
3
4
5
/* dengsl */
if_const ( IS_BLEND && !BlendAdjust ( pCur ) ) {
isParserEnd = true;
return ProcessParsedWord();
}

在27210行,m_tHits.AddHit ( uDocid, iWord, m_tState.m_iHitPos );后面增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
///add by luodongshan 20140626
if(sWord != NULL) {
int sWord_len = strlen((char*)sWord);
if(sWord_len + 2 <= 3 * SPH_MAX_WORD_LEN + 3 && sWord[sWord_len + 1] == 1 &&
getenv("IS_INDEX") != NULL && !bSkipEndMarker ) {
CSphWordHit * pHit = const_cast < CSphWordHit * > ( m_tHits.Last() );
HITMAN::SetEndMarker ( &pHit->m_iWordPos );

}
}
///add by luodongshan 20140626 end

将过上面的修改,重新编译源码,之后设置环境变量IS_INDEX,即运行export IS_INDEX=1,就可以支持细粒度的划分。

一个需要注意的地方是,对于searchd,也变成细粒度分词了,这并不是我们想要的,所以对于searchd,需要使用未修改代码的searchd.因为我们想建索引时细粒度,搜索时粗粒度。

之所以要这样,是因为如果不这样处理,很多结果会搜出来了。如有文章内容分别为中大酒店,中大假日酒店。如果搜索时也是细粒度,则有中大,酒店,中,大,大酒店,酒,店等查询词,而大酒店只在中大酒店中存在,所以只会搜出中大酒店,这并不是我们想要的。

联系作者

离职已经三个多月了,关于Sphinx的知识都快忘的差不多了,所以得赶紧记下来,以备不时之需。

离职前,在Sphinx-for-Chinese讨论组里异常活跃,很热心帮助群里的人解决问题。其中有个问题就是属性更新时无法设置为负值。于是看看Sphinx的更新属性流程。从searchd.cpp的main函数开始,到ServiceMain,TickPreforked,HandleClient,HandleClientSphinx,HandleCommandUpdate在这里看到

1
2
3
4
5
6
7
8
9
10
11
12
13
ARRAY_FOREACH ( i, tUpd.m_dAttrs )
{
tUpd.m_dAttrs[i] = tReq.GetString().ToLower().Leak();
tUpd.m_dTypes[i] = SPH_ATTR_INTEGER;
if ( iVer>=0x102 )
{
if ( tReq.GetDword() )
{
tUpd.m_dTypes[i] = SPH_ATTR_UINT32SET;
bMvaUpdate = true;
}
}
}

也就是说,这里默认是SPH_ATTR_INTEGER,而在Sphinx里,这个是无符号整型。因为在后面的一个判断语句里,有如下句子

} else
{     
    tUpd.m_dPool.Add ( tReq.GetDword() );
}

查看GetDword(),就可以知道返回的是无符号整型。

之后跳转到DoCommandUpdate,UpdateAttributes
在其中发现这样一句话:
// this is a hack
// Query parser tries to detect an attribute type. And this is wrong because, we should
// take attribute type from schema. Probably we’ll rewrite updates in future but
// for now this fix just works.
// Fixes cases like UPDATE float_attr=1 WHERE id=1;
也就是说,Sphinx更新属性时,没有去读取配置文件。而只是根据上面代码中的设定去读取更新信息,所以没有办法读取负数。一个主要的原因是,Sphinx没有32位整型数据的概念,只有32位无符号整型的概念。

因为这样,你也许会尝试将要更为负值的字段设置成64位整型,因为这个是有正负的,可是尝试之后还是不行。这是因为在代码里,没有根据配置文件去读数据,所以它还是按照上面的设定去读数据,这样还是无符号的。所以对于这个问题,还有待Sphinx的开发人员去解决.

太久没用vim看代码了,连ctags的跳转是ctrl + ] 和ctrl + o都快忘记了。

联系作者

最近因为需要在分布式group查询时自定义自己的排序,因为在许多应用中都需要定义针对应用的排序规则。例如在用户名时,需要针对name,添加最匹配原则最左侧优先,最短优先等排序规则。而要使用这些规则, 一个前提条件是,先要拿到这个字段的值。可是在Solr提供的api中,无法定义这样精细的规则,所以必须修改代码才能支持.

在此之前,要了解分布式group查询的过程.当进行分布式group查询时,从QueryComponent中,可以知道,leader会向shard发送三次请求,分别对应三个阶段 ResponseBuilder.STAGE_TOP_GROUPS,ResponseBuilder.STAGE_EXECUTE_QUERY,ResponseBuilder.STAGE_GET_FIELDS三个阶段。

第一个阶段也可称为firstPhase,主要是得到字段的分组信息,也就是得到字段有哪些分组,请求的构造在 SearchGroupsRequestFactory中.在shard中,对这次请求作出响应是在QueryComponent中的process函数 内,if (params.getBool(GroupParams.GROUP_DISTRIBUTED_FIRST, false)) 中完成的,查询得到的结果由SearchGroupsResultTransformer的transform进行转换。对于shard返回的结 果,leader在SearchGroupShardResponseProcessor中进行处理.

第二个阶段也可称为secondPhase,这个阶段主要是得到每个分组内的文档id,在这个阶段,leader会将上一阶段得到的分组 信息发给shard,请求的构造在TopGroupsShardRequestFactory中.在shard中,对这次请求作出响应是在 QueryComponent中的process函数内,else if (params.getBool(GroupParams.GROUP_DISTRIBUTED_SECOND, false))中完成,查询得到的结果由TopGroupsResultTransformer的transform函数进行转换。对于shard返回的 结果,leader在TopGroupsShardResponseProcessor中进行处理

第三个阶段主要是得到文档的字段信息,在这个阶段,leader会将最终结果中的文档id发送给shard,请求的构造在 StoredFieldsShardRequestFactory中.在shard中,对这次请求作出响应是在QueryComponent中的 process函数内,String ids = params.get(ShardParams.IDS);语句后的if (ids != null)中。对于shard返回的结果,leader是在StoredFieldsShardResponseProcessor中.

分布式group查询的过程差不多就这样,以后再介绍如何定义自己的排序。

联系作者

试想这样一种情形,一个publish_time,原先是只索引不保存,运行了很长一段时间后,发现需要返回这个字段,于是改成既索引又保存。这样新进来的数据就可以返回这个字段的值,可是原先保存的数据将无法返回这个字段的值,因为没有保存。那如何解决这个问题?

一个解决的办法是将数据重新跑一遍,重建索引,这样所有的数据都可以返回publish_time这个字段。想想还有没有其它办法,看到建了索引,这样还是有办法可以拿到数据,一个解决的办法是读fiedcache. 索引加载时,会在fieldcache里记录字段信息,这样可以提高字段查询的速度。事实上,在上述例子中,进行publish_time字段查询时,就可以拿到所有数据的publish_time字段信息,而这些信息就是来自于fieldcache.

于是找到了一个切入点,在进行段合并时,将之前没有保存的字段信息从fieldcache中读出,写到新的段中,这样新生成的段中,所有数据都会有publish_time字段信息。

于是问题的关键就变成了如何处理在段合并时,读取fieldcache信息,并且增加到新段中.从《Lucene原理与代码分析》中可以知道,段合并主要是在SegmentMerger中完成,具体是在copyFieldsWithDeletions和copyFieldsNoDeletions中。看名字就可以知道这两个函数是对应的,所以只要讨论其中一个就行了。在合并时主要分两种情况,一种是合并的段所有字段的顺序和个数都是一样的,这样只要将段数据复制到新段中即可,另一种则需要像添加一篇新文档一样将段中的文档一篇篇添加,具体体现就在fieldsWriter.addDocument(doc);这句。

对于后一情况,需要从fieldcache中读取之前没有保存的字段,如FieldCache.DEFAULT.getInts(reader, fieldName),这里的reader是一个SegmentReader实例, fieldName则是字段名,这样就可以得到字段的值,。而文档已经由Document doc = reader.document(docCount, fieldSelectorMerge)读出,之后构建一个Field将它添加到文档中,并将文档添加到段中即可.

需要注意的是,reader一定要加载索引,否则会报,terms index was not loaded when this reader was created错误.

还有就是,对于int,double等数值型数据,需要调用Field(String name, byte[] value)方法,也就是先将int,double等转化成byte[]数组,之后再构建。具体转化方法参见int,double等转化成byte数组.

联系作者

最近需要用到这个功能,本来想自己写,怕写错了,上网找了一下,都没找到合适的。看了solr与源码中的TrieField.java,有这一部分的代码,copy到这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ByteUtil {
public static int toInt(byte[] arr) {
return (arr[0] << 24) | ((arr[1] & 0xff) << 16)
| ((arr[2] & 0xff) << 8) | (arr[3] & 0xff);
}

public static long toLong(byte[] arr) {
int high = (arr[0] << 24) | ((arr[1] & 0xff) << 16)
| ((arr[2] & 0xff) << 8) | (arr[3] & 0xff);
int low = (arr[4] << 24) | ((arr[5] & 0xff) << 16)
| ((arr[6] & 0xff) << 8) | (arr[7] & 0xff);
return (((long) high) << 32) | (low & 0x0ffffffffL);
}

public static float toFloat(byte[] arr) {
return Float.intBitsToFloat(toInt(arr));
}

public static double toDouble(byte[] arr) {
return Double.longBitsToDouble(toLong(arr));
}

public static byte[] toArr(int val) {
byte[] arr = new byte[4];
arr[0] = (byte) (val >>> 24);
arr[1] = (byte) (val >>> 16);
arr[2] = (byte) (val >>> 8);
arr[3] = (byte) (val);
return arr;
}

public static byte[] toArr(long val) {
byte[] arr = new byte[8];
arr[0] = (byte) (val >>> 56);
arr[1] = (byte) (val >>> 48);
arr[2] = (byte) (val >>> 40);
arr[3] = (byte) (val >>> 32);
arr[4] = (byte) (val >>> 24);
arr[5] = (byte) (val >>> 16);
arr[6] = (byte) (val >>> 8);
arr[7] = (byte) (val);
return arr;
}

public static byte[] toArr(float val) {
return toArr(Float.floatToRawIntBits(val));
}

public static byte[] toArr(double val) {
return toArr(Double.doubleToRawLongBits(val));
}
}

联系作者