1. Java基本数据类型,各占几个字节?
Java一共有8种基本数据类型:int占4字节,short占2字节,long占8字节,byte占1字节,float占4字节,double占8字节,char占2字节,boolean占1字节。
2. Java反射机制的原理以及创建类实例的三种方式是什么?
原理:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
创建类实例的三种方式:
通过通过Class.forName("全类名")
try {
Class<?> perClazz = Class.forName("reflect_fanshe.Person");
System.out.println(perClazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
类名.class
Class<?> perClazz2 = Person.class;
对象.getClass()
3. JVM的内存模型
将这张图记熟即可
4. redis的几种基本数据类型
String(字符串)
List(列表)
Set(集合)
Hash(哈希)
Zset(sorted set:有序集合)
5. 高并发下如何安全修改同一行数据
使用悲观锁,FIFO(First Input First Output,先进先出)缓存队列思路,使用乐观锁
6. spring mvc的执行流程
首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
7. 使用一种方式实现动态代理
JDK动态代理
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
CGlib动态代理
利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别
JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
方式一:JDK动态代理
目标接口类
/**
* 目标接口类
*/
public interface UserManager {
void addUser(String username, String password);
void delUser(String username);
}
接口实现类
/**
* 动态代理:
* 1. 特点:字节码随用随创建,随用随加载
* 2. 作用:不修改源码的基础上对方法增强
* 3. 分类:
* 1)基于接口的动态代理
* 1. 基于接口的动态代理:
* 1)涉及的类:Proxy
* 2)提供者:JDK官方
* 3)如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 4)创建代理对象的要求
* 被代理类最少实现一个接口,如果没有则不能使用
* 5)newProxyInstance方法的参数:
* ClassLoader:类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类
* 2)基于子类的动态代理
*/
public class JDKProxy implements InvocationHandler {
// 用于指向被代理对象
private Object targetObject;
public Object newProxy(Object targetObject) {
// 将被代理对象传入进行代理
this.targetObject = targetObject;
// 返回代理对象
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),this.targetObject.getClass().getInterfaces(),this);
}
/**
* 被代理对象的任何方法执行时,都会被invoke方法替换,即:代理对象执行被代理对象中的任何方法时,实际上执行的时当前的invoke方法
* @param proxy(代理对象的引用)
* @param method(当前执行的方法)
* @param args(当前执行方法所需的参数)
* @return(和被代理对象方法有相同的返回值)
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在原来的方法上增加了日志打印功能,增强代码
printLog();
Object ret = null;
// 调用invoke方法(即执行了代理对象调用被调用对象中的某个方法)
ret = method.invoke(targetObject, args);
return ret;
}
/**
* 模拟日志打印
*/
private void printLog() {
System.out.println("日志打印:printLog()");
}
}
测试类
public class TestJDKProxy {
public static void main(String[] args) {
UserManager userManager = new UserManagerImpl();
JDKProxy jdkProxy = new JDKProxy();
UserManager userManagerProxy = (UserManager)jdkProxy.newProxy(userManager);
System.out.println("--------------------没有使用增强过的方法--------------------");
userManager.addUser("root","root");
userManager.delUser("root");
System.out.println("--------------------使用代理对象增强过的方法--------------------");
userManagerProxy.addUser("scott","tiger");
userManagerProxy.delUser("scott");
}
}
测试结果
--------------------没有使用增强过的方法--------------------
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
--------------------使用代理对象增强过的方法--------------------
日志打印:printLog()
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
方式二:CGlib动态代理
/**
* 动态代理:
* 1. 特点:字节码随用随创建,随用随加载
* 2. 作用:不修改源码的基础上对方法增强
* 3. 分类:
* 1)基于接口的动态代理
* 2)基于子类的动态代理
* 1. 基于子类的动态代理:
* 1)涉及的类:Enhancer
* 2)提供者:第三方cglib库
* 3)如何创建代理对象:
* 使用Enhancer类中的create方法
* 4)创建代理对象的要求
* 被代理类不能是最终类
* 5)create方法的参数:
* Class:字节码,它是用于指定被代理对象的字节码。固定写法。
* Callback():用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类。固定写法。
*/
public class CGLibProxy implements MethodInterceptor {
// 用于指向被代理对象
private Object targetObject;
// 用于创建代理对象
public Object createProxy(Object targetObject) {
this.targetObject = targetObject;
return new Enhancer().create(this.targetObject.getClass(),this);
}
/**
*
* @param proxy(代理对象的引用)
* @param method(当前执行的方法)
* @param args(当前执行方法所需的参数)
* @param methodProxy(当前执行方法的代理对象)
* @return(和被代理对象方法有相同的返回值)
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object ret = null;
// 过滤方法
if ("addUser".equals(method.getName())) {
// 日志打印
printLog();
}
ret = method.invoke(targetObject, args);
return ret;
}
/**
* 模拟日志打印
*/
private void printLog() {
System.out.println("日志打印:printLog()");
}
}
测试类
public class TestCGLibProxy {
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
UserManager userManager = new UserManagerImpl();
UserManager cgLibProxyProxy = (UserManager)cgLibProxy.createProxy(userManager);
System.out.println("--------------------没有使用增强过的方法--------------------");
userManager.addUser("root","root");
userManager.delUser("root");
System.out.println("--------------------使用代理对象增强过的方法--------------------");
cgLibProxyProxy.addUser("scott","tiger");
cgLibProxyProxy.delUser("scott");
}
}
测试结果
--------------------没有使用增强过的方法--------------------
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
--------------------使用代理对象增强过的方法--------------------
日志打印:printLog()
调用了UserManagerImpl.addUser()方法!
调用了UserManagerImpl.delUser()方法!
8. 悲观锁和乐观锁的区别,InnoDB的标准行级锁有哪两种,解释其含义
悲观锁(Pessimistic Lock):
每次拿数据的时候都会担心会被别人修改(疑心重很悲观),所以每次在拿数据的时候都会上锁。确保自己使用的过程中不会被别人访问,自己使用完后再解锁。
期间需要访问该数据的都会等待。
乐观锁(Optimistic Lock):
每次拿数据的时候都完全不担心会被别人修改(心态好很乐观),所以每次在拿数据的时候都不会上锁。但是在更新数据的时候去判断该期间是否被别人修改过(使用版本号等机制),期间该数据可以随便被其他人读取。
两种锁各有优缺点,不能单纯的定义哪个好于哪个。乐观锁比较适合数据修改比较少,读取比较频繁的场景,即使出现了少量的冲突,这样也省去了大量的锁的开销,故而提高了系统的吞吐量。但是如果经常发生冲突(写数据比较多的情况下),上层应用不不断的retry,这样反而降低了性能,对于这种情况使用悲观锁就更合适。
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
9. 解释一下浅拷贝和深拷贝的区别
浅拷贝:
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
10. LinkedList和ArrayList的区别
ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。(参考数组与链表来思考)
对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
11. redis如何保证数据一致性
方式一:先更新数据库,再更新缓存场景
方式二:先更新缓存,再更新数据库场景
方式三:先删除缓存,再更新数据库的场景
方式四:先更新数据库,在删除缓存场景
方式五:最佳实现,数据异步同步
12. sql优化的一般步骤
通过show status命令了解各种SQL的执行频率
定位执行效率较低的SQL语句
通过EXPLAIN分析较低SQL的执行计划
通过show profile分析SQL
通过trace分析优化器如何选择执行计划
确定问题并采取相应的优化措施