java常用类库(九)——String

本文详细解析了Java中String类的不可变性原理、为何设计为final,以及与StringBuilder和StringBuffer的对比,涉及内存管理、安全性、性能优化和应用场景。

该类非常重要!方法比较多,请经常翻阅,以熟悉。
来源java.lang.String
先看一张总体框架:
String总体概览

常用构造方法:

最常用的就是直接双引号,以下描述约占1%使用

构造器描述
String(byte[ ] bytes)使用默认字符集解码指定的字节数组构造新的String
String(char[ ] value)包含当前字符数组的字符序列
String(char[ ] value, Charset charset)用指定字符集解码字符数组

常用方法:

返回类型方法名描述备注
charcharAt(int index)返回指定索引的char值-
intcodePointAt(int index)返回指定索引的unicode如a为97
intcompareTo(String anoherString)比较两个字符串0相同,正数大,负数小
intcompareToIgnoreCase(String str)比较两个字符串,忽略大小写0相同,正数大,负数小
Stringconcat(String str)拼接字符串一般用‘+’;追加到末尾
booleancontains(CharSequence s)包含字符序列返回trueCharSequence为String父接口,多态,一般用+
static StringcopyValueOf(char[ ] data)相当于valueOf(char[ ])此方法有重载
static StringcopyValueOf(char[ ] data, int offset, int count)相当于valueOf(char[ ] ,int , int)-
booleanendWith(String suffix)是否以指定后缀结尾-
booleanequals(Object anObject)此字符串与指定对象比较-
booleanequalsIgnoreCase(String anotherString)两个String对象比较,忽略大小写-
static Stringformat(String format, Object… args)按指定格式格式化字符串方法有重载
byte[ ]getBytes()使用默认字符集将字符串存入到新的字符数组此方法有重载
inthashCode()返回哈希码-
intindexOf(int ch)返回指定字符第一次在字符串中出现的字符索引;没有返回-1注意字符与int互换
intindexOf(int ch, int fromIndex)从指定索引开始,返回第一次在字符串中出现的字符索引;没有返回-1-
intindexOf(String str)返回第一次出现指定字符串的索引,没有-1-
intindexOf(String str, int fromIndex)从指定位置开始,返回第一次出现指定字符串的索引,没有-1-
booleanisEmpty()判断是否空length()是0返回true
booleanisBlank()如果字符串为空或仅包含空格,返回true-
static Stringjoin(CharSequence delimiter, CharSequence…elements)按指定分隔符连接字符串方法有重载,第二个参数为Iterable<? extends CharSequence> elements,就是集合(我的理解)
intlastIndexOf(int ch)返回最后一次出现的字符索引从后往前找,剩下和从前往后的其他方法一样,不赘述
intlength()返回字符串长度-
booleanmatches(String regex)判定字符串是否与指定字符串匹配-
Stringrepeat(int count)重复此字符串count次-
Stringreplace(char oldChar, char newChar)所有旧字符被替换成新的-
StringreplaceAll(String regex, String replacement)按正则匹配替换-
String[ ]spilt(String regex)按指定字符分隔字符串-
String[ ]spilt(String regex, int limit)limit为正,不超过此长度限制,最后一个条目包含=超出的所有输入;为0,任意长度,丢弃尾随空格;为负数任何长度-
booleanstartsWith(String prefix)是否以指定前缀开始-
booleanstartsWith(String prefix, int toffset)指定toffset开始判断是否满足指定前缀toffset为负或大于String对象长度,返回false
Stringstrip()删除所有前导和尾随空格-
StringstripLeading()删除前导空格-
StringstripTrailing()去掉尾随空格-
Stringsubstring(int beginIndex)返回规定位置开始得=的子串-
Stringsubstring(int beginIndex, int endIndex)返回规定起始位置的子串-
StringtoCharArray()转换成字符数组-
StringtoLowerCase()全部转换成小写要获取区域设置不敏感请加Local local参数,此方法相当于toLowerCase(Local.getDefault());由于Unicode,大小写映射不总是1:1,所以生成的长度可能和原始String长度不一致
StringtoUpperCase()全部转换成大写要获取区域设置不敏感请加Local local参数,此方法相当于toUpperCase(Local.getDefault());由于Unicode,大小写映射不总是1:1,所以生成的长度可能和原始String长度不一致
char[ ]toCharArray()转换为数组-
Stringtrim()删除所有空格和这里的空格定义为其代码点小于或等于 ‘U+0020’ (空格字符)的任何字符
static StringvalueOf( ? )获取问号的字符串由于是重载方法,里面的问号替换基本数据类型,包括char数组和Object

源码及其相关问题部分

关于unicode字符
使用final修饰的类,底层使用final数组实现,
StringSource
还需要注意,他已经实现了序列化。

相关问题

问:为什么String类是不可变的?

答:

在Java里面String类型是不可变对象,这一点毫无疑问,那么为什么Java语言的设计者要把String类型设计成不可变对象呢?这是一个值得思考的问题
Java语言的创建者James Gosling,曾经在一次采访中被人问到:什么时候应该使用不可变对象(immutable object),他回答:任何可以使用的时候都会使用。
在这之前,我们先来简单了解一下,什么是不可变对象?
不可变对象指的是在对象创建之后,对象的内部状态以及对象的内存指针地址都不不能被改变。在Java里面final关键字就是用来辅助创建不可变对象的,但需要注意的是,对于基本类型被final修饰后,就彻底变成了不可变对象,而引用类型被final修饰后,仅仅是指针的内存地址不能改变,如果想要变成彻底的不可变类型,要把该对象里面所有的字段都得用final声明,包括嵌套的对象,否则对象的内部状态也是会变化的,这一点需要理解。
ok,下面我们来分析下为什么String是不可变的?
通过String源码可以看到,String类型的底层是由final修饰的char数组存储。
图片: https://uploader.shimo.im/f/m5czln33SIuRr48a.png

String能被设计成不可变类型的一个重要前是因为它是编程语言里面使用频率最高的一种类型。不可变类型带来的好处,体现在四个方面,分别是:缓存,安全,同步和性能。
(一)缓存
在JVM的运行时数据区域里面,有一个专门的字符串常量池用来存储字符串字面量,如下面一段代码:
图片: https://uploader.shimo.im/f/NbCb8y141LTufVos.png
s1和s2变量指针的内存地址其实是一样的,也就是说他们代表是同一个对象,这是jvm常量池做的优化,当第一个字面量声明的时候,它的值会被字符串常量池存储,当s2变量声明的时候,jvm发现常量池已经存在该对象,所以就不会再创建一次,而是直接将一样的内存指针赋值给s2变量,从避免了重复创建对象,节省了内存空间。
此外,由于字符串的不可变性,从而可以让其hashCode也被缓存,在Java里面哈希类数据结构如HashMap, HashTable, HashSet其key用的最多的基本都是String类型,如此一来key的hashCode的也可以在第一次调用之后被缓存,之后直接使用无须重新生成,从而间接的提升访问效率。
(二)安全
不可变特性也能够减少了应用程序在运行时间的安全问题,如下面的一段代码:
图片: https://uploader.shimo.im/f/wSoHMVdQJXFxyd04.png

在上面的一段代码,在调用这个方法之后,先检查用户名,如果合法才可以继续查询相关数据,如果String可变,那么攻击者就可以在通过check验证之后,再改变查询的用户名,那么就会存在安全风险,而不可变性能够避免和减少这一情况。另一方面,如果String是可变的,那么同时运行的其他线程如果修改这个值,就有可能导致混乱。
(三)同步
由于String类型的不可变性,使得String对象可以安全的在多个线程之间传递和访问,也就是说你在多线程中是不能改变字符串本身的值,而是在堆里面新创建一个字符串然后操作。当然如果没有final修饰,你是可以改变这个变量的引用地址,也就是说你可以把新生成的内存引用覆盖原来的变量引用,但这里仅仅是引用,并不是变量的值。这一点要注意。
(四)性能
性能方面,其实前面已经提到了,比如字符串的常量池节省内存,缓存Hash类以字符串做key数据结构的hashCode,从而提高访问性能等。由于字符串是编程语言里面最广泛使用的数据结构,所以针对字符串的不可变性带来的优势,可以放大到整个运行的应用程序,从而带来应用程序整体的性能提升。
总结:
本文主要介绍了Java语言里面String类型为什么设计成不可变类型,以及分析了不可变类型的带来的主要优势,需要注意的是虽然不可变类型能够带来不少的好处,但并不是说其没有弊端,不可变类型的每一次修改都需要在内存中新生成一个对象,从另一个方面说针对经常变化的对象是不适合使用不可变类型的,这也是为什么Java里面还提供了可修改值的StringBuilder和StringBuffer类,这在实际开发中常常是需要根据具体情况权衡的。

问:为什么char数组比String更适合存储密码?

答:

1.由于字符串在Java中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它。并且为了可重用性,会存储在字符串池中,它很可能会保留在内存中持续很长时间,从而构成安全威胁。
由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
2.Java本身建议使用JPasswordField的getPassword()方法,该方法返回一个char[]和不推荐使用的getTex()方法,该方法以明文形式返回密码。由于安全原因,应遵循Java团队的建议,坚持标准而不是反对它。
3.使用String时,总是存在在日志文件或控制台中打印纯文本的风险,但如果使用Array,则不会打印数组的内容而是打印其内存位置。虽然不是一个真正的原因,但仍然有道理。
还是建议使用散列或加密的密码而不是纯文本,并在验证完成后立即从内存中清除它。因此,在Java中,用字符数组存储密码比用字符串是更好的选择。虽然仅使用char[]还不够,你还需要擦除内容才能更安全。

问:为什么Java语言的开发者,把String类定义为final的呢?

答:

因为只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

问:说一说String、StringBuffer、StringBuilder的区别?

答:
String,StringBuffer,StringBuilder

StringBuilder和StringBuffer

注意String是不可变字符串,如果需要拼接,请使用StringBuilder或StringBuffer。
使用前请先创建对象。
常用的方法为append()、toString(),概览图中也有。

StringBuilder为线程不安全,使用注意;查询多可以考虑此结构;
StringBuffer为线程安全,但是效率相对于前者低;

!注意,StringBuffer和StringBuilder的方法近乎一致,构造器参数一致。所以我以StringBuilder来描述,记得改名称就行。.

常用构造器

常用构造器描述
StringBuilder()不包含任何字符,默认长度为16
StringBuilder(int capacity)指定初始容量
StringBuilder(CharSequence seq)包含的指定字符
StringBuilder(String str)包含指定字符串

常用方法:

返回值方法名描述备注
voidtrimToSize()尝试减少字符序列空间-
StringBuilderreverse()反转字符串-
voidsetCharAt(int index, char ch)更改指定索引位置字符-
StringBuilderinsert(int offset, boolean b)指定位置插入boolean值-
StringBuilderinsert(int offset, char c)指定位置插入char值-
StringBuilderinsert(int offset, char[ ] c)指定位置插入char数组-
StringBuilderinsert(int offset, double d)指定位置插入double值-
StringBuilderinsert(int offset, float f)指定位置插入float值-
StringBuilderinsert(int offset, int i)指定位置插入int值-
StringBuilderinsert(int offset, long l)指定位置插入long值-
StringBuilderinsert(int offset, Object obj)指定位置插入Object类型值-
StringBuilderinsert(int offset, String str)指定位置插入String值-
StringBuilderinsert(int index, char[ ] str, int offset, int len)将char数组偏移量offset到len长度插入到index的位置-
StringBuilderinsert(int dstOffset, CharSequence s)指定位置插入CharSequence值-
StringBuilderinsert(int dstOffset, CharSequence s, int start, int end)将s从start开始到end插入到detOffset的位置-
intcompareTo(StringBuilder another)比较大小-
intcapacity()当前容量-
StringBuliderdelete(int start, int end)删除指定字符(串)-
StringBuliderdeleteCharAt(int index)删除指定位置字符-

差不多就这些,后面需要在补充!
下次见,新年快乐啊!
sue
2021-2-12 10:00:43

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值