2021年09月10日 java1
考点1:HashMap源码
HashMap的数据结构是怎样的?
- A 数组
- B 链表
- C 数组+链表
- D 二叉树
显示答案/隐藏答案
正确答案: CHashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
JDK8及其以后版本,HashMap的数据结构是数组+链表+红黑树
HashMap内部包含了一个默认大小为 16 Entry 类型的数组 table,其中每个Entry 是一个链表,当链表长度大于等于 8 时会将链表转换为红黑树。
JDK1.7中HashMap存储结构
jdk1.7 中使用个 Entry 数组来存储数据,用key的 hashcode 取模来决定key会被放到数组里的位置,如果 hashcode 相同,或者 hashcode 取模后的结果相同( hash collision ),那么这些 key 会被定位到 Entry 数组的同一个格子里,这些 key 会形成一个链表。在 hashcode 特别差的情况下,比方说所有key的 hashcode 都相同,这个链表可能会很长,那么 put/get 操作都可能需要遍历这个链表,也就是说时间复杂度在最差情况下会退化到 O(n)
JDK1.8中HashMap存储结构
jdk1.8 中使用一个 Node 数组来存储数据,但这个 Node 可能是链表结构,也可能是红黑树结构,如果插入的 key 的 hashcode 相同,那么这些key也会被定位到 Node 数组的同个格子里。如果同一个格子里的key不超过8个,使用链表结构存储。如果超过了8个,那么会调用 treeifyBin 函数,将链表转换为红黑树。那么即使 hashcode 完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销也就是说put/get的操作的时间复杂度最差只有 O(log n),但是真正想要利用 JDK1.8 的好处,有一个限制:key的对象,必须正确的实现了 Compare 接口
HashMap在Node数组中进行哈希查找,
使用链接法处理冲突,
冲突较少时使用链表,
目前版本当冲突的键达到8时,
会把链表转换为红黑树.
1、HashMap 底层的数据是数组被成为哈希桶(默认的初始值为 16 ),每个桶存放的是链表,链表中的每个节点,就是 HashMap 中的每个元素。
2、在 JDK 8 当链表长度大于等于 8 时,就会转成红黑树的数据结构,以提升查询和插入的效率。
考点2:不用编码的java文件编译命令 javac -encoding
语句:char foo='中'
,是否正确?(假设源文件以GB2312编码存储,并且以javac -encoding GB2312
命令编译)
- A 正确
- B 错误
显示答案/隐藏答案
正确答案: A测试类:
1 | package base.ecoding; |
这个应该和系统的编码有关,保存为GB2312时,直接使用javac -d . Test.java
可以正常编译,因为我的Windows简体中文系统使用的是GBK编码。
所以可以正常编译,但是如果把上面的文件保存为UTF-8编码,再次使用javac -d . Test.java
就无法编译,报错如下:
1 | PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding> javac -d . .\Test.java |
正确的编译命令为:javac -d . -encoding UTF-8 .\Test.java
,运行也是正常的。
1 | PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding> javac -d . -encoding UTF-8 .\Test.java |
总结
编译java文件时,如果不指定编码时,javac会使用默认的编码,也就是操作系统的编码,如果操作系统的编码和java文件的编码不一致时,就会编译错误。
所以使用javac编译时,都指定文件的编码,这样就不会失败。
1 | javac -encoding 文件编码 xxx.java |
即使java文件的编码和操作系统的相同,也指定编码。因为这样必然会成功。
考点3:异常处理 运行时异常非运行时异常
下列关于异常处理的描述中,错误的是()。
- A 程序运行时异常由
Java
虚拟机自动进行处理 - B 使用
try-catch-finally
语句捕获异常 - C 可使用
throw
语句抛出异常 - D 捕获到的异常只能在当前方法中处理,不能在其他方法中处理
显示答案/隐藏答案
正确答案: D运行时异常不需要显示处理
运行时异常可以不处理。当出现这样的异常时,总是由虚拟机接管。
比如我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。
抛出之后,如果是线程,这个线程也就退出了。
如果是主程序抛出的异常,整个程序也就退出了。
运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往不对它处理罢了。
也就是说,如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
throw和throws的区别
C:throw关键字:语句抛出异常 throws关键字:声明异常(方法抛出一个异常)
1.编译时异常必须显示处理,运行时异常交给虚拟机自行处理。
考点4:HashMap源码
下面有关java hashmap的说法错误的是?
- A HashMap的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。
- B HashMap的实现不是同步的,意味着它不是线程安全的
- C HashMap通过开放地址法解决哈希冲突
- D HashMap中的key-value都是存储在Entry数组中的
显示答案/隐藏答案
正确答案: CJava8之前HashMap使用链地址法
1.在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。在最坏的情况下,这种方式会将HashMap的get方法的性能从O(1)降低到O(n)。
Java8 HashMap
为了解决在频繁冲突时hashmap性能降低的问题,Java 8中使用平衡树来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从O(n)提高到O(logn)。
在Java 8中使用常量TREEIFY_THRESHOLD来控制是否切换到平衡树来存储。HashMap一开始使用链表,并在冲突的元素数量超过TREEIFY_THRESHOLD时用平衡二叉树替换链表。目前,这个常量值是8。
不过这一特性在所有基于hash table的类中并没有,例如Hashtable和WeakHashMap。
1. 关于HashMap的一些说法:
a) HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。
b) HashMap的实例有俩个参数影响其性能: “初始容量” 和 装填因子。
c) HashMap实现不同步,线程不安全。 HashTable线程安全
d) HashMap中的key-value都是存储在Entry中的。
e) HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals()方法保证键的唯一性
解决冲突的三种方法
f) 解决冲突主要有三种方法:定址法,拉链法,再散列法。
HashMap是采用拉链法解决哈希冲突的。 注: 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位;
开放定址法
用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
拉链法
拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小。
2. Hashtable和HashMap的区别:
a) 继承不同
public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
b) 线程安全不同
Hashtable中的方法是同步的,
而HashMap中的方法在缺省情况下是非同步的。
在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
c) key和value能有null不同
Hashtable中,key和value都不允许出现null值。
在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。
因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
d) 遍历方式不同
两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了Iterator。
而由于历史原因,Hashtable还使用了Enumeration的方式 。
e) 哈希值的使用不同
哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
f) 内部实现方式的数组的初始大小和扩容的方式。
Hashtable中hash数组默认大小是11,
增加的方式是old*2+1。
HashMap中hash数组的默认大小是16,而且一定是2的指数。
HashMap有子类HashSet
注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。
HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。
考点5:HashMap源码
在Java中,关于HashMap类的描述,以下正确的是 ()
- A HashMap使用键/值得形式保存数据
- B HashMap能够保证其中元素的顺序
- C HashMap允许将null用作键
- D HashMap允许将null用作值
显示答案/隐藏答案
正确答案: ACDMap集合类 | key | value |
---|---|---|
HashMap | 允许为null | 允许为null |
TreeMap | 不允许为null | 允许为null |
ConcurrentMap | 不允许为null | 不允许为null |
Hashtable | 不允许为null | 不允许为null |
HashMap 不按插入顺序排序,按照哈希值排序。所以无序。
但是不增删改键的情况下,输出是按照一定顺序不变的
HashMap允许一个key为null,多个value为null,而Hashtable不允许有null值
我们常说的Map是无序的,其实这里的描述是不清楚的,我们所说的无序通常是指HashMap无序,因为TreeMap按自然顺序排序,LinkedHashMap按添加元素顺序排序
考点6:常见final类
下面哪些类可以被继承? Java.lang.Thread、java.lang.Number、java.lang.Double、java.lang.Math、 java.lang.ClassLoader
- A Thread
- B Number
- C Double
- D Math
- E ClassLoader
显示答案/隐藏答案
正确答案: ABEThread
Thread可以被继承,用于创建新的线程
1 | public class Thread extends Object implements Runnable |
Number是抽象类,可以被继承
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Number.html
public abstract class Numberextends Object implements Serializable
Direct Known Subclasses:
AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double, DoubleAccumulator, DoubleAdder, Float, Integer, Long, LongAccumulator, LongAdder, Short
Number类可以被继承,Integer,Float,Double等都继承自Number类
包装类是final修饰的,无法继承
public final class Double extends Number implements Comparable<Double>
Math类是final类
Math类的声明为
public final class Mathextends Object
ClassLoader是抽象类
ClassLoader可以被继承,用户可以自定义类加载器
public abstract class ClassLoader extends Object