字符串相关的类

一、String类

在Java中String类代表字符串,使用一对""引起来表示。Java 程序中的所有字符串字面值(如 "abc" )都是此类的实例实现。

1.1 String特性

String类在JDK中部分实现如下:

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

可以看出:

  • String声明为final的,不可被继承

  • String实现了一些接口

    • String实现了Serializable接口,表示字符串是支持序列化的

    • String实现了Comparable接口,表示String可以比较大小

  • String内部定义了final char[] value用于存储字符串数据

特别需要注意的是,String代表不可变的字符序列,简称不可变性,主要体现在如果对原有String字符串进行”修改“操作包括:

  • 当对字符串重新赋值时

  • 当对现有的字符串进行连接操作时

  • 当调用String的replace()方法修改指定字符或字符串时

时,均需要重写指定内存区域赋值,不对原有的内存区域进行赋值。例如以下代码:

String s1 = "abc";
String s2 = "abc";//①
s1 = "hello";//②

①时内存状态图示:字符串s1和字符串s2虽然变量名不一致,但是存储内容相同,所以这两个变量的地址值是一致的,都指向字符串常量池中的”abc“字符串常量。

②时内存状态图示:此时对字符串s1重新赋值,”修改“原有字符串的内容,此时不会去改变原地址字符串的值,而是会在字符串常量池中重新开辟一块空间,存储新的字符串常量,并将此字符串的地址值赋给字符串s1。

String通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中,并且字符串常量池中是不会存储相同内容的字符串的。

public class StringTest {
    String str = new String("good");
    char[] ch = { 't', 'e', 's', 't' };
    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'b';
    }
    public static void main(String[] args) {
        StringTest ex = new StringTest();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");//
        System.out.println(ex.ch);
    }
}

关于JVM中字符串常量池存放位置的说明:

  • jdk1.6:字符串常量池存储在方法区(永久区);

  • jdk1.7:字符串常量池存储在堆空间;

  • jdk1.8:字符串常量池存储在方法区(元空间);

1.2 Stirng对象的创建

方式一:通过字面量定义的方式

String s1 = "javaEE";
String s2 = "javaEE";

此时的s1和s2的值”javaEE”声明在方法区中的字符串常量池中。

方式二:通过new + 构造器的方式

String s3 = new String("javaEE");
String s4 = new String("javaEE");

此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。并且此时例如String s = new String("abc");这种方式创建对象,会在内存中创建了两个对象:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"。

对于上述例子,s1、s2、s3和s4的内存图示:

1.3 String拼接

String s1 = "a";:此时在字符串常量池中创建了一个字面量为"a"的字符串。

s1 = s1 + "b";:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符 串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响 程序的性能。

String s2 = "ab";:直接在字符串常量池中创建一个字面量为"ab"的字符串。

String s3 = "a" + "b";:s3指向字符串常量池中已经创建的"ab"的字符串。

String s4 = s1.intern();:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串 赋值给s4。

String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
System.out.println(s3 == s8);//true

结论:

  • 字面量与字面量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

  • 只要其中有一个是变量名,结果就在堆中。但是注意,如果变量名是常量,拼接的结果则是在常量池中,例如:

String s1 = "javaEEhadoop";
String s2 = "hadoop";
String s3 = "javaEE" + s2;
final String s4 = "javaEE";
String s5 = s4 + "hadoop";

System.out.println(s1==s3);//false
System.out.println(s1==s5);//true

 

  • 如果拼接的结果调用intern()方法,返回值就在常量池中

1.4 String常用方法

  • int length():返回字符串的长度;

  • char charAt(int index): 返回某索引处的字符;

  • boolean isEmpty():判断是否是空字符串;return value.length == 0

  • String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写

  • String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写

  • String trim():返回字符串的副本,忽略前导空白和尾部空白

String s1 = "Hello World";
System.out.println(s1.length());//11

System.out.println(s1.charAt(0));//H
System.out.println(s1.charAt(4));//o
//System.out.println(s1.charAt(11));//java.lang.StringIndexOutOfBoundsException: String index out of range

System.out.println(s1.isEmpty());//false
String s2 = "";
System.out.println(s2.isEmpty());//true

String s3 = s1.toLowerCase();
String s4 = s1.toUpperCase();
System.out.println(s1);//s1不可变,Hello World
System.out.println(s3);//hello world
System.out.println(s4);//HELLO WORLD

String s5 = "   H   ell   o   W or   ld    ";
String s6 = s5.trim();
System.out.println("---" + s5 + "---");//---   H   ell   o   W or   ld    ---
System.out.println("---" + s6 + "---");//---H   ell   o   W or   ld---

 

  • boolean equals(Object obj):比较字符串的内容是否相同

  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写

  • String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”

String s1 = "Hello World";
String s2 = "HELLO WORLD";
System.out.println(s1.equals(s2));//false
System.out.println(s1.equalsIgnoreCase(s2));//true

String s3=s1.concat(s2);
System.out.println(s3);//Hello WorldHELLO WORLD

 

  • int compareTo(String anotherString):比较两个字符串的大小

String s1 = "abc";
String s2 = "abe";
System.out.println(s1.compareTo(s2));//-2

 

  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex开始截取到最后的一个子字符串。

  • String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取(包含)到endIndex(不包含)的一个子字符串。(左闭右开)

String s1 = "Hello World";

String s2 = s1.substring(3);
System.out.println(s1);//Hello World
System.out.println(s2);//lo World

String s3 = s1.substring(2,8);
System.out.println(s3);//llo Wo

 

  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束

  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始

  • boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

String s1 = "Hello World";
boolean b1 = s1.startsWith("He");
boolean b2 = s1.startsWith("he");
System.out.println(b1);//true
System.out.println(b2);//false

boolean b3 = s1.endsWith("lld");
System.out.println(b3);//false

boolean b4 = s1.startsWith("ll", 2);
System.out.println(b4);//true

 

  • boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true

  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引

  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出 现处的索引,从指定的索引开始

  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引

  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后 一次出现处的索引,从指定的索引开始反向搜索

    • 注:IndexOf和lastIndexOf方法如果未找到都是返回-1

    • IndexOf和lastIndexOf返回结果相同:

      • 存在长度为1的字符串str;

      • str长度不存在;

boolean b1 = s1.contains("llo");
boolean b2 = s1.contains("lol");
System.out.println(b1);//true
System.out.println(b2);//false

int i1 = s1.indexOf("lo");
int i2 = s1.indexOf("lol");
System.out.println(i1);//3
System.out.println(i2);//-1

int i3 = s1.indexOf("lo", 5);
System.out.println(i3);//15

int i4 = s1.lastIndexOf("lo");
System.out.println(i4);//15

int i5 = s1.lastIndexOf("lo", 14);
System.out.println(i5);//3

 

  • String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。

  • String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

String s1 = "我要好好学习Java和JavaScript";
String s2 = s1.replace("Java", "C++");
System.out.println(s1);//我要好好学习Java和JavaScript
System.out.println(s2);//我要好好学习C++和C++Script

 

  • String replaceAll(String regex, String replacement):使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

  • String replaceFirst(String regex, String replacement):使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string);//hello,world,java,mysql

 

  • boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。

String str = "12345";
//判断str字符串中是否全部有数字组成,即有1-n个数字组成
boolean matches = str.matches("\\d+");
System.out.println(matches);//true
String tel = "0571-4534289";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result);//true

 

  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

  • String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

String str = "hello|world|java";
String[] strs = str.split("\\|");
for (int i = 0; i < strs.length; i++) {
    System.out.println(strs[i]);
}
System.out.println();
String str2 = "hello.world.java";
String[] strs2 = str2.split("\\.");
for (int i = 0; i < strs2.length; i++) {
    System.out.println(strs2[i]);
}

 

 

1.5 String与其他类型转换

  • String 与基本数据类型、包装类之间的转换。(具体见2.4、2.5)

    • String→ 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)

    • 基本数据类型、包装类→String:调用String重载的valueOf(xxx)

  • String 与 char[]之间的转换

    • 字符数组→字符串:调用String的构造器

      • String(char[]) 和 String(char[],int offset,int length) :分别用字符数组中的全部字符和部分字符创建字符串对象。

    • 字符串→字符数组:调用String的toCharArray()

      • public char[] toCharArray():将字符串中的全部字符存放在一个字符数组 中的方法。

      • public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

  • String 与 byte[]之间的转换

    • 字节数组→字符串:调用String的构造器

      • String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。

      • String(byte[],int offset,int length) :用指定的字节数组的一部分, 即从数组起始位置offset开始取length个字节构造一个字符串对象。

    • 字符串→字节数组:调用String的getBytes()

      • public byte[] getBytes() :使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

      • public byte[] getBytes(String charsetName) :使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。

String str = "中";
System.out.println(str.getBytes("ISO8859-1").length);// -128~127
System.out.println(str.getBytes("GBK").length);
System.out.println(str.getBytes("UTF-8").length);
System.out.println(new String(str.getBytes("ISO8859-1"),"ISO8859-1"));// 乱码,表示不了中文
System.out.println(new String(str.getBytes("GBK"), "GBK"));
System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));

 

1.6 String常见算法

二、StringBuffer

java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。StringBuffer中很多方法与String相同,并且StringBuffer 作为参数传递时,方法内部可以改变值。

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;

以上为StringBuffer源码中部分实现,其中value没有final声明,代表value可以不断扩容;count用于记录有效字符的个数

注意:

  • StringBuffer类对象调用.length方法返回的是count不是value.length;

  • value扩容默认情况下扩容为原来的两倍+2,并且同时将原有数组中的元素复制到新的数组中去;但建议避免出现扩容的情况,多使用StringBuffer(int size)这一带参构造器;

StringBuffer类不同于String,其对象必须使用构造器生成,有三个构造器:

StringBuffer():初始容量为16的字符串缓冲区

StringBuffer(int size):构造指定容量的字符串缓冲区

StringBuffer(String str):将内容初始化为指定字符串内容

StringBuffer的常用方法:

  • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接

  • StringBuffer delete(int start,int end):删除指定位置的内容

  • StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str

  • StringBuffer insert(int offset, xxx):在指定位置插入xxx

  • StringBuffer reverse() :把当前字符序列逆转

  • public int indexOf(String str)

  • public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串

  • public int length()

  • public char charAt(int n )

  • public void setCharAt(int n ,char ch)

StringBuffer的一道笔试题:

String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);//
System.out.println(sb.length());//4
System.out.println(sb);//"null"
StringBuffer sb1 = new StringBuffer(str);//抛异常NullPointerException
System.out.println(sb1);//

三、StringBuilder

StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,只不过StringBuilder是线程不安全的而StringBuffer是线程安全的。

四、String、StringBuffer、StringBuilder三者对比

String(JDK1.0):不可变字符序列

StringBuffer(JDK1.0):可变字符序列、效率低、线程安全

StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全

三者底层都使用char[]存储

对比String、StringBuffer、StringBuilder三者的效率,从高到低排列:StringBuilder > StringBuffer > String

//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));

源码分析:

String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//返回的是count不是value.length
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];

 

版权声明:
作者:jackqiang
链接:http://www.jackqiang.com/javase/classes/1898/strings/
来源:JackQiang's
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录