当前位置:   article > 正文

java字符串_java输出单个字符

java输出单个字符

java字符串

一、String类

概述:字符串是由多个字符组成的一串数据(字符序列),它可以看成是一个字符数组。Java程序中的所有字符串面值(如:“abc”)都可以看成是一个字符串对象。

String str = "abc";
相当于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
// String底层是靠字符数组实现的。
  • 1
  • 2
  • 3
  • 4
  • 5

一)特点

  • String类被final修饰,不可被继承
  • 字符串是不可变序列,是常量,一旦被赋值,就不能被改变。(其值不能改变,但是String类型的引用所指向的地址值可以发生改变)
  • 字符串字面值存放在方法区中的字符串常量池,字符串常量池中一定不存在两个相同的字符串
public static void main(String[] args) {
   String name = "zhangsan";
    /**
     * 重新赋值时,并不是直接在原地址上覆盖zhangsan,而是在字符串池中
     * 找到(创建)lisi,然后把lisi的地址赋值给name,从而实现新的赋值过程
     */
    name = "lisi";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意:当我们重新赋值时,并不是直接修改他的字面值,因为字面值是不可改变的,而是通过重新开辟一个空间,把新空间的地址赋值给变量,这就是不可变性

二)构造方法

方法描述
String();无参构造
String(byte[] bytes);把字节数组转换成字符串
String(byte[],int offset,int length);把字节数组的一部分转换成字符串
String(char[] value);把字符数组转换成字符串
String(char[] value,int offset,int count);把字符数组的一部分转换成字符串
String(String original);把字符串常量转换成字符串

示例:

// 直接创建
String s = "abc";// 创建一个字符串对象abc,放在方法区字符串常量池中
// 通过构造器传字符串的创建
String s1 = new String("abc");// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象
// 通过字符数组构造
char chars[] = {'a', 'b', 'c'};
String str2 = new String(chars);// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象
// 通过字节数组构造
byte bytes[] = { 97, 98, 99 };
String str3 = new String(bytes);// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意:字符串直接赋值的方式是先到方法区的字符串常量池里面查找,如果有就直接返回,如果没有就创建并返回。

String str = “abc”;String str2 = new String(“abc”);的区别
  • String str = “abc”:可能创建一个或者不创建对象。直接到方法区的字符串常量池里面查找,如果abc 在字符串池中存在, str直接指向这个内存地址;如果abc在字符串池中不存在,会在java字符串池中创建一个String对象abc,然后str指向这个内存地址,无论以后用这种方式创建多少个值为abc的字符串对象,始终指向的都是该内存地址,即以""方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一 个 String 对象,并在字符串池中维护。
  • String str = new String(“abc”):至少会创建一个对象,也有可能创建两个。因为用到new关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在abc,则不会再字符串池中创建一个String对象,如果不存在,则会在字符串常量池中也创建一个对象。

在这里插入图片描述

三)常用方法

方法名描述
int length()返回字符串的长度
char charAt(int index)根据下标获取字符
int indexOf(String str)查找str首次出现的下标,存在,则返回该下标;不存在,则返回-1
int indexOf(int ch,int fromIndex); 查找指定字符在指定位置后第一次出现处的索引
int indexOf(String str,int fromIndex); 查找指定字符串在指定位置后第一次出现处的索引
char[] toCharArray()将字符串转换成数组。
byte[] getBytes();把字符串转换为字节数组
static String valueOf(...); String类的valueOf方法可以把任意类型的数据转换为字符串
String trim()去掉字符串前后的空格
String toLowerCase();把字符串转换为小写
String toUpperCase()把字符串转换为大写
String replace(String oldString,String newString)把字符串中的子字符串替换为新的子字符串,原字符串不变
String[] split(String str)根据str做拆分,如空格符或给定正则表达式的匹配拆分此字符串
String concat(String str);把字符串进行拼接
String subString(int beginIndex,int endIndex)在字符串中截取出一个子字符串,范围[beginIndex,endIndex),包含开始,不含结束
boolean equals(Object obj);比较字符串的内容是否相同,区分大小写
boolean equalsIgnoreCase(String str);比较字符串的内容是否相同,忽略大小写
int compareTo(String str);按字典顺序比较两个字符串
boolean contains(String str)判断当前字符串中是否包含str
boolean startsWith(String str);判断字符串是否以某个指定的字符串开头
boolean endsWith(String str)判断字符串是否以str结尾
boolean isEmpty();判断字符串内容是否为空
native String intern();直接指向常量池

new String()的一些参考:

演示:

System.out.println("---------字符串方法的使用 1-------------");
        //字符串方法的使用
        //1、length();返回字符串的长度
        //2、charAt(int index);返回某个位置的字符
        //3、contains(String str);判断是否包含某个子字符串
        String content = "java是世界上最好的java编程语言,java真香";
        System.out.println(content.length()); // 26
        System.out.println(content.charAt(content.length() - 1)); // 香
        System.out.println(content.contains("java")); // true
        System.out.println(content.contains("php")); // false

        System.out.println("--------字符串方法的使用 2--------------");
        //字符串方法的使用
        //4、toCharArray();返回字符串对应的数组
        //5、indexOf();返回子字符串首次出现的位置
        //6、lastIndexOf();返回字符串最后一次出现的位置
        System.out.println(Arrays.toString(content.toCharArray()));
        System.out.println(content.indexOf("java")); // 0
        System.out.println(content.indexOf("java", 4)); // 11
        System.out.println(content.lastIndexOf("java"));// 20

        System.out.println("------------字符串方法的使用3------------------");

        //7、trim();去掉字符串前后的空格
        //8、toUpperCase();//把小写转成大写 toLowerCase();把大写转成小写
        //9、endWith(str);判断是否已str结尾,startWith(str);判断是否已str开头
        String content2 = "   hello World   ";
        System.out.println(content2.trim()); // hello World
        System.out.println(content2.toUpperCase()); // HELLO WORLD
        System.out.println(content2.toLowerCase()); // hello world
        String filename = "hello.java";
        System.out.println(filename.endsWith(".java")); // true
        System.out.println(filename.startsWith("hello")); // true

        System.out.println("------------字符串方法的使用4------------------");

        //10、replace(char old,char new); 用新的字符或字符串替换旧的字符或字符串
        //11、split();对字符串进行拆分
        //12、S.substring(开始,结束)  截取某段字符
        System.out.println(content.replace("java", "php"));
        String s = "java is the best,  programing language";
        System.out.println(s.substring(5,16)); // is the best
        // 根据空格分隔字符串
        String[] arr = s.split(" ");
        for (String i : arr) {
            System.out.println(i);
        }
        System.out.println("---------------");
        // 根据空格和逗号分隔,中括号表示选择,可以选择空格或逗号分隔
        String[] arr2 = s.split("[ ,]");
        for (String i : arr2) {
            System.out.println(i);
        }
        System.out.println("---------------");
        /**
         *  根据空格和逗号分隔,中括号表示选择,可以选择空格或逗号分隔,
         *   +号表示可以出现多个空格或逗号
         */
        String[] arr3 = s.split("[ ,]+");
        for (String i : arr3) {
            System.out.println(i);
        }

        //补充两个方法equals 、compareTo();比较大小
        System.out.println("---------补充---------");
        String s1 = "hello";
        String s2 = "HELLO";
        System.out.println(s1.equalsIgnoreCase(s2));

        String s3 = "abc";//97
        String s4 = "ayzawe";//120
        System.out.println(s3.compareTo(s4));

        String s5 = "abc";
        String s6 = "abc";
        System.out.println(s5.compareTo(s6));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
intern()
/**
 * intern:先用equals方法判断字符串常量池中是否存在指定字符串,存在则直接返回常量
 *        池中的字符串地址,如果字符串池中没有该字符串,则创建一个然后返回地址
 * 即:无论过程多么复杂,intern方法最终返回的一定是字符串常量池中的字符串地址
 */
String s1 = "a";
String s2 = new String("a");
System.out.println(s1 == s2);// false
System.out.println(s1 == s2.intern());// true
System.out.println(s2 == s2.intern());// false
System.out.println(s1 == s1.intern());// true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意内容为空与字符串为空的区别:

字符串内容为空:String str = "";

字符串对象为空:String str = null;(对象为空,无法调用方法:空指针异常)

String类拼接

重要规则:常量相加,发生在常量池中;变量相加,发生在堆中

  • 如果两个字符串变量相加,先开辟空间,再做拼接。
  • 如果两个字符串常量相加,先做拼接,然后在字符串常量池里面查找,如果有就直接返回,如果没有就创建并返回
String a = "hello";
String b = "World";
String s = "hello" + "World";//常量相加,直接在常量池中创建对象
String s1 = a + b; // 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
String s2 = a.concat(b); // 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
a += "World";// 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
字符串转数字

使用各包装类的parseXxx()静态方法,但是在使用时要注意字符串必须是纯数字

String i = "150";
System.out.println(Integer.parseInt(i)); // 150

String i = "150s";
System.out.println(Integer.parseInt(i));//Exception in thread "main" java.lang.NumberFormatException: For input string: "150s"//数字格式化错误,150s中含有非数字字符
  • 1
  • 2
  • 3
  • 4
  • 5
字符串转boolean

字符串转换成boolean类型时,true就是true,非true为false

public static void main(String[] args) {
    String i = "true";//为true
    System.out.println(Boolean.parseBoolean(i));//true
    String i1 = "false";//为false
    System.out.println(Boolean.parseBoolean(i1));//false
    String i2 = "ksajd";//非true
    System.out.println(Boolean.parseBoolean(i2));//false
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
字符串比较
==
  • 比较基本数据类型:比较的是具体的值
  • 比较引用数据类型:比较的是对象地址值(相当于把对象地址当做值来比较)
equals

Object.equals()的实现方式也是==,但是String重写了equals方法,所以是比较两个字符串内容是否相同(区分大小写)

public class Test {
    public static void main(String[] args) {
        String s = "a";
        String s1 = "a";
        String s2 = "A";
        System.out.println(s.equals(s1));// true
        System.out.println(s2.equals(s1)); // false
        /**
         * p1.name.equals(p2.name):由于String类重写了equals方法,所以比较的是内容
         * p1.name == p2.name:虽然在堆中是两个对象地址,但是p1.name与p2.name指向
         *          的字符串都存储在字符串常量池中,所以返回相同的地址
         * p1.name == "张三":p2.name指向的就是字符串常量池中的张三
         */
        Person p1 = new Person();
        p1.name = "张三";
        Person p2 = new Person();
        p2.name = "张三";
        System.out.println(p1.name.equals(p2.name)); // true
        System.out.println(p1.name == p2.name); // true
        System.out.println(p1.name == "张三"); // true
    }
}
class Person {
    String name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在这里插入图片描述

== 与 equals的异同:
  • 相同点:equals的实现也是==,即如果不重写equals方法,它与 == 的结果一致
  • 不同点:

1)== 可以比较基本数据类型和引用类型,equals方法只能比较引用数据类型

2)== 比较的是内存地址,equals方法经过重写之后比较的是内存空间中的内容

四)练习

1、键盘录入一个字符,统计字符串中大小写字母及数字字符个数

//键盘录入一个字符串数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串数据:");
String s = sc.nextLine();
//定义三个统计变量,初始化值都是0
int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
//遍历字符串,得到每一个字符
for(int x=0; x<s.length(); x++) {
    char ch = s.charAt(x);
    //拿字符进行判断
    if(ch>='A'&&ch<='Z') {
    	bigCount++;
    }else if(ch>='a'&&ch<='z') {
    	smallCount++;
    }else if(ch>='0'&&ch<='9') {
    	numberCount++;
    }else {
   		System.out.println("该字符"+ch+"非法");
    }
}
//输出结果
System.out.println("大写字符:"+bigCount+"个");
System.out.println("小写字符:"+smallCount+"个");
System.out.println("数字字符:"+numberCount+"个");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2、已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示

import java.util.Scanner;
/*
思路:
1:已知用户名和密码,定义两个字符串表示即可
2:键盘录入要登录的用户名和密码,用 Scanner 实现
3:拿键盘录入的用户名、密码和已知的用户名、密码进行比较,给出相应的提示。字符串的内容比较,用
equals() 方法实现
4:用循环实现多次机会,这里的次数明确,采用for循环实现,并在登录成功的时候,使用break结束循环
*/
public class Test {
    public static void main(String[] args) {
        //已知用户名和密码,定义两个字符串表示即可
        String username = "tom";
        String pwd = "123";
        //用循环实现多次机会,这里的次数明确,采用for循环实现,并在登录成功的时候,使用break结束循环
        for (int i = 0; i < 3; i++) {
            //键盘录入要登录的用户名和密码,用 Scanner 实现
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String name = sc.nextLine();
            System.out.println("请输入密码:");
            String pwd = sc.nextLine();
            //拿键盘录入的用户名、密码和已知的用户名、密码进行比较,给出相应的提示。字符串的内容比较,
            if (name.equals(username) && pwd.equals(pwd)) {
                System.out.println("登录成功");
                break;
            } else {
                if (2 - i == 0) {
                    System.out.println("你的账户被锁定,请与管理员联系");
                } else {
                    //2,1,0
                    //i,0,1,2
                    System.out.println("登录失败,你还有" + (2 - i) + "次机会");
                }
            }
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

3、键盘录入一个字符串,使用程序实现在控制台遍历该字符串

import java.util.Scanner;
/*
思路:
1:键盘录入一个字符串,用 Scanner 实现
2:遍历字符串,首先要能够获取到字符串中的每一个字符
public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的
3:遍历字符串,其次要能够获取到字符串的长度
public int length():返回此字符串的长度
数组的长度:数组名.length
字符串的长度:字符串对象.length()
4:遍历字符串的通用格式
*/
public class Test {
    public static void main(String[] args) {
//键盘录入一个字符串,用 Scanner 实现
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String line = sc.nextLine();
        for(int i=0; i<line.length(); i++) {
            System.out.println(line.charAt(i));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

五)常见面试题

创建了几个对象
第一题
/**
 * 一开始,s指向hello,所以在字符串池中创建了一个hello对象
 * 将s的指向改为hi,此时又在池中创建了一个hi对象,所以创建了两个对象
 */
String s = "hello";
s = "hi";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述

第二题
        /**
         * 创建了一个对象
         * 两个常量相加,编译器会做出优化:由于编译器会判断创建出来的
         * 对象是否有引用指向,为了节约空间,编译器不允许创建垃圾对象,
         * 所以会在底层做出优化,即将多步合成一步,这将避免空间浪费,所
         * 以    String s = "hello" + " zhangsan";
         * 等价于 String s = "hello zhangsan";
         */
        String s = "hello" + "zhangsan";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
第三题
        String s = "hello";// 创建了一个hello对象
        String s1 = "zhangsan"; // 创建了一个zhangsan对象
        /**
         * 在jdk1.8中:
         * 1、首先创建一个StringBuilder对象st
         * 2、调用append方法将hello对象添加到st对象:st.append("hello");
         * 3、调用append方法将zhangsan对象添加到st对象:st.append("zhangsan");
         * 4、调用st的toString方法,将StringBuilder类型转换为String类型
         * 5、将结果返回给s2
         * 所以,s2最终直接指向的是堆中的String对象,而不是常量池中的hellozhangsan对象
         *  创建了3个对象:hello、zhangsan、hellozhangsan
         */
        String s2 = s + s1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

注意:上述是在JDK1.8中的创建过程,JDK17中有所不同

public static void main(String[] args) {
    String s = "hello";
    s += "World";
    System.out.println(s);
}

过程分析:
// 首先跳转DirectMethodHandle.internalMemberName(Object mh) 
static Object internalMemberName(Object mh) {
    return ((DirectMethodHandle)mh).member;
}

// 再跳转首先跳转DirectMethodHandle$Holder.invokeStatic(Object var0, Object var1, Object var2)
static Object invokeStatic(Object var0, Object var1, Object var2) {
    Object var3 = DirectMethodHandle.internalMemberName(var0);
    return MethodHandle.linkToStatic(var1, var2, (MemberName)var3);
}

// 再跳转StringConcatHolder.simpleConcat(Object first, Object second)
static String simpleConcat(Object first, Object second) {
    String s1 = stringOf(first); // first = "hello"
    /*
        static String stringOf(Object value) {
            String s;
             //判断传入的第一个参数是否为null,不为null返回该字符串,即 "hello"
             //若第一个参数为null,则将null转换为字符串并返回,即返回  "null"
            return (value == null || (s = value.toString()) == null) ? "null" : s;
        }
      */
    String s2 = stringOf(second); // second = "world"
    // 判断是否为空字符串,即 "",注意空字符串 "" 与 null的区别
    if (s1.isEmpty()) {
        // 创建第二个参数为字符串对象
        return new String(s2);  
    }
    if (s2.isEmpty()) {
        // 创建第一个参数为字符串对象
        return new String(s1);
    }
    // 
    long indexCoder = mix(initialCoder(), s1); //获取第一个参数的长度 s1 = "hello"
	    /*
	    	// String.COMPACT_STRINGS = true
	    	// LATIN1 = 0
	    	static long initialCoder() {
	        	return String.COMPACT_STRINGS ? LATIN1 : UTF16;
	    	}
	    */
		/*
	    static long mix(long lengthCoder, String value) {
	    	// 获取字符串长度
	        lengthCoder += value.length();
	        if (value.coder() == String.UTF16) {
	        	/*
	        		 //COMPACT_STRINGS = true
	        		 // coder = 0
	                 byte coder() { // 确定编码长度
	                    return COMPACT_STRINGS ? coder : UTF16;
	                 }
	             */
	             
	            lengthCoder |= UTF16;
	        }
	        return checkOverflow(lengthCoder); // 检查是否溢出
		        /*
				private static long checkOverflow(long lengthCoder) {
		            if ((int)lengthCoder >= 0) {
		                return lengthCoder; // 返回字符串长度
		            }
		            throw new OutOfMemoryError("Overflow: String length out of range");
		    		}
		    	*/
	    	
	    	}
	    */
        
    indexCoder = mix(indexCoder, s2);// 加上第二个参数的长度
    byte[] buf = newArray(indexCoder); // 创建一个新的数组
		/*
	        static byte[] newArray(long indexCoder) {
	            byte coder = (byte)(indexCoder >> 32);
	            int index = (int)indexCoder;
	            return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index << coder);
	        }
		*/

    indexCoder = prepend(indexCoder, buf, s2);
		/*
			private static long prepend(long indexCoder, byte[] buf, String value) {
	        indexCoder -= value.length(); // 10 - 5
	        if (indexCoder < UTF16) {
	            value.getBytes(buf, (int)indexCoder, String.LATIN1);
	            /*
	            void getBytes(byte[] dst, int dstBegin, byte coder) {
	                if (coder() == coder) {
	                    System.arraycopy(value, 0, dst, dstBegin << coder, value.length);
	                } else {    // this.coder == LATIN && coder == UTF16
	                    StringLatin1.inflate(value, 0, dst, dstBegin, value.length);
	                }
	            }
	            */
	
	        } else {
	            value.getBytes(buf, (int)indexCoder, String.UTF16);
	        }
	        	return indexCoder;
	    	}
		*/
        
    indexCoder = prepend(indexCoder, buf, s1);
    return newString(buf, indexCoder);
		/*
			static String newString(byte[] buf, long indexCoder) {
	            // Use the private, non-copying constructor (unsafe!)
	            if (indexCoder == LATIN1) {
	                return new String(buf, String.LATIN1);
	            } else if (indexCoder == UTF16) {
	                return new String(buf, String.UTF16);
	            } else {
	                throw new InternalError("Storage is not completely initialized, " + (int)indexCoder + " bytes left");
	            }
	        }
		*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124

二、可变字符串

概述:每次对字符串进行拼接操作,都会构建一个新的String对象,既耗时又浪费空间,而可变字符串就可以解决这个问题。

由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。例如:

public class StringDemo {
    public static void main(String[] args) {
        String s = "Hello";
        s += "World";
        System.out.println(s);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改

根据这句话分析我们的代码,其实总共产生了三个字符串,即"Hello""World""HelloWorld"。引用变量s首先指向Hello对象,最终指向拼接出来的新字符串对象,即HelloWord 。由此可知,如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。为了解决这一问题,可以使用可变字符串类。

可变字符串分为java.lang.StringBuffer类和java.lang.StringBuilder

StringBuilderStringBuffer的直接父类是AbstractStringBuilder,而且他们的数据也是存放在AbstractStringBuilder类中的char[] value数组中,这里的value数组没有使用final修饰,且value的值是存放在堆中的,所以StringBuffer和StringBiulder才是可变的,注意:StringBuffer和StringBiulder都是final修饰的类,所以不可继承

在这里插入图片描述

StringBuffer类

概念:可变长字符串,JDK1.0提供,运行效率慢、线程安全。可在内存中创建可变的缓冲空间,存储频繁改变的字符串,线程安全的可变字符序列。其所有成员方法都使用synchronized关键字修饰,所以是线程安全的

StringBuffer与String的区别:
  • String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低 //private final char value[];
  • StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高 //char[] value; 这个放在堆.
  • 在使用StringBuffer做字符串拼接,不会浪费太多的资源
构造方法:

public StringBuffer(); 无参构造方法,其初始容量为 16 个字符

public StringBuffer(int capacity); 指定容量的字符串缓冲区对象

public StringBuffer(String str); 指定字符串内容的字符串缓冲区对象

成员方法:

int capacity(); 返回当前容量 – 理论值

int length(); 返回长度(字符数) – 实际值

(1)添加功能
  • StringBuffer append(String str); 把字符串添加到字符串缓冲区,并返回字符串缓冲区本身

注意:该方法被多次重载,可以把任意类型的数据添加到字符串缓冲区

  • StringBuffer insert(int offset,String str); 在指定位置把字符串插入到字符串缓冲区,并返回本身

    注意:该方法被多次重载,可以在指定位置把任意类型的数据插入到字符串缓冲区

(2)删除功能
  • public StringBuffer deleteCharAt(int index); 删除指定位置的字符,并返回本身

  • public StringBuffer delete(int start,int end); 删除从指定位置开始到指定位置结束的内容,并返回本身

    注意:包含start,但是不包含end

(3)替换功能
  • public StringBuffer replace(int start,int end,String str); 从start开始到end结束用str替换,并返回本身
  • public void setCharAt(int index,char ch); 将给定索引处的字符设置为ch
(4)反转功能

public StringBuffer reverse();

(5)截取功能
  • public String substring(int star);
  • public String substring(int star,int end);

注意:截取功能和前面的几个功能有所不同,其返回值是String类型,但本身并没有发生改变,即字符串本身并没有

        String a = "hello World !";
        String s = a.substring(5, a.length() - 1);
        System.out.println("原字符串:" + a); // 原字符串:hello World
        System.out.println("截取的字符串:" + s); //截取的字符串: World 
  • 1
  • 2
  • 3
  • 4
String和StringBuffer的相互转换:
(1)String -> StringBuffer
  • 构造方法:public StringBuffer(String str); //返回才是StringBuffer对象,str不变
  • append方法:public StringBuffer append(String str);
(2)StringBuffer -> String
  • 构造方法:public String(StringBuffer sb);

  • toString方法:public String toString();

    备注:StringBuilder已经覆盖重写了Object当中的toString方法。

StringBuffer与数组的区别:

两者都可以看作是一个容器,用来放置数据:

  • StringBuffer的数据最终是一个字符串数据
  • 数组可以放置多种类型的数据,但同一个数组中的数据必须是同一类型的
String和StringBuffer作为参数传递:
  • String是常量值,一种特殊的引用类型,当它作为参数传递时,效果和基本数据类型作为参数传递一样
  • StringBuffer作为参数传递,当它用来调用方法时,其形式参数的改变直接影响实际参数的改变

StringBuilder类

可变长字符串,JDK5.0提供,运行效率快、线程不安全,其方法和属性与StringBuffer几乎完全一致,只是没有使用synchronized关键字修饰,java.lang.StringBuilder 是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。 默认16字符空间,超过自动扩充

使用时机

字符串缓冲区被单个线程使用的时候,即单个线程的时候使用StringBuilder效率较高,多个线程的时候使用StringBuffer比较安全

线程安全(多线程内容)

安全 -- 同步 -- 数据是安全的
不安全 -- 不同步 -- 效率高一些
  • 1
  • 2

安全和效率两者不可兼得,需要根据需求有所取舍:

安全:银行网站,医院网站
效率:新闻网站,论坛
  • 1
  • 2
String,StringBuffer和StringBuilder的区别:

(1)String的内容和长度都是不可变的,而StringBuffer和StringBuilder的内容和长度都是可变的

(2)StringBuffer是同步的,数据安全,但效率低;而StringBuilder是不同步的,效率高,但不安全(多线程情况)

注意:StringBuilder和StringBuffer没有重写Object的equals方法

二者的方法基本相同,StringBuilder线程不安全,但是效率高,StringBuffer线程安全,效率低,单线程使用StringBuilder,他们都比String效率高,比较节省内存

测试StringBuffer与StringBuilder与String之间的效率
public class TestStringBuilder {
    public static void main(String[] args) {
        /**
         * 测试String类与StringBuilder的效率
         */
        StringBuilder str = new StringBuilder("abc");
        String str1 = new String("abc");
        /**
         * 测试string与StringBuilder的效率
         */
        long r = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
        long time = System.currentTimeMillis();//获取当前系统时间
        //测试string
        for (int i = 0; i < 500; i++) {
            str1 = str1 + i;
            //System.out.println(str1.hashCode());
        }
        long r1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
        long time1 = System.currentTimeMillis();//获取当前系统时间
        System.out.println("运行时间:" + (time1 - time));
        System.out.println("占用空间:" + (r - r1));

        long r3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
        long time3 = System.currentTimeMillis();//获取当前系统时间
        //测试StringBuilder
        for (int i = 0; i < 500; i++) {
            str.append(i);
            //System.out.println(str.hashCode());
        }
        long r4 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
        long time4 = System.currentTimeMillis();//获取当前系统时间
        System.out.println("运行时间:" + (time4 - time3));
        System.out.println("占用空间:" + (r3 - r4));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
32、链式调用

连续调用。该方法的核心是调用了return this,把自己返回了,只要是返回自己,就可以连续调用

StringBuilder str = new StringBuilder();
str.insert(0, "张").insert(1, "三").reverse().reverse();
System.out.println(str);//输出三张



public StringBuilder reverse() { // 反转字符串
    super.reverse();
    return this;//返回自己
}

public StringBuilder insert(int offset, String str) {
    super.insert(offset, str);//在指定位置插入字符串
    return this;//返回自己
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/558956
推荐阅读
相关标签
  

闽ICP备14008679号