2021年09月10日 java1

考点1:HashMap源码

HashMap的数据结构是怎样的?

  • A 数组
  • B 链表
  • C 数组+链表
  • D 二叉树
显示答案/隐藏答案正确答案: C

HashMap 由数组+链表组成的,数组是 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
2
3
4
5
6
7
8
package base.ecoding;

public class Test {
public static void main(String[] args) {
char foo='中';
System.out.println(foo);
}
}

这个应该和系统的编码有关,保存为GB2312时,直接使用javac -d . Test.java可以正常编译,因为我的Windows简体中文系统使用的是GBK编码。
所以可以正常编译,但是如果把上面的文件保存为UTF-8编码,再次使用javac -d . Test.java就无法编译,报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding> javac -d . .\Test.java
.\Test.java:5: 错误: 编码 GBK 的不可映射字符 (0xAD)
char foo='涓?';
^
.\Test.java:5: 错误: 未结束的字符文字
char foo='涓?';
^
.\Test.java:5: 错误: 未结束的字符文字
char foo='涓?';
^
3 个错误
PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding>

正确的编译命令为:javac -d . -encoding UTF-8 .\Test.java,运行也是正常的。

1
2
3
4
PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding> javac -d . -encoding UTF-8 .\Test.java
PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding> java base.ecoding.Test

PS G:\dev2\idea_workspace\MyJavaTools\RunableTools\src\base\ecoding>

总结

编译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数组中的
显示答案/隐藏答案正确答案: C

Java8之前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用作值
显示答案/隐藏答案正确答案: ACD
Map集合类 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
显示答案/隐藏答案正确答案: ABE

Thread

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