【华为】面试真题:
1、Java 常用集合及特点?
List:ArrayList、LinkedList、Vector、Stack Set:LinkedSet、HashSet、TreeSet
Queue->Deque->LinkedList。
Map:HashMap、LinkedHashMap、TreeMap Dictionary->HashTable->Properties。
Vector: 底层数据结构是数组,查询快,增删慢,线程安全,效率低,默认长度为 10,超过会 100%延长,变成 20,浪费空间。
ArrayList :基于数组,便于按 index 访问,超过数组需要扩容,扩容成本较高。
LinkedList:使用链表实现,无需扩容。
HashSet:底层数据结构是哈希表(无序,唯一),通过 hashcode()和 equals()保证元素唯一。
LinkedHashSet: 底层数据结构是链表和哈希表(FIFO 插入有序,唯一),由链表保证元素有序,由哈希表保证元素唯一。
TreeSet:底层数据结构是红黑树(唯一,有序),通过自然排序和比较器排序保证元素有序,根据比较返回值是否是 0 来保证元素唯一性。
TreeMap 是有序的。
HashMap :空间换时间,哈希冲突不大的情况下查找数据性能很高。
LinkedHashMap 基本特点:继承自 HashMap,对 Entry 集合添加了一个双向链表。
2、开启一个线程的方法?
继承 Thread 类,新建一个当前类对象,并且运行其 start()方法。
实现 Runnable 接口,然后新建当前类对象,接着新建 Thread 对象时把当前类对象传进去,最后运行 Thread 对象的 start()方法。
实现 Callable 接口,新建当前类对象,在新建 FutureTask 类对象时传入当前类对象,接着新建 Thread 类对象时传入 FutureTask 类对象,最后运行 Thread 对象的 start()方法
3、Java 面向对象包括哪些特性,怎么理解的?
封装:
通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
继承:
继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
多态:
多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:
第一:方法重写(子类继承父类并重写父类中已有的或抽象的方法);
第二:对象造型(用父类型引用指向子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
抽象:
抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
4、Java 如何保证线程安全?
使用同步代码块
使用同步方法
使用 Lock 锁机制, 通过创建 Lock 对象,采用 lock()加锁,unlock()解锁,来保护指定的代码块。
5、介绍 Spring MVC 的工作流程 ?
用户向服务端发送一次请求,这个请求会先到前端控制器 DispatcherServlet。
DispatcherServlet 接收到请求后会调用 HandlerMapping 处理器映射器。由此得知,该请求该由哪个 Controller 来处理(并未调用 Controller,只是得知) DispatcherServlet 调用 HandlerAdapter 处理器适配器,告诉处理器适配器应该要去执行哪个 Controller
HandlerAdapter 处理器适配器去执行 Controller 并得到 ModelAndView(数据和视图),并层层返回给 DispatcherServlet
DispatcherServlet 将 ModelAndView 交给 ViewReslover 视图解析器解析,然后返回真正的视图。
DispatcherServlet 将模型数据填充到视图中
DispatcherServlet 将结果响应给用户
6、Spring 框架中用到了哪些设计模式?
工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller。
7、Redis 的特点是什么?
Redis 本质上是一个 Key-Value 类型的内存数据库,很像 Memcached,整个数据库统加载在内存当中进行操作,定期通过异步操作把数据库数据 flush 到硬盘上进行保存。
因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。
Redis 的出色之处不仅仅是性能,Redis 最大的魅力是支持保存多种数据结构,此外单个value 的最大限制是 1GB,不像 Memcached 只能保存 1MB 的数据,因此 Redis 可以用来实现很多有用的功能。
比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set 可以做高性能的 tag 系统等等。另外 Redis 也可以对存入的 Key-Value 设置 expire时间,因此也可以被当作一个功能加强版的 Memcached 来用。
Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
8、为什么使用 Redis,有什么好处?
速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1)
支持丰富数据类型,支持 string,list,set,sorted set,hash
支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除
9、Redis 雪崩和击穿了解吗?
缓存击穿
问题:某个 KEY 失效的时候,正好有大量并发请求访问这个 KEY。
分析:跟穿透其实很像,属于比较偶然的。
解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。
缓存雪崩
问题:当某一时刻发生大规模的缓存失效的情况,导致大量的请求无法获取数据,从而将流量压力传导到数据库上,导致数据库压力过大甚至宕机。
原因:一般而言,缓存雪崩有 2 种可能性:大量的数据同一个时间失效:比如业务关系强相关的数据要求同时失效 Redis 宕机
分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合适,数据要均匀分享,缓存服务器要多台高可用。
解决办法:更新策略在时间上做到比较平均。如果数据需要同一时间失效,可以给这批数据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从的部署,当主节点挂掉后,能快速的使用从结点顶上。实现熔断限流机制,对系统进行负载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取数据。服务降价:提供默认返回值,或简单的提示信息。
10、什么是面向对象,谈谈你的理解?
世间万物都可以看成一个对象。每个物体包括动态的行为和静态的属性,这些就构成了一个对象。
11、访问数据库除了 JDBC 还有什么?
自己封装 JDBC 的工具类
Commons-Dbutils dbcp【QueryRunner】
SpringJDBC【JdbcTemplate】
JPA【配置文件、domain 实体类 注解、EntityManager】
SpringDataJpa
Hibernate 框架
Mybatis80
12、你知道有哪些设计原则?
遵循单一职责原则
开放-封闭原则
里氏代换原则(LSP)
依赖倒置原则
接口隔离原则(Interface Segregation Principle)
迪米特法则(Law of Demeter)
13、在生产环境 Linux 服务器上,发现某台运行 Java 服务的服务器的
CPU100%,不借助任何可视化工具,怎么进行问题的定位?
top 找出进程 CPU 比较高 PID
top -Hp PID 打印 该 PID 进程下哪条线程的 CPU 占用比较高 tid
printf “%xn” tid 将该 id 进行 16 进制转换 tidhex
jstack PID |grep tidhex 打印线程的堆栈信息
14、JDK 里面带的工具你知道哪些?
jstat:虚拟机进程状况工具
jinfo:Java 配置信息工具
jmap:Java 内存映像工具
jhat:虚拟机堆转储快照分析工具
jstack:Java 堆栈跟踪工具
JConsole: Java 监视与管理控制台
VisualVM: 多合一故障处理工具
15、基本数据类型 bit 长度?
byte:1*8
short:2*8
int: 4*8
long: 8*8
float: 4*8
double: 8*8
char: 2*8l boolean: 1*8
16、char 能不能存中文?
可以,不过,如果某个特殊的汉字没有被包含在 unicode 编码字符集中,那么,这个 char型变量中就不能存储这个特殊汉字。
17、谈谈你对泛型的理解?
Java 中的泛型有 3 种形式,泛型方法,泛型类,泛型接口。Java 通过在编译时类型擦除的方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型,同时在要调用具体类型方法或者成员变量的时候插入强转代码,为了保证多态特性,Java 编译器还会为泛型类的子类生成桥接方法。类型信息在编译阶段被擦除之后,程序在运行期间无法获取类型参数所对应的具体类型。
18、Java 程序是怎样运行的?
首先通过 Javac 编译器将 .java 转为 JVM 可加载的 .class 字节码文件。
Javac 是由 Java 编写的程序,编译过程可以分为:① 词法解析,通过空格分割出单词、操作符、控制符等信息,形成 token 信息流,传递给语法解析器。② 语法解析,把token 信息流按照 Java 语法规则组装成语法树。③ 语义分析,检查关键字使用是否合理、类型是否匹配、作用域是否正确等。④ 字节码生成,将前面各个步骤的信息转换为字节码。
字节码必须通过类加载过程加载到 JVM 后才可以执行,执行有三种模式,解释执行、JIT编译执行、JIT 编译与解释器混合执行(主流 JVM 默认执行的方式)。混合模式的优势在于解释器在启动时先解释执行,省去编译时间。 之后通过即时编译器 JIT 把字节码文件编译成本地机器码。
Java 程序最初都是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会认定其为"热点代码",热点代码的检测主要有基于采样和基于计数器两种方式,为了提高热点代码的执行效率,虚拟机会把它们编译成本地机器码,尽可能对代码优化,在运行时完成这个任务的后端编译器被称为即时编译器。
还可以通过静态的提前编译器 AOT 直接把程序编译成与目标机器指令集相关的二进制代码。
19、GC root 有哪些?
Thread-存活的线程。
Java 虚拟机栈中的引用的对象。
方法区中的类静态属性引用的对象。 (一般指被 static 修饰的对象,加载类的时候就加载到内存中。)
方法区中的常量引用的对象。
本地方法栈中的 JNI(native 方法)引用的对象。
Monitor Used-用于同步监控的对象。
20、栈帧的大小什么时候确定?
有时候编译期能够确定,有些时候函数的栈帧的大小在编译期并不确定。比如用了 VLA。所以一般会有两个寄存器(IA-32 上就是 ebp 和 esp)来记录栈帧的首尾地址。当进入一个函数时,首先把上个栈帧的首尾地址分别保存起来(一般做法是将 ebp 压栈、并将 esp 写入 ebp),接着再分配新的栈帧大小(先给 esp 减一个常数,如果需要动态分配再接着减)。
21、静态 filed 声明和构造器哪个先执行?
filed 声明先执行。
22、线程创建方式是什么?
通过继承 Thread 类创建线程类
实现 Runnable 接口创建线程类
通过 Callable 和 Future 接口创建线程
23、传统 I/O 跟 NIO 的区别?
所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。
流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。传统流 IO 的好处是使用简单,将底层的机制都抽象成流,但缺点就是性能不足。而且 IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。 原来的I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
NIO 性能的优势就来源于缓冲的机制(buffer 机制),不管是读或者写都需要以块的形式写入到缓冲区中。NIO 实际上让我们对 IO 的操作更接近于操作系统的实际过程。
NIO 作为非阻塞式的 IO,它的优点就在于,1、它由一个专门的线程去处理所有的 IO事件,并负责分发;2、事件驱动,只有事件到了才会触发,而不是同步的监听这个事件;3、线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
当我们在执行持续性的操作(如上传下载)时,IO 的方式是要优于 NIO 的。分清情况,合理选用。
NIO 相对于 IO 流的优势:
非阻塞
buffer 机制
流替代块
24、消息队列的在各种场景下如何选型?
优先级队列;队列设置最大的优先级,之后每条消息设置对应的优先级,队列根据消息优先级进行消费,(在有可能队列堆积的情况才有意义);应用场景:不同业务消息推送。
延迟队列:消息发送后,并不想让消费者立即拿到消息,等待特定的事件后,消费者才能拿到并消费;应用场景:订单系统中订单支付 30 分钟内没有支付成功,那么将这个订单进行异常处理;远程操作智能设备在指定时间进行工作等。(rabbit 中没有延迟队列,但可以借助死信队 列与 TTL 设置来完成)
死信队列:当消息在一个队列中变成死信之后,它能被重新被发送到另一个交换器(DLX交换器)中,绑定 DLX 的队列就称为死信队列。
重试队列:消费端,一直不回传消费的结果,rocketmq 认为消息没收到,consumer 下一次拉取,broker 依然会发送该消息(有次数限制)。
重试队列其实可以看成是一种回退队列,具体指消费端消费消息失败时,为防止消息无故丢失而重新将消息回滚到Broker 中。
消费模式: 推模式:对于 kafka 而言,由 Broker 主动推送消息至消费端,实时性较好,不过需要一定的流 制机制来确保服务端推送过来的消息不会压垮消费端。拉模式:对于 kafka 而言,消费端主动向 Broker 端请求拉取(一般是定时或者定量)消息,实时性较推模式差,但是可以根据自身的处理能力而控制拉取的消息量。
消息回溯:重置消息 offset(如:kafka、rokcetMq) 一般消息在消费完成之后就被处理了,之后再也不能消费到该条消息。消息回溯正好相反,是指消息在消费完成之后,还能消费到之前被消费掉的消息。对于消息而言,经常面临的问题是“消息丢失”,至于是真正由于消息中间件的缺陷丢失还是由于使用方的误用而丢失一般很难追查,如果消息中间件本身具备消息回溯功能的话,可以通过回溯消费复现“丢失的”消息 进而查出问题的源头之所在。消息回溯的作用远不止与此,比如还有索引恢复、本地缓存重建,有些业务补偿方案也可以采用回溯的方式来实现。
消息堆积:流量削峰是消息中间件的一个非常重要的功能,而这个功能其实得益于其消息堆积能力。从某种意义上来讲,如果一个消息中间件不具备消息堆积的能力,那么就不能把它看做是一个合格的消息中间件。消息堆积分内存式堆积和磁盘式堆积。
消息持久化:持久化确保 MQ 的使用不只是一个部分场景的辅助工具,而是让 MQ 能像数据库一样存储核心的数据。有些功能是默认不开启的,需要进行配置。
多租户: 也可以称为多重租赁技术,是一种软件架构技术,主要用来实现多用户的环境下公用相同的系统或程序组件,并且仍可以确保各用户间数据的隔离性。RabbitMQ 就能够支持多租户技术,每一个租户表示为一个 vhost,其本质上是一个独立的小型 RabbitMQ服务器,又有自己独立 的队列、交换器及绑定关系等,并且它拥有自己独立的权限。
vhost 就像是物理机中的虚拟机 一样,它们在各个实例间提供逻辑上的分离,为不同程序安全保密地允许数据,它既能将同一 个 RabbitMQ 中的众多客户区分开,又可以避免队列和交换器等命名冲突。
跨语言支持: 对很多公司而言,其技术栈体系中会有多种编程语言,如 C/C 、JAVA、Go、PHP 等,消息 中间件本身具备应用解耦的特性,如果能够进一步的支持多客户端语言,那么就可以将此特性 的效能扩大。跨语言的支持力度也可以从侧面反映出一个消息中间件的流行程度。
消息顺序消息:先进先出、 逐条进行消费顾名思义,消息顺序性是指保证消息有序。这个功能有个很常见的应用场景就是 CDC(Change Data Chapture),以 MySQL 为例,如果其传输的 binlog 的顺序出错,比如原本是先对一条数据加 1,然后再乘以 2,发送错序之后就变成了先乘以 2 后加 1 了,造成了数据不一致。
安全机制: 在 Kafka 0.9 版本之后就开始增加了身份认证和权限控制两种安全机制。身份认证是指客户端与服务端连接进行身份认证,包括客户端与 Broker 之间、Broker 与Broker 之间、Broker 与 ZooKeeper 之间的连接认证,目前支持 SSL、SASL 等认证机制。权限控制是指对客户端的读写操作进行权限控制,包括对消息或 Kafka 集群操作权限控制。权限控制是可插拔的,并支持与外部的授权服务进行集成。对于 RabbitMQ而言,其同样提供身份认证(TLS/SSL、SASL)和 权限控制(读写操作)的安全机制。
事务支持: 事务本身是一个并不陌生的词汇,事务是由事务开始(Begin Transaction)和事务结束(End Transaction)之间执行的全体操作组成。支持事务的消息中间件并不在少数,Kafka 和 RabbitMQ 都支持,不过此两者的事务是指生产者发生消息的事务,要么发送成功,要么发送失败。消息中间件可以作为用来实现分布式事务的一种手段,但其本身并不提供全局分布式事务的功能。
25、Java 的安全性体现在哪里?
Java SE 安全性概述 Java SEl 平台基于一个动态、可扩展、基于标准、可互操作的安全架构。加密、身份证和授权、公共密钥基础架构等安全特性是内置的。Java 安全模型基于一个可定制的“沙盒”,Java 软件程序可在其中安全运行,对系统或用户无潜在风险。
Java 编译器和虚拟机强制实施的内置的语言安全特性:
强大的数据类型管理
自动内存管理
字节码验证
安全的类加载
26、static 方法怎么访问非 static 变量?
类的静态成员(变量和方法)都属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问
27、讲下你理解的 Java 多继承?
若子类继承的父类中拥有相同的成员变量,子类在引用该变量时将无法判别使用哪个父类的成员变量
若一个子类继承的多个父类拥有相同方法,同时子类并未覆盖该方法(若覆盖,则直接使用子类中该方法),那么调用该方法时将无法确定调用哪个父类的方法。
28、Java 基本类型有哪些?
byte 1
short 2
int 4
long 8
float 4
double 8
char 2
boolean 1
29、线程池如果满了会怎么样?
如果使用的是无界队列 Linke dBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务 如果使用的是有界队列比如 ArrayBlockingQueue , 任务首先会被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是AbortPolicy。
30、什么是双亲委派机制,它有什么作用?
双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。
通过带有优先级的层级关可以避免类的重复加载;
保证 Java 程序安全稳定运行,Java 核心 API 定义类型不会被随意替换。