Java字符串连接的多种实现方法及效率对比
JDK 1.8(Java 8)里新增String.join()方法用于字符串连接。本文基于《Java实现String.join()和效率比较》一文,分析和比较四种自定义实现与String.join()方法的效率,并纠正原文的一些错误。
代码示例如下:
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 | public class Test { public static void main(String[] args) { String[] strOri = { "a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" }; //同new string[]{"a","b","c","d","e","f","g","h"} String strRes = "" ; int loops = 100000; Date date = new Date(); for ( int i = 0; i < loops; i++) {strRes = join1(strOri, ":" );} date = recordTime(date, 1); //1 for ( int i = 0; i < loops; i++) {strRes = join2(strOri, ":" );} date = recordTime(date, 2); for ( int i = 0; i < loops; i++) {strRes = join3(strOri, ":" );} date = recordTime(date, 3); for ( int i = 0; i < loops; i++) {strRes = join4(strOri, ":" );} date = recordTime(date, 4); for ( int i = 0; i < loops; i++) {strRes = join5(strOri, ":" );} date = recordTime(date, 5); long startTime = System.currentTimeMillis(); //2 for ( int i = 0; i < loops; i++) {strRes = join5(strOri, ":" );} long endTime = System.currentTimeMillis(); System. out .println( "5c:{" + strRes + "} costs " + (endTime-startTime) + "ms" ); startTime = System.nanoTime(); for ( int i = 0; i < loops; i++) {strRes = join5(strOri, ":" );} endTime = System.nanoTime(); System. out .println( "5n:{" + strRes + "} costs " + (endTime-startTime) + "ns" ); } private static void recordTime_Wrong(Date date, int no) { System. out .println(no + ": costs " + ( new Date().getTime()-date.getTime()) + "ms" ); date = new Date(); } private static Date recordTime(Date date, int no) { System. out .println(no + ": costs " + ( new Date().getTime()-date.getTime()) + "ms" ); return new Date(); } private static String join1(String[] strOri, String delimiter) { StringBuffer sb = new StringBuffer(); //3 for (String s : strOri) { sb.append(s+delimiter); //4 } return sb.toString().substring(0, sb.toString().length()-1); } private static String join2(String[] strOri, String delimiter) { StringBuffer sb = new StringBuffer(); for (String s : strOri) { sb.append(s+delimiter); } String s = sb.toString(); return s.substring(0, s.length()-1); } private static String join3(String[] strOri, String delimiter) { StringBuffer sb = new StringBuffer(); for ( int i = 0; i < strOri.length; i++) { if (i != strOri.length-1) { sb.append(strOri[i]+delimiter); } else { sb.append(strOri[i]); } } return sb.toString(); } private static String join4(String[] strOri, String delimiter) { StringBuilder stringBuilder = new StringBuilder(); for ( int i = 0; i < strOri.length-1; i++) { stringBuilder.append(strOri[i]).append(delimiter); } stringBuilder.append(strOri[strOri.length-1]); return stringBuilder.toString(); } private static String join5(String[] strOri, String delimiter) { return String. join (delimiter, strOri); //5 } } |
选取三次运行输出结果如下:
1: costs 930ms 2: costs 902ms 3: costs 637ms 4: costs 230ms 5: costs 364ms 5c:{a:b:c:d:e:f:g:h} costs 413ms 5n:{a:b:c:d:e:f:g:h} costs 286466296ns
1: costs 834ms 2: costs 788ms 3: costs 576ms 4: costs 248ms 5: costs 350ms 5c:{a:b:c:d:e:f:g:h} costs 384ms 5n:{a:b:c:d:e:f:g:h} costs 283256112ns
1: costs 774ms 2: costs 728ms 3: costs 605ms 4: costs 297ms 5: costs 417ms 5c:{a:b:c:d:e:f:g:h} costs 280ms 5n:{a:b:c:d:e:f:g:h} costs 279838638ns
可见,join4()执行最快,其次是join5()。join1()和join2()执行效率接近,前者调用两次toString(),故效率略低。
总结如下:
1. 原文recordTime(即本文recordTime_Wrong)方法中,无法通过"date = new Date()"修改外部的date引用(根因详见《java中的传值与传引用》)。这会导致每次调用recordTime()时,起始时间始终是"Date date = new Date()"获得的对象(表现为join*耗时递增)。
2. 查看Java源码可知,new Date()其实就是调用System.currentTimeMillis():
1 public Date() { 2 this(System.currentTimeMillis()); //相当于Date(System.currentTimeMillis()) 3 }
可以使用new Date().getTime()获取当前时间戳(毫秒)。注意,该毫秒数一般以1970-01-01 00:00:00为参考点,但东八区要加上时区,即以1970-01-01 08:00:00为参考时间。此外,通过getTime()获取毫秒数效率不如System.currentTimeMillis(),后者返回自1970年1月1日0时起的毫秒数。
System.nanoTime()的计时精度不保证一定高于System.currentTimeMillis(),但可保证数值递增(后者相减时可能产生负值)。
若要对代码进行更准确的计时,可参考《How do I write a correct micro-benchmark in Java?》一文。
3. StringBuffer对象是线程安全的,其方法都是同步的(synchronized)。临时变量应使用StringBuilder(效率更高),避用StringBuffer。
4. 在循环内部,不要使用append(a+b)的形式,而应改为append(a).append(b)。
5. String.join()内部使用StringBuilder实现,因此join5()性能接近join4()。当然,String.join()的功能比join4()更多。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人