招银网络 一面

2023-10-25 09:53:24 浏览数 (1)

大家好,我是老田

前段时间,一位同学给我反馈的招银网络一面面经,今天来写一下参考答案。

说说你对多态的理解

Java多态是指同一个方法名可以被不同的对象调用,并且根据对象的不同,会自动选择合适的方法进行调用。多态是面向对象编程的重要特性之一。

在Java中,多态实现的关键是继承和方法重写。当一个子类继承自父类,并重写了父类的方法时,可以通过父类的引用指向子类的对象,并调用重写后的方法。这样,无论父类引用指向的是父类对象还是子类对象,都可以调用到合适的方法,实现多态。

多态的好处在于增加了代码的灵活性和可扩展性。通过多态,可以编写出更通用、更灵活的代码,减少了代码的重复编写。当需要新增一个子类时,只需要继承父类并重写相应的方法即可,而不需要修改已有的代码。

另外,多态还可以实现接口的统一调用。当多个类实现了同一个接口时,可以使用接口的引用指向不同的实现类对象,通过接口调用方法,实现了对不同类对象的统一调用。

总结来说,Java多态是通过继承和方法重写实现的,可以提高代码的灵活性和可扩展性,实现了对不同类对象的统一调用。

用java手写生产者消费者

生产者消费者模型是一种解决多线程间数据交换的经典模式。在该模型中,生产者负责生产数据并放入共享的缓冲区中,而消费者则负责从缓冲区中取出数据进行消费。

以下是使用Java手写生产者消费者模型的示例:

代码语言:javascript复制
import java.util.LinkedList;
/**
 * @author tianwc  公众号:java后端技术全栈、面试专栏
 * @version 1.0.0
 * 博客地址:<a href="http://woaijava.cc/">博客地址</a>
 * 生产者与消费者案例
 */
public class ProducerConsumer {

    private LinkedList<Integer> buffer = new LinkedList<>();
    private int capacity = 5;

    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (this) {
                while (buffer.size() == capacity) {
                    wait();
                }
                System.out.println("Producer produced: "   value);
                buffer.add(value  );
                notify();
                Thread.sleep(1000);
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (buffer.isEmpty()) {
                    wait();
                }
                int value = buffer.removeFirst();
                System.out.println("Consumer consumed: "   value);
                notify();
                Thread.sleep(1000);
            }
        }
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Thread producerThread = new Thread(() -> {
            try {
                pc.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                pc.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}

在这个例子中,我们使用了一个LinkedList作为共享的缓冲区,使用synchronized关键字保证了线程安全。

生产者的produce方法不断地往缓冲区中添加数据,如果缓冲区已满,则调用wait()方法将生产者线程阻塞,等待消费者消费数据后唤醒。在往缓冲区中添加数据后,调用notify()方法唤醒消费者线程。

消费者的consume方法不断地从缓冲区中取出数据进行消费,如果缓冲区为空,则调用wait()方法将消费者线程阻塞,等待生产者生产数据后唤醒。在消费完数据后,调用notify()方法唤醒生产者线程。

在生产者和消费者模式中我们应该注意以下几点:

  1. 生产者和消费者必须共享同一个缓冲区,以实现数据交换。
  2. 使用synchronized关键字确保对缓冲区的访问是线程安全的,避免出现竞态条件。
  3. 使用wait()和notify()方法进行线程间的通信,保证生产者和消费者能够正确地阻塞和唤醒。
  4. 在生产者和消费者的方法中使用while循环判断条件,而不是使用if语句,以避免虚假唤醒的问题。
  5. 在生产者和消费者的方法中使用sleep()方法进行简单的模拟,以便观察效果。在实际应用中,可以根据需求进行相应的处理。
SQL查询语句执行流程

在MySQL中,一条查询SQL的执行流程可以分为以下几个步骤:

  1. 语法解析:MySQL首先对查询SQL进行语法解析,检查SQL语句的语法是否正确。如果SQL语句存在语法错误,MySQL将返回相应的错误信息。
  2. 语义解析:语法解析通过后,MySQL会对查询SQL进行语义解析,即根据SQL语句的语法结构和语义规则,确定查询的执行计划。在这个步骤中,MySQL会检查表、列的存在性,以及对应的权限等。
  3. 查询优化器:在语义解析之后,MySQL会通过查询优化器对查询进行优化,以确定最佳的执行计划。查询优化器会考虑多种因素,如索引的使用、连接的顺序、子查询的优化等。优化器会生成多个可能的执行计划,并通过成本估算器来评估每个执行计划的代价,选择代价最低的执行计划。
  4. 执行计划生成:查询优化器确定最佳的执行计划后,MySQL会根据该执行计划生成实际的执行代码。执行计划包括了具体的操作,如表的扫描、索引的使用、连接、过滤条件的判断等。
  5. 执行查询:生成执行计划后,MySQL会执行查询操作。执行查询的过程中,MySQL会根据执行计划逐步执行各个操作,并将结果存储在临时表或内存中。
  6. 结果返回:查询执行完毕后,MySQL会将查询结果返回给客户端。如果查询结果较大,MySQL可能会使用分页等技术将结果分批返回。

需要注意的是,以上步骤是一个大致的流程,实际执行过程中可能会有一些细微的差别。此外,MySQL还有一些高级特性,如并发控制、事务处理等,也会影响查询的执行流程。

Mybatis用到了哪些设计模式?

Mybatis框架中使用了以下设计模式:

  1. 生成器模式(Builder Pattern):Mybatis使用了构建器模式来构建SqlSessionFactory。SqlSessionFactoryBuilder类提供了一系列方法来配置Mybatis并最终构建SqlSessionFactory实例。
  2. 代理模式(Proxy Pattern):Mybatis使用了动态代理来生成Mapper接口的实现类。MapperProxy类实现了InvocationHandler接口,并在invoke方法中执行SQL语句。
  3. 工厂模式(Factory Pattern):Mybatis使用了工厂模式来创建不同的对象。例如,SqlSessionFactoryBuilder类用于创建SqlSessionFactory实例,SqlSessionFactory类用于创建SqlSession实例。
  4. 注解模式(Annotation Pattern):Mybatis使用了注解来映射SQL语句和Java方法。例如,@Select、@Insert、@Update和@Delete等注解用于标记方法并指定对应的SQL语句。
  5. 模板模式(Template Pattern):Mybatis使用了模板模式来定义了一些基本操作的算法骨架,具体的实现由用户进行定制。例如,BaseExecutor类定义了数据库操作的基本流程,而具体的执行逻辑由子类实现。
  6. 单例模式(Singleton Pattern):Mybatis中的SqlSessionFactory和MapperRegistry等类都采用了单例模式来保证全局唯一的实例。
  7. 装饰器模式(Decorator Pattern):Mybatis使用了装饰器模式来增强对象的功能。例如,Executor接口的实现类可以通过装饰器模式来增加缓存、日志等功能。

总结:Mybatis框架中使用了生成器模式、代理模式、工厂模式、注解模式、模板模式、单例模式和装饰器模式等多种设计模式。这些设计模式使得Mybatis具有灵活、可扩展和可定制的特性。

HashMap,HashTable,ConcurrentHashmap区别

三者区别如下:

  1. 线程安全性
    • HashMap:非线程安全,多线程环境下需要自行保证同步。
    • HashTable:线程安全,使用synchronized关键字保证线程安全。
    • ConcurrentHashMap:线程安全,使用了锁分段技术(Segment),每个Segment相当于一个小的HashTable,不同的Segment可以被不同的线程同时访问,从而提高并发性能。
  2. 锁粒度
    • HashMap:无锁,不保证线程安全,适用于单线程环境。
    • HashTable:使用全局锁,对整个HashTable进行加锁,性能较差。
    • ConcurrentHashMap:使用锁分段技术,每个Segment都有一个独立的锁,不同的线程可以同时访问不同的Segment,提高了并发性能。
  3. 数据结构
    • HashMap:数组 链表 红黑树,当链表长度超过阈值(默认为8)时,链表转换为红黑树,提高查询效率。
    • HashTable:数组 链表,没有红黑树优化。
    • ConcurrentHashMap:数组 链表 红黑树,与HashMap类似,但每个Segment都是一个小的HashTable,Segment内部结构与HashMap相同。
  4. 扩容机制
    • HashMap:当元素个数超过容量的75%时,进行扩容,扩容为原来的两倍。
    • HashTable:当元素个数超过容量的75%时,进行扩容,扩容为原来的两倍加一,并且要求扩容后的容量是一个素数。
    • ConcurrentHashMap:当元素个数超过容量的75%时,进行扩容,扩容为原来的两倍。

总结:HashMap是非线程安全的,HashTable是线程安全的但性能较差,ConcurrentHashMap是线程安全且并发性能较好的。在多线程环境下,推荐使用ConcurrentHashMap,而在单线程环境下,推荐使用HashMap。

内存泄漏的情况有哪些?

以下是一些可能会导致内存泄漏的场景:

  1. 对象的生命周期超出了其实际使用范围:如果创建的对象没有被正确释放或销毁,它们将继续占用内存直到应用程序结束。
  2. 长生命周期的对象引用短生命周期的对象:如果长生命周期的对象持有对短生命周期对象的引用,即使短生命周期对象不再使用,它们也不会被垃圾收集器回收。
  3. 集合类对象的使用不当:如果集合类对象(如List、Map等)在使用过程中没有正确地清除不再需要的元素,这些元素将继续占用内存。
  4. 资源未正确关闭:如果在使用资源(如文件、数据库连接、网络连接等)后没有正确关闭它们,将导致资源泄漏。
  5. 缓存使用不当:如果缓存对象没有正确管理,即没有删除过期的缓存或缓存的大小没有限制,将导致内存泄漏。
  6. 事件监听器未正确移除:如果注册的事件监听器没有在不再需要时正确地移除,它们会继续保持对对象的引用,导致内存泄漏。
  7. 多线程内存泄漏:如果多线程中的线程没有正确地结束或资源没有正确释放,将导致内存泄漏。

以上只是一些常见的场景,实际上内存泄漏可能发生在许多其他情况下。为了避免内存泄漏,我们应该注意正确地管理对象的生命周期,及时释放不再需要的资源,并确保使用合适的数据结构和算法来优化内存使用。

classpath,path的区别

在Java项目中,classpathpath是两个不同的概念。

  1. classpathclasspath是指Java程序在运行时搜索类、接口和资源文件的路径。当Java程序需要加载类或资源时,它会在classpath下搜索相应的文件。classpath可以包含多个路径,路径之间使用分隔符(如Windows下使用分号,Unix/Linux下使用冒号)分隔。classpath可以设置为文件系统中的目录或JAR文件。
  2. pathpath是操作系统环境变量,用于指定系统在命令行下搜索可执行文件的路径。当在命令行中输入一个可执行文件名时,操作系统会在path路径下搜索相应的可执行文件。path也可以包含多个路径,路径之间使用与classpath相同的分隔符。

区别:

  • classpath是Java程序在运行时搜索类和资源文件的路径,而path是操作系统在命令行下搜索可执行文件的路径。
  • classpath用于Java程序,而path用于操作系统。
  • classpath可以包含多个路径,路径之间使用分隔符分隔,而path也可以包含多个路径,路径之间使用与classpath相同的分隔符。
  • classpath可以设置为文件系统中的目录或JAR文件,而path指定的是可执行文件的路径。

总结:classpath和path是两个不同的概念,用于不同的目的,但它们的用法和设置方式相似。

JVM运行过程

JVM(Java虚拟机)是Java程序的运行环境,它可以解释执行Java字节码,并提供了内存管理、垃圾回收、线程管理等功能。

JVM的运行过程可以分为以下几个步骤:

  1. 加载:JVM会通过类加载器加载Java字节码文件(.class文件)到内存中。类加载器会根据类的全限定名找到对应的字节码文件,并将字节码文件解析成JVM能够理解的内部数据结构。
  2. 验证:在加载完字节码文件后,JVM会对它进行验证,以确保字节码的安全性和正确性。验证过程包括类型检查、字节码验证、符号引用验证等。
  3. 准备:在准备阶段,JVM会为类的静态变量分配内存,并初始化为默认值。例如,对于整型变量,JVM会将其初始化为0。
  4. 解析:在解析阶段,JVM会将类、接口、字段和方法的符号引用解析为直接引用。符号引用是一种能够唯一标识类、接口、字段和方法的描述符,而直接引用则是指向内存中实际对象的指针。
  5. 初始化:在初始化阶段,JVM会执行类的静态初始化代码。静态初始化代码块会按照顺序执行,并初始化静态变量。如果一个类有父类,那么会先初始化父类,再初始化子类。
  6. 执行:在执行阶段,JVM会按照字节码的指令序列一条一条地执行。字节码指令包括加载、存储、运算、跳转、方法调用等操作。JVM会维护一个栈帧栈来保存方法的局部变量、操作数栈和方法返回值。
  7. 垃圾回收:JVM会定期进行垃圾回收,释放不再使用的内存。垃圾回收器会标记和清理不再使用的对象,并将它们的内存回收。
  8. 异常处理:在执行过程中,如果发生异常,JVM会根据异常处理表(Exception Table)来查找对应的异常处理代码,并执行相应的异常处理逻辑。
  9. 程序结束:当程序执行完毕或遇到System.exit()方法时,JVM会终止程序的执行,并释放所有资源。

总之,JVM的运行过程包括加载、验证、准备、解析、初始化、执行、垃圾回收、异常处理和程序结束等阶段。这些阶段相互协作,使得Java程序能够在JVM上运行。

讲一讲GC机制

JVM(Java虚拟机)的垃圾回收(Garbage Collection)机制是自动管理和释放不再使用的内存对象。它通过检测不再被引用的对象,并将其回收来优化内存的使用。

VM的GC机制有多种实现方式(垃圾收集算法),其中比较常见的有以下几种:

  1. 标记-清除算法Mark-Sweep):标记阶段和垃圾回收阶段分开进行。首先标记出所有的活动对象,然后清除所有的垃圾对象。这种算法的缺点是会产生内存碎片。
  2. 复制算法Copying):将堆内存分为两个相等的部分,每次只使用其中一部分。当垃圾回收时,将活动对象复制到另一部分,并清除原来的部分。这种算法的优点是不会产生内存碎片,但缺点是只能使用堆内存的一半。
  3. 标记-整理算法Mark-Compact):标记阶段和整理阶段一起进行。首先标记出所有的活动对象,然后将活动对象向一端移动,然后清除剩余的内存空间。这种算法可以解决内存碎片问题。
  4. 分代算法Generational):根据对象的生命周期将堆内存划分为不同的区域,每个区域使用不同的垃圾回收算法。一般将新创建的对象放入年轻代,使用复制算法进行回收;将存活时间较长的对象放入老年代,使用标记-整理算法进行回收。

总的来说,JVM的垃圾回收机制是通过标记不再被引用的对象,并将其清除或整理来释放内存空间。不同的GC算法和策略可以根据应用程序的需求进行选择和调优。

阻塞队列
start()方法和run()方法的区别,可以多次调用start()方法吗?

Thread的start()方法用于启动一个新线程,并JVM会回调线程的run()方法。当调用start()方法时,会创建一个新的线程,并在新的线程中执行run()方法。

而run()方法是Thread类中定义的一个普通方法,用于线程的具体执行逻辑。

可以多次调用start()方法吗?不可以,调用start()方法会启动一个新的线程,如果多次调用start()方法,会抛出IllegalThreadStateException异常。每个线程只能启动一次,多次调用会导致不可预料的结果。如果要多次执行线程的逻辑,可以将线程的逻辑封装到一个方法中,然后通过多次调用该方法来实现。

说说线程池原理

Java线程池是一种管理和复用线程的机制,它能够提高程序的效率和性能。线程池中包含一个线程队列和一些管理线程的组件,当需要执行任务时,可以从线程池中获取一个空闲线程来执行任务。

Java线程池的原理如下:

  1. 创建线程池:通过ThreadPoolExecutor类创建线程池,可以指定线程池的大小、任务队列和拒绝策略等参数。
  2. 提交任务:将任务提交给线程池,线程池会根据自身的状态和参数来决定是直接执行任务、放入队列或者拒绝任务。
  3. 执行任务:线程池中的线程从任务队列中获取任务,并执行任务。
  4. 线程复用:当线程执行完任务后,并不会立即销毁,而是会继续等待执行新的任务,以复用线程,减少线程的创建和销毁开销。
  5. 线程管理:线程池会管理线程的状态、数量和生命周期。可以根据需要动态调整线程池的大小,增加或减少线程的数量。
  6. 任务队列:线程池中的任务队列用于存储待执行的任务,可以是有界队列或无界队列,根据不同的需求选择不同的队列实现。
  7. 拒绝策略:当任务队列已满并且线程池中的线程数达到最大值时,新提交的任务会被拒绝执行。可以通过设置拒绝策略来处理这种情况,如抛出异常、丢弃任务或者调用者自己执行任务。

通过使用线程池,可以减少线程的创建和销毁开销,提高线程的复用性,避免线程数量过多导致系统资源耗尽的问题,从而提高程序的性能和效率。

代理模式和装饰器模式有什么区别?

代理模式和装饰器模式是两种不同的设计模式,虽然它们有一些共同的特点,但是在使用方式和实现上有一些区别。

区别如下:

目的不同 :代理模式的主要目的是为了控制对对象的访问,而装饰器模式的主要目的是为了给对象添加额外的功能。

关注点不同 :代理模式关注于对对象的访问进行控制和管理,装饰器模式关注于对对象的功能进行增强。

涉及的类不同:代理模式通常涉及到三个角色,即接口、代理类和被代理类,而装饰器模式通常只涉及一个接口和多个装饰器类。

功能增强方式不同:代理模式通过在代理类中调用被代理类的方法实现功能增强,而装饰器模式通过在装饰器类中调用被装饰对象的方法,并在其前后添加额外的功能实现功能增强。

相关面经:

16k面试中的10个问题

猫眼 面经和答案

顺丰科技面试

合奥科技 面经

途虎 面经,其实挺简单的!

推荐

MySQL 开发规范,非常详细,建议收藏!

16k面试中的10个问题

从0开始搭建公司技术栈,yyds

简历写成这样,CTO会主动联系你

0 人点赞