参考博文:
StringBuilder 以及 StringBuffer默认大小与扩容
String, StringBuffer and StringBuilder 区别
如有侵权即删。
一、数据类型
1、基本类型
- 类型/字节
- byte/8
- char/16
- short/16
- int/32
- float/32
- long/64
- double/64
- boolean/~
boolean 只有两个值:true、false,可以使用 1 bit 来存储,但是具体大小没有明确规定。JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。JVM 支持 boolean 数组,但是是通过读写 byte 数组来实现的。
byte | byte数据类型是8位带符号的二进制补码整数。最小值为-128,最大值为127(含)。 byte数据类型保存大型存储器有用数组,节省存储空间 |
short | short数据类型是16位带符号的二进制补码整数。最小值为-32,768,最大值为32,767(含)。与一样byte,也适用相同的准则 |
int | 默认情况下,int数据类型是32位带符号的二进制补码整数,最小值-231,最大值为231-1.。在java8以上,可以使用int数据类型表示无符号的32位整数,其最小值为0,最大值为2 32 -1。使用Integer类可将int数据类型用作无符号整数。 |
long | long数据类型是64位二进制补码整数。带符号的long的最小值为-263,最大值为263 -1。在Java SE 8和更高版本中,您可以使用long数据类型表示无符号的64位长,其最小值为0,最大值为264-1。当您需要的值范围比所提供的宽时,请使用此数据类型int。该 Long班还包含方法,如compareUnsigned,divideUnsigned等长,以支持算术运算的无符号。 |
float | float数据类型是单精度32位IEEE 754浮点。如果您需要将内存保存在大的浮点数数组中short,请使用float(而不是double)。永远不要将这种数据类型用于精确值,例如货币。为此,您将需要使用 java.math.BigDecimal类。 数字和字符串覆盖BigDecimal以及Java平台提供的其他有用的类。 |
double | double数据类型是双精度64位IEEE 754浮点。对于十进制值,此数据类型通常是默认选择。如上所述,永远不要将这种数据类型用于精确值,例如货币。 |
boolean | boolean数据类型只有两个可能的值:true和false。将此数据类型用于跟踪真/假条件的简单标志。此数据类型表示一小部分信息,但其“大小”不是精确定义的。 |
char | char数据类型是单个16位Unicode字符。它的最小值为'\u0000'(或0),最大值为'\uffff'(或65,535,包括端值)。 |
浮点运算
- 比较运算符,其结果为type boolean:
- 数值比较运算<,<=, >,和>=
- 数值相等运算符==和!=
- 数值运算符,其结果为类型float或 double:
- 一元加号和减号运算符+和-
- 乘法运算符*,/和%
- 加法运算符+和-
- 增量运算符++,递减运算符--
- 条件运算符? :
- 强制转换运算符
- 字符串串联运算符+,当给定一个String操作数和一个浮点操作数时,它将把浮点操作数转换为一个String十进制形式的值(没有信息丢失),然后产生一个新的String通过串联两个字符串 创建
2、包装类型
- 原始类型/包装类型
- int/Integer
- double/Double
- boolean/Boolean
- char/Character
- float/Float
- long/Long
- short/Short
自动装箱是Java编译器在原始类型及其对应的对象包装器类之间进行的自动转换。例如,将int转换为Integer,将double转换为Double,依此类推。如果转换采用其他方式,则称为拆箱。
考虑以下代码:
List <Integer> li = new ArrayList <>();
for(int i = 1; i <50; i + = 2)
li.add(i);
将int值作为基本类型而不是Integer对象添加到li,但代码仍会编译。因为li是Integer对象的列表,而不是int值的列表,编译器不会产生错误,因为它会从i创建一个Integer对象并将该对象添加到li。因此,编译器在运行时将先前的代码转换为以下代码:
List <Integer> li =new ArrayList <>();
for(int i = 1; i <50; i + = 2)
li.add(Integer.valueOf(i));
将原始值(例如int)转换为相应包装器类(Integer)的对象称为自动装箱。
当原始值是:
- 作为参数传递给需要相应包装类对象的方法。
- 分配给相应包装器类的变量。
Java编译器将应用自动装箱
将包装器类型(Integer)的对象转换为其对应的原始(int)值称为拆箱。
当包装器类的对象是:
- 作为参数传递给需要相应原始类型值的方法。
- 分配给相应原始类型的变量。
Java编译器将应用自动拆箱
3、缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
new Integer(123) 每次都会新建一个对象;
Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=<size> 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。
二、String
1、概览
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 内部使用 char 数组存储数据。
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
2、不可变的好处
- 可以缓存 hash 值
- 因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
- String Pool 的需要
- 如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
- 安全性
- String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
- 线程安全
- String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
3、String, StringBuffer and StringBuilder
- 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
- 线程安全
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
- 应用场景
- 在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)
- 在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。
- ]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。
<1> StringBuffer/StringBuilder
StringBuffer和StringBuilder都实现了AbstractStringBuilder抽象类,拥有几乎一致对外提供的调用接口;其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuffer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变。
两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。
StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。
默认大小:16字符
有参构造方法:str+16,初始化长度=默认值+参数长度
每一次扩容,原大小*2+2,容量还不够就扩充到需要的大小
集合中也有类似情况,ArrayList 和LinkedList也有默认值10 ,也有扩容算法采用的是右偏移1 + 原有长度 ==也就是变为1.5倍大小 ,如果不够用的话,就直接扩充到需要的大小;
<2> String特性
- 不可变。是指String对象一旦生成,则不能再对它进行改变。不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
- 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
4、String Pool
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
When the intern method is invoked, if the pool already contains a
string equal to this {@code String} object as determined by
the {@link #equals(Object)} method, then the string from the pool is
returned. Otherwise, this {@code String} object is added to the
pool and a reference to this {@code String} object is returned.
<p>
It follows that for any two strings {@code s} and {@code t},
{@code s.intern() == t.intern()} is {@code true}
if and only if {@code s.equals(t)} is {@code true}.
如果是采用 String s5 = "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。
String s = new String("abc")这个语句创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。
我们平时使用时,内存空间肯定不是无限大的,不使用 intern 占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。如果大量的数据使用intern,很快会把常量池填满导致效率下降。
三、运算
1、参数传递
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。
在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象。
但是在方法中将指针引用了其它对象,那么此时方法里和方法外的两个指针指向了不同的对象,在一个指针改变其所指向对象的内容对另一个指针所指向的对象没有影响。
2、float和double
Java 不能隐式执行向下转型,因为这会使得精度降低。
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
float f = 1.1;
1.1f 字面量才是 float 类型。
float f = 1.1f;
3、隐式类型转换
因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
short s1 = 1;// s1 = s1 + 1;
但是使用 += 或者 ++ 运算符会执行隐式类型转换。
s1 += 1;
s1++;
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
4、switch
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
switch 语句中的变量类型可以是: byte、short、int 或者 char。从 Java SE 7 开始,switch 支持字符串 String 类型了,同时 case 标签必须为字符串常量或字面量。
四、关键字
- final
- 数据
-
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
-
对于基本类型,final 使数值不变;
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
-
final int x = 1;// x = 2; // cannot assign value to final variable 'x' final A y = new A(); y.a = 1;
-
-
- 方法
- 声明方法不能被子类重写。
- private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
- 类
-
声明类不允许被继承。
-
- 数据
-
static
- 静态变量
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
- 静态方法
- 只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因此这两个关键字与具体对象关联。
- 静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
- 静态语句块
- 静态语句块在类初始化时运行一次。
- 静态内部类
- 静态内部类不能访问外部类的非静态的变量和方法。
- 非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
- 静态导包
- import static com.xxx.ClassName.*
- 在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
- 初始化顺序
-
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
-
public static String staticField = "静态变量"; static { System.out.println("静态语句块"); } public String field = "实例变量"; { System.out.println("普通语句块"); }
-
最后才是构造函数的初始化。
-
public InitialOrderTest() { System.out.println("构造函数"); }
-
- 存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
- 静态变量
本博客主要是用于自己复习归档知识点,核心是按照CyC-note的知识点顺序,然后集合了其他人的博客。