文章目录
- 一、分支循环
- 1.1 分支结构
- 1.2 循环结构
- 1.3 跳转语句
- 1.4 分支循环相关问题
- 1.4.1 switch是否能作用在byte上,是否能作用在long上,是否能作用在String上*
- 1.4.2 continue、break、和return的区别
- 1.4.3 switch(字符串)的实现原理
- 1.4.4 if和switch的性能对比
- 1.4.5 让if...else更优雅的几种写法
- 1.4.6 循环体中的语句要考量性能
- 二、数组
- 2.1 声明数组
- 2.2 初始化数组
- 2.3 遍历数组
- 2.4 数组作为方法参数/方法返回值
- 2.5 数组复制
- 2.6 多维数组
- 2.7 数组在内存中如何分配
- 三、字符串
- 3.1 String的创建
- 3.2 String的使用
- 3.2.1 获取指定位置字符
- 3.2.2 字符串比较
- 3.2.3 字符串连接
- 3.2.4 判断是否以指定子串开头/结尾
- 3.2.5 检索字符/子字符串的所在位置
- 3.2.6 获取字符串长度
- 3.2.7 分割字符串
- 3.2.8 截取字符串
- 3.2.9 删除字符串的头尾空白符
- 3.2.10 将不同类型的值转化为字符串
- 3.2.11 大小写转换
- 3.3 String优化
- 3.3.1 编译优化
- 3.3.2 intern优化
- 3.4 String相关问题
- 3.4.1 什么是字符串常量池?
- 3.4.2 String有哪些特性?
- 3.4.3 为什么String在Java中是不可变的*
- 3.4.4 常量池的使用
- 3.4.5 常规字符串拼接*
- 3.4.6 特殊字符串拼接
- 3.4.7 String s=new String("abc") 创建了几个对象*
- 3.4.8 equals与==的区别
- 3.4.9 String str="i"与 String str=new String(“i”)一样吗?
- 3.4.10 在使用HashMap的时候,用String做key有什么好处?
- 3.4.11 String编码,UTF-8和GBK的区别?
- 3.4.12 数组有没有length()方法?String有没有length()方法?
- 3.4.13 如何实现字符串的反转
- 3.4.14 怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?
- 3.4.15 String s="a"+"b"+"c"+"d";一共创建了多少个对象
- 3.4.16 String性能提升的几个小技巧
- 3.4.17 String类能被继承吗
- 3.5 可变字符串
- 3.5.1 StringBuffer
- 3.5.2 StringBuilder
- 3.5.3 String、StringBuffer和StringBuilder的比较*
- 3.5.4 StringBuilder在日志框架中的使用
- 3.5.5 字符串拼接原理
- 3.5.6 为什么StringBuilder.append()⽅法⽐"+="的性能⾼
- 四、方法
- 4.1 方法的定义
- 4.2 变量作用域
- 五、常用类
- 5.1 Math类
- 5.2 Arrays类
- 5.3 Collections类
- 5.4 Object类
- 5.4.1 Object中的重要方法
- 5.4.2 hashCode与equals*
- 5.4.3 hashCode与equals的区别*
- 5.4.4 hashCode与equals的相关问题
本系列文章: Java(一)数据类型、变量类型、修饰符、运算符 Java(二)分支循环、数组、字符串、方法 Java(三)面向对象、封装继承多态、重写和重载、枚举 Java(四)内部类、包装类、异常、日期 Java(五)反射、克隆、泛型、语法糖、元注解 Java(六)IO、NIO、四种引用 Java(七)JDK1.8新特性 Java(八)JDK1.17新特性
一、分支循环
Java中,除了普通的顺序结构外,特殊的结构有三种:分支结构、循环结构和跳转语句。
1.1 分支结构
switch-case结构与多种if结构作用相似,但是有局限性。其特点:
- 1、JDK1.7之前的版本中,switch语句支持的数据类型有(在整数类型中,唯独不支持long类型);在JDK1.7及以后的版本中,增加了对String类型的支持(编译后是把String转化为hash值,其实还是整数)。
- 2、 。
- 3、break是每种case的结束标志,如无break则会继续走下个case。
- 4、一般最后都会有个default,用来处理匹配不到任何case的情况。
switch使用示例:
1.2 循环结构
Java中循环结构有两种:
- 1、for循环
有三部分:初始化操作、判断终止条件表达式及迭代部分。。
此外,在JDK1.5中还引入了for-each语法,也很常用,示例:
- 2、while循环
有两种:
do-while先执行循环体语句,然后进行判断,也就是无论如何会先执行一次循环体语句。
1.3 跳转语句
- 在switch中,用来终止一个语句序列。
- 用来退出一个循环。
- 返回方法的返回值;
- 终止当前程序。
示例:
测试结果:
i:1
i:2
i:4
i:5
i:6
i:7
i:8
i:9
所求的数字之和是:42
这段代码是一个序列的数字之和,不过不需要+3,所以在i == 3时,使用continue,表示跳过此次循环,进入下一次循环。我们也不希望+10,所以在i == (num-1)时,使用了break,直接跳出循环。return的作用就是返回到调用该方法的地方,有返回值的话将返回值返回,无返回值的话则不用返回。
1.4 分支循环相关问题
1.4.1 switch是否能作用在byte上,是否能作用在long上,是否能作用在String上*
在JDK1.5以前,switch(expr)中,expr只能是byte、short、char、int。
从JDK1.5开始,Java中引入了枚举类型,expr也可以是enum类型。
。
- 为什么只支持上面几种?int、String都可以,为什么不支持long?
原因是switch对应的JVM字节码lookupswitch、tableswitch指令只支持int类型。 byte、char、short类型在编译期默认提升为int,并使用int类型的字节码指令。所以对这些类型使用switch,其实跟int类型是一样的。
1.4.2 continue、break、和return的区别
在循环结构中,当循环条件不满足或循环次数达到要求后,循环会正常结束。但是,有时候可能需要在循环的过程中,提前终止循环,这就是需要用到以下关键词:
- 1、continue
指挑出当前的这一次循环,继续下一次循环。 - 2、break
指跳出整个循环体,继续执行循环下面的语句。 - 3、return
指跳出所在方法,结束该方法的运行。return一般有两种用法:
- ,直接使用return结束方法执行,用于没有返回值函数的方法;
- ,return一个特定值,用于有返回值函数的方法。
1.4.3 switch(字符串)的实现原理
1.4.4 if和switch的性能对比
switch的性能比if高。原因是:在switch中只取出了一次变量和条件进行比较,而if中每次都会取出变量和条件进行比较,因此if的效率就会比switch慢很多。并且,分支的判断条件越多,switch性能高的特性体现的就越明显。
- tableswitch和lookupswitch
对于switch来说,最终生成的字节码有两种形态:一种是tableswitch,另一种是lookupswitch,决定最终生成的代码使用那种形态取决于switch的判断添加是否紧凑。例如到case是1…2…3…4这种依次递增的判断条件时,使用的是tableswitch,而像case是1…33…55…22这种非紧凑型的判断条件时则会使用lookupswitch。
当执行一次时,堆栈顶部的int值直接用作表中的索引,以便抓取跳转目标并即执行跳转。也就是说tableswitc 的存储结构类似于数组,是直接用索引获取元素的,所以,这也意味着它的搜索速度非常快。
而执行时,,所以使用lookupswitch会比tableswitch慢。
1.4.5 让if…else更优雅的几种写法
- 1、使用return
使用return可以去掉多余的else。示例:
- 2、使用Map
使用Map数组,把相关的判断信息,定义为元素信息可以直接避免if…else…判断。示例:
- 3、使用三元运算符
示例:
- 4、合并条件表达式
有些逻辑判断是可以通过梳理和归纳,变更为更简单易懂的逻辑判断代码。示例:
- 5、使用枚举
JDK1.5中引入了枚举。示例:
- 6、使用Optional
从JDK 1.8开始引入Optional类,在JDK1.9时对Optional类进行了改进,增加了ifPresentOrElse()方法,可以借助它,来消除if else的判断。示例:
- 7、梳理优化判断逻辑
即通过分析if…else…的逻辑判断语义,写出更加易懂的代码。示例:
需要尽量把表达式中的包含关系改为平行关系,这样代码可读性更好,逻辑更清晰。
- 8、选择性地使用switch
if和switch都能使用的情况下,可以尽量使用switch,因为switch在常量分支选择时,switch性能会比if…else好。示例:
1.4.6 循环体中的语句要考量性能
以下操作尽量移至循环体外处理,如定义对象、变量、 获取数据库连接,进行不必要的try-catch操作。这样不仅能保证程序的正确性还能提高程序的执行效率。
二、数组
一个数组就代表在内存中开辟一段、用来存储数据的空间,其特征如下:
- 数组名代表的是连续空间的首地址。
- 通过首地址可以依次访问数组所有元素,元素在数组中的位置叫做下标,从0开始。
- 数组长度一旦声明,不可改变不可追加。
2.1 声明数组
数组的声明方式有两种,以int型数组举例:int[ ] array和int array[ ],一般用第一种方式。示例:
该语句的意思是创建一个容量大小为10的int型数组,数组中默认值是0。也就是说,当数组元素未赋值时,数组元素的值为数组元素类型的默认值(此处是int型,所以默认值是0)。
2.2 初始化数组
数组初始化的几种形式:
- 1、直接给每个元素赋值
- 2、给一部分赋值,后面的都为默认值
- 3、由赋值参数个数决定数组的个数
- 4、数组用作可变参数
Java中还有一种数组较冷门的用法是可变参数,示例:
2.3 遍历数组
数组的遍历方式也有两种,以int型数组举例。
- 1、for循环
- 2、foreach循环
2.4 数组作为方法参数/方法返回值
数组可以作为参数传递给方法。示例:
数组也可以数组作为函数的返回值。示例:
2.5 数组复制
对数组进行复制的方法,常常有System.arraycopy和使用Arrays工具类两种方法,这两种方法在JDK源码中经常出现。此处介绍第一种,具体的方法是:
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度
示例:
2.6 多维数组
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组,格式:
type可以为基本数据类型和复合数据类型,typeLength1和typeLength2必须为正整数,typeLength1为行数,typeLength2为列数。例如:
2.7 数组在内存中如何分配
对于数组的初始化,有以下两种方式:静态初始化和动态初始化。
- 1、静态初始化
初始化时显式指定每个数组元素的初始值,由系统决定数组长度,如:
- 2、动态初始化
初始化时显式指定数组的长度,由系统为数据每个元素分配初始值,如:
三、字符串
String(字符串),。这个特性可以从String的底层实现看出:
每次操作 : 隐式。
3.1 String的创建
常常用来比较的两种创建字符串的方式:
第一种直接赋值的方式,创建的对象是在;第二种通过构造方法,创建的对象是在。
JDK1.7及之后版本的JVM将运行时常量池从方法区中移了出来,在Java堆中开辟了一块区域存放运行时常量池。
这两种变量的比较:
==比较的是对象的地址,str1对象和str2对象分别在常量池()和堆,所以用此方式比较的结果为false。Strng重写了equals方法,比较的是对象的值,所以比较结果为true。
3.2 String的使用
3.2.1 获取指定位置字符
从上面可以看出使用charAt时,索引下标是从0开始的,这也从侧面说明了String的底层实现是数组。
3.2.2 字符串比较
- 1、字符串比较(ASCII码)
返回值是两个字符串的ASCII码差值。如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值。如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较。以此类推,直至比较的字符或被比较的字符有一方结束。示例:
- 2、字符串比较(值)
此处重写了Object的euqals方法,String中重写equals方法的步骤:
- 使用==操作符检查“实参是否为指向对象的一个引用”。
- 使用instanceof操作符检查“实参是否为正确的类型”。
- 把实参转换到正确的类型。
- 对于该类中每一个“关键”域,检查实参中的域与当前对象中对应的域值是否匹配。
- 3、字符串忽略大小写比较(值)
3.2.3 字符串连接
3.2.4 判断是否以指定子串开头/结尾
- 1、是否以指定子字符串开头
- 2、是否以指定的子字符串结尾
3.2.5 检索字符/子字符串的所在位置
- 1、从初始位置,检索字符第一次出现的位置
- 2、从指定位置,检索字符第一次出现的位置
- 3、检索子字符串第一次出现的位置
- 4、从指定位置,检索子字符串第一次出现的位置
- 5、从初始位置,检索字符最后一次出现的位置
- 6、从指定位置,检索字符最后一次出现的位置
- 7、检索字符串最后一次出现的位置
- 8、从指定位置,检索字符串最后一次出现的位置
3.2.6 获取字符串长度
3.2.7 分割字符串
测试结果:
分隔符返回值 :
Y
s
t
e
n
。示例:
结果:
3.2.8 截取字符串
- 1、截取子字符串,从指定位置到末尾
- 2、截取子字符串,从start(包含)到end(不包含),即区间是前开后闭
3.2.9 删除字符串的头尾空白符
3.2.10 将不同类型的值转化为字符串
3.2.11 大小写转换
3.3 String优化
3.3.1 编译优化
3.3.2 intern优化
JDK1.7之后,JVM将字符串常量池放入了堆中,之前是放在方法区。
intern()方法设计的初衷,就是重用String对象,以节省内存消耗。
。
当调用intern方法时,如果池已经包含一个等于此String对象的字符串(该对象由equals(Object) 方法确定),则返回池中的字符串。否则,常量池中直接存储堆中该字符串的引用(1.7 之前是常量池中再保存一份该字符串)。简单来说,。
- 示例1
String s = newString(“1”),生成了常量池中的"1"和堆空间中的字符串对象。
s.intern(),这一行的作用是s对象去常量池中寻找后发现"1"已经存在于常量池中了。
String s2 = “1”,这行代码是生成一个 s2 的引用指向常量池中的“1”对象。
结果就是s和s2的引用地址明显不同。因此返回了 false。
String s3 = new String(“1”) + newString(“1”),这行代码在字符串常量池中生成“1” ,并在堆空间中生成 s3 引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
s3.intern(),这一行代码,是将 s3 中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6 的做法是直接在常量池中生成一个 “11” 的对象。
但是在 JDK1.7 中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说 s3.intern() ==s3 会返回 true。
String s4 = “11”, 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此 s3 == s4 返回了 true。
- 示例2
String s3 = new String(“1”) + newString(“1”),这行代码在字符串常量池中生成"1",并在堆空间中生成 s3 引用指向的对象(内容为"11")。注意此时常量池中是没有 “11"对象的。
String s4 = “11”, 这一行代码会直接去生成常量池中的"11”。
s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。
结果就是 s3 和 s4 的引用地址明显不同。因此返回了 false。
- 示例3
- 示例4
str2先在常量池中创建了“SEUCalvin”,那么 str1.intern()当然就直接指向了str2,后面的"SEUCalvin"也一样指向 str2。所以谁都不搭理在堆空间中的str1了,所以都返回了false。
3.4 String相关问题
3.4.1 什么是字符串常量池?
,可以提高内存的使用率,避免开辟多块空间存储相同的字符串。在创建字符串时,JVM会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
3.4.2 String有哪些特性?
- 1、不变性
String是只读字符串。 - 2、常量池优化
String创建之后,会在字符串常量池中进行缓存,如果下次创建同样的String时,会直接返回缓存的引用。 - 3、final
使用final来定义String类,表示String类不能被继承,提高了系统的安全性。String类利用了final修饰的char类型数组存储字符,源码:
3.4.3 为什么String在Java中是不可变的*
String作为不可变类有很多优势。
- 1、字符串池(节约内存)
字符串池是特殊存储区域。创建字符串时,如果池中已存在该字符串时,将返回现有字符串的引用,而不是创建新String。 - 2、缓存Hashcode(高性能)
Java中经常会用到字符串的哈希码(hashcode)。例如,在HashMap或HashSet中,字符串的不可变能保证其hashcode保持一致,这样就可以避免一些不必要的麻烦。这也就意味着每次在使用一个字符串的hashcode的时候不用重新计算一次,这样更加高效。在String类中,有以下这段代码:
此处的hash变量就是为了保存了String对象的hashcode,因为String类不可变,所以一旦对象被创建,该hash值也无法改变。所以,每次想要使用该对象的hashcode的时候,直接获取即可。
String不可变之后就保证的hash值的唯⼀性,这样它就更加⾼效,并且更适合做HashMap的key- value缓存。
- 3、不可变对象自然是线程安全的(安全)
由于无法更改不可变对象,因此可以在多个线程之间自由共享它们,不存在同步的问题。
总之,String被设计成不可变的主要目的是为了安全和效率。
3.4.4 常量池的使用
示例:
创建s1对象时,就在常量池中产生了一个"abc"字符串,所以再用赋值的方式创建相同内容的字符串时,就直接使用常量池中,不再重复创建,所以两种比较方式的结果都是true,因为本就是一个对象。
3.4.5 常规字符串拼接*
示例:
结果分析:s2是创建在常量池中的,。所以两者的地址是不一样的,==比较结果是false;内容是一样的,equals比较结果是true。
3.4.6 特殊字符串拼接
示例:
结果分析:字符串拼接时,会先调用String.valueOf(Object obj)来将对象转换为字符串,该方法的实现是:
所以在字符串拼接时,会先判断是不是null,是null的话,会先转换成"null",而不是""进行拼接。
字符串常量池是在编译期确定好的,(String s1="zxc"或String s2=“z”+“xc”)。
,字符串拼接的实际过程:
3.4.7 String s=new String(“abc”) 创建了几个对象*
3.4.8 equals与==的区别
Java对于eqauls方法和hashCode方法是这样规定的:
- 如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
‘==’ 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。
equals用来比较的是两个对象的内容是否相等。所有的类都是继承自Object类,而Object中的equals方法返回的却是==的判断。Object 类中的equals() 方法:
因此,一般的实体类为了equals方法可以正确使用,都要重写equals方法。
- 总结
==对于基本类型来说是值比较,对于引用类型来说是比较的是引用(即判断两个对象是不是同一个对象)。
equals默认情况下是引用比较,只是很多类重新了equals方法,比如String、Integer等把它变成了值比较,所以一般情况下equals比较的是值是否相等。
3.4.9 String str="i"与 String str=new String(“i”)一样吗?
3.4.10 在使用HashMap的时候,用String做key有什么好处?
HashMap内部实现是通过key的hashcode来确定value的存储位置,因为字符串是不可变的,所以当创建字符串时,它的hashcode被缓存下来,不需要再次计算,所以相比于其他对象更快。
3.4.11 String编码,UTF-8和GBK的区别?
- GBK和UTF8有什么区别
UTF8编码格式很强大,支持所有国家的语言,正是因为它的强大,才会导致它占用的空间大小要比GBK大。
GBK编码格式,它的功能少,仅限于中文字符,当然它所占用的空间大小会随着它的功能而减少。
3.4.12 数组有没有length()方法?String有没有length()方法?
数组没有length()方法,有length的属性。String有length()方法。
3.4.13 如何实现字符串的反转
示例:
3.4.14 怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?
示例:
3.4.15 String s=“a”+“b”+“c”+“d”;一共创建了多少个对象
题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象。
3.4.16 String性能提升的几个小技巧
- 1、不要直接+=字符串
StringBuilder使用了char[ ]作为实际存储单元,每次在拼加时只需要修改char[ ]数组即可,只是在toString()时创建了1个字符串;而String一旦创建之后就不能被修改,因此在每次拼加时,都需要重新创建新的字符串,所以StringBuilder.append()的性能就会比字符串的+=性能高很多。 - 2、善用intern方法
善用String.intern() 方法可以有效的节约内存并提升字符串的运行效率。
intern是个高效的本地方法。当调用intern方法时,如果字符串常量池中已经包含此字符串,则直接返回此字符串的引用,如果不包含此字符串,先将字符串添加到常
量池中,再返回此对象的引用。
什么情况下适合使用intern方法?比如要存地址信息,预估需要32G的内存:
考虑到其中有很多用户在地址信息上是有重合的,比如:国家、省份、城市等,这时就可以将这部分信息单独列出个类,以减少重复。示例:
通过优化,数据存储大小减到了20G左右。此时就可以用intern来优化了。示例:
- 3、慎重使用Split方法
Split方法在多数情况下使用的是正则表达式,这种分割方式本身没有什么问题,但是由于正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致CPU居高不下。
Java正则表达式使用的引擎实现是NFA(不确定型有穷自动机),这种正则表达式引擎在进行字符匹配时会发生回溯,一旦发生回溯,那其消耗的时间就会变得很长,有可能是几分钟,也有可能是几个小时,时间长短取决于回溯的次数和复杂度。
看个例子:
- 首先,读取正则表达式第一个匹配符a和字符串第一个字符a比较,匹配上了,于是读取正则表达式第二个字符;
- 读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符b比较,匹配上了。但因为 b{1,3}表示1-3个b字符串,以及NFA自动机的贪婪特性(也就是说要尽可能多地匹配),所以此时并不会再去读取下个正则表达式的匹配符,而是依旧使用b{1,3}和字符串的第三个字符b比较,发现还是匹配上了,于是继续使用b{1,3}和字符串的第四个字符c比较,发现不匹配了,此时就会发生回溯;
- 发生回溯后,我们已经读取的字符串第四个字符c将被吐出去,指针回到第三个字符串的位置,之后程序读取正则表达式的下一个操作符c ,然后再读取当前指针的下一个字符c进行对比,发现匹配上了,于是读取下一个操作符,然后发现已经结束了。
3.4.17 String类能被继承吗
- String类不可变性的好处
1、因为String类的不可变性,才能使得JVM可以实现字符串常量池;字符串常量池可以在程序运行时节约很多内存空间,因为不同的字符串变量指向相同的字面量时,都是指向字符串常量池中的同一个对象。这样一方面能够节约内存,另一方面也提升了性能。
2、因为String类的不可变性,从而保证了字符串对象在多线程环境下是线程安全的。
3.5 可变字符串
3.5.1 StringBuffer
StringBuffer是可变字符序列,它是一个类似于String的字符串缓冲区,可以装很多字符串,并且能够对其中的字符串进行各种操作。
StringBuffer是线程安全的,因为其对外提供的方法都有synchronized关键字修饰。
StringBuffer常用方法有:构造方法、追加字符串和toString方法。
- 1、构造方法
无参StringBuffer构造方法,默认开辟16个字符的长度的空间。
- 2、追加字符串
- 3、转换为字符串
3.5.2 StringBuilder
- 1、构造方法
- 2、追加字符串
- 3、转换为字符串
3.5.3 String、StringBuffer和StringBuilder的比较*
三者共同之处:都是final类,不允许被继承。
- 三者不同之处
/div>
p>String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。
/p>
div>
/div>
p>
String的每次+操作 : 隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法拼接+后面的字符。
StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,AbstractStringBuilder的底层是一个可变数组:
/p>
div>
/div>
p>由于StringBuffer和StringBuilder的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。 另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
/p>
h5>3.5.4 StringBuilder在日志框架中的使用
/h5>
p>在后端应用开发时,常用用日志框架占位符的形式来打印信息,示例:
/p>
div>
/div>
p>。
/p>
h5>3.5.5 字符串拼接原理
/h5>
p>:常量,不可变,不适合用来字符串拼接,每次都是新创建的对象,消耗较大;
:适合用来作字符串拼接,;
:JDK1.5引入,适合用来作字符串拼接,与StringBuffer区别是的。
/p>
p>String拼接时,中间会产生StringBuilder对象(JDK1.5之前产生StringBuffer)。具体的原理是:
/p>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
两个String(str1、str2)拼接时,首先会调用String.valueOf(obj)方法,这个obj为str1,然后产生StringBuilder, 调用的StringBuilder( )构造方法, 把StringBuilder初始化,长度为str1.length()+16,并且调用append(str1)。接下来调用StringBuilder.append(str2), 把第二个字符串拼接进去, 然后调用StringBuilder.toString()返回结果。
/blockquote>
h5>3.5.6 为什么StringBuilder.append()⽅法⽐"+="的性能⾼
/h5>
p>StringBuilder⽗类AbstractStringBuilder的实现源码:
/p>
div>
/div>
p>StringBuilder 使⽤了⽗类提供的 char[] 作为⾃⼰值的实际存储单元,每次在拼加时会修改char[] 数组。
StringBuilder toString() 源码:
/p>
div>
/div>
p>可以看出:StringBuilder 使⽤了 char[] 作为实际存储单元,每次在拼加时只需要修改char[] 数组即可,只是在 toString() 时创建了⼀个字符串;⽽ String ⼀旦创建之后就不能被修改,因此在每次拼加时,都需要重新创建新的字符串,所以 StringBuilder.append() 的性能就会⽐字符串的 += 性能⾼很多。
/p>
/h3>
p>方法是语句的集合,它们在一起执行一个功能;方法是解决一类问题的步骤的有序组合;方法包含于类或对象中。
方法的命名一般用驼峰命名法,即第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写。
方法的优点:
/p>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
- 使程序变得更简短而清晰。
- 有利于程序维护。
- 可以提高程序开发的效率。
- 提高了代码的重用性。
/blockquote>
/h4>
p>一般情况下,定义一个方法包含以下语法:
/p>
div>
/div>
p>方法包含一个方法头和一个方法体。下面是一个方法的所有部分:
/p>
ul>
修饰符,这是可选的,告诉编译器如何调用该方法。定义了该方法的访问类型。
方法可能会返回值。方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果(前提是该方法可能产生结果)。
是方法的实际名称。方法名和参数表共同构成方法签名。
参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。
方法体包含具体的语句,定义该方法的功能。
/ul>
/h4>
p>变量的范围是程序中该变量可以被引用的部分。
/p>
p>。局部变量的作用范围从声明开始,直到包含它的块结束。局部变量必须声明才可以使用。
/p>
p>方法的参数范围涵盖整个方法。参数实际上是一个局部变量。
/p>
p>最常见的局部变量是:for循环的初始化部分声明的变量,其作用范围在整个循环。但循环体内声明的变量其适用范围是从它声明到循环体结束。它包含如下所示的变量声明:
/p>
/p>
p>
/p>
p>你可以在一个方法里,不同的非嵌套块中多次声明一个具有相同的名称局部变量,但你不能在嵌套块内两次声明局部变量。
/p>
/h3>
/h4>
p>Math类包含用于执行基本数学运算的方法。最常用的方法是数值计算类方法。
/p>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
/h4>
p>Arrays是一个封装好一些对数组操作的类,其中的public方法都是静态的。Arrays的静态方法,有针对不同类型(int、long、short、char、byte、float、double、Object)的重载方法,接下来以int类型为例。Arrays里的方法可以分为以下几大类:
/p>
ul>
在数组中搜索某个值,返回该值在数组中的下标。。
/ul>
div>
/div>
ul>
给数组中元素赋予默认值,实现是遍历赋值。
/ul>
div>
/div>
ul>
计算数组中元素的哈希值。
/ul>
div>
/div>
ul>
比较两个数组是否相等。
/ul>
div>
/div>
ul>
该类方法的作用是对数组中的元素进行排序,。
/ul>
div>
/div>
ul>
该类方法的作用是拷贝数组中的元素,可以指定起始位置,未指定初始位置的话,默认从0开始。方法实现代码:
/ul>
div>
/div>
ul>
asList,示例:
/ul>
div>
/div>
/h4>
p>Collections类是针对集合操作的工具类。
常用方法:
/p>
ul>
使用默认排序规则和自定义规则的示例:
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
ul>
/ul>
div>
/div>
/h4>
h5>5.4.1 Object中的重要方法
/h5>
p>Object是是所有类的父类,。
/p>
ul>
/ul>
div>
/div>
p>返回类的名字@实例的哈希码的16进制的字符串(即对象地址),示例:
/p>
div>
/div>
p>。
/p>
ul>
/ul>
div>
/div>
p>equals方法主要是比较两个对象是否相同,Object中的equals方法比较的是对象的地址是否相同。
String类重写了该方法,改成了比较值是否相同。在实际开发中,对于实体类,一般要重写equals(比较对象的内容而不是地址)和hashCode(尽量让不同的对象产生不同的哈希值)方法。
/p>
ul>
/ul>
div>
/div>
p>返回对象的哈希码。示例:
/p>
div>
/div>
p>该方法返回对象的哈希码,是一个整数。这个方法遵守以下三个规则:
/p>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
1、在java程序运行期间,若用于equals方法的信息或者数据没有修改,同一个对象多次调用此方法,返回的哈希码是相同的。
2、如果根据equals方法,两个对象相同,则这两个对象的哈希码一定相同。
3、假如两个对象通过equals方法比较不相同,那么这两个对象调用hashCode也不是要一定不同,相同也是可以的。
/blockquote>
ul>
/ul>
div>
/div>
p>对象中各个属性的复制,即浅拷贝一个对象,但它的可见范围是protected的。所以实体类使用克隆的前提是:
/p>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
- 实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常
CloneNotSupportedException。 - 覆盖clone()方法,可见性提升为public。
/blockquote>
p>示例:
/p>
div>
/div>
ul>
返回当前运行时对象的Class对象,常用于java反射机制。
/ul>
div>
/div>
ul>
在Object中存在三种wait方法:
/ul>
div>
/div>
p>wait()和wait(long timeout, int nanos)都在在内部调用了wait(long timeout)方法。这个方法是线程方面的,在线程相关文章会详细介绍。
/p>
ul>
/ul>
div>
/div>
p>这两个方法是唤醒线程。
/p>
h5>5.4.2 hashCode与equals*
/h5>
p>hashCode()的作用是获取哈希码(散列码);它实际上是返回一个int型整数。这个。hashCode()定义在Object类中,这就意味着Java中的任何类都包含有hashCode()。
/p>
div>
/div>
p>散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码(可以快速找到所需要的对象)。
。
/p>
ul>
hashCode()的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;只有每个对象的hash码尽可能不同才能保证散列的存取性能,事实上Object类提供的默认实现确实保证每个对象的hash码不同(在对象的内存地址基础上经过特定算法返回一个hash码)。
在Java有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的equals方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了。比如集合中现在已经有3000个元素,则第3001个元素加入集合时就要调用3000次equals方法,这显然会大大降低效率。
于是Java采用了哈希表的原理,这样当集合要添加新的元素时,会先调用这个元素的hashCode方法,这样一下子就能定位到它应该放置的物理位置上。如果这个位置上没有元素,则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了,则就调用它的equals方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次,而hashCode的值对于每个对象实例来说是一个固定值。
如果两个对象相等,则hashcode一定也是相同的。两个对象相等,对两个对象分别调用equals方法都返回true。因此,equals方法被覆盖过,则hashCode方法也必须被覆盖。
/ul>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode() ,则该 class的两个对象的hash值无论如何都不会相等(即使这两个对象指向相同的数据)。
/blockquote>
ul>
/ul>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
- 。
- 。
- 。
- 因此,。
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
/blockquote>
ul>
因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的hashCode )。
/ul>
h5>5.4.3 hashCode与equals的区别*
/h5>
p>这两者的区别主要体现在性能和可靠性上。
/p>
ul>
因为重写的equals方法里实现的比较逻辑一般较复杂,这样效率就比较低;而利用hashCode比较,只比较一个哈希值就可以了,效率较高。
因为hashCode并不是完全可靠。有时不同对象生成的哈希值也会一样,所以hashCode大部分时候可靠,并不是绝对可靠。可以得出:
/ul>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
- equals()相等的两个对象,他们的hashCode()肯定相等,也就是用equals对比是绝对可靠的。
- hashCode()相等的两个对象,他们的equals()不一定先等,也就是hashCode()不是绝对可靠的。
/blockquote>
h5>5.4.4 hashCode与equals的相关问题
/h5>
ul>
因为Set中存储的是不重复的对象,需要根据hashCode和equals进行判断,所以要重写这两个方法。
同理,Map中的key也需要重写这两个方法。String重写了这两个方法,所以可以用来用作Map中的key。
一般的地方不需要重写,只有当类需要放在HashTable、HashMap、HashSet等hash结构的集合时,才需要重写。
如果重写了equals,而没有重写hashCode,就可能导致:两个对象“相等”,但hashCode不一样的情况。此时,当利用这对象作为key保存到HashMap、HashTable、HashSet中时,再用相同的key去查找时,找不到。
有可能。在产生hash冲突时,两个不相等的对象就会有相同的hashcode值。当hash冲突产生时,一般有以下几种方式来处理:
1)拉链法。每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储。
2)开放定址法。一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
3)再哈希。又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个、第三个….等哈希函数计算地址,直到无冲突。
equals与hashcode的关系:
/ul>
blockquote style="margin-top: 5px; margin-bottom: 5px; padding-left: 1em; margin-left: 0px; border-left: 3px solid rgb(238, 238, 238); opacity: 0.6;">
- 如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
/blockquote>
p>hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。
/p>
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/20543.html