JavaSE-05-字符串(全面深入)

JavaSE-05-字符串(全面深入) JavaSE-05-字符串全面深入字符串在各个编程语言中都是使用非常多文本处理基本都是最终落到字符串上去替换、拼接、截取、统计等操作。String是一个重要强大的类。Java中String类型容易认为是基础类型用得很顺手其实它是引用类型被final修饰体验官方对其的保护不允许子类继承破坏底层是final byte[] value不变的字节数组维护所以初始化了其值就不变同时学习StringBuilder、StringBuffer等可变的字符串容器以及format、MessageFormat字符串格式化最后一些字符串常见使用场景优化等全面深入了解字符串。一、String类不变Java 中出现的所有字符串文字如“water”都被实现为String类的实例。换言之Java 程序中所有的双引号字符串都是 String 类的对象。其UML类图如下基本特点字符串不可变创建后其值不能被改变虽然 String 的值是不可变的但是它们可以利用字符串常量池进行共享字符串效果上相当于字符数组( char[] )但是底层原理是字节数组( byte[] )public final class String implements java .io.Serializable, ComparableString, CharSequence, Constable, ConstantDesc { /** * The value is used for character storage. * * implNote This field is trusted by the VM, and is a subject to * constant folding if String instance is constant. Overwriting this * field after construction will cause problems. * * Additionally, it is marked with { link Stable} to trust the contents * of the array. No other facility in JDK provides this functionality (yet). * { link Stable} is safe here, because value is never null. */ Stable private final byte [] value; // 其他 }构造方法方法名说明public String()创建一个空白字符串对象不含有任何内容public String(char[] chs)根据字符数组的内容来创建字符串对象public String(byte[] bys)根据字节数组的内容来创建字符串对象String s “abc”;直接赋值的方式创建字符串对象内容就是abc示例代码public class StringDemo01 { public static void main (String[] args) { //public String()创建一个空白字符串对象不含有任何内容 String s1 new String (); System.out.println( s1: s1); //public String(char[] chs)根据字符数组的内容来创建字符串对象 char [] chs { a , b , c }; String s2 new String (chs); System.out.println( s2: s2); //public String(byte[] bys)根据字节数组的内容来创建字符串对象 byte [] bys { 97 , 98 , 99 }; String s3 new String (bys); System.out.println( s3: s3); //String s “abc”; 直接赋值的方式创建字符串对象内容就是abc String s4 abc ; System.out.println( s4: s4); } }创建字符串对象两种方式的区别通过构造方法创建 通过 new 创建的字符串对象每一次 new 都会申请一个内存空间虽然内容相同但是地址值不同直接赋值方式创建 以“”方式给出的字符串只要字符序列相同(顺序和大小写)无论在程序代码中出现几次JVM 都只会建立一个 String 对象并在字符串常量池中维护。推荐使用直接赋值的方式创建字符串利用字面量可复用的特点节约资源提高性能避免new对象。多行文本字符串模板JDK13出现好处是轻松保留字符串的样式如HTML所见即所得。Html模板// 多行文本支持JDK 13 public void multiLineString () { String html html body h1Hello/h1 /body /html ; System.out.println(html); }json报文示例包括注释破坏了Json的语法所以无法转换为实体仅作为参考示例。static String requestDemoWithDescAndCanNotBeJsonParsed { merchantCode: 898340149000005, // 商户号 terminalCode: 00000001, // 终端号 transactionAmount: 1, // 交易金额 单位分 transactionCurrencyCode: 156, // 交易币种 merchantOrderId: SO16497473287935135489683, // 商户订单号 merchantRemark: 003598测试-雁塔门店, // 商户备注 payMode: CODE_SCAN, // 支付方式 payCode: 134524350966727175, // 支付码 deviceType: 02, // 设备类型 systemTraceNum: 123456, // 系统跟踪号 goods: [ { goodsId: 001, // 商品ID goodsName: 鸡蛋, // 商品名称 quantity: 10, // 数量 price: 1, // 价格 goodsCategory: food meterial, // 商品分类 body: two eggs, // 商品描述 discount: // 折扣 } ], srcReserved: , // 源保留域 storeId: 01, // 门店号 limitCreditCard: false, // 是否限制信用卡 operatorId: 01, // 操作员ID bizIdentifier: , // 业务标识 goodsTag: , // 商品标签 installmentFlag: true, // 分期标识 installmentNumber: 12, // 分期期数 retCommParams: { foodOrderType: pre_order, // 预订单类型 eduSchoolId: 01, // 学校ID eduScene: , // 教育场景 parkId: , // 停车场ID vehicleNo: // 车牌号 }, transChnl: , // 交易渠道 thirdPartyInstalSubsFlag: , // 第三方分期补贴标识 feeRatio: , // 费率比例 costSubsidy: , // 成本补贴 subAppId: , // 子应用ID serialNum: , // 序列号 encryptRandNum: , // 加密随机数 secretText: , // 密文 appVersion: , // 应用版本 longitude: -121.48352, // 经度 latitude: 31.221345, // 纬度 networkLicense: P3100, // 网络许可证 ip: 172.20.11.089, // IP地址 name: 张*三, // 姓名 mobile: 131****3453, // 手机号 certType: IDENTITY_CARD, // 证件类型 certNo: 110101********9008, // 证件号码 fixBuyer: false, // 固定买家 targetChnl: , // 目标渠道 invocationMode: , // 调用模式 orderDesc: , // 订单描述 pwdEnc: , // 密码加密 pwdTransKeyEnc: , // 密码传输密钥加密 creditFeeRatio: , // 信用费率比例 acctAmt: , // 账户金额 location: BD09, // 位置 discountCode: , // 折扣码 promoParams: , // 促销参数 presetPayTool: {}, // 预设支付工具 transparentFields: {}, // 透明字段 encryptData: // 加密数据 } ;两种字符串比较方式号的作用比较基本数据类型比较的是具体的值如1213false比较引用数据类型比较的是对象地址值如0x001 0x003falseequals方法的作用public boolean equals (String s) 比较两个字符串内容是否相同、区分大小写示例代码public class StringDemo02 { public static void main (String[] args) { //构造方法的方式得到对象 char [] chs { a , b , c }; String s1 new String (chs); String s2 new String (chs); //直接赋值的方式得到对象 String s3 abc ; String s4 abc ; //比较字符串对象地址是否相同 System.out.println(s1 s2); // false System.out.println(s1 s3); // false System.out.println(s3 s4); // true常量池共用字符串值一样 System.out.println( -------- ); //比较字符串内容是否相同 System.out.println(s1.equals(s2)); // trueequals方法被重写比较的是内容 System.out.println(s1.equals(s3)); // 同上 System.out.println(s3.equals(s4)); // 同上 } }小技巧使用aa.equals(xx)这种字符串在前的比较方式避免xx空指针null。以下是String类中一些常用方法的举例说明结合代码示例和应用场景substring截取子串方法substring(int beginIndex, int endIndex)作用截取字符串的一部分。String str Hello World ; String subStr str.substring( 0 , 5 ); // 输出: Hello应用场景提取固定格式字符串的部分内容如日志解析、文件名提取。字符串截断处理。split分割方法split(String regex)作用使用正则表达式分割字符串为数组。String str apple,banana,cherry ; String[] parts str.split( , ); // 输出: [apple, banana, cherry]应用场景解析 CSV 数据。URL 参数拆分。日志信息解析。replace替换某部分方法replace(CharSequence target, CharSequence replacement)作用替换字符串中的某部分。String str Hello World ; String newStr str.replace( World , Java ); // 输出: Hello Java应用场景动态替换模板字符串。数据清洗如替换敏感词。contains包含判断方法contains(CharSequence s)作用判断字符串是否包含指定子串。String str Hello World ; boolean contains str.contains( World ); // 输出: true应用场景检查用户输入是否包含关键词。过滤非法字符或敏感词。第一次字符出现的索引方法indexOf(String str)作用返回指定子串在字符串中第一次出现的索引。String str Hello World ; int index str.indexOf( World ); // 输出: 6应用场景定位特定内容的位置。辅助实现自定义字符串解析逻辑。去除两端空白符方法trim()作用去除字符串两端的空白字符。String str Hello World ; String trimmed str.trim(); // 输出: Hello World应用场景用户输入清理如登录名、密码。数据预处理如读取配置文件。大小写转换方法toLowerCase()/toUpperCase()作用将字符串转换为小写或大写形式。String str Hello World ; String lower str.toLowerCase(); // 输出: hello world String upper str.toUpperCase(); // 输出: HELLO WORLD应用场景不区分大小写的比较。格式化输出如用户名显示统一格式。获取长度方法length()作用获取字符串的长度。String str Hello ; int len str.length(); // 输出: 5应用场景验证用户输入长度限制。控制文本显示区域。获取字符方法charAt(int index)作用获取指定索引位置的字符。String str Hello ; char ch str.charAt( 1 ); // 输出: e应用场景自定义字符处理逻辑。实现加密/解密算法。equals比较方法equals(Object anObject)/equalsIgnoreCase(String anotherString)作用比较两个字符串是否相等equalsIgnoreCase忽略大小写。String str1 Hello ; String str2 hello ; boolean equals str1.equals(str2); // 输出: false boolean ignoreCase str1.equalsIgnoreCase(str2); // 输出: true应用场景登录验证忽略大小写的用户名匹配。文件扩展名检查如.jpg,.JPG。前后缀检查方法startsWith(String prefix)/endsWith(String suffix)作用检查字符串是否以指定前缀或后缀开头/结尾。String str http://example.com ; boolean starts str.startsWith( http ); // 输出: true boolean ends str.endsWith( .com ); // 输出: true应用场景URL 或文件路径校验。格式校验如邮箱地址、电话号码。获取常量池引用方法intern()作用返回字符串在常量池中的引用确保相同内容的字符串共享内存。String str1 new String ( hello ); String str2 str1.intern(); String str3 hello ; System.out.println(str2 str3); // 输出: true应用场景减少重复字符串对象适用于大量重复字符串场景。优化内存使用如大数据分析中字符串去重。正则匹配方法matches(String regex)作用判断字符串是否匹配某个正则表达式。String email testexample.com ; boolean valid email.matches( \\w\\w\\.\\w ); // 输出: true应用场景输入校验如邮箱、手机号、身份证号。数据清洗。concat拼接方法concat(String str)作用拼接两个字符串。String str1 Hello ; String str2 World ; String result str1.concat(str2); // 输出: HelloWorld应用场景简单拼接操作注意性能问题高频拼接推荐使用StringBuilder。转字符数组方法toCharArray()作用将字符串转换为字符数组。String str Hello ; char [] chars str.toCharArray(); // 输出: [H, e, l, l, o]应用场景字符串加密/解密。自定义字符处理逻辑。常用方法总结方法应用场景substring提取子串split分割字符串replace替换内容contains包含判断indexOf查找位置trim去除空格toLowerCase / toUpperCase大小写转换length获取长度charAt获取字符equals / equalsIgnoreCase字符串比较startsWith / endsWith前缀/后缀判断intern常量池管理matches正则匹配concat字符串拼接toCharArray转换为字符数组这些方法是日常开发中最常用的String方法合理使用可以显著提升字符串处理效率。二、StringBuilder类可变容器StringBuilder可以看成是一个容器创建之后里面的内容是可变的。当我们在拼接字符串和反转字符串的时候会使用到。方法如下基本使用包括创建、添加、反转、获取长度等public class StringBuilderDemo3 { public static void main (String[] args) { //1.创建对象 StringBuilder sb new StringBuilder ( abc ); //2.添加元素 /*sb.append(1); sb.append(2.3); sb.append(true);*/ //反转 sb.reverse(); //获取长度 int len sb.length(); System.out.println(len); //因为StringBuilder是Java已经写好的类 //java在底层对他做了一些特殊处理。 //打印对象不是地址值而是属性值。 System.out.println(sb); } }打印本质是调用对象的toString方法而StringBuilder的该方法被重写如下Override IntrinsicCandidate public String toString () { // Create a copy, dont share the array return new String ( this ); }接着是创建了一个String对象看出其存储字符的不变数组value被拷贝了asb的字节数组就是复制了一波收集好的内容。打印的最终就是这个创建了复制了一样内容的String对象。public String (StringBuilder builder) { this (builder, null ); } String(AbstractStringBuilder asb, Void sig) { byte [] val asb.getValue(); int length asb.length(); if (asb.isLatin1()) { this .coder LATIN1; this .value Arrays.copyOfRange(val, 0 , length); } else { // only try to compress val if some characters were deleted. if (COMPACT_STRINGS asb.maybeLatin1) { byte [] buf StringUTF16.compress(val, 0 , length); if (buf ! null ) { this .coder LATIN1; this .value buf; return ; } } this .coder UTF16; this .value Arrays.copyOfRange(val, 0 , length 1 ); } }this链式编程其成员方法append返回this代表当前的StringBuilder对象优雅public class StringBuilderDemo4 { public static void main (String[] args) { //1.创建对象 StringBuilder sb new StringBuilder (); //2.添加字符串 sb.append( aaa ).append( bbb ).append( ccc ).append( ddd ); System.out.println(sb); //aaabbbcccddd //3.再把StringBuilder变回字符串 String str sb.toString(); System.out.println(str); //aaabbbcccddd } }对称字符串键盘接受一个字符串程序判断出该字符串是否是对称字符串并在控制台打印是或不是对称字符串123321、111 非对称字符串123123代码示例public class Test { public static void main (String[] args) { Scanner sc new Scanner (System.in); System.out.println( 请输入一个字符串 ); String str sc.next(); String result new StringBuilder ().append(str).reverse().toString(); if (str.equals(result)){ System.out.println( 当前字符串是对称字符串 ); } else { System.out.println( 当前字符串不是对称字符串 ); } } }拼接字符串更高性能需求定义一个方法把 int 数组中的数据按照指定的格式拼接成一个字符串返回。调用该方法并在控制台输出结果。例如数组为int[] arr {1,2,3}; 执行方法后的输出结果为[1, 2, 3]。这种方式比String的拼接方式性能要高5-10倍。代码示例:public class Test { public static void main (String[] args) { //1.定义数组 int [] arr { 1 , 2 , 3 }; //2.调用方法把数组变成字符串 String str arrToString(arr); System.out.println(str); } public static String arrToString ( int [] arr) { StringBuilder sb new StringBuilder (); sb.append( [ ); for ( int i 0 ; i arr.length; i) { if (i arr.length - 1 ){ sb.append(arr[i]); } else { sb.append(arr[i]).append( , ); } } sb.append( ] ); return sb.toString(); } }线程安全处理StringBuilder线程不安全多线程拼接同一个StringBuilder对象时会有问题。解决的方案多样给这个对象资源修改时添加锁synchronized方法或代码块使用StringBuffer线程安全类方法一样使用ThreadLocal线程本地变量每个线程处理自己的// 使用ThreadLocal保证线程安全 public class ThreadSafeStringProcessor { private static final ThreadLocalStringBuilder builders ThreadLocal.withInitial(() - new StringBuilder ( 1024 )); public static String formatData (String prefix, String suffix) { StringBuilder sb builders.get(); sb.setLength( 0 ); return sb.append(prefix).append(suffix).toString(); } }三、StringBuffer类线程安全与StringBuilder类相比方法加了Synchronized关键字线程安全方法使用和StringBuidler一样。public StringBuffer (CharSequence seq) { super (seq); } Override public synchronized int compareTo (StringBuffer another) { return super .compareTo(another); } Override public synchronized int length () { // 线程安全 return count; } Override public synchronized int capacity () { // 线程安全 return super .capacity(); }四、StringJoiner类连接器StringJoiner跟StringBuilder一样也可以看成是一个容器创建之后里面的内容是可变的。作用提高字符串拼接的操作效率容易输出带有间隔符的字符串。基本使用//1.创建一个对象并指定中间的间隔符号 StringJoiner sj new StringJoiner ( --- ); //2.添加元素 sj.add( aaa ).add( bbb ).add( ccc ); //3.打印结果 System.out.println(sj); //aaa---bbb---ccc//1.创建对象 StringJoiner sj new StringJoiner ( , , [ , ] ); //2.添加元素 sj.add( aaa ).add( bbb ).add( ccc ); int len sj.length(); System.out.println(len); //15 //3.打印 System.out.println(sj); //[aaa, bbb, ccc] String str sj.toString(); System.out.println(str); //[aaa, bbb, ccc]五、字符串内存分析字符串存储的内存原理String s “abc”直接赋值特点 此时字符串abc是存在字符串常量池中的。 先检查字符串常量池中有没有字符串abc如果有不会创建新的而是直接复用。如果没有abc才会创建一个新的。所以直接赋值的方式代码简单而且节约内存。new出来的字符串看到new关键字一定是在堆里面开辟了一个小空间。String s1 new String“abc”String s2 “abc”s1记录的是new出来的在堆里面的地址值。s2是直接赋值的所以记录的是字符串常量池中的地址值。号比较的到底是什么如果比较的是基本数据类型比的是具体的数值是否相等。如果比较的是引用数据类型比的是地址值是否相等。结论只能用于比较基本数据类型。不能比较引用数据类型。六、String.format格式化String.format是 Java 中用于格式化字符串的常用方法它允许开发者按照指定的格式生成字符串。String.format是一个功能强大且灵活的字符串格式化工具适用于需要将变量嵌入字符串模板的场景。但在性能敏感或大规模循环中应谨慎使用避免不必要的性能损耗。String formattedString String.format(format, args...);format格式化模板字符串。args要插入到模板中的参数。格式化占位符占位符含义%s字符串%d整数%f浮点数%c字符%b布尔值%n换行符平台无关String message String.format( 姓名: %s, 年龄: %d , 张三 , 25 ); System.out.println(message); // 输出: 姓名: 张三, 年龄: 25格式化选项可以使用格式化选项控制输出格式例如宽度、对齐方式、精度等。// 控制浮点数小数位数 double value 3.1415926 ; String result String.format( 保留两位小数: %.2f , value); System.out.println(result); // 输出: 保留两位小数: 3.14 // 控制宽度和对齐 String aligned String.format( %10s , Hello ); // 右对齐总宽度为10 System.out.println(aligned); // 输出: Hello应用场景日志记录构建结构化的日志信息。数据展示格式化输出用户信息、金额、日期等。调试输出生成可读性更强的调试信息。public void logInfo (String user, String action) { String log String.format( [INFO] 用户 %s 执行了操作: %s , user, action); System.out.println(log); }注意事项性能问题在高频调用的代码路径中频繁使用String.format可能会带来性能开销。建议在性能敏感场景中优先使用StringBuilder或其他高效拼接方式。线程安全String.format是线程安全的因为它不依赖共享状态。国际化支持可以通过Locale参数指定格式化时使用的语言环境。String formatted String.format(Locale.US, 金额: $%.2f , 12345.678 ); System.out.println(formatted); // 输出: 金额: $12345.68七、MessageFormat格式化MessageFormat是 Java 中用于格式化字符串的高级工具特别适合处理带有动态参数的复杂文本。它支持多种格式化选项包括日期、数字和字符串等并且可以实现多语言环境下的消息格式化。String formatted MessageFormat.format(pattern, arguments);pattern包含占位符的模板字符串。arguments要替换到模板中的参数。占位符语法MessageFormat的占位符使用{index}表示例如{0},{1}等表示对应的参数索引。String pattern 姓名: {0}, 年龄: {1} ; String result MessageFormat.format(pattern, 张三 , 25 ); System.out.println(result); // 输出: 姓名: 张三, 年龄: 25类型格式化MessageFormat支持对参数进行类型格式化比如日期、数字等。格式含义{index, type}指定类型格式化{index, type, style}指定类型和样式Date now new Date (); String pattern 当前时间: {0, date, yyyy-MM-dd HH:mm:ss} ; String result MessageFormat.format(pattern, now); System.out.println(result); // 输出: 当前时间: 2025-04-05 12:34:56 double amount 12345.6789 ; String pattern 金额: {0, number, currency} ; String result MessageFormat.format(pattern, amount); System.out.println(result); // 输出: 金额: ¥12,345.68根据本地化显示多语言支持国际化MessageFormat可以结合Locale实现多语言消息格式化适用于国际化场景。Locale locale Locale.US; String pattern Hello, {0}! You have {1} new messages. ; String result MessageFormat.format(pattern, locale, Alice , 5 ); System.out.println(result); // 输出: Hello, Alice! You have 5 new messages.动态参数替换MessageFormat支持传入数组或Object[]进行参数替换适用于不确定参数数量的场景。Object[] args { Alice , 5 }; String pattern 用户: {0}, 新消息数: {1} ; String result MessageFormat.format(pattern, args); System.out.println(result); // 输出: 用户: Alice, 新消息数: 5应用场景场景使用方式日志记录构建结构化的日志信息支持动态参数。国际化消息结合 ResourceBundle 提供多语言支持。报表生成生成带格式的数据展示内容。异常信息格式化构建可读性强的错误提示信息。注意事项性能开销相比String.formatMessageFormat性能略低建议在需要复杂格式化或国际化时使用。线程安全MessageFormat不是线程安全的多线程环境下应避免共享实例。资源管理在频繁使用的场景中建议缓存MessageFormat实例以提高性能。与String.format对比特性MessageFormatString.format占位符{0}, {1}%s, %d国际化支持支持不支持类型格式化支持日期、货币等支持基本类型性能相对较低更高线程安全非线程安全线程安全综合示例public void formatUserMessage () { String pattern 用户: {0}, 登录时间: {1, date, yyyy-MM-dd HH:mm:ss}, 尝试次数: {2} ; Date loginTime new Date (); Object[] args { admin , loginTime, 3 }; String message MessageFormat.format(pattern, args); System.out.println(message); }输出用户: admin, 登录时间: 2025-04-05 12:34:56, 尝试次数: 3总结MessageFormat是一个功能强大且灵活的格式化工具尤其适用于需要国际化支持和复杂格式化的场景。虽然其性能不如String.format但在需要处理多语言、日期、货币等复杂格式时它是更合适的选择。八、大文本处理方案使用缓冲字符流逐行读逐行处理即使内存只有几百M也能逐步拆解处理几个GB的文件。// 使用BufferedReader逐行读取大文件 public void processLargeFile (Path path) throws IOException { try ( BufferedReader reader Files.newBufferedReader(path)) { String line; while ((line reader.readLine()) ! null ) { processLine(line); // 处理每一行 } } }九、JVM调优项目若经常处理大量文本、字符串需要对字符串常量池进行有效监控进行配置优化。# 查看字符串驻留情况 jcmd PID VM.string_table_statsJVM调优参数参数推荐值适用场景-XX:UseStringDeduplication开启减少重复字符串内存占用-XX:StringTableSize100000自定义大小大量字符串驻留需求-XX:PrintStringTableStatistics启用分析字符串常量池