| 4 bytes | <- phone.dat 版本号(如:1701即17年1月份)
------------
| 4 bytes | <- 第一个索引的偏移
-----------------------
| content | <- 记录区(每条记录内容<省份>|<城市>|<邮编>|<长途区号>\0)
-----------------------
| index | <- 索引区(每条索引内容<手机号前七位><记录区的偏移><卡类型>)
-----------------------
//读文件
const buf = fs.readFileSync(path.join(__dirname, 'phone.dat'))
//找到索引区的起点偏移,即起点位置
const indexOffset = buf.readInt32LE(4, 4)
//计算总记录数,每个索引的大小固定是9个字节
const size = (buf.length - indexOffset) / 9
每个索引9个字节的分配,前4位用于保存号码的前7位,例如1300000,接着4位用户存储该条索引对应内容的存储位置偏移量,接着1位用于存储是什么运营商,是个数字,对应内容如下
const opMap = [
'异常',
'移动', // 1
'联通', // 2
'电信', // 3
'电信虚拟运营商', // 4
'联通虚拟运营商', // 5
'移动虚拟运营商' // 6
]
//获取某条索引,pos从0开始到size
function getIndex(pos){
var index = buf.readInt32LE(indexOffset + pos * 9, 4);
var infoOffset = buf.readInt32LE(indexOffset + pos * 9 + 4, 4)
var phoneType = buf.readInt8(indexOffset + pos * 9 + 8, 1)
return {index,infoOffset,phoneType}
}
<省份>|<城市>|<邮编>|<长途区号>\0
//根据索引得到的偏移量获取具体的内容信息
function getInfo(infoOffset){
var content = buf.slice(infoOffset, infoOffset + 100)
var endIdx = 0
while (endIdx < 100 && content[endIdx] !== 0) {
endIdx++
}
return content.toString('utf8', 0, endIdx))
}
由于这个文件的索引区是按电话号码升序排列,所以可以使用二分查找的方式快速查到电话号码的归属地等详细信息,但如果想做其他诸如区号,查询效率就非常低了。
function find (phoneOrigin) {
var phone = parseInt((phoneOrigin + '').substr(0, 7))
var left = 0
var right = size - 1
while (left <= right) {
var pos = ((right + left) / 2) | 0
var index = buf.readInt32LE(indexOffset + pos * 9, 4)
if (index < phone) {
if (left === pos) {
return formatResult(phoneOrigin, 0, null)
}
left = pos
} else if (index > phone) {
if (right === pos) {
return formatResult(phoneOrigin, 0, null)
}
right = pos
} else {
// match
var infoOffset = buf.readInt32LE(indexOffset + pos * 9 + 4, 4)
var phoneType = buf.readInt8(indexOffset + pos * 9 + 8, 1)
var content = buf.slice(infoOffset, infoOffset + 100)
var endIdx = 0
while (endIdx < 100 && content[endIdx] !== '0') {
endIdx++
}
return formatResult(phoneOrigin, phoneType, content.toString('utf8', 0, endIdx))
}
}
}
function formatResult (phone, opType, content) {
var arr = (content || '||||').split('|')
console.log("content",content)
return {
phone: phone,
op: opMap[opType],
province: arr[0],
city: arr[1],
zipcode: arr[2],
areacode: arr[3]
}
}
参考:
https://github.com/xluohome/phonedata?tab=readme-ov-file
大小端字节序(Endianness)是一种用于表示多字节数据在内存中存储顺序的方式。它决定了多字节数据的高位字节和低位字节的存储顺序。
在小端字节序(Little Endian)中,较低有效字节(低位字节)存储在较低的内存地址,而较高有效字节(高位字节)存储在较高的内存地址。这意味着在内存中,多字节数据的字节顺序是从右到左的。
例如,对于一个32位整数值 0x12345678
,在小端字节序中,它在内存中的存储顺序如下:
低地址 -> 高地址
78 56 34 12
相反,在大端字节序(Big Endian)中,较高有效字节(高位字节)存储在较低的内存地址,而较低有效字节(低位字节)存储在较高的内存地址。这意味着在内存中,多字节数据的字节顺序是从左到右的。
使用相同的例子,对于一个32位整数值 0x12345678
,在大端字节序中,它在内存中的存储顺序如下:
低地址 -> 高地址
12 34 56 78
在不同的计算机体系结构和编程语言中,可能会使用不同的字节序。因此,在处理二进制数据时,我们需要了解数据的字节序,并根据需要进行适当的字节序转换。