编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
        • 4.1 字符串概述
          • 4.1.1 String类介绍
          • 4.1.2 字符串不可变性
          • 4.1.3 字符串常量池
          • 4.1.4 综合案例:字符串内存分析器
          • 4.1.5 训练题
        • 4.2 字符串操作
          • 4.2.1 创建字符串
          • 4.2.2 字符串常用方法
          • 4.2.3 字符串比较
          • 4.2.4 字符串转换
          • 4.2.5 字符串格式化
          • 4.2.6 综合案例:文本处理工具
          • 4.2.7 字符串操作训练题
        • 4.3 StringBuilder和StringBuffer
          • 4.3.1 为什么需要StringBuilder
          • 4.3.2 StringBuilder用法
          • 4.3.3 StringBuilder和StringBuffer区别
          • 4.3.4 StringBuilder底层原理
          • 4.3.5 综合案例:高效字符串拼接对比
          • 4.3.6 StringBuilder训练题
        • 4.4 数组
          • 4.4.1 数组基本概念
          • 4.4.2 数组的声明和初始化
          • 4.4.3 访问数组元素
          • 4.4.4 数组遍历
          • 4.4.5 多维数组
          • 4.4.6 数组作为方法参数
          • 4.4.7 Arrays工具类
          • 4.4.8 综合案例:数组操作工具集
          • 4.4.9 数组训练题
          • 4.4.10 字符串反转
          • 4.4.11 数组排序
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

字符串和数组

# 04.字符串和数组

# 目录介绍

  • 4.1 字符串概述
    • 4.1.1 String类介绍
    • 4.1.2 字符串不可变性
    • 4.1.3 字符串常量池
    • 4.1.4 综合案例:字符串内存分析器
    • 4.1.5 训练题
  • 4.2 字符串操作
    • 4.2.1 创建字符串
    • 4.2.2 字符串常用方法
    • 4.2.3 字符串比较
    • 4.2.4 字符串转换
    • 4.2.5 字符串格式化
    • 4.2.6 综合案例:文本处理工具
    • 4.2.7 字符串操作训练题
  • 4.3 StringBuilder和StringBuffer
    • 4.3.1 为什么需要StringBuilder
    • 4.3.2 StringBuilder用法
    • 4.3.3 StringBuilder和StringBuffer区别
    • 4.3.4 StringBuilder底层原理
    • 4.3.5 综合案例:高效字符串拼接对比
    • 4.3.6 StringBuilder训练题
  • 4.4 数组
    • 4.4.1 数组基本概念
    • 4.4.2 数组的声明和初始化
    • 4.4.3 访问数组元素
    • 4.4.4 数组遍历
    • 4.4.5 多维数组
    • 4.4.6 数组作为方法参数
    • 4.4.7 Arrays工具类
    • 4.4.8 综合案例:数组操作工具集
    • 4.4.9 数组训练题
    • 4.4.10 字符串反转
    • 4.4.11 数组排序

# 4.1 字符串概述

# 4.1.1 String类介绍

Java 中的字符串是对象,使用 String 类表示。String 类在 java.lang 包中,自动导入,不需要手动 import。

String s1 = "Hello World";         // 字面量方式
String s2 = new String("Hello");   // new 方式
1
2

对比 C++:C++ 有两种字符串——C 风格字符串(char[])和 std::string。Java 只有 String 类,没有 C 风格的字符数组字符串。

# 4.1.2 字符串不可变性

Java 的 String 是不可变的(immutable):一旦创建,内容不能被修改。

String s = "Hello";
s = s + " World";  // 没有修改原字符串,而是创建了新字符串
// 原来的 "Hello" 对象没有改变,s 指向了新的 "Hello World" 对象
1
2
3

为什么设计成不可变?

  1. 线程安全:不可变对象天然是线程安全的。
  2. 字符串常量池:相同内容的字符串可以共享。
  3. 安全性:作为 HashMap 的 key、网络连接参数等不会被意外修改。

String 不可变性的底层原理:在 JDK 8 及之前,String 内部使用 private final char[] value 存储字符数据;JDK 9+ 改为 private final byte[] value(Latin1 编码用1字节/字符,UTF-16 用2字节/字符,称为 Compact Strings 优化)。final 保证引用不可变,而 private 且不提供修改方法保证内容不可变。hashCode() 只在首次调用时计算并缓存到 private int hash 字段,后续调用直接返回缓存值——这正是因为不可变性才能安全缓存。

# 4.1.3 字符串常量池

Java 维护了一个字符串常量池(String Pool),用于存储字符串字面量。

字符串常量池的底层原理:JDK 7 之前,字符串常量池位于方法区(永久代 PermGen);JDK 7+ 移至**堆内存(Heap)**中,这样常量池中的字符串对象也能被 GC 回收。字面量 "Hello" 在编译时写入 .class 文件的常量池(Constant Pool),类加载时调用 String.intern() 将其放入运行时字符串常量池。new String("Hello") 会在堆上创建新对象,但传入的 "Hello" 字面量仍来自常量池。可以手动调用 intern() 方法将堆上的字符串放入常量池并返回池中引用。

String s1 = "Hello";  // 放入常量池
String s2 = "Hello";  // 从常量池中获取同一个对象
String s3 = new String("Hello");  // 在堆上创建新对象

System.out.println(s1 == s2);  // true(同一个对象)
System.out.println(s1 == s3);  // false(不同对象)
System.out.println(s1.equals(s3));  // true(内容相同)
1
2
3
4
5
6
7

# 4.1.4 综合案例:字符串内存分析器

编写一个程序,演示字符串常量池、不可变性和 intern() 方法的行为,帮助理解 String 的内存模型。

public class StringMemoryAnalyzer {
    public static void main(String[] args) {
        // 常量池复用验证
        System.out.println("===== 常量池复用 =====");
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = new String("Hello");
        String s4 = s3.intern();

        System.out.println("s1 == s2: " + (s1 == s2));           // true 常量池同一对象
        System.out.println("s1 == s3: " + (s1 == s3));           // false 堆上新对象
        System.out.println("s1 == s4: " + (s1 == s4));           // true intern返回常量池引用
        System.out.println("s1.equals(s3): " + s1.equals(s3));   // true 内容相同

        // 编译期常量折叠
        System.out.println("\n===== 编译期优化 =====");
        String a = "Hello" + "World";      // 编译期合并为"HelloWorld"
        String b = "HelloWorld";
        String c = "Hello";
        String d = c + "World";            // 运行时拼接,新对象
        System.out.println("编译期拼接 == 字面量: " + (a == b));  // true
        System.out.println("运行时拼接 == 字面量: " + (d == b));  // false

        // 不可变性演示
        System.out.println("\n===== 不可变性 =====");
        String original = "Java";
        String modified = original.concat(" Language");
        System.out.println("original: " + original);    // 未改变
        System.out.println("modified: " + modified);     // 新对象
        System.out.println("原始未变: " + (original.equals("Java")));

        // hashCode缓存验证
        System.out.println("\n===== hashCode缓存 =====");
        String test = "Test String";
        long start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) test.hashCode();
        long time1 = System.nanoTime() - start;

        start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) test.hashCode();
        long time2 = System.nanoTime() - start;
        System.out.println("首次100万次hashCode: " + time1 + "ns");
        System.out.println("再次100万次hashCode: " + time2 + "ns (缓存命中)");
    }
}
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

# 4.1.5 训练题

  1. 选择题:以下哪个说法是正确的?

    • A. String s = "abc" 和 String s = new String("abc") 创建了相同数量的对象
    • B. String 的不可变性是因为 final 修饰了 char[]/byte[] 的引用
    • C. String 的 hashCode() 每次调用都会重新计算
    • D. JDK 9 之后 String 内部使用 byte[] 替代了 char[]
  2. 代码题:以下代码共创建了几个 String 对象?请分别说明在堆上和常量池中的分布:

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = new String("Hello");
    String s4 = new String("Hello");
    
    1
    2
    3
    4
  3. 思考题:String 的不可变性设计虽然带来了线程安全和缓存优势,但也带来了性能问题(如频繁拼接产生大量临时对象)。假如让你重新设计 String 类,你会如何在不可变性和性能之间取得平衡?

# 4.2 字符串操作

# 4.2.1 创建字符串

// 字面量方式(推荐)
String s1 = "Hello";

// new 方式
String s2 = new String("Hello");

// 字符数组转字符串
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String s3 = new String(chars);

// 字节数组转字符串
byte[] bytes = {72, 101, 108, 108, 111};
String s4 = new String(bytes);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.2.2 字符串常用方法

String s = "Hello World";

// 获取长度
System.out.println(s.length());           // 11

// 获取字符
System.out.println(s.charAt(0));          // H

// 查找子串
System.out.println(s.indexOf("World"));   // 6
System.out.println(s.contains("World"));  // true

// 截取子串
System.out.println(s.substring(6));       // World
System.out.println(s.substring(0, 5));    // Hello

// 大小写转换
System.out.println(s.toUpperCase());      // HELLO WORLD
System.out.println(s.toLowerCase());      // hello world

// 去除首尾空格
String s2 = "  Hello  ";
System.out.println(s2.trim());            // Hello

// 替换
System.out.println(s.replace("World", "Java"));  // Hello Java

// 分割
String csv = "张三,李四,王五";
String[] names = csv.split(",");
// names = ["张三", "李四", "王五"]

// 判断开头结尾
System.out.println(s.startsWith("Hello"));  // true
System.out.println(s.endsWith("World"));    // true

// 判断是否为空
System.out.println("".isEmpty());           // true
System.out.println("  ".isBlank());         // true (JDK 11+)
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

# 4.2.3 字符串比较

String s1 = "Hello";
String s2 = "hello";

// equals:区分大小写
System.out.println(s1.equals(s2));             // false

// equalsIgnoreCase:不区分大小写
System.out.println(s1.equalsIgnoreCase(s2));   // true

// compareTo:字典序比较,返回差值
System.out.println("abc".compareTo("abd"));    // -1
System.out.println("abc".compareTo("abc"));    // 0
System.out.println("abd".compareTo("abc"));    // 1
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.2.4 字符串转换

// 基本类型转字符串
String s1 = String.valueOf(123);       // "123"
String s2 = String.valueOf(3.14);      // "3.14"
String s3 = String.valueOf(true);      // "true"
String s4 = 123 + "";                  // "123"(简便写法)

// 字符串转基本类型
int i = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true");

// 字符串转字符数组
char[] chars = "Hello".toCharArray();

// 字符数组转字符串
String s = new String(chars);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.2.5 字符串格式化

String name = "张三";
int age = 25;
double salary = 8500.50;

// String.format
String info = String.format("姓名:%s,年龄:%d,薪资:%.2f", name, age, salary);
System.out.println(info);
// 输出:姓名:张三,年龄:25,薪资:8500.50

// printf(直接输出)
System.out.printf("姓名:%s,年龄:%d%n", name, age);
1
2
3
4
5
6
7
8
9
10
11

# 4.2.6 综合案例:文本处理工具

编写一个程序,综合运用字符串的创建、常用方法、比较、转换和格式化,实现一个简单的文本分析工具。

public class TextProcessor {
    public static void main(String[] args) {
        String text = "  Hello, World! Welcome to Java Programming. Java is great!  ";

        // 基本处理
        System.out.println("===== 基本处理 =====");
        String trimmed = text.trim();
        System.out.println("原始: [" + text + "]");
        System.out.println("去空格: [" + trimmed + "]");
        System.out.println("长度: " + trimmed.length());
        System.out.println("大写: " + trimmed.toUpperCase());
        System.out.println("小写: " + trimmed.toLowerCase());

        // 查找和替换
        System.out.println("\n===== 查找和替换 =====");
        System.out.println("包含Java: " + trimmed.contains("Java"));
        System.out.println("Java首次位置: " + trimmed.indexOf("Java"));
        System.out.println("Java末次位置: " + trimmed.lastIndexOf("Java"));
        System.out.println("替换Java→Python: " + trimmed.replace("Java", "Python"));
        System.out.println("以Hello开头: " + trimmed.startsWith("Hello"));
        System.out.println("以!结尾: " + trimmed.endsWith("!"));

        // 截取和分割
        System.out.println("\n===== 截取和分割 =====");
        System.out.println("前5字符: " + trimmed.substring(0, 5));
        String[] words = trimmed.split("[\\s,!.]+");
        System.out.println("单词数: " + words.length);
        System.out.print("所有单词: ");
        for (String w : words) System.out.print("[" + w + "] ");
        System.out.println();

        // 统计单词频率
        System.out.println("\n===== 单词频率(简易版) =====");
        String lower = trimmed.toLowerCase();
        String[] allWords = lower.split("[\\s,!.]+");
        for (String target : new String[]{"java", "hello", "to"}) {
            int count = 0;
            for (String w : allWords) {
                if (w.equals(target)) count++;
            }
            System.out.println("  \"" + target + "\" 出现 " + count + " 次");
        }

        // 字符串格式化
        System.out.println("\n===== 格式化输出 =====");
        String name = "张三";
        int age = 25;
        double salary = 15800.50;
        System.out.println(String.format("姓名:%-6s 年龄:%3d 薪资:¥%,.2f", name, age, salary));

        // 字符串与其他类型互转
        System.out.println("\n===== 类型转换 =====");
        int num = Integer.parseInt("12345");
        String numStr = String.valueOf(num * 2);
        char[] chars = "Hello".toCharArray();
        String fromChars = new String(chars);
        System.out.println("字符串→int: " + num);
        System.out.println("int→字符串: " + numStr);
        System.out.println("字符串→char[]: " + java.util.Arrays.toString(chars));
        System.out.println("char[]→字符串: " + fromChars);
    }
}
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
53
54
55
56
57
58
59
60
61
62

# 4.2.7 字符串操作训练题

训练1:编写一个方法 boolean isPalindrome(String s),判断字符串是否为回文(忽略大小写和空格),如 "A man a plan a canal Panama" → true。

训练2:编写一个方法 String compress(String s),实现基本的字符串压缩。如 "aabcccccaaa" → "a2b1c5a3"。如果压缩后更长,返回原字符串。

训练3:以下代码创建了几个 String 对象?分别在哪里?

String s1 = "Hello";
String s2 = new String("Hello");
String s3 = s2.intern();
System.out.println(s1 == s3);  // ?
1
2
3
4

思考:String.intern() 方法的作用是什么?在什么场景下使用可以优化内存?使用不当会有什么风险?(提示:JDK 6 的永久代 vs JDK 7+ 的堆)

# 4.3 StringBuilder和StringBuffer

# 4.3.1 为什么需要StringBuilder

String 是不可变的,每次拼接都会创建新对象,性能较差:

// 不推荐:大量拼接时性能差
String s = "";
for (int i = 0; i < 10000; i++) {
    s += i;  // 每次都创建新 String 对象
}

// 推荐:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);  // 在原对象上追加,不创建新对象
}
String result = sb.toString();
1
2
3
4
5
6
7
8
9
10
11
12

# 4.3.2 StringBuilder用法

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString());  // Hello World

// 链式调用
String result = new StringBuilder()
    .append("姓名:").append("张三")
    .append(",年龄:").append(25)
    .toString();

// 其他常用方法
sb.insert(5, ",");      // 在指定位置插入
sb.delete(5, 6);         // 删除指定范围
sb.replace(0, 5, "Hi"); // 替换指定范围
sb.reverse();            // 反转
System.out.println(sb.length());  // 获取长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4.3.3 StringBuilder和StringBuffer区别

对比项 StringBuilder StringBuffer
线程安全 不安全 安全(方法加了 synchronized)
性能 快 慢
适用场景 单线程环境 多线程环境

实际开发中绝大多数情况使用 StringBuilder 即可。

# 4.3.4 StringBuilder底层原理

疑惑:StringBuilder 为什么比 String 拼接快?

答疑:String 每次拼接 + 都创建新的 String 对象和新的 char[]/byte[] 数组。而 StringBuilder 内部维护一个可扩展的数组,append() 只是向数组尾部追加数据,只有容量不足时才扩容。

论证:

// String 拼接(编译后的字节码,JDK 8):
String s = "a" + "b" + "c";
// 编译器优化为常量折叠:"abc"(编译期确定的常量会直接合并)

// 变量拼接(JDK 8 编译后):
String s = str1 + str2;
// 等价于 new StringBuilder().append(str1).append(str2).toString()
// 但如果在循环中,每次迭代都会创建新的 StringBuilder!

// JDK 9+ 使用 StringConcatFactory(invokedynamic),更高效
1
2
3
4
5
6
7
8
9
10

结果展示——性能对比(拼接10万次):

方式 耗时 内存消耗
String += ~5000ms 大量临时对象
StringBuilder ~3ms 单对象扩容
StringBuffer ~5ms 单对象扩容 + 同步开销

# 4.3.5 综合案例:高效字符串拼接对比

编写一个程序,对比 String 拼接、StringBuilder 和 StringBuffer 的性能,并展示 StringBuilder 的常用操作。

public class StringBuilderDemo {
    public static void main(String[] args) {
        // 性能对比
        System.out.println("===== 性能对比(拼接10000次) =====");
        int count = 10000;

        // String += 拼接
        long start = System.currentTimeMillis();
        String s = "";
        for (int i = 0; i < count; i++) s += i;
        long t1 = System.currentTimeMillis() - start;
        System.out.println("String +=: " + t1 + "ms, 长度=" + s.length());

        // StringBuilder
        start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) sb.append(i);
        long t2 = System.currentTimeMillis() - start;
        System.out.println("StringBuilder: " + t2 + "ms, 长度=" + sb.length());

        // StringBuffer(线程安全)
        start = System.currentTimeMillis();
        StringBuffer sbuf = new StringBuffer();
        for (int i = 0; i < count; i++) sbuf.append(i);
        long t3 = System.currentTimeMillis() - start;
        System.out.println("StringBuffer: " + t3 + "ms, 长度=" + sbuf.length());

        // StringBuilder常用操作
        System.out.println("\n===== StringBuilder操作 =====");
        StringBuilder builder = new StringBuilder("Hello World");
        System.out.println("原始: " + builder);

        builder.insert(5, ",");
        System.out.println("insert(5,','): " + builder);

        builder.delete(5, 6);
        System.out.println("delete(5,6): " + builder);

        builder.replace(6, 11, "Java");
        System.out.println("replace World→Java: " + builder);

        builder.reverse();
        System.out.println("reverse: " + builder);

        // 链式调用构建SQL
        System.out.println("\n===== 链式调用构建文本 =====");
        String sql = new StringBuilder()
                .append("SELECT * FROM users")
                .append(" WHERE age > ").append(18)
                .append(" AND name LIKE '").append("%张%").append("'")
                .append(" ORDER BY id")
                .append(" LIMIT ").append(10)
                .toString();
        System.out.println(sql);

        // 容量和长度
        System.out.println("\n===== 容量机制 =====");
        StringBuilder cap = new StringBuilder();
        System.out.println("初始容量: " + cap.capacity() + ", 长度: " + cap.length());
        cap.append("Hello");
        System.out.println("追加后容量: " + cap.capacity() + ", 长度: " + cap.length());
        cap.append("This is a longer string to trigger expansion");
        System.out.println("扩容后容量: " + cap.capacity() + ", 长度: " + cap.length());
    }
}
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
53
54
55
56
57
58
59
60
61
62
63
64
65

# 4.3.6 StringBuilder训练题

训练1:使用 StringBuilder 实现字符串反转,但只反转字母,不反转数字和符号。如 "a1b2c3" → "c1b2a3"。

训练2:StringBuilder 的默认初始容量是多少?扩容策略是什么?与 ArrayList 的扩容策略有何异同?

# 4.4 数组

# 4.4.1 数组基本概念

数组是一种用于存储相同类型元素的连续内存数据结构。Java 中数组的大小在创建后不可改变。

数组的底层原理:Java 数组在 JVM 中是一个特殊的对象,由 JVM 直接创建(不是通过类加载器)。数组对象的内存布局为:对象头(Mark Word + 类型指针 + 数组长度)+ 连续的元素数据。基本类型数组(如 int[])直接存储值,引用类型数组(如 String[])存储的是引用(指针)。数组越界检查由 JVM 在每次访问时执行,虽然有微小的性能开销,但 JIT 编译器会进行边界检查消除(Bounds Check Elimination)优化——如果编译器能证明索引不会越界,就会跳过检查。

对比 C++:C++ 的数组分为 C 风格数组和 std::array/std::vector。Java 的数组是对象,自带 length 属性,有边界检查(越界抛异常),比 C++ 安全。

# 4.4.2 数组的声明和初始化

// 声明方式(推荐第一种)
int[] arr1;     // Java 风格(推荐)
int arr2[];     // C++ 风格(不推荐)

// 动态初始化:指定大小,默认值为0
int[] nums = new int[5];  // [0, 0, 0, 0, 0]

// 静态初始化:直接给出元素
int[] nums2 = {1, 2, 3, 4, 5};
int[] nums3 = new int[]{1, 2, 3, 4, 5};

// 字符串数组
String[] names = {"张三", "李四", "王五"};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.4.3 访问数组元素

int[] arr = {10, 20, 30, 40, 50};

// 通过索引访问(从0开始)
System.out.println(arr[0]);  // 10
System.out.println(arr[4]);  // 50

// 获取数组长度
System.out.println(arr.length);  // 5

// 越界访问会抛异常(比 C++ 安全)
// System.out.println(arr[5]);  // ArrayIndexOutOfBoundsException
1
2
3
4
5
6
7
8
9
10
11

# 4.4.4 数组遍历

int[] arr = {10, 20, 30, 40, 50};

// 方式1:普通 for 循环
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 方式2:增强 for 循环(for-each)
for (int num : arr) {
    System.out.println(num);
}

// 方式3:Arrays.toString 直接打印
System.out.println(Arrays.toString(arr));
// 输出:[10, 20, 30, 40, 50]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对比 C++:C++11 也有范围 for 循环 for (int x : arr),但 C++ 没有 Arrays.toString 这样方便的工具。

# 4.4.5 多维数组

// 二维数组
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 访问元素
System.out.println(matrix[1][2]);  // 6

// 遍历二维数组
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

// Java 支持不规则数组(每行长度可以不同)
int[][] irregular = new int[3][];
irregular[0] = new int[]{1, 2};
irregular[1] = new int[]{3, 4, 5};
irregular[2] = new int[]{6};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4.4.6 数组作为方法参数

public static void printArray(int[] arr) {
    for (int num : arr) {
        System.out.print(num + " ");
    }
    System.out.println();
}

public static int[] reverseArray(int[] arr) {
    int[] result = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        result[i] = arr[arr.length - 1 - i];
    }
    return result;
}

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5};
    printArray(arr);
    int[] reversed = reverseArray(arr);
    printArray(reversed);  // 5 4 3 2 1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.4.7 Arrays工具类

java.util.Arrays 提供了大量数组操作的工具方法:

import java.util.Arrays;

int[] arr = {5, 3, 1, 4, 2};

// 排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));  // [1, 2, 3, 4, 5]

// 二分查找(数组必须已排序)
int index = Arrays.binarySearch(arr, 3);
System.out.println(index);  // 2

// 填充
int[] filled = new int[5];
Arrays.fill(filled, 10);
System.out.println(Arrays.toString(filled));  // [10, 10, 10, 10, 10]

// 复制
int[] copy = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(copy));  // [1, 2, 3]

// 比较
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(Arrays.equals(a, b));  // true
System.out.println(a == b);               // false
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

# 4.4.8 综合案例:数组操作工具集

编写一个程序,综合运用数组的声明、初始化、遍历、排序、查找、多维数组和 Arrays 工具类。

import java.util.Arrays;

public class ArrayToolkit {
    // 查找最大值和最小值
    static int[] findMinMax(int[] arr) {
        int min = arr[0], max = arr[0];
        for (int n : arr) {
            if (n < min) min = n;
            if (n > max) max = n;
        }
        return new int[]{min, max};
    }

    // 合并两个有序数组
    static int[] mergeSorted(int[] a, int[] b) {
        int[] result = new int[a.length + b.length];
        int i = 0, j = 0, k = 0;
        while (i < a.length && j < b.length) {
            result[k++] = a[i] <= b[j] ? a[i++] : b[j++];
        }
        while (i < a.length) result[k++] = a[i++];
        while (j < b.length) result[k++] = b[j++];
        return result;
    }

    // 去重(保持顺序)
    static int[] removeDuplicates(int[] arr) {
        if (arr.length == 0) return arr;
        int[] temp = new int[arr.length];
        int count = 0;
        for (int n : arr) {
            boolean exists = false;
            for (int j = 0; j < count; j++) {
                if (temp[j] == n) { exists = true; break; }
            }
            if (!exists) temp[count++] = n;
        }
        return Arrays.copyOf(temp, count);
    }

    public static void main(String[] args) {
        // 基本操作
        System.out.println("===== 基本操作 =====");
        int[] arr = {64, 25, 12, 22, 11, 25, 64};
        System.out.println("原数组: " + Arrays.toString(arr));
        int[] minMax = findMinMax(arr);
        System.out.println("最小值: " + minMax[0] + ", 最大值: " + minMax[1]);

        // 排序和查找
        int[] sorted = Arrays.copyOf(arr, arr.length);
        Arrays.sort(sorted);
        System.out.println("排序后: " + Arrays.toString(sorted));
        int idx = Arrays.binarySearch(sorted, 22);
        System.out.println("二分查找22: 索引=" + idx);

        // 去重
        int[] unique = removeDuplicates(arr);
        System.out.println("去重后: " + Arrays.toString(unique));

        // 合并有序数组
        System.out.println("\n===== 合并有序数组 =====");
        int[] a = {1, 3, 5, 7};
        int[] b = {2, 4, 6, 8, 10};
        int[] merged = mergeSorted(a, b);
        System.out.println(Arrays.toString(a) + " + " + Arrays.toString(b));
        System.out.println("合并: " + Arrays.toString(merged));

        // Arrays工具类
        System.out.println("\n===== Arrays工具 =====");
        int[] filled = new int[5];
        Arrays.fill(filled, 7);
        System.out.println("fill(7): " + Arrays.toString(filled));
        System.out.println("equals: " + Arrays.equals(new int[]{1,2}, new int[]{1,2}));

        // 多维数组:杨辉三角
        System.out.println("\n===== 杨辉三角(前6行) =====");
        int[][] pascal = new int[6][];
        for (int i = 0; i < 6; i++) {
            pascal[i] = new int[i + 1];
            pascal[i][0] = pascal[i][i] = 1;
            for (int j = 1; j < i; j++) {
                pascal[i][j] = pascal[i - 1][j - 1] + pascal[i - 1][j];
            }
            System.out.println("  " + " ".repeat(10 - i * 2) + Arrays.toString(pascal[i]));
        }
    }
}
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

# 4.4.9 数组训练题

训练1:实现一个方法 int[] merge(int[] a, int[] b),将两个已排序的数组合并为一个新的有序数组(归并操作,时间复杂度 O(n+m))。

训练2:实现二维数组的"螺旋矩阵"打印。给定一个 m×n 的二维数组,按顺时针螺旋顺序打印所有元素。

思考:Java 数组和 ArrayList 都可以存储一组数据,什么时候用数组更好?什么时候用 ArrayList 更好?

# 4.4.10 字符串反转

public class Main {
    public static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }

    public static void main(String[] args) {
        System.out.println(reverse("Hello World"));
        // 输出:dlroW olleH
    }
}
1
2
3
4
5
6
7
8
9
10

# 4.4.11 数组排序

import java.util.Arrays;

public class Main {
    // 冒泡排序
    public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {5, 3, 8, 1, 9, 2};
        bubbleSort(arr);
        System.out.println(Arrays.toString(arr));
        // 输出:[1, 2, 3, 5, 8, 9]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上次更新: 2026/06/10, 11:13:41
运算符
流程语句

← 运算符 流程语句→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式