有关String的一些内容

.String类

Java.lang.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



    /** use serialVersionUID from JDK 1.0.2 for interoperability */

    private static final long serialVersionUID = -6849794470754667710L;



    /**

     * Class String is special cased within the Serialization Stream Protocol.

     *

     * A String instance is written into an ObjectOutputStream according to

     * <a href="{@docRoot}/../platform/serialization/spec/output.html">

     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>

     */

    private static final ObjectStreamField[] serialPersistentFields =

        new ObjectStreamField[0];

...

}

从上面可以看出几点:

 

1)String类是final类

也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被 final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用 以提升执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。因此在现在的Java SE版本中,不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时,才将方法设置为final。

2) 私有属性

上面列举出了String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。

3) 一些常用方法

①求字符串长度

length()//返回该字符串的长度

②求字符串某一位置字符

public char charAt(int index)//返回字符串中指定位置的字符;注 意字符串中第一个字符索引是0,最后一个是length()-1。

③提取子串

用String类的substring方法可以提取字符串中的子串,该方法有两 种常用参数:

public String substring(int beginIndex)//该方法从beginIndex 位置起,从当前字符串中取出剩余的字符作为一个新的字符串返回。

public String substring(int beginIndex, int endIndex)//该方 法从beginIndex位置起,从当前字符串中取出到endIndex-1位置的字 符作为一个新的字符串返回。

④字符串比较

 

public int compareTo(String anotherString)//该方法是对字符串 内容按字典顺序进行大小比较,通过返回的整数值指明当前字符串与参 数字符串的大小关系。若当前对象比参数大则返回正整数,反之返回负 整数,相等返回0。

public int compareToIgnore(String anotherString)//与 compareTo方法相似,但忽略大小写。

public boolean equals(Object anotherObject)//比较当前字符串 和参数字符串,在两个字符串相等的时候返回true,否则返回false。

public boolean equalsIgnoreCase(String anotherString)//与 equals方法相似,但忽略大小写。

⑤字符串连接

public String concat(String str)//将参数中的字符串str连接到当 前字符串的后面,效果等价于"+"。

⑥字符串中单个字符查找

public int indexOf(int ch/String str)//用于查找当前字符串中 字符或子串,返回字符或子串在当前字符串中从左边起首次出现的位置, 若没有出现则返回-1。

public int indexOf(int ch/String str, int fromIndex)//改方法 与第一种类似,区别在于该方法从fromIndex位置向后查找。

public int lastIndexOf(int ch/String str)//该方法与第一种类 似,区别在于该方法从字符串的末尾位置向前查找。

public int lastIndexOf(int ch/String str, int fromIndex)//该 方法与第二种方法类似,区别于该方法从fromIndex位置向前查找。

⑦字符串中字符的大小写转换

public String toLowerCase()//返回将当前字符串中所有字符转换 成小写后的新串

public String toUpperCase()//返回将当前字符串中所有字符转换 成大写后的新串

⑧字符串中字符的替换

public String replace(char oldChar, char newChar)//用字符 newChar替换当前字符串中所有的oldChar字符,并返回一个新的字符 串。

public String replaceFirst(String regex, String replacement)// 该方法用字符replacement的内容替换当前字符串中遇到的第一个和字 符串regex相匹配的子串,应将新的字符串返回。

public String replaceAll(String regex, String replacement)// 该方法用字符replacement的内容替换当前字符串中遇到的所有和字符 串regex相匹配的子串,应将新的字符串返回。

 

⑨其他类方法

String trim()//截去字符串两端的空格,但对于中间的空格不处理。

boolean statWith(String prefix)或boolean endWith(String suffix)//用来比较当前字符串的起始字符或子字符串prefix和终止字 符或子字符串suffix是否和当前字符串相同,重载方法中同时还可以指 定比较的开始位置offset。

regionMatches(boolean b, int firstStart, String other, int otherStart, int length)//从当前字符串的firstStart位置开始比较, 取长度为length的一个子字符串,other字符串从otherStart位置开 始,指定另外一个长度为length的字符串,两字符串比较,当b为true 时字符串不区分大小写。

contains(String str)//判断参数s是否被包含在字符串中,并返回 一个布尔类型的值

String[] split(String str)//将str作为分隔符进行字符串分解, 分解后的字字符串在字符串数组中返回。

4)字符串与基本类型的转换

①字符串转换为基本类型

java.lang包中有Byte、Short、Integer、Float、Double类的调用方 法:

1)public static byte parseByte(String s)

2)public static short parseShort(String s)

3)public static short parseInt(String s)

4)public static long parseLong(String s)

5)public static float parseFloat(String s)

6)public static double parseDouble(String s)

②基本类型转换为字符串类型

String类中提供了String valueOf()放法,用作基本类型转换为字符 串类型。

1)static String valueOf(char data[])

2)static String valueOf(char data[], int offset, int count)

3)static String valueOf(boolean b)

4)static String valueOf(char c)

5)static String valueOf(int i)

6)static String valueOf(long l)

7)static String valueOf(float f)

8)static String valueOf(double d)

③进制转换

使用Long类中的方法得到整数之间的各种进制转换的方法:

Long.toBinaryString(long l)

Long.toOctalString(long l)

Long.toHexString(long l)

Long.toString(long l, int p)//p作为任意进制

(上述string方法引用自“https://www.cnblogs.com/ABook/p/5527341.html”)

 

拓展1:

concat,subString等一些方法操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

对String对象的任何改变都不影响到原对象,相关的任何change作都会生成新的对象”。

 

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

拓展2:

java6中的subSreing方法是共享字符数组,在大量循环及大规模数据情况下有可能没办法垃圾回收,导致内存泄漏,已作为bug在java7中修改为复制内存数组,导致效率下降

 

 

 

.有关常量池

 

1.字符串常量池的设计意图是什么?

字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。

JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:

1)为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池;

2如果字符串已经存在池中,就返回池中的实例引用;

3如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享;

 

2.实现的基础:

1实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享;

2运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收

我们来看下面一段代码,就是从字符串常量池中获取相应的字符串:

 

String str1 = “hello”;

String str2 = “hello”;

System.out.printl("str1 == str2" : str1 == str2 ) //true

 

3.字符串常量池在哪里?

在分析字符串常量池的位置时,首先得了解JVM内存结构,JVM内存区域分为类装载器子系统,运行时数据区,执行引擎,其中运行时数据区分为线程共享区和线程独占区

线程共享区包括堆和方法区;

线程独占区包括Java虚拟机栈、本地方法栈和程序计数器。

简单的介绍下JVM内存结构,看完第三版《深入理解JVM虚拟机第三版》之后总结一篇比较完善的文章

1)程序计数器

是一块比较小的内存区域,是唯一一个不会发生OutOfMemoryError的区域,可以这样理解方法进栈后,每一行代码都有一个标识,程序按着标识往下执行。

2)Java虚拟机栈:

每个方法执行,都会创建一个栈帧,方法调用进栈,方法结束出栈;

栈帧里面存放着局部变量表,操作数栈,动态链接以及方法出口等;

局部变量表里面存放着基本数据类型,引用类型等;

栈帧伴随着方法的开始而开始,结束而结束;

局部变量表所需的内存空间在编译期间就完成了分配,在运行期间是不会改变的;

栈很容易出现StackOverFlowError,栈内存溢出错误,常见于递归调用;

3)本地方法栈

和Java虚拟机栈其实是差不多的,但是也是有区别的Java虚拟机栈为Java方法服务,本地方法栈为native方法服务堆功能单一,就是存储对象的实例,

4)

其实又分新生代和老年代;

新生代又分Eden、Survivor01和Survivor02三个区域,垃圾收集器主要管理的区域,Eden区回收效率很高。并不是所有的对象实例都会分配到堆上去,Java虚拟机栈也会分配。堆很容易出现OutOfMemoryError错误,内存溢出

5)方法区

存放加载的类信息、常量、静态变量,静态代码块等信息;

类信息包括类的版本、字段、方法、接口等,方法区也被称为永久代。

而我们所说的字符串常量池存在于方法区

 

 

 

3.如何操作字符串常量池?

JVM实例化字符串常量池时

1String str1 = "hello";

2String str2 = "hello";

3System.out.println("str1 == str2" : str1 == str2 ) //true

String.intern()

通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。java.lang.String.intern()返回一个保留池字符串,就是一个在全局字符串池中有了一个入口。如果以前没有在全局字符串池中,那么它就会被添加到里面。

(这部分有关常量池的内容应用自“java蚂蚁”微信号相关内容)

通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的

 

 

三.string和stringbuffer ,stringbuilder

 string += "hello";的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加操作再存进另一个新的String对象当中,再让string变量指向新生成的对象。每次循环会new出一个 StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。

 

string+="hello"的操作事实上会自动 被JVM优化成:

StringBuilder str = new StringBuilder(string);

str.append("hello");

str.toString();

StringBuffer类的成员方法前面多了 一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

 

拓展1:

形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

 

拓展2:

String、StringBuilder、StringBuffer三者的执行效率:

StringBuilder > StringBuffer > String

  当然这个是相对的,不一定在所有情况下都是这样。

  比如String str = "hello"+ "world"的效率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。

 

拓展3:

String a = "hello2";   String b = "hello" + 2;   System.out.println((a == b));输出结果为:true。原因很简单,"hello"+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。

String a = "hello2";    String b = "hello";       String c = b + 2;       System.out.println((a == c));输出结果为:false。由于有符号引用的存在,所以  String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。

String a = "hello2";     final String b = "hello";       String c = b + 2;       System.out.println((a == c));输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。

 

拓展4:

String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并 返回一个指向该字符串的引用。

 

拓展5:

String str = new String("abc")创建了2个对象,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象 呢,这里面要澄清一个概念  该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个 String对象。

 

拓展6: //str1 += "love"+"java";        1)

        str1 = str1+"love"+"java";      //2)

1的效率比2高,自动优化

 

猜你喜欢

转载自blog.csdn.net/qq_34823218/article/details/106595744