对于JAVA的字符串连接操作符(+)相信大家都十分熟悉 ,它的作用是把多个字符串合并为一个字符串,当然我们使用它是非常方便的 ,但它确不适合运用在大规模的场景中 。
下面我们通过程序说明一下:
假设我们有一个需求会对字符串进行数量很大连接操作
如果我们使用String进行操作,由于 String是不可变的,每次进行用(+)连接时都相当于重新创建了一个对象
这无疑是相当耗时的,以下程序进行了10万次的字符连接操作
- public static void main(String[] args) {
- long start=System.currentTimeMillis();
- String string="aa";
- for (int i = 0; i <100000; i++) {
- string+="a";
- }
- long end=System.currentTimeMillis();
- System.out.println(end-start);
- }
第一个程序输出8425(具体因计算机处理速度不定)
由于我们每次连接两个字符串时,它们的内容都要被拷贝 ,所以程序的执行时候是呈几何增长的。
为了获得可以接受的性能,我们可以用StringBuilder代替String
- public static void main(String[] args) {
- long start=System.currentTimeMillis();
- StringBuilder sb=new StringBuilder("aa");
- for (int i = 0; i <100000; i++) {
- sb.append("a");
- }
- long end=System.currentTimeMillis();
- System.out.println(end-start);
- }
第二个程序输出15(具体因计算机处理速度不定)
由程序的执行结果不难看出 ,二者的性能差十分的大,操作的数量越多 ,差距就越明显 。
结论:当我们的程序需要性能的时候,不要使用字符串连接操作符(+)来合并多个字符串 。最好使用StringBuilder的append方法。
直接看示例1:
- public class StringTest{
- void stringReplace(String strTemp){
- strTemp=strTemp.replace('l','i');
- }
- void stringBufferAppend(StringBuffer sbTemp){
- sbTemp=sbTemp.append('c');
- }
- public static void main(String[] args){
- StringTest st=new StringTest();
- String str=new String("hello");
- StringBuffer sb=new StringBuffer("hello");
- // String str1=new String("hello");
- // StringBuffer sb1=new StringBuffer("hello");
- // System.out.println("str.equals(sb)= "+str.equals(sb));
- // System.out.println("str.equals(str1 )= "+str.equals(str1));
- // System.out.println("sb.equals(str)= "+sb.equals(str));
- // System.out.println("sb.equals(sb1)= "+sb.equals(sb1));
- st.stringReplace(str);
- st.stringBufferAppend(sb);
- System.out.println("str= "+str+"\tsb= "+sb);
- }
- }
输出结果:
- str= hello sb= helloc
上面的示例无非就是方法传值与传址的问题,因为形参为引用类型,因此实参传递过来的是对象的地址值,问题来了:改变该地址所对应的内容,实参应该也会跟着发生变化,如sb最终结果那样,但str却没有,为何?
这就是String与StringBuilder的区别:
字符串的可变与不可变
JDK_1.8中这样解释:
- Strings are constant; their values cannot be changed after they are created.
- String buffers support mutable strings. Because String objects are immutable
- they can be shared.
String创建的字符串是不可变的,而StringBuilder(或StringBuffer)通过字符缓冲区创建字符串,可变;
先来解析下String的不可变:
- String s="hello";
- s="world";
对象s在创建的时候先查看常量池中是否有"hello",有则指向它,没有就创建一个"hello",再指向它,但当重新对s赋值为"world"时,常量池中的"hello"并没有改变,java会重新开辟一个内存存储新值"world",并令s指向"world";
因此在示例1中,当调用stringReplace()方法时,实参str所存储的地址值传给了形参strTemp后,strTemp指向了常量池中"hello",当对其进行字符替换时,java会重新创建"heiio",并让形参strTemp指向它,因此形参和实参指向了不同的区域,结果自然显而易见;
另外,String的不可变是由于:
- public final class String
- implements java.io.Serializable, Comparable<String>, CharSequence {
- private final char value[];//final的作用,使之初始化后不可改变
- ……
- }
(P.S.虽然value是用final来修饰的,但仍有办法可以直接改变其值,具体可以参见 反射 )
再来对比下StringBuilder的可变:
- StringBuilder sb=new StringBuilder("hello");
- sb.append("world");
在创建StringBuilder对象时,java实际上在堆中创建了字符类型数组char[],存储完"hello"后通过append方法在增加"world",并未开辟新的内存区,形参和实参指向同一区域,因此形参对对象内容进行变化,实参也会跟着变化;
来看下StringBuilder构造器的底层代码:
- public StringBuilder() {
- super(16);
- }
- public StringBuilder(int capacity) {
- super(capacity);
- }
- public StringBuilder(String str) {
- super(str.length() + 16);
- append(str);
- }
- public StringBuilder(CharSequence seq) {
- this(seq.length() + 16);
- append(seq);
- }
其基类AbstractStringBuilder的构造方法:
- char[] value;
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
……
可以发现StringBuilder实际创建了一个默认16字符长的char型数组(亦可指定长度):char[] value;
若为对象赋值时,所存储的字符串长度未超过所定义的char数组长度,则按顺序存储相应字符,若超过所定义的数组长度,则自动扩充其长度,因此说StringBuffer是可变的;
我们还是继续看append()底层代码比较形象:
- public AbstractStringBuilder append(String str) {
- if (str == null)
- return appendNull();
- int len = str.length();
- ensureCapacityInternal(count + len);
- str.getChars(0, len, value, count);
- count += len;
- return this;
- }
- 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);
- }
当为StringBuilder对象增加内容时,会先计算所需最小空间:minimumCapacity=count+len;并与原数组长度的2倍+2即newCapacity进行比较,取较大值并为数组value重新赋值,赋值采用的是数组复制的方式进行;
常见的数组复制有两种方法:一为:Arrays.copyOf(),另一为:System.arraycopy(),差别在于后者可能存在下标越界的问题,其实前者也是通过调用后者的方法来实现的:
- public static char[] copyOf(char[] original, int newLength) {
- char[] copy = new char[newLength];
- System.arraycopy(original, 0, copy, 0,
- Math.min(original.length, newLength));
- return copy;
- }
由于重新为临时数组copy定义了长度newLength,因此复制的时候不会出现越界问题;在数组复制结束后,将临时数组copy赋值给value,使之指向新的区域,完成append()操作 (这里注意:引用变量value的指向发生了变化,但StirngBuilder的指向没有变,只不过其成员变量(恰巧是个引用变量value)的值发生了改变,因此形参sbTemp和sb仍指向同一区域);
equals方法
示例1中注释掉的几个equals()语句,结果只有String与String比较时才返回true,这是String与StringBuilder的另一个区别;
我们要直到equals()方法的返回结果取决于你如何重写的,如直接继承Object的方法,则比较的是变量的值,对于StringBuilder,它并未重写该方法,因此对两个引用变量进行比较自然返回false;而String类重写了该方法,具体如下:
- public boolean equals(Object anObject) {
- if (this == anObject) {
- return true;
- }
- 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;
- }
可知,若两个引用变量指向同一区域,则自然返回true,若非,则判断anObject是否是String的实例,若是则进行值比较,否则直接返回false;
效率的差别
- public class StringBuilderTest2{
- public static void main(String[] args){
- StringBuilder sb=new StringBuilder();
- StringBuilder sb1=new StringBuilder(Integer.MAX_VALUE/7);
- String s=null;
- System.out.println(Integer.MAX_VALUE+"\n"+sb1.capacity());
- long start=System.currentTimeMillis(),end;
- for(int i=0;i<1000000;i++){
- sb.append(1);
- }
- end=System.currentTimeMillis();
- System.out.println("默认初始化SB时循环100w次耗时:"+(end-start));
- for(int i=0;i<1000000;i++){
- sb1.append(1);
- }
- start=System.currentTimeMillis();
- System.out.println("给定值初始化SB1时循环100w次耗时:"+(start-end));
- for(int i=0;i<100000;i++){
- s+=1;
- }
- end=System.currentTimeMillis();
- System.out.println("String“+”运算10W次耗时:"+(end-start));
- }
- }
输出:
- 2147483647
- 306783378//初始化值
- 默认初始化SB时循环100w次耗时:35
- 给定值初始化SB1时循环100w次耗时:16
- String“+”运算10W次耗时:3211
可以发现在循环“+”/append()运算时,String类的效率明显低于StringBuilder,为何?
这里继续使用javap来分析:
- String s=null;
- s+=1;
- 0: aconst_null
- 1: astore_1
- 2: new #2 // class java/lang/StringBuilder
- 5: dup
- 6: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
- 9: aload_1
- 10: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 13: iconst_1
- 14: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
- 17: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
- 20: astore_1
- 21: return
可以看到编译器在解析String"+"运算时,会转换成StringBuilder,调用append()方法,再通过toString()赋值,因此循环运算时,就不断发生创建对象和产生垃圾,这过程消耗了资源,造成了效率的下降;
另外,我们也可以发现调用StringBuilder构造方法时若给它传入较大整数作为参数,则运算效率也会有明显的提升,这跟前面所提扩容时数组复制有关,给定较大初始缓存区,自然不需要频繁扩容;
当然,这里顺带提一下,如果直接把String的"+"运算写成形如:s=""+1+1+1……,而不是通过循环来不断调用变量,则编译器会直接求出其字面量,运行时不会再转换为StringBuilder,效率当然就比后者高了;
相关推荐
"基于Comsol的采空区阴燃现象研究:速度、氧气浓度、瓦斯浓度与温度分布的二维模型分析",comsol采空区阴燃。 速度,氧气浓度,瓦斯浓度及温度分布。 二维模型。 ,comsol; 采空区; 阴燃; 速度; 氧气浓度; 瓦斯浓度; 温度分布; 二维模型;,"COMSOL模拟采空区阴燃:速度、浓度与温度分布的二维模型研究"
安全驱动的边云数据协同策略研究.pdf
MATLAB代码实现电-气-热综合能源系统耦合优化调度模型:精细电网、气网与热网协同优化,保姆级注释参考文档详可查阅。,MATLAB代码:电-气-热综合能源系统耦合优化调度 关键词:综合能源系统 优化调度 电气热耦合 参考文档:自编文档,非常细致详细,可联系我查阅 仿真平台:MATLAB YALMIP+cplex gurobi 主要内容:代码主要做的是一个考虑电网、热网以及气网耦合调度的综合能源系统优化调度模型,考虑了电网与气网,电网与热网的耦合,算例系统中,电网部分为10机39节点的综合能源系统,气网部分为比利时20节点的配气网络,潮流部分电网是用了直流潮流,气网部分也进行了线性化的操作处理,代码质量非常高,保姆级的注释以及人性化的模块子程序,所有数据均有可靠来源 ,关键词:MATLAB代码; 电-气-热综合能源系统; 耦合优化调度; 电网; 热网; 气网; 潮流; 直流潮流; 线性化处理; 保姆级注释; 人性化模块子程序; 可靠数据来源。,MATLAB代码:电-气-热综合能源系统耦合优化调度模型(保姆级注释,数据来源可靠)
内容概要:本文详细探讨了人工智能(AI)对就业市场的深远影响及其发展趋势。首先介绍了到2027年,44%的工人核心技能将受技术变革尤其是AI影响的事实,并提及自动化可能取代部分工作的现象。其次指出虽然某些职位面临风险,但也带来了全新的职业机遇与现有角色改进的可能性,关键在于人类要学会借助AI释放自身潜力并培养软实力,以适应快速发展的科技需求。再者,强调终身学习理念下企业和教育培训须革新教学手段与评估机制,以便紧跟AI进化速率,为个体和社会持续注入新动力。最后提到了教育机构应当加快调整步伐以匹配技术变革的速度,并利用AI实现个性化的教育,进而提升学习者的适应能力和解决问题的能力。 适用人群:政策制定者、企业管理层、在职人员及教育工作者,还有广大学生群体均能从中获得启示。 使用场景及目标:面向关注未来职场动向及教育发展方向的专业人士,提供前瞻性思考角度,助力各界积极规划职业生涯路径或调整教育资源分配策略。 其他说明:本文综合多位行业领袖的观点展开讨论,旨在唤起社会各界共同思考AI带来的变革及对策,而非单方面渲染危机感。
2025最新空调与制冷作业考试题及答案.doc
2025最新初级电工证考试题及答案.docx
飞剪PLC控制系统——采用西门子S7-200SMART和触摸屏实现智能化操控及图纸详述,飞锯追剪程序,PLC和触摸屏采用西门子200smart,包含图纸,触摸屏程序和PLC程序。 ,核心关键词:飞锯追剪程序; 西门子200smart; PLC程序; 触摸屏程序; 图纸; 控制系统。,"西门子200smart飞锯追剪系统程序包:含图纸、PLC与触摸屏程序"
使用PyQt6制作的Python应用程序。
三相桥式整流电路双闭环控制策略:电压外环与电流内环协同优化研究,三相桥式整流电路双闭环控制 电流内环 电压外环(也有开环控制) 采用电压电流双闭环控制,在电压、电流控制电路中,电压单环控制易于设计和分析,但是响应速度慢,无限流功能。 而电流环能增强电路稳定性、响应速度快。 三相桥式全控整流电路由整流变压器、阴极相连接的晶闸管(VT1, VT3, VT5)、阳极相连接的晶闸管(VT4, VT6, VT2)、负载、触发器和同步环节组成(如图1),6个晶闸管依次相隔60°触发,将电源交流电整流为直流电。 matlab仿真模型(开闭环都有)控制效果良好,可写报告。 ,三相桥式整流电路;双闭环控制;电流内环;电压外环;开环控制;MATLAB仿真模型。,基于双闭环控制的电压电流三相整流技术分析与Matlab仿真实现
MATLAB四旋翼仿真PID控制:从入门到精通的手把手教学,含QAV方法、模型代码、Simulink布局思路及详细图文说明,MATLAB四旋翼仿真 PID控制,有完全对应的说明文档,专门为初级学习者提供。 不用问在不在,直接拿即可。 亮点: 拥有和模型完全对应的讲解文档,相当于手把手教学。 内容包括: 1.QAV详细方法 2.模型及代码 3.模型2(提供simulink排版布局思路) 4.相关图片 5.使用备注 ,核心关键词:MATLAB四旋翼仿真; PID控制; 完全对应说明文档; 初级学习者; QAV详细方法; 模型及代码; simulink排版布局思路; 相关图片; 使用备注。,"MATLAB四旋翼仿真教程:PID控制详解与手把手教学"
定子磁链控制下的直接转矩控制系统MATLAB仿真研究及结果分析报告,基于定子磁链控制的直接转矩控制系统 MATLAB SIMULINK仿真模型(2018b)及说明报告,仿真结果良好。 报告第一部分讨论异步电动机的理论基础和数学模型,第二部分介绍直接转矩控制的具体原理,第三部分对调速系统中所用到的脉宽调制技术CFPWM、SVPWM进行了介绍,第四部分介绍了MATLAB仿真模型的搭建过程,第五部分对仿真结果进行了展示及讨论。 ,关键词:定子磁链控制;直接转矩控制系统;MATLAB SIMULINK仿真模型;异步电动机理论基础;数学模型;直接转矩控制原理;脉宽调制技术CFPWM;SVPWM;仿真结果。,基于MATLAB的异步电机直接转矩控制仿真研究报告
2025中小学教师编制考试教育理论基础知识必刷题库及答案.pptx
Python游戏编程源码-糖果消消消.zip
三相PWM整流器双闭环控制:电压外环电流内环的SVPWM调制策略及其代码编写详解——动态稳态特性优越的技术参考。,三相PWM整流器双闭环控制,电压外环,电流内环,PLL。 采用SVPWM调制,代码编写。 动态和稳态特性较好,可提供参考资料 ,三相PWM整流器;双闭环控制;电压外环;电流内环;PLL调制;SVPWM调制;动态特性;稳态特性;参考资料,三相PWM整流器双闭环SVPWM调制策略:稳态与动态特性优化参考指南
永磁同步电机滑膜观测器参数识别与仿真研究:转动惯量、阻尼系数及负载转矩的Matlab Simulink仿真分析文章及文档说明,永磁同步电机 滑膜观测器参数识别Matlab simulink仿真 包括转动惯量 阻尼系数 负载转矩 波形很好 跟踪很稳 包含仿真文件说明文档以及文章 ,关键词:永磁同步电机;滑膜观测器;参数识别;Matlab simulink仿真;转动惯量;阻尼系数;负载转矩;波形质量;跟踪稳定性;仿真文件;说明文档;文章。,基于Matlab Simulink仿真的永磁同步电机滑膜观测器参数识别及性能分析
基于永磁涡流的电梯缓冲结构设计.pdf
Python自动化办公源码-28 Python爬虫爬取网站的指定文章
MATLAB下的安全强化学习:利用Constraint Enforcement块训练代理实现目标接近任务,MATLAB代码:安全 强化学习 关键词:safe RL 仿真平台:MATLAB 主要内容:此代码展示了如何使用 Constraint Enforcement 块来训练强化学习 (RL) 代理。 此块计算最接近受约束和动作边界的代理输出的动作的修改控制动作。 训练强化学习代理需要 Reinforcement Learning Toolbox 。 在此示例中,代理的目标是使绿球尽可能靠近红球不断变化的目标位置。 具体步骤为创建用于收集数据的环境和代理,学习约束函数,使用约束强制训练代理,在没有约束执行的情况下训练代理。 ,核心关键词:safe RL; MATLAB代码; Constraint Enforcement 块; 强化学习代理; 绿球; 红球目标位置; 数据收集环境; 约束函数; 约束强制训练; 无约束执行训练。,MATLAB中安全强化学习训练的约束强化代理实现
基于EtherCAT总线网络的锂电池激光制片机控制系统,融合欧姆龙NX系列与威伦通触摸屏的智能制造方案。,锂电池激光模切机 欧姆龙NX1P2-1140DT,威伦通触摸屏,搭载从机扩展机架控制,I输入输出IO模块模拟量模块读取控制卷径计算 汇川IS620N总线伺服驱动器7轴控制,总线纠偏器控制 全自动锂电池激光制片机,整机采用EtherCAT总线网络节点控制, 伺服凸轮同步运动,主轴虚轴控制应用,卷径计算,速度计算,放卷张力控制。 触摸屏设计伺服驱动器报警代码,MC总线报警代码,欧姆龙伺服报警代码 张力摆臂控制,PID控制,等等 触摸屏产量统计,触摸屏故障统计,触摸屏与PLC对接信息交互,触摸屏多账户使用,多产品配方程序,优秀的触摸屏模板。 NX在收放卷控制的设计 欧姆龙NX系列实际项目程序+威纶触摸屏程序+新能源锂电设备 涵盖威纶通人机,故障记录功能,st+梯形图+FB块,注释齐全。 ,"新能源锂电池激光模切机:欧姆龙NX与威纶通触摸屏的智能控制与信息交互系统"
2025装载机理论考试试题库(含答案).pptx