Java面试题知识汇总

简介: 面试遇到了一个很厉害的面试官,问了很多底层和架构的知识点问题,在这里我们就针对这些问题进行一个罗列,也算是对自己的一个自我深度总结,希望能够记住,不要再想不起来了。

面试遇到了一个很厉害的面试官,问了很多底层和架构的知识点问题,在这里我们就针对这些问题进行一个罗列,也算是对自己的一个自我深度总结,希望能够记住,不要再想不起来了。

一、事务的特性以及类型

事务是为了确保对数据操作的正确性。

事务的4大特性是

原子性:一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性。

一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。


隔离性:事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。


持久性:一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态


在对数据库进行增删改查操作的时候可能会发生异常,错误,导致脏读、幻读、数据写入错误等情况。事务的提交一般会在Service层,所以事务管理要放在这里

Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。

JDBC 事务是用 Connection 对象控制的。
JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:
自动提交和手工提交。
java.sql.Connection 提供了以下控制事务的方法:
public void setAutoCommit(boolean)
public boolean getAutoCommit()
public void commit()
public void rollback()
使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。
JTA允许应用程序执行分布式事务处理
容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。

二、Spring的注入方式

Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:

构造方法注入:

<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<!--多个参数-->
    <constructor-arg index="0" name="userDao" ref="userDaoJdbc"></constructor-arg>
    <constructor-arg index="1" name="user" ref="user"></constructor-arg>
</bean>

构造器注入:通过将@Autowired注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配。


setter注入:

<bean id="userService" class="com.lyu.spring.service.impl.UserService">
    <!-- 写法一 -->
    <!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
    <!-- 写法二 -->
    <property name="userDao" ref="userDaoMyBatis"></property>
    <property name="user" ref="user"></property>
</bean>
:上面这两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上”set”构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关,下面的这种写法是可以运行成功的

注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。


基于注解的注入:

先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。

constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。


byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。


byType:查找所有的set方法,将符合符合参数类型的bean注入。

主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean

描述依赖关系主要有两种:

@Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。

@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){

@Autowired:spring注解,默认是以byType的方式去匹配与属性名相同的bean的id,如果没有找到,就通过byName的方式去查找,

@Autowired默认是根据参数类型进行自动装配,且必须有一个Bean候选者注入默认required=true,如果允许出现0个Bean候选者需要设置属性“required=false”,“required”属性含义和@Required一样,只是@Required只适用于基于XML配置的setter注入方式,只能打在setting方法上。

@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;

写在最后:虽然有这么多的注入方式,但是实际上开发的时候自己编写的类一般用注解的方式注册类,用@Autowired描述依赖进行注入,一般实现类也只有一种(jdbc or hibernate or mybatis),除非项目有大的变动,所以@Qualifier标签用的也较少;但是在使用其他组件的API的时候用的是通过xml配置文件来注册类,描述依赖,因为你不能去改人家源码嘛。

三、java的代理模式

Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP、Java注解的获取、日志、用户鉴权等。


代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。


**静态代理:**是指代理类在程序运行前就已经存在,这种情况下的代理类通常都是我们在Java代码中定义的。

**动态代理:**动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理

#与$在mybatis中的作用

#{ } :#代表占位符,用来传递参数;


${ } : $ 用来拼接sql 语句的;

譬如:把数据库中的表名作为参数拼接在 sql 语句中,必须使用 $

order by 时,必须使用${}


#{ }和${ } 区别如下:

1、#是预编译的方式,$是直接拼接;

2、#不需要关注数据类型,mybatis实现自动数据类型转换;$不做数据类型转换,需要自行判断数据类型;

3、#可以防止sql注入;$不能防止sql注入;

4、如果 parameterType 只有一个参数,默认情况下,#{ }中可以写任意的名字;${ }中只能用value来接收。

<select id="findUserById" parameterType="java.lang.Integer" resultType="model.User">
  select * from user where id=#{id}
 </select>
    <!--模糊查询   like +条件  '% ${匹配的内容} %'    $是-->
    <select id="findUserByName" parameterType="String" resultType="model.User">
 select * from user where name like  '%${value}%';
</select>

mybatis中有哪些标签

20201229084754688.png

(2)foreach 标签的使用


foreach标签主要用于构建in条件,他可以在sql中对集合进行迭代。如下:

<delete id="deleteBatch"> 
    delete from user where id in
    <foreach collection="array" item="id" index="index" open="(" close=")" separator=",">
      #{id}
    </foreach>
  </delete>

我们假如说参数为---- int[] ids = {1,2,3,4,5} ----那么打印之后的SQL如下:


delete form user where id in (1,2,3,4,5)


释义:


collection :collection属性的值有三个分别是list、array、map三种,分别对应的参数类型为:List、数组、map集合,我在上面传的参数为数组,所以值为array


item : 表示在迭代过程中每一个元素的别名


index :表示在迭代过程中每次迭代到的位置(下标)


open :前缀


close :后缀


separator :分隔符,表示迭代时每个元素之间以什么分隔


我们通常可以将之用到批量删除、添加等操作中。

文章推荐:https://blog.csdn.net/weixin_40950778/article/details/78655288

redis的sortedSet有什么作用

关于有序集,我们有一个十分常见的使用场景就是用户评论。在APP或者网站上发布一条消息,下面会有很多评论,通常展示是按照发布时间倒序排列,这个需求就可以使用有序集,以发布评论的时间戳作为score,然后按照展示评论的数量倒序查找有序集。

多线程callable与thread和Runable有什么不同?

实现Runnable接口相比继承Thread类有如下优势:


可以避免由于Java的单继承特性而带来的局限;

增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;

适合多个相同程序代码的线程区处理同一资源的情况。


实现Runnable接口和实现Callable接口的区别:


Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的

Callable规定的方法是call(),Runnable规定的方法是run()

Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)

call方法可以抛出异常,run方法不可以

运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。


Redis缓存遇到的常见问题及解决办法

缓存雪崩

可以简单的理解为: 系统刚刚部署完毕,所有缓存数据还未准备完毕或者由于原有缓存有效期集体到达

例如: 系统中所有缓存都设置的一致的过期时间,在同一时刻出现大面积的缓存过期,所以原本应该查询缓存的请求都去查询数据库了,造成数据库压力骤增,甚至宕机.


解决办法

使用加锁(分布式锁)或者队列(ribbitMQ)的方式保证不会有大量线程对数据库进行一次性读写,设置热点数据永不过期,同时将缓存的过期时间设置分散。

设置系统上线后就对需要缓存的数据进行缓存,而不是在第一次查询后才把数据加入缓存,这样做到了事先对缓存数据进行预热。


解决思路

1.写一个刷新缓存的接口,系统上线后手动请求刷新缓存

2.缓存数据量不大时,可以设置项目启动成功后加载缓存数据

3.设置定时刷新缓存

4.双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点

● I 先从缓存A读数据,有则直接返回;

● II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程;

● III 更新线程同时更新缓存A和缓存B。


缓存穿透

缓存穿透指用户端查询了一个数据库中不存在的数据,自然在缓存中也不存在.这就导致了用户进行了两次无用查询.


解决办法

推荐的方式是使用Bloom-Filter(布隆过滤器),将所有可能存在的数据存储到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap过滤掉,从而避免了对底层数据库系统的查询压力.

还有一个简单粗暴的方式,如果一个查询返回的数据为空(不管是数据不存在还是系统故障),仍然把这个空结果放入缓存中,设置很短的过期时间(不超过5分钟).通过这个直接设置默认值进缓存的方式,这样第二次查询缓存的时候就有值了,避免去访问底层数据库系统了.


缓存击穿

缓存击穿(热点key)

名词解释:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案:

(一)使用互斥锁(mutex key):先去获得锁,得到锁后构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了;

(二)"提前"使用互斥锁:在value内部设置1个超时值(timeout1), timeout1比实际的timeout(timeout2)小。当从cache读取到timeout1发现它已经过期,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中;

(三)永不过期:没有设置过期时间就保证不会出现热点key过期问题

(四)资源保护:做资源的隔离保护主线程池


缓存并发竞争


缓存并发竞争(并发set)

名词解释:多个client线程同时set key引起的并发问题

解决方案:

(一)分布式锁+时间戳:准备一个分布式锁(SETNX实现),大家去抢锁,抢到锁就做set操作,value需要保存一个时间戳判断set顺序;抢到锁的线程的时间戳早于缓存中的时间戳就不做set操作。

(二)消息队列:把Redis.set操作放在队列中使其串行化,必须的一个一个执行。这种方式在一些高并发的场景中算是一种通用的解决方案。

写一个单例模式

懒汉式:线程不安全

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

懒汉式:线程安全

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

饿汉式:线程安全

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

详细见https://www.runoob.com/design-pattern/singleton-pattern.html

冒泡排序

#include<stdio.h>
#include<stdlib.h>
void BubbleSort(int a[], int len)
{
  int i, j, temp;
  for (j = 0; j < len - 1; j++)
  {
    for (i = 0; i < len - 1 - j; i++)
    if (a[i] > a[i + 1])
    {
      temp = a[i];
      a[i] = a[i + 1];
      a[i + 1] = temp;
    }
  }
}
int main()
{
  int arr[] = { 5, 8, 6, 3, 9, 2, 1, 7 };
  int len = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  printf("排序前:");
  for (i = 0; i < len; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  BubbleSort(arr, len);
  printf("排序后:");
  for (i = 0; i < len; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  system("pause");
  return 0;
}


相关文章
|
6天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
2天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
17 4
|
3天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
27 4
|
1天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
27天前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
78 1
Java面试题之Java集合面试题 50道(带答案)
|
15天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
37 5
|
14天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
16 1
|
23天前
|
Java 程序员
Java 面试高频考点:static 和 final 深度剖析
本文介绍了 Java 中的 `static` 和 `final` 关键字。`static` 修饰的属性和方法属于类而非对象,所有实例共享;`final` 用于变量、方法和类,确保其不可修改或继承。两者结合可用于定义常量。文章通过具体示例详细解析了它们的用法和应用场景。
25 3
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
374 37
|
27天前
|
Java
Java面试题之cpu占用率100%,进行定位和解决
这篇文章介绍了如何定位和解决Java服务中CPU占用率过高的问题,包括使用top命令找到高CPU占用的进程和线程,以及使用jstack工具获取堆栈信息来确定问题代码位置的步骤。
75 0
Java面试题之cpu占用率100%,进行定位和解决