String类
- String类:代表字符串。如“abc”都是该类的实现。
- String是一个final,代表不可变的字符序列 。
- 字符串是常量,用双引号表示。它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字节数组byte[]中的。(String的底层就是一个byte[]实现)
String的不可变性
String类位于java.lang包中,主要用来处理在初始化后其不能被改变的字符串。
那么为什么不能被改变呢?
- final修饰一个类时,表明这个类不能被继承。
- final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
不可变性体现在哪?
首先了解: java中 “ == ” 判断符的特性
- 在与基本数据类型进行判断时,“ == ” 比较的是两者的值
- 在与引用数据类型进行判断时,“ == ” 比较的是两者的地址
- 基本数据类型:long、int、short、byte、float、double、char、boolean
- 引用数据类型:类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
再了解equlas()方法
equals方法是由Object类提供的,可以由子类来进行重写
了解这些后,那么再来思考一个问题:
String s1 = "abc"; // 字面量方式定义
String s2 = "abc";
System.out.println( s1 == s2);
首先了解下java的常量池概念:
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
通过字面量的方式(区别new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
但是字符串常量池中是不会存储相同内容的字符串的
这里可以看到s1与s2引用的内存区域是相同的,所以System.out.println( s1 == s2);输出的是true
接下来可以思考,当我把s1的值修改后,s1与s2的引用的内存区域还是相同的吗?
String s1 = "abc"; // 字面量方式定义
String s2 = "abc";
s1 = "java";
System.out.println( s1 == s2);
可以很容易想到,既然s1与s2的内容不一样了,那么在常量池中也不能引用同一个区域,所以会开辟一个新区域给s1引用
所以当对字符串重新赋值时,需要重新指定内存区域,不能用原有的value进行赋值
字面量定义与new实例化String类的区别
String s1 = "abc"; // 字面量方式定义
String s2 = new String("abc");
System.out.println(s1 == s2);
先要知道栈跟堆的区别:
- 栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。
- 堆内存用于存放由new创建的对象和数组
因为堆内存用于存放由new创建的对象和数组,而s1的地址在编译时就确定在字符串常量池中,s2则是在运行阶段确定地址在堆中,所以可以确定s1的地址不等于s2的地址。 因此输出的是 false
接着可以比较:System.out.println(s1.equals(s2));
这输出的又是什么呢?
前面知道在Object类中,equlas()方法实际上就是“ == ”判断符
那么在类中,如果没有重写equlas()方法,则比较的永远是地址
看源码:
从源码中可以看出:String 类中重写了 equals() 方法用于比较两个字符串的内容是否相等。
String类常用的方法
1. 获取字符串长度:length()方法
String s1 = "abc";
System.out.println(s1.length()); // 3
2. 字符串的比较:equals(String anotherString)、compareTo(String anotherString)
compareTo()方法是按照字典顺序与参数s指定的字符串比较大小。
String s1 = "abc";
String s2 = "aba";
System.out.println(s1.compareTo(s2)); // 2 大于0
3. 获取字符串的子串:substring(int start,int end) //第二个参数可选
String s1 = "abcdefg";
String s2 = s1.substring(2); // cdefg
String s3 = s1.substring(2, 5); // cde
4.获取指定索引处的字符:charAt(int index)
String s1 = "abcdefg";
System.out.println(s1.charAt(2)); // c
5.获取str在字符串对象中第一次出现的索引:indexOf(String str)
String s1 = "abcdefg";
String s2 = "cd";
System.out.println(s1.indexOf(s2)); // 2
6.比较字符串的内容是否相同,忽略大小写:equalsIgnoreCase(String anotherString)
String s1 = "abcdefg";
String s2 = "ABCDEFG";
System.out.println(s1.equalsIgnoreCase(s2)); // true
StringBuffer类
StringBuffer类表示的是一个本身内容可变的字符串对象,包含一个缓冲区
,主要用于完成字符串的动态添加、插入、和替换等等操作
String与StringBuffer、StringBuilder的异同:
- String:不可变的字符序列:底层使用byte[]存储
- StringBuffer:可变的字符序列:线程安全的,但效率低;底层使用byte[]存储
- StringBuilder:可变的字符序列:jdk5后才有的线程不安全的,但效率高;底层使用byte[]存储
String与StringBuffer的区别在于是否可变;
StringBuffer与StringBuilder的区别在于线程是否安全
为什么StringBuffer可变呢,怎么实现缓冲的呢?
来看一下源码,在没有传参的情况下默认构造初始容量为16的空字符缓冲区。
有参数的情况下,初始容量是16+字符串的长度,然后用append()方法将传入的字符放入字符缓冲区中:
如果在追加字符串的时候容量不够了怎么办,我们看到有个ensureCapacityInternal()的方法:
在ensureCapacityInternal()
中会判断需不需要扩容:
如果需要扩容,则会进入newCapacity()
方法中重新设定大小
StringBuffer类的常用方法
1. 添加操作:append(xxx)
StringBuffer s1 = new StringBuffer("abc");
s1.append("def");
System.out.println(s1);
2.插入操作:insert(int offset , xxx) offset参数是要插入的位置
StringBuffer s1 = new StringBuffer("abcdef");
s1.insert(2,"def");
System.out.println(s1); // abdefcdef
3.删除操作:delete(int start, int end) star是删除的起始序号,end是删除的终止序号
注意:包括start,但不包括end
StringBuffer s1 = new StringBuffer("abcdef");
s1.delete(2,4);
System.out.println(s1); // abef
4.内容替换:replace(int start,int end,String str)
StringBuffer s1 = new StringBuffer("abcdef");
s1.replace(2,4,"qqqq");
System.out.println(s1); // abqqqqef
5.反转操作:reverse()
StringBuffer s1 = new StringBuffer("abcdef");
s1.reverse();
System.out.println(s1); // fedcba