Java面试题——基础篇

这篇博客详细探讨了Java面试中常见的基础问题,包括JDK、JRE、JVM的区别,equals与"=="的差异,以及hashCode的使用。还深入讲解了String、StringBuffer和StringBuilder的特性和选择,线程安全的实现方式如接口与抽象类的对比,以及并发编程中的锁机制。此外,还涵盖了IO流、多线程、HashMap和TreeMap的区别以及线程池的相关知识。通过这些内容,帮助读者巩固Java基础,为面试做好准备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.JDK,JRE,JVM

JDK:java开发工具包,偏向程序员

JRE:java运行环境,偏向用户

JVM:java虚拟机,用于编译解释

2.equals与“==”

equals本质上就是==,因为java.lang.String类重写了equals方法,所以如果两个字符串对象包含有相同的内容它就返回true,但是==只有他们的引用地址相同时才返回true。

3.hashcode相同,equals也不一定相同

1.hashcode用来储存地址,他的存在用于查找的快捷,如hashMap

2.如果两个对象相同,就是适用于equals方法,那么两个对象的hashcode一定要相同

3.如果equals方法被重写,那么hascode也尽量重写,并且产生hashcode使用的对象,一定要和equals方法中使用的一致,否则就会违反第二点。重写hashcode和equals可保证hashMap键唯一性

4.两个对象的hashcode相同,并不一定代表对象就相同,也就不一定适用于equals方法,只能够说明这两个对象再散列存储结构中,如hashtable,他们“存放在同一个篮子里”

4.String,StringBuffer,StringBuilder的区别

String声明的是不可变对象,其他两个是可变的(String源码可知用final修饰)

StringBuffer:同步,线程安全,性能低,建议多线程使用

StringBuilder:异步,线程不安全,性能高,建议单线程使用

常见的字符串拼接应选择StringBuilder,比String快千百倍。

5.String str = “i” 与 String str = new String(“i”)

String str = “i” Java虚拟机将其分配到常量池中

String str = new String(“i”)会被分配到堆内存中,创建了两个对象,但如果再执行一遍就是创建一个对象,因为常量池中已经有了。

6.接口与抽象类的区别

1.抽象类可以有默认的方法实现,接口不能有默认的方法实现

2.抽象类可以有构造函数,接口不能有构造函数

3.抽象类可以有main(),接口不能有

4.抽象类中的方法可以是任意访问修饰符,接口默认是pubilc

5.可以多实现,不能多继承(假设A类和B类都有t方法,且具体实现不一样。C类继承A类和B类,当C类调用t方法时,会出现歧义。因为,A类和B类都有t方法,但具体实现不一样。可以多实现是因为,接口中的方法没有具体实现。继承多个接口,就算两个接口中有相同的方法,也不会出现矛盾。)

7.IO流的分类

按类型(传输单位)来分:字节流和字符流

按功能(流向)来分:输入流和输出流

字节流: 它处理单元为1个字节(byte),操作字节和字节数组,存储的是二进制文件,如果是音频文件、图片、歌曲,就用字节流好点(1byte = 8位);

字符流: 它处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,如果是关系到中文(文本)的,用字符流好点(1Unicode = 2字节 = 16位);

最简单的区分字节流和字符流

万物皆文件,那就将文件在记事本里面打开,如果打开后能看的懂的就是字符流,如果看不懂那就是字节流

8.BIO,NIO,AIO有什么区别

BIO:Block IO 同步阻塞式IO,就是平常我们使用的传统IO。他的特点是模式简单使用方便,并发处理能力低

NIO:NEW IO 同步非阻塞式IO,是传统IO的升级,客户端和服务器端通过了Channel(通道)通讯,实现了多路复用

AIO:Asynchronous IO 是NIO的升级,也叫NIO2,实现了异步非阻塞IO,异步IO的操作基于事件和回调机制

9. HashMap和TreeMap的区别


1、HashMap是通过hashcode()对其内容进行快速查找的;HashMap中的元素是没有顺序的;

TreeMap中所有的元素都是有某一固定顺序的,如果需要得到一个有序的结果,就应该使用TreeMap;

2、HashMap和TreeMap都不是线程安全的;

3、HashMap继承AbstractMap类;覆盖了hashcode() 和equals() 方法,以确保两个相等的映射返回相同的哈希值;

TreeMap继承SortedMap类;他保持键的有序顺序;

4、HashMap:基于hash表实现的;使用HashMap要求添加的键类明确定义了hashcode() 和equals() (可以重写该方法);为了优化HashMap的空间使用,可以调优初始容量和负载因子;

TreeMap:基于红黑树实现的;TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;

5、HashMap:适用于Map插入,删除,定位元素;

TreeMap:适用于按自然顺序或自定义顺序遍历键(key);

10.ArrayList和LinkList的区别

ArrayList的实现是基于数组来实现的,LinkedList的基于双向链表来实现。这两个数据结构的逻辑关系是不一样,当然物理存储的方式也会是不一样。

LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

对于随机访问,ArrayList要优于LinkedList。

对于插入和删除操作,LinkedList优于ArrayList。

在ArrayList的 中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

11.hashMap实现原理

通过put(key,value)存储,get(key)来获取,当传入key时,HashMap会根据key.hashCode()计算Hash值,当计算出相同的哈希值时(也叫hash冲突),个数较少,使用数组+链表存储相同的hash值,个数较多,使用数组+红黑树存储相同的hash值,如果hash值和equals都相同,表示完全一样,就不存,就去重

12.队列(Queue)的用法

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作

LinkedList类实现了Queue接口,因此可以把LinkedList当成Queue来使用

13.HashMap与HashTable的区别

相同点:都是双列集合且键不能重复,值可以重复

不同点:HashMap线程不安全,线程不同步,效率高,可以存储null键null值

HashTable线程安全,线程同步,效率低,不可以存储null键null值

双列集合优先使用HashMap集合

如果是在多线程的场景下,也无需使用HashTable集合,可以用ConcurrentHashMap集合,该集合是线程同步的

14.Map集合遍历的四种方式

1.根据键找值,首先使用keyset()获取所有的键集合,然后for循环遍历键集合,通过get(key)方法根据键找到所有值

2.获取键值对对象,通过迭代器取值,首先使用entrySet()获取键值对entry类型对象,然后通过迭代器Iterator获取键值对对象,通过entry.getkey(),entry.getvalue()方法获取值

3.获取键值对对象,通过增强for取值,与2一样,遍历的方式改为增强for

4.通过Map的values方法,缺点是只能拿到所有的值Map.values(),返回结果是String类型的单列集合

15.异常

什么是异常:程序所发生的错误叫做异常,异常分为编译时异常和运行时异常,编译时异常是指编译期间编译器检测到某段代码可能会发生某些问题,需要程序员提前给代码做出错误的解决方案,否则则编译不通过,运行时异常指的是代码运行期间出现的错误

体系结构:Throwable(祖宗)-----  Error(严重性错误),例如递归没有出口 -----  Exception:RuntimeException(运行时异常),!RuntimeException(编译时异常)

java对异常的默认处理方式:如果出现了问题,java会将问题所描述的异常类创建一个对象(实例),然后将该对象抛出给上一级

异常的手动处理方式

1.问题可以自己处理掉的,try抓捕可能会出现异常的代码,catch为出现异常的处理方式,finally无论有无异常都执行的代码,优点是不影响后续代码的执行

2.问题自己处理不掉,就将问题抛给调用者,适用于自己写的代码别人传的参数控制不了

throws对方法进行声明,告知调用者此方法存在异常

throw将异常对象抛给调用者

throw和throws的区别:
1、
throw代表动作,表示抛出一个异常的动作;
throws代表一种状态,代表方法可能有异常抛出

throw用在方法实现中,而throws用在方法声明中;
throw只能用于抛出一种异常,而throws可以抛出多个异常

2、
throw关键字用来在程序中明确的抛出异常,相反 throws语句用来表明方法不能处理的异常。
每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。

3、
throw是在代码块内的,即在捕获方法内的异常并抛出时用的
throws是针对方法的,即将方法的异常信息抛出去
可以理解为throw是主动(在方法内容里我们是主动捕获并throw的),而throws是被动(在方法上是没有捕获异常进行处理,直接throws的)

如果抛出的异常对象是runtimeException则不需throws,因为runtimeException是运行时异常

16.创建线程的方式

1.继承Thread类,重写run()方法,优点是实现多线程简单,缺点是该类无法继承别的类,因为Java没有多继承

2.实现Runnable接口,优点是继承其他类,统一实现接口的实例可以共享资源,缺点是代码复杂

3.实现Callable接口,与Runnable差不多但是有返回值

17.线程池

就是一个容纳多个线程的容器,其中的线程可以重复使用,省去了频繁创建线程对象的操作,优点:实现自动装配,易于管理,循环利用资源

线程池的处理流程

1.首先判断核心线程是否已满,未满则创建核心线程执行

2.如果核心线程已满,判断任务队列是否已满,未满则将任务放到队列当中,等待别的线程执行完毕

3.如果任务队列已满,判断最大线程数是否已经达到,未达到则创建临时线程执行,已经达到则根据拒绝策略处理任务

18.乐观锁 VS 悲观锁

乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。在Java和数据库中都有此概念对应的实际应用。

先说概念。对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。

而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

19.公平锁 VS 非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

20.Lock接口与synchronized的区别

synchronized是关键字,Lock是接口;

synchronized是隐式的加锁,lock是显式的加锁;

synchronized可以作用于方法上,lock只能作用于方法块;

synchronized底层采用的是objectMonitor,lock采用的AQS;

synchronized是阻塞式加锁,lock是非阻塞式加锁支持可中断式加锁,支持超时时间的加锁;

synchronized在进行加锁解锁时,只有一个同步队列和一个等待队列, lock有一个同步队列,可以有多个等待队列;

synchronized只支持非公平锁,lock支持非公平锁和公平锁;

21.为什么要使用多线程

1.吞吐量,多请求一个线程只能处理一个用户的请求

2.伸缩性,通过增加CPU的核数来提升性能

22.JVM类的加载机制

描述:当程序要使用某个类时,如果该类还没有被加载到内存中,则会通过类的加载,类的链接,类的初始化这三个步骤来对类进行初始化,如果不出意外情况,JVM将会连接完成这三个步骤,所以有时候也把这三个步骤统称为类初始化

类的加载:指将class文件读入内存,并为之创建一个java.lang.class对象,任何类被使用时,系统都会为之创建一个.class对象

类的链接:链接包含三个部分:验证,准备,解析

验证(Verification):确保被加载类的正确性。这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
准备(Preparation):准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。(如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成;如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成)
解析(Resolution):把类中的符号引用转化为直接引用(比如说方法的符号引用,是有方法名和相关描述符组成,在解析阶段,JVM把符号引用替换成一个指针,这个指针就是直接引用,它指向该类的该方法在方法区中的内存位置)。解析包含:类或接口的解析、字段解析、类方法解析、接口方法解析。

类的初始化:在链接的准备阶段,类的静态变量已赋过一次初始值(默认值),而在初始化阶段,则是为静态变量赋指定值。

 何时触发初始化?

为一个类型创建一个新的对象实例时(比如new、反射、序列化)
调用一个类型的静态方法时(即在字节码中执行invokestatic指令)
调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式
调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)
初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)
JVM启动包含main方法的启动类时。

23.并发、并行、串行

并发:允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行

并行:在时间上是重叠的,两个任务在同一时刻互不干扰同时执行

串行:在时间上不可能发生重叠,前一个任务没搞定,下一个任务只能继续等

24.线程之间是如何进行通讯的

线程之间可以通过共享内存或基于网络来进行通信

1.如果是通过共享内存来进行通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒,Java中阻塞和唤醒是wait()、notify()

2.通过网络连接是将通信数据发送给对方,当然也要考虑到并发问题,处理方式是加锁等

25.线程安全

可以理解为内存安全,堆是共享内存,栈是安全的

当多个线程访问同一个对象时,如果不能进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得预期正确的结果(跟单线程的结果一样),我们就说这个对象是线程安全的

26.反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.


反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
     (其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值