• 面试题--基础篇


    基础

    1、面向对象的三大特性?

    参考答案:封装、继承、多态。
    封装的好处是:隐藏实现细节,提供公共的访问方式;提高代码的复用性;提高安全性。
    继承的好处:使用父类的属性方法,更大程度的实现代码复用。把共性的全部抽到父类。
    多态:多态是同一个行为具有多个不同表现形式或形态的能力。维护和拓展。

    2、static关键字特点?

    • 随着类的加载而加载,随着类的消失而消失。
    • 被类的所有对象共享。
    • 可以通过类名直接调用。
    • 静态方法只能访问静态的成员变量和静态的成员方法。

    3、代码块的执行顺序?

    静态代码块 > 构造代码块 > 构造方法 > 局部代码块

    4、final修饰符作用?

    • final可以修饰类,该类不能被继承。
    • final可以修饰方法,该方法不能被重写。(覆盖,复写)
    • final可以修饰基本类型变量,该变量不能被重新赋值。因为这个变量其实常量。
    • final可以修饰引用类型变量,引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。

    5、谈谈final finally finalize的区别?

    finally是一个关键字。finally在处理异常机制的提供finally方法来执行一切操作,不管有没有异常捕捉或者抛出,finally都会执行操作,通常用于释放资源,关闭资源的操作。注:不要在fianlly中使用return、不要在finally中抛出异常。

    finalize()是Object中的protected方法,该方法是由垃圾收集器在确定这个对象没有被引用时调用,子类可以覆盖方法来实现对象从内存中清除出去之前做必要的清理工作。

    6、多态的条件?

    1. 有继承或者实现关系。
    2. 子类重写(实现)父类(父接口)的方法。
    3. 有父类或者父接口引用指向子类对象。

    7、多态中的成员访问

    1. 成员变量:编译看左边,运行看左边。
    2. 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
    3. 成员方法:编译看左边,运行看右边。
    4. 静态方法:编译看左边,运行看左边。

    8、Int和Integer有什么区别?

    • int则是java的一种基本数据类型,Integer是int的包装类。
    • int的默认值是0,而Integer的默认值是null。
    • int变量不需要实例化,Integer变量必须实例化才能使用。

    注:使用Integer时,对于-128到127之间的数,会进行缓存,Integer i1 = 127时,会将127进行缓存,下次再写Integer i2 = 127时,就会直接从缓存中取,不会新new一个Integer。

    9、String、StringBuffer、Stringbuilder有什么区别?

    • String字符串内容是不能被改变的。而StringBuffer和StringBuilder字符串对象可以直接进行修改。
    • StringBuffer类中的方法都添加了synchronized关键字,保证了线程安全,但效率低。
    • StringBuilder是在单线程环境下使用的,数据不安全,但它的效率也比StringBuffer要高。

    10、同步代码块、同步方法、静态同步方法分别锁什么?

    • 同步代码块:锁对象可以是任意对象。
    • 同步方法:锁对象是this。
    • 静态同步方法:锁对象是当前类的字节码文件对象,相当于该类的一个全局锁。

    11、什么是四大引用?区别是什么?

    • 强引用:就算是出现了内存泄漏也不会对该对象进行回收,因此强引用有时也是造成 Java 内存泄露的原因之一。
    • 软引用:只有当内存不足时垃圾回收器才会去清理这些对象
    • 弱引用:无论内存够不够,只要垃圾回收器启动,弱引用关联的对象肯定被回收。
    • 虚引用:虚引用关联对象在任何时候都有可能被垃圾回收器回收。设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。

    12、怎么实现深拷贝?

    clone方法是浅拷贝。序列化可以实现深拷贝

    13、谈谈接口和抽象的区别?

    • 抽象类要被子类继承,接口要被类实现。
    • 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
    • 接口所有的属性肯定是 public、static 和 final,所有的方法都是 abstract。
    • 接口可以继承接口,并且可多继承接口,但类只能单—继承。

    抽象类是对一组具有相同属性和方法共性功能的一种抽象,接口中定义的是该继承体系的扩展功能。

    集合

    1、Java中的集合类?

    Java中的集合类可以分为两大类:一类是实现 Collection 接口;另一类是实现 Map 接口。Collection有两个重要的子接口 List和 Set。

    List是Collection的子接口。特点:有序(存储顺序和取出顺序一致)、可重复。ArrayList:数组、LinkedList:链表。List特有遍历:size()和get() ListIterator 可以通过下标获取、增加、修改、删除下标对应的元素值。

    Set是Collection的子接口。特点:无序、唯一。Set常见子类有HashSet:哈希表 hashCode() 和 equals()、LinkedHashSet:哈希表和链表(存储和取出是一致)、TreeSet:排序、唯一。红黑树,Comparable Comparator 。

    Map子类:HashMap:哈希表;哈希表的作用是用来保证键的唯一性的、LinkedHashMap:哈希表和链表、Hashtable:线程安全。TreeMap:红黑树。

    2、对比Vector、ArrayList、LinkedList有什么区别?

    三个类都实现了List接口,都是有序集合,数据是允许重复的;

    • Vector基于数组实现存储的,使用synchronized同步方法,线程安全。
    • ArrayList基于数组实现存储的,线程不安全,性能高。插入修改删除慢,查询快。
    • LinkedList基于双向链表实现存储的,线程不安全,插入修改删除快,查询慢。

    3、对比HashTable、HashMap、TreeMap有什么不同?

    • HashTable:是早期哈希表实现,本身是同步的,不支持 null 键和值,由于同步带来的开销,导致性能低,基本不使用。
    • HashMap:基于哈希表。相比较HashTable不是同步的,支持 null 键和值等。通常情况下,HashMap 进行 put 或者 get 操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选。高并发使用 ConcurrentHashMap
    • LinkedHashMap:哈希表和链表。
    • TreeMap:基于红黑树的顺序访问的Map,具体顺序可以由指定的 Comparator 来决定,或者根据键的自然顺序来判断。

    4、如何保证集合是线程安全的?ConcurrentHashMap 如何实现高效的线程安全的?

    在传统集合框架内部,除了Hashtable等同步容器,还提供了同步包装容器(如Collections.synchronizedMap),利用了 this 作为互斥的mutex,但在高并发情况下,性能比较低下。

    更加普遍的选择是利用并发包提供的线程安全容器类:ConcurrentHashMap、CopyOnWriteArrayList、SynchronousQueue等等。

    ConcurrentHashMap通过分段锁的形式来实现高效的并发操作。
    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放哪一个分段中,然后对分段加锁,所以当多线程put的时候,只要不是放在一个分段这种,就实现了真正的并行插入。
    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

    泛型

    1、什么是泛型?

    泛型其实指的就是参数化类型,可以在编译期明确参数类型,防止运行期转换异常的问题。

    2、泛型-类型消除问题?

    Java泛型的类型参数的实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。集合类容纳的对象都是Object类的实例,一旦把一个对象置入集合类中,它的类信息将丢失。

    也就是说,集合类中容纳的都是指向Object类对象的引用。这样的设计是为了使集合类具有通用性,因为Object类是所有类的祖先,所以可以在这些集合中存放任何类而不受限制。

    3、泛型为什么不能为基本数据类型?

    Java中类的泛型会在编译期间经过泛型擦除的过程,当泛型类型被擦除后,退化成为默认类型也就是Object类型,然后Object类型是一个类,如int、double等这种基本类型并不是引用类型,其父类不是Object(它们本身也没有父类),因此泛型不能为基本数据类型。

    异常

    1、Exception和Error有什么区别?

    Exception和Error都继承自Throwable。

    Error:JVM本身的错误。错误不能被程序员通过代码处理,绝大部分的Error或导致程序崩溃,如OutOfMemoryError、StackOverflowError。

    Exception:代表程序运行时发生的各种不期望发生的事件。程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

    2、异常(Exception)分为哪两类?

    • 运行时异常 RuntimeException:NullPointerException、IndexOutOfBoundsException、ClassCastException。
    • 编译时异常:Exception 中除 RuntimeException及其子类之外的异常:IOException、ClassNotFoundException。

    IO

    1、IO流的分类?

    按流的数据单位分为:字节流 和 字符流。

    2、JAVA同步、异步、阻塞和非阻塞之间的区别?

    同步和异步描述的是消息通信的机制。

    • 同步:当一个request发送出去后,会得到一个response,哪怕response为空,但针对这一次请求而言就是一个同步的调用。
    • 异步:当一个request发送出去以后,没有得到想要的response,而是通过后面的callback、状态通知的方式获得结果。

    阻塞和非阻塞描述的是程序在等待调用结果时的状态。

    • 阻塞:调用方发出request的线程因为某种原因(如:等待系统资源)被服务方挂起,当服务方得到response后就唤醒挂起线程,并将response返回给调用方。
    • 非阻塞:调用方发出request的线程在没有等到结果时不会被挂起,通过select通知调用者,并且直到得到response后才返回。

    阻塞和非阻塞最大的区别就是看调用方线程是否会被挂起。

    3、同步阻塞IO、异步阻塞IO、同步非阻塞IO、异步非阻塞IO?

    • 同步阻塞IO:针对Sender而言,请求发送出去以后,一直等到Receiver有结果了才返回,这是同步。在Sender获取结果的期间一直被block住了,也就是在此期间Sender不能处理其它事情,这是阻塞。

    • 同步非阻塞IO:针对Sender而言,请求发送出去以后,立刻返回,然后再不停的发送请求,直到Receiver处理好结果后,最后一次发请求给Receiver才获得response。Sender一直在主动轮询,每一个请求都是同步的,整个过程也是同步的。在Sender等待Receiver的response期间一直是可以处理其它事情的(比如:可以发送请求询问结果),这是非阻塞。

    • 异步阻塞IO:针对Sender而言,请求发送出去以后,立刻返回,然后再等待Receiver的callback,这整个过程是异步。在Sender等待Receiver的callback期间一直被block住了,也就是在此期间Sender不能处理其它事情,这是阻塞。

    • 异步非阻塞IO:针对Sender而言,请求发送出去以后,立刻返回,然后再等待Receiver的callback,最后再次请求获取response,这整个过程是异步。在Sender等待Receiver的callback期间一直是可以处理其它事情的,这是非阻塞。

    4、Java提供了哪些IO方式?NIO如何实现多路复用的?

    • 同步阻塞IO(BIO):同步堵塞。一个连接一个线程。客户端有连接请求时服务器端就需要启动一个线程并处理,可以通过线程池机制改善。

    • 同步非阻塞IO(NIO):同步(多路复用)非阻塞。一个IO请求一个线程。即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。

    提供了Channel(通道)、Selector(NIO实现多路复用的基础,检测到注册在Selector上的多个Channel中是否有Channel处于就绪状态,进而实现了单线程对多Channel的高效管理。)、Buffer(高效的数据容器)

    • 异步非阻塞IO(AIO):异步非阻塞。一个有效请求一个线程。客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

    程序调用AIO的accept方法并传入Completionhandler,该方法是非阻塞方法。等数据准备完成后回调Completionhandler处理响应操作。

    注:select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

    5、简述下IO和NIO的区别?

    IO是面向流的,NIO是面向缓冲区的。

    IO的各种流是阻塞的。NIO的IO多路复用模型 通过系统调用select,单个线程不断的轮询select系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。

    6、Java有几种文件拷贝方式?哪一种最高效?

    第一种,使用java.io包下的库,使用FileInputStream读取,再使用FileOutputStream写出。需要调用函数 read()、write() ,由于内核指令的调用会使得当前用户线程切换到内核态,然后内核线程负责把相应的文件数据读取到内核的IO缓冲区,再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO操作。

    第二种,利用java.nio包下的库,使用transferTo或transfFrom方法实现。常说的零拷贝实现。利用现代操作系统底层机制,避免不必要拷贝和上下文切换,因此在性能上表现比较好。

    public static void copyFileByChannel(File source, File dest) throws IOException {
        try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
             FileChannel targetChannel = new FileOutputStream(dest).getChannel();){
            for (long count = sourceChannel.size() ;count>0 ;) {
                long transferred = sourceChannel.transferTo(sourceChannel.position(), count, targetChannel);          
                sourceChannel.position(sourceChannel.position() + transferred);
                count -= transferred;
            }
        }
     }
    

    第三种,Java 标准类库本身已经提供了 Files.copy 的实现。

    线程

    1、线程实现方式?

    1. 继承Thread类
    2. 实现Runnable接口
    3. 基于Callable实现
    4. 线程池

    2、说一下线程池七个参数的含义?

    corePoolSize:核心线程数。
    maximumPoolSize:最大线程数。
    keepAliveTime:空闲线程存活时间。
    TimeUnit:时间单位。
    BlockingQueue:线程池任务队列。
    ThreadFactory:创建线程的工厂。
    RejectedExecutionHandler:拒绝策略。

    3、ThreadLocal和Synchronized在实现同步的区别?

    • ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,隔离了多个线程对数据的数据共享。那就不存在多线程间共享的问题。
    • Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
    • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

    4、ThreadLocalMap为什么 key 要用弱引用?

    如果忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收,对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏。

    5、ThreadLocal变量为什么定义成private static的?

    private static修饰ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值。
    防止弱引用ThreadLocal被回收后,其对应的value没有被回收,从而导致内存泄露。

    动态代理

    1、动态代理?

    JDK动态代理:利用反射机制。CGLIB动态代理:利用asm开源包,将代理对象类的class文件加载进来,通过修改其字节码动态生成子类来处理。

    2、动态代理是基于什么原理?

    JDK的动态代理基于Java反射机制实现;cglib的动态代理基于ASM机制实现,将代理对象类的class文件加载进来,通过修改其字节码动态生成子类来处理。

    JVM

    1、JVM是如何执行new一个对象的?

    • 类加载器:把class字节码文件加载到内存,并对数据进行检验、转换解析和初始化,最终形成可以被虚拟机直接使用的类对象。
    • 程序计数器
    • 栈:Java 虚拟机栈、本地方法栈
    • 堆:用来存储对象。
    • 方法区:方法区和堆一样,是线程共享的区域,它用来存储类信息、静态变量、常量,以及字节码等。
    • GC垃圾回收器:引用计数算法。给每一个对象分配一个计数器,根据计数器数值来清除。

    2、GC垃圾回收?

    • 垃圾判断算法:
      • 引用计数法:通过判断对象的引用数量判断对象是否可以被回收 无法检查循环引用
      • 可达性分析法:通过判断对象的引用链是否可达来决定对象是否可以被回收
    • 垃圾回收算法:
      • 标记-清除算法:第一个阶段先标记,看看哪些对象可以是垃圾,第二个阶段是清除,把垃圾对象所占用的空间释放。优点:速度快,缺点:容易产生内存碎片。
      • 标记-整理算法:第一个阶段先标记,看看哪些对象可以是垃圾,第二个阶段是整理,清理过程中,将可用的对象的内存前移,使内存变得更紧凑,防止碎片产生。优点:避免了内存碎片,缺点:速度慢。
      • 复制算法:新生代的内存区域又分成Eden、From、To三个区域,垃圾回收时,将From区标记不是垃圾的对象复制到To区,From区对象全部回收,再将From区和To区交换。优点:不会产生内存碎片,缺点:占用双倍的内存空间。
      • 分代收集算法:年轻代:存活率低,使用复制算法。老年代:存活率高,区域大,标记清除(内存碎片不太多就清除)+标记压缩混合实现。
    • 垃圾回收器:Serial 回收器、CMS 回收器、G1

    这里可能会有一个小误区,释放是不是意味着把整个内存每个字节进行一个清零操作呢,注意:不会,它只需要把对象
    所占用内存的起始、结束的地址给记录下来,放在一个空闲的地址列表里就可以了,下次再分配新对象的时候就到空闲
    地址列表中去找看有没有一块足够的空间容纳新对象,如果有,那就进行一个内存分配,并不会把可以作为垃圾的对象
    占用的内存做一个清零的处理。

    参考文章:
    垃圾回收之垃圾回收算法

    3、GC垃圾回收器作用区域?常用的回收算法?

    堆(新生区、老年区)。

    分代收集算法:

    4、ClassLoader双亲委托机制?

    当一个类加载的过程中,它首先不会去加载,而是委托给自己的父类去加载,后父类自己无法完成这个加载请求,子加载器才会尝试自己去加载。 Bootstrap Classloader > Extention ClassLoader > Application ClassLoader > Custom ClassLoader

    通过这种层次模型,可以避免类的重复加载,也可以避免核心类被不同的类加载器加载到内存中造成冲突和混乱,从而保证了Java核心库的安全。

    5、如何自定义类加载器?

    继承ClassLoader,重写findClass,并生成字节数组,然后获取的字节数组通过defineClass()生成了Class对象。打破双亲委托机制。

    6、ClassLoader的应用场景?

    1. 依赖冲突
    2. 热加载
    3. 热部署
    4. 加密保护

    7、JVM调优?

    8、内存泄漏,怎么定位?

    9、Object o= new Object()在内存中占多少字节?

    对象包括:

    • 对象头:
      • Mark word:存储对象本身运行时信息 hashcode gc状态 锁状态 偏向锁id 它是实现轻量级锁和偏向锁的关键。
      • 类型指针
      • 数组长度
    • 实例数据:存放类的属性数据信息,包括父类的属性信息。
    • 对齐填充

    markWord 64位占8字节,class pointer开启压缩占4字节,关闭压缩占8字节。也就是对象头默认占12字节。
    Object o= new Object() 空对象,实例数据为0,而虚拟机默认占用字节要为8的整数倍,则对齐填充4,大小为12 + 4 =16字节。

    JMM

    1、JMM三大特性?

    原子性,可见性,有序性。

    2、什么是自旋锁?

    自旋锁的实现基础是CAS算法机制。CAS自旋锁属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。缺点:CPU开销较大。不能保证代码块的原子性。 AtomicInteger

    3、synchronized和volatile的区别?

    • volatile主要是保证内存的可见性,即变量在寄存器中的内存是不确定的,需要从主存中读取。synchronized主要是解决多个线程访问资源的同步性。
    • volatile 作用于变量,synchronized作用于代码块或者方法。
    • volatile 仅可以保证数据的可见性,不能保证数据的原子性。synchronized可以保证数据的可见性和原子性。
    • volatile 不会造成线程的阻塞,synchronized会造成线程的阻塞。

    4、synchronized和Lock的区别?

    • synchronized是Java语法的一个关键字,加锁的过程是在JVM底层进行。Lock是一个类,是JDK应用层面的。
    • synchronized在加锁和解锁操作上都是自动完成的,Lock锁需要我们手动加锁和解锁。
    • synchronized能修饰方法和代码块,Lock锁只能锁住代码块。
    • Lock锁有丰富的API,可根据不同的场景,在使用上更加灵活

    5、JDK1.6对synchronized做了哪些优化?

    无锁->偏向锁->轻量级锁->重量级锁

    6、ConcurrentHashMap

    通过分段锁的形式来实现高效的并发操作。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放哪一个分段中,然后对分段加锁,所以当多线程put的时候,只要不是放在一个分段这种,就实现了真正的并行插入。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

    杂项

    1、谈谈你对Java平台的理解?

    2、谈谈你知道的设计模式?

    📌代理模式:代理类与委托类之间通常会存在关联关系,我们在访问实际对象时,是通过代理对象来访问的。代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。代理类提供附加功能。

    📌装饰模式:动态地给一个对象增加一些额外的职责。就扩张功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。维护一个指向抽象构建对象的引用,通过该引用可以调用装饰之前构建对象的方法,并通过其子类扩展该方法,已达到装饰的目的。

    装饰器模式强调的是增强自身,增强过后还是你,只不过能力变强了。代理模式中最终都是通过代理类对象来访问。

    代理模式应用有Spring AOP和Spring 事务等;装饰模式应用IO类,Spring过滤器也会用到装饰模式,可以链式调用。

    📌模板方法模式:模板模式一般只针对一套算法,注重对同一个算法的不同细节进行抽象提供不同的实现。

    3、删除语句delete、truncate的区别?

    1. delete 是DML语句,truncate 是DDL语句。
    2. delete 支持事务,可以进行事务回滚rollback;truncate 不支持事务,不能执行事务回滚,删除数据不能恢复。
    3. delete 支持带条件的删除,可以只删除某一条数据;truncate 则不行,只能用于删除表中的所有数据。
    4. delete 删除不会重置自动增长(auto_increment),truncate 则会重置自动增长的值,重新以 1 开始。
    5. delete 可以触发触发器,truncate 则不行。
    6. delete 不会释放空间,truncate 会释放空间。用delete删除一张10M的表,空间不会释放,而truncate会。所以当确定表不再使用,应truncate。
    7. delete 逐条删除表行数据,truncate 释放存储表数据所用的数据页来删除数据。 delete 删除数据的速度慢,truncate 删除数据的速度快。

    4、drop、truncate、delete的区别

    1. drop 直接将表都删除掉,表的结构也会删除。
    2. truncate 删除表中的数据,再插入数据的时候自增长id又从1开始,在清空表中数据的时候使用。
    3. truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。
    4. truncate 和 drop 属于 DDL语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。
    5. 执行速度:drop > truncate > delete
  • 相关阅读:
    C# Winform无边框窗体实现界面拖动
    web安全学习笔记【17】——信息打点(7)
    警惕国外科技断供风险:CACTER邮件网关信创一体机为商业银行提供全国产化防护
    设计模式-创建型-04-建造者模式
    笔记本电脑切换Fn功能键
    使用Fiddler如何抓取手机上的包
    uniapp实现图片上传——支持APP、微信小程序
    爬虫学习(15):selenium自动化测试(四):截屏、弹出框和下拉框
    Linux项目自动化构建工具 -make/makefile
    想你所想,华为云桌面Workspace助你轻松办公
  • 原文地址:https://blog.csdn.net/weixin_40017062/article/details/126941436