百度实习
spring使用了哪些设计模式?简单工厂设计模式:工厂根据传入的参数,动态决定应该创建哪一个产品,BeanFactory根据传入的一个唯一标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建,这个需要依据情况来定工厂方法:应用程序将对象的创建以及初始化职责交给工厂对象;如果将应用程序自己的工厂对象交给spring管理,那么就是工厂bean单例模式:保证一个类仅有一个实例,并且提供一个访问它的全局访问点,spring默认下的bean就是单例的,如果想更改需要更改scope属性适配器模式:AOP中,使用advice来增强被代理类的功能,每一种都有对应的拦截器,各种不同的拦截器对外提供统一的接口代理模式:为其他对象提供一种代理以控制对这个对象的访问,比如jdk代理和cglib代理观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并且自动更新,例如ApplicationListener的实现模板方法模式:定义一个操作的算法骨架,将一些步骤延迟到子类中,比如jdbcTemplate等以Template结尾的类; 代理模式详细讲一下
一个对象A通过持有另一个对象B,可以具有B同样的行为的模式,往往A和B会去实现一个接口,但是B才是真正的实现类,A是借用了B的方法区实现的接口,A拥有增强B的功能;代理细分为静态代理和动态代理,静态代理就是在程序运行前,代理类和委托类的关系就已经确定了;动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成;
动态代理又分为jdk代理和cglib代理:
各自的局限性:jdk动态代理机制只能代理实现类接口的类,不实现接口的类就不能实现jdk动态代理,cglib是针对类来实现代理的,它的原理是对指定目标类生成一个子类,覆盖其中的方法实现增强,但是由于使用的是继承的方法,所以不能对final修饰的类进行代理
性能区别:在少量调用的时候,jdk代理效率高于cglib,多量调用的时候jdk低于cglib,java8以后jdk效率高于cglib代理
原理区别:jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用invokeHandler来处理,核心是实现代理类实现invocationHandler接口,使用invoke方法进行面向切面的处理,调用相应的通知;cglib动态代理是修改字节码生成子类,核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理
工厂方法模式和抽象工厂模式有什么区别简单工厂:使用静态方法,通过接收的参数的不同来返回不同的对象实例
工厂方法:针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例
抽象工厂:用抽象工厂是应对产品族概念的,增加新的产品线很容易,但是无法增加新的产品,比如说,每个汽车公司可能要同时生产轿车,货车,客车,那么每一个工厂都要有创建轿车,货车和客车的方法
用种蔬菜的例子来说明事实,最初的时候,由于规模小,只种植一种蔬菜,根菜类蔬菜,这个时候由于种植方式比较简单,采用简单工厂模式即可,主要目的是让工人轻松,下达工厂种植即可,但是随着种植厂的发展以及市场的需求,要增加一种蔬菜类型种植了,茎菜,由于茎菜与根菜种植方式不一致,就需要两个专门的种植工厂来进行管理,那么久采用工厂模式来管理,一个工厂负责一种作物的种植,这个时候产品可以理解为仍然在一个层次。但是随着科技的发展,我们逐步要种植转基因与非转基因食品了,在以前的蔬菜种类上又增加了一个层次,这个时候无法将其作为一个层次来解决,所以必须采用抽象工厂的方式来解决。
说一下ConcurrentHashMap其数据结构基本和HashMap雷同
在Java8以前,数组,链表+reentrantLock+segment,使用的是分段锁的概念,将数据一段一段的存储,当一个线程占用锁访问一个数据段的时候,其他数据段依然能被正常访问;它需要使用两次hash,第一次hash到对应segment第二次hash到对应的链表;put方法在指定的segment加锁,get方法使用volatile关键字保证可见性,不加锁,size方法是对其进行两次计算,如果结果不一致再加锁计算一次,一致就直接返回
在java8以后,采用的是数组,红黑树,链表+CAS+Synchronized的方法,以前是对segment加锁,现在是对数组元素加锁,锁的粒度更加细致了;put方法,如果putKey的位置已经存在链表了,采用的是synchronized加锁的方法,锁的是当前链表否则采用的是CAS;get操作使用volatile保证可见性,不加锁.size方法会在更新链表的时候实时更新,所以也不加锁
介绍一下aop和ioc,有哪些应用场景AOP:面向切面编程,即在一个功能模块中增加其他新的功能;场景:比方说要使用一个日志,记录某些接口调用的方法时间,可以使用aop在接口前插入一段代码去记录开始时间,在之后插入代码记录结束时间;又比如你访问数据库,不想管事务,spring自动帮你在访问数据库之前开启事务,在结束之后提交事务或者回滚事务;还比如异常处理,可以开启环绕通知,一旦运行接口报错,环绕通知捕获异常跳转异常处理界面
IOC:依赖注入或者叫做控制反转;正常情况使用对象要new OBJ(),IOC是把需要使用的对象提前创建号,方法哦spring容器里面,由spring来管理这些类的创建,销毁;场景:如果想在service层调用另外一个service的方法,不需要去new,直接把它丢给spring,然后使用注解的方式就可以使用了
如何自己设计一个aop切面使用jdk动态代理或者cglib动态代理实现;
jdk动态代理:代理对象必须是某个接口的实现,因为它是通过运行期间创建一个接口的实现类来完成对目标对象的代理,其核心是invocationHandler和Proxy类
cglib动态代理,底层是操作字节码实现的,它本质是通过生成子类覆盖其中的方法实现增强,所以如果是final修饰的类就不能使用;
MySql的默认隔离级别?解决了什么问题?幻读和脏读的区别?可重复读,解决了脏读,不可重复读的问题;
脏读:事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的
不可重复度:在一个事务内,多次读同一条数据,发现结果不一样;
幻读:事务1准备插入一条记录,select它不存在,准备插入的时候,发现存在了
为什么提到幻读的时候,都说是其他数据插入引发的幻读,而没有提及删除数据在rr隔离级别下删除数据不会发生幻读,但是插入会不会,这就有关到读写锁
读锁:共享锁,多个读操作同时进行而不影响
写锁:排他锁,当前写操作没有完成之前,它会阻断其他写锁和读锁
如果事务1进行读操作,那么给该记录加读锁,事务2进行写操作,如果是同一条记录进行写,比如删除或者更新,则无法并行,必须等待读锁之后才行,如果是insert操作,事务1的读锁不会阻碍事务2的新增操作,所以两个事务依然可以并行,故而是有可能引发幻读的
redis持久化?分为RDB持久化和AOF持久化,RDB把数据生成快照保存在硬盘上,AOF记录每次对数据的操作以日志的形式写到硬盘上,RDB效率高于AOF,但是每次触发持久化都会fork子进程,属于重量级操作,无法做到实时持久化,并且如果redis挂掉会损失最近一次快照的数据;AOF缺点是文件体积大,恢复时间长
RDB持久化过程:在bgsave命令执行的过程中,redis的进程会被阻塞
AOF持久化流程:
AOF持久化配置:
# appendonly改为yes,开启AOF appendonly yes # AOF文件的名字 appendfilename "appendonly.aof" # AOF文件的写入方式 # everysec 每个一秒将缓存区内容写入文件 默认开启的写入方式 appendfsync everysec # 运行AOF重写时AOF文件大小的增长率的最小值 auto-aof-rewrite-percentage 100 # 运行AOF重写时文件大小的最小值 auto-aof-rewrite-min-size 64mb
Redis4,0以后可以配置混合持久化方式,bgsave做全量持久化,aof做增量持久化
Redis为什么单线程还这么快?redis是基于内存的,内存的读写速度非常快;
redis是单线程的,省去了很多上下文切换线程的时间;
redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
主从模式下,master节点负责写请求,然后异步的同步给slave节点,从节点负责处理请求,如果master宕机了,需要手动将节点晋升为主节点,并且还要切换客户端的连接数据源;
哨兵模式是redis的高可用模式,哨兵架构下client端第一次从哨兵找出redis的主节点,后续直接访问redis的主节点,当redis的主节点挂掉的时候,哨兵会第一时间感觉到,并且从slave节点选出一个新的主节点,然后将新的master通知给client端,从而实现高可用;
哨兵的主要工作就是:监控(主从节点挂了没),提醒(某个节点出现问题的时候向管理员发送通知)和自动故障迁移(一个master不能工作,升级一个slave成为新的master)
消息队列中消费者如何消费消息?发送消息:convertAndSend方法,向指定交换机和路由键发送消息
消费消息:使用@RabbitListener注解,监听指定队列,自动注入实体类,如果手动模式需要调用channel的basicACK或者basicReject处理消息;
RabbitListener起作用的原因?ps:@RabbitListener 可以标注在类上面与@RabbitHandler 注解一起使用@RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,根据接受的参数类型进入具体的方法中。
@RabbitListener注解的方法所在的类首先是一个bean,遍历bean中由用户自己定义的所有的方法,找出其中添加了@RabbitListener注解的方法读取上面找出的所有方法上@RabbitListener注解中的值,并为每一个方法创建一个RabbitListenerEndpoint,保存在RabbitListenerEndpointRegistrar类中。在所有的bean都初始化完成,即所有@RabbitListener注解的方法都创建了endpoint之后,为每个endpoint创建MessageListenerContainer。MessageListenerContainer启动后将能够接受到消息,再将消息交给它的MessageListener处理消息。MessageListener中创建了一个HandlerAdapter,这个adapter与rabbitmq broker建立一个connection,接收rabbitmq broker push过来的message,放到一个blocking queue中。至此完成消息的接收。 最接近的三数之和
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int min=999999;
for (int i = 0; i < nums.length; i++) {
int left=i+1;
int right=nums.length-1;
while (lefttarget){
right--;
}
if(sum
大数相加
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder("");
int i = num1.length() - 1, j = num2.length() - 1, carry = 0;
while(i >= 0 || j >= 0){
int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
int tmp = n1 + n2 + carry;
carry = tmp / 10;
res.append(tmp % 10);
i--; j--;
}
if(carry == 1) res.append(1);
return res.reverse().toString();
}
大数相减
//num1和num2规定必须为正数
public String subStract(String num1,String num2){
int n1=num1.length();
int n2=num2.length();
if(n1>n2)return helper(num1,num2);
if(n10)return helper(num1,num2);
return "-"+helper(num2,num1);
}
//保证进来的数字,num1>num2
private String helper(String num1, String num2) {
int index1=num1.length()-1;
int index2=num2.length()-1;
int index=index1;
int borrow=0;//借位
int res[]=new int[index+1];
while (index1>=0||index2>=0){
int div=10;//防止出现负数,预先留一个10,从前一位借
div+=index1>=0?num1.charAt(index1--)-'0':0;
div-=index2>=0?num2.charAt(index2--)-'0':0;
div-=borrow;//从下一位借的10,这里要减去
res[index--]=div%10;//放入结果集
borrow=div/10==0?1:0;//如国结果大于10说明不需要借,否则需要借,把borrow设置为1,后续循环会减掉此值
}
int i=0;
if(res[i]==0)i++;
StringBuilder builder = new StringBuilder();
for (int j = i; j <=num1.length()-1; j++) {
builder.append(res[j]);
}
return builder.toString();
}
public static void main(String[] args) {
Solution solution = new Solution();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String num1=scanner.next();
String num2=scanner.next();
System.out.println(solution.subStract(num1,num2));
}
}
大数相乘
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) return "0";
int n1 = num1.length();
int n2 = num2.length();
int[] res = new int[n1 + n2];//存储结果的数组,最大的结果是两者数位和相加
for (int i = n1 - 1; i >= 0; i--) {
int d1 = num1.charAt(i) - '0';
for (int j = n2 - 1; j >= 0; j--) {
int d2 = num2.charAt(j) - '0';
//d1和d2相乘的结果,地位在i+j+1的位置,高位在i+j的位置
int sum = res[i + j + 1] + d1 * d2;
res[i + j + 1] = sum % 10;//计算地位的结果和
res[i + j] += sum / 10;//累加到高位
}
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < res.length; i++) {
if(i==0&&res[i]==0)continue;//排除首位为0的情况
builder.append(res[i]);
}
return builder.toString();
} 


