简单来说,用双引号引起来的几个字符组成的序列,比如:“abcdefg”、“今天天气不错呀” 这些就是字符串。
在Java语言中,字符串String是一个特殊的对象,属于引用类型。
String str1 = "这是第1种创建字符串的方式";
String str2 = new String("这是第2种创建字符串的方式")
//这是第3种创建字符串的方式:用字符数组创建字符串
char[] ch = {'h','e','l','l','o'};
String str2 = new String(ch);
//final关键字修饰的类、方法、变量都是不可变的。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
@Stable
private final byte[] value;
}
(这里final关键字的作用不懂的可以先去了解final)
不管我们对字符串做出什么操作,都不会改变原先的字符串,而是返回一个新的字符串对象。
对String字符串的操作都不是在原字符串上进行的,而是重新生成了一个新的字符串对象。也就是说,对字符串进行操作后,原始字符串并没有被改变。
或者我们可以这样理解:
每次对字符串(假设叫str)进行操作的时候,都是把str复制一份(strcopy),然后对strcopy操作。
String类对象创建后,一旦被初始化就只有唯一的常量,不能再改变String对象的值或者数据。而字符串在Java程序中虽然是不可变的,但仍旧会被频繁使用,JVM为了提高效率并节约内存,会将内容相同的字符串共用一个String实例放在常量池中,常量池中的String常量可以共享。
String类实际上通过char数组来保存字符串。
随机看一个源码中的方法:
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
我们来看下面这段代码,并分析区别:
String str1 = "123";
String str2 = new String("123");
String str1 = “123”; ====> 先检测(内容)字符串常量池中是否存在str1,不存在就缓存到常量池。
String str2 = new String(“123”); ====> 直接在堆区创建新的对象。通过new关键字创建对象是在堆区进行,而在堆区创建新的对象并不会检测对象是否存在,是直接创建,即便字符串的内容相同。
equals()
比较的是字符串内容是否相等。
//源码
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (!COMPACT_STRINGS || this.coder == aString.coder) {
return StringLatin1.equals(value, aString.value);
}
}
return false;
}
//案例代码
String s1 = new String("123");
String s2 = "123";
boolean flag = s1.equals(s2);
System.out.print(flag);//结果为:true
==
比较的是字符串是否指向同一对象(对象地址或者叫哈希值是否相同)
String s1 = new String("123");
String s2 = "123";
boolean flag = (s1==s2);
System.out.print(flag);
//结果为:false
compareTo
反转字符串的方式有很多,这里只说几种。
1、利用StringBuilder的reverse()方法
public class ReverseWithStringBuilderBuiltinMethod {
public static void main(String[] args) {
ReverseWithStringBuilderBuiltinMethod builtinMethod = new ReverseWithStringBuilderBuiltinMethod();
builtinMethod.reverseWithStringBuilderBuiltinMethod("javaguides");
}
public String reverseWithStringBuilderBuiltinMethod(String string) {
final StringBuilder builder = new StringBuilder(string);
//reverse()
display(string, builder.reverse().toString());
return builder.reverse().toString();
}
private void display(String input, String output) {
System.out.println(" input string :: " + input);
System.out.println(" output string :: " + output);
}
}
2、使用 Collections的reverse() 方法
public class ReverseStringUsingCollectionsReverseMethod {
// Function to reverse a string in Java using Collections.reverse()
public static String reverse(String str) {
// base case: if string is null or empty
if (str == null || str.equals(""))
return str;
// create an empty list of characters
List < Character > list = new ArrayList < Character > ();
// push every character of the given string into it
for (char c: str.toCharArray())
list.add(c);
// reverse list using java.util.Collections reverse()
Collections.reverse(list);
// covert ArrayList into String using StringBuilder and return it
StringBuilder builder = new StringBuilder(list.size());
for (Character c: list)
builder.append(c);
return builder.toString();
}
public static void main(String[] args) {
String str = "Java Guides";
// String is immutable
str = reverse(str);
System.out.println("Reverse of the given string is : " + str);
}
}
更多方式详见:https://www.cnblogs.com/taich-flute/p/10070143.html
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{
}
我们来看这段String类代码:
String str = "123";
for(int =0;i<1000;i++){
str += "hello";
}
分析:
循环内部的代码(str += “hello”;)实际上会自动被JVM优化!
优化成如下代码:
StringBuilder sb = new StringBuilder(str);
sb.append("hello");
sb.toString();
每次循环都会new一个StringBuilder对象出来,造成资源浪费。
所以,字符串拼接问题可以直接使用StringBuilder类解决。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{
}
@Override
public synchronized int compareTo(StringBuffer another) {
return super.compareTo(another);
}
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return super.capacity();
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
拥有的成员属性和成员方法基本相同。
都被final关键字修饰。
父类都是AbstractStringBuilder。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
}
StringBuffer类使用了关键字---->synchronized(同步),该关键字在多线程访问时起安全保护作用。
即:
StringBuffer是线程安全的,StringBuilder是线程不安全的。
@Override
public synchronized void setCharAt(int index, char ch) {
toStringCache = null;
super.setCharAt(index, ch);
}
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
@HotSpotIntrinsicCandidate
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
三个类测试字符串相加的代码如下:
package string;
/**
* @author HLM
* 测试 不同场景下String、StringBuilder、StringBuffer的性能
*/
public class Test {
private static int time = 50000;
/*
* 测试String类的字符串相加
* */
public void testString() {
String str = " ";
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
str += "java";
}
long end = System.currentTimeMillis();
System.out.println("String类字符串相加花费时间为:"+(end - begin)+"毫秒");
}
/*
* 测试StringBuilder类的字符串相加
* */
public void testStringBuilder() {
StringBuilder builder = new StringBuilder();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
builder.append("java");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder类字符串相加花费时间为:"+(end - begin)+"毫秒");
}
/*
* 测试StringBuffer类的字符串相加
* */
public void testStringBuffer() {
StringBuffer buffer = new StringBuffer();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
buffer.append("java");
}
long end = System.currentTimeMillis();
System.out.println("StringBuffer类字符串相加花费时间为:"+(end - begin)+"毫秒");
}
/*
* 测试String类的字符串直接相加
* */
public void testZhijie() {
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
String str = "java" + "is" + "good";
}
long end = System.currentTimeMillis();
System.out.println("String类字符串直接相加:"+(end - begin)+"毫秒");
}
/*
* 测试String类的字符串间接相加
* */
public void testJianjie() {
String str1 = "java";
String str2 = "is";
String str3 = "good";
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
String str = str1 + str2 + str3;
}
long end = System.currentTimeMillis();
System.out.println("String类字符串间接相加:"+(end - begin)+"毫秒");
}
public static void main(String[] args) {
Test test = new Test();
test.testString();
test.testStringBuilder();
test.testStringBuffer();
test.testZhijie();
test.testJianjie();
}
}
测试结果如图:
String类字符串相加花费时间为:2435毫秒
StringBuilder类字符串相加花费时间为:3毫秒
StringBuffer类字符串相加花费时间为:10毫秒
String类字符串直接相加:1毫秒
String类字符串间接相加:12毫秒
测试结果分析:
有以下代码:
String a = "hellojava";
String b = "hello"+"java";
System.out.println(a == b);//true
解释:String b = “hello”+“java”; 该行代码在编译期间被JVM自动优化成“hellojava”,然后去常量池中查找"hellojava",因此变量a和b指向的是同一个对象。
又有以下代码:
String x = "hellojava";
String y = "hello";
String z = y + "java";
System.out.println(x == z);//false
解释:因为有符号引用,所以 String z = y + “java”; 编译期间不会被JVM优化。
所以生成新的对象。
再看以下代码:
String x = "hellojava";
final String y = "hello";
String z = y + "java";
System.out.println(x == z);//true
因为变量y被final修饰,final修饰的变量会在常量池保存副本,所以对final变量的访问在编译期间都会直接转换为真实值,那么 String x = “hellojava”; 在编译期间会被JVM自动优化。
有以下代码:
String x = "hellojava";
String y = new String("hellojava");
String z = y.intern();
System.out.println(x == z);//true
注意:
String类的intern方法是它的本地方法。
在JavaSE6之前,intern方法会在运行时常量池查找是否存在内容相同的字符串,如果存在就返回该字符串的引用;如果不存在,就会将该字符串存入常量池,并返回一个指向该字符串的引用。
有如下代码:
String str = new String("java");
在类加载过程中,是在运行时常量池中创建了 “java” 对象,而在代码执行过程中只创建了一个String对象。
所以,涉及两个对象,只创建一个对象。