0%

Scala 流程控制:顺序、分支与循环的优雅实现

流程控制是编程语言的基础,用于控制代码的执行顺序。Scala 作为一门多范式语言,在流程控制上既保留了与 Java 相似的语法,又融入了函数式编程的特性,尤其在循环和中断处理上有独特设计。本文详细介绍 Scala 的三大流程控制语句。

顺序控制

顺序控制是最基础的流程模式,代码按照从上到下的顺序依次执行,没有跳转或分支。这是所有编程语言的默认执行方式,Scala 也不例外。

1
2
3
4
5
// 顺序执行示例
val a = 10
val b = 20
val sum = a + b
println(s"a + b = $sum") // 先计算sum,再打印结果

特点

  • 无特殊关键字,代码自然流淌。
  • 适用于简单的步骤化操作(如变量定义、计算、输出)。

分支控制(if…else)

Scala 的分支控制通过 if...else if...else 实现,与 Java 语法相似,但更灵活 ——if 表达式有返回值,可直接赋值给变量。

基本语法

阅读全文 »

HBase写数据流程全解析:从请求到持久化

HBase 的写数据过程设计兼顾了 高吞吐数据可靠性,通过 “预写日志 + 内存缓存 + 异步刷盘” 的机制,在保证数据不丢失的前提下最大化写入性能。以下详细拆解写流程的每个环节,包括核心步骤、优化机制及故障恢复逻辑。

写数据核心流程概述

HBase 写操作的核心目标是 快速接收数据并确保不丢失,整体流程可概括为 “客户端请求 → 预写日志 → 内存缓存 → 异步刷盘”,具体步骤如下:

  1. 客户端向目标 RegionServer 发送写请求;
  2. RegionServer 先将数据写入预写日志(WAL/HLog),确保崩溃后可恢复;
  3. 数据写入内存缓存(MemStore),立即反馈客户端 “写成功”;
  4. 当 MemStore 达到阈值,异步刷写到磁盘(StoreFile),完成持久化;
  5. 定期合并小文件并拆分过大 Region,优化存储结构。

详细流程拆解

第一步:客户端定位目标 RegionServer

写操作前,客户端需先确定数据所属的 Region 及对应的 RegionServer,流程与读操作类似:

  • 客户端通过 ZooKeeper 获取 hbase:meta 表位置;
  • 查询 hbase:meta 表,根据 RowKey 定位目标 Region 所在的 RegionServer;
  • 客户端直接向该 RegionServer 发送写请求(携带表名、RowKey、列族:列名、值等信息)。

第二步:写入预写日志(WAL/HLog)

为防止内存数据丢失,HBase 采用 “预写日志(Write-Ahead Log,WAL)” 机制,这是数据可靠性的核心保障。

  • RegionServer 操作
    1. 接收到写请求后,RegionServer 先将数据按固定格式(包含 RowKey、列信息、时间戳、操作类型等)写入本地 HLog 文件;
    2. HLog 文件同步存储在 HDFS 上(多副本),确保即使 RegionServer 宕机,日志也不会丢失。
  • 作用:HLog 是数据的 “安全网”,当 RegionServer 崩溃且 MemStore 数据未刷盘时,可通过 HLog 重建数据。

第三步:写入内存缓存(MemStore)

数据写入 HLog 后,RegionServer 将数据写入对应 Region 的 MemStore(内存缓存):

阅读全文 »

FastDateFormat 线程安全的底层原理:从 SimpleDateFormat 的缺陷说起

在 Java 日期处理中,SimpleDateFormat 因线程不安全常导致诡异的并发问题,而 Apache Commons Lang 库的 FastDateFormat 则以线程安全为核心设计目标。本文将深入对比两者的实现差异,解析 FastDateFormat 如何通过设计规避线程安全问题,以及其缓存机制带来的性能优势。

SimpleDateFormat 的线程不安全根源

SimpleDateFormat 是 Java 原生的日期格式化工具,但在多线程环境下使用同一个实例会导致数据错乱,其根本原因在于共享的 Calendar 成员变量

核心问题:共享状态的并发修改

SimpleDateFormat 内部维护了一个 Calendar 实例(成员变量),用于解析和格式化日期。在多线程场景下,多个线程会同时操作这个共享的 Calendar,导致以下问题:

1
2
3
4
5
6
7
8
9
10
11
12
// SimpleDateFormat 的关键成员变量  
protected Calendar calendar;

// 格式化方法的核心逻辑(简化版)
private StringBuffer format(Date date, StringBuffer toAppendTo) {
// 步骤1:将日期设置到共享的 calendar 中
calendar.setTime(date); // 线程A执行到此处,尚未完成格式化

// 步骤2:基于 calendar 格式化字符串
// 若此时线程B调用 setTime 修改了 calendar,线程A的结果会被污染
// ... 格式化逻辑 ...
}
  • 并发冲突:线程 A 在执行 calendar.setTime(dateA) 后,尚未完成格式化,线程 B 调用 calendar.setTime(dateB) 覆盖了 calendar 的状态,导致线程 A 最终格式化的是 dateB 的值。
  • 表现症状:格式化结果出现随机的日期错乱(如年份、月份错误),且难以复现(与线程调度时机相关)。

常见错误用法

开发者常将 SimpleDateFormat 定义为静态变量以复用,这会放大线程安全问题:

阅读全文 »

Scala 数据类型:面向对象的类型系统

Scala 作为纯粹的面向对象语言,其数据类型系统与 Java 有显著差异 ——所有类型都是对象,不存在 Java 中的 “基本数据类型” 与 “引用类型” 的严格区分。Scala 类型系统以 Any 为根,分为 AnyVal(值类型)和 AnyRef(引用类型)两大分支,形成层次清晰的类型体系。

类型体系概览

Scala 类型系统的核心层次结构如下:

数据类型

  • 根类型:Any(所有类型的父类)
    • 值类型AnyVal(包括数值类型、布尔型等)
    • 引用类型AnyRef(包括类、特质、数组等,对应 Java 的 Object
  • 特殊类型Null(所有 AnyRef 的子类)、Nothing(所有类型的子类)

AnyVal:值类型

AnyVal 代表值类型,对应 Java 中的 “基本数据类型”,但在 Scala 中仍是对象,拥有方法和属性。常见的 AnyVal 类型包括:

类型 描述 示例
Int 32 位整数 val a: Int = 10
Long 64 位整数(后缀 L) val b: Long = 100L
Float 32 位浮点数(后缀 f) val c: Float = 3.14f
Double 64 位浮点数(默认小数类型) val d: Double = 3.14159
Boolean 布尔值(true/false) val e: Boolean = true
Char 16 位字符(单引号包裹) val f: Char = 'A'
Byte 8 位整数 val g: Byte = 0x10
Short 16 位整数 val h: Short = 32767
Unit 无返回值标记(类似 void) def hello(): Unit = println("hi")

值类型的特性

  1. 默认类型

    • 整数默认是 Int(超出范围自动推断为 Long
    • 小数默认是 Double(需显式加 f 声明 Float
    1
    2
    3
    4
    val num1 = 100      // 推断为Int
    val num2 = 10000000000L // 显式声明Long
    val num3 = 3.14 // 推断为Double
    val num4 = 3.14f // 显式声明Float
  2. 自动类型转换

    • 低精度类型可自动转换为高精度类型(类似 Java 的 “拓宽转换”)
    • 转换方向:Byte → Short → Int → Long → Float → Double
阅读全文 »

Scala 变量:声明、特性与命名规则

变量是编程语言的基础构建块,Scala 作为一门多范式语言,其变量声明方式与 Java 有显著差异,尤其体现在不可变性和类型推断上。本文详细介绍 Scala 变量的核心特性。

变量声明语法

Scala 变量声明必须初始化,基本语法如下:

1
2
// 完整语法:关键字 变量名[: 类型] = 初始值
var|val 变量名[: 变量类型] = 变量值

示例解析

1
2
3
4
5
6
7
// 1. 显式指定类型
var age: Int = 10 // 可变变量,类型为Int
val score: Double = 95.5 // 不可变变量,类型为Double

// 2. 省略类型(依赖编译器的类型推导)
var name = "Alice" // 推导为String类型
val pi = 3.14159 // 推导为Double类型
  • 类型推导:Scala 编译器能根据初始值自动推断变量类型,通常无需显式声明,简化代码。
  • 必须初始化:与 Java 不同,Scala 不允许声明未初始化的变量(如 var x: Int; 会报错),确保变量始终有明确的值。

var 与 val:可变与不可变

Scala 变量分为两种类型,核心区别在于是否可重新赋值:

var(可变变量)

  • 允许后续重新赋值
  • 类似 Java 中未用 final 修饰的变量
阅读全文 »