数据类型:字符串String
字符串常量池
Java 中字符串对象创建有两种形式,
一种为字面量形式,如 String str = "abc";,另一种就是使用 new 这种标准的构造对象的方法,如 String str = new String("abc");,
这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。
这一切都是源于 JVM 为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。
工作原理
当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,
否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
public class Test {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
// 以上两个局部变量都存在了常量池中
System.out.println(s1 == s2); // true
// new出来的对象不会放到常量池中,内存地址是不同的
String s3 = new String();
String s4 = new String();
/**
* 字符串的比较不可以使用双等号,这样会比较内存地址
* 字符串比较应当用equals,可见String重写了equals
*/
System.out.println(s3 == s4); // false
System.out.println(s3.equals(s4)); // true
}
}
字符型常量和字符串常量的区别?
形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的 0 个或若干个字符
含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节),
字符封装类 Character 有一个成员常量 Character.SIZE 值为 16,单位是bits,该值除以 8(1byte=8bits)后就可以得到 2 个字节

装箱与拆箱
// 普通的创建对象方式
Integer a = new Integer(5);
// 装箱 调用了valueOf方法
Integer b = 5;
// 拆箱 调用了intValue方法
int c = b + 5;
包装类存在一个缓存机制,当数值在-128到127之间时会从缓存中获取。
// 装箱过程中,反编译回来发现调用的是valueOf方法,在一范围内会缓存起来,引用的地址相同。
Integer a=5;
Integer b=5;
System.out.println(a==b);//true 比较的是引用的地址
Integer c=129;
Integer d=129;
System.out.println(c==d);//false
String对象一经创建就不可修改。
String 对象的两种创建方式
//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,
//然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";
String str1 = "abcd";
String str2 = new String("abcd");//堆中创建一个新的对象
String str3 = new String("abcd");//堆中创建一个新的对象
System.out.println(str1==str2);//false
System.out.println(str2==str3);//false这两种不同的创建方法是有差别的。
第一种方式是在常量池中拿对象;
第二种方式是直接在堆内存空间创建一个新的对象。
记住一点:只要使用 new 方法,便需要创建新的对象

String 类型的常量池比较特殊。它的主要使用方法有两种
直接使用双引号声明出来的 String 对象会直接存储在常量池中。
如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。
String.intern() 是一个 Native 方法,它的作用是:
如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;
如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false,因为一个是堆内存中的 String 对象一个是常量池中的 String 对象,
System.out.println(s3 == s2);//true,因为两个都是常量池中的 String 对象
字符串拼接
尽量避免多个字符串拼接,因为这样会重新创建对象。
如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

String s1 = new String("abc");这句话创建了几个字符串对象
将创建 1 或 2 个字符串。如果池中已存在字符串文字“abc”,则池中只会创建一个字符串“s1”。
如果池中没有字符串文字“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
Srtring 的方法
final类不能被继承,不能被覆盖
Spring为final类,里面存储数据的成员也是final的。不能被继承与修改
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;
这里面value是引用,引用不可变,是否可以更改引用的变量来达到改变String的目的呢?
public static void main(String[] args) {
char[] arr = new char[]{'a', 'b', 'c', 'd'};
String str = new String(arr);
arr[3] = 'e';
System.out.println("str= " + str);
System.out.println("arr[]= " + Arrays.toString(arr));
//str= abcd
//arr[]= [a, b, c, e]
//用数据atr来初始化String。然后想通过改变atr的引用的值来达到改变String的目,发现没达到目的。
}
这是因为在初始化String时,不是直接将引用进行赋值,而是将value的值重新拷贝了一份。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
尝试采用String提供的方法,因为有不少方法看起来是可以改变String对象的,如replace()、replaceAll()、substring()
// 当使用substring时是重新new了一个对象
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
*/
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);//重新new了一个对象
}
}
return this;
}
StringBuilder和StringBuffer的区别
参考:
https://mp.weixin.qq.com/s/ZyhIHDU7AC0bakorsha12Q
StringBuilder不是线程安全的,StringBuffer是线程安全的
简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[],所以String 对象是不可变的。
在 Java 9 之后,String 、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串 private final byte[] value
而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是AbstractStringBuilder 实现的,大家可以自行查阅源码。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}}
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String类为什么是final类型?
1. 为了实现字符串池
2. 为了线程安全
3. 为了实现String可以创建HashCode不可变性
只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。
如果字符串是可变的,那么会引起很严重的安全问题。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象的值,造成安全漏洞。
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。
譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。
这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

判断字符串是否为数字
1、使用java自带的函数
public static boolean isNumeric1(String str){
int len=str.length();
if(len>1&&str.charAt(0)=='0'){//当大于2位时,左起第一位不能为0
return false;
}
for(int i=0;i<len;i++){//左起每一位都不能为除数字外其他字符
char iStr=str.charAt(i);
if(!Character.isDigit(iStr)){
return false;
}
}
return true;
}
2、采用正则表达式
public static boolean isNumeric1(String str){
Pattern pattern=Pattern.compile("^-?([0-9])|([1-9][0-9]{1,})$");
return pattern.matcher(str).matches();
}
参考:


浙公网安备 33010602011771号