Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究

简介: Java基础复习(DayThree):字符串基础与StringBuffer、StringBuilder源码研究

String temp = “a”;

这种方式,会先去找字符串常量池里是否有保存了同样值的字符串对象,如果有,那么直接将该对象的地址赋值给temp,如果没有,那么就在字符串常量池里申请地址然后存放,temp变量保存的是常量池里的地址

//看起来像是两个对象,实则是同一个对象
String one = “a”;
String two = “b”;
sout(one == two);

第二种就是使用构造方法创建

构造方法可以根据字符串或者字符数组去建立

不过,这里与第一种不同的是,创建的字符串对象是保存在堆中的,而不是字符串常量池里面

String three = new String(“a”);

如果使用字符串作为构造参数去构造的话,仍然要用到字符串常量池(因为要在字符串常量池新创建或者里面就有,才可以使用),如果使用到的字符串参数不用,后面会被gc收集器回收。

这样设计的优点是,可以带来共享的效率

比较

字符串比较最好不要使用双等于,而是使用equal方法

双等于比较的只是引用地址,而equal方法是先比较引用地址,然后再比较里面的值

public boolean equals(Object anObject) {
//先判断应用地址,如果相等,肯定是相同对象
if (this == anObject) {
return true;
}
//如果应用地址不正确,再判断是否是String类型
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//遍历底层字符串底层的字符数组进行比较
//先判断底层字符数组长度是否相等,不相等也不用比了,肯定不对
if (n == anotherString.value.length){
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//进行遍历比较
while (n-- != 0) {
//只要有一个比不上,就确认不相同
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

底层

从上面的源码可以看到,底层是一个字符数组,所以,它可以存储多字节的字符。

前面提到过,字符里面存储的是代码单元,所以底层字符串数组存的就是多个代码单元,这样就可以去表示各种字符串了。

这里要提及String.length方法,返回的是字符数组的长度,也就是代码单元的长度,所以不一定是字的个数

构建字符串


常用构建字符串的构造器有StringBuffer与StringBuilder

AbstractStringBuilder

我们可以看到,无论是StringBuffer或者是StringBuilder都是继承了这个类,所我们先研究这个类

里面共有4个属性

  • value:存储字符串的(字符串底层是字符数组,默认容量为16)
  • count:记录字符数组的长度,这样做的好处就是让返回字符数组长度的时间复杂度为1,而不用去遍历数组
  • MAX_ARRAY_SIZE:字符数组允许的最大长度,为( 2 31 − 8 2^{31}-8 231−8),从注释上就可以看到,这个变量并不是必要的,只不过是避免大数组会导致内存溢出(超出虚拟机的限制)。

下面就来看看append的源码

public AbstractStringBuilder append(String str) {
//判断是否为空
if (str == null)
//没有的话就调用这个方法,这个方法其实是返回一个NULL,然后value里面就存储了Null
return appendNull();
//记录长度
int len = str.length();
//也是判断底层数组长度是否足够,不够就要进行扩容
ensureCapacityInternal(count + len);
//调用字符串的getChars方法进行拼接
str.getChars(0, len, value, count);
count += len;
return this;
}
appendNull的实现
private AbstractStringBuilder appendNull() {
//就很简单使用一个字符数组,存储null,然后返回
int c = count;
//判断底层数组的长度是否足够,不够需要进行扩容
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = ‘n’;
value[c++] = ‘u’;
value[c++] = ‘l’;
value[c++] = ‘l’;
//记录此时底层字符数组长度
//至于这里为什么只用count = c
//看看value[]里面的是什么
//再看看c是什么吧
count = c;
return this;
}
ensureCapacityInternal的实现
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
//minimumCapacity是最小需要的底层字符数组长度
if (minimumCapacity - value.length > 0) {
//如果不够,进行复制
//不够的话,要按照规则进行扩容
//Arrays.copyof其实就是新建一个数组然后存放而已
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
newCapacity的实现(扩容的规则)
private int newCapacity(int minCapacity) {
// overflow-conscious code
//新的规则为,底层数组长度话为二进制然后左移一位,相当于扩展2倍
//然后再加2,所以新道德数组长度为原来的2倍加2
int newCapacity = (value.length << 1) + 2;
//再判断新的长度是否大于旧的长度
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//如果新的长度不小于0(这里可能是因为超过了int类型的4个字节),
//又不大于最大底层字符数组长度(这个变量防止内存溢出)
//就返回新的长度
//如果大于最大底层数组长度,就调用hugeCapacity
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)newCapacity;
}
hugeCapacity的实现:处理超大底层数组长度
private int hugeCapacity(int minCapacity) {
//如果是newCapacity <= 0,代表int类型字节不够存储,内存溢出


相关文章
|
2月前
|
存储 缓存 安全
Java 字符串详解
本文介绍了 Java 中的三种字符串类型:String、StringBuffer 和 StringBuilder,详细讲解了它们的区别与使用场景。String 是不可变的字符串常量,线程安全但操作效率较低;StringBuffer 是可变的字符串缓冲区,线程安全但性能稍逊;StringBuilder 同样是可变的字符串缓冲区,但非线程安全,性能更高。文章还列举了三者的常用方法,并总结了它们在不同环境下的适用情况及执行速度对比。
96 17
|
2月前
|
存储 缓存 安全
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
62 13
|
6月前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
208 83
|
6月前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
117 26
|
3月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
233 60
【Java并发】【线程池】带你从0-1入门线程池
|
8天前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
|
1月前
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
69 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
|
2月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
112 23
|
1月前
|
数据采集 存储 网络协议
Java HttpClient 多线程爬虫优化方案
Java HttpClient 多线程爬虫优化方案