某小厂java后端初面,记录一下

好吧,,,,,本人很菜,再接再励吧,继续刷。简单记录一下面试题,未亡羊补牢呗。
1.lift join ;inner join ;right join 的区别
2. union 和union all的区别
3.like查询会走索引吗?
4.主键和索引的区别
5.count(*),count(1),count(ID)的区别
6.exists了解吗?作用是什么
7.线程池了解吗?
8.spring创建的bean是单例模式还是多例模式,怎么区分?
9.国产数据库了解吗?
10.hashmap和hashTable的区别
11.list是有序的吗?
12.如果一个程序中有个方法响应很慢,你会怎么排查?说说你的思路
13.你有没有什么问题要问我的?没有,好吧,我给你介绍一下我们做的事情,巴拉巴拉…

1.lift join ;inner join ;right join 的区别

INNER JOIN(内连接): 只返回两个表中匹配的行,即只返回连接条件为真的行。
如果一个表中的行在另一个表中没有匹配的行,那么这些行不会出现在结果中。 INNER JOIN返回的是两个表的交集部分。 LEFT
JOIN(左连接): 返回左边表中的所有行,以及与右边表匹配的行。 如果右边表中没有与左边表匹配的行,则返回NULL。 LEFT
JOIN以左表为基础,返回左表的所有记录,以及右表中与左表匹配的记录。如果右表中没有匹配项,则结果中对应字段的值将为NULL。 RIGHT
JOIN(右连接): 返回右边表中的所有行,以及与左边表匹配的行。 如果左边表中没有与右边表匹配的行,则返回NULL。 RIGHT
JOIN以右表为基础,返回右表的所有记录,以及左表中与右表匹配的记录。如果左表中没有匹配项,则结果中对应字段的值将为NULL。

2. union 和union all的区别

1.对结果集的处理方式:Union会对两个或多个结果集进行并集操作,并自动压缩掉其中的重复行,只显示唯一的值,相当于执行了distinct操作。而Union All也会进行并集操作,但它会包括所有的结果,无论是否重复,即所有的结果全部显示。
2.执行效率:由于Union需要对结果集进行去重操作,而Union All则不需要,因此在处理大量数据时,Union All的执行效率通常会比Union高。当可以确认合并的两个结果集中不包含重复数据且不需要排序时,使用Union All会更合适。
3. 是否排序:Union会按照字段的顺序对结果集进行排序,而Union All只是简单地将两个结果合并后就返回,不会对结果进行排序。

3.like查询会走索引吗?

LIKE查询在一定条件下是可以走索引的,关键在于查询的具体形式以及数据库系统的实际处理方式。为了确保LIKE查询能够有效利用索引,最佳实践是尽量避免在搜索词首使用通配符,并确保为常作为LIKE查询条件的列创建合适的索引。
在实际应用中,为了确定LIKE查询是否使用了索引,可以使用数据库的执行计划工具来查看查询的执行计划。这些工具可以显示查询是如何使用索引的,以及是否进行了全表扫描。

4.主键和索引的区别

主键是用于确保数据唯一性的关键约束,同时也自带了一个隐含的唯一索引;而索引则是为了提高数据检索效率的辅助结构,它并不直接涉及数据完整性约束,但能极大提高查询性能。

5.count(*),count(1),count(ID)的区别

count(*):计算结果集中所有行数,包括那些含有null值的行; 它只是简单计算行数,并不关心里面的数据 count(1):
功能上与count(*)类似,也是计算所有行数。
count(ID):计算列(ID)中非null的值的数量。如果ID列中有null的值,这些值不会计算在总数中。由于COUNT(ID)会检查ID中的值是否为null,所以查询效率相对低一些。

6.exists了解吗?作用是什么

EXISTS 是一个重要的关键字,它的主要作用是用来检验一个子查询是否至少会返回一行数据。EXISTS 并不关注子查询返回的具体数据内容,而是关心是否存在满足子查询条件的结果行。
具体作用如下:
结果判断:EXISTS 子查询的结果是一个布尔值 (TRUE 或 FALSE)。如果子查询的结果集中包含至少一行数据,那么 EXISTS
子查询的结果就是 TRUE;若子查询没有返回任何数据,则结果是 FALSE。

过滤行:在主查询中配合 WHERE 子句使用 EXISTS 时,可以用来过滤主查询的结果集。只有当 EXISTS 子查询的结果为 TRUE时,对应的主查询中的行才会被选择出来。

例如,下面是一个使用 EXISTS 的 SQL 查询语句示例:

Sql SELECT column1, column2 FROM table1 WHERE EXISTS (
     SELECT 1 FROM table2 WHERE 
     table2.columnA = table1.columnA 
     AND table2.columnB = 'some_value' ); 

在这个查询中,将从 table1 中选择那些在 table2 中具有相同 columnA 值并且
columnB 为 ‘some_value’ 的行。如果对于 table1中的任何一行,子查询能找到匹配的记录,那么这一行就会被包含在最终的结果集中。

相对于 IN 子查询,EXISTS 在某些情况下可能具有更好的性能表现,特别是在主表较小而子查询结果集较大的场景下,因为 EXISTS
只需找到匹配的第一行即可得出结论。而 IN 子查询通常需要构造完整的子查询结果集并进行比较。此外,EXISTS 更适用于检查相关性而非具体值

7.线程池了解吗?(高频问题好吧)

我的回答是不了解,惭愧惭愧
后面几个明天再总结,晚安,玛卡巴卡
3月16号,继续吧。
好消息是面过了,两年半经验给的是中级开发的职称。
这里插一句:高并发和多线程基本必问,当然我用到的比较少,后续会专门学习这一块的内容,并在博客上记录下来。

线程池是一种多线程处理形式,它预先创建了一定数量的线程保存在池中,当有任务提交时,从线程池中取出一个空闲线程来执行任务,而不是每次都新建线程。完成任务后,线程并不会被销毁而是回到线程池中等待下一个任务。这样做的好处非常多,主要体现在以下几个方面:
线程池的作用:

  1. 资源复用减少了线程创建和销毁的开销,因为创建和销毁线程是一个相对昂贵的操作,涉及到系统资源的分配与回收。
  2. 提高响应速度:由于线程已经存在并且随时准备执行任务,所以任务到来时可以直接分派给线程执行,无需等待线程创建。
  3. 线程管理:线程池可以限制系统内并发执行的任务数量,从而避免了因大量并发线程导致的系统资源耗尽问题,增强了系统的稳定性和可控性。
  4. 易于控制和调度:线程池提供了诸如任务队列、拒绝策略、线程生命周期管理和定时/周期性执行任务等功能,便于开发者更好地控制并发行为。

应用场景:

  • 高并发服务:例如网络服务器处理请求数据库连接池中的并发查询操作,或者批量处理后台任务。
  • 定时任务:使用ScheduledThreadPoolExecutor可以方便地实现定时任务或定期执行的任务。
  • 批处理作业:一次性处理大量数据时,可以通过线程池分解任务,将大任务拆分为小任务并发执行。
  • 异步处理:在需要快速响应用户的场景下,主线程可以将耗时较长的操作交给线程池去异步执行,自己继续处理其他逻辑。

注意事项:

  1. 线程池大小设置:应合理设置线程池的核心线程数和最大线程数,过大可能导致系统资源耗尽,过小则无法充分利用CPU资源。
  2. 任务排队策略:线程池通常包含任务队列,不同的队列类型(如FIFO、优先级队列等)会影响任务执行顺序和性能。
  3. 拒绝策略:当线程池饱和,即所有线程都在工作并且任务队列已满时,需要定义拒绝新任务的策略,比如直接丢弃、抛出异常或替换旧任务等。
  4. 线程安全性:线程池中的线程共享资源时,需要注意并发访问的同步控制,防止数据竞争和不一致问题。
  5. 线程池关闭:使用完毕后需正确关闭线程池,确保所有任务完成并释放系统资源。

具体在Java中,常用的线程池实现包括:

  • ThreadPoolExecutor:这是最通用的线程池实现类,可以根据需求自定义各种参数。
  • Executors 工具类提供的便捷方法:如 newFixedThreadPool(固定大小线程池)、newCachedThreadPool(可伸缩线程池)、newSingleThreadExecutor(单线程线程池)和
    newScheduledThreadPool(支持定时任务的线程池)。
    在Java中,虽然底层具体的线程池实现是由 ThreadPoolExecutor 类完成的,但为了简化使用,Java标准库中的 java.util.concurrent.Executors 类提供了一系列工厂方法来创建不同类型的线程池。以下是其中常用的四种线程池实现:
  1. FixedThreadPool (定长线程池): java ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads);
    这个线程池的大小是固定的,一旦创建,池中的线程数始终保持不变。当提交的任务数超过线程池中的线程数时,多余的任务将在任务队列中等待,直到有线程空闲出来。适用于负载较重且任务执行时间相对较短的情况,可以有效地控制并发线程的数量,防止过多线程消耗系统资源。

  2. CachedThreadPool (可缓存线程池): java ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    这种线程池会根据需要创建新线程,而且在线程空闲一段时间后会被自动回收。这意味着它的线程数量可以动态调整,适合处理大量短生命周期的任务,因为不需要预先设定线程数目的上限,可以高效利用系统资源。

  3. ScheduledThreadPool (定时任务线程池): java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(corePoolSize);
    这种线程池主要用于执行定时任务或周期性任务,它可以调度延时执行的任务,也可以执行定期执行的任务。核心线程数可以在创建时指定,非核心线程空闲时也会被回收。

  4. SingleThreadExecutor (单线程线程池): java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    单线程线程池内部只有一个工作线程,所有的任务都会在这个唯一的线程上按顺序执行,因此不会有并发执行的情况,保证了所有任务的执行顺序。适用于需要保证任务顺序执行或者环境不允许并发执行的情况。

以上四个线程池都是基于 ThreadPoolExecutor 构建的,只是通过 Executors
提供了更简洁的创建方式,并针对特定场景进行了预设配置。在实际应用中,需要根据业务需求选择合适的线程池类型,并可能需要进一步定制其属性,如线程存活时间、任务拒绝策略等。

8.spring创建的bean是单例模式还是多例模式,怎么区分?

Spring 创建的 Bean,默认情况下是单例模式。这意味着,对于同一个 Spring 容器中的某个 Bean 类型,Spring 只会创建一个实例,并且在后续的所有请求中,只要需要这个 Bean,Spring 容器都会返回这个共享的单例实例。

要区分 Spring Bean 是单例还是多例,主要看在 Bean 的配置或者注解中如何声明其作用域(scope)。可以通过以下方式设置 Bean 的作用域:

  1. XML 配置方式:
    在 Spring 的 XML 配置文件中,为 标签添加 scope 属性来指定作用域,例如:

    <bean id="myBean" class="com.example.MyClass" scope="singleton"/>
    
    <bean id="myPrototypeBean" class="com.example.MyClass" scope="prototype"/>
    

    其中,singleton 表示单例,prototype 表示多例。

  2. 注解方式:
    使用 Spring 注解配置时,可以使用 @Scope 注解来指定 Bean 的作用域,例如:

    @Component
    @Scope("singleton") // 表示单例
    public class SingletonBean {}
    
    @Component
    @Scope("prototype") // 表示多例
    public class PrototypeBean {}
    

在上述例子中,SingletonBean 将以单例模式创建,每次请求都会返回相同的实例;而 PrototypeBean 每次请求都会创建一个新的实例返回。

在 Spring Boot 中,情况与常规 Spring 项目相同,Spring Boot 也是基于 Spring 框架构建的,其 Bean 的默认作用域同样为单例模式。也就是说,如果不特别指定,Spring Boot 自动配置或用户自定义的 Bean 默认都是单例的。
如果希望某个 Bean 以多例(原型)模式创建,可以通过以下方式来配置:
在 Spring Boot 的 Java 配置类中,使用 @Scope 注解:

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public MyBean myBean() {
        return new MyBean();
    }
}
  1. 如果在使用 Spring Boot 的自动配置功能,且希望覆盖某个自动配置的 Bean 使其变为原型模式,可以在的配置类中重新定义该 Bean 并加上 @Scope("prototype") 注解。
  2. 对于基于 Spring Boot 的 YAML 或 properties 文件配置,目前尚无直接的方式指定 Bean 的作用域为原型,通常还是推荐在 Java 配置类中使用 @Scope 注解来明确指定。
    总结,在 Spring Boot 应用中,如果没有特殊说明,所有由 Spring 管理的 Bean 默认都是单例的,若需改为多例(原型),需明确标注 @Scope("prototype")

9.国产数据库了解吗?

因为我说不了解,所以此问题没用展开问,项目上用的基本都是Oracle。
数据库的知识也不赘述了,面试资料里有,背就完事

两本Java面试题库(题库是b站大学找的,一会儿我也会分享出来,如果分享的题库侵害了您的权力,请您留言我会第一时间删除资料)
通过百度网盘分享的文件:面试题 链接:https://pan.baidu.com/s/1-BUn626P4JOV8rce6Dfyow
提取码:urx8

10.hashmap和hashTable的区别

HashMapHashTable 都是Java中用来存储键值对(key-value pairs)的数据结构,它们都实现了
Map 接口,但在多个关键点上有显著的区别:

  1. 线程安全性

    • HashTable 是线程安全的,其方法都被同步(synchronized)修饰,意味着在多线程环境下,不需要额外的同步控制就能保证数据的一致性,但这会导致在单线程环境中性能下降,因为每次操作都需要获取锁。
    • HashMap 是非线程安全的,如果不采取外部同步措施,在多线程环境下可能会出现数据不一致的问题,但是在单线程环境下,由于没有锁的开销,性能更好。
  2. Null键值支持

    • HashTable 不允许插入 null 键或 null 值,尝试这样做会抛出 NullPointerException
    • HashMap 允许插入 null 键和 null 值,但是仅有一个 null 键,且允许多个 null 值。
  3. 继承体系

    • HashTable 继承自 Dictionary 类,但由于 Dictionary 类在后续的Java版本中已被标记为过时,现在不再推荐使用。
    • HashMap 继承自 AbstractMap 类,这是一个更为现代的设计,提供了更丰富的API和功能。
  4. 迭代器

    • HashTable 使用的是 Enumeration 进行迭代,而 HashMap 使用的是 Iterator,后者提供了一个更丰富的迭代接口,更适合现代编程风格。
  5. 初始容量与扩容

    • HashTable 的初始容量为11,扩容时大约翻倍+1。
    • HashMap 的初始容量默认为16,扩容时通常是原来的两倍。
  6. 计算hash的方式

    • HashTable 直接使用key的hashCode对table数组的长度取模来定位元素。
    • HashMap 在计算hash时会对key的hashCode做进一步处理,以减少碰撞的可能性,然后再对table数组长度取模。
  7. 更新历史

    • HashTable 出现在早期的Java版本(JDK 1.1),随着Java的发展,逐渐被更高效且功能更完善的集合类所替代。
    • HashMap 是后来推出的(JDK 1.2),成为在单线程环境下更常用的选择。

在实际应用中,如果需要在多线程环境中使用 HashMap,可以选择 ConcurrentHashMap 类,它是Java并发包
(java.util.concurrent) 中提供的线程安全且性能优秀的 HashMap
替代品。而在单线程或对线程安全要求不高的场合,一般推荐使用 HashMap

11.list是有序的吗?

Java中的List接口是有序的。当向List中添加元素时,它们会按照添加的顺序保持排列,且可以通过索引来访问列表中的每个元素。List允许重复元素,常见的实现类有ArrayListLinkedListVector。对于这个问题,可以进一步拓展 ,list,map,set的区别

区别如下:

  1. List:

    • 有序:元素的顺序是确定的,可以通过索引访问。
    • 允许重复:同一个元素可以出现在列表中多次。
    • 主要实现类:ArrayList(基于动态数组,随机访问速度快,插入删除效率相对较低)、LinkedList(基于双向链表,插入删除速度快,随机访问效率低)和Vector(线程安全的List,早期的多线程环境下的选择,现在通常被CopyOnWriteArrayList等代替)。
  2. Set:

    • 无序:虽然元素的实际位置是由其哈希码决定的,但从集合整体来看,Set并不保证任何特定的顺序。
    • 不允许重复:不能有两个相等(根据equals()方法判断)的元素存在于同一个Set中。
    • 主要实现类:HashSet(基于哈希表,查找速度快,不允许重复,无序)、TreeSet(基于红黑树,自动排序,不允许重复,有序)。
  3. Map:

    • 键值对存储:不同于List和Set存储单个对象,Map存储的是键(key)和值(value)的映射关系。
    • 键无序且唯一:Map中的键是无序且不允许重复的,基于键可以快速查找对应的值。
    • 值可以重复:尽管键必须唯一,但值是可以重复的,只要它们对应不同的键。
    • 主要实现类:HashMap(基于哈希表,查找速度快,键值对无序)、TreeMap(基于红黑树,键值对有序,自然排序或自定义比较器排序)以及其他如LinkedHashMap(保留插入顺序或最近最少使用的顺序)等。

每种集合类型适用于不同的场景,根据数据存储、访问、修改的需求以及对线程安全性的要求来选择最适合的集合实现。

12.如果一个程序中有个方法响应很慢,你会怎么排查?说说你的思路

面对程序中响应较慢的方法,排查步骤可以遵循以下流程:

  1. 定位问题方法: 首先,确认哪个方法或哪段代码执行缓慢。可以使用性能分析工具(如Java的VisualVM、JProfiler、YourKit)进行CPU Profiling,找出占用CPU时间较长或阻塞时间较长的方法。

  2. 查看日志: 分析运行时日志,查找是否存在长时间运行、频繁IO操作、数据库查询慢、网络延迟等问题。特别是对于涉及数据库操作,检查SQL查询是否优化不足,是否有全表扫描等情况。

  3. 资源监控: 监控内存、CPU、磁盘I/O和网络流量等资源,看是否存在资源瓶颈。例如,内存泄漏可能导致频繁GC,影响程序性能;磁盘I/O过高可能是由于频繁写入等原因。

  4. 代码审查: 手动检查问题方法的源代码,看看是否存在以下问题:

    • 递归调用过深或无限循环;
    • 大量不必要的计算、数据遍历或复杂的算法;
    • 同步锁争抢严重,导致线程阻塞;
    • 数据结构或算法选择不当,导致复杂度较高;
    • 大量实时创建或销毁对象造成内存压力;
    • 频繁的系统调用、数据库查询或其他外部服务调用且未进行适当缓存。
  5. 性能基准测试: 设计针对性的基准测试,模拟生产环境的压力,观察问题方法在特定条件下的表现,以便进一步优化。

  6. 并发问题排查: 若涉及多线程环境,检查是否存在竞态条件、死锁或者其他并发问题。

  7. 数据库优化: 若方法中有数据库操作,考虑是否能优化SQL语句、建立合适的索引、分库分表、读写分离等手段提高查询性能。

  8. 缓存策略: 判断是否可以引入缓存机制,如Redis、Memcached等,减少对数据库或其他远程服务的访问频次。

  9. 服务依赖排查: 若方法调用了外部服务或中间件,检查这些依赖的服务是否存在性能问题。

通过上述步骤逐一排查,找到问题所在后,制定相应的优化策略进行改进。在整个过程中,务必保持细致和耐心,结合实际情况不断试错和验证。

不会吧不会吧,你真的看完啦,你真棒,加油。面试题和面经我放另一篇博客啦,希望会对您有所帮助
Java后端面试经验分享,~纯分享

你可能感兴趣的:(Java开发1天1道面试题,java,面试)