Java开发3年应该掌握的小知识(上)
关于基础
1.String,StringBuilder,StringBuffer区别是什么?底层数据结构是什么?分别是如何实现的?
(1)String:是不可变的,同一个变量重新赋值只是新创建一个同名变量进行赋值。用final修饰,实例化后不能被修改。
(2)StringBuffer:对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。
(3)StringBuilder:StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。
不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。
2.HashSet的底层实现是什么?它与HashMap有什么关系?
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,在HashSet中,元素都存到HashMap键值对的Key上面,而Value时有一个统一的值private static final Object PRESENT = new Object();。
HashMap | HashSet |
---|---|
实现了Map接口 | 实现Set接口 |
调用put()方法添加元素 | 调用add()方法向Set中添加元素 |
存储键值对 | 只存储对象 |
HashMap相对HashSet快,是因为它采用唯一的键获取对象 | HashSet比HashMap要慢一些 |
3.Java 的并发包里面有那些知识点?
从jdk1.5以后,Java提供了并发包,用来解决实际工作开发中经常遇到的并发问题。
(1)ConcurrentHashMap:安全版本的HashMap。
(2)CopyOnWriteArrayList:线程安全版本的ArrayList。
(3)CopyOnWriteArraySet:基于CopyOnWriteArrayList实现,CopyOnWriteArraySet不能插入重复数据。
(4)ArrayBlockingQueue:基于数据实现的线程安全的队列服务。
4.HashCode与HashMap的关系?
HashCode() 方法用于返回字符串的哈希码。
HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。
5.map在put()的做了哪些事?
put()方法是map中最长用的方法之一,其目的是将Value放到对应的key中,通过key可获取相对应的Value。
6.如果发生hash碰撞,有什么方法可以解决?
Hash算法并不完美,有可能两个不同的原始值在经过哈希运算后产生同样的结果,既哈希碰撞。解决方法:
开放定址法和链地址。
7.锁的分类有哪些?
公平锁/非公平锁
可重入锁
独享锁/共享锁
互斥锁/读写锁
乐观锁/悲观锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
8.synchronize和lock区别?
1.Lock是一个接口,而synchronized是关键字。
2.synchronized会自动释放锁,而Lock必须手动释放锁。
3.Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
4.通过Lock可以知道线程有没有拿到锁,而synchronized不能。
5.Lock能提高多个线程读操作的效率。
6.synchronized能锁住类、方法和代码块,而Lock是块范围内的。
9.hashCode的原理?
在String类中有个私有实例字段hash表示该串的哈希值,在第一次调用hashCode方法时,字符串的哈希值被计算并且赋值给hash字段,之后再调用hashCode方法便可以直接取hash字段返回。
10.什么情况下需要重写类的hashCode()方法?
未重写之前hashcode方法返回的是对象的32位JVM内存地址,那么当我们把这不同地址但是实际相等的两个对象放进HashMap里面的时候它们不被看成是同一个对象,占据了两个位置。这个跟我们的预期不符,所以要重写hashcode方法。
11.什么时候需要重写equals()方法?
从Object里面继承的equal它只是简单比较了一下两个对象的地址是不是同一个,这很明显不符合我们的需求,所以要我们自己重写。
12.Hashmap的是否线程安全?为什么是安全或者不安全?
HashMap是线程不安全的,原因是在使用put方法时,该方法是不是同步的,如果有两个线程A、B他们的put的key的hash相同,无论是从头插入还是从尾插入,如果A获取插入位置为X,此时B也计算待插入位置为X,无论AB插入的先后顺序肯定有一个会丢失。
13.HashMap、HashTable以及ConcurrentHashMap的区别?
- HashMap:底层数组+链表,线程不安全。允许键和值为null,null键保存在数组0的位置。
- HashTable:底层数组+链表,线程安全。键和值不允许空,键为null的话没有hashCode,值明确判断不为空。线程安全是因为方法中加了sychronized锁。因为锁的是方法,所以效率比较低。
- ConcurrentMap:底层数组+链表,线程安全,相比于HashCode、concurrentMap通过分段锁机制降低了锁的粒度,因此性能相比HashTable要好一些。
14.volatile关键字的理解;用在哪些场景?
当一个变量被定义为volatile之后,就可以保证此变量对所有进程的可见性,当一个线程修改了这个变量,变量新的值对于其他线程来说是可以立刻知道的。可以理解为volatile变量所有的写操作都能立刻被其他线程知道。使用场景是状态位,只有一个线程写,其他线程是读的场景。
15.线程的理解以及其实现方式?
一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立存在,它必须是进程多的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
其中实现线程有两种方式,一种是继承Thread类,另一种是实现Runnable接口。
16.线程池的参数有哪些?实现原理?
参数 | 说明 |
---|---|
corePoolSize | 核心线程数 |
maximumPoolSize | 最大线程数,一般大于等于核心线程数 |
keepAliveTime | 线程存活时间(针对最大线程数大于核心线程数时,非核心线程) |
unit | 存活时间单位,和线程存活时间配套使用 |
workQueue | 任务队列 |
threadFactory | 创建线程的工程 |
handler | 拒绝策略 |
17.线程同步方法有哪些?
同步方法:用synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法
时,内置锁会保护整个方法。调用方法前,需要获取内置锁,否则就处于堵塞状态。
同步代码块:有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
volatile:关键字为域变量的访问提供了一种免锁机制, 使用volatile修饰域相当于告诉JVM该域可能会被其他线程更新, 因此每次使用该域就要重新计算,而不是使用寄存器中的值,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
18.缓存的原理?为什么要用缓存?为什么会比数据库查询快?
把这些数据缓存到内存中。每次操作的时候,先去内存中找,看有没有这些数据,如果有,就直接使用,如果没有,就获取它,然后设置到缓存中,下次访问的时候直接从内存中获取,从而节省大量时间。缓存是典型的以空间换取时间的方案。用缓存有两个用途:高性能、高并发。
19.反射机制的理解?
在运行时构造一个类的对象;判断一个类所具有的成员变量和方法;调用一个对象的方法;生成动态代理。反射最大的应用就是框架。
20.Object中的方法?
equals、getClass、toString、hashCode。
21.String的toString()方法的实现?底层原理?
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。
public String toString() {
return this;
}
22.垃圾回收机制的理解?
在程序运行过程中,会产生一些没用的对象,这些对象占据着一定的内存,如果不对这些对象内存进行清理回收的话,可能会导致内存的耗尽,所以垃圾回收机制回收的是内存,GC回收的是对区和方法区的内存。
JVM回收机制特点:(stop-the-world)当要进行垃圾回收时,不管用哪种GC算法,除了垃圾回收线程之外其他的线程都将停止运行。被中断的任务将会在垃圾回收完毕后恢复运行。GC不同算法或是GC调优就是减少stop-the-world的时间。
23.java8的新特性有哪些?如何使用?
- Lambda表达式:又称闭包,Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用Lambda 表达式可以使代码变的更加简洁紧凑。
- 方法引用通过方法的名字来指向一个方法。方法引用可以使语言的构造更紧凑简洁,减少冗余代码。方法引用使用一对冒号 :: 。
- Java 8 新增了接口的默认方法。简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个default关键字即可实现默认方法。
- Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。
- 在Java8中,Base64编码已经成为Java类库的标准。Java 8 内置了 Base64 编码的编码器和解码器。
- Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
24.static关键字的场景和意义?
- 修饰成员变量和成员方法
- 静态代码块
- 修饰类(只能修饰内部类)
25.如何理解事务?
- 一致性:要成功前后一致,要失败也是前后一致,为了保证数据的一致性。
- 隔离性:事务的隔离性是为解决事务并发访问制造的问题。
- 永久性:事务一旦提交后,对数据的修改是永久的,不能回滚。
26.动态代理的两种实现方式?
- jdk动态代理
- 定义接口
- cglib动态代理
27.悲观锁和乐观锁的区别?
- 悲观锁:每次拿数据都会认为别人会修改,因此在拿数据的时候会上锁,传统的关系型数据库就是用到了锁的机制,如行锁、表锁、读锁、写锁等,都是在操作前上锁,Java中的synchronized就是悲观锁的原理。
- 乐观锁:每次拿数据都会认为别人不会修改,因此不会上锁,但更新时会判断数据是否被修改,这个使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
- 区别:乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
28.如何解决跨域问题?
跨域指的是浏览器不能执行其它网站的脚本,它是由浏览器的同源策略造成的,是浏览器对JavaScript 施加的安全限制。解决办法有:jsonp、cors(跨域资源共享)。
29.xml文件解析方式有几种?
- dom解析
- sax解析
- jdom解析
- dom4j解析
30.synchronize锁?
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),三种应用方式:
- 普通同步方法(实例方法),锁是当前实例对象,进入同步代码前要获得当前实例的锁。
- 静态同步方法,锁是当前类class对象,进入同步代码前要获得当前类对象的锁。
- 同步代码块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
31.秒杀系统的设计?
- 防止作弊:
①检测指定IP请求速率,若发现ip请求速率过高,可以弹出验证码验证或者直接封禁ip
②用户限流,在某一时间段只允许用户提交一次请求。 - 实现原理:
①利用CDN和浏览器缓存进行一级流量拦截
秒杀前因为用户不断刷新商品详情页,我们可以将该页面上的元素尽量静态化处理,除了秒杀按钮需要服务端进行动态判断,其他的静态数据可以缓存在浏览器和 CDN 上。
②利用读写分离Redis缓存拦截流量
将数据库中的库存信息缓存到redis中,所有减库存的操作都放在redis中,当秒杀结束后,再把用户秒杀请求同步到数据库中。
③利用消息队列缓存请求
先把这些请求都缓存到消息队列中,数据库订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
32.分库分表?
- 原因:随着单库中的数据量越来越大,相应的,查询所需要的时间也越来越多,这个时候,相当于数据的处理遇到了瓶颈。单库发生意外的时候,需要修复的是所有的数据,而多库中的一个库发生意外的时候,只需要修复一个库。(处理并发做的那些优化?前后端分离、限流、分库分表)
- 分库分表的常用策略:
①垂直切分:将很多字段的表分为多个表,代价较大,对表的关系要明确,随着数据量增多切分复杂。
②水平切分:数据表结构,将数据分散在多个表中。
③按照id的大小分库分表
④取模分库分表
33.Spring底层是如何实现的?
- 反射机制:spring底层实现机制,通过反射实现注入,IOC控制反转。
- 动态代理:事务控制,通过声明方式进行,AOP,底层就是JAVA设计模式中的动态代理。
34.Tomcat容器启动的时候,spring的启动流程?
tomcat启动时会通过StandardContext.listenerStart() 初始化spring的监听器,流程如下:
- tomcat在启动web容器的时候会启动一个叫ServletContextListener的监听器。
- 每当在web容器中有ServletContextListener这个接口被实例化的时候,web容器会通知ServletContextListener被实例的对象去执行其contextInitialized()的方法进行相应的业务处理,而spring框架在设计的过程中ContextLoadListener这个类实现了ServletContextListener这个接口,因此每当有ContextLoadListener这个类被实例化的时候,web容器会通知他执行contextInitialized()这个方法。
34.rpc框架?
rpc即远程进程调用,本地机器调用远程的服务,在项目规模大到一定程度,需要使用RPC相关框架进行服务化部署。如:hessian 、webservice等。
35.事务的隔离级别和传播机制?
数据库事务 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
七种传播机制:
- PROPAGATION_REQUIRED:加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
- PROPAGATION_SUPPORTS:如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
- PROPAGATION_MANDATORY:必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
- PROPAGATION_REQUIRES_NEW
- PROPAGATION_NOT_SUPPORTED:当前不支持事务
- PROPAGATION_NEVER:不能在事务中运行
- PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW:另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。而Nested事务的好处是他有一个savepoint
35.什么是Docker,为什么要用Docker?
Docker:是一个开源项目,它基于 Google 公司推出的 Go 语言实现。Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案。Docker 的基础是 Linux 容器(LXC)等技术。
作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。更高效的利用系统资源、更快速的启动时间、一致的运行环境、持续交付和部署、更轻松的迁移、更轻松的维护和扩展、更快速的交付和部署、更高效的虚拟化、更轻松的迁移和扩展、更简单的管理。