JAVA面试专题一:基础

本文深入解析Java中的核心概念,如ArrayList与LinkedList的区别,Map类的使用与线程安全性,ConcurrentHashMap的设计,以及垃圾回收机制等,帮助读者理解Java内部工作原理。

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

1、Object有哪些方法

Object类总共13个方法

在这里插入图片描述

2、 arrayList和LinkList有哪些区别

在这里插入图片描述

1)ArrayList和LinkedList可想从名字分析,它们一个是Array(动态数组)的数据结构,一个是Link(链表)的数据结构,此外,它们两个都是对List接口的实现。
前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列

2)当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

3)当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

4)从利用效率来看,ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

5)ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

3、用过哪些Map类,都有什么区别,HashMap是线程安全的吗

最常用的Map实现类有:HashMap,ConcurrentHashMap(jdk1.8),LinkedHashMap,TreeMap,HashTable;
其中最频繁的是HashMap和ConcurrentHashMap,他们的主要区别是HashMap是非线程安全的。ConcurrentHashMap是线程安全的。

并发下可以使用ConcurrentHashMap和HashTable,他们的主要区别是:
1)ConcurrentHashMap的hash计算公式:(key.hascode()^ (key.hascode()>>> 16)) & 0x7FFFFFFF
HashTable的hash计算公式:key.hascode()& 0x7FFFFFFF

2)HashTable存储方式都是链表+数组,数组里面放的是当前hash的第一个数据,链表里面放的是hash冲突的数据
ConcurrentHashMap是数组+链表+红黑树

3)默认容量都是16,负载因子是0.75。就是当hashmap填充了75%的busket是就会扩容,最小的可能性是(16*0.75),一般为原内存的2倍

4 )线程安全的保证:HashTable是在每个操作方法上面加了synchronized来达到线程安全,ConcurrentHashMap线程是使用CAS(compore and swap)来保证线程安全的

4、JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。

jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。

但是ConcurrentHashMap的一些操作使用了synchronized锁,而不是ReentrantLock,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用ReentrantLock锁能更多的提高性能

5、 有没顺序的 Map 实现类,如果有,他们是怎么保证有序的 。

顺序的 Map 实现类:LinkedHashMap,TreeMap

LinkedHashMap 是基于元素进入集合的顺序或者被访问的先后顺序排序,TreeMap 则是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。

6、有了解java的原子类?实现原理是什么?

采用硬件提供原子操作指令实现的,即CAS。每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全。

7、解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

8、hashcode默认实现是什么

hashCode是根类Obeject中的方法。默认情况下,Object中的hashCode() 返回对象的32位jvm内存地址。也就是说如果对象不重写该方法,则返回相应对象的32为JVM内存地址。

关于hashCode方法,一致的约定是:
1、重写了euqls方法的对象必须同时重写hashCode()方法。
 也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
  如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
  如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
  如果两个对象的hashcode值相等,则equals方法得到的结果未知。

2、String 类型的 hashcode 方法:
在 JDK 中,Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的内存地址;
JDK 中,我们经常把 String 类型作为 key,那么 String 类型是重写 了hashCode 方法;

9、如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。

父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。

但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。

如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。

10、Java面试题之final、finally和finalize的区别final:

final是一个修饰符,可以修饰变量、方法和类,如果final修饰变量,意味着变量的值在初始化后不能被改变;修饰的方法不能被重写,修饰的类不能被继承;防止编译器把final域重排序到构造函数外;

finally:finally与try和catch一起用于异常处理,finally块一定会被执行,无论在try块中是否发生异常;实际中一般用于关闭文件流,释放资源等操作。

finalize:finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在 Object 类中定义的,因此所有的类都继承了它,子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。

public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub  
        for(int i=0;i<6;i++){
            System.gc();
            Test t = new Test();            
        }
        System.gc();//若注释此语句,则只打印0-4,这就是Java垃圾回收机制的一个简单验证。
    }
    static int count;
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("-----------finalize-----------"+count++);
    }       
}

打印如下:
———–finalize———–0
———–finalize———–1
———–finalize———–2
———–finalize———–3
———–finalize———–4
———–finalize———–5

11、String和StringBuilder、StringBuffer的区别?

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。
其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。
而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。
StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
StringBuffer线程安全!

12、在自己的代码中,如果创建一个java.lang.String对象,这个对象是否可以被类加载器加载?为什么。

不可以,双亲委派模式会保证父类加载器先加载类,就是BootStrap(启动类)加载器加载jdk里面的java.lang.String类,而自定义的java.lang.String类永远不会被加载到

13、Java 中会存在内存泄漏吗,请简单描述。

理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。

14、GC是什么?为什么要有GC?

GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显式操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显式的垃圾回收调用。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。

垃圾回收机制有很多种,包括:标记-清除算法复制算法标记-整理算法分代算法等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会根据Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:

伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
存活区(Survivor):从伊甸园幸存下来的对象会被挪到这里。
老年代(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。
 					当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),
      				这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。

与垃圾回收相关的JVM参数:

-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年轻代的大小
-XX:-DisableExplicitGC — 让System.gc()不产生任何作用
-XX:+PrintGCDetails — 打印GC的细节
-XX:+PrintGCDateStamps — 打印GC操作的时间戳
-XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小
-XX:NewRatio — 可以设置老生代和新生代的比例
-XX:PrintTenuringDistribution — 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
-XX:TargetSurvivorRatio:设置幸存区的目标使用率

JVM内存管理

15、动态代理与cglib实现的区别。

动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理
1)简单区别:
JDK动态代理是基于接口反射;只能对实现了接口的类生成代理,而不能针对类;原因是因为Java是单继承的,JDK动态代理时,会继承Proxy类,故无法再继承其他父类,只能基于接口实现发射,这就造成了当前对象和目标对象不同;代理对象跟目标类实现一个接口,从而避过虚拟机的校验。

cglib动态代理是继承,并重写目标类,所以目标类和方法不能被声明成final。是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。但而接口是可以被继承的。所以cglib方式也可以对接口实现代理。

2)Spring在选择用JDK还是CGLiB的依据:
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)

3)CGlib比JDK快?
  (1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  (2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

16、构造器(constructor)是否可被重写(override)?

构造器不能被继承,因此不能被重写,但可以被重载。

17、如何实现对象克隆

有两种方式:
  1). 实现Cloneable接口并重写Object类中的clone()方法;
  2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,

18、深拷贝和浅拷贝区别。

浅拷贝只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。

19、在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用。

20、什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。

序列化是一种用来处理对象流的机制 ,所谓对象流就是将对象的内容进行流化。

序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值