在学习《 Java
编程的逻辑》一书时记录的一些笔记,扫清了一些 Java
基础的知识盲区,感谢作者马俊昌老师。
序号 | 数据类型 | 大小(位) | 包装类 | 默认值 | 数据范围 |
---|---|---|---|---|---|
1 | byte | 8 | Byte | 0 | -128 ~ 127 |
2 | short | 16 | Short | 0 | -32768 ~ 32767 |
3 | int | 32 | Integer | 0 | -2147483648 ~ 2147483647 |
4 | long | 64 | Long | 0L | -9223372036854775808 ~ 9223372036854775807 |
5 | boolean | 8 | Boolean | false | true/false |
6 | char | 16 | Character | 空 | 0 ~ 65535 |
7 | float | 32 | Float | 0.0F | 1.4E-45 ~ 3.4028235E38 |
8 | double | 64 | Double | 0.0D | 4.9E-324 ~ 1.7976931348623157E308 |
装箱:将基本类型转换为包装类的过程。
拆箱:将包装类型转换为基本类型的过程。
Java 5
以后引入了自动装箱和拆箱技术:
Integer a = 100; int b = a;
自动装箱/拆箱是 Java
编译器提供的能力,背后,它会替换为调用对应的 valueOf/xxx-Value
方法,比如,上面的代码会被 Java
编译器替换为:
Integer a = Integer.valueOf(100); int b = a.intValue();
一般建议使用 valueOf
方法。
new
每次都会创建一个新对象,而除了 Float
和 Double
外的其他包装类,都会缓存包装类对象,减少需要创建对象的次数,节省空间,提升性能。
IntegerCache
表示 Integer
缓存,其中的 cache
变量是一个静态 Integer
数组,在静态初始化代码块中被初始化。
默认情况下,保存了 -128~127
共 256
个整数对应的 Integer
对象。
在 valueOf
代码中,如果数值位于被缓存的范围,即默认 -128~127
,则直接从 Integer-Cache
中获取已预先创建的 Integer
对象,只有不在缓存范围时,才通过 new
创建对象。
序号 | 数据类型 | 数据缓存 |
---|---|---|
1 | Boolean | true, false |
2 | Byte | -128 ~ 127 |
3 | Short | -128 ~ 127 |
4 | Integer | -128 ~ 127 |
5 | Long | -128 ~ 127 |
6 | Character | 0 ~ 127 |
7 | Float | 无缓存 |
8 | Double | 无缓存 |
Java 5
之前,只支持 byte
、 short
、 int
、 char
四种。 Java 5
中,引入了枚举类型。因为枚举类的 ordinal
返回一个 int
值。 Java 7
中,支持 String
类型,使用 String
的 hashCode
方法。
switch
的效率相对较高,编译后使用跳转表实现。
跳转表有序,可以二分查找,所以支持的数据类型都是整数,枚举和 String
也是转换为 int
值。
跳转表的存储空间是 32
位,容纳不下 long
,所以 switch
不支持 long
类型。
接口中变量修饰符是 public static final
。
接口中方法修饰符是 public abstract
。
java 8
允许在接口里定义默认方法 default
和静态方法 static
。
java 9
允许默认方法和静态方法可以是 private
。
一个类的对象要想序列化成功,必须满足两个条件:
java.io.Serializable transient
否则会抛出 NotSerializableException
异常。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID
是否一致。
序列化并不保存静态变量。
父类如果没有实现 Serializable
接口时,反序列化时,会调用父类无参的构造函数。
Externalizable
接口继承自 java.io.Serializable
。
public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
实现 Externalizable
接口后,序列化的细节由开发人员自己实现。
并且 Externalizable
的优先级比 Serializable
的优先级高。
try/catch/finally
语法中, catch
不是必需的,也就是可以只有 try
和 finally
,表示不捕获异常,异常自动向上传递,但 finally
中的代码在异常发生后也执行。
java 7
开始支持:多个异常之间可以用 |
操作符:
try { // 可能抛出ExceptionA和ExceptionB } catch (ExceptionA | ExceptionB e) { // 异常处理逻辑 }
java 7
提供了一种新的语法,称之为 try-with-resources
。
这种语法针对实现了 java.lang.AutoCloseable
接口的对象,接口定义:
public interface AutoCloseable { void close() throws Exception; }
语法形式如下:
try (AutoCloseable resource = new FileInputStream("yunzhi.txt")) { // 使用资源 }
资源 resource
的声明和初始化放在 try
语句内,不用再调用 finally
,在执行完 try
语句后,会自动调用资源的 close
方法。
String
类内部用一个字符数组表示字符串,实例变量定义为:
private final char value[];
System.out.println("yunzhi.club".length()); System.out.println("yunzhi.club".contains("yunzhi")); System.out.println("yunzhi.club".indexOf("yunzhi"));
实际上,这些常量就是 String
类型的对象,在内存中,它们被放在一个共享的地方,这个地方称为字符串常量池,它保存所有的常量字符串,每个常量只会保存一份,被所有使用者共享。
当通过常量的形式使用一个字符串的时候,使用的就是常量池中的那个对应的 String
类型的对象。
Java
中, String
可以直接使用 +
和 +=
运算符,这是 Java
编译器提供的支持,背后, Java
编译器一般会生成 StringBuilder
, +
和 +=
操作会转换为 append
。
String hello = "hello"; hello += ", world"; System.out.println(hello);
背后, Java
编译器一般会转换为:
StringBuilder hello = new StringBuilder("hello"); hello.append(", world"); System.out.println(hello.toString());
对于简单的情况,可以直接使用 String
的 +
和 +=
,对于复杂的情况,尤其是有循环的时候,应该直接使用 StringBuilder
。
不可变使得程序更为简单安全,因为不用操心数据被意外改写的可能,可以安全地共享数据,尤其是在多线程的环境下。
hash
默认采用单链表解决冲突,如果链表长度超过 8
,将单链表转换为红黑树。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
tab[i = (n - 1) & hash]
,使用 n-1
的 &
运算,提高效率。
扩容的条件:实际节点数大于等于容量的 0.75
,就是负荷系数。
因为不同版本的 JDK
计算的 hash
值可能是不同的。只存储了数组的容量、实际节点数量和各个节点的 key value
值。
O(1)
HashSet
内部是调用 HashMap
来实现的,核心就是利用 HashMap
中的 key
不能相同进行去重, key
就是 Set
中的值, value
存了一个空对象。
TreeMap
按键有序,为了实现有序,要求要么键实现 Comparable
接口,要么创建 TreeMap
时传递一个 Comparator
对象。
内部使用红黑树实现,存储映射。
红黑树是一种平衡二叉树,但它不是高度平衡的,而是大致平衡的。确保任意一条从根到叶子节点的路径,没有任何一条的路径的长度会比其他路径长过两倍。
红黑树减弱了对平衡的要求,降低了保持树平衡需要的开销,在实际应用中,统计性能超过平衡二叉树。
TreeSet
内部是基于 TreeMap
的,实现了有序。
HashMap
的子类,内部使用双向链表维护键值对的顺序,每个键值对既位于哈希表中,也位于这个双向链表中。
支持插入顺序维护或访问顺序维护。
插入顺序:先添加的在前面,后添加的在后面,修改操作不影响顺序。
访问顺序:对一个键值对进行 get/put
操作,该键值对会移动到链表末尾,最末尾的是最近访问的,最开始的是最久没有访问的。
HashSet
的子类,内部采用 LinkedHashMap
实现。
一个 key
是枚举类型的 Map
。
内部使用了两个长度相同的数组,一个存键,一个存储对应的值,值为 null
表示没有该键值对。键都有一个对应的索引,根据索引可直接访问和操作其键的值,效率高。
EnumSet
的实现和 EnumMap
没有任何关系!
采用位向量实现。 0
代表没有存这个枚举值, 1
代表存了这个枚举值,效率很高。
优先级队列,内部采用堆实现。和其他需要比较的操作一样,要么实现 Comparable
接口,要么创建时传递一个 Comparator
对象。
维护一个最小堆,堆的根节点是最小的,即第 K
大元素。
每次有新数据来,和根节点比,如果小,不操作,如果大,替换根节点,调整堆。调整的复杂度为 O(logK)
。
维护一个最大堆和最小堆,假设当前中位数为 M
,最大堆维护 <=M
的元素,最小堆维护 >=M
的元素,但两堆中都不包含 M
。
新数据来时,如果 <=M
,放到最大堆中,如果 >=M
,放到最小堆中。
数据加入之后,如果两堆元素相差 >=2
,将 M
加入到元素少的堆中,元素多的堆的根节点移除作为新的中值元素。
之前一直很怕这张图,但是随着积累,图中的类我们也逐渐地学会并掌握了。
Collections
常用方法。
方法名 | 方法描述 |
---|---|
binarySearch
|
二分查找 |
max
|
最大值 |
min
|
最小值 |
frequency
|
集合中元素出现次数 |
indexOfSubList
lastIndexOfSubList
|
在原 List
中查找目标 List
的位置 |
disjoint
|
集合是否有交集 |
replaceAll
|
List
中元素替换 |
方法名 | 方法描述 |
---|---|
sort
|
排序 |
swap
|
元素交换 |
reverse
|
列表反转 |
shuffle
|
洗牌,随机打乱顺序 |
rotate
|
循环移位 |
方法名 | 方法描述 |
---|---|
emptyList
emptySet
emptyMap
emptyIterator
|
返回空的集合,使用时比返回 null
更合理,更安全 |
singleton
singletonList
singletonMap
|
将单一对象转化为标准容器接口对象 |
方法名 | 方法描述 |
---|---|
unmodifiableCollection
unmodifiableList
unmodifiableMap
unmodifiableSet
|
让容器只读 |
synchronizedCollection
synchronizedList
synchronizedMap
synchronizedSet
|
让容器线程安全,不是最优实现。 |