java之——Spring类
1、创建字符串(三种方式)
1 // 方式一 2 String str = "hello!"; 3 // 方式二 4 String str = new String("hello!"); 5 // 方式三 6 char[] arr = {'h', 'e', 'l', 'l', 'o'}; 7 String str = new String(arr);
注:其创建方式不止有这三种,只是这三种比较常用。
用String类型创建出来的字符串,一旦创建就不能进行修改,例如方式一中的hello是一个字符串常量,存储在常量池中(在后面第三条会介绍),而类似我们常用的str = str + "a"这种方式看似是在str的后面增加了一个a字符,但实际上创建了一个新的字符串,并将str重新指向这个新的字符串。
2、字符串比较相等
在C/C++中我们通常用 "==" 操作符来进行判等操作,在java中==只适用于基本类型,引用类型的==比较的是引用所指对象是否是同一个,即地址是否相等,而不是比较其所指内容是否相等,字符串相等比较需要用到equles方法。
注:在java中引用类型类似于C语言中的指针,但其功能简化了很多,在上述方式一创建的字符串str,str中存储的知识hello字符串在内存中存储的地址,包括java中的数组,自定义类型等其对象都是引用类型,在println的时候会自动调用对象的toString方法,否则打印出来的就是其内容的地址。
1 String str = "hello"; 2 String tmp = "hello"; 3 System.out.println(str == tmp); // 比较str与tmp的指向地址 4 System.out.println(str.equals(tmp)); // 比较str与tmp的指向空间内容
注:若是用方式一创建出来的String对象其==比较结果是true,因为hello直接存储在常量池中,无论创建多少个值为hello的String对象,其hello字符串都只有一个,其对象存储的都是该字符串在常量池中的存储地址。
在使用equles方法时,若出现一个字符串常量与一个对象进行判等,如下所示:
1 String str = "hello"; 2 System.out.println(str.equals("hello")); // 方式一 3 System.out.println("hello".equals(str)); // 方式二
在语法方面,方式一与方式二均正确,但推荐使用方拾二,因为hello是一个确定的字符串常量,而str不确定是否是null,若str在前面被置为了null,则null.equles()方法会抛出异常,对null进行解引用。
3、字符串常量池
前面提到过在创建字符串时如果直接将“hello”这类字符串赋值给对象,“hello”被称为字符串常量,其存储在JVM在内存中单独开辟出来的一块空间——字符串常量池。无论用hello字符串创建多少个对象,其在内存中都只存在一份。String类在设计中使用了共享设计模式。
若采用String类的构造方法创建对象,即方式二,在内存中会创建多个hello内容的String类型空间。这种方式有一个缺点,初始化的时候会创建两块空间,其中一块在执行完操作后悔被JVM垃圾回收机制当成垃圾回收,效率相对会低下一些,因此我们在创建String对象的时候最好采用第一种方式。
String类还可以通过intern方法将一个字符串放入到常量池中。该方法会将这个字符串的内容在字符串常量池中寻找,看当前这个内容是否在池中,若在池中,则返回该池中的地址;若不存在,则把当前内容放在池中并返回这个字符串在池中的地址。
4、字符串的不可变性
在前面提到过如果对一个字符串进行连接操作,即str = str+"a";实际上是创建一个新的字符串,并将str指向这个新的字符串,若原字符串没有对象再指向,则会被JVM垃圾回收机制释放该块空间。
在String的源码中可以看到字符串实际上是一个private final byte[] 类型的数组。final修饰就表明这是一个常量一经创建便不可修改。在JDK1.8之前其底层使用的是char类型的数组,但是后来开发人员发现人们在字符串中使用拉丁字符占了绝大多数,而一个拉丁字符只需要一个字节就可以表示,但在java中char类型是两个字节来表示,这样就会造成大量的空间浪费,因此使用了一个字节大小的byte类型数组来存储字符串,上面说到了存储拉丁字符的情况占了绝大多数情况,但是会有存储汉字的情况,一个汉字需要两个字节来进行存储,这样一个byte类型就没办法存储一个汉字,在String类中还有一个变量叫做coder,只能取LATIN1和UTF16两个值,根据属性COMPACT_STRINGS是否为true来判断是否需要压缩存储,若为ture,则coder=LATIN1,此时就是一个byte来表示一个字符。(只是大体将我的理解说出来了,要是有不正确的地方欢迎大佬进行更正)
因此想要修改字符串的内容只有两种方法:
(1)借助原字符串创建一个新的字符串,将该对象指向新的字符串,释放原字符串空间。
(2)通过反射方法破坏String类的封装机制,强制修改其private属性内容。
1 String str = "Hello"; 2 // 获取到String类中的value字段 3 Field valueField = String.class.getDeclaredField("value"); 4 // 将value字段的访问属性设置成true 5 valueField.setAccessible(true); 6 // 获取到str对象的value属性 7 char[]value = (char[])valueField.get(str); 8 // 修改str中value的值 9 value[0] = 'h';
注:反射打破了封装,牺牲了编译器自身的一些校验机制,需要人工确保代码的准确性。
为什么String要设置成不可变?
(1)方便实现字符串常量池,若String可变,则一个对象对该字符串进行修改后悔影响所有指向这个池对象的结果;
(2)不可变对象是线程安全的;
(3)更加方便缓存hash code,hash code不变方便其作为Key是可以更高效的保存到HashMap中。
5、字符与字符串
方法名称 | 类型 | 描述 |
public String(char[] ch) | 构造 | 将字符串所有内容变为字符串 |
public char charAt(int index) | 普通 | 查询index索引位置的字符,索引从0开始 |
public char[] toCharArray() | 普通 | 将字符串转变为字符数组返回 |
6、字节与字符串
方法名称 | 类型 | 描述 |
public String(byte[] bytes) | 构造 | 将字节数组中的所有内容变为字符串 |
public byte[] getBytes() | 普通 | 将字符串转换为字节数组返回 |
7、字符串常见操作
(1)字符串比较
方法名称 | 类型 | 描述 |
public boolean equles(Object anObject) | 普通 | 区分大小写比较是否相等 |
public boolean equlesIgnoreCase(String anotherString) | 普通 | 不区分大小写比较是否相等 |
public int compareTo(String anotherString) | 普通 |
比较两个字符串的大小关系, 若this>anotherString,返回一个大于零的数;反之返回一个小于零的数字;若相等则返回零 |
(2)字符串查找
方法名称 | 类型 | 描述 |
public boolean contains(Charsequence s) | 普通 | 判断 s 字符串是否是子串 |
public int indexOf(String str) | 普通 | 从前向后寻找 |
public int indexOf(String str, int fromIndex) | 普通 | 从指定位置向后寻找 |
public int lastIndexOf(String str) | 普通 | 从后向前寻找 |
public int lastIndexOf(String str, int fromIndex) | 普通 | 从指定位置由后向前寻找 |
public boolean startsWith(String prefix) | 普通 | 判断是否以prefix字符串开头 |
public boolean startsWith(String prefix, int toffset) | 普通 | 从toffset位置开始,是否以prefix字符串开头 |
public boolean endWith(String suffix) | 普通 | 判断是否以suffix字符串结尾 |
(3)字符串替换
方法名称 | 类型 | 描述 |
public String replaceAll(String regex, String replacement) | 普通 | 将regex全部替换成replacement |
public String replaceFirst(String regex, String replacement) | 普通 | 替换首个内容 |
(4)字符串拆分
方法名称 | 类型 | 描述 |
public String[] split(String regex) | 普通 | 将字符串按照regex来切分成几部分 |
public String[] split(String regex, int limit) | 普通 | 将字符串按照regex来切分成最多limit部分 |
(5)字符串截取
方法名称 | 类型 | 描述 |
public String substring(int beginIndex) | 普通 | 截取包括beginIndex索引往后的所有内容 |
public String substring(int beginIndex, int endIndex) | 普通 | 截取[beginIndex,endIndex)索引范围的内容 |
(6)其他
8、StringBuilder和StringBuffer
任何字符串常量都是String类型对象,而且String一旦申明则不可改变,为了方便字符串的修改,java中提供了两个类StringBuilder和Stringbuffer类。若频繁进行字符串内容的修改,在此情况下可以考虑使用StringBuffer。例如用append方法来进行字符串的增加。
public StringBuffer append(各种数据类型 d)
注:String和StringBuffer两个不能直接进行隐式类型转换:
(1)String-->StringBuffer:利用StringBuffer的构造方法或者append方法
(2)StringBuffer-->String:调用toSting()方法。
String、StringBuilder、StringBuffer三者的区别:
(1)String的内容不可以修改,StringBuffer和Stringbuilder的内容可以修改;
(2)StringBuffer与StringBuilder功能基本上相似;
(3)StringBuffer采用同步处理,属于线程安全操作;StringBuilder不采用同步处理,属于线程不安全操作。