目录
面试经历 --- (随缘更新)
/        

面试经历 --- (随缘更新)

面试题(经历)

事务的四大特性:原子性(Atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。

数据库引擎类型

你能用的数据库引擎取决于MySQL安装的时候是如何被编译的。要添加一个新的引擎,就必须重新编译MySQL。在缺省情况下,MySQL支持三个引擎:ISAM、MYISAM和HEAP。另外两种类型INNODB和BERKLEY(BDB),也常常可以使用。

ISAM

ISAM是一个定义明确且经历时间考验的数据表格管理方法,它在设计之时就考虑到数据库被查询的次数要远大于更新的次数。因此,ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。ISAM的两个主要不足之处在于,它不支持事务处理,也不能够容错:如果你的硬盘崩溃了,那么数据文件就无法恢复了。如果你正在把ISAM用在关键任务应用程序里,那就必须经常备份你所有的实时数据,通过其复制特性,MySQL能够支持这样的备份应用程序。

MYISAM

MYISAM是MySQL的ISAM扩展格式和缺省的数据引擎。除了提供ISAM里没有的索引和字段管理的大量功能,MYISAM还使用一种表格锁定机制,来优化多个并发的读写操作。其代价是你需要经常运行OPTIMIZE TABLE 命令,来恢复被更新机制所浪费的空间。MYISAM还有一些有用的扩展,例如用来修复数据库文件的MYISAMCHK工具和用来恢复浪费空间的MYISAMPACK工具。

MYISAM强调了快速读取操作,这可能就是为什么MySQL收到了WEB开发如此青睐的主要原因:在WEB开发中你所进行的大量数据操作都是读取操作。所以大多数虚拟机提供商和INTERNET平台提供商只允许使用MYISAM格式。

HEAP

HEAP允许只驻留在内存里的临时表格。驻留在内存里让HEAP要比ISAM和MYISAM都快,但是它所管理的数据是不稳定的,而且如果在关机之前没有进行保存,那么所有的数据都会丢失。在数据行被删除的时候,HEAP也不会浪费大量的空间。HEAP表格在你需要使用SELECT表达式来选择和操控数据的时候非常有用。要记住,在用完表格之后就删除表格。

INNODB和BERKLEYDB

INNODB和BERKLEYDB(BDB)数据库引擎都是造就MySQL灵活性的技术的直接产品,这项技术就是MySQL++API。在使用MySQL的时候,你所面对的每一个挑战几乎都源于ISAM和MYISAM,它们不支持事务处理也不支持外来键。INNODB尽管要比ISAM和MYISAM引擎慢很多,但是INNODB和BDB包括了对事务处理和外来键的支持,这两点都是前两个引擎所没有的 。如前所述,如果你的设计需要这些特性中的一者或者两者,那你就要被迫使用后两个引擎中的一个了。

InnoDB和Mylsam的对比

  1. count运算上的区别:

    因为MyLSAM缓存有表 meta-data(行数等),因此在做count(*)时,对于一个结构很好的查询是不需要消耗多少资源的。对于InnoDB来说,则没有这种缓存

  2. 是否支持事务和崩溃后的安全恢复:

    MyISAM强调的是性能,每次查询具有原子性,其执行行数度比InnoDB类型更快,但是不支持事务。但是InnoDB提供事务支持事务,外部键等高级数据库功能。具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant) )型表。

  3. 是否支持外键:

    MyISAM不支持,而InnoDB支持

  4. 是否支持行级锁:

    MyISAM只有表级锁(table-level locking),而InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。

关于两者的总结

MyISAM更适合读密集的表,而InnoDB更适合写密集的表。在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。

一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM 的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。

Spring 七大组件

1. 核心容器(Spring Core)

核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用的配置和依赖性规范与实际应用程序代码分开。BeanFactory使用依赖注入的方式提供给组件依赖。

2. Spring 上下文(Spring Context)

Spring 上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

3. Spring 面向切面编程(Spring AOP)

通过配置管理特性,Spring AOP模块直接将面向方面的编程功能集成到了Spring框架中。所以,可以很容易使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不依赖EJB组件,就可以将声明性事务管理集成到应用程序中。

4. Spring DAO模块

DAO模式主要目的是将持久层相关问题与一般的业务规则和工作流程隔离开来。Spring中的DAO提供一致的方式访问数据库,不管采用何种持久化技术,Spring都提供一致的编程模型。Spring还对不同的持久层技术提供一致的DAO方式的异常层次结构。

5. Spring ORM模块

Spring与所有的主要的ORM映射框架都集成的很好,包括Hibernate、JDO实现、TopLink和iBatis SQL Map等。Spring为所有的这些框架提供了模块之类的辅助类,达成了一致的编程风格。

6. Spring Web模块

Web 上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。Web层使用Web层框架,可选的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts2、Webwork、tapestry和jsf。

7. Spring MVC框架(Spring WebMVC)

MVC 框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。

Spring MVC的执行流程

1. 什么是Spring MVC?简单介绍你对Spring MVC的理解?

Spring MVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级框架,通过把Model、View、Controller分离,将Web层进行解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。

2. Spring MVC的执行流程

(1) 用户发送请求至前端控制器DispatcherServlet

(2) DispatcherServlet 收到请求后,调用HandlerMapping处理映射器,请求获取Handler;

(3) 处理器映射器根据请求url找到具体的处理器,生成的处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet

(4) DispatcherServlet调用HandlerAdapt请求适配器。

(5) HandlerAdapter经过适配调用 具体处理器(Handler,也叫后端控制器)

(6) Handler执行完成返回ModelAndView

(7) HandlerAdapter将Handler执行结果ModelAndVie返回给DispatcherServlet

(8) DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析。

(9) ViewResolver解析后返回具体View

(10) DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)

(11) DispatcherServlet响应用户。

方法的重载和重写的区别:

重载:方法名相同,参数的个数、类型、以及类型的顺序不同。

重写:方法名相同,参数的个数、类型都一样,返回值类型也一样。方法的核心代码可以不一样。

父类和子类异常的处理

https://blog.csdn.net/qq_25827845/article/details/85109390

java的中异常祖先是Throwable,Throwable的直接子类是Exception和Error。

Error通过单词我们就知道,是错误的意思,这种错误一般是jvm运行产生的错误,出现这种错误,我们的程序不能解决,比如内存溢出oom,堆溢出等。这种错误,我们不必处理,直接让jvm抛出报错,我们没办法解决就不管了。

Exception中文意思是异常,那么Exception又分为检查性异常和非检查性异常。比如RuntimeException类及子类就是非检查性异处,表示运行时出现的异常,有数组越界,空指针异常,我们也可以不进行处理,让jvm自己抛出异常,当然如果我们可以预见这种异常的话,最好在程序中进行判断检查,程序写健壮些,有的这种异常就可以避免了。effect java有这种处理的推荐,具体的可以看看这本书。

**Exception还有一类是检查性异常,这是除RuntimeException类及子类外的Exception类和Exception类的其他子类。检查性异常,必须要进行异常处理的或者抛出,否则编译器会报错。 **

一个类如果实现了抽象类或者接口中的抽象方法,那么实现该方法能否抛出异常由抽象方法决定。

当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆盖。

可以这么理解:重写就是指**子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。**这样,就可以实现对父类方法的覆盖。

UTOOLS1595743285336.png

方法重写的时候,如果父类没有抛出任何异常,那么子类只可以抛出运行时异常,不可以抛出编译时异常。

如果父类的方法抛出了一个异常,那么子类在方法重写的时候不能抛出比被重写方法申明更加宽泛的编译时异常。

子类重写方法的时候可以随时抛出运行时异常,包括空指针异常,数组越界异常等。

接口和继承的作用和区别

继承:提高了代码的复用性、提高了代码的维护性、让类与类之间产生了一个关系、是多态的前提、可以使方法更加的完善

接口:主要是扩展功能、主要针对方法,描述对象的行为、更加灵活,便于扩展

接口是:对功能的描述 继承是:什么是一种什么

始终记者:你可以有多个干爹(接口),但只能有一个亲爹( 继承)

举例:

  如果狗的主人只是希望狗能爬比较低的树,但是不希望它继承尾巴可以倒挂在树上,像猴子那样可以飞檐走壁,以免主人管不住它。

那么狗的主人肯定不会要一只猴子继承的狗。

  设计模式更多的强调面向接口。猴子有两个接口,一个是爬树,一个是尾巴倒挂。我现在只需要我的狗爬树,但是不要它尾巴倒挂,那么我只要我的狗实现爬树的接口就行了。同时不会带来像继承猴子来带来的尾巴倒挂的副作用。这就是接口的好处。

请求转发和重定向的区别

  1. 数据共享:请求转发可以共享request里的数据,重定向不能共享
  2. 地址栏显示:请求转发 url不会发送变化,而重定向会发生改变。
  3. 效率:请求转发的效率 比 重定向 要高。
  4. 请求转发 是 一次请求,而重定向是两次请求。

get 和 post请求有哪些区别?

  1. get 请求会被浏览器主动缓存,而post不会
  2. get 传递参数有大小限制,而post没有
  3. post参数传输更安全,get的参数会明文限制在url上,post不会。

说一下你熟悉的设计模式?

  1. 单例模式:保证被创建一次,节省系统开销
  2. 工厂模式(简单工厂、抽象工厂):解耦代码
  3. 观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖都会收到通知并自动更新。
  4. 外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。
  5. 模板方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。
  6. 状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

spring的事务隔离

  1. ISOLATION_DEFAULT: 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别.

另外四个与 JDBC的隔离级别相对应:

  1. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它允许令外一个事务可以看到这个事务未提交的数据,

这种隔离级别会产生脏读,不可重复读和幻像读。

  1. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
  2. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
  3. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

除了防止脏读,不可重复读外,还避免了幻像读。

其中的一些概念的说明:

脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一 个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。 那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻觉读:指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及 到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,就会发生操作第一个事务的用户发现表中还有 没有修改的数据行,也就是说幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读,就好象发生了幻觉一样。

数据库的三种范式是什么?

第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项

第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。

第三范式: 任何非主属性不依赖于其它非主属性。

MySQL 的内连接、左连接、右连接有什么区别?

  • 内连接关键字:inner join
  • 左连接:left join
  • 右链接:right join

内连接就是把匹配的关联数据显示出来

左连接是左边的表全部显示出来,右边的表显示符合条件的数据

右连接是右边的表全部显示出来,左边的表显示符合条件的数据

说一下 MySQL的行锁和表锁?

MyISAM 只支持表锁,InnoDB 支持表锁和行锁,默认为行锁。

表级锁: 开销小,加锁快,不会出现死锁。锁定力度大,发生锁冲突的概率最高,并发量低。

行级锁: 开销大,加锁慢,会出现死锁。锁定力度小,发生锁冲突的概率小,并发度最高。

说一下乐观锁和悲观锁?

乐观锁: 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。

悲观锁: 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到整个锁被释放。

数据库的乐观锁需要自己实现,在表的里面添加一个version字段,每次修改成功加1,这样每次修改的时候先对比一下,自己拥有的version和数据库现在的version是否一致,如果不一致就不修改,这样就实现了乐观锁。

说一下堆栈的区别?

功能方面:堆是用来存放对象的,栈是用来执行程序的

共享性:堆是线程共享的,栈是线程私有的。

空间大小:堆的大小远远大于栈。

java中包的作用

为了避免相同的类名带来的问题,java中采用了包的方法解决这一问题!

Spring Boot 内嵌插件

MyBatis和JPA的区别

git命令

查看提交记录,显示为树状

git log --oneline --graph --decorate --all

合并分支 no fast-forward

git merge --no-ff

把文件从暂存区拉到工作区

git checkout 文件名

回退到指定版本

git reset –-soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可;

git reset -–hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的commit中所包含的更改被冲掉;

git reset 版本id

回退到指定版本,硬恢复,即对应文件数据也回退

git reset --hard 版本id

删除分支

git branch -d 分支的名字

删除暂存区的文件

git rm -r --cached ...

-r 是递归的意思 当最后面是文件夹的时候有用,如果只删除一个文件,

git rm --cached 文件名

git branch –-merged 它列出了已合并到当前分支的分支。

git branch –-no-merged 它列出了尚未合并的分支。

UTOOLS1595654880541.png

原始的树状图

UTOOLS1595657348293.png

使用rebase之后的树状图

UTOOLS1595657388535.png

MySQL 基础知识

(Data Query Language) DQL(数据查询语言):查询语句,凡是select 语句都是DQL。

(Data Manipulation Language) DML(数据操作语言):insert delete update ,对表中的数据进行增删改

(Data Definition Language) DDL(数据定义语言): create drop alter 对表结构的增删改

(Transaction Control Language) TCL(事务控制语言):commit提交事务、rollback回滚事务

(Data Control Language) DCL(数据控制语言):grant授权、revoke撤销权限等。

查看版本号

select version();

查看数据库名字

select database();

条件查询

语法格式:

select

字段, 字段...

from

表名

where

条件;

执行顺序:先from、然后where、最后select

between ... and ... 是闭区间 [a, b]; 左小右大。

模糊查询

  • % 代表任意多个字符
  • _ 代表任意1个字符

找出名字中第二个字母是A的?

select ename from emp where ename like '_A%';

创建一个 t_user 表。

create table t_user (
    id int(10) primary key auto_increment,
    name varchar(20),
    age int(4));

插入数据

insert into t_user (name,age) values ('zhangsan', 14);
insert into t_user (name,age) values ('li_si', 16);
mysql> select * from t_user where name like '%_%';
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | zhangsan |  14 |
|  2 | li_si    |  16 |
+----+----------+-----+

这里 '_' 代表匹配任意一个字符。

使用转义字符 \

mysql> select * from t_user where name like '%\_%';
+----+-------+-----+
| id | name  | age |
+----+-------+-----+
|  2 | li_si |  16 |
+----+-------+-----+
1 row in set (0.03 sec)

排序(升序、降序)

默认是升序。指定升序 asc、desc表示降序

select * from emp order by sal;

降序 desc

select * from emp order by sal desc;

升序

select * from emp order by sal asc;

按照工资的降序排列,当工资相同的时候再按照名字的升序排列。

select * from emp order by sal desc, ename asc;

排序时,越靠前的主导字段,越能起到主导作用。只有当前面的字段无法完成排序的时候,才会启用后面的字段。

分组函数

分组函数不可直接使用在where子句中

count 计数

select count(字段) ...

查询的是 该字段不为空的 记录数

count(*)

不是统计某个字段中数据的个数,而是统计总记录数。(和某个字段无关)

sum 求和

avg 平均值

max 最大值

min 最小值

所有的分组函数都是对 某一组 数据进行操作的。

==分组函数自动忽略null==

单行处理函数

  • 输入一行,输出一行

ifnull()空处理函数

可能为null的数据,被当作做什么处理

select ename, ifnull(sal, 0) from emp;

如果该字段为NULL了话,会自动赋值为0。

group by 和 having

group by: 按照某个字段或者某些字段进行分组

having : having是对分组之后的数据进行再次过滤。

执行顺序

select 5

...

from 1

...

where 2

...

group by 3

...

having 4

...

order by 6

...

去重distinct

distinct 只能出现在所有字段的最前面

select ename, distinct job from emp ==该语句是错误的==

==正确的应该是:==

select distinct job, ename from emp;

ConcurrentHashMap和HashTable的区别

  1. 底层数据结构:

JDK1.7的ConcurrentHashMap底层使用分段的数据+链表实现,JDK1.8采用的数据结构和HashMap1.8的结构一样。数据+链表/红黑二叉树。

Hashttable和JDK1.8之前的HashMap的底层数据结构类似都是采用数据+链表的形式,数组是HashMap的主体,链表是为了解决哈希冲突而存在的。

  1. 实现线程安全的方式(重要):

在JDK1.7的时候,ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中的一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

在JDK1.8的时候,已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。

Hashtable(同一把锁),使用synchronsized来保证线程安全,效率低下。当一个线程访问同步方法时,其他线程也访问同步方法时,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,也不能get。

ConcurrentHashMap 的 put方法

    public V put(K key, V value) {
        return putVal(key, value, false);
    }

	final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

HashTable的put方法

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

JAVA中接口存在的意义

img

https://blog.csdn.net/googleoyyp/article/details/78912259

googleoyyp 2017-12-27 14:54:10 img 4758 img 收藏 1

很多JAVA程序员对于接口存在的意义很疑惑。不知道接口到底是有什么作用,为什么要定义接口。好像定义接口是提前做了个多余的工作。下面我给大家总结了4点关于JAVA中接口存在的意义:

1、重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。

2、简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。

3、维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类,可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦,如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。

4、安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。

网上的说法1
接口提供了一个公用的方法提供方。 接口是用来规定子类的行为的。
例如, 比如有个需求, 需要保存客户信息, 有些客户从网站来, 有些从手机客户端来, 有些从后台管理系统录入, 假设不同来源的客户有不同的处理业务流程, 这个时候我定义接口来提供一个保存客户的方法, 然后不同平台实现我这个保存客户的接口, 以后保存客户的话, 我只用知道这个接口就可以了, 具体调用哪个方法 去实例化具体你需要用的类,这也就是JAVA的多态的体现。 而如果你不用接口的话 , 首先我需要哪个方法 我就去实例化哪个类, 冗余很高, 其次扩展很差。 接口就是一个规范, 其子类都会有他提供的方法。 统一管理。

网上的说法2
面相接口编程:
1.根据客户提出的需求提出来,作为接口的;业务具体实现是通过实现接口类来完成的。
2.当客户提出新的需求时,只需编写该需求业务逻辑新的实现类。
3.假如采用了这种模式,业务逻辑更加清晰,增强代码可读性,扩展性,可维护性。
4.接口和实现分离,适合团队协作开发。
5.实现松散耦合的系统,便于以后升级,扩展。

抽象类的特点以及和普通类的区别

final修饰的变量,只能赋一次值。

final修饰对象(引用类型)的时候,该对象的内存地址不能变。

package org.example.test;


abstract class AbastractTest {

    public final String PATH = "ws://socket";

    static {
        System.out.println("爷爷 的静态代码块。。。");
    }

    AbastractTest () {
        System.out.println("爷爷的构造方法 实例化。。。");
    }

    abstract void hello ();

    abstract void eat ();

    void test () {
        System.out.println("test 方法。。。");
    }

}



/**
 * @Author:
 * 抽象类学习:
 *  - 抽象类的特点:
 *          1. 抽象方法和抽象类 使用abstract修饰
 *          2. 只要有抽象方法的类,必须是抽象类
 *          3. 抽象类中不一定有抽象方法
 *          4. 没有抽象方法的抽象类没有意义,防止外界创建对象。
 *
 *     防止外界实例的方式:
 *          - 构造方法私有
 *          - 抽象类
 *          - 接口
 *          - 内部类
 *
 *          5. 抽象类不能够实例化,得利用多态
 *
 *          6. 抽象类的子类的特点:
 *              - 如果子类想要继承抽象类,就必须实现抽象类中的所有抽象方法
 *              - 如果子类不想实现父类的抽象方法,那么子类必须升级为抽象类
 *
 *          7. 抽象类中父类的特点:
 *              - 抽象类中:
 *                  = 成员变量:给子类使用
 *                  = 成员方法:给子类使用
 *                  = 构造方法:帮助子类初始化父类
 *                  = 静态方法:直接通过类名访问,防止创建的不建议访问方式
 *                  = 常量:方便访问
 *                  = 抽象类和普通类没有区别,只不过抽象多了抽象方法
 *                  = 抽象类的抽象方法强制子类重写。
 *                  = 非抽象方法直接给子类使用
 *                  = 构造方法和成员变量直接给子类使用,
 *          8. 抽象类是服务类,成员一般使用public或者proteccted
 *          9. private修饰的方法不能被子类继承,更不能被重写,asbtract修饰的方法强制子类重写。
 *              final修饰的方法不能被子类继承,二者冲突。
 *
 * @Date: 2020/7/27 0027 下午 15:11
 */
public abstract class AbstractClassTest extends AbastractTest {

    final String P_PATH = "http://aaa";

    static {
        System.out.println("父亲的 静态代码块");
    }

    AbstractClassTest () {
        System.out.println("父亲的 构造方法初始化。。。");
    }

    @Override
    void hello() {
        System.out.println("儿子 --- 子类重写hello方法。。。");
    }

}

class Main extends AbstractClassTest {

    Main () {
        System.out.println("孙子的构造方法实例化");
    }

    static {
        System.out.println("孙子的静态代码块。。。");
    }

    @Override
    void eat() {
        System.out.println("孙子 --- 子类重写eat方法");
    }

    public static void main(String[] args) {
        Main main = new Main();
        System.out.println("爷爷的局部变量:" + main.PATH);
        System.out.println("父亲的局部变量:" + main.P_PATH);
        main.hello();
        main.eat();
        main.test();
        System.err.println("如果儿子 不想继承 父亲的抽象方法,需要把自己改成抽象类。\n当孙子继承儿子的时候,爷爷还存在抽象方法没有被父亲实现\n 并且孙子不是抽象类,想要继承父亲,就必须要实现爷爷未被实现的方法。");
    }
}

执行结果:

爷爷 的静态代码块。。。
父亲的 静态代码块
孙子的静态代码块。。。
爷爷的构造方法 实例化。。。
父亲的 构造方法初始化。。。
孙子的构造方法实例化
爷爷的局部变量:ws://socket
父亲的局部变量:http://aaa
儿子 --- 子类重写hello方法。。。
孙子 --- 子类重写eat方法
test 方法。。。
如果儿子 不想继承 父亲的抽象方法,需要把自己改成抽象类。
当孙子继承儿子的时候,爷爷还存在抽象方法没有被父亲实现
并且孙子不是抽象类,想要继承父亲,就必须要实现爷爷未被实现的方法。

  1. 首先执行爷爷、父亲、孙子的静态代码块
  2. 然后再执行爷爷、父亲、孙子的构造方法

抽象类和接口的区别

多态的定义以及测试用例

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

package org.example.test;

/**
 * @Author: gitsilence
 * @Date: 2020/7/27 0027 下午 17:27
 */
public class PolymorphicTest {

    public static void main(String[] args) {

        show(new Cat());
        show(new Dog());
        Animal animal = new Cat();  // 向上转型
        animal.eat();

        Cat cat = (Cat) animal;  // 向下转型
        cat.run();

    }

    public static void show (Animal animal) {
        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.run();
        } else if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.sleep();
        }
    }

}

abstract class Animal {
    abstract void eat ();
}


class Cat extends Animal {

    @Override
    void eat() {
        System.out.println("猫吃东西");
    }

    void run () {
        System.out.println("猫在跑。。。。");
    }
}

class Dog extends Animal {

    @Override
    void eat() {
        System.out.println("狗吃东西");
    }

    void sleep () {
        System.out.println("狗在睡觉。。。");
    }
}

Java堆、栈、方法区

链接:https://www.cnblogs.com/eason-chan/p/3644667.html

JVM相关知识

JVM内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈 五个部分。

程序计数器

2022-09-21

CAP原则

指的是在一个分布式系统重,Consistency(一致性)、Availablity(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。

  • Consistency 一致性:即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性。一致性的问题在并发系统中,不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致性。
  • Availability 可用性:即服务一直可用,而且是正常响应时间,好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
  • Partition Tolerance 分区容错性:即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

例如两台服务器A和B,A和B中都一个服务和各自的数据库,假如对A中服务发送修改请求,A要将修改同步到B,反之亦然;如果A和B发生了网络问题,对A中服务发送修改请求,A未成功将修改操作发送到B中,这就导致了A中的数据是最新的,B中的数据是旧的;

如果用户访问了B服务,如果返回旧的数据,就违反了一致性原则,如果阻塞等待,就违反了可用性原则。

对于上述的问题,只能在一致性和可用性两者中,选择其中一个。也就是说分布式系统不可能同时满足三个特性。

CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这就是违背分布式系统设计的初衷。

CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致性,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、Hbase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。

在向一个节点 A 写入数据成功后,并不是马上给客户端响应写成功的信号,而是等待数据同步到其他节点后(个数取决于配置),才响应客户端,表示此次写数据成功了!这个在一定程度上保证了数据一致性,为了防止数据混乱,写数据时只允许往Leader节点写,读数据时可以从所有节点读取。

AP without C :要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据库提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是在 A 可用性方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

向一个节点 A 写入数据c横岗后,立刻给客户端响应写成功的信号。

如果此时集群节点之间网络断开了,由于其可用性,其他节点仍然提供服务,但是 A 节点的数据还未写入到其他节点,当访问除 A 之外的其他节点时,就会出现数据不一致的问题,当网络恢复后,才会通过心跳保证最终一致性。

请问Nacos遵守了CAP中哪两个原则?

各种分布式中间件使用的架构如下:

  • MySQL 单机:CA架构
  • eureka 集群:AP架构
  • zookeeper集群:CP架构
  • nacos集群:AP或者CP架构,可根据实例的(临时、持久)类型,来选择AP或者CP
  • redis集群:主要是AP架构,也可通过配置 min-slaves-to-write=x(大于1哥节点 去模拟CP架构

leader 选举时,要求节点获取到的投票数量 > 总节点数量 / 2,有了这个选举原则,当发生网络分区时,无论如果最多只能有一个小集群选出 leader ,避免集群发生脑裂

集群节点个数为什么推荐是奇数个?

1、在集群启动时,偶数个节点的集群一旦节点对半分区,整个集群无法选出 leader, 集群无法提供服务,无法满足 CAP 中P

2、容错能力相同的情况下,奇数节点比偶数节点更节省资源,比如5个节点,最多挂掉2个节点还能选 leader,6个节点最多也只能挂掉2哥节点才能保证可以选leader。

Nacos 的CP和AP架构的选择,取决于我们配置的服务实例是临时实例还是持久实例

1、临时实例,选择 AP 架构,使用 Distro 协议,分布式协议的一种,阿里内部的协议,服务是放在内存在中。

2、持久实例,选择 CP 架构,使用 Raft 协议来实现,服务是放在磁盘中。Raft协议点击查看

线程池相关

几个详细参数:http://blog.lacknb.cn/articles/2021/06/01/1622557522423.html

Redis 的基本数据类型以及特性

1、String 类型,一个 key 对应一个 value。string 类型是二进制安全的,意思是 redis 的string 可以包含任何数据。比如 jpg 图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,一个键最大能存储 512 MB

  • 缓存功能
  • 计数器
  • 共享用户 session
  • 分布式锁
  • 分布式系统全局序列号

2、Hash

Redis Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象

Redis 中每个 hash 可以存 2^32 - 1 键值对

3、List 列表

4、Set

5、Zset (sorted set 有序集合)


标题:面试经历 --- (随缘更新)
作者:MrNiebit
地址:https://blog.lacknb.cn/articles/2020/10/07/1602035383878.html