JavaSE基础部分面试题

题目1:遍历map的几种方法?

(1) 问题分析:

考官主要考核map集合的四种遍历方式

(2) 核心答案讲解:

第一种,通过key取值。(Map.keyset()遍历key和value)。

第二种,通过迭代器取值。(Map.enteySet()使用iterator遍历key和value)。

第三中,通过entryset()。(通过Map.enteySe()遍历key和value)。

第四中,通过map的value方法。(Map.values()遍历所有的value)。

(4) 结合项目中使用:

在商城中,通过map记录保存相关商品信息。

题目2:hashmap的特性?

(1) 问题分析:

考官主要考核map中hashmap自身独有的特点。

(2) 核心答案讲解:

  1. 允许空键和空值(但空键只有一个,且放在第一位)

  2. 元素是无序的,而且顺序会不定时改变

  3. key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 hashCode 和 equals 方法。

  4. 底层实现是 链表数组,JDK 8 后又加了红黑树。

  5. 实现了 Map 全部的方法

(3) 问题扩展:

红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。

  • 每个节点要么是红色,要么是黑色;

  • 根节点永远是黑色的;

  • 所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);

  • 每个红色节点的两个子节点一定都是黑色;

  • 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点

(4) 结合项目中使用:

有一个 people 类,HashMap 的 key 想通过 name 和 age 判断 people 是否相等,而不是通过 people 对象的存储地址.从而根据 HashMap 原理,需同时重写equals() 和 hashCode()

题目3:Java虚拟机中的内存模型?

(1) 问题分析:

考官主要是对Java程序中各个变量的访问规则的考核。Java虚拟节内存空间分为方法区,Java堆,Java栈,本地方法栈。

(2) 核心答案讲解:

Java虚拟机运行时内存所有的类的实例(不包括局部变量与方法参数)都存储在Java堆中,每条线程有自己的工作内存(Java栈),不同线程之间无法直接访问对方工作内存中的变量。方法区用于存储被虚拟机加载的类信息、常量、static变量等数据,堆用于存储对象实例,比如通过 new创建的对象实例就保存在堆中,堆中的对象的由垃圾回收器负责回收。Java栈用于实现方法调用,每次方法调用就对应栈中的一个栈帧,栈帧包含局部变量表、操作数栈、方法接口等于方法相关的信息,栈中的数据当没有引用指向数据时,这个数据就会消失。本地方法栈保存的是本地方法的调用。

(3) 问题扩展:

线程安全问题就是,多个线程的工作内存同时对堆中同一个数据的修改,使用Java锁避免线程安全问题。

(4) 结合项目中使用:

多线程消费同一个商品

题目4:简单说说Java中的异常处理机制的简单原理和应用。

(1) 问题分析:

考官是对异常的考核。异常的分类,非检查异常和检查异常,try chtch finally的使用。

(2) 核心答案讲解:

  • 所有异常的根类为Java.lang.Throwable.Throwable派生了2个子类:Error和Exception。

  • Error代表了JVM本身的错误,不能被程序员通过代码处理,如内存溢出。

  • Exception 分为IoException和RuntimeException 。

  • Error 和 RuntimeException 以及他们的子类。Javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常称之为非检查异常,比如下标越界。编译器强制必须try.catch处理或throws声明继续抛给上层调用方法处理的异常称之为检查异常,比如使用jdbc连接数据库的SQLException。try块中放可能发生异常的代码。每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类,顺序为从小到大。finally无论异常是否发生,异常是否匹配被处理,finally都会执行。

(3) 问题扩展:

Spring 框架的事务默认是RuntimeException才进行回滚,修改Transactional注解中的rollbackFor属性可以指定为exception异常回滚

(4) 结合项目中使用:

编写自定义异常,利用throw抛出自定义异常

题目5:创建线程的几种方式?

(1) 问题分析:

  • 考官主要想对线程方面的考核,如线程的生命周期、线程安全问题等。

(2) 核心答案讲解:

  • 通过继承Thread类实现,多个线程之间无法共享该线程类的实例变量。

  • 实现Runnable接口,较继承Thread类,避免继承的局限性,适合资源共享。

  • 使用Callable,方法中可以有返回值,并且抛出异常。

  • 创建线程池实现,线程池提供了一个线程队列,队列中保存所有等待状态的线程,避免创建与销毁额外开销,提高了响应速度。

(3) 问题扩展:

线程的生命周期:线程要经历新建、就绪、运行(活动)、阻塞和死亡五种不同的状态。这五种状态都可以通过Thread类中的方法进行控制。

① 新建状态:使用new 操作符创建一个线程后,该线程仅仅是一个空对象,这时的线程处于创建状态。

② 就绪状态:使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于就绪状态。

③ 运行状态:系统真正执行线程的run()方法。

④ 阻塞和唤醒线程阻塞状态:使用sleep(),wait()方法进行操作。

⑤ 死亡状态:线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。

线程安全问题:使用synchronized声明同步或使用锁lock,Lock使用起来比较灵活,但需要手动释放和开启,采用synchronized不需要用户去手动释放锁。

(4) 结合项目中使用:

模拟实现银行业务调度系统逻辑,具体需求如下:

  1. 银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。

  2. 有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。

  3. 异步随机生成各种类型的客户,生成各类型用户的概率比例为:VIP客户 :普通客户 :快速客户 = 1 :6 :3。

  4. 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。

  5. 各类型客户在其对应窗口按顺序依次办理业务。

  6. 当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。

  7. 随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。

  8. 不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

题目6:谈谈你对垃圾回收机制的了解?

(1) 问题解析:

考官主要针对你对GC方面的考核,比如:什么是垃圾回收机制、JVM回收特点等。

(2) 核心答案解析

什么是垃圾回收机制:在系统运行过程中,会产生一些无用的对象,这些对象占据着一定的内存,如果不对这些对象清理回收无用对象的内存,可能会导致内存的耗尽,所以垃圾回收机制回收的是内存。同时GC回收的是堆区和方法区的内存。

JVM回收特点:(stop-the-world)当要进行垃圾回收时候,不管何种GC算法,除了垃圾回收的线程之外其他任何线程都将停止运行。被中断的任务将会在垃圾回收完成后恢复进行。GC不同算法或是GC调优就是减少stop-the-world的时间。à(为何非要stop-the-world),就像是一个同学的聚会,地上有很多垃圾,你去打扫,边打扫边丢垃圾怎么都不可能打扫干净的哈。当在垃圾回收时候不暂停所有的程序,在垃圾回收时候有new一个新的对象B,此时对象A是可达B的,但是没有来及标记就把B当成无用的对象给清理掉了,这就会导致程序的运行会出现错误。

(3) 问题扩展:

如何判断哪些对象需要回收呢:

1.引用计数算法(java中不是使用此方法):每个对象中添加一个引用计数器,当有别人引用它的时候,计数器就会加1,当别人不引用它的时候,计数器就会减1,当计数器为0的时候对象就可以当成垃圾。算法简单,但是最大问题就是在循环引用的时候不能够正确把对象当成垃圾。

2.根搜索方法(这是后面垃圾搜集算法的基础):这是JVM一般使用的算法,设立若干了根对象,当上述若干个跟对象对某一个对象都不可达的时候,这个对象就是无用的对象。对象所占的内存可以回收。

根搜索算法的基础上,现代虚拟机的实现当中,垃圾搜集的算法主要有三种,分别是标记-清除算法、复制算法、标记-整理算法。

(4) 结合项目中使用:

尽量不要new很大的object,大对象(>=85000Byte)直接归为G2代,GC回收算法从来不对大对象堆(LOH)进行内存压缩整理,因为在堆中下移85000字节或更大的内存块会浪费太多CPU时间;不要频繁的new生命周期很短object,这样频繁垃圾回收频繁压缩有可能会导致很多内存碎片,可以使用设计良好稳定运行的对象池(ObjectPool)技术来规避这种问题;之前在维护一个系统的时候,发现有很多大数据量的处理逻辑,但竟然都没有批量和分页处理,随着数据量的不断膨胀,隐藏的问题会不断暴露。然后我在重写的时候,都按照批量多次的思路设计实现,有了多线程、多进程和分布式集群技术,再大的数据量也能很好处理,而且性能不会下降,系统也会变得更加稳定可靠。

题目7:说说HashCode()、equals()的区别?

(1) 问题分析:

考官主要想对hashCode()方法和equal()方法作用和效率上进行比较。

(2) 核心答案讲解:

equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。

hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。

对于需要大量并且快速的对比的话如果都用equal()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!

(3) 问题扩展:

hashCode的重写:

hashCode()和equal()一样都是基本类Object里的方法,而和equal()一样,Object里hashCode()里面只是返回当前对象的地址,如果是这样的话,那么我们相同的一个类,new两个对象,由于他们在内存里的地址不同,则他们的hashCode()不同,所以这显然不是我们想要的,所以我们必须重写我们类的hashCode()方法,即一个类,在hashCode()里面返回唯一的一个hash值。

equals方法的作用:

默认情况(没有覆盖equals方法)下equals方法都是调用Object类的equals方法,而Object的equals方法主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象);

要是类中覆盖了equals方法,那么就要根据具体的代码来确定equals方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等。

(4) 结合项目中使用:

equals方法是默认的判断2个对象是否相等的方法,在Object类里有实现,判断的是2个对象的内存地址。在hibernate中,不允许存在同类对象中有2个一样的实例。hibernate通过equals方法做判断。如:

User u1 = new User(“张三”);

User u2 = new User(“李四”);

User u3 = new User(“张三”);

按照项目需求,用户只要名字相同,就表示同一个用户,所以我们认为,u1和u3是同一个人,同一个对象。但是因为u1,u2,u3三者的内存地址都各不相同,所以hibernate会认为这是3个不同的对象。这与我们假设的出了矛盾。 因此,我们将覆盖Object类中的equals方法。

public class User{

private String userName;.//get ,set方法省

//覆盖Object里的equals方法

public boolean equals(Object arg0){

if (!(arg0 instanceof User)){

return false;

}

User user = (User)arg0;

//如果名字相同,则表示属于同一个对象。

if(user.getName().equals(this.getName)){

return true;

}else{

return false; }

}

这样hibernate在插入数据的时候,如果传过来一个叫”张三”的用户,hibernate会先判断有没有叫“张三”的用户,如果没有,就允许插入,如果有,就不允许插入。这样做可以保证数据的高度一致性,不同的项目有不同的需求,所以要根据自己的需求来覆盖equals方法。

题目8:LinkedList和ArrayList区别?

(1) 问题分析:

考官主要想对集合进行考核,如集合的特点及原理方面的了解。

(2) 核心答案讲解:

ArrayList是基于动态数组的数据结构,LinkedList基于链表的数据结构。
ArrayList查询操作快,LinedList增删操作快。

(3) 问题扩展:

为了使得突破动态长度数组而衍生的ArrayList初始容量为10,每次扩容会固定为之前的1.5倍,每次扩容的过程是内部复制数组到新数组;

LinkedList的每一个元素都需要消耗一定的空间。

(4) 结合项目中使用:

对于需要快速插入,删除元素,应该使用LinkedList。
对于需要快速随机访问元素,应该使用ArrayList。
对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。
对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

题目9:String,StringBuilder,StringBuffer三者的区别?

(1) 问题分析:

考官主要想对final修饰符的作用,同步锁,以及数据类型的考察。在工作中为什么业务层频繁的拼接sql不用string而用StringBuilder,为什么Stringbuilder比StringBuffer效率高

(2) 核心答案讲解:

String是引用类型,底层是被final修饰的字符数组,所以String相当于一个常量,是不可改变的,每拼接一次就会产生一个新的对象,而由于垃圾回收机制的原理,原有的对象不会立马被回收,这是对内存极大的消耗;而StringBuilder和StringBuffer 是可变长度的,可以利用append方法向原有对象拼接,然后用toString方法将其转化为String类型;这两个相比起来StringBuilder的效率更高,因为他是非线程安全的,不需要花费资源去维护同步锁。

(3) 问题扩展:

Final修饰符的作用是什么?

在工作中你们如果在业务层去拼接sql,使用String类型去接收的吗?

(4) 结合项目中使用:

在项目中如果频繁的拼接字符串需要用什么类型对象去接收

题目10:是否可以从一个static方法内部发出对非static方法的调用?

(1) 问题分析:

考官主要相对static方法的考察,涉及到static关键词考核,如抽象的(abstract)方法是否可同时是静态的(static);static 可否用来修饰局部变量; 内部类与静态内部类的区别;java中是否可以覆盖(override) 一个private或者是static的方法

(2) 核心答案讲解:

不可以。static方法是静态方法,是属于类的方法,非static方法是属于对象的方法。因为非static方法是要与对象关联在一起的,必须在创建出一个对象后,才可以通过这个对象调用非static方法;而static方法可以直接通过类名来调用,不需要创建对象。也就是说,在一个static方法被调用时,还可能没有创建任何实例对象,此时如果从static内部发出对非static方法的调用,非static方法是无法关联到对象的。

(3) 问题扩展:

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块。

static修饰的变量习惯称为静态变量,static修饰的方法称为静态方法,static修饰的代码块叫做静态代码块。

static的意义在于方便在没有创建对象的情况下来进行调用(方法/变量)。

“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。

(4) 结合项目中使用:

1.在项目中,很多工具类会使用static定义方法 ,达到不用new对象直接类名.方法名直接调用,使用工具更方便,减少重复代码的作用。例如:项目中的UUIDUtils

2常见的单例模式

单例模式方法定义为静态方法:达到不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象的目的。

题目11:java中 sleep 和 wait 的区别?

(1) 问题分析:

面试官考核的线程方面的问题,线程的生命周期与过程中的阻塞状态。

(2) 核心答案讲解:

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

(3) 问题扩展:

线程生命周期及线程同步安全问题。

(4) 结合项目中使用:

生产者消费者 生产完成 仓库最多放一百个,必须等卖出五十个之后再通知生产

题目12:实现一个线程有哪几种方式,各有什么优缺点,比较常用的是那种?

(1) 问题分析:

面试官考核的是线程创建方式及

(2) 核心答案讲解:

1.继承Thread类 2.实现Runnable接口

3.实现Callable接口 4.线程池方式

优缺点:

1.继承Thread类

优点:代码简单 。 缺点:该类无法集成别的类。

2.实现Runnable接口

优点:继承其他类。 同一实现该接口的实例可以共享资源。

缺点:代码复杂

3.实现Callable

优点:可以获得异步任务的返回值

4.线程池:实现自动化装配,易于管理,循环利用资源。

(3) 问题扩展:

在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

(4) 结合项目中使用:

用户登录成功之后需要记录用户连续登录天数,给用户奖励积分。可以创建一个线程,单独调用积分接口。

题目13:String s = new String(“xyz”);创建了几个StringObject?是否可以继承String类?

(1) 问题分析:

考官主要是对jvm方面问题考察,以及String类的考察。例如tring str = “aaa” + new String(“bbb”)创建了几个String对象?String是否是基本类型,String是否有length()这个方法

(2) 核心答案讲解:

String s=new String(“xyz”)究竟创建String Object分为两种情况:
1.如果String常理池中,已经创建"xyz",则不会继续创建,此时只创建了一个对象new String(“xyz”)

2.如果String常理池中,没有创建"xyz",则会创建两个对象,一个对象的值是"xyz",一个对象new String(“xyz”)。

String类不能被继承,因为它是被final修饰的。

(3) 问题扩展:

1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表

3、全局区(静态区)(static)全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域程序结束后由系统释放。

4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码。

(4) 结合项目中使用:

String它被用于裁剪,拼接。搜索字符串,比较字符串,截取字符串,转换大小写等。在项目中不经常发生变化的业务场景中,优先使用String。

题目14:String s = “Hello”;s = s + “world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?

(1)问题分析:

考官主要是对String类的考察。例如:String是否是基本数据类型?String是否能被继承?String如何进行字符串截取?String、StringBuffer、StringBudder区别?

(2) 核心答案讲解:

因为String被设计为不可变(immutable)类,所以它的所有对象都是不可变的对象。在这段代码中,s原先指向一个String对象,内容是hello,然后我们对s进行+操作,那么s所指向的那个对象是否发生了变化呢?答案是没有,这时,s不再指向原来的那个对象了,而指向另一个String对象,内容为hello world ,原来的那个对象还存在于内存中,只是s这个引用变量不再指向它了。

(3) 问题扩展:

如果经常对字符串进行各种各样的操作,或者说不可预见的修改,那么使用String对象来代表字符串的话会引起很大的内存的额开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这是应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。

String**、StringBuffer、StringBudder区别?**

  1. 可变不可变

String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。private final char value[];

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。char[] value;

  1. 是否线程安全

String中的对象是不可变的,也就可以理解为常量,显然线程安全。

StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的.

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

3.StringBuilder与StringBuffer共同点

StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。

抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer会在方法上加synchronized关键字,进行同步。

(4) 结合项目中使用:

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

题目15:多线程解决同步问题的方式?

(1) 问题分析:

考官主要相对多线程方面的考核,被多个线程同时访问的,使用线程同步技术,确保数据在任何时刻最多只有一个线程访问

(2) 核心答案讲解:

同步代码块:使用 synchronized() 对需要完整执行的语句进行“包裹”,synchronized(Obj obj) 构造方法里是可以传入任何类的对象

同步方法:

在方法的申明里申明 synchronized

(3) 问题扩展

死锁

当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形:

线程 A 当前持有互斥所锁 lock1,线程 B 当前持有互斥锁 lock2。

接下来,当线程 A 仍然持有 lock1 时,它试图获取 lock2,因为线程 B 正持有 lock2,因此线程 A 会阻塞等待线程 B 对 lock2 的释放。

如果此时线程 B 在持有 lock2 的时候,也在试图获取 lock1,因为线程 A 正持有 lock1,因此线程 B 会阻塞等待 A 对 lock1 的释放。

二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。

(4) 应用场景

XX去银行开个银行账户,银行给 me 一张银行卡和一张存折,XX用银行卡和存折来搞事情:银行卡疯狂存钱,存完一次就看一下余额;同时用存折子不停地取钱,取一次钱就看一下余额。

题目16:Hashtable 与 HashMap 有什么不同之处?

(1) 问题分析

考官主要考核对于两个map的区别。

(2) 核心答案讲解:

相同点:

HashMap和Hashtable都是存储“键值对(key-value)”的散列表,而且都是采用 拉链法实现的。
存储的思想都是:通过table数组存储,数组的每一个元素都是一个Entry;而 一个 Entry就是一个单向链表,Entry链表中的每一个节点就保存了key- value键值对数据

不同点:

​ 1 继承和实现方式不同

​ 2 线程安全不同

​ 3 对null值的处理不同

​ 4 支持的遍历种类不同

5 通过Iterator迭代器遍历时,遍历的顺序不同

6 容量的初始值 和 增加方式都不一样

7 添加key-value时的hash值算法不同

(3) 问题扩展

HashTable中hash数组默认大小是11,增加的方式是 old*2+1;HashMap中 hash数组的默认大小是16,而且一定是2的指数;

扩容的临界点是加载因子loadFactor>0.75,其中loadFactor=size/capaticy

(4) 使用场景

在多线程中,我们可以自己对HashMap进行同步,也可以选择ConcurrentHashMap。当HashMap和Hashtable都不能满足自己的需求时,还可以考虑新定义一个类,继承或重新实现散列表;当然,一般情况下是不需要的了。

题目17:单例中的懒汉和恶汉模式的区别?

(1) 问题分析:

主要考察懒汉和饿汉模式在创建时的区别以及分别在什么情况下使用懒汉模式,什么情况下使用饿汉模式。

懒汉模式:在类加载的时候不被初始化。

饿汉模式:在类加载时就完成了初始化,但是加载比较慢,获取对象比较快。

饿汉模式是线程安全的,在类创建好一个静态对象提供给系统使用,懒汉模式在创建对象时不加上synchronized,会导致对象的访问不是线程安全的。

(2) 核心答案讲解:

//饿汉式:
     public class Singleton{
       private static Singleton singleton = new Singleton ();
       private Singleton (){}
       public static Singleton getInstance(){return singletion;}
    } 

 //懒汉式:
    public class Singleton{
       private static Singleton singleton = null;
       public static synchronized synchronized getInstance(){
         if(singleton==null){
           singleton = new Singleton();
         }
         return singleton;
       }
    } 

​ 饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变
​ 懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。

从实现方式来讲他们最大的区别就是懒汉式是延时加载,是在需要的时候才创建对象,而饿汉式在虚拟机启动的时候就会创建,

(3) 问题扩展

懒汉式不会预先创建对象,只在第一次调用时才创建对象,但是多线程并发执行的时候就很容易出现安全隐患,比如说第一个线程在判断newInstance == null时,还没有new出实例时,第二个线程也进来,判断的newInstance也是null,然后也会new出实例,这就不符合单例模式了, 所以需要加锁。使用synchronized关键字加锁能解决安全问题,但是加锁同时会出现一个问题,那就是每次都需要判断锁,这样性能就会降低,所以为了提高性能,我们应该尽量减少锁判断的次数,加上双重判断。

 //静态工厂方法、单锁
   public synchronized static SingletonTest2 getInstance1() {
     if (single2==null) {
       single2 = new SingletonTest2();
     }
     return single2;
   }
     
   //静态工厂方法、双重锁
   public static SingletonTest2 getInstance2() {
     if (single2==null) {
       synchronized (SingletonTest2.class) {
         if (single2==null) {
           single2 = new SingletonTest2();
         }
       }
     }
     return single2;
   }

(4) 结合项目中的使用

懒汉式的特点是延迟加载,比如配置文件,采用懒汉式的方法。

题目18:类加载机制有了解嘛?

(1) 问题分析:
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

(2) 核心答案讲解:

类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

(1) 装载:查找和导入Class文件;

(2) 链接:把类的二进制数据合并到JRE中;

​ (a)校验:检查载入Class文件数据的正确性;

​ (b)准备:给类的静态变量分配存储空间;

​ ©解析:将符号引用转成直接引用;

(3) 初始化:对类的静态变量,静态代码块执行初始化操作

Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态)。

(3) 问题扩展

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_dong丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值