Java 字符串就是 Unicode 字符序列,例如串 “Java” 就是 4 个 Unicode 字符 J,a,v,a 组成的。
Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是 String 类的一个实例。
String e = “” ; // an empty string
String greeting = “Hello World”;
Java 允许使用符号 "+" 把两个字符串连接起来
String s1 = “Hello”;
String s2 = “World!”;
符号 “+” 把两个字符串按给定的顺序连接在一起,并且是完全按照给定的形式。
当 “+” 运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接
int age = 18;
String s = "age is" + age; //s赋值为age is 18
这种特性通常被用在输出语句中:
System.out.println(“age is” + age);
char charAt(int index)
返回字符串中第 index 个字符。
boolean equals(String other)
如果字符串与 other 相等,返回 true。因为 String 类重写了 equals 方法。
boolean equalsIgnoreCase(String other)
如果字符串与 other 相等(忽略大小写),则返回 true
int indexOf(String str) lastIndexOf()
返回与 str 匹配的第一个字符串的开始位置,该位置从 0 开始计算,如果原始串中不存在 str,返回 -1。
int indexOf(String str,int fromIndex)
返回与 str 匹配的第一个字符串的开始位置,该位置从 fromIndex 开始计算,如果原始串中不存在 str,返回 -1。
int length()
返回字符串的长度。
String replace(char oldChar,char newChar)
返回一个新串,它是通过用 newChar 替换此字符串中出现的所有oldChar而生成的
boolean startsWith(String prefix)
如果字符串以prefix开始,则返回 true
boolean endsWith(String prefix)
如果字符串以 prefix 结尾,则返回 true
String substring(int beginIndex)
返回一个新字符串,该串包含从原始字符串 beginIndex 到串尾的所有字符
String substring(int beginIndex,int endIndex)
返回一个新字符串,该串包含从原始字符串 beginIndex 到串尾或 endIndex-1 的所有字符
String toLowerCase()
返回一个新字符串,该串将原始字符串中的所有大写字母改成小写字母
String toUpperCase()
返回一个新字符串,该串将原始字符串中的所有小写字母改成大写字母
String trim()
返回一个新字符串,该串删除了原始字符串头部和尾部的空格 注:在 Java 中某个索引区间进行一些操作的方法,索引取值范围一般都是包头不包尾,就拿上面的 String substring(int beginIndex,int endIndex) 来说,它是截取子串 从 beginIndex 开始 到 endIndex - 1 结束。
equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则s.equals(t) 返回 true,否则返回 false.
s 和 t 既可以是字符串变量,也可以是字符串常量,例如: “Hello”.equals(t);
要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase 方法,例如:
“Hello”.equalsIgnoreCase(“hellO”); //true
判断字符串是否相等不要使用 "==","=="比较是引用是否相等(是否为同一个对象)
创建了 4 个对象
分析:
先去 "字符串池" 中找 "a", 没有找到,在 "字符串池" 中创建 "a" 这个String 对象,先去先去 "字符串池" 中找 "a",找到 "a", new String("a") 在堆中创建一个 String 对象(因为 new 关键字一出现,肯定会创建一个对象)。
循环
i = 0; gh = "a0"; 先去 "字符串池" 中找 "a0", 没有找到,在 "字符串池" 中创建 "a0" 这个String 对象
i = 1; gh = "a01"; 先去 "字符串池" 中找 "a01", 没有找到,在 "字符串池" 中创建 "a01" 这个String 对象
String 不是不可变对象吗?那为什么可以字符串拼接啊?你是在逗我吗?
String JDK 源码
final 修饰了 String 类,使得 String 类不可被继承。
final 修饰了 char value[],使得字符串的值不可以改变。
但是 final 并没有修饰 String 的引用,即 final String str;
这样的话,字符串的引用可以改变指向,比如上面的字符串拼接,gh 刚开始指向 "a",随着拼接又指向了 "a0"、"a01"。实际上这个过程中只是 gh 这个引用的指向在改变,"a"、"a0"、"a01" 并没有被改变。
注:这是我画的几个草图为了帮助大家理解,实际上字符串的内存分析要比这个复杂点。
明显可以看出来(在字符串拼接的过程中,创建出来的这些中间 String 对象并不会被回收),"+" 字符串拼接对于内存的浪费比较大,如果是服务器端编程,多线程将会很浪费空间。 字符串拼接存在的问题
要得到上面的 s4,就会 s1 和 s2 拼接生成临时一个 String 对象 t1,内容为 "hello word",然后有 t1 和 s3 拼接生成最终我们需要的 s4 对象,这其中,产生了一个中间的 t1,而且 t1 创建之后,没有主动回收,势必会占一定的空间。如果是一个很多(尤其是一个部署到服务器上的项目,每个用户开一个线程,那用户越来越多的时候这个性能的损耗就很明显了)字符串的拼接,那么代价就更大了,性能一下会降低很多。
一个 Java 程序如果想运行起来,需要经过两个时期,编译时和运行时。在编译时,Java 编译器(Compiler)将 java 文件转换成字节码。在运行时,Java 虚拟机(JVM)运行编译时生成的字节码。通过这样两个时期,Java 做到了所谓的一处编译,处处运行。
早期的版本中,字符串拼接是会在常量池创建对象的,所以不少编程规范都会说不要直接用加号去拼接字符串,因为老是去常量池创建对象的话,开销也不小。
在 jdk5.0 之后 java 对字符串拼接做了编译器的优化处理 。
当 Java 编译器遇到字符串拼接的时候,会创建一个 StringBuilder 对象,后面的拼接,实际上是调用 StringBuilder 对象的 append 方法。这样就不会有我们上面担心的问题了。
分析上边这个代码,看起来没毛病。但是这里面有一个很重要的就是 StringBuilder 对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个 StringBuilder 对象,这样明显不好。稍微优化一下。
StringBuilder 对象的创建在循坏外面,这样就只创建了一个对象,比较好。 总结