String类核心讲解

String类核心讲解

在Java中,String对象是不可变的,一旦String对象被创建,其值就不能被修改。String类提供了一些对字符串操作的方法,如比较字符串、查找字符串、提取子串等方法。

主要方法

返回值类型方法描述
charcharAt(int index)返回指定索引的单个字符
Stringconcat(String str)在原字符串的末尾连接指定的字符串
booleanequals(Object anObject)比较当前字符串与指定对象是否相等
intindexOf(int ch)返回指定字符在字符串中第一次出现的索引值
Stringreplace(char oldChar, char newChar)用新的字符替换原来的字符,并返回替换后的字符串
String[]split(String regex)按照匹配所给的正则表达式分割字符串,返回值为字符串数组
Stringsubstring(int beginIndex, int endIndex)返回索引值为beginIndexendIndex之间的字符串
Char[]toCharArray()将字符串转换为一个字符数组
Stringtrim()去除字符串首尾的空格

String对象不可变的原因

JDK8中String的部分源码如下


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** 字符串的值用一个字符数组来存储 */
    private final char value[];
    ......
    public String() {
        this.value = "".value;
    }
  
  /** contact方法 **/
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
  
   /** replace方法 **/
     public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; 

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
  
  /** substring方法 **/
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
  
  
  ......
}

从源码中我们可以看到String这个类是用一个char数组存储字符,并且使用了final关键字修饰,而final修饰的类、方法、变量均是不可修改的。而contactreplacesubstring等方法都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

String s=new String()与String s = ""的区别

   public static void main(String[] args) {
        String s1 = "Java";
        String s2 = new String("Java");
        String s3 = "Java";
        String s4 = new String("Java");
        System.out.println(s1==s2);
        System.out.println(s1==s3);
        System.out.println(s2==s4);
    }

上述代码的输出结果为:

在这里插入图片描述

String s1 = "Java"会先去String常量池中查找有没有已经存在的引用,如果没有,声明的s1会直接生成一个String对象,并且会在String常量池中存入一个引用指向这个String对象。

之后直接声明的字符串同样也会遵循上面的步骤,所以第二次String s3 = "Java"String常量池中找到了一个引用指向第一次声明的字符串对象。而new String("Java")这样会直接在堆中创建新的对象。

在这里插入图片描述

StringBuilderStringBuffer

String对象重载了+操作符,可用于连接String,比如这句

String string = ""; string += "Java";

string += "Java",这行代码的过程相当于将原有的string变量指向的对象内容取出与"Java"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。

Test.Java
        String s0 = "";
    
        for (int i = 0; i < 10; i++)
            s0 += "java";
        

对上述代码进行反编译,得到如下输出:

在这里插入图片描述

从第11行开始到44行是循环代码的执行过程,我们可以看到每次循环会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,在源代码中并没有使用到StringBuilder这个类,但是编译器却使用了它,这是因为StringBuilder更高效。

StringBuilder

StringBuilder是可变的字符序列,主要的操作有appendinsert这两个方法,append方法通常在字符串最后添加字符,insert方法是在指定索引处插入字符。每个String builder都有一个容量值,只要string builder中的字符序列的长度没有超过容量值就不需要分配新的缓冲区,如果缓冲区溢出它会自动调节变得更大些。StringBuilder中默认的构造方法中初始化的容量为16,因此在使用的时候尽量考虑字符串的长度是否超过了默认的容量,或者我们可以自己指定初始化的容量,在一定程度上能提升程序的性能。需要注意的是StringBuilder是非线程安全的,只能在单线程的环境下使用,如果需要在多线程中保持同步,请使用StringBuffer

StringBuilder线程不安全的原因
   public static void main(String[] args) throws InterruptedException {

        StringBuilder stringBuilder = new StringBuilder();

        //创建10个线程
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    stringBuilder.append("A");
                }
            });
        }
        System.out.println(stringBuilder.length());
        
    }

输出结果总是小于预期值1000,说明多线程环境下StringBuilder是非线程安全,具体原因我们分析一下append方法源码

   public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
//StringBuilder中的append方法,通过调用父类AbstractStringBuilder中的append实现
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
     
   }

AbstractStringBuilderappend方法实现源码:

 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;
    }

count += len这行代码不是原子操作,因此多线程环境下存在线程不安全问题。

StringBuffer

StringBufferStringBuilder最大的区别就是前者是线程安全的,阅读StringBuffer的源码可知,它在每个操作方法前都使用了synchronized关键字修饰以保证多线程环境下的数据同步。但是这样明显影响性能,因此,在单线程环境中使用StringBuilder即可。

参考资料

《Java编程思想》

StringBuilder为什么线程不安全

探秘Java中String、StringBuilder以及StringBuffer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值