开发易忽视问题:字符串处理效率

在Java中,有多种方式可以进行字符串格式化,以下是几种常见的方式:

  1. 使用String.format()方法: String.format()方法提供了一种类似于C语言中的printf的格式化方式。它可以将各种类型的数据格式化为一个字符串。

     

    java

    代码解读

    复制代码

    int age = 25; String name = "Alice"; String formattedString = String.format("Name: %s, Age: %d", name, age); System.out.println(formattedString); // 输出: Name: Alice, Age: 25
  2. 使用System.out.printf()方法: System.out.printf()String.format()类似,只是它直接打印到控制台而不是返回一个字符串。

     

    java

    代码解读

    复制代码

    int age = 25; String name = "Alice"; System.out.printf("Name: %s, Age: %d%n", name, age); // 输出: Name: Alice, Age: 25
  3. 使用MessageFormat: MessageFormat类提供了一种基于占位符的格式化方式,非常适合处理复杂的国际化问题。

     

    java

    代码解读

    复制代码

    int age = 25; String name = "Alice"; String pattern = "Name: {0}, Age: {1}"; String formattedString = java.text.MessageFormat.format(pattern, name, age); System.out.println(formattedString); // 输出: Name: Alice, Age: 25
  4. 使用字符串连接(+ 操作符) : 虽然不是严格意义上的格式化,但简单的字符串连接也能满足很多场景的需求。

     

    java

    代码解读

    复制代码

    int age = 25; String name = "Alice"; String formattedString = "Name: " + name + ", Age: " + age; System.out.println(formattedString); // 输出: Name: Alice, Age: 25
  5. 使用StringBuilderStringBuffer: 当需要频繁修改字符串时,使用StringBuilderStringBuffer可以提高性能。

    篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处】即可免费获取

    java

    代码解读

    复制代码

    int age = 25; String name = "Alice"; StringBuilder builder = new StringBuilder(); builder.append("Name: ").append(name).append(", Age: ").append(age); System.out.println(builder.toString()); // 输出: Name: Alice, Age: 25

性能对比

以下是一个简单的性能测试示例,用于比较上述几种方法在拼接1000次相同字符串时的性能差异:

 

java

代码解读

复制代码

public class StringPerformanceTest { public static void main(String[] args) { int iterations = 100000; // Using + operator long startTime = System.nanoTime(); String result = ""; for (int i = 0; i < iterations; i++) { result += "test"; } long duration = System.nanoTime() - startTime; System.out.println("+ operator: " + duration / 1_000_000.0 + " ms"); // Using StringBuilder startTime = System.nanoTime(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < iterations; i++) { builder.append("test"); } duration = System.nanoTime() - startTime; System.out.println("StringBuilder: " + duration / 1_000_000.0 + " ms"); // Using StringBuffer startTime = System.nanoTime(); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < iterations; i++) { buffer.append("test"); } duration = System.nanoTime() - startTime; System.out.println("StringBuffer: " + duration / 1_000_000.0 + " ms"); // Using String.format startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { result = String.format("%s%s", result, "test"); } duration = System.nanoTime() - startTime; System.out.println("String.format: " + duration / 1_000_000.0 + " ms"); // Using MessageFormat startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { result = java.text.MessageFormat.format("{0}{1}", result, "test"); } duration = System.nanoTime() - startTime; System.out.println("MessageFormat: " + duration / 1_000_000.0 + " ms"); } }

结论

image.png

StringBuilder底层实现原理

StringBuilder 是Java中用于创建和操纵可变字符串的类。它与 StringBuffer 类似,但 StringBuilder 不是线程安全的,因此在单线程环境中性能更高。StringBuilder 底层通过一个字符数组和一些管理数组内容的方法实现其功能。以下是 StringBuilder 的底层实现原理:

主要字段

  1. char[] value:存储字符串内容的字符数组。
  2. int count:记录当前字符数组中的字符数量。

主要方法

构造函数

StringBuilder 提供了多种构造函数来初始化对象:

 

java

代码解读

复制代码

public StringBuilder() { this.value = new char[16]; } public StringBuilder(int capacity) { this.value = new char[capacity]; } public StringBuilder(String str) { this.value = new char[str.length() + 16]; append(str); }

  • 默认构造函数分配一个长度为16的字符数组。
  • 指定容量的构造函数分配指定大小的字符数组。
  • 使用字符串的构造函数根据字符串长度加上16分配字符数组,并将字符串内容追加到数组中。
append 方法

append 方法用于将字符、字符序列或其他字符串添加到 StringBuilder 中:

 

java

代码解读

复制代码

public StringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }

  1. ensureCapacityInternal 方法确保字符数组有足够的容量。如果现有容量不够,则扩容。
  2. getChars 方法将输入字符串的字符复制到 value 数组中。
  3. 更新 count 字段以反映新增加的字符数。
ensureCapacityInternal 方法

该方法用于确保字符数组有足够的容量,如果不足则进行扩容:

 

java

代码解读

复制代码

private void ensureCapacityInternal(int minimumCapacity) { if (minimumCapacity - value.length > 0) { expandCapacity(minimumCapacity); } } void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); }

  1. ensureCapacityInternal 检查是否需要扩容。如果需要,则调用 expandCapacity
  2. expandCapacity 根据当前容量的两倍加二计算新的容量。如果计算出的容量仍然小于所需容量,则直接使用所需容量。
  3. 使用 Arrays.copyOf 方法创建一个新的更大容量的数组,并将原数组内容复制到新数组中。
toString 方法

将 StringBuilder 转换为 String 对象:

 

java

代码解读

复制代码

public synchronized String toString() { return new String(value, 0, count); }

  1. 创建新的 String 对象,使用内部字符数组 value 和当前字符计数 count

总结

  • 动态扩容StringBuilder 的字符数组会在需要时自动扩容,通常是当前容量的两倍加二,以减少频繁扩容带来的开销。
  • 字符数组管理:所有字符串操作都是基于内部的字符数组进行的。
  • 非线程安全:相比 StringBufferStringBuilder 没有同步加锁机制,导致多线程情况下,如果触发数组扩容,因为扩容后需要将原数组中元素复制到新数组,并发场景下会出现覆盖的情况(多个线程同时在新数组的相同下标位置写数据,导致数组中元素丢失),所以适用于单线程环境。

通过这种设计,StringBuilder 在处理大量字符串拼接、修改等操作时能够提供较高的性能,而避免了使用不可变 String 对象所带来的大量内存分配和垃圾回收开销。

String.format()底层实现原理

String.format() 是 Java 中用于格式化字符串的一个非常强大的方法。它允许使用指定的格式将不同类型的数据组合成一个格式化的字符串。在底层,它依赖于 java.util.Formatter 类来实现实际的格式化工作。

下面我们深入探讨一下 String.format() 的底层实现原理:

基本用法

首先,让我们看看 String.format() 的基本用法:

 

java

代码解读

复制代码

String formattedString = String.format("Hello, %s! You are %d years old.", "Alice", 30); System.out.println(formattedString); // 输出: Hello, Alice! You are 30 years old.

内部实现

String.format() 方法的内部实现大致如下:

  1. 入口方法

    String.format() 提供了多种重载版本,最常见的是:

     

    java

    代码解读

    复制代码

    public static String format(String format, Object... args) { return new Formatter().format(format, args).toString(); }
  2. 创建 Formatter 对象

    Formatter 对象是核心,它负责处理格式字符串和参数,并生成最终的格式化字符串。

     

    java

    代码解读

    复制代码

    public Formatter format(String format, Object... args) { // Format string and arguments return this; }
  3. 解析格式字符串

    Formatter 包含一个私有的嵌套类 FormatSpecifierParser,该类负责解析格式字符串中的各个部分(例如 %s%d)。解析后的信息存储在多个字段中,如转换符、标志、宽度、精度等。

  4. 格式化每个参数

    在解析完格式字符串后,Formatter 会根据解析结果逐一格式化传入的参数。例如,如果遇到 %s,则会调用相应的方法将参数转换为字符串:

     

    java

    代码解读

    复制代码

    private void printString(Object arg) { if (arg == null) { append("null"); } else { append(arg.toString()); } }

    对于其他类型的格式,例如 %d(整数)、%f(浮点数)等,都有对应的处理方法。

  5. 生成最终字符串

    格式化完成后,所有部分会被拼接成一个完整的字符串,并返回给调用者:

     

    java

    代码解读

    复制代码

    @Override public String toString() { return out.toString(); }

关键步骤详细说明

1. 解析格式字符串

Formatter 会解析输入的格式字符串。以下是一个简单的解析示例:

 

java

代码解读

复制代码

private void parseFormatString(String format) { int length = format.length(); for (int i = 0; i < length; i++) { char c = format.charAt(i); if (c == '%') { // 检测到格式化标记,解析各种标志、宽度、精度和转换符 ... } else { // 普通字符直接添加到输出中 append(c); } } }

2. 处理格式化标记

对于每个检测到的格式化标记,Formatter 会提取其各个组成部分(如标志、宽度、精度等),并调用适当的方法进行处理。

3. 调用具体的格式化方法

根据解析结果和参数类型,调用不同的方法进行格式化。例如,对于 %d 使用 printInteger() 方法,对于 %s 使用 printString() 方法等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值