Java初级开发技术面试题
1.请说一下String和StringBuffer、StringBuilder的区别是什么?
线程安全性:
String中的对象是不可变,线程安全
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的
性能:
String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
2.Java的堆、栈、方法区(也叫常量池)理解?
堆: 1.存放new出来的东西(对象、数组等) 2.可以被所有的线程共享,不会存放别的对象的引用.
栈: 1.存放基本变量(会包含这个基本类型的具体数值) 2.引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区: 1.可以被所有的线程共享 2.包含了所有的class和static变量.
3.Java中的和toString方法和equals方法的区别?
== : 用来判断两个变量是否相等,1.如果是基本类型,则只要值相等,判断的结果就是true. 2.如果是比较的引用类型,那么只有在两个变量指向的是同一对象时才是true
equals: 默认情况下内部就是比较,所以如果不重写的话,比较结果和==比较一样,在String中已经重写了equals方法.
toString : 该方法属于Object,Object是所有类的顶级父类,都继承了toString方法,在实际使用的时候需要重写toString方法然后实现内容的直接输出.
4.ArrayList和LinkedList的区别?
List是一个接口,他继承Collection接口,代表有序的队列。
ArrayList、LinkedList是是List的实现类.
ArrayList是实现了基于动态数组的数据结构,线程是不安全的,而LinkedList是基于链表的数据结构
对于随机访问的get和set,ArrayList要优于LinkedList,因为LinkedList基于指针的移动
对于添加和删除操作add和remove,一般LinkedList要比ArrayList快,因为ArrayList要移动数据。(而数组是通过下角标)
当添加数据量很小时,两者区别不太大,当添加的数据量大时,大约在容量的1/10之前,LinkedList会优于ArrayList,在其后就劣与ArrayList,且越靠近后面越差。所以个人觉得,一般首选用ArrayList,由于LinkedList可以实现栈、队列以及双端队列等数据结构,所以当特定需要时候,使用LinkedList,当然咯,数据量小的时候,两者差不多,视具体情况去选择使用;当数据量大的时候,如果只需要在靠前的部分插入或删除数据,那也可以选用LinkedList,反之选择ArrayList反而效率更高。
6 . java中为啥内部类可以访问外部类的成员?
编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用.
一个类需要用到某个接口的方法,需要将类A和接口B的实现关联起来,最简单的方法是在类A中创建一个对于接口B的实现类C的实例,但是用这种方法显然两者的依赖太大.稍微好一点的方式是使用工程模式,实例类通过工厂类创建,减少了类A与接口B的实现类C的依赖,但是创建工厂类也不是很方便.而IOC相当于一个大的bean工厂,使用IOC后,只在类A中定义好用于关联接口B的实现方法,将类A,接口B和接口B的实现放在IOC容器中,通过一定的配置由容器来实现类A与接口B的实现C的关联.
8.java接口为什么不能实例化?
首先,我们需要明白实例化的含义。实例化实际意义是在jvm的堆中开辟出一块内存空间,比如Student s = new Student();此处声明Student对象s,并且实例化一个Student对象,实则是在堆中开辟出一块空间来存放Student对象,s则是指向这块空间,也就是内存中的一块地址。这块地址中所存放的值就是我们这个Student对象的一些属性。
而接口中允许有什么:静态的属性以及方法声明。 接口中所有的东西的具体值都是存放在代码去和静态数据区的,所以接口的这块地址上并没有任何实际的值需要存储,那么为什么要给他一块地址来浪费空间呢。其实用底层代码在内存中开辟出一块空间很容易,那么为什么java设定不允许接口实例化呢,以我的理解而言,那就是接口的实例化没有任何实际意义,只会占用一块内存空间,却不会在这块空间中放任何实际的值,所以java主动去规避掉了这个问题.
9.谈谈你对异常的理解?
异常处理机制可以说是让我们编写的程序运行起来更加的健壮,无论是在程序调试、运行期间发生的异常情况的捕获,都提供的有效的补救动作,任何业务逻辑都会存在异常情况,这时只需要记录这些异常情况,抛出异常,绝不能生吞异常,不要再finally中处理返回值。
拓展:
什么是编译时异常,什么是运行时异常?
运行时异常可以通过改变程序避免这种情况发生,比如,除数为0异常,可以先判断除数是否是0,如果是0,则结束此程序。从继承上来看,只要是继承RunTimeException类的,都是运行时异常,其它为编译时异常。
二编译时异常和运行时异常的区别
使用抛出处理方式处理异常时,对于编译时异常,当函数内部有异常抛出,该函数必须声明,调用者也必须处理,运行时异常则不一定要声明,调用者也不必处理;编译时异常必须在函数中声明,调用者也必须在函数中处理.
常见的编译时异常
1.FileNotFoundException
2.ClassNotFoundException
3.SQLException
4.NoSuchFieldException
5.NoSuchMethodException
6.ParseException
常见的运行时异常
1.NullPointerException
2.ArithmeticException(算术异常)
3.ClassCastException(类转换异常)
4.ArrayIndexOutOfBoundsException(数组溢出边界)
5.StringIndexOutOfBoundsException(字符串溢出边界)
此处问题考的是springboot的一些注解以及springboot的启动流程,大致总结为以下步骤:
启动时候 :
1.会创建一个springApplication对象实列,调用springApplication的实列方法,在springApplication实列初始化的时候会做三件事情
第一件:判断classpath下查找并判断与之对应的ApplicationContext类型,并且创建出来这个ApplicationContext
第二件:springFactionLaoder在classpath下找出并加载所有可用的applicationContextInitializer
第三件:springFactionLaoder在classpath下找出并加载所有可用的applicationListener ,找出main方法的定义类
2.springApplication实例初始完成后,开始执行run方法逻辑,首先会遍历所有的springFactions查到的springApplicationRunListener,接着调用started方法,告诉springApplicationRunListener执行
3 创建并配置当前springboot应用要使用的环境(environment)
4.遍历所有的environment,并调用prepered方法,告诉springboot环境已经准备好了
5.根据设置springContext的class类型以及初始化阶段的推断结果,来决定当前spingboot应用创建什么类型的applicationContext,并且把我们的的环境设置进去,然后创建出这个applicationContext
6.再次执行springFactionLaoder在classpath下找出并加载所有可用的applicationContextInitializer
7.springFactionLaoder在classpath下找出并加载所有可用的applicationListener
8,.通过@enableAutoconfiguration获取所有的配置以及IOC容器配置加载到我们准备好的applicationContext
9.遍历所有的applicationListener ,调用contextLoader的方法
10.调用applicationContext的refresh()方法,对我们的IOC容器初始化完成
11.在applicationContext中查找是否有注册的commandeLineRuner,有就遍历
12.最后一步就是遍历springApplicationRunListener调用finished()方法,有异常会抛出来,整个过程结束。
11.说说你知道的springBoot常用的注解?
@PathVaribale 获取url中的数据
@RequestParam 获取请求参数的值
@GetMapping 组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写
@RestController是@ResponseBody和@Controller的组合注解。
@SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解
其中 @ComponentScan让spring Boot扫描到Configuration类并把它加入到程序上下文。
@EnableAutoConfiguration 自动配置。
@ComponentScan 组件扫描,可自动发现和装配一些Bean。
@Component可配合CommandLineRunner使用,在程序启动后执行一些基础任务。
@RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器。
@Configuration:相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。
@Autowired:自动导入依赖的bean
@Service:一般用于修饰service层的组件
@Repository:使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。
@Bean:用@Bean标注方法等价于XML中配置的bean
@Value:注入Spring boot application.properties配置的属性的值
12.说说springMVC实现原理?
1.当客户端(浏览器)发送请求,直接请求到DispatcherServlet。
2.DispatcherServlet会根据请求信息来调用HandlerMapping,解析请求对应的Handler。
3.解析到对应的Handler后,开始由HandlerAdapter适配器处理。
4.HandlerAdapter会根据Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
5.处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。
6.ViewResolver会根据逻辑View查找实际的View。
7.DispaterServlet把返回的Model传给View。
8.通过View返回给请求者(浏览器).
13.描述一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件.
14.谈谈sleep()方法wait()方法,它们有什么区别?
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。
wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。(记住这个主要考察sleep 不会放弃锁, wait是放弃锁)
15.在性能测试中发现cpu占用过高应该如何进行分析?
1.查找进程( top查看进程的占用资源情况,可以很明显看出java的那个进程占用的过高cpu)
2. 查找线程( 使用 top -H -p 查看线程占用情况)
3.查找java的堆栈信息 (将线程id转换成十六进制)
4.使用jstack查询线程的堆栈信息( jstack |grep -a 线程id(十六进制) )
2.已提交读 (在一个事务修改数据过程中,如果事务没有进行提交,其他事务不能读取该数据。会出现不可重复读的现象.
3.可重复读( 事务A在读取数据的过程中,事务B也可以对相同数据进行读取,但是不能进行修改直到事务A事务结束后,才会释放共享锁,这时事务B才可以增加排他锁,对数据进行修改。会出现幻读的现象.)
4.序列化 ( 事务A在读取A表时,增加了表级共享锁,此时事务B也可以读取A表,但是不能进行任何数据的修改,直到事务A事务结束。随后事务B可以增加对A表的表级排他锁,此时事务A不能读取A表中的任何数据,更不能进行修改.)
17.请简单解释下脏读、不可重复读、幻读?
脏读:事务A修改某个数据,同时,事务B来读取,此时,事务A在进行回滚,此时,事务B读取的是事务A回滚前的数据,即出现脏读。
不可重复读:事务A读取A数据,事务B也读取A数据并修改了这个A数据,事务A在读取A数据会发现两次读取的不一样。
幻读:事务A修改了表A的所有数据,同时事务B向A表插入一条数据,事务A在读A表,会发现还有一条没有修改的数据,就像出现了幻觉一样。
18.请问sql的书写顺序就是它的执行顺序吗?
不是,sql的书写顺序跟执行顺序不一样的.
书写顺序:
SELECT →FROM → JOIN → ON → WHERE → GROUP BY → HAVING → ORDER BY→ LIMIT
执行顺序:
FROM → JOIN → ON → WHERE → GROUP BY → HAVING → SELECT → ORDER BY→ LIMIT
19.请简述一下存储引擎MyISAM与InnoDB 区别与作用?
MyISAM(5.5 版本前默认引擎)
只支持表级锁,不支持行锁。用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁 (读取时对需要读到的所有表加锁,写入时则对表加排他锁;)
不支持事务,不支持外键,不支持崩溃后的安全恢复
InnoDB (5.5 版本后默认引擎)
支持行锁,采用MVCC来支持高并发,有可能死锁,支持事务,支持外键,支持崩溃后的安全恢复
作用:
1. MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。
20.关于sql优化你如何做的?
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描.
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,
5.in 和 not in 也要慎用,否则会导致全表扫描,
6.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。
如: select id from t where num/2=100 应改为: select id from t where num=1002
7.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
8.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
9.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
10.任何地方都不要使用 select * from t ,用具体的字段列表代替“”,不要返回用不到的任何字段
21.请简述动态代理的几种实现方式,它们分别是什么以及区别?
在java中,动态代理有两种主要的实现方式,分别为:JDK 动态代理和 CGLIB 动态代理.
JDK 动态代理就是基于 JDK 实现的代理模式,主要运用了其拦截器和反射机制,其代理对象是由 JDK 动态生成的,而不像静态代理方式写死代理对象和被代理类。JDK 代理是不需要第三方库支持的,只需要 JDK 环境就可以进行代理,使用条件:
1.被代理的对象必须要实现接口;(可以直接说这一句,)
2.使用Proxy.newProxyInstance产生代理对象;
3.必须实现InvocationHandler接口;
CGLIB 动态代理利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1.被代理的对象不用实现接口;(可以直接说这一句,
2.CGLIB 必须依赖于 CGLIB 的类库,其为需要被代理的类生成一个子类,覆盖其中的方法,实际上是一种继承
主要区别:
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类;
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的.
23.你了解redis的持久化吗?简单概叙一下?
RDB(半持久化方式):按照配置不定期的通过异步的方式、快照的形式直接把内存中的数据持久化到磁盘的一个dump.rdb文件(二进制的临时文件)中,redis默认的持久化方式,它在配置文件(redis.conf)中。
优点:只包含一个文件,将一个单独的文件转移到其他存储媒介上,对于文件备份、灾难恢复而言,比较实用。
缺点:系统一旦在持久化策略之前出现宕机现象,此前没有来得及持久化的数据将会产生丢失
AOF(全持久化的方式):把每一次数据变化都通过write()函数将你所执行的命令追加到一个appendonly.aof文件里面,Redis默认是不支持这种全持久化方式的,需要在配置文件(redis.conf)中将appendonly no改成appendonly yes
优点:数据安全性高,对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机问题,也不会破坏日志文件中已经存在的内容;
缺点:对于数量相同的数据集来说,aof文件通常要比rdb文件大,因此rdb在恢复大数据集时的速度大于AOF;
加分项(面试官要是感兴趣可以在说下面这些)
在Redis的配置文件中存在三种同步方式,它们分别是:
二种持久化方式区别:
1.AOF在运行效率上往往慢于RDB,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效;
2.如果缓存数据安全性要求比较高的话,用aof这种持久化方式(比如项目中的购物车);
3.如果对于大数据集要求效率高的话,就可以使用默认的。而且这两种持久化方式可以同时使用。
缓存雪崩:
当缓存服务器重启或者大量缓存集中在一段时间内失效,发生大量的缓存穿透,这样在失效的瞬间对数据库的访问压力就比较大,所有的查询都落在数据库上,造成了缓存雪崩。
缓存雪崩解决方案:
1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2.可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
3.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
4.做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
cookie和session的区别,分布式环境怎么保存用户状态况?
Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器,是一种在客户端保持状态的方案第二种:session持久化到数据库
原理:就不用多说了吧,拿出一个数据库,专门用来存储session信息。保证session的持久化。
优点:服务器出现问题,session不会丢失
缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。
26.Spring是如何处理循环依赖的?
Spring中主要是采用了三级缓存的一种模式来解决循环依赖,内在就是三个缓存的map结构。他们分别是singletonObjects,singletonFactories,earlySingletonObjects。
singletonObjects:一级缓存,存储beanName(key)和bean实例(value)之间的关系,这里存储的bean实例是已经完全创建完成的bean实例
earlySingletonObjects:二级缓存,也是存储beanName和bean实例之间的关系,注意和singletonObjects的区别,这里存储的bean实例是没有创建完成的bean实例,即该bean还在创建过程中,为了解决循环引用的问题,将未创建完全的bean缓存起来。
singletonFactories:三级缓存,用于保存beanName和bean工厂之间的关系。当三级缓存创建bean成功后,会将bean放入二级缓存,并将beanName对应的beanFactory从singletonFactories中移除。
ObjectFactory:有getobject()方法,在创建单例bean的过程中,为了解决循环依赖问题,会创建beanName对应的ObjectFactory放入SingletonFactoies中,达到提前曝光bean的目的。三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
27.为什么要三级缓存?
getEarlyBeanReference中支持AwareBeanPostProcessor这样的后置处理器,给用户提供接口扩展的。
28.SpringBoot如何解决跨域问题?
方法级别的跨域访问
SpringBoot提供了一种简单的声明式方法来实现跨域请求,使用@CrossOrigin注解,来启用允许跨域访问某些接口
@CrossOrigin(origins=”*”,maxAge=3600)//允许所有域名访问,3600访问有效期
全局跨域访问
可以通过使用自定义的addCorsMappings(CorsRegistry)方法注册WebMvcConfigurer bean来定义全局CORS配置
@Configuration
Public class MyConfiguaration{
@Bean
Public WebMvcConfigurer corsConfigurer(){
@Override
Public void addCorsMappings(CorsRegistry registry){
Registry.addMapping(“/api/**”);
}
}
}
29.请问JVM 有哪些基本的垃圾回收算法?
一、按照基本回收策略
1.引用计数 :
对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象(无法解决循环引用)
2.标记-清除
此算法分为两个阶段.第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把没有标记的对象清除(需要暂停整个应用,同时会产生碎片)
3.复制
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中(每次只处理正在使用中的对象,不会出现“碎片”问题。就是需要两倍内存空间)
4.标记-整理
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。避免了碎片问题,也避免了“复制”算法的空间问题。
二、按分区对待的方式
1.增量收集
实时垃圾回收算法,即在应用进行的同时进行垃圾回收
2.分代收集
基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的.
三、按系统线程
1.串行收集
串行收集使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。
2.并行收集
并行收集使用多线程处理垃圾回收工作,速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。
3.并发收集
相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。
请问JVM 如何解决同时存在的对象创建和对象回收问题?
1.垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存,从这点看,两者是矛盾的。
2.因此,在现有的垃圾回收方式中,要进行垃圾回收前,一般都需要暂停整个应用(即:暂停内存的分配),然后进行垃圾回收,回收完成后再继续应用。(缺点:当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大。就是一些对相应时间要求很高的应用,比如最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就很有可能超过这个限制,在这种情况下,垃圾回收将会成为系统运行的一个瓶颈。
3.并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时,“碎片”问题将会比较难解决.
因此,针对不同引用做不同的调优处