一.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高,自动优化