1.==和equals区别
如果是基础数据类型的话比较的是值,如果是引用数据类型的话比较的是内存地址。equals在Object中和一样的,如果equals不重写的话跟是一样的作用。但是我们通常都会重写equals,String可以帮我们重写equals方法,重写的方法逻辑就是把字符串的字符拿出来一个一个的比较,如果一样就返回true,反之则返回false。
2.String、StringBuffer、StringBuilder
String 是final修饰的,不可变,每次操作都会产生新的String对象
StringBuffer和StringBuider都是在原对象上操作
StringBuffer是线程安全的,StringBuilder是线程不安全的
性能是:StringBUilder > StringBuffer > String
使用场景:进场需要改变字符串内容的可以使用StringBuffer和StringBuilder
优先使用StringBuilder,如果是在多线程且共享变量时为了保证结果的正确性我们就使用StringBuffer。
3.重载和重写的区别?
重载是发生在同一个类中,同名不同参,参数类型不同,参数个数不同,参数顺序不同,返回的值可以不同,访问的修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表、必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private则子类就不能重写该方法。
4.接口和抽象类的区别
抽象类可以存在普通成员的方法,而接口只能存在pubilc abstract 抽象的方法。
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final 常量类型的。
抽象类只能单一继承,接口可以打破抽象类的单一继承,实现多个。
5.List和Set的区别
List有序,按对象进入的顺序保存对象,可重复,允许多个NULL元素对象,可以使用lterator(迭代器)取数所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。
Set无序,不可重复,最多只能有一个NULL元素对象,取元素时只能用Iterator(迭代器)接口取得所有元素,再逐一遍历各个元素。
6.hashCode与equals
如果两个对象相等,则hashcode一定也是相同的。
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同的hashcode值,他们也不一定是相等的
因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
7.ArrayList和LinkedList区别
ArrayList基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据孩回涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过LinkedList(需要创建大量的node对象,所有可以性能能超过LinkeList)。
LinkedList:基于链表,可以存储再分散的内存中,适合做数据插入及删除操作,适合查询:需要逐一遍历。遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对List重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对List进行遍历,当结果为空时会遍历整个列表。
8.HashMap和HashTable的区别?
区别:
(1)HashMap线程不安全,HashTable线程安全(因为每个方法都是加了synchronized对象锁)
(2)HashMap允许key和value为空NULL,而HashTable不允许。
底层实现:数组+链表实现
jdk8开始链表高度达到8、数组长度超过64的话,链表就会转为红黑树,元素以内部类Node节点存在
数组扩容
9.ConcurrentHashMap原理,jdk7和jdk8版本的区别
jdk7:
数据结构:用ReentrantLock+Segment+HashEntry来保证数据的安全性,其中一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构(类似hashMap的结构)。
元素查询:涉及到二次hash,第一次hash定位到Segment,第二次hash定位到元素所在的链表的头部
锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get方法无需加锁,用volatile保证可见性,不会读到脏数据
jdk8:
数据结构:synchronized+CAS(乐观锁)+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,赋值操作都使用CAS
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
10.何如实现一个IOC容器
配置文件配置包扫描路径
递归包扫描获取.class文件
反射、确定需要交给IOC管理的类
对需要注入的类进行依赖注入
详细操作:
配置文件中指定需要扫描的包路径
定义一些注解,分别表示访问控制层、业务服务层、数据持久化、依赖注入注解、获取配置文件注解
从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象
遍历这个IOC容器,获取到每一个类的实例,判断里面是又依赖其他的类的实例,然后进行递归注入
11.谈谈对IOC的理解
容器概念、控制反转、依赖注入
ioc容器:实际上就是一个map(key,value),里面存的各种对象(在xml文件里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里,扫描到打上上述注解的类还是通过反射创建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。
控制反转:
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,有主动行为变成了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
全部对象的控制权全部上缴给“第三方”IOC容器,所有,IOC容器成了整个系统的关键核心,他起到了一种类似于“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
依赖注入:
“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由ioc容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
12.谈谈你对AOP的理键
系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能(比如说每一个controller,都有自己的接口功能)。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。列如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不合适定位从左到右的关系,列如日志功能。
日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。
13.Spring是什么?
轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起到一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快,更简洁
Spring是一个轻量级的控制反转(ioc)和面向切面(aop)的容器框架
– 从大小与开销两方面而言Spring的都是轻量级的。
--通过控制反转的技术达到松耦合的目的
--提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
--包含并管理应用对象(Bean)的配置和生命周期,这个意义是一个容器。
--将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。
14.描述一下Spring Bean的生命周期?
15.解释一下Spring支持的几种bean的作用域
global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
16.Spring Boot 、Spring MVC和Spring有什么区别
spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更加方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志,异常处理等等。
spring MVC是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端。
springBoot是spring提供的一个快速开发工具包,让程序员能跟方便、更快速的开发Spring+springMVC应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用
17.Spring mvc的九大主要组件?
Handler:也就是处理器(不是九大组件)。它直接应对着MVC中的C也就是Controller层,它具体表现形式由很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler。
HandlerMapping(核心的组件)
initHandlerMappings(context),处理器映射器,根据用户请求的资源url来查找Handler的。在SpringMVC中会由很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行,这就是HandlerMapping需要做的事。
HandlerAdapter(核心的组件)
initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢? 着就是HandlerAdapter要做的事情。
Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
HandlerExceptionResolver
专门处理异常的
ViewResolver
解析ModouAndView(视图模型)
RequestToViewNameTranslator
LocaleResolver
可以国际化资源或者主题(把英文转为中文)
ThemeResolver
用于解析主题。
MultipartResolver
处理文件上传。
FlashMapManager
用来管理FlashMap的,FlashMap主要用于在redirect中传递参数。
18.mybatis的优缺点
优点:
缺点:
19.#{}和${}的区别是什么?
#{}是预编译处理、是占位符,${}是字符串替换、是拼接符。
Mybatis在处理#{}时,会将sql中的#{}替换成?号,调用PreparedStatement来赋值;
Mybatis在处理 时 , 就 是 把 {}时,就是把 时,就是把{}替换成变量的值,调用Statement来赋值;
#{}的变量替换是在DBMS中、变量替换后,#{}对应的变量自动加上单引号
的 变 量 替 换 实 在 D B M S 外 、 变 量 替 换 后 , {}的变量替换实在DBMS外、变量替换后, 的变量替换实在DBMS外、变量替换后,{}对应的变量不会加上单引号
使用#{}可以有效的防止SQL注入,提高系统安全性。
20.索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
21.RDB和AOF机制
RDB:Redis DataBase
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
优点:
缺点:
AOF:Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
AOF文件比RDB更新频率高,有限使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都赔了优先加载AOF
22.Redis的过期键的删除策略
Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示过期时间,键空间是指Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
23.Redis线程模型,单线程为什么快?
Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器 file event handler。这个文件事件处理器,它是单线程的,所有Redis才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个Socket可以能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket放入一个队列中排队,每次从队列中取出一个Socket给事件分派器,事件分派器把Socket给对应的事件处理器。
然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。
单线程快的原因:
24.缓存雪崩、缓存穿透、缓存击穿
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
缓存击穿是指缓存中没有但数据库中由的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,有同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
25.线程的生命周期,线程有哪些状态
线程通常有五种状态,创建,就绪,运行,阻塞和死亡状态。
阻塞的情况又分为三种:
(1)等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法。
(2)同步阻塞:运行的线程再获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
(3)其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep状态超时、join等待线程终止或者超市、或者I/O处理完毕时,线程重新转入就绪状态。sleep时Thread类的方法。
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直接线程进入就绪状态,才有机会转到运行状态。
死亡状态:(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。
26.sleep()、wait()、join()、yield()的区别
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当面前的线程释放同步锁后锁池中的线程取竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会取竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。
1、sleep是Thread类的静态本地方法,wait则是Object类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
sleep就是把cpu的执行资格和执行权释放出去,不在允许此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。
6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。
yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所有又可能cpu下次进行线程调度孩回让这个线程获取到执行权继续执行。
join()执行后线程进入阻塞状态,列如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程。
27.说说你对线程安全的理解
不是线程安全,应该叫内存安全,推是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显示的分解和释放。
目前主流操作系统都是多任务的,及多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这个由操作系统保障的。
在每个进程的内存空间中都会由一个块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
28.并发、并行、串行的区别
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行在时间上是重叠的,连个任务在同一时刻互不干扰的同时执行。
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行。
29.什么是面向对象?
面向对象编程(Object-Oriented Programming,OOP)
面向对象的本质就是:以类的方式组织代码,以对象的组织(封装)数据。
抽象
三大特性
从认识论角度考虑是现有对象后有类。对象,是具体的事务。类,是抽象的,是对对象的抽象
从代码运行角度考虑是先有类后又对象。类是对象的模板。