Java 校招面试大全

本文详细梳理了Java面试中的核心知识点,包括HBase的基础结构、写读流程以及Zookeeper的角色,强调了Client仅需访问Zookeeper的原因。此外,还涵盖了面向对象设计原则SOLID,深入讲解了Spring MVC的请求处理流程、IOC和DI的区别以及Spring AOP的工作原理。文章还探讨了Java内存管理和垃圾回收机制,如HashMap的扩容、对象是否可回收的判断以及GC的触发条件。最后,讨论了TCP的三次握手和四次挥手,HTTP的特性以及GET和POST的区别,以及Session和Cookie的使用场景和区别。

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

面试准备


HBase

HBase 基础结构

HBase基础架构.png-183.1kB

1. HMaster

  • HMaster 是 HBase 主/从集群架构的的中央节点;
  • HMaster 将 Region 分配给 RegionServer,协调 RegionServer 的负载并维护集群状态;
  • 维护表和 Region 的元数据,不参与数据的输入/输出过程

2. RegionServer

  • 维护 HMaster 分配的 Region,并且处理对应 Region 的 I/O 请求
  • 负责切分正在运行过程中变的过大的 Region

3. Zookeeper

  • Zookeeper 是集群的协调器
  • HMaster 在启动的时候把系统表加载在 Zookeeper 上
  • 维护 RegionServer 的状态,提供 HBase RegionServer 的状态信息

HBase 写流程

HBase写流程.png-156.4kB

  1. Client 先访问 Zookeeper,获取表相关的信息,并得到对应的 RegionServer 地址,根据要插入的 Rowkey 获取指定的 RegionServer 的信息(如果为批量提交,会把 Rowkey 根据 HRegionLocation 进行分组);
  2. Client 对 RegionServer 发起写请求(RegionServer 会进入检查状态,比如当前的 Region 是否处于只读状态,MemoStore 的大小是否超过了 BlockingMemoStoreSize 等等),RegionServer 接受数据写入内存(会依次写入 MemoStore 和 HLog);
  3. 当 MemStore 大小达到一定的值后,flush 到 StoreFile 并存储到 HDFS 中。如果 StoreFile 达到一定的阈值,会触发 Split 机制,将 Region 一分为二,然后 HMaster 给两个 Region 分配相应的 RegionServer 进行管理,从而分担压力。

HBase写

  • RegionServer:RegionServer上有一个或者多个Region。我们读写的数据就存储在Region上。如果你的HBase是基于HDFS的,那么Region所有数据存取操作都是调用了HDFS的客户端接口来实现的。
  • Region:表的一部分数据。HBase是一个会自动分片的数据库。一个Region就相当于关系型数据库中分区表的一个分区,或者MongoDB的一个分片。每一个Region都有起始rowkey和结束rowkey,代表了它所存储的row范围。
  • HDFS:Hadoop的一部分。HBase并不直接跟服务器的硬盘交互,而是跟HDFS交互,所以HDFS是真正承载数据的载体。
  • HLog:每一个 RegionServer都会有一个HLog示例,并且将操作写在里面。 HLog是WAL(Write-Ahead Log,预写日志)的一个实现实例。WAL是一个保险机制,数据在写到Memstore之前,先被写到WAL了。这样当故障恢复的时候可以从WAL中恢复数据。

HBase 读流程

HBase读流程.png-167.9kB

  1. Client 先访问 Zookeeper,得到对应的 RegionServer 地址;
  2. Client 对 RegionServer 发起读请求;
  3. 当 RegionServer 收到 Client 的读请求的时候,先扫描自己的 Memstore,再扫描 BlockCache(加速读内容缓冲区),如果还没有找到相应的数据,则从 StoreFile 中读取数据,然后将数据返回给 Client。

为什么 Client 只需要访问 Zookeeper?

HMaster 启动的时候会把 Meta 的信息表记录在 Zookeeper 中。这个元数据信息表存储了 HBase 中所有的表,以及 Region 的详细信息。如 Region 开始和结束的 Key,所在的 RegionServer 的地址。HBASE 的 Meta 表相当于一个目录。通过访问 Meta 表可以快速的定位到数据的实际位置,所以读写操作只需要与 Zookeeper 和对应的 RegionServer 进行交互,而 HMaster 只需要负责维护 table 和 Region 的元数据信息,协调各个 RegionServer,也因此减少了 HMaster 的负载。

参考资料

  1. HBase工作原理学习

面向对象设计原则

S.O.L.I.D

简写 全拼 中文翻译
SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
ISP The Interface Segregation Principle 接口分离原则
DIP The Dependency Inversion Principle 依赖倒置原则

1. 单一责任原则

修改一个类的原因应该只有一个。

换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。

如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。

2. 开放封闭原则

类应该对扩展开放,对修改关闭。

扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。

符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。

3. 里氏替换原则

子类对象必须能够替换掉所有父类对象。

继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。

如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。

4. 接口分离原则

不应该强迫客户依赖于它们不用的方法。

因此使用多个专门的接口比使用单一的总接口要好。

5. 依赖倒置原则

高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。

依赖于抽象意味着:

  • 任何变量都不应该持有一个指向具体类的指针或者引用;
  • 任何类都不应该从具体类派生;
  • 任何方法都不应该覆写它的任何基类中的已经实现的方法。

Spring MVC 请求流程

Spring MVC请求流程.png-86.7kB

  1. 发起请求到前端控制器 (DispatcherServlet);
  2. 前端控制器请求 HandlerMapping 查找 Handler (可以根据 xml 配置、注解进行查找);
  3. 处理器映射器 HandlerMapping 向前端控制器返回 Handler,HandlerMapping 会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象,多个 HandlerInterceptor 拦截器对象),通过这种策略模式,很容易添加新的映射策略;
  4. 前端控制器调用处理器适配器去执行 Handler;
  5. 处理器适配器 HandlerAdapter 将会根据适配的结果去执行 Handler;
  6. Handler 执行完成给适配器返回 ModelAndView;
  7. 处理器适配器向前端控制器返回 ModelAndView (ModelAndView 是 Spring MVC 框架的一个底层对象,包括 Model 和 view);
  8. 前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图 (jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可;
  9. 视图解析器向前端控制器返回 View;
  10. 前端控制器进行视图渲染 (视图渲染将模型数据 (在 ModelAndView 对象中) 填充到 request 域);
  11. 前端控制器向用户响应结果。

参考资料

  1. springMVC请求流程详解

Spring IOC

IOC 是 Inversion of Control 的缩写,多数书籍翻译成“控制反转”。

IOC 理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。

软件系统在没有引入 IOC 容器之前,如图 1 所示,对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。无论是创建还是使用对象 B,控制权都在自己手上。

此处输入图片的描述

软件系统在引入 IOC 容器之后,这种情形就完全改变了,如图所示,由于 IOC 容器的加入,对象 A 与对象 B 之间失去了直接联系,所以,当对象 A 运行到需要对象 B 的时候,IOC 容器会主动创建一个对象 B 注入到对象 A 需要的地方。
此处输入图片的描述

通过前后的对比,我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

DI 依赖注入

2004 年,Martin Fowler 探讨了同一个问题,既然 IOC 是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由 IOC 容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现 IOC 的方法:注入。所谓依赖注入,就是由 IOC 容器在运行期间,动态地将某种依赖关系注入到对象之中。

IOC 和 DI 的区别

理解以上概念需要搞清以下问题:

  1. 参与者都有谁?
      一般有三个参与者。 1)是某个对象;2)是 IOC/DI 的容器;3)某个对象的外部资源。
      其中 1)某个对象指的是任意的,普通的 Java 对象;2)IOC/DI 容器指的是指用于实现 IOC/DI 功能的框架程序;3)对象的完毕资源指的是对象所需要的,但是需要从外部获取的统称为资源;比如一个对象的属性为另外一个对象,或者是对象需要的是一个文件资源等等。
  2. 依赖: 谁依赖于谁?为什么需要依赖?
      对象依赖于 IOC/DI 的容器。 因为对象需要 IOC 来提供对象所需要的外部资源。
  3. 注入:谁注入谁?到底注入什么?
      IOC/DI 容器注入某个对象。 注入某个对象所需要的外部资源。
  4. 控制反转:谁控制谁?控制了什么?为什么叫反转(有反转就应该有正转)?
      是 IOC 容器控制对象,主要是控制了对象实例的创建。
      反转是针对正向而言,正向是针对常规下的应用程序而言的。正规应用程序下,如果要在 A 里面使用 C,则会直接创建 C 的对象。,也就是说,是在 A 类中主动去获取所需要的外部资源 C,这种情况被称为正向的。
      那么什么是反向呢?就是 A 类不再主动去获取 C,而是被动等待,等待 IoC/DI 的容器获取一个 C 的实例,然后反向的注入到 A 类中。
  5. 依赖注入和控制反转是同一概念么?
      依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。
      依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;
      而控制反转是从容器的角度在描述。描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

Spring AOP

AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。

所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

AOP 常见的使用场景

  1. Authentication 权限
  2. Caching 缓存
  3. Context passing 内容传递
  4. Error handling 错误处理
  5. Lazy loading 懒加载
  6. Debugging  调试
  7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  8. Performance optimization 性能优化
  9. Persistence  持久化
  10. Resource pooling 资源池
  11. Synchronization 同步
  12. Transactions 事务

实现原理

Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP 中的动态代理方法主要有两种:

  1. JDK 动态代理:JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。
  2. CGLIB 动态代理:如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

JDK 与 Cglib 代理对比

  1. JDK 只能针对有接口的类的接口方法进行动态代理。
  2. Cglib 基于继承实现代理,无法对 static 或者 final 类进行代理。
  3. Cglib 基于继承实现代理,也无法对 private 或者 static 方法进行代理。

Spring AOP 对两种方法的选择

  1. 如果目标对象实现了接口,则默认采用 JDK 动态代理。

  2. 如果目标对象没有实现接口,则默认采用 Cglib 进行动态代理。

  3. 如果目标对象实现了接口,则强制 Cglib 代理,则采用 Cglib 进行代理。

    // 采用注解开启强制代理
    @EnableAspectJAutoProxy(proxyTragetClass = true)
    

参考资料

  1. Spring AOP 的实现原理

Spring Bean 的初始化流程

SpringBean 初始化.png-379.2kB

  1. Spring 对 bean 进行实例化;
  2. Spring 将值和 bean 的引用注入到 bean 对应的属性中;
  3. 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName() 方法;
  4. 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入;
  5. 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文的引用传入进来;
  6. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 方法;
  7. 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 after-PropertiesSet() 方法。类似地,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用;
  8. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 post-ProcessAfterInitialization() 方法;
  9. 此时,bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  10. 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果 bean 使用 destroy-method 声明了销毁方法,该方法也会被调用。
/**
 * bean的生命周期:
 * 		bean创建---初始化----销毁的过程
 * 容器管理bean的生命周期;
 * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法
 * 
 * 构造(对象创建)
 * 		单实例:在容器启动的时候创建对象
 * 		多实例:在每次获取的时候创建对象
 * 
 * BeanPostProcessor.postProcessBeforeInitialization
 * 初始化:
 * 		对象创建完成,并赋值好,调用初始化方法。。。
 * BeanPostProcessor.postProcessAfterInitialization
 * 销毁:
 * 		单实例:容器关闭的时候
 * 		多实例:容器不会管理这个bean;容器不会调用销毁方法;
 * 
 * 
 * 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,
 * 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
 * 
 * BeanPostProcessor原理
 * populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值
 * initializeBean
 * {
 * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
 * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
 * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
 *}
 * 
 * 
 * 
 * 1)、指定初始化和销毁方法;
 * 		通过@Bean指定init-method和destroy-method;
 * 2)、通过让Bean实现InitializingBean(定义初始化逻辑),
 * 				DisposableBean(定义销毁逻辑);
 * 3)、可以使用JSR250;
 * 		@PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
 * 		@PreDestroy:在容器销毁bean之前通知我们进行清理工作
 * 4)、BeanPostProcessor【interface】:bean的后置处理器;
 * 		在bean初始化前后进行一些处理工作;
 * 		postProcessBeforeInitialization:在初始化之前工作
 * 		postProcessAfterInitialization:在初始化之后工作
 * 
 * Spring底层对 BeanPostProcessor 的使用;
 * 		bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor;
 * 
 * @author lfy
 *
 */

Spring 组件注册

Spring 组件注册主要有以下三种方式:

  1. 包扫描 + 组件标注注解(@Controller/@Service/@Repository/@Component)
    缺点: 只能对自定义的类进行注解标注,无法对三方类库中的属性进行组件注册
  2. @Bean[主要用于三方类库中的组件注册 ]
  3. @Import[可以快速给容器中导入一个组件,其 Bean 的 id 默认为组件的全类名]

Spring 自动装配

@Autowired

  1. 默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
  2. 如果找到多个相同类型的组件,再将属性的名称作为组件的 id 去容器中查找,如applicationContext.getBean("bookDao")
  3. @Qualifier(“bookDao”):使用@Qualifier 指定需要装配的组件的 id,而不是使用属性名
  4. 自动装配默认一定要将属性赋值好,没有就会报错;可以使用@Autowired(required=false);
  5. @Primary:让 Spring 进行自动装配的时候,默认使用首选的 bean;(也可以继续使用@Qualifier 指定需要装配的 bean 的名字)
BookService{
	@Autowired
	BookDao  bookDao;
}

@Autowired 构造器,参数,方法,属性;都是从容器中获取参数组件的值。

  1. 构造器:如果组件只有一个有参构造方法,则该有参构造器的 @Autowired 可以省略,有参构造器参数的值自动从容器中获取。
  2. 方法:如果只是 @Bean 注解 + 方法参数,参数的值也会自动从容器中获取。默认不写 @Autowired 效果是一样的;都能自动装配。
  3. 属性:

@Resource [JSR250]

可以和@Autowired 一样实现自动装配功能;默认是按照组件名称进行装配的;
没有支持@Primary 功能,没有支持 @Autowired(reqiured=false)

@Inject [JSR300]

需要导入 javax.inject 的依赖,和 @Autowired 的功能一样。

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

没有支持 @Autowired(reqiured=false) 的功能;

自动注入流程

AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能;

容器组件注入

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx);
自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;均实现 `` 接口;
把Spring底层一些组件注入到自定义的Bean中;
xxAware 均有对应的 xxxProcessor 进行值的注入;
ApplicationContextAware==》ApplicationContextAwareProcessor;

Java 读取一个文件, 有哪些方法, 考虑性能, 用哪一个类

文件读写主要有以下集中常用的方法:

  1. 字节读写(InputStream/OutputStream)
  2. 字符读取(FileReader/FileWriter)
  3. 行读取(BufferedReader/BufferedWriter)

通过测试 ,可以发现,就写数据而言,BufferedOutputStream耗时是最短的,而性能FileWriter最差;读数据方面,BufferedInputStream性能最好,而FileReader性能最差劲。

Java OOM

为什么为发生 OOM

原因一般出现为以下两点:

  1. 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的 VM 参数指定)太少。
  2. 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

其对应的两个术语为:

  1. 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
  2. 内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值