首页 > 编程知识 正文

java string函数(java web入门)

时间:2023-05-06 07:22:00 阅读:97575 作者:2007

作者|平头哥技术博文

编辑| wndcdq/p制作| CSDN博客

字符串对象的实现

String对象是Java中使用频率最高的对象之一,因此Java公司正在不断优化String对象的实现,以提高String对象的性能。看下图了解String对象的优化过程。

1.在Java6和早期版本中

String对象是封装char数组的对象。它主要有四个成员变量:字符数组、偏移量、字符计数和哈希值哈希。

String对象是通过offset和count属性定位char数组来获取字符串。这样可以高效快速地共享数组对象,节省内存空间,但是这种方法很可能会导致内存泄漏。

2.从Java7版本到Java8版本

从Java版本7开始,Java对String类做了一些修改。字符串类中没有更多的偏移量和计数变量。该方法的优点是String对象占用的内存略少,同时String.substring方法不再共享char,从而解决了使用该方法可能导致的内存泄漏问题。

3.从Java9版本开始

将字符数组更改为字节数组。你为什么需要这么做?我们知道字符是两个字节。如果用来存储一个字节的字符有点浪费的话,为了节省空间,Java公司把它改成了一个字节来存储字符串。这样,当存储一个字节的字符时,就避免了浪费。

在Java9中,维护了一个新的属性编码器,这是编码格式的标识。计算字符串长度或调用indexOf函数时,需要根据这个字段判断如何计算字符串长度。默认情况下,Coder属性有0和1两个值,0表示拉丁-1(单字节编码),1表示UTF-16编码。如果字符串判断字符串只包含拉丁文-1,则coder属性的值为0,否则为1。

如何创建字符串对象

1.通过字符串常量

String str='pingtouge '形式。使用这种形式创建字符串时,JVM首先会检查字符串常量池中是否存在该对象,如果存在,会返回该对象的引用地址;如果它不存在,它将在字符串常量池中创建string对象并返回引用。这种方法的优点是:避免重复创建相同值的字符串,节省内存。

2.字符串构造函数的方式

String=new String ('pingtouge '),用这种方式创建string对象的过程比较复杂,分为两个阶段。首先,字符串pingtouge将在编译时添加到常量结构中,并在加载类时在常量池中创建。然后,在调用new时,JVM会调用String的构造函数,引用常量池中的pingtouge String,在堆内存中创建一个String对象,并返回堆中的引用地址。

了解创建字符串对象两种方法,让我们分析下面的代码来加深我们对这两种方法的理解。在下面的代码中,str是否等于str1?

String str=' pingtougeString str1=新字符串(' ping touge ');让我们逐一分析这几行代码。首先,我们从String str='pingtouge '开始。这里,我们使用字符串常量来创建字符串对象。在创建pingtouge字符串对象时,JVM会去常量池中查找该字符串是否存在。这里的答案肯定不是。所以JVM会在常量池中创建string对象,并返回对象的地址引用,所以str引用常量池中pingtouge string对象的地址引用。

然后是string str 1=new string(' ping touge ')行,其中构造函数用于创建string对象。根据我们对通过构造函数创建字符串对象的理解,str 1应该在堆中获取pingtouge字符串的引用地址。由于str指的是常数池中pingtouge字符串对象的地址引用,而str1指的是堆中pingtouge字符串的引用地址,所以str肯定不等于str1。

="https://p26.toutiaoimg.com/origin/pgc-image/RTJXJ1kBqzfCnu?from=pc">

String 对象的不可变性

从我们知道String对象的那一刻起,我想大家都知道了String对象是不可变的。那它不可变是怎么做到的呢?Java 这么做能带来哪些好处?我们一起来简单的探讨一下,先来看看String 对象的一段源码:

public final class Stringimplements 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/** use serialVersionUID from JDK 1.0.2 for interoperability */private static final long serialVersionUID = -6849794470754667710L;}

从这段源码中可以看出,String类用了 final 修饰符,我们知道当一个类被 final 修饰时,表明这个类不能被继承,所以String类不能被继承。这是String不可变的第一点

再往下看,用来存储字符串的char value数组被private 和final修饰,我们知道对于一个被final的基本数据类型的变量,则其数值一旦在初始化之后便不能更改。这是String不可变的第二点。

Java 公司为什么要将String设置成不可变的,主要从以下三方面考虑:

1、保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。

2、保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。

3、可以实现字符串常量池

String 对象的优化

字符串是我们常用的Java类型之一,所以对字符串的操作也是避免不了的,在对字符串的操作过程中,如果使用不当,性能会天差地别。那么在字符串的操作过程中,有哪些地方需要我们注意呢?

优雅的拼接字符串

字符串的拼接是对字符串操作使用最频繁的操作之一,由于我们知道String对象的不可变性,所以我们在做拼接时尽可能少的使用+进行字符串拼接或者说潜意识里认为不能使用+进行字符串拼接,认为使用+进行字符串拼接会产生许多无用的对象。事实真的是这样吗?我们来做一个实验。我们使用+来拼接下面这段字符串。

String str8 = "ping" +"tou"+"ge";

一起来分析一下这段代码会产生多少个对象?如果按照我们理解的意思来分析的话,首先会创建ping对象,然后创建pingtou对象,最后才会创建pingtouge对象,一共创建了三个对象。真的是这样吗?其实不是这样的,Java 公司怕我们程序员手误,所以对编译器进行了优化,上面的这段字符串拼接会被我们的编译器优化,优化成一个String str8 = "pingtouge";对象。除了对常量字符串拼接做了优化以外,对于使用+号动态拼接字符串,编译器也做了相应的优化,以便提升String的性能,例如下面这段代码:

String str = "pingtouge";for(int i=0; i<1000; i++) {str = str + i;}

编译器会帮我们优化成这样:

String str = "pingtouge";for(int i=0; i<1000; i++) {str = (new StringBuilder(String.valueOf(str))).append(i).toString;}

可以看出 Java 公司对这一块进行了不少的优化,防止由于程序员不小心导致String性能急速下降,尽管 Java 公司在编译器这一块做了相应的优化,但是我们还是能看出 Java 公司优化的不足之处,在动态拼接字符串时,虽然使用了 StringBuilder 进行字符串拼接,但是每次循环都会生成一个新的 StringBuilder 实例,同样也会降低系统的性能。

所以我们在做字符串拼接时,我们需要从代码的层面进行优化,在动态的拼接字符串时,如果不涉及到线程安全的情况下,我们显示的使用 StringBuilder 进行拼接,提升系统性能,如果涉及到线程安全的话,我们使用 StringBuffer 来进行字符串拼接

巧妙的使用 intern 方法

* <p>* When the intern method is invoked, if the pool already contains a* string equal to this {@code String} object as determined by* the {@link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {@code String} object is added to the* pool and a reference to this {@code String} object is returned.* <p>public native String intern;

这是 intern 函数的官方注释说明,大概意思就是 intern 函数用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后 返回引用。

有一位Twitter工程师在QCon全球软件开发大会上分享了一个他们对 String对象优化的案例,他们利用String.intern方法将以前需要20G内存存储优化到只需要几百兆内存。这足以体现String.intern的威力,我们一起来看一个例子,简单的了解一下String.intern的用法。

public static void main(String[] args) {String str = new String("pingtouge");String str1 = new String("pingtouge");System.out.println("未使用intern方法:"+(str==str1));System.out.println("未使用intern方法,str:"+str);System.out.println("未使用intern方法,str1:"+str1);String str2= new String("pingtouge").intern;String str3 = new String("pingtouge").intern;System.out.println("使用intern方法:"+(str2==str3));System.out.println("使用intern方法,str2:"+str2);System.out.println("使用intern方法,str3:"+str3);}

从结果中可以看出,未使用String.intern方法时,构造相同值的字符串对象返回不同的对象引用地址,使用String.intern方法后,构造相同值的字符串对象时,返回相同的对象引用地址。这能帮我们节约不少空间。

String.intern方法虽然好,但是我们要结合场景使用,不能乱用,因为常量池的实现是类似于一个HashTable的实现方式,HashTable 存储的数据越大,遍历的时间复杂度就会增加。如果数据过大,会增加整个字符串常量池的负担。

灵活的字符串的分割

字符串的分割是字符串操作的常用操作之一,对于字符串的分割,大部分人使用的都是 Split 方法,Split 方法大多数情况下使用的是正则表达式,这种分割方式本身没有什么问题,但是由于正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致 CPU 居高不下。在以下两种情况下 Split 方法不会使用正则表达式:

传入的参数长度为1,且不包含“.$|[{^?*+”regex元字符的情况下,不会使用正则表达式

传入的参数长度为2,第一个字符是反斜杠,并且第二个字符不是ASCII数字或ASCII字母的情况下,不会使用正则表达式

所以我们在字符串分割时,应该慎重使用 Split 方法,首先考虑使用 String.indexOf 方法进行字符串分割,如果 String.indexOf 无法满足分割要求,再使用 Split 方法,使用 Split 方法分割字符串时,需要注意回溯问题。

文章不足之处,望大家多多指点,共同学习,共同进步

参考资料

Java性能调优实战 xfdxl

声明:本文为CSDN博主「平头哥的技术博文」的原创文章,版权归作者所有。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。