面试
面试知识点
1. final
- 修饰类:当用final修饰一个类时,表明这个类不能被继承
- 修饰方法:任何继承类不能重写该方法(final修饰的方法同时访问控制权限为private,此时不叫重写)
- 修饰变量:
- 修饰基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。
- 如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的(例如数组的值是可以修改的,StringBuffer.append()方法)
- finally:是异常处理语句结构的一部分,表示总是执行(资源释放,关闭数据库连接)
- finalize():在垃圾收集器将对象从内存中清除出去前,,做必要的清理工作。如果对象有finalize()方法,并且没有执行过,就会执行finalize()方法,但是不会保证finalize()方法执行完。finalize()方法还可以拯救对象。
2. 接口和抽象类
-
接口
- 成员变量的默认修饰符为:public static final
- 接口方法默认public 、abstract
-
相同:
- 都不能直接被实例化
- 接口的实现类和抽象类的子类只有全部实现了接口或者抽象类中的方法后才可以被实例化
-
不同点:
- 接口不能继承类,接口不能实现接口,接口只能继承接口,而且可以继承多个
- 抽象类可以继承抽象类,实现多个接口
- 抽象类可以有普通成员变量 接口没有
- 接口只能定义抽象方法,抽象类可以有构造方法,实现方法,main方法,并且可以没有抽象方法
- 抽象类中访问类型可以是public,protected,default,接口只能是 public
3. 集合结构
-
Collection接口的子接口包括:Set接口和List接口
-
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap,LinkedHashMap以及Properties等
-
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
-
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
-
Iterator:边遍历边删除是需要使用迭代器
-
Iterator
iterator = lst.iterator(); -
iterator = map.values().iterator(); while (iterator.hasNext()){ Object str = iterator.next(); System.out.println(str); }
-
-
Collection
- set:无序容器,不允许重复对象,只允许一个 null 元素
- HashSet:存储是没有顺序的,并不是怎么存储就是怎么获取的。存储对象,保证元素唯一,需要重写HashCode和equals()方法
- HashSet的底层其实就是HashMap,只不过我们HashSet是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存
- LinkedHashSet:根据插入顺序存储,怎么存怎么取,因为是HashSet的子类所以也是保证元素唯一的
- TreeSet:不仅可以保证元素唯一、还可以对元素进行排序,根据其compare()和compareTo()的定义进行排序
- list:有序容器,可以允许重复的对象,可以插入多个null元素,
- Linkedlist:LinkedList基于链表的数据结构,新增和删除快,查询慢
- Arraylist :ArrayList是实现了基于动态数组的数据结构,随机访问快,新增和删除慢
- Vector:基于动态数组,Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe);.当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间
- set:无序容器,不允许重复对象,只允许一个 null 元素
-
Map:Map不是collection的子接口或者实现类。Map是一个接口
- HashMap可以让你将空值null作为一个表的条目的key或value. 但是 HashTable 不允许
- hashMap:数组+链表(红黑树),父类AbstractMap,初始16,扩容翻倍,负载因子默认0.75
- hashTable:线程安全,父类Dictionary,初始11,扩容2n+1
- 不同点:父类不同,都实现了map接口,hashTable是线程安全的,
- LinkedHashMap保存了记录的插入顺序, 在用Iteraor遍历LinkedHashMap时, 先得到的记录肯定是先插入的. 在遍历的时候会比HashMap慢. 有HashMap的全部特性.加了指针
- TreeMap能够把它保存的记录根据键排序, 默认是按升序排序, 也可以指定排序的比较器. 当用Iteraor遍历TreeMap时, 得到的记录是排过序的. TreeMap的键和值都不能为空.
-
遍历hashmap
- for(String key:map.keySet())//遍历key,两次取值
- Iterator map1it=map.entrySet().iterator();//一次取值
- for(Map.Entry<String, String> entry: map.entrySet())//遍历entry,一次取值
- for(String v:map.values())//遍历值
-
Java集合的快速失败机制 “fail-fast”?(Hash Map)
- 是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制
- 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历
-
BlockingQueue:
- BlockingQueue即阻塞队列,基于ReentrantLock,最常用的还是用于实现生产者与消费者模式
- LinkedBlockingQueue,
-
2)HashMap与HashTable的区别?
答:
HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的; HashMap允许K/V都为null;后者K/V都不允许为null; HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;
4. 类加载器
- 启动类加载器(Bootstrapp ClassLoader):负责将存放在<JAVA_HOME>lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>libext目录或java.ext.dirs系统变量指定的路径中的所有类库
- 应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器
- 类加载步骤:
- 加载:通过类的全限定名(绝对路径,包名+类名)来获取定义此类的二进制字节流,将静态存储结果转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class(用于存储类的相关信息的类)对象,作为方法去这个类的各种数据的访问入口
- 验证:确保Class文件的字节流包含的信息符合当前虚拟机的要求,不会危害到虚拟机自身的安全
- 文件格式验证
- 元数据验证(是否有父类,释放继承了不能继承的类,不是抽象类是否实现了所有方法)
- 字节码验证(类型转换,指令跳转正确,主要检查语义是否合法)
- 符号引用验证(根据类的全限定名是否能找到类)引用类是否可以被当前类访问
- 准备:为类变量分配内存并设置类变量初始值(零值,真正的赋值在初始化阶段)的阶段,类变量(被static修饰的变量)都将在方法区中分配
- 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(比如从类的全限定名转化为具体的内存地址)
- 初始化
- 父类–静态变量/父类–静态初始化块
- 子类–静态变量/子类–静态初始化块
- 父类–变量/父类–初始化块
- 父类–构造器
- 子类–变量/子类–初始化块
- 子类–构造器
- 双亲委派模式,收到类加载请求时,不会第一时间加载,而是把这个请求委派给父类加载器完成.需要注意的是他们之间不是继承关系,而是组合关系(回溯委托,父辈代理)
5. 异常
异常和错误的区别是,异常是可以被处理的,而错误是没法处理的
- 都继承Throwable
- Error
- 对于所有的编译时期的错误以及系统错误都是通过Error抛出的,表示应用程序本身无法克服和恢复的一种严重问题,应用表示无能为力
- Exception
- 异常是程序本身可以处理的异常,表示程序还能够克服和恢复的问题
- ArrayIndexOutOfBoundsException、NullPointerException、ClassCastException
- Checked Exception:可检查的异常,这是编码时非常常用的,所有checked exception都是需要在代码中处理的,比如IOException
- Unchecked Exception:RuntimeException,这种异常是运行时发生,无法预先捕捉处理的
- Error
6. 枚举
- 把相关的常量分组到一个枚举类型
- 枚举成员是用public修饰的
- name:名称
- ordinal:序号
- 不能被继承,反编译之后,可以看到时加了final的
7. 泛型的实现
- 泛型的本质是参数化类型
- Object 来实现通用,每次使用时都需要强制转换成想要的类型,并且编译期间不容易发现问题,运行时容易出错
- 数组中不能使用泛型
- 泛型的参数类型可以使用extends语句
- 泛型擦除:编译之前的检查,防止出错,然后进行泛型擦除
- 在运行中获取泛型(反射):Type type = ((ParameterizedType) foo.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
- getGenericSuperclass返回直接继承的父类(包含泛型参数)
- getActualTypeArguments实际类型参数的 Type 对象的数组
8. 多线程
-
线程死锁条件
- 互斥条件:资源是互斥的
- 请求和保持条件:请求其他资源,保持现有资源的拥有
- 不可剥夺条件:已获得的资源不可剥夺
- 环路等待条件:多个进程形成资源的环形等待链
-
如何预防死锁:
- 请求不保持:拿不到就释放资源
- 资源可剥夺
-
死锁避免
- 系统对资源申请进行动态检查,并根据检查结果决定是否分配资源,如果可能死锁就觉得是不安全的,不予分配,否则予以分配
- 加锁顺序
-
解决死锁
- 加锁时限
-
线程由等待池移动到锁池(等待锁的地方)
- notifyAll():唤醒所有的wait 线程
- notify()方法:只随机唤醒一个 wait 线程
-
都是Object类的方法
-
sleep,wait
- wait() 方法是在 Object 类里. 而 sleep() 是在 Thread 类里
-
sleep方法没有释放锁,可以指定时间自动唤醒
- wait方法释放了锁,wait是进入线程等待池等待,可以设置等待时间,碰到 notify() 或者 notifyAll() 就会提前启动
-
同步方法:
- synchronized:
-
jvm关键字
-
同步方法:通过哪个对象调用的,就锁住哪个对象
-
同步代码块:锁住指定的对象(不要用this,尽量锁变量)
-
同步静态方法:锁住整个class
-
锁的对象越小越好
-
ThreadLocal
- 每个线程提供一个独立的变量副本
- 线程间的数据隔离,无法共享同一个资源
-
volatile
- 强制将对缓存的修改操作立即写入主存,保证可见性
- 禁止指令重排序,有序性(双重检查锁)
- 不保证原子性
-
ReentrantLock
-
Api层面的锁
- 需要手动加锁和解锁
- 可以实现公平锁,性能没有非公平锁性能好
- 提供了一个可以响应中断的获取锁的方法,可以用来解决死锁,lock.lockInterruptibly(),调用threadTest02.interrupt()来中断;
- 获取锁限时等待的方法
tryLock()
,配合失败重试机制来更好的解决死锁问题 - 结合Condition接口可以实现等待通知机制,condition.await();condition.signal();
-
9.GC算法
- 8:1:1
- 当Eden区满时,触发Minor GC(年龄加1)
- System.gc()方法的调用(有可能触发)
- 大对象直接进入老年代
- 老年代空间不足:1.幸存者空间,到达一定年龄(默认15)或者一半以上的对象到达的年龄(动态年龄判断),往老年代转移,2.大对象,数组直接进入老年代
- 方法区空间不足
- 空间分配担保:进行minorGC时,首先检查“老年代”最大可用连续空间大小是否大于所有新生代对象大小
- 如果大于,则直接进行minorGC
- 如果小于
- 是否允许担保失败
- 允许:“老年代”最大可用连续空间大小是否大于历代平均晋升新生代对象大小
- 大于:一次又风险的担保
- 小于:fullGC
- 不允许:fullGC
- 允许:“老年代”最大可用连续空间大小是否大于历代平均晋升新生代对象大小
- 是否允许担保失败
9.字符串
- String:String的值是不可变的,这就导致每次对String的操作都会生成新的String对象
- “”包裹的在常量池,new的是新对象
- Stringbuffer:线程安全
- Stringbuilder:不是线程安全的,快
- 字符串拼接
- +号:全是常量的时候,常量折叠的优化
- String每次+操作都会new一个StringBuffer来进行append(),然后调用tostring方法
- String 对象是不可变对象,每次操作Sting 都会重新建立新的对象来保存新的值
10.面对对象六大原则
- 开闭原则:对扩展开放,对修改关闭,需要使用接口和抽象类
- 里氏代换原则:任何基类可以出现的地方,子类一定可以出现
- 依赖倒转原则:开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
- 接口隔离原则:使用多个隔离的接口,比使用单个接口要好
- 迪米特法则:一个实体应当尽量少地与其他实体之间发生相互作用,提高独立性,减少耦合
- 单一职责原则:即一个类只负责一项职责,只有一个发生变化的原因
11.代理
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理
-
静态代理
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,不是编译器生成的代理类,而是手动创建的类。代理类和委托类的关系在运行前就确定了。代理类持有委托类的实例。接口与代理类是1对1的,有多个接口需要代理,就需要新建多个代理类,繁琐,类爆炸一旦接口增加方法,目标对象和代理对象都要维护
- 手动创建,一对一关系确定,修改繁琐
-
动态代理:动态代理重要特点是代理接口,代理类比如实现接口,代理的方法接口中也必须有(自己新增的方法也不能代理),是利用JDK的API,动态的在内存中构建代理对象
- 通过实现InvocationHandler接口创建自己的调用处理器
- 通过为Proxy类指定ClassLoader对象和一组interface代理类需要实现的接口,创建动态代理类类文件
- 反射机制获取动态代理类的一个构造函数
- 通过构造函数实例创建代理类实例,此时需将调用处理器对象作为参数被传入
- Proxy类中的newInstance工具方法封装了2~4步
-
CGlib:基于继承,final修饰的类不可继承,所以不能对final修饰的类进行代理。如果是 static方法,private方法,final方法等描述的方法是不能被代理的
-
他的原理是对代理的目标类生成一个子类,并覆盖其中方法实现增强,因为底层是基于创建被代理类的一个子类,所以它避免了JDK动态代理类的缺陷
-
CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.
-
方法拦截器 实现 MethodInterceptor 接口
-
内部创建cglib 代理类,setSuperclass(target)
-
直接通过proxy 对象访问被代理对象的方法
-
12.线程池
- 常用线程池:
- newCachedThreadPool:可缓存的线程池,根据需要创建新线程。当有新的任务提交时,有空闲线程则直接处理任务,没有空闲线程则创建新的线程处理任务,队列中不储存任务。线程池不对线程池大小做限制,如果线程空闲时间超过了60秒就会被回收。
- newFixedThreadPool:固定大小的线程池,再有新的任务提交时则放入无界阻塞队列中
- newSingleThreadExecutor:它创建了一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。无界队列
- newScheduleThreadPool:固定大小的线程池,支持定时及周期性任务执行
- 手动创建线程池
- ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize,
0, TimeUnit.SECONDS,
queue,
policy); -
线程池空闲大小和最大线程数根据实际情况确定
-
keepAliveTime一般设置为0(线程数大于内核数时,空闲线程终止前等待新任务的时间)
-
unit一般设置为TimeUnit.SECONDS(其他的也行,反正keepAliveTime是0)
-
任务队列需要指定大小,不要使用无界队列,容易造成OOM-> **new ArrayBlockingQueue<>(512)**
-
ThreadFactory threadFactory使用系统默认的
-
拒绝策略(1、直接忽略;2、丢弃最老的;3、由提交者执行;4、抛出一个受检异常)
- ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize,
什么是 Spring beans?
一个 bean 是被实例化, 组装, 以及由Spring IoC容器管理的对象.
1. 基础
- 控制反转是目标,依赖注入是我们实现控制反转的一种手段
- IOC
- ioc容器的实现:
- BeanFactory: IOC 容器的基本实现.只提供基础的 DI 支持
- ApplicationContext: 提供了更多的高级特性. 是 BeanFactory 的子接口.ApplicationContext面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory 。ApplicationContext发布事件给已经注册为 listener 的 bean.
- ClassPathXmlApplicationContext:从类路径下加载配置文件
- FileSystemXmlApplicationContext: 从文件系统中加载配置文件
- ApplicationContext 在初始化上下文时就实例化所有单例的 Bean
- WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作
- 就是对象之间的依赖关系由容器来创建,对象之间的关系本来是由我们开发者自己创建和维护的,在我们使用Spring框架后,对象之间的关系由容器来创建和维护,将开发者做的事让容器做,这就是控制反转。BeanFactory接口是Spring Ioc容器的核心接口
- Inversion of Control,控制反转
- 在传统的开发模式下,我们都是采用直接 new 一个对象的方式来创建对象,也就是说你依赖的对象直接由你自己控制,但是有了 IOC 容器后,则直接由 IoC 容器来控制。所以“谁控制谁”,当然是 IoC 容器控制对象
- 为何是反转:没有 IoC 的时候我们都是在自己对象中主动去创建被依赖的对象,这是正转。但是有了 IoC 后,所依赖的对象直接由 IoC 容器创建后注入到被注入的对象中,依赖的对象由原来的主动获取变成被动接受,所以是反转
- ioc容器的实现:
- DI
- 我们在使用Spring容器的时候,容器通过调用set方法或者是构造器来建立对象之间的依赖关系
- 即由容器动态的将某个依赖关系注入到组件之中
- 谁依赖于谁:当然是应用程序依赖于IoC容器
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)
- AOP:减少重复代码的目的
- 通知Advice:想要增强的功能
- 前置通知(Before)
- 后置通知(AfterReturning)
- 异常通知(AfterThrowing)
- 最终通知(After)
- 环绕通知(Around):环绕通知需要返回返回值,否则真正调用者将拿不到返回值
- 连接点:允许通知的地方
- 切入点:在连接点中选出的需要增强的点
- 切面:通知+切入点
- 引入:把切面加到目标类
- 目标对象:业务类
- 代理:一个类被AOP织入后生成出了一个结果类,它是融合了原类和增强逻辑的代理类
- JDK的动态代理:只能用于实现了接口的类产生代理。 Cglib代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强技术,生成当前类的子类对象
- 通知Advice:想要增强的功能
2. Spring Bean的生命周期
- 例化一个Bean,也就是我们通常说的new
- 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
- 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
- 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
- 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术
- 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
- 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
- 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
以上10步骤可以作为面试或者笔试的模板,另外我们这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了
3. MVC结构
模型:用于封装业务逻辑处理(java类); 视图:用于数据展现和操作界面(Servlet); 控制器:用于协调视图和模型(jsp)
- 模型(Model):负责存储系统的中心数据
- 视图(View):将信息显示给用户(可以定义多个视图
- 控制器(Controller):处理用户输入的信息。负责从视图读取数据,控制用户输入,并向模型发送数据,是应用程序中处理用户交互的部分。负责管理与用户交互,交互控制
4. MVC处理请求过程
前端控制器 (DispatcherServlet)
映射处理器(HandlerMapping)
处理器(Controller)
模型和视图(ModelAndView)
视图解析器(ViewResolver)
- Http请求:客户端请求提交到DispatcherServlet
- 寻找处理器:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)
- 处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略
- 前端控制器调用处理器适配器去执行Handler
- 处理器适配器HandlerAdapter将会根据适配的结果去执行Handler
- Handler执行完成给适配器返回ModelAndView
- 处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)
- 前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
- 视图解析器向前端控制器返回View
- 前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)
- 前端控制器向用户响应结果
5. 事务隔离级别
- ISOLATION_DEFAULT:使用后端数据库默认的隔离界别
- ISOLATION_READ_UNCOMMITTED: 允许读取尚未提交的的数据变更,可能会导致脏读、幻读或不可重复读
- ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行
6.Java中实现依赖注入的三种方式?
- 构造器注入
- set方法注入
- 接口注入
7.spring的三种实例化方式
1.默认无参的构造器实例化
2.静态工厂方法实例化
3.实例工厂方法实例化
8.配置bean方式
- 基于XML文件的配置:id,class,property(不加id,id默认为全限定名#序号)
- 基于注解的配置 可以使用注解的方式来代替XML方式的bean元素的配置(组件扫描)(id默认类名首字母小写)
- 基于Java的配置
9.延迟加载
- 默认情况下,容器启动之后会将所有作用域为单例的bean创建好;但是有的业务场景我们并不需要它提前都创建好;
- 此时,我们可以在bean中设置lzay-init=“true”,这样,当容器启动之后,作用域为单例的bean,就不在创建
10 .自动装配
- 就是将一个Bean注入到其它的Bean的Property中
11.bean元素的作用域
- 当通过Spring容器创建一个Bean实例的时候,不仅可以完成bean实例的实力化,还可以为bean指定作用域。Spring bean元素的支持以下5种作用域:
- Singleton:单例模式,在整个spring IOC容器中,使用singleton定义的bean将只有一个实例。
- Prototype:多例模式,每次通过容器中的getBean方法获取prototype定义的beans时,都会产生一个新的bean的实例。
- Request:对于每次Http请求,使用request定义的bean都会产生一个新的实例,只有在web应用时候,该作用域才会有效。
- Session:对于每次Http Session,使用session定义的Bean都将产生一个新的实例。
- Globalsession:每个全局的Http Sesisonn,使用session定义的本都将产生一个新的实例
12 .bean是线程安全的么
- Spring框架并没有对单例的bean进行多线程的封装处理,线程安全问题和并发问题,需要我们开发者自己考虑
- 但实际上,大部分的Spring bean并没有可变的状态(比如:service类和dao类),所有在某种程度上来说Spring单例bean是线程安全的。如果bean有多种状态的话(比如:View Model对象),就需要自行考虑线程安全问题
13. spring中注入一个Java Collection
list、set、map、props(字符串的键值对)(基本类型value,对象ref)
-
<list> <value>INDIA</value> <value>Pakistan</value> <value>USA</value> <value>UK</value> </list>
-
<property name="customMap"> <map> <entry key="1" value="INDIA"/> <entry key="2" value="Pakistan"/> <entry key="3" value="USA"/> <entry key="4" value="UK"/> </map> </property>
14.Spring框架中都用到了哪些设计模式
- spring代理模式,在AOP中被使用最多
- 单例模式,在Spring配置文件中定义bean的时候默认的是单例模式
- 工厂模式, BeanFactory用来创建对象的实例
- 模板方法, 用来解决重复性代码
- 前端控制器,Spring提供了DispatcherSerclet来对请求进行分发
- 视图帮助,Spring提供了一系列的JSP标签
- 依赖注入,它是惯穿于BeanFactory/ApplicationContext接口的核心理念
15.Spring中的事件处理
Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的接收者得到的该消息,该进程被阻塞并且流程将不会继续。因此,如果事件处理被使用,在设计应用程序时应注意
- 内置事件(ApplicationContext)(ConfigurableApplicationContext 中的方法):
- ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布;也可以在 ConfigurableApplicationContext*接口中使用 refresh() 方法来发生
- ContextStartedEvent:当使用ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布
- ContextStoppedEvent: 当使用ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件
- ContextClosedEvent:当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布
- RequestHandleEvent:这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。
- 监听上下文事件
- 自定义事件
- spring的事件驱动模型使用的是 观察者模式
- 通过ApplicationEvent抽象类和ApplicationListener接口,可以实现ApplicationContext事件处理
- 监听器在处理Event时,通常会进行判断传入的Event是不是自己所想要处理的,使用instanceof关键字
16.SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?
- 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能,解决方案是在控制器里面不能写字段
17.SpringMVC怎样设定重定向和转发 ?
* 在返回值的前面加”forword”,就可以实现让结果转发;
* 在返回值的前面加上”redirect”,就可以让返回值重定向
18.事务传播特性
-
PROPAGATION_REQUIRED(默认实现):当前没有事务则新建事务,有则加入当前事务
-
PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务则以非事务方式执行
-
PROPAGATION_MANDATORY:使用当前事务,如果没有则抛出异常
-
PROPAGATION__REQUIRES_NEW:新建事务,如果当前有事务则把当前事务挂起
-
PROPAGATION_NOT_SUPPORIED:以非事务的方式执行,如果当前有事务则把当前事务挂起
-
PROPAGATION_NEVER:以非事务的方式执行,如果当前有事务则抛出异常
-
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行1
19.自动注入
- @Autowired注入首先根据byType注入,当类型大于1时在根据byName注入,可以用@qualifier限定bean名称
- @Resource,两个参数分是name和type,设置哪个用哪个,都没有的话,反射获取类默认按 byName自动注入
1. 基础
- commit
- SqlSessionFactory接口(openSession(boolean autoCommit))
- DefaultSqlSessionFactory实现类(update->doUpdate)(commit)
- Mybatis框架实际上是对Jdbc的封装
- java基础api中的Connection中的commit
- springboot(SqlSessionTemplate)
- @Autowired 的 seesion 应该是 spring 管理的,依赖注入和关闭
- 一级缓存
- Mabits默认开启一级缓存
- 一级缓存的作用域是SQlSession
- 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的所有缓存会被清空。 每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中
- Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象
- SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存
- 二级缓存
- 配置文件cacheEnable true,mapper.xml中加入
标签 - 二级缓存是mapper级别的,二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享
- Mybatis默认是没有开启二级缓存的
- 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域
- 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果
- 二级缓存的POJO类实现Serializable接口(可能存储到硬盘,序列化—>存储—>反序列化获取数据)
- 二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的(需要保证对同一个表的操作在一个namespace下)
- 缓存结构式hashmap,key是namesapce+sql+参数,value是查询结果
- 配置文件cacheEnable true,mapper.xml中加入
- mapper是一级缓存还是二级缓存
- 二级缓存
2. 步骤
- 创建SQLSessionFactory
- 通过SQLSessionFactory创建SQLSession
- 通过SQLSession执行数据库操作
- 调用session.commit()提交事物
- 调用session.close()关闭会话
3. 使用Mybatis的mapper接口调用时候有哪些要求?
- Mapper接口方法名和Mapper.xml中定义的每个SQL的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sqlparameterType类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
- Mapper.xml文件中的namespace,就是接口的类路径。
- 在sqlMapConfig.xml中配置mapper.xml的位置。如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
4.配置
- mappers(映射器)
- typeAliases:设置别名的方式就是去除类所在的包后的简单的类名
- dataSource(数据源)
5. Mapper编写有几种方式 ?
- 接口实现类继承SqlSessionDaoSupport(dao类)
- 使用org.mybatis.spring.mapper.MapperFactoryBean(在config.xml中配置map.xml)
- 使用mapper扫描器:扫描器将接口通过代理方法生成实现对象,要spring容器中自动注册,名称为mapper 接口的名称
6.标签
id 属性用来标记和区别该 select,如果需要对应接口则 id 和接口方法名要一致;
parameterType 表示输入参数的类型;
resultType 表示输出结果的类型
${}
符号的作用是直接进行字符串替换,#{ }可以防止sql注入
- sql:可被其他语句引用的可重用语句块,其他语句可以用过
标签来将之包含其中 - insert(若数据库支持自动生成主键useGeneratedKeys="true" keyProperty="id")
- update
- delete
- select
- 查询出来的列名和对象属性对应关系设置
7分页查询
- Mybatis本身有分页查询,但是并不是正真的分页查询,它是把数据查出来放在内存里面
- Mysql:select * from table limit N , M; 其中:N表示从第几页开始,M表示每页显示的条数
8 Mybatis动态SQL
-
<if test="id != null and id != 0"> </if> <set> <if test="name != null and name != ''"> name = #{name}, </if> <if test="county != null and county != ''"> county = #{county}, </if> </set> <choose> <when test="name != null and name != ''"> AND name = #{name} </when> <when test="county != null and county != ''"> AND county = #{county} </when> <otherwise> AND id = #{id} </otherwise> </choose> ```
-
批量插入
-
<insert id="dynamicSqlInsertList" useGeneratedKeys="true" keyProperty="id"> insert into users (name, age, county, date) values <foreach collection="list" item="user" separator="," > (#{user.name}, #{user.age}, #{user.county}, #{user.date}) </foreach> </insert> ```
-
-
list集合参数
-
<insert id="dynamicSqlInsertList" useGeneratedKeys="true" keyProperty="id"> insert into users (name, age, county, date) values <foreach collection="list" item="user" separator="," > (#{user.name}, #{user.age}, #{user.county}, #{user.date}) </foreach> </insert> ```
-
-
map参数:需要主要${}和#{}的使用
<select id="dynamicSqlSelectMap" resultType="com.lks.bean.User">
select * from users WHERE
<foreach collection="map" index="key" item="value" separator="=">
${key} = #{value}
</foreach>
</select>
9 多表映射
- resultMap设置映射关系
- 建立一个多表字段的实体类
10 常用注解
- @Insert 、@Select、@Update 、@Delete
- @Param : 入参,作用是给参数命名,不使用@Param注解,就必须使用使用 #{}方式;不使用@Param注解时,最好传递 Javabean
- @Results :结果集合
- @Result : 包含在Results集合里,@Result代表一个字段的映射关系,column指定数据库字段的名称,property指定实体类属性的名称
- @ResultMap:已经定义的@Results可以用ResultMap直接使用,通过id匹配
1. 单例模式
- 构造函数是私有的,对象也是私有的,对外提供静态get方法
- 实现方式
- 懒汉式,线程不安全(可以给方法加锁synchronized )
- 饿汉式,类加载时就初始化,浪费内存
- 双重校验锁( synchronized (Singleton.class))
- 登记式/静态内部类:因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类
- 枚举
2. 工厂方法
- 实现方式
- 一个接口,多个实现类,一个工厂方法,根据给定类生成对应实例并返回new 对应的实现类
3. 抽象工厂
- 实现方式
- 多个接口,多个接口的各自的实现类
- 创建一个抽象工厂,包含多个工厂工厂方法,方法的实现由子类负责
- 创建工厂继承抽象工厂,覆写本工厂需要实现的特定get方法,其他get方法返回null;
4. 适配器模式
- 类适配器
- 类适配器主要是使用继承的方式连接两个接口
- 继承一个接口的实现类,然后实现另一个接口,在当前的方法中调用另一个接口的方法
- 对象适配器
- 在类中声明另一个接口实现类的对象,然后一个set值的方法
- 在当前类的方法中调用另一个实现类接口的方法
5. 装饰器模式
- 动态地给一个对象添加一些额外的职责
- 装饰模式是继承的一个替代模式
- 实现相同的接口
- 持有原有类或接口的对象,并调用它的方法,在调用前后增加额外的方法
- 装饰器强调的是增强自身
6. 代理模式
- 为其他对象提供一种代理以控制对这个对象的访问
- 代理模式是为了实现对象的控制,但不对对象本身的功能进行增强
- 是继承方案的一个替代方案
7. 观察者模式
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知
- 创建 Observer 类
- 创建实体观察者类
- 抽象被观察对象,具体被观察对象
- 然后在被观察对象里面保存观察者对象(抽象类里有一个 ArrayList 存放观察者们),有变化的时候就通知(建立一套触发机制)
8. 迭代器模式
- 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示
9.责任链模式
-
将接受者对象连成一条链,并在该链上传递请求
-
Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递
10.模板方法
- 在父类里面定义多个方法和一个方法的调用步骤
- 子类按需实现方法,共用一个条用步骤
1. 性能优化
- 查询需要的内容,比如字段,需要哪些字段查哪些字段,不要用*号,查询记录可以用limit限制条数
- 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索
引 - 为搜索字段建索引
- 应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型
- 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全
表扫描 - 避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,
将导致引擎放弃使用索引而进行全表扫描 - 使用like关键字的查询语句。在使用like关键字进行查询的查询语句中,如果匹配字符串的第一个字符为"%",索引不会起作用。只有"%"不在第一个位置,索引才会起到作用
- 组合索引,按照索引字段查询,最左前缀匹配原则
- 避免数据类型出现隐式转化。如varchar不加单引号的话可能会自动转换为int型,使索引无效,产生全表扫描。
- 避免对索引字段进行计算操作、字段上使用函数
- 查询字段少,尽量建立覆盖索引,避免对主键索引的二次扫描
2. 隔离级别
- ISOLATION_READ_UNCOMMITTED: 允许读取尚未提交的的数据变更,可能会导致脏读、幻读或不可重复读
- ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行
3. ACID
- 原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
- 一致性(Consistency): 数据库总是从一个一致性的状态转换到另一个一致性的状态
- 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其他事务是不可见的
- 持久性(Durability):一旦事务提交,则其所做的修改不会永久保存到数据库
4. 存储引擎
- MyISAM
- 不支持事务、行级锁和外键约束的功能
- 拥有较高的插入,查询速度
- 并发插入特性允许同时选择和插入数据
- 本地磁盘上建立三个文件:表定义文件(.frm),数据文件(.MYD),索引文件(.MYI)
- InnoDB
- 支持事务和行级锁定,比MyISAM处理速度稍慢
- 会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引
- MySQL支持外键的存储引擎只有InnoDB
- 支持自动增加列AUTO_INCREMENT属性
- 两个文件表定义文件(.frm),数据和索引文件(.IBD)
- InnoDB的表必须要有主键,如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引、如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引
5. 索引结构
- HASH
- Hash索引的底层实现是由Hash表来实现的,非常适合以 key-value 的形式查询,也就是单个key 查询,或者说是等值查询
- Hash索引仅仅能满足“=”,“IN”,“<=>”查询,不能使用范围查询
- 联合索引中,Hash索引不能利用部分索引键查询。 对于联合索引中的多个列,Hash是要么全部使用,要么全部不使用,并不支持BTree支持的联合索引的前缀匹配
- Hash索引还存在Hash冲突的问题,当有多条记录满足某个Hash键值的数据时,还需要通过访问表中的实际数据进行比较,并得到相应的结果
- BTree
- B+Tree索引可以被用在=,>,>=,<,<=和between这些比较操作符上(范围查询)
- B+Tree索引还可以用于like操作符,只要它的查询条件是一个不以通配符(%)开头的常量
- B+Tree非叶子节点不存数据,只存索引,一个节点16K,为的是尽可能多的读到索引到内存进行比较,压缩树的高度,减少IO次数,所以索引字段大小也尽可能的小
- B+Tree索引根节点一般是常驻内存的,减少一次IO
6. 如何建立索引
- 直接创建索引:CREATE INDEX index_name ON table(column(length))
- 修改表结构的方式添加索引:ALTER TABLE table_name ADD INDEX index_name ON (column(length))
- 创建表的时候:
-
CREATE TABLE `table` ( `id` int(11) NOT NULL AUTO_INCREMENT , `title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL , `time` int(10) NULL DEFAULT NULL , PRIMARY KEY (`id`), INDEX index_name (title(length)) )
-
7. 索引类型
-
聚集索引
- 指索引项的排序方式和表中数据记录排序方式一致的索引,每张表只能有一个聚集索引,聚集索引的叶子节点存储了整个行数据(说的就是InnoDB引擎的表)
-
非聚集索引
- 非聚集索引中索引的逻辑顺序与磁盘上记录的物理存储顺序不同。一个表中可以拥有多个非聚集索引。叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,还存储了一个指向该记录的聚集索引建的书签(说的就是MyISAM引擎的表)
-
索引类型
- 普通索引:最基本的索引,它没有任何限制
- 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
- 主键索引:特殊的唯一索引,一个表只能有一个主键,不允许有空值
- 组合索引:多个字段上创建的索引,使用组合索引时遵循最左前缀匹配原则
- 全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较,类似搜索引擎
8. join
- inner join:内连接查询只能查询出匹配的记录
- Left/Right join:左右分别为主表去关联另一张表的值,主表的值会全部查出来
- Full join:全连接的查询结果是左外连接和右外连接查询结果的并集
9.UNION
- UNION
- 内部的 SELECT 语句必须拥有相同数量的列.
- 列也必须拥有相似的数据类型.
- 每条 SELECT 语句中的列的顺序必须相同.
- UNION 会排序去重,UNION ALL不会,所以UNION ALL效率高一点
10.Where 和 Having
- WHERE 从句一般是在行的层级去筛选数据 (before grouping). HAVING 从句一般在 GROUP BY 之后所以是在 "groups" 的基础上删选.
11.通配符
- % 替代一个或多个字符
- _ 仅替代一个字符
大数据处理框架,数据量大或者处理时间长的,利用集群计算
1. spark
- spark本身并没有提供分布式文件系统,因此spark的分析大多依赖于Hadoop的分布式文件系统HDFS
- 相比于Mapreduce,spark的速度更快并且提供的功能更加丰富
- 能够实现实时计算
1. hadoop
- Hadoop有两个核心模块,分布式存储模块HDFS和分布式计算模块Mapreduce
- 由 HDFS 负责静态数据的存储,并通过 MapReduce 将计算逻辑分配到各数据节点进行数据计算和价值发现
2. zk数据结构
zookeeper=文件系统+监听通知机制
zookeeper实现一致性协议的服务(cap中的cp),配置管理
zookeeper:用作服务注册和发现,集群管理
zookeeper:zookeeper的文件系统里创建一个目录,即有唯一的path。命名服务
- 树形结构,其上的每一个节点,我们称之为“znode”
- 每一个znode默认能够存储1MB的数据
- znode
- zxid:时间戳
- version:版本号
- data:
- tick:租约协议
3. zk选举过程
redis,通过哨兵实现主从切换
Eureka集群中的各节点之间不存在主从关系,相互复制
- Leader选举
- (1) 服务器初始化启动
- (2) 服务器运行期间无法和Leader保持连接
- 启动时的选举过程
- 每个Server发出一个投票,初始情况,Server2都会将自己作为Leader服务器来进行投票,使用(myid, ZXID)来表示
- 接受来自各个服务器的投票,判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器
- 处理投票,投票pk,优先检查ZXID,相同则检查myid,大的作为leader
- 统计投票,过半机器(防止脑裂,redis-fail也需要半数)接受到相同的投票信息,便认为已经选出了Leader
- 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING
- Leader服务器挂了,选举过程
- 变更状态,都会将自己的服务器状态变更为LOOKING
- 每个Server会发出一个投票
- 接收来自各个服务器的投票
- 处理投票
- 统计投票
- 改变服务器的状态
4. zk分布式锁
所有客户端都去创建 /distribute_lock 节点,创建成功的拿到锁,如果/distribute_lock 已经预先存在
- 在获取分布式锁的时候在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点
- 调用getChildren(“locker”)来获取locker下面的所有子节点
- 如果自己创建的子节点序号最小,就说明获取到锁了,如果不是最小,就对修小的节点调用exist()方法,同时对其注册事件监听器
- 被关注的节点删除,得到通知,又开始判断自己的子节点序号是不是最小的,重复之前的步骤,直到自己的子节点序号最小,获得锁
1.nginx反向代理配置
- Nginx.conf
- location
- proxy_pass http://domain;
- proxy_set_header X-Real-IP $remote_addr;
- 多地址
- weight、ip_hash、fair(按后端服务器的响应时间来分配请求。响应时间短的优先分配)、url_hash、轮询
- upstream prod {
server localhost:8081 weight=5;
} - proxy_pass http://prod/;
- upstream prod {
2. springboot自动配置是如何实现的
- @SpringBootApplication由三个注解组成
- @Configuration
- @ComponentScan
- @EnableAutoConfiguration
- @EnableAutoConfiguration 是实现自动配置的入口,该注解又通过 @Import 注解导入了AutoConfigurationImportSelector,在该类中加载 META-INF/spring.factories 的配置信息。然后筛选出以 EnableAutoConfiguration 为 key 的数据,加载到 IOC 容器中,实现自动配置功能!
3.spring缓存
- @Cacheable
- @CachePut
- @CacheEvict
4.内置了 Tomcat/ Jetty 等容器
5. 读取配置文件
- @PropertySource,@Value,@Environment, @ConfigurationProperties
6.刷新上下文事件时,从一个工厂类初始化的
7.多态:
- 方法的多态性
- 重载overloading:就是一个类里有两个或更多的函数,名字相同而他们的参数不同.(Compile 时的多态)
- 覆写overriding:是发生在子类中!也就是说必须有继承的情况下才有覆盖发生. 当你继承父类的方法时, 如果你感到哪个方法不爽,功能要变,那就把那个函数在子类中重新实现一遍(runtime 多态)
- 对象的多态性(前提:方法覆写)
- 父类 父类实例 = 子类实例
8. 反射机制
- 运行时取得类的方法和字段的相关信息。
- 创建某个类的新实例(.newInstance())
- 取得字段引用直接获取和设置对象字段,无论访问修饰符是什么
9. Integer的缓存
- 两个包装类型相比,只要有一个是new出来的,都为false
- int和Integer(无论是否new的对象)比,都为true,因为会把Integer自动拆箱为int再去比
- 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false