Java:String对象小记

对 Java 中的 String 对象,做一个微不足道的小小小小记

字节和字符的区别

字节 byte:

  1. 一个字节包含8个位(bit),因此byte的取值范围为-128~127;
  2. 字节是存储容量的基本单位;

字符 char:

  1. 字符是数字、字母、汉字以及其他语言的各种符号;
  2. Java 采用 unicode 来表示字符,Java 中的一个 char 是2个字节,一个中文或英文字符的 unicode 编码都占2个字节;
  3. 在 GB2312 编码或 GBK 编码中,一个英文字母字符存储需要1个字节,一个汉字字符存储需要2个字节;
  4. 在 UTF-8 编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节;
  5. 在 UTF-16 编码中,一个英文字母字符存储需要2个字节,一个汉字字符储存需要3到4个字节(Unicode扩展区的一些汉字存储需要4个字节);

String 常用方法

indexOf():返回指定字符的索引

charAt():返回指定索引处的字符

replace():字符串替换

trim():去除字符串两端空白

split():分割字符串,返回一个分割后的字符串数组

getBytes():返回字符串的 byte 类型数组

length():返回字符串长度

toLowerCase():将字符串转成小写字母

toUpperCase():将字符串转成大写字符

substring():截取字符串

equals():字符串比较

这些方法还是挺好用的,特别是在刷题的时候

String 为不可变类

不可变类

类在被实例化之后,不可用被重新赋值,Java 提供的八个包装类和 String 类都是不可变类。

创建自定义不可变类需要遵守的规则:

  1. 使用 private 和 final 修饰成员变量。
  2. 提供带参构造方法,用于初始化成员变量。
  3. 不要为成员变量提供 setter 方法。
  4. 如果成员变量中有可变类时需要重写 Object 中的 hashCode 方法和 equals 方法。

String 部分源码

通过String的源码可以看到

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}

可以看到 String 的本质是一个 char 数组,是对字符串数组的封装,并且是被 final 修饰的,创建后不可改变。

在 Java 中将 String 设计成不可变的是综合考虑到各种因素的结果。主要的原因主要有以下4点

  1. 便于实现字符串池(String pool):字符串常量池是 Java 堆内存中一个特殊的存储区域, 当创建一个 String 对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;

    即在堆中有一个 StringTable,用于存放字符串常量,具体内容在JVM笔记中说明

  2. 加快字符串处理速度:由于 String 是不可变的,保证了 hashcode 的唯一性,于是在创建对象时其 hashcode就可以放心的缓存了,不需要重新计算。这也就是 Map 喜欢将 String 作为 Key 的原因,处理速度要快过其它的键对象。所以 HashMap 中的键往往都使用 String。

  3. 避免安全问题:String 被许多的 Java 类(库)用来当做参数,例如:网络连接地址 URL、文件路径 path、还有反射机制所需要的 String 参数等, 假如 String 不是固定不变的,将会引起各种安全隐患。

  4. 使多线程安全:

    // 下面的例子中可以看到:可变的 StringBuffer 参数就被改变了,而String传入的参数却未被修改
    public class test {
    // 不可变的String
    public static String appendStr(String s) {
    s += "bbb";
    return s;
    } // 可变的StringBuilder
    public static StringBuilder appendSb(StringBuilder sb) {
    return sb.append("bbb");
    } public static void main(String[] args) {
    String s = new String("aaa");
    String ns = test.appendStr(s);
    System.out.println("String aaa>>>" + s.toString()); // String aaa>>>aaa
    // StringBuilder做参数
    StringBuilder sb = new StringBuilder("aaa");
    StringBuilder nsb = test.appendSb(sb);
    System.out.println("StringBuilder aaa >>>" + sb.toString()); // StringBuilder aaa >>>aaabbb
    }
    }

    所以 String 不可变的安全性就体现在这里。在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。

String 对象创建的区别

创建方式1:String str = "i"

创建方式2:String str = new String("i")

上述两者创建方式是不一样的,因为内存的分配方式不一样。String str = "i" 的方式,Java 虚拟机会将其分配到常量池中;而 String str = new String("i") 则会被分到堆内存中。

public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc"); System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str3 == str4); // false
System.out.println(str3.equals(str4)); // true
}
}

进一步说明:

在执行 String str1 = "abc" 的时候,JVM 会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串常量池中的内存地址;如果该字符串还不存在字符串常量池中,那么就会在字符串常量池中创建该字符串对象,然后再返回。所以在执行 String str2 = "abc" 的时候,因为字符串常量池中已经存在 “abc” 字符串对象了,就不会在字符串常量池中再次创建了,所以栈内存中 str1 和 str2 的内存地址都是指向 "abc" 在字符串常量池中的位置,所以 str1 == str2 的运行结果为 true。

而在执行 String str3 = new String("abc") 的时候,JVM 会首先检查字符串常量池中是否已经存在 “abc” 字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建 "abc" 字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的 "abc" 字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址,即栈内存中存储的地址是堆内存中对象的内存地址。String str4 = new String("abc") 是在堆内存中又创建了一个对象,所以 str 3 == str4 运行的结果是 false。

补充:

String str5="ab"+"c";

System.out.println(str1 == str5);  // true

此时编译器做了相应的优化,先把字符串拼接,再在常量池中查找这个字符串是否存在,如果存在,则让变量直接引用该字符串,故 str5 也指向字符串常量区中的 'abc'。

String&StringBuilder&StringBuffer

  1. 执行效率:StringBuilder > StringBuffer > String

    String最慢的原因:String 为字符串常量,而 StringBuilder 和 StringBuffer 均为字符串变量,即 String 对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

  2. 线程安全:在线程安全上,StringBuilder 是线程不安全的,而 StringBuffer 是线程安全的

    如果一个 StringBuffer 对象在字符串缓冲区被多个线程使用时,StringBuffer 中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但 StringBuilder 的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的 StringBuilder。

    StringBuffer 的补充:

    说明:StringBuffer 中并不是所有方法都使用了 Synchronized 修饰来实现同步:

    // 带关键字synchronized
    @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
    toStringCache = null;
    super.insert(offset, String.valueOf(obj));
    return this;
    } // 不带关键字synchronized
    @Override
    public StringBuffer insert(int dstOffset, CharSequence s) {
    // Note, synchronization achieved via invocations of other StringBuffer methods
    // after narrowing of s to specific type
    // Ditto for toStringCache clearing
    super.insert(dstOffset, s);
    return this;
    }
  3. 总结一下:

    String:适用于少量的字符串操作的情况

    StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

    StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

参考

https://www.cnblogs.com/wugongzi/p/11334052.html

https://www.php.cn/java/base/437469.html

https://www.cnblogs.com/wkfvawl/p/11693260.html

https://www.cnblogs.com/su-feng/p/6659064.html

https://www.cnblogs.com/nov5026/p/7248509.html

https://mp.weixin.qq.com/s/4E3xRXOVUQzccmP0yahlqA

Java:String对象小记的更多相关文章

  1. JAVA String对象和字符串常量的关系解析

    JAVA String对象和字符串常量的关系解析 1 字符串内部列表 JAVA中所有的对象都存放在堆里面,包括String对象.字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了 ...

  2. 深入理解java String 对象的不可变性

    下面我们通过一组图表来解释Java字符串的不可变性 1.声明一个String对象 String s = "abcd"; 2.将一个String变量赋值给另一个String变量 St ...

  3. 关于Java String对象创建的几点疑问

    我们通过JDK源码会知道String实质是字符数组,而且是不可被继承(final)和具有不可变性(immutable).可以如果想要了解String的创建我们需要先了解下JVM的内存结构. 1.JVM ...

  4. Java——String对象

    前言 实际上任何语言都没有提供字符串这个概念,而是使用字符数组来描述字符串.Java里面严格来说也是没有字符串的,在所有的开发里面字符串的应用有很多,于是Java为了应对便创建了String类这个字符 ...

  5. Java String对象的问题 String s="a"+"b"+"c"+"d"

    1, String s="a"+"b"+"c"+"d"创建了几个对象(假设之前串池是空的) 2,StringBuilde ...

  6. 我的Java开发学习之旅------>Java String对象作为参数传递的问题解惑

    又是一道面试题,来测试你的Java基础是否牢固. 题目:以下代码的运行结果是? public class TestValue { public static void test(String str) ...

  7. Java String对象的经典问题

     先来看一个样例,代码例如以下:  public class Test {       public static void main(String[] args) {           Strin ...

  8. Java String对象的经典问题(转)

    public class StringTest { public static void main(String[] args) { String strA = "abc"; St ...

  9. Java String 对象,你真的了解了吗?

    String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...

随机推荐

  1. pyRevit开发:如何创建轴网

    必看部分: Document获取: 必看文章 如何添加基本项目引用 基础部分: 创建轴网 基本思路: 首先添加引用 获取当前项目文档 创建轴网定位线 创建轴网 设置轴网名称 实现代码: import ...

  2. io流-文件流\节点流

    FileOutputStream类(jdk1.0) 描述 java.io.FileOutputStream 类是文件字节输出流,用于将数据写入到文件中. 构造方法 //构造方法 FileOutputS ...

  3. C++吃金币小游戏

    上图: 游戏规则:按A,D键向左和向右移动小棍子,$表示金币,0表示炸弹,吃到金币+10分,吃到炸弹就GAME OVER. 大体思路和打字游戏相同,都是使用数组,refresh和run函数进行,做了一 ...

  4. python matplotlib.pyplot 散点图详解(2)

    python matplotlib.pyplot 散点图详解(2) 上期资料 一.散点图叠加 可以用多个scatter函数叠加散点图 代码如下: import matplotlib.pyplot as ...

  5. goto语法在PHP中的使用

    在C++.Java及很多语言中,都存在着一个神奇的语法,就是goto.顾名思义,它的使用是直接去到某个地方.从来代码的角度来说,也就是直接跳转到指定的地方.我们的PHP中也有这个功能,我们先来看看它是 ...

  6. Docker系列(14)- Portainer可视化面板安装

    官网 https://documentation.portainer.io/v2.0-be/deploy/beinstalldocker/ 可视化 portainer docker run -d -p ...

  7. Jmeter系类(32) - JSR223(2) | Groovy常见内置函数及调用

    常见内置函数及调用 获取相关函数 获取返回数据并转换为String字符串 prev.getResponseDataAsString() 例子 String Responsedata = prev.ge ...

  8. Elasticsearch -head 查询报 406错误码

    问题:利用Elasticsearch -head插件不能查看数据或者在Elasticsearch -linux的curl命令操作时总是提示: {"error":"Cont ...

  9. docker启动jenikns,提示 :This image is for research only, DO NOT USE

    下载的jenkins镜像有问题?

  10. pip3 install beautifulsoup4 出现错误 There was a problem confirming the ssl certificate

    chenhuimingdeMacBook-Pro:groceryList Mch$ sudo pip3 install beautifulsoup4 The directory '/Users/Mch ...