java从入门到放弃--[1.6]字符串

本文深入解析Java中的字符串处理,包括字符串的创建、常量池优化、操作方法如拼接、格式化、查找替换,以及String、StringBuilder和StringBuffer的区别。探讨字符串不可变性带来的优势,并介绍JVM对字符串操作的智能优化。

字符串

Java 字符串就是 Unicode 字符序列。Java 没有内置的字符类型,而是在标准 Java 类库中提供了一个预定义类 String。每个用双引号括起来的字符串都是 String 类的一个实例。可以通过直接赋值或者 new 操作符来创建字符串。

String str1 = "";
String str2 = "hello, this is a string.";
String str3 = new String("create string with new");

String 类没有提供用于修改字符串的方法,所以我们将 String 类对象称为不可变字符串,它被声明为 final class,所有的属性也被定义为 final 的。但是我们可以修改字符串变量,让它指向另外一个字符串。

为了提高内存利用率,JVM 有一个字符串常量池,每次使用双引号定义字符串,JVM 会先到该常量池中来检测是否已经存在,存在则直接该对象的引用;否则在常量池中创建一个新增并返回该值的引用。

使用 new 创建字符串(new String("字符串");)时,会直接在堆中创建该字符串并返回其引用。从 Java 6 开始,String 类提供了 intern() 方法,调用该方法时,JVM 去字符串常量池检测是否已存在该字符串,如果已经存在则直接返回引用;如果不存在则在常量池中添加并返回其引用。

Java 6 中,字符串常量池存在 PermGen 里,也就是(“永久代”),而这个空间是有限的,基本不会被 FullGC 之外的垃圾收集机制扫描。如果使用不当,经常会发生 OOM。在后续版本中,将字符串常量池放在了堆中,而且默认缓存大小也在不断扩大。在 Java 8 中永久代 PermGen 也被元数据区 MetaSpace 替代。

 

下边我们通过一些示例来验证一下:

String str1 = "hello";
String str2 = "hello";
String str3 = "hello" + "world";
String str4 = str2 + "world";
String str5 = new String("hello");
String str6 = new String("hello");
String str7 = str6.intern();
String str8 = new String("hello").intern();
​
System.out.println("str1 = str2, " + (str1 == str2));
System.out.println("str3 = str4, " + (str3 == str4));
System.out.println("str1 = str5, " + (str1 == str5));
System.out.println("str5 = str6, " + (str5 == str6));
System.out.println("str1 = str7, " + (str1 == str7));
System.out.println("str1 = str8, " + (str1 == str8));
​
String str9 = "hello";
str9 += "world";
System.out.println("str3 = str9, " + (str3 == str9));

字符串操作

  • 长度

    • int length() 返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。也即是 String 类内部 char 数组的长度。(char 数据类型是一个采用 UTF-16 编码表示 Unicode 代码点的代码单元)

    • int codePointCount(int beginIndex, int endIndex) 表示字符串的实际长度,及代码点数。

      String str = "hello,\uD835\uDD5D\uD835\uDD60\uD835\uDD60\uD835\uDD5C";
      System.out.println(str);
      System.out.println("length is: " + str.length());
      System.out.println("code point count is: " + str.codePointCount(0, str.length()));
  • 子串

    • String substring(int beginIndex)

    • String substring(int beginIndex, int endIndex)

      String str = "hello, world!";
      ​
      System.out.println(str.substring(1));
      ​
      System.out.println(str.substring(0, 1));
      System.out.println(str.substring(0, str.length() - 1));
      System.out.println(str.substring(0, str.length() + 1));
  • 拼接

    可以直接使用 ++= 运算符来进行字符串的拼接,例如:

    • String str = "hello" + " world!";

    • String str = "hello"; str += " world!";

    • String str = "hello"; str = str + " world!";

  • 格式化

    为了让字符串拼接更简洁直观,我们可以使用字符串格式化方法 String.format

    • %s 字符串

    • %c 字符类型

    • %b 布尔类型

    • %d 整数类型(十进制数)

    • %x 整数类型(十六进制数)

    • %o 整数类型(八进制数)

    • %f 浮点类型

    • %a 浮点类型(十六进制数)

    • %% 百分比类型

    • %n 换行

    示例:

    System.out.printf("hello, %s %n", "world");
    System.out.printf("大写a:%c %n", 'A');
    System.out.printf("100 > 50:%b %n", 100 > 50);
    System.out.printf("100除以2:%d %n", 100 / 2);
    System.out.printf("100的16进制数是:%x %n", 100);
    System.out.printf("100的8进制数是:%o %n", 100);
    System.out.printf("100元打8.5折扣是:%f 元%n", 50 * 0.85);
    System.out.printf("上述价格的16进制数是:%a %n", 50 * 0.85);
    System.out.printf("上面的折扣是%d%% %n", 85);
  • 相等判断

    • equals 判断是否相等。

    • equalsIgnoreCase 不区分大小写判断是否相等。

  • 前缀判断

    "hello".startsWith("h")

  • 后缀判断

    "hello".endsWith("o")

  • 包含判断

    "hello".contains("ll")

  • 查找

    • indexOf 从前边查找

    • lastIndexOf 从后边开始找

    示例:

    String str = "hello, world!";
    System.out.println(str.indexOf("e"));
    System.out.println(str.indexOf('e'));
    System.out.println(str.indexOf(101));
    ​
    System.out.println(str.indexOf("e", 2));
    ​
    System.out.println(str.indexOf("l"));
    System.out.println(str.lastIndexOf("l"));
    System.out.println(str.lastIndexOf("l", 9));
  • 查找替换

    • replace

    • replaceAll

    示例:

    System.out.println("hello, world!".replace('o', 'A'));
    System.out.println("hello, world!".replace("o", "OOO"));
    System.out.println("hello, world!".replaceAll("o", "OOO"));
  • 去空格

    System.out.println(" hello, world! ".trim());

  • 大小写转换

    • System.out.println("Hello, world!".toUpperCase());

    • System.out.println("Hello, world!".toLowerCase());

  • 空串和 Null 串

    • 空串是一个长度为0且内容为空的 String 对象。

    • String 存放 null,表示没有任何对象与该变量关联。

String / StringBuilder / StringBuffer

String 在拼接过程中或操作不当时,可能会产生大量的中间对象。而 StringBuffer 就是为了解决这个问题而提供的一个类,StringBuffer 是线程安全的,如果没有线程安全的需要则使用 StringBuilder(Java 1.5 中新增)。

StringBufferStringBuilder 都继承自 AbstractStringBuilder 类,而 StringBuffer 类的所有方法都使用关键字 synchronized 来保证线程安全。它们的底层都是通过可修改的 char 数组(Java 9 以后改为 byte 数组实现)来实现修改。以下内容没有特别说明则均基于 Java 8。

StringBuffer,StringBuilder 在创建时,如果未指定容量,默认容量为 16。如果容量可预估,则最好在创建时指定合适的大小,这样可以避免多次扩容。扩容会产生多重开销:抛弃原有数组、创建新的数组、进行 arraycopy。

StringBuffer,StringBuilder 常用方法:

  • append 在字符串结尾追加

  • length 返回当前长度

  • setLength 设置字符串长度

示例:

String str1 = "hello" + " world" + "!";
System.out.println(str1);
​
StringBuffer strB1 = new StringBuffer();
strB1.append("hello");
strB1.append(" world");
strB1.append("!");
System.out.println(strB1.toString());
​
StringBuilder strB2 = new StringBuilder();
strB2.append("hello");
strB2.append(" world");
strB2.append("!");
System.out.println(strB2.toString());
​
System.out.println("strB2 length is " + strB2.length());
​
strB2.setLength(strB2.length() - 1);
System.out.println(strB2.toString());
​
strB2.setLength(strB2.length() + 10);
System.out.println(strB2.toString());

JVM 对字符串的优化

现代 JVM 的实现是很智能的,编译时 JVM 对 String 操作进行一些优化以提高程序的运行效率。

示例1

String str = "hello" + ", " + "world!";
System.out.println(str);

JVM 优化后

String str = "hello, world!";
System.out.println(str);

示例2

String str1 = "hello";
String str2 = str1 + ", world!";
System.out.println(str2);

JVM 优化后

String str1 = "hello";
StringBuilder str2 = new StringBuilder();
str2.append(str1);
str2.append(", world!");
System.out.println(str2.toString());

示例3

long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 50000; i++) {
    str += i;
}
System.out.println(str.length());
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

JVM 优化后

long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 50000; i++) {
    StringBuilder tmp = new StringBuilder();
    tmp.append(str);
    tmp.append(i);
    str = tmp.toString();
}
System.out.println(str.length());
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

for 循环经过优化后,虽然节省了空间,但是 StringBuilder 是在 for 循环内,每次都会创建。性能并不会提升,反而可能会下降。按下边实现方式改写代码后性能提升好几个数量级。

long start = System.currentTimeMillis();
StringBuilder str = new StringBuilder();
for (int i = 0; i < 50000; i++) {
    str.append(i);
}
System.out.println(str.length());
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");

说明:可以通过 javap -c 编译生成的class文件 反编译出字节码,然后来分析

应用练习

去掉字符串开头/结尾/中间的空格(不使用 trim 方法)

public String trimAll(String str) {
    StringBuilder tmp = new StringBuilder();
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (c == ' ') {
            continue;
        }
        tmp.append(c);
    }
    return tmp.toString();
}

反转字符串,比如输入 123,反转结果 321

public String reverse(String str) {
    StringBuilder tmp = new StringBuilder();
    char[] chars = str.toCharArray();
    for (int i = chars.length - 1; i >= 0; i--) {
        char c = chars[i];
        tmp.append(c);
    }
    return tmp.toString();
}

常见面试问题

==equals 的区别

  1. == 对于基本类型来说是值比较;而对于引用类型比较的是引用,是否是指向同一个对象的引用。

  2. equals 默认是引用比较,而 Integer、String 等包装类都重写了 equals 方法,改为了值比较。

所以对象都可以看作是继承自 Object,我们来看一下 Object 的 equals 实现,如果自定义类未覆写 equals,调用对象实例的 equals 方法默认是引用比较。

public boolean equals(Object obj) {
    return (this == obj);
}

我们再来看一下 Integer 类的 equals 方法

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

下列代码的执行结果

String s1 = "hello" + ", world!";
String s2 = "hello";
s2 += ", world!";
String s3 = "hello, world!";
String s4 = s2.intern();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s4);
System.out.println(s2 == s3);
System.out.println(s2 == s4);

执行结果:

false
true
true
false
false

String / StringBuffer / StringBuilder 的区别

  • String 为不可变字符串;StringBuffer 和 StringBuilder 为字符串可变对象。

  • String 的 substring 等修改操作每次都会产生一个新的 String 对象;字符串拼接性能 String 低于 StringBuffer,而 StringBuffer 低于 StringBuilder。

  • StringBuffer 是线程安全的,StringBuilder 而线程不安全的。二者都是继承自 AbstractStringBuilder ,它们的唯一区别是 StringBuffer 的所有方法都使用了 synchronized 修饰符来保证线程安全。

String 对象的 intern 的作用

String 对象的 intern 方法用于字符串的显示排重。调用此方法时,JVM 去字符串常量池查找池中是否已经存在该字符串,如果已存在则直接返回它的引用;如果不存在则在池中先创建然后返回其引用。

String 不可变性的优点

  • 字符串不可变,因此可以通过字符串常量池来实现,共享对象,从而节省空间,提高性能。

  • 多线程安全,因为字符串不可变,所以当字符串被多个线程共享时不会存在线程安全问题。

  • 适合做缓存的 Key,因为字符串不可变,因此它的哈希值也就不变;创建时它的哈希值就被缓存了,不需要重新计算,速度更快。

String 是否可以被继承

String 不能被继承。因为 String 被声明为 final,所以不能被继承。

 

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值