使用Scala实现手机号码归属地查询系统
项目概述
在现代社会,手机已经成为人们生活中不可或缺的一部分。面对陌生来电,查询号码归属地可以帮助我们识别来电性质,避免骚扰和诈骗。本文将介绍如何使用Scala构建一个完整的手机号码归属地查询系统。
一、项目准备
1.1 数据模型设计
首先,我们需要定义一个数据模型来表示手机号码归属地信息:
scala
case class PhoneNumberInfo(
id: Long, // 编号
segment: String, // 号码段(前7位)
province: String, // 省份
city: String, // 城市
carrier: String, // 运营商
areaCode: String, // 区号
zipCode: String // 邮编
)
1.2 数据加载
我们从CSV文件中加载数据,这里假设数据已保存为phone_numbers.csv:
scala
import scala.io.Source
import scala.collection.mutable.ListBuffer
object PhoneNumberDataLoader {
def loadData(filePath: String): List[PhoneNumberInfo] = {
val buffer = ListBuffer[PhoneNumberInfo]()
val source = Source.fromFile(filePath, "UTF-8")
try {
for (line <- source.getLines()) {
val fields = line.split(",")
if (fields.length == 7) {
val info = PhoneNumberInfo(
fields(0).trim.toLong,
fields(1).trim,
fields(2).trim,
fields(3).trim,
fields(4).trim,
fields(5).trim,
fields(6).trim
)
buffer += info
}
}
} finally {
source.close()
}
buffer.toList
}
}
二、任务实现
2.1 任务一:识别号码类型
我们可以根据号码的前三位来判断运营商类型:
scala
object PhoneNumberAnalyzer {
// 定义运营商前缀
private val carrierPrefixes = Map(
"中国移动" -> List("134", "135", "136", "137", "138", "139", "147", "150",
"151", "152", "157", "158", "159", "178", "182", "183",
"184", "187", "188"),
"中国联通" -> List("130", "131", "132", "145", "155", "156", "166", "175",
"176", "185", "186"),
"中国电信" -> List("133", "149", "153", "173", "177", "180", "181", "189", "199")
)
/**
* 根据手机号码识别运营商
*/
def identifyCarrier(phoneNumber: String): String = {
if (phoneNumber.length < 3) {
return "未知运营商"
}
val prefix = phoneNumber.substring(0, 3)
carrierPrefixes.find { case (carrier, prefixes) =>
prefixes.contains(prefix)
} match {
case Some((carrier, _)) => carrier
case None => "未知运营商"
}
}
/**
* 验证手机号码格式
*/
def validatePhoneNumber(phoneNumber: String): Boolean = {
val pattern = "^1[3-9]\\d{9}$".r
pattern.matches(phoneNumber)
}
}
2.2 任务二:统计广州号码段数量
scala
object StatisticsAnalyzer {
/**
* 统计指定城市的号码段数量
*/
def countSegmentsByCity(data: List[PhoneNumberInfo], city: String): Int = {
data.count(_.city == city)
}
/**
* 统计广州号码段(包含详细统计信息)
*/
def analyzeGuangzhouSegments(data: List[PhoneNumberInfo]): Map[String, Int] = {
val guangzhouData = data.filter(_.city == "广州")
// 按运营商统计
val carrierStats = guangzhouData.groupBy(_.carrier)
.map { case (carrier, items) =>
s"${carrier}广州号码段" -> items.length
}
// 总数
val total = Map("广州总号码段数" -> guangzhouData.length)
total ++ carrierStats
}
}
2.3 任务三:根据归属地对手机号码段分组
scala
object GroupingAnalyzer {
/**
* 按省份分组
*/
def groupByProvince(data: List[PhoneNumberInfo]): Map[String, List[PhoneNumberInfo]] = {
data.groupBy(_.province)
}
/**
* 按城市分组
*/
def groupByCity(data: List[PhoneNumberInfo]): Map[String, List[PhoneNumberInfo]] = {
data.groupBy(_.city)
}
/**
* 按省份和运营商双重分组
*/
def groupByProvinceAndCarrier(data: List[PhoneNumberInfo]):
Map[String, Map[String, List[PhoneNumberInfo]]] = {
data.groupBy(_.province)
.map { case (province, items) =>
province -> items.groupBy(_.carrier)
}
}
/**
* 获取省份号码段统计
*/
def getProvinceStatistics(data: List[PhoneNumberInfo]): List[(String, Int)] = {
groupByProvince(data)
.map { case (province, items) => (province, items.length) }
.toList
.sortBy(-_._2) // 按数量降序排序
}
}
2.4 任务四:手机号码归属地信息查询程序
scala
object PhoneNumberQuerySystem {
// 使用Map提高查询效率
private var segmentMap: Map[String, PhoneNumberInfo] = Map()
/**
* 初始化查询系统
*/
def initialize(data: List[PhoneNumberInfo]): Unit = {
segmentMap = data.map(info => info.segment -> info).toMap
}
/**
* 查询手机号码归属地
*/
def queryByPhoneNumber(phoneNumber: String): Option[PhoneNumberInfo] = {
if (!PhoneNumberAnalyzer.validatePhoneNumber(phoneNumber)) {
return None
}
val segment = phoneNumber.substring(0, 7)
segmentMap.get(segment)
}
/**
* 模糊查询(按城市查询所有号码段)
*/
def queryByCity(data: List[PhoneNumberInfo], city: String): List[PhoneNumberInfo] = {
data.filter(_.city.contains(city))
}
/**
* 按运营商查询
*/
def queryByCarrier(data: List[PhoneNumberInfo], carrier: String): List[PhoneNumberInfo] = {
data.filter(_.carrier == carrier)
}
/**
* 交互式查询界面
*/
def interactiveQuery(data: List[PhoneNumberInfo]): Unit = {
println("=== 手机号码归属地查询系统 ===")
println("1. 按手机号码查询")
println("2. 按城市查询")
println("3. 按运营商查询")
println("4. 退出")
var continue = true
while (continue) {
print("\n请选择查询方式 (1-4): ")
val choice = scala.io.StdIn.readLine()
choice match {
case "1" =>
print("请输入手机号码: ")
val phone = scala.io.StdIn.readLine()
queryByPhoneNumber(phone) match {
case Some(info) =>
println(s"\n查询结果:")
println(s"号码段: ${info.segment}")
println(s"归属地: ${info.province}省${info.city}市")
println(s"运营商: ${info.carrier}")
println(s"区号: ${info.areaCode}")
println(s"邮编: ${info.zipCode}")
case None =>
println("未找到该号码的归属地信息")
}
case "2" =>
print("请输入城市名称: ")
val city = scala.io.StdIn.readLine()
val results = queryByCity(data, city)
if (results.nonEmpty) {
println(s"\n${city}的号码段 (共${results.length}个):")
results.take(10).foreach { info =>
println(s" ${info.segment} - ${info.carrier}")
}
if (results.length > 10) {
println(s" ... 还有${results.length - 10}个")
}
} else {
println("未找到该城市的号码段")
}
case "3" =>
println("请选择运营商:")
println("1. 中国移动")
println("2. 中国联通")
println("3. 中国电信")
print("选择: ")
val carrierChoice = scala.io.StdIn.readLine()
val carrier = carrierChoice match {
case "1" => "中国移动"
case "2" => "中国联通"
case "3" => "中国电信"
case _ => "未知"
}
if (carrier != "未知") {
val results = queryByCarrier(data, carrier)
println(s"\n${carrier}号码段统计:")
val stats = results.groupBy(_.province)
.map { case (province, items) =>
s" ${province}: ${items.length}个"
}
.toList
.sorted
stats.foreach(println)
}
case "4" =>
println("感谢使用,再见!")
continue = false
case _ =>
println("无效选择,请重新输入")
}
}
}
}
三、完整应用示例
scala
object MainApplication {
def main(args: Array[String]): Unit = {
// 1. 加载数据
println("正在加载数据...")
val data = PhoneNumberDataLoader.loadData("phone_numbers.csv")
println(s"数据加载完成,共${data.length}条记录")
// 2. 初始化查询系统
PhoneNumberQuerySystem.initialize(data)
// 3. 执行各种分析任务
// 任务1: 识别号码类型示例
val testNumber = "13012345678"
println(s"\n=== 任务1: 识别号码类型 ===")
println(s"测试号码: $testNumber")
println(s"运营商: ${PhoneNumberAnalyzer.identifyCarrier(testNumber)}")
// 任务2: 统计广州号码段数量
println(s"\n=== 任务2: 统计广州号码段 ===")
val guangzhouStats = StatisticsAnalyzer.analyzeGuangzhouSegments(data)
guangzhouStats.foreach { case (key, value) =>
println(s"$key: $value")
}
// 任务3: 根据归属地分组
println(s"\n=== 任务3: 归属地分组统计 ===")
val topProvinces = GroupingAnalyzer.getProvinceStatistics(data).take(5)
println("号码段数量前5的省份:")
topProvinces.foreach { case (province, count) =>
println(s" $province: $count 个")
}
// 任务4: 启动交互式查询
println(s"\n=== 任务4: 交互式查询系统 ===")
PhoneNumberQuerySystem.interactiveQuery(data)
}
}
四、性能优化建议
4.1 使用缓存提高查询性能
scala
object QueryCache {
private val cache = scala.collection.mutable.Map[String, PhoneNumberInfo]()
def getOrElseUpdate(segment: String,
compute: => Option[PhoneNumberInfo]): Option[PhoneNumberInfo] = {
cache.get(segment) match {
case Some(info) => Some(info)
case None =>
compute match {
case Some(info) =>
cache.put(segment, info)
Some(info)
case None => None
}
}
}
def clear(): Unit = cache.clear()
}
4.2 使用并行处理加速统计
scala
object ParallelProcessor {
/**
* 并行统计各省份号码段数量
*/
def parallelProvinceStatistics(data: List[PhoneNumberInfo]): Map[String, Int] = {
data.par
.groupBy(_.province)
.map { case (province, items) => province -> items.length }
.seq
.toMap
}
/**
* 并行查找多个号码
*/
def batchQuery(phoneNumbers: List[String],
data: List[PhoneNumberInfo]): List[(String, Option[PhoneNumberInfo])] = {
phoneNumbers.par.map { phone =>
phone -> PhoneNumberQuerySystem.queryByPhoneNumber(phone)
}.seq.toList
}
}
五、测试用例
scala
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class PhoneNumberQuerySpec extends AnyFlatSpec with Matchers {
val testData = List(
PhoneNumberInfo(1, "1300000", "山东", "济南", "中国联通", "0531", "250000"),
PhoneNumberInfo(2, "1300001", "江苏", "常州", "中国联通", "0519", "213000"),
PhoneNumberInfo(3, "1380000", "广东", "广州", "中国移动", "020", "510000")
)
"PhoneNumberAnalyzer" should "正确识别运营商" in {
PhoneNumberAnalyzer.identifyCarrier("13012345678") shouldBe "中国联通"
PhoneNumberAnalyzer.identifyCarrier("13812345678") shouldBe "中国移动"
PhoneNumberAnalyzer.identifyCarrier("13312345678") shouldBe "中国电信"
}
"StatisticsAnalyzer" should "正确统计广州号码段" in {
StatisticsAnalyzer.countSegmentsByCity(testData, "广州") shouldBe 1
}
"PhoneNumberQuerySystem" should "正确查询手机号码归属地" in {
PhoneNumberQuerySystem.initialize(testData)
val result = PhoneNumberQuerySystem.queryByPhoneNumber("13000001234")
result shouldBe defined
result.get.city shouldBe "济南"
}
}
六、总结
通过这个项目,我们实现了:
- 数据模型设计:使用case class定义清晰的数据结构
- 号码类型识别:基于号码前缀的运营商识别
- 数据统计:按城市、运营商等多维度统计分析
- 分组查询:灵活的归属地分组功能
- 交互式查询:用户友好的命令行界面
这个系统不仅可以帮助用户查询手机号码归属地,还可以用于数据分析,比如:
- 运营商市场份额分析
- 地区通信资源分布
- 号码段使用情况统计
Scala的函数式编程特性和强大的集合操作能力,使得这类数据处理任务变得简洁高效。希望这个项目能帮助你更好地理解Scala在实际数据处理中的应用。