61.下面代码的输出是什么?
public class Base
{
private String baseName = "base";
public Base()
{
callName();
}
public void callName()
{
System. out. println(baseName);
}
static class Sub extends Base
{
private String baseName = "sub";
public void callName()
{
System. out. println (baseName) ;
}
}
public static void main(String[] args)
{
Base b = new Sub();
}
}
A.null
B.sub
C.base
答案: A
解析:
这道题也是考察类加载顺序的题目.
首先,需要明白类的加载顺序:
- (1) 父类静态代码块(包括静态初始化块,静态属性,但不包括静态方法)
- (2) 子类静态代码块(包括静态初始化块,静态属性,但不包括静态方法 )
- (3) 父类非静态代码块( 包括非静态初始化块,非静态属性 )
- (4) 父类构造函数
- (5) 子类非静态代码块 ( 包括非静态初始化块,非静态属性 )
- (6) 子类构造函数
其中:类中静态块按照声明顺序执行,并且(1)和(2)不需要调用new类实例的时候就执行了(意思就是在类加载到方法区的时候执行的)
其次,需要理解子类覆盖父类方法的问题,也就是方法重写实现多态问题。
Base b = new Sub();它为多态的一种表现形式,声明是Base,实现是Sub类,我们可以简单理解为 b 编译时表现为Base类特性,运行时表现为Sub类特性
当子类覆盖了父类的方法后,意思是父类的方法已经被重写,题中 父类初始化调用的方法为子类实现的方法,子类实现的方法中调用的baseName为子类中的私有属性。
62.关于volatile关键字,下列描述不正确的是?
A.用volatile修饰的变量,每次更新对其他线程都是立即可见的。
B.对volatile变量的操作是原子性的。
C.对volatile变量的操作不会造成阻塞。
D.不依赖其他锁机制,多线程环境下的计数器可用volatile实现。
答案: B D
解析:
所谓 volatile的措施,就是
- 每次从内存中取值,不从缓存中什么的拿值。这就保证了用 volatile修饰的共享变量,每次的更新对于其他线程都是可见的。
- volatile保证了其他线程的立即可见性,就没有保证原子性
- 由于有些时候对 volatile的操作,不会被保存,说明不会造成阻塞。不可用与多线程环境下的计数器。
63.方法通常存储在进程中的哪一区()
A.堆区
B.栈区
C.全局区
D.方法区
答案:D
解析:
一条进程的栈区、堆区、数据区和代码区在内存中的映射:
- 栈区:主要用来存放局部变量, 传递参数, 存放函数的返回地址。.esp 始终指向栈顶, 栈中的数据越多, esp的值越小。
- 堆区:用于存放动态分配的对象, 当你使用 malloc和new 等进行分配时,所得到的空间就在堆中。动态分配得到的内存区域附带有分配信息, 所以能够 free和delete它们。
- 数据区:全局,静态和常量是分配在数据区中的,数据区包括bss(未初始化数据区)和初始化数据区。
注意:- 1)堆向高内存地址生长;
- 2)栈向低内存地址生长;
- 3)堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk。
64.下列关于Java并发的说法中正确的是()
A.CopyOnWriteArrayList适用于写多读少的并发场景
B.ReadWriteLock适用于读多写少的并发场景
C.ConcurrentHashMap的写操作不需要加锁,读操作需要加锁
D.只要在定义int类型的成员变量i的时候加上volatile关键字,那么多线程并发执行i++这样的操作的时候就是线程安全的了
答案:B
解析:
- 1.CopyOnWriteArrayList的实现原理
在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向ArrayList里添加元素,可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。
读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayListpublic boolean add(T e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; // 复制出新数组 Object[] newElements = Arrays.copyOf(elements, len + 1); // 把新元素添加到新数组里 newElements[len] = e; // 把原数组引用指向新数组 setArray(newElements); return true; } finally { lock.unlock(); } } final void setArray(Object[] a) { array = a; }
CopyOnWriteArrayList适用于读多写少的并发场景public E get(int index) { return get(getArray(), index); }
- 2.ConcurrentHashMap
ConcurrentHashMap采用分段锁技术,写操作加锁,读操作一般不加锁,除非读到的锁是空的,才会加锁重读 - 3.volatite
volatite只保证线程在“加载数据阶段”加载的数据是最新的,并不能保证线程安全
一个线程执行的过程有三个阶段:
加载(复制)主存数据到操作栈 --> 对操作栈数据进行修改 --> 将操作栈数据写回主存
volatite关键字,让编译器不去优化代码使用缓存等,以保证线程在“加载数据阶段”加载的数据都是最新的
对于D选项,之所以volatile不能保证i++的线程安全,除了volatile只能保证可见性之外,与 i++ 不是原子操作也有关,如果换个场合,有些人可能就会把 i++ 误当成原子操作,其实不是的,因为i++做了三次指令操作:- 1.从内存中读取i 变量的值到CPU的寄存器;
- 2.在寄存器中的i自增1;
- 3.将寄存器中的值写入内存;
65.jre 判断程序是否执行结束的标准是()
A.所有的前台线程执行完毕
B.所有的后台线程执行完毕
C.所有的线程执行完毕
D.和以上都无关
答案:A
解析:
- 后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
- 前台线程:是指接受后台线程服务的线程
其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
前台线程和后台线程的区别和联系:
- 1、后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止。所有剩余的后台线程都会停止且不会完成。
- 2、可以在任何时候将前台线程修改为后台线程,方式是设置Thread.IsBackground 属性。
- 3、不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
- 4、托管线程池中的线程都是后台线程,使用new Thread方式创建的线程默认都是前台线程。
说明:
应用程序的主线程以及使用Thread构造的线程都默认为前台线程
使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序
66.下列关于系列化和反序列化描述正确的是:
A.序列化是将数据转为n个 byte序列的过程
B.反序列化是将n个 byte转换为数据的过程
C.将类型int转换为4 byte是反序列化过程
D.将8个字节转换为long类型的数据为序列化过程
答案:AB
76.在Java中,关于HashMap类的描述,以下正确的是 ()
A.HashMap使用键/值得形式保存数据
B.HashMap 能够保证其中元素的顺序
C.HashMap允许将null用作键
D.HashMap允许将null用作值
答案:A C D
解析:
67.下面哪些类实现或者继承了Collection接口?
A.HashMap
B.ArrayList
C.Vector
D.Iterator
答案:B C
解析:
68.关于Java内存区域下列说法不正确的有哪些
A.程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器,每个线程都需要一个独立的程序计数器.
B.Java虚拟机栈描述的是java方法执行的内存模型,每个方法被执行的时候都会创建一个栈帧,用于存储局部变量表、类信息、动态链接等信息
C.Java堆是java虚拟机所管理的内存中最大的一块,每个线程都拥有一块内存区域,所有的对象实例以及数组都在这里分配内存。
D.方法区是各个线程共享的内存区域,它用于存储已经被虚拟机加载的常量、即时编译器编译后的代码、静态变量等数据。
答案:B C
解析:
- A.程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器(偏移地址),Java编译过程中产生的字节码有点类似编译原理的指令,程序计数器的内存空间存储的是当前执行的字节码的偏移地址,每一个线程都有一个独立的程序计数器(程序计数器的内存空间是线程私有的),因为当执行语句时,改变的是程序计数器的内存空间,因此它不会发生内存溢出 ,并且程序计数器是jvm虚拟机规范中唯一一个没有规定 OutOfMemoryError 异常 的区域;
- B.java虚拟机栈:线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。 没有类信息,类信息是在方法区中
- C.java堆:对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组
- D.方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
69.以下哪个不属于JVM堆内存中的区域()?
A.survivor区
B.常量池
C.eden区
D.old区
答案:B
解析:
jvm堆分为:新生代(一般是一个Eden区,两个Survivor区),老年代(old区)。
常量池属于 PermGen(方法区)
70.下面有关java hashmap的说法错误的是?
A.HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”
B.HashMap 的实现不是同步的,意味着它不是线程安全的
C.HashMap通过开放地址法解决哈希冲突
D.HashMap中的key-value都是存储在Entry数组中的
答案:C
解析:
- 关于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。拉链法适合未规定元素的大小。
- Hashtable和HashMap的区别:
- a) 继承不同。 public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
- b) Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
- c) 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和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。
HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。
71.Java是一门支持反射的语言,基于反射为Java提供了丰富的动态性支持,下面关于Java反射的描述,哪些是错误的:( )
A.Java反射主要涉及的类如Class, Method, Filed,等,他们都在java.lang.reflet包下
B.通过反射可以动态的实现一个接口,形成一个新的类,并可以用这个类创建对象,调用对象方法
C.通过反射,可以突破Java语言提供的对象成员、类成员的保护机制,访问一般方式不能访问的成员
D.Java反射机制提供了字节码修改的技术,可以动态的修剪一个类
E.Java的反射机制会给内存带来额外的开销。例如对永生堆的要求比不通过反射要求的更多
F.Java反射机制一般会带来效率问题,效率问题主要发生在查找类的方法和字段对象,因此通过缓存需要反射类的字段和方法就能达到与之间调用类的方法和访问类的字段一样的效率
答案:A D F
解析:
- A Class类在java.lang包
- B 动态代理技术可以动态创建一个代理对象,反射不行
- C 反射访问私有成员时,Field调用setAccessible可解除访问符限制
- D CGLIB实现了字节码修改,反射不行
- E 反射会动态创建额外的对象,比如每个成员方法只有一个Method对象作为root,他不胡直接暴露给用户。调用时会返回一个Method的包装类
- F 反射带来的效率问题主要是动态解析类,JVM没法对反射代码优化。
72.关于身份证号,以下正确的正则表达式为( )
A.isIDCard=/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$/;
B.isIDCard=/^[1-9]\d{7}((9\d)|(1[0-2]))(([0|1|2]\d)|3[9-1])\d{3}$/;
C.isIDCard=/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$/;
D.isIDCard=/^[1-9]\d{5}[1-9]\d{3}((9\d)|(1[9-2]))(([0|1|2]\d)|3[9-1])\d{4}$/;
答案: A C
解析:
- ^:起始符号,^x表示以x开头
- $:结束符号,x$表示以x结尾
- [n-m]:表示从n到m的数字
- \d:表示数字,等同于[0-9]
- X{m}:表示由m个X字符构成,\d{4}表示4位数字
15位身份证的构成:六位出生地区码+六位出身日期码+三位顺序码
18位身份证的构成:六位出生地区码+八位出生日期码+三位顺序码+一位校验码
73.Java表达式"13 & 17"的结果是什么?()
A.30
B.13
C.17
D.1
答案:D
解析:
此处考察与运算,做这种题只需要2步
1.将数值转为二进制:13(01101),17(10001)
2.将二进制数值进行相与运算,只有都为1的情况下才为1
最终的运算结果为00001即对应的10进制为1
74.关于protected 修饰的成员变量,以下说法正确的是()
A.可以被该类自身、与它在同一个包中的其它类、在其它包中的该类的子类所访问
B.只能被该类本身和该类的所有的子类访问
C.只能被该类自身所访问
D.只能被同一个包中的类访问
答案:A
解析:
作用域名 | 本类能不能访问访问 | 同一个包(Package)下的其他类能不能访问 | 不同包(Package),但是作为子类 | 不同包,不是子类 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
75.以下代码执行的结果显示是多少( )?
A.true,false,true
B.false,true,false
C.true,true,false
D.false,false,true
答案:D
解析:
首先我先解释一下String的地址引用
- 1.String s = “abc”:通过字面量赋值创建字符串。则将栈中的引用直接指向该字符串,如不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串
- 2.String s = “a”+“bc”:编译阶段会直接将“a”和“bc”结合成“abc”,这时如果方法区已存在“abc”,则将s的引用指向该字符串,如不存在,则在方法区中生成字符串“abc”对象,然后再将s的引用指向该字符串
- 3.String s = “a” + new String(“bc”):栈中先创建一个"a"字符串常量,再创建一个"bc"字符串常量,编译阶段不会进行拼接,在运行阶段拼接成"abc"字符串常量并将s的引用指向它,效果相当于String s = new String(“abc”),只有’+'两边都是字符串常量才会在编译阶段优化。
我们再回到题目,其中i3和i4分别属于1,3的情况,i3属于字符串常量,在栈中(常量池中),i4属于拼接,只有在编译阶段才能知道,所以在堆中,地址则肯定不同。
接下来我接着解释一下整型的地址引用
Integer i1=128 表示Integer i1=Integer.valueOf(128)
Interger 赋予的int数值在-128 - 127的时候,直接从IntegerCache中获取,这些IntegerCache引用对Integer对象地址是不变的,但是不在这个范围内的数字,则new Integer(i) 这个地址是新的地址,不可能一样的。