Static静态,Transient事务,foreach循环的原理,Switch,java反射机制,单线程安全类demo,常规算法等关键字作用,至少掌握其用法和场景
Exception和Error区别
Exception是程序错误,可控或不可控,可在程序中捕获异常
Error是系统异常,不可控,可能的话可在系统中捕获异常
java.util.Date和java.sql.Date的区别及应用
java.util.Date 就是在除了SQL语句的情况下面使用
java.sql.Date 是针对SQL语句使用的,它只包含日期而没有时间部分,它都有getTime方法返回毫秒数,自然就可以直接构建
java.util.Date 是 java.sql.Date 的父类
java.util.Date d = new java.util.Date(sqlDate.getTime());
java.util.Date d=new java.util.Date (date.getTime());.
集合的结构
其中ArrayList和HashMap用到特别多,也是被问到特别多的地方
ArrayList原理
以数组实现。节约空间,但数组有容量限制。
超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。
按数组下标访问元素—get(i)/set(i,e) 的性能很高,这是数组的基本优势。
直接在数组末尾加入元素—add(e)的性能也高,但如果按下标插入、删除元素—add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。
特点:
是一个相对来说比较简单的数据结构,最重要的一点就是它的自动扩容,可以认为就是我们常说的“动态数组”。
其中操作可以理解为直接将数组的内容置位,remove操作可以理解为删除index为0的节点,并将后面元素移到0处
add函数:
当我们在ArrayList中增加元素的时候,会使用add函数。他会将元素放到末尾,其实最核心的内容就是ensureCapacityInternal。这个函数其实就是自动扩容机制的核心
过程:
如果ArrayList的大小已经不满足需求时,那么就将数组变为原长度的1.5倍,之后的操作就是把老的数组拷到新的数组里面。
set和get函数:
Array的put和get函数就比较简单了,先做index检查,然后执行赋值或访问操作:
remove函数:
先做index检查,然后执行删除操作
LinkedList原理: Hashtable原理: HashMap原理:
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。
当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。
当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
碰撞:
HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对
负载因子:
默认负载因子大小0.75
当map填满75%的bucket时候,和其他集合类一样(ArrayList)将会创建原来HashMapda大小两倍的bucket数组
重新调整map大小,并将原来的对象放入新的bucket数组中,此过程称之为rehashing
ConcurrentHashMap 原理 HashSet原理: ArrayList和LinkedList的大致区别:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
hashMap和HashTable和ConcurrentHashMap区别
HashMap线程不安全,HashTable线程安全,ConcurrentHashMap线程安全
HashMap的键值可以为null,HashTable不能不null,ConcurrentHashMap不能为空
HashMap是HashTable的轻量级实现
HashMap相比HashTable快,因为HashTable是同步,ConcurrentHashMap锁分段技术
多线程场景HashMap可能会造成死循环,导致cpu利用率100%
HashTable容器是同步,会造成阻塞、轮训,ConcurrentHashMap的分布式锁,快、安全
集合为什么不直接实例化接口
Set set =new HashSet()而不是Set set = new Set()
Map map = new HashMap()而不是HashMap map = new HashMap()
List list = new ArrayList()而不是ArrayList list = new ArrayList();
1.Set,List,map是接口,不能实例化,只能实例化接口实现类,HashSet,ArrayList,HashMap
2.实现类中的属性和方法set,map,list不能调用,只能调用接口本身属性和方法
3.接口有多个实现类,便于代码的重构,以后只需改实现类,其他可不变
关于在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种,通过在xml中定义init-method和destory-method方法
第二种,通过bean实现InitializingBean和 DisposableBean接口
第三种,通过Spring @PostConstruct和@PreDestroy方法
#{} 和 ${} 在预编译中的处理是不一样的。#{} 在预处理时,会把参数部分用一个占位符 ? 代替,而 ${} 则只是简单的字符串替换,在动态解析阶段
AOP(Aspect Oriented Programming):面向切面编程,也是一种变成思想。(静态角度)
OOP(Object Oriented Programming):面向对象编程。(动态代理:JDK动态代理 CGLIB动态代理)
代理模(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
基本术语(一些名词):
(1)切面(Aspect)
切面泛指[交叉业务逻辑]。事务处理和日志处理可以理解为切面。常用的切面有通知(Advice)与顾问(Advisor)。实际就是对主业务逻辑的一种增强。
(2)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。代理的invoke方法完成的工作,可以称为织入。
(3) 连接点(JoinPoint)
连接点是指可以被切面织入的方法。通常业务接口的方法均为连接点
(4)切入点(PointCut)
切入点指切面具体织入的方法
注意:被标记为final的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(5)目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
(6)通知(Advice)
通知是切面的一种实现,可以完成简单的织入功能。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是执行之后执行等。切入点定义切入的位置,通知定义切入的时间。
(7)顾问(Advisor)
顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。
适用场景:权限、日志、事务
RabbitMQ
RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式
(1)单机模式
单机模式平常使用在开发或者本地测试场景,一般就是测试是不是能够正确的处理消息,生产上基本没人去用单机模式,风险很大。
(2)普通集群模式
普通集群模式就是启动多个RabbitMQ实例。在你创建的queue,只会放在一个rabbtimq实例上,但是每个实例都同步queue的元数据。在消费的时候完了,上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。
这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个queue所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。
而且如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让RabbitMQ落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个queue拉取数据。
这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。
(3)镜像集群模式
镜像集群模式是所谓的RabbitMQ的高可用模式,跟普通集群模式不一样的是,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。
优点在于你任何一个实例宕机了,没事儿,别的实例都可以用。缺点在于性能开销太大和扩展性很低,同步所有实例,这会导致网络带宽和压力很重,而且扩展性很低,每增加一个实例都会去包含已有的queue的所有数据,并没有办法线性扩展queue。
开启镜像集群模式可以去RabbitMQ的管理控制台去增加一个策略,指定要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
Kafka
Kafka天生就是一个分布式的消息队列,它可以由多个broker组成,每个broker是一个节点;你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。
kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。
kafka 0.8以后,提供了HA机制,就是replica副本机制。kafka会均匀的将一个partition的所有replica分布在不同的机器上,来提高容错性。每个partition的数据都会同步到吉他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都去leader,其他replica就是follower,leader会同步数据给follower。当leader挂了会自动去找replica,然后会再选举一个leader出来,这样就具有高可用性了。
写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。
4、如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
其实消息重复消费的主要原因在于回馈机制(RabbitMQ是ack,Kafka是offset),在某些场景中我们采用的回馈机制不同,原因也不同,例如消费者消费完消息后回复ack, 但是刚消费完还没来得及提交系统就重启了,这时候上来就pull消息的时候由于没有提交ack或者offset,消费的还是上条消息。
那么如何怎么来保证消息消费的幂等性呢?实际上我们只要保证多条相同的数据过来的时候只处理一条或者说多条处理和处理一条造成的结果相同即可,但是具体怎么做要根据业务需求来定,例如入库消息,先查一下消息是否已经入库啊或者说搞个唯一约束啊什么的,还有一些是天生保证幂等性就根本不用去管,例如redis就是天然幂等性。
还有一个问题,消费者消费消息的时候在某些场景下要放过消费不了的消息,遇到消费不了的消息通过日志记录一下或者搞个什么措施以后再来处理,但是一定要放过消息,因为在某些场景下例如spring-rabbitmq的默认回馈策略是出现异常就没有提交ack,导致了一直在重发那条消费异常的消息,而且一直还消费不了,这就尴尬了,后果你会懂的。
RabbitMQ 有哪些重要的角色?
RabbitMQ 有哪些重要的组件?
RabbitMQ 有几种广播类型?
三种广播模式:
fanout: 所有bind到此exchange的queue都可以接收消息(纯广播,绑定到RabbitMQ的接受者都能收到消息);
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息;
topic: 所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息;
Kafka 可以脱离 zookeeper 单独使用吗?为什么?
kafka 不能脱离 zookeeper 单独使用,因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。
Kafka 有几种数据保留的策略?
kafka 有两种数据保存策略:
按照过期时间保留
按照存储的消息大小保留
Kafka 的分区策略有哪些?
给定了分区号,直接将数据发送到指定的分区里面去
没有给定分区号,给定数据的key值,通过key取上hashCode进行分区
既没有给定分区号,也没有给定key值,直接轮循进行分区
自定义分区
数据库优化:
1.使用连接(JOIN)来代替子查询
2.使用联合(UNION)来代替手动创建的临时表
3.事务,事物以BEGIN关键字开始,COMMIT关键字结束
4.使用外键
5.使用索引
6.优化的查询语句(不使用NOT IN和<>操作 用 not exists)避免全表扫描
线程
新建(new)
运行(Runable)
无限期等待(Waiting)
限期等待(Timed Waiting)
阻塞(Blocked)
结束(Terminated)
线程池:
java中的有哪些线程池?
1.newCachedThreadPool创建一个可缓存线程池程
2.newFixedThreadPool 创建一个定长线程池
3.newScheduledThreadPool 创建一个周期性执行任务的线程池
4.newSingleThreadExecutor 创建一个单线程化的线程池
使用线程池的优点
1.重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销
2.有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞
3.能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能
java虚拟机
强引用
类似于 Object obj = new Object(); 创建的,只要强引用在就不回收。
软引用
SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。
弱引用
WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。
虚引用
PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
垃圾回收算法
标记 —— 清除算法
复制算法:把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。
标记-整理算法:不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是把存活对象移到内存的一端。
分代回收:根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。
新生代
每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。
老年代
老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除 或者 标记 —— 整理 算法回收。
Springboot:
Spring Boot 优点非常多,如:
独立运行 简化配置 自动配置 无代码生成和XML配置 无需部署war文件
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
配置文件有.properties 和 .yml,它们的区别主要是书写格式不同。
SpringBoot的核心注解是 @SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
开启SpringBoot特性有
1)继承spring-boot-starter-parent项目
2)导入spring-boot-dependencies项目依赖
运行SpringBoot有
1)打包用命令或者放到容器中运行
2)用 Maven/ Gradle 插件运行
3)直接执行 main 方法运行
常用注解:@ConfigurationProperties注解读取方式
设计模式:
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
最后需要看jdk的源码