概述
Java基础和一些小知识点
基本类型
byte 1字节
short 2字节
int 4字节
long 8字节
char 2字节(C语言中是1字节)可以存储一个汉字
float 4字节
double 8字节
boolean false/true(理论上占用1bit,1/8字节,实际处理按1byte处理)
接口和抽象类的区别
抽象类要被子类继承,接口要被类实现。
接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现
接口是更抽象的抽象类
接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量。
泛型的作用
把类型当作是参数一样传递
String StringBuffer StringBuild区别
1、String的对象是不可变的,每次对String对象进行改变的时候,都会new一个新对象,然后再将指针指向新的对象,而StringBuffer与StringBuild都是对自身进行操作。
2、String是线程安全的,Stringbuffer对方法添加了同步锁也是线程安全的,StringBuild没有对方法添加同步锁,属于非线程安全的。
和StringBuffer的区别在于Stringbuild是一个单线程使用的类,不值执行线程同步所以比StringBuffer的速度快,效率高。是线程非安全的。
深拷贝和浅拷贝的区别
1、拷贝的两层含义,对应了浅拷贝和深拷贝的概念,做了第一层,就是浅拷贝,做到第二层,就是深拷贝。
2、浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。
3、深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”。
==和equals的区别
一、对象类型不同
1、equals():是超类Object中的方法。
2、==:是操作符。
二、比较的对象不同
1、equals():用来检测两个对象是否相等,即两个对象的内容是否相等。
2、==:用于比较引用和比较基本数据类型时具有不同的功能,具体如下:
(1)、基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。
(2)、引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。
三、运行速度不同
1、equals():没有==运行速度快。
2、==:运行速度比equals()快,因为==只是比较引用。
try catch finally
只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行
当finally 相对应的 try 语句块之前,已经抛出错误,或者已经返回,return,就不会执行finally
结论:
-
不管有没有出现异常,finally代码块都会执行;
-
不管try和catch的代码块中有return时,finally仍会执行,且如果finally代码块也有return,则此代码肯定会返回finally执行的return值。
String a="abc"和String b = new String("abc");
String a="abc"
编译时,String a="abc" 会把"abc"放到常量池中,再定义b时,因为常量池中已存在“abc”,所有不会再创建,运行时JVM(JAVA虚拟机)则认为这两个变量赋的是同一个对象,所以返回true。
String b=new String("abc")
编译过程会把会把字符串“abc”放到在常量池中。用构造器创建的对象,是生成不同的对象。每new一次JVM就会在堆中创建一个对象。String a,b只是内容相同罢了。用equals()或者System.out.print(a.intern()==b.intern());就返回true了。(intern() 方法返回字符串对象的规范化表示形式)
迭代器
迭代器(Iterator)
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。 (2) 使用next()获得序列中的下一个元素。 (3) 使用hasNext()检查序列中是否还有元素。 (4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
拦截器、过滤器、监听器
过滤器和拦截器的区别
二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。不同的是:①过滤器是JAVAEE标准,基于函数回调,而拦截器是基于java的反射机制;②过滤器依赖于servlet容器,拦截器不依赖于servlet容器,因为Filter是在Servlet规范中定义,是Servlet容器支持的。而拦截器是在Spring容器内,是Spring框架支持的;③Filter是Servlet规范规定的,只能用于Web程序中,只在Servlet前后起作用。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中,能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在 Spring 构架的程序中,要优先使用拦截器;④过滤器可以对几乎所有的请求起作用,而拦截器只能对action请求起作用;⑤过滤器不能访问action上下文、值栈里的对象,而拦截器可以访问;⑥在action的生命周期中,过滤器只能被调用一次(在容器初始化时),而拦截器可以多次被调用;⑦拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
过滤器的触发时机是在请求进入容器后,进入servlet之前进行预处理。请求结束返回也是,是在servlet处理完后,返回给前端之前进行后期处理。
所以过滤器的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)的入参是ServletRequest ,而不是httpservletrequest。因为过滤器是在httpservlet之前。
拦截器是被包裹在过滤器之中的。
SpringMVC 中的Interceptor 拦截器的主要作用就是拦截用户的 url 请求,并在执行 handler 方法的前中后加入某些特殊请求,类似于 servlet 里面的过滤器。
springMVC监听器主要的作用就是spring容器启动的时候加载一些数据,最常用的功能就是开发权限系统的时候,当监听器启动的时候,从数据库加载权限url。
Listener监听器也是Servlet层面的,可以用于监听Web应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。
根据实现原理分成下面两大类:
-
Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
-
Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean。
Mybatis一级缓存二级缓存
mybatis 的一级缓存和二级缓存?
Mybatis的一级缓存是默认开启的,它只相对于同一个SqlSession有效,所以也称之为SqlSession缓存。当参数和SQL完全相同的情况下,使用同一个SqlSession对象调用同一个Mapper方法,当第1次执行SQL语句后,MyBatis会自动将其放在缓存中,后续再次查询时,如果没有声明需要刷新,且缓存没有超时,会直接取出此前缓存的数据,而不会再次发送SQL到数据库。Mybatis的二级缓存是默认未开启的,如果希望开启,需要在配置SQL的XML文件中配置<cache>节点,由于每个XML都通过根节点的namespace属性对应一个Mapper接口,所以,二级存储也称之为namespace缓存!在使用二级存储时,查询数据的select节点需要配置useCache="true",并且,查询返回的结果类型必须是实现了Serializable接口的!另外,当缓存 了数据后,如果执行了当前XML中配置的增、删、改操作,会自动刷新此前的缓存数据!
异常
1.ClassCastException(类转换异常)
数据类型转换错误,比如有个String temp="abc";
如果设为(int)temp就会报错了,因为它们类型不一样,但是设为(object)temp就可以,因为object是它们的父类
2.IndexOutOfBoundsException(数组越界)
这个异常我们在操作数组的时候会经常遇到,异常的解释是“数组下标越界“,现在程序中大多都有对数组的操作,因此在调用数组的时候一定要认真检查,看自己调用的下标是不是超出了数组的范围,一般来说,显示(即直接用常数当下标)调用不太容易出这样的错,但隐式(即用变量表示下标)调用就经常出错了,还有一种情况,是程序中定义的数组的长度是通过某些特定方法决定的,不是事先声明的,这个时候,最好先查看一下数组的length,以免出现这个异常。
3.NullPointerException(空指针)
这个异常在编程时也经常遇到,异常的解释是 “程序遇上了空指针
“,简单地说就是调用了未经初始化的对象或者是不存在的对象,这个错误经常出现在调用数组这些操作中,对数组操作中出现空指针,很多情况下是一些刚开始学习编程的人常犯的错误,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)。
4.IllegalAccessException(安全权限异常)
这个异常的解释是“没有访问权限“,当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常。
-
IOException(输入输出异常)
一般读写文件会出现这个异常,比如你想从磁盘上读一个文件到你写的程序,如果硬盘上没有这文件,java虚拟机就会报这个异常。
集合
集合和数组的区别
数组是固定长度的;集合可变长度的。数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。数据结构:就是容器中存储数据的方式。
Collection接口的子接口包括:Set接口、Queue接口和List接口。
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等。
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等。
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、 ConcurrentHashMap以及Properties等 。
List,Set,Map三者的区别?
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、 List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是 collection的子接口。
Collection集合主要有List和Set两大接口
List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有ArrayList、LinkedList 和 Vector。
Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、 LinkedHashSet 以及 TreeSet。
Map:是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。 Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap。
集合框架底层数据结构
1.List
Arraylist: Object数组 。扩容1.5倍
Vector: Object数组 。
LinkedList: 双向循环链表 。
2.Set
1.HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素。
2.LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
3.TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
3.Map
1.HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主 体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。(利用拉链法形成的数据结构)
扩容
-
HashMap中有两个重要参数,初始容量大小和负载因子,在HashMap刚开始初始化的时候,使用默认的构造方法,会返回一个空的table,并且 thershold(扩容阈值)为
0
,因此第一次扩容的时候默认值就会是16
,负载因子默认为0.75
,用数组容量乘以负载因子得到一个值,一旦数组中存储的元素个数超过这个值就会调用rehash方法将数组容量增加到原来的两倍,threshold也会变为原来的两倍 -
在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。所以,如果知道要存入的数据量比较大的话,可以在创建的时候先指定一个比较大的数据容量
-
也可以引申到一个问题HashMap是先插入还是先扩容:HashMap初始化后首次插入数据时,先发生resize扩容再插入数据,之后每当插入的数据个数达到threshold时就会发生resize,此时是先插入数据再resiz
2.LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是 基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
3.HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 。
4.TreeMap: 红黑树(自平衡的排序二叉树)
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。
HashTable是直接在操作方法上加synchronized关键字,锁住整个数组,粒度比较大;
Collections.synchronizedMap是使用Collections集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;
ConcurrentHashMap使用分段锁,降低了锁粒度,让并发度大大提高。
ConcurrentHashMap 的基本策略是将 table 细分为多个 Segment 保存在数组 segments 中,每个 Segment 本身又是一个可并发的哈希表,同时每个 Segment 都是一把 ReentrantLock 锁,只有在同一个 Segment 内才存在竞态关系,不同的 Segment 之间没有锁竞争,这就是分段锁机制。Segment 内部拥有一个 HashEntry 数组,数组中的每个元素又是一个链表。
ArrayList线程不安全,安全方案
1、使用synchronized关键字。
2.使用Collections.synchronizedList();
ArrayList 和 LinkedList 的区别是什么?
数据结构实现:ArrayList 是动态数组的数据结构实现,实现了 RandomAccess 接口,因此查找的时候非常快。而 LinkedList 是双向链表的数据结构实现。
随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。ArrayList 比较适合顺序添加、随机访问的场景。
增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高;因为 ArrayList 增删操作要影响数组内的其他数据的下标,ArrayList 在顺序添加一个元素的时候非常方便。
内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
HashMap和HashTable的区别?
(1)hashMap同步,hashTable不同步,都非安全
(2)hashTable的key和value都不允许null,hashMap允许
(3)HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历
(4)HashTable直接使用对象的hashCode,hashmap重新计算hash值
线程
线程池
1.参数
corePoolSize(线程池的基本大小)
maximumPoolSize(线程池最大大小)
runnableTaskQueue(任务队列)
ThreadFactory(创建线程工厂)
RejectedExecutionHandler(拒绝策略)
keepAliveTime(线程活动保持时间)
TimeUnit(线程活动保持时间的单位)
2.线程池流程(原理)
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
3.线程池拒绝策略
AbortPolicy:ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。直接抛出异常也不处理
CallerRunsPolicy:CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。
DiscardPolicy:采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
DiscardOldestPolicy:DiscardOldestPolicy策略的作用是,当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去
4.创建线程池
线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors
创建的,1 种是通过ThreadPoolExecutor
创建的):
-
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
-
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
-
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
-
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
-
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
-
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
-
ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
进程和线程
区别
1、进程是资源分配最小单位,线程是程序执行的最小单位;
2、进程有自己独立的地址空间,线程没有独立的地址空间;
3、CPU切换一个线程比切换进程花费小,线程比进程开销小;
4、进程对资源保护要求高,线程资源保护不高。
关系:
一个进程可以有多个线程,但至少有一个线程;一个线程只能在一个进程的地址空间内活动
多线程和多进程
多线程的优点
(1)无需跨进程边界;程序逻辑和控制方式简单;
(2)所有线程可以直接共享内存和变量等;
(3)线程方式消耗的总资源比进程方式好。
多线程的缺点
(1)每个线程与主程序共用地址空间,受限于2GB地址空间;
(2)线程之间的同步和加锁控制比较麻烦;一个线程的崩溃可能影响到整个程序的稳定性;(3)到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
(4)线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU 。
多进程的优点
(1)每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
(2)通过增加CPU,就可以容易扩充性能;
(3)可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
(4)每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。
多进程的缺点
(1)逻辑控制复杂,需要和主程序交互;
(2)需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。总结:最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+CPU+轮询方式来解决问题……方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。按照多个不同的维度(类别),来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。
wait和sleep
在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
线程的六种状态
NEW:初始状态,创建一个线程对象时就是该状态。 RUNNABLE:运行状态,它包含了就绪(READY)和运行中(RUNNING)两种状态。当线程对象创建后,调用该对象的 start() 方法就会进入就绪状态(READY)。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 CPU 的使用权,在获得 CPU 时间片后会变为运行中状态(RUNNING)。
BLOCKED:阻塞状态,表示线程此时阻塞于锁。 WAITING:等待状态,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
TIMED_WAITING:超时等待状态,该状态与 WAITING 的不同点在于它可以在指定的时间后自行返回。 TERMINATED:终止状态,表示该线程已经执行完。
实现一个线程有哪几种方式
线程有3种实现方式:
①.通过继承Thread类,优点:可以直接调用start方法启动。缺点:继承一个类后,不能再继承别的类。需要重写run方法。无返回值。
②.实现Runnable接口,优点:可以实现多个接口或继承一个类;缺点:不能直接启动,要通过构造一个Thread把自己传进去。需要重写run方法,无返回值。
③.实现Callable接口,优点:可以抛出异常,有返回值;缺点:只有jkd1.5以后才支持。需要重写call方法。结合FutureTask和Thread类一起使用,最后调用start启动。
一般最常用的是第二种,实现Runnable接口。比较方便,可扩展性高。
实现线程要重写什么方法?
使用Thread类,要重写run方法,或实现Runnable接口时,要实现run()方法
使用Callable接口时,要重写call方法,且有返回值。futuretask
start方法和run方法有什么区别
start用于启动线程,当调用start后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu给的时间片。
run用于子类重写来实现线程的功能。
我们一般调用的是start方法,系统调用的是run方法。
sleep方法有什么作用,一般用来做什么
sleep是一个Thread类的静态方法,让调用它的线程休眠指定的时间,可用于暂停线程,但不会把锁让给其他线程,时间一到,线程会继续执行。
wait和sleep
sleep方法是当前线程休眠,让出cpu,不释放锁,这是Thread的静态方法;wait方法是当前线程等待,释放锁,这是Object的方法
讲下join,yield方法
join线程有严格的先后顺序,调用它的线程需要执行完以后其他线程才会跟着执行。
yield是暂停当前正在执行的线程对象,把时间让给其他线程。
使用场合:join线程有严格的先后顺序,yield当前线程占用cpu使用率很高时,把时间让出来。(死循环时)
什么是线程同步,什么是线程安全
当两个或两个以上的线程需要共享资源,他们就需要某种方法来确定资源在某一刻仅被一个线程占用。
线程安全就是多线程操作同一个对象不会有问题,线程同步一般来保护线程安全,final修饰的也是线程安全
讲下同步方法和同步块的区别
同步方法就是被synchronized修饰的方法,同步整个方法,且整个方法都会被锁住,同一时间只有一个线程可以访问该方法。整个业务,缺点:性能差
同步块就是使用synchronized修饰的代码块,可以同步一小部分代码
同步块越小性能越好,当性能要求比较高时,用同步块
讲下什么是死锁
死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此有不肯放开,处于僵持状态,此时便造成了死锁。
1.互斥条件:共享资源x,y只能被一个线程占用2.占有且等待:线程t1已经获得了共享资源x,在等待y的时候不释放x3.不可抢占:其他线程不可强行抢占t1所拥有的资源4.循环等待:t1等待t2,t2等待t1解决方法:1.互斥不可打破2.打破占有且等待:一次性申请所有资源3.打破不可抢占:在等待其他资源的时候释放已经占有的资源4.打破循环等待:按序申请资源
wait,notify,notifyAll
wait ,notify , notifyAll都必须在synchronized修饰的方法或synchronized块中使用,都属于Object的方法,可以被所有类继承,都是final修饰的方法,不能通过子类覆写去改变他们的行为。
Lock锁和synchronized
Lock锁介绍:
在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景、高效的性能,java还提供了Lock接口及其实现类ReentrantLock和读写锁 ReentrantReadWriteLock。
相比synchronized来实现同步,使用Lock实现同步主要有以下差异性:
1、使用synchronized关键字时,锁的控制和释放是在synchronized同步代码块的开始和结束位置。而在使用Lock实现同步时,锁的获取和释放可以在不同的代码块、不同的方法中。这一点是基于使用者手动获取和释放锁的特性。
2、Lock接口提供了试图获取锁的tryLock()方法,在调用tryLock()获取锁失败时返回false,这样线程可以执行其它的操作 而不至于使线程进入休眠。tryLock()方法可传入一个long型的时间参数,允许在一定的时间内来获取锁。
3、Lock接口的实现类ReentrantReadWriteLock提供了读锁和写锁,允许多个线程获得读锁、而只能有一个线程获得写锁。读锁和写锁不能同时获得。实现了读和写的分离,这一点在需要并发读的应用中非常重要,如lucene允许多个线程读取索引数据进行查询但只能有一个线程负责索引数据的构建。
4、基于以上3点,lock来实现同步具备更好的性能。
IO流
什么是IO流?
它是一种数据的流从源头流到目的地。比如文件拷贝,输入流和输出流都包括了。输入流从文件中读取数据存储到进程(process)中,输出流从进程中读取数据然后写入到目标文件。
字节流和字符流的区别。
字节流在JDK1.0中就被引进了,用于操作包含ASCII字符的文件。JAVA也支持其他的字符如Unicode,为了读取包含Unicode字符的文件,JAVA语言设计者在JDK1.1中引入了字符流。ASCII作为Unicode的子集,对于英语字符的文件,可以使用字节流也可以使用字符流。
Java中流类的超类主要有那些?
-
java.io.InputStream
-
java.io.OutputStream
-
java.io.Reader
-
java.io.Writer
FileInputStream和FileOutputStream是什么?
这是在拷贝文件操作的时候,经常用到的两个类。在处理小文件的时候,它们性能表现还不错,在大文件的时候,最好使用BufferedInputStream (或 BufferedReader) 和 BufferedOutputStream (或 BufferedWriter)
字节流和字符流,你更喜欢使用哪一个?
个人来说,更喜欢使用字符流,因为他们更新一些。许多在字符流中存在的特性,字节流中不存在。比如使用BufferedReader而不是BufferedInputStreams或DataInputStream,使用newLine()方法来读取下一行,但是在字节流中我们需要做额外的操作。
System.out.println()是什么?
println
是PrintStream的一个方法。out
是一个静态PrintStream类型的成员变量,System
是一个java.lang包中的类,用于和底层的操作系统进行交互。
什么是Filter流?
Filter Stream是一种IO流主要作用是用来对存在的流增加一些额外的功能,像给目标文件增加源文件中不存在的行数,或者增加拷贝的性能。
说说PrintStream和PrintWriter
他们两个的功能相同,但是属于不同的分类。字节流和字符流。他们都有println()方法。
在文件拷贝的时候,那一种流可以提升更多的性能?
在字节流的时候,使用BufferedInputStream和BufferedOutputStream。在字符流的时候,使用BufferedReader 和 BufferedWriter
说说File类
它不属于 IO流,也不是用于文件读写的,它主要用于知道一个文件的属性,读写权限,大小等信息。
File 类是磁盘文件和目录的抽象表示。为了便于对文件和目录进行统一管理,java 把目录也作为一种特殊的文件处理。File 类提供了一些方法来操作文件和获取文件的基本信息。通过这些方法,可以得到文件或目录的诸如路径、名称、大小、日期、文件长度、读写属性 等信息,也可以创建、删除、重命名目录或文件,对文件或目录列表显示、查找等。但是File 类没有包含读写文件内容的方法。
反射机制
什么是反射?
JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
Java反射机制主要提供了以下功能:
1.在运行时判断任意一个对象所属的类。
2.在运行时构造任意一个类的对象。
3.在运行时判断任意一个类所具有的成员变量和方法。
4.在运行时调用任意一个对象的方法。
动态代理是什么?应用场景?
动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程
怎么实现动态代理?
Java领域中,常用的动态代理实现方式有两种,一种是利用JDK反射机制生成代理,(另外一种是使用CGLIB代理。)
此时引入ORM,主要用到的方法
数据库连接步骤
注册驱动
Class.forName(“驱动类的完整路径”); 强烈建议方式
建立连接(Connection)
Connection conn = DriverManager.getConnection(url,user,pass);
创建用于向数据库发送SQL的Statement对象,并发送sql
Statement st = conn.createStatement(); //建议PreparedStatement
ResultSet rs = st.excuteQuery(sql);
PreparedStatement是预编译的,对于批量处理可以大大提高效率.
从代表结果集的ResultSet中取出数据,打印到命令行窗口
while(rs.next())
{
System.out.println(rs.get());
}
断开与数据库的连接,并释放相关资源
rs.close();
st.close();
conn.close();
ORM工具(对象映射工具)
访问数据库底层利用了Java的JDBC的操作,但是这个操作有点繁琐,于是我先把JDBC封装起来。使用这个工具需要设置了一个po类,有两个约定,表名即为类名,字段名即为属性名。在存储过程中,把po类放入工具里面,根据Java的反射机制生成一个sql语句;查询过程中,需要传递一个字节码,告诉工具需要封装成哪一个po类对象,根据查询到的结果封装成一个po类。在这个类里面,我也写了一个数据库连接池,用队列管理,初始化的时候创建一系列的对象放在队列里,需要用的完了就归还,这个过程需要加锁。还有用一个配置文件,实现可配置化。这个工具我也拿给其他同学用,他们的评价还蛮高的。
数据结构
链表
头插法
要点:一个head指针,head==null和head!=null效果一样
Node head=null; int m=reader.nextInt(); while(m>0) { Node p=new Node(m); //head==null和head!=null效果一样 p.next=head; head=p; m=reader.nextInt(); } return head; }
尾插法
要点:两个指针,一个head,一个tail,从tail插入
Node head=null,tail=null; int m=reader.nextInt(); while(m>0) { Node t=new Node(); if(head==null) head=tail=t; else { tail.next=t; tail=t; } m=reader.nextInt(); }
遍历链表
Node head = this.next; while(head!=null) { System.out.print(" "+head.data); head=head.next; }
链表倒置
要点:两个指针p,q从
if(this.next==null)return; Node head=this.next; Node p=head,q=head; head=null; while (p!=null) { q=p.next; p.next=head; head=p; p=q; } this.next=head;
合并两个链表
public void mergLink(Node head) { if(head==null)return; if(this.next==null) {this.next=head;return;} Node p=this.next; Node tail=null; if(p.data<head.data) { tail=p; p=p.next; } else { this.next=head; tail=head; head=head.next; } while(p!=null&&head!=null) { if(p.data<head.data) { tail.next=p; tail=p; p=p.next; } else { this.next=head; tail=head; head=head.next; } } if(p==null) tail.next=head; else tail.next=p; }
有序链表中插入一个数保证有序
public boolean insertNumber(int number) { Node t=new Node(number); if(this.next==null) {this.next=t;return true;} Node p=this.next,q=this.next; while(p!=null&&p.data<number) { q=p; p=p.next; } if(p==q) { t.next=this.next; this.next=t; } else { q.next=t; t.next=p; } return true; }
删除一个数
public boolean deleteNumber(int number) { Node p=null,q=null; if(p==null) p=q=this.next; if(p.next==null) return false; while (p!=null) { p=p.next; if(q.data==number) this.next=p; else q=q.next; } return true; }
判断Y型链表
public Node isLikeYLinked(Node head1,Node head2) { if(head1==null||head2==null) return null; Node p=head1; Node q=head2; int length1=0; int length2=0; while(p!=null) { p=head1.next; head1=p; length1++; } while(q!=null) { q=head2.next; head2=p; length2++; } int y=Math.abs(length1-length2); for(int i=0;i<=y;i++) { p=head1.next; head1=p; q=head2.next; head2=p; } if(p==q) return p; else if(length1>length2) { Node t=q; while(p!=t) { p=head1.next; head1=p; } return p; } else { Node t=p; while(p!=t) { q=head1.next; head1=q; } return q; } }
二叉树
前序
中序
后序
红黑树
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。
排序
排序算法原理
冒泡
冒泡排序就是对于一个数组,通过n次遍历,每次遍历将相邻的数组元素两两比较,如果前一个数比后一个数大,就交换数据,这样就实现了了从小到大的排序。 选择排序:选择排序就是对于一个数组,通过n次遍历,每次遍历找到剩余元素的最小 值,将这个最小值放在已排好的元素的后面。
选择
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
快排
就是使⽤用一个指针指向某一个元素,然后先从后往前找到第⼀一个比它小的元素, 交换元素值,然后再从前往后找到第一个比它大的元素,再交换元素值。然后使⽤用递归或者非递归的方法重复这些操作,只到元素被排好序。堆排序
把要排序的n 个数看作是⼀一棵顺序存储的二叉树,调整它们的存储序号,使它 成为一个小根堆,将堆顶元素输出,得到n个元素中最小的元素。然后对剩下的n-1个元 素按同样的方法递归。(堆中某节点的值总是不大于或不小于其父节点的值) 归并排序:先把n个元素拆分为一个个独立的区间,再利利⽤用区间两两合并的方法直到产生一个顺序序列。
插入法
就是对于数组中的任意一个元素,从前面开始找到第一个比它大的元素,如果存在的话就把这个元素插入到比它大的元素的位置。
二分法
就是对于数组中的任意⼀一个元素,将它前面的元素看成⼀一个区间,把这个元素 插入到这个区间中。
归并排序
-
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
-
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
-
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
-
重复步骤 3 直到某一指针达到序列尾;
-
将另一序列剩下的所有元素直接复制到合并序列尾。
private static void mergOne(int[] data,int left,int mid,int right) { if(data[mid]<=data[mid+1]) return ; for(int i=mid+1;i<=right;i++) for(int j=i;j>left;j--) if(data[j]<data[j-1]) { int t=data[j]; data[j]=data[j-1]; data[j-1]=t; }else break; } private static void mergSort(int[] data,int left,int right) { int mid=(left+right)/2; if(left<mid) mergSort(data,left,mid); if(mid+1<right) mergSort(data,mid+1,right); mergOne(data,left,mid,right); } public static void mergSort(int[] data) { if(data==null) return ; mergSort(data,0,data.length-1); } public static void mergSort2(int[] data) { if(data==null) return ; LinkedQueue<Integer> queue=new LinkedQueue<Integer>(); for(int i=0;i<data.length;i++) { queue.inQueue(i); queue.inQueue(i); } while(!queue.isEmpty()) { int left1=queue.outQueue(); int right1=queue.outQueue(); int left2=queue.outQueue(); int right2=queue.outQueue(); if(right1+1!=left2) { queue.inQueue(left1); queue.inQueue(right1); left1=left2;right1=right2; left2=queue.outQueue(); right2=queue.outQueue(); } mergOne(data, left1, right1, right2); if(queue.isEmpty()) break; queue.inQueue(left1); queue.inQueue(right2); } }
快排
不稳定的。
比如对9 6 9 1 2 3排序,第一趟排序会变成3 6 9 1 2 9。后面的那个9是第一次出现的9,因此是不稳定的。
对于时间复杂度,快排在最好的状态下是nlogn,最坏的情况下是n2。下面讲讲我自己的理解。
因为快排是个递归算法,一个递归算法的时间复杂度是和递归的深度有关,递归就是个二叉树。
如果情况比较好,二叉树是平衡的,那么它的深度就是logn,再加上每次划分时候需要比较n次,所以时间复杂度是nlogn。
如果情况最坏,二叉树完全偏向一边,那么二叉树就退化成线性的了,深度就是n。再加上划分时候需要比较n次,所以时间复杂度是n2
private static int quickOne(int[] data,int left,int right) { int key=data[left]; while(left<right) { while(left<right&&data[right]>=key) right--; data[left]=data[right]; while(left<right&&data[left]<key) left++; data[right]=data[left]; } data[left]=key; return left; } private static void quickSort(int[] data,int left,int right) { int mid=quickOne(data, left, right); if(left<mid-1) quickSort(data,left,mid-1); if(mid+1<right) quickSort(data, mid+1, right); } public static void quickSort(int[] data) { if(data==null) return ; quickSort(data,0,data.length-1); }
JVM垃圾回收
Java内存空间
-
JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):
-
JVM对自己的内存进行了划分5个区域
a、寄存器:内存和CPU之间
b、本地方法栈:JVM调用了系统中的功能
c、方法和数据共享:运行时期class文件,进行的地方
d、方法栈:所有的方法运行的时期,进行的内存
e、堆(heap):存储的是容器和对象
垃圾
垃圾是指在运行程序时没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
内存泄露:对象已经不再使用,但是它指向了其它对象,导致这个对象无法回收,称之为内存泄露。
为什么需要GC?(垃圾回收)
1、如果不及时对内存中的垃圾进行回收,这些垃圾对象所占有的内存空间就会一直保留到应用程序结束,被保留的空间无法被其它对象占用,设置有可能导致内存溢出。
2、除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片,碎片整理将所占用的堆内存移到堆的一端,以便JVM将整理出的内存分配给新的对象
3、当应用程序较大、较复杂时,没有GC就无法保证应用程序的正常进行,因此也需要根据实际需求对GC进行优化。
内存动态分配和回收关注的是哪几部分的内存?
Java虚拟机在运行时会管理以下的内存区域:
-
程序计数器:当前线程执行的字节码的行号指示器,线程私有
-
JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
-
本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
-
JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
-
方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。
-
运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
-
直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分。
按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。
在Java内存运行时区域的各个部分当中,程序计数器、Java虚拟机栈、本地方法栈3个区域的内存分配和回收都具有确定性,方法结束或者线程结束时,内存自动回收,因此不需要过多考虑回收的问题。而在Java堆空间和方法区中,内存的分配和回收都是动态的(因为一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有程序处于运行期间才能知道会创建哪些对象,才能知道如何分配内存)。因此,内存动态分配和回收主要是对于堆空间和方法区而言的。
自动内存管理,无需开发人员手动参与内存的分配与回收,降低了内存泄漏和内存溢出的风险;并且从内存管理中解放出来而专注于业务本身。但是过度以来与自动内存管理,会弱化开发人员在程序出现内存溢出时定位问题和解决问题的能力。
垃圾回收算法
标记清除算法,标记整理算法,标记复制算法。
标记清除:第一步先对所有对象进行标记是否存活,第二步将不存活的对象进行清理。
标记整理:第一步先对所有对象进行标记是否存活,第二步将存活的对象往空间一端移动,第三步清理掉边界之外的内存。
标记复制:第一步将内存分为大小相等的两块,每次只使用其中的一块,第二步标记使用的那块存活的对象,第三步将存活的对象拷贝到另一边,第四步清除掉之前使用的一边。
数据库MySQL
distinct关键字可以去重
内外连接
内连接:指连接结果仅包含符合连接条件的行,参与连接的两个表都应该符合连接条件。返回两张表都满足条件的部分 外连接:连接结果不仅包含符合连接条件的行同时也包含自身不符合条件的行。包括左外连接、右外连接和全外连接 外连接分为外左连接(left outer join)和外右连接(right outer join) 左连接,取左边的表的全部,右边的表按条件,符合的显示,不符合则显示null 右连接:取右边的表的全部,左边的表按条件,符合的显示,不符合则显示null
索引
索引是一种有序的数据结构,高效获取数据;索引是对数据库表的一列或者多列的值进行排序一种结构,使用索引可以快速访问数据表中的特定信息。
可以快速检索,减少I/O次数;根据索引分组和排序,可以加快分组和排序
索引本身也是表,因此也会占用空间,索引表占用的空间是数据表的1.5倍,索引表的创建和维护也需要时间成本,且随着数据量的增大而增大;构建索引会降低数据表的修改操作(删除、添加、修改)的效率,因为在修改数据表的同时还需要修改索引。
创建索引时需要对表加锁,因此实际操作中需要在业务空闲期间进行。
MySQL支持多存储引擎,而各种存储引擎对索引的支持也不相同,因此MySQL数据库支持多种索引类型,如BTree索引、B+Tree索引、哈希索引、全文索引。
-
聚集索引(主键索引):在数据库里面,所有行数都会按照主键索引进行排序。
-
非聚集索引:就是给普通字段加上索引。
-
联合索引:就是好几个字段组成的索引,称为联合索引
B树和B+树的区别
-
B树,每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为nul,叶子结点不包含任何关键字信息。
B树其实最开始源于的是二叉树,二叉树是只有左右孩子的树,当数据量越大的时候,二叉树的节点越多,那么当从根节点搜索的时候,影响查询效率。所以如果这些节点存储在外存储器中的话,每访问一个节点,相当于进行了一次I/O操作。
-
B+树,所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接,所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。(而B 树的非终节点也包含需要查找的有效信息)
B+数一个节点可以存放多个数据,查找一个节点的时候可以有多个元素,大大提升查找效率,这就是为什么数据库索引用的就是B+树,因为索引很大,不可能都放在内存中,所以通常是以索引文件的形式放在磁盘上,所以当查找数据的时候就会有磁盘I/O的消耗,而B+树正可以解决这种问题,减少与磁盘的交互,因为进行一次I/O操作可以得到很多数据,增大查找数据的命中率。
B树和B+树的区别?
B树和B+树最主要的区别主要有两点:
-
B树中的内部节点和叶子节点均存放键和值,而B+树的内部节点只有键没有值,叶子节点存放所有的键和值。
-
B+树的叶子节点是通过相连在一起的,方便顺序检索。
这就可以很明显的看出B+树的优势:
-
单个节点可以存储更多的数据,减少I/O的次数。
-
查找性能更稳定,因为都是要查找到叶子结点。
-
叶子结点形成了有序链表,便于查询。
什么是聚簇索引,什么是非聚簇索引?
聚簇索引和非聚簇索引最主要的区别是数据和索引是否分开存储。
-
聚簇索引:将数据和索引放到一起存储,索引结构的叶子节点保留了数据行。
-
非聚簇索引:将数据进和索引分开存储,索引叶子节点存储的是指向数据行的地址。
在InnoDB存储引擎中,默认的索引为B+树索引,利用主键创建的索引为主索引,也是聚簇索引,在主索引之上创建的索引为辅助索引,也是非聚簇索引。为什么说辅助索引是在主索引之上创建的呢,因为辅助索引中的叶子节点存储的是主键。
在MyISAM存储引擎中,默认的索引也是B+树索引,但主索引和辅助索引都是非聚簇索引,也就是说索引结构的叶子节点存储的都是一个指向数据行的地址。并且使用辅助索引检索无需访问主键的索引。
索引的种类有哪些?
1.PRIMARY KEY(主键索引)
mysql > ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
2.UNIQUE(唯一索引)
mysql > ALTER TABLE `table_name` ADD UNIQUE (`column` )
3.INDEX(普通索引)
mysql > ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
4.FULLTEXT(全文索引)
mysql > ALTER TABLE `table_name` ADD FULLTEXT ( `column` )
5.多列索引(组合索引)
mysql > ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
删除索引
删除索引的语法: DROP INDEX [ indexName ] ON table;
为什么使用数据索引能提高效率
1、数据索引的存储是有序的,在有序的情况下,通过索引查询一个数据是无需遍历索引记录的2、极端情况下,数据索引的查询效率为二分法查询效率,趋近于 log2(N)
目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构。
建立了索引的数据,就是通过事先排好序,从而在查找时可以应用二分查找来提高查询效率。这也解释了为什么索引应当尽可能的建立在主键这样的字段上,因为主键必须是唯一的,根据这样的字段生成的二叉查找树的效率无疑是最高的。
事务的特性(ACID)
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
Jdbc中使用事务
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句
JDBC程序控制事务
Connection.setAutoCommit(false); //设置自动提交
Connection.rollback(); //程序回滚
Connection.commit(); //提交事务
设置事务回滚点
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
注意:事务回滚后一定要将事务提交
MySQL 的隔离级别有哪些?
未提交读 READ UNCOMMITTED
在该级别事务中的修改即使没有被提交,对其他事务也是可见的。事务可以读取其他事务修改完但未提交的数据,这种问题称为脏读。这个级别还会导致不可重复读和幻读,性能没有比其他级别好很多,很少使用。
提交读 READ COMMITTED
多数数据库系统默认的隔离级别。提交读满足了隔离性的简单定义:一个事务开始时只能"看见"已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前的任何修改对其他事务都是不可见的。也叫不可重复读,因为两次执行同样的查询可能会得到不同结果。
可重复读 REPEATABLE READ(MySQL默认的隔离级别)
可重复读解决了不可重复读的问题,保证了在同一个事务中多次读取同样的记录结果一致。但还是无法解决幻读,所谓幻读指的是当某个事务在读取某个范围内的记录时,会产生幻行。InnoDB 存储引擎通过多版本并发控制MVCC 解决幻读的问题。
可串行化 SERIALIZABLE
最高的隔离级别,通过强制事务串行执行,避免幻读。可串行化会在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别,只有非常需要确保数据一致性且可以接受没有并发的情况下才考虑该级别。
多表联查效率问题
尽量用嵌套查找,不用多表联查
J2EE常用标准:JDBC, JSP, Servlet, XML
什么是XML
XML是扩展标记语言,能够用一系列简单的标记描述数据
HTTP无状态
无状态是指协议对于 事务处理没有记忆能力。用session和cookie解决
AJAX、JSON、JS
什么是ajax?ajax作用是什么?
AJAX = 异步 JavaScript 和 XML。 AJAX 是一种用于创建快速动态网页的技术。 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.
原生js ajax请求有几个步骤?分别是什么
//创建 XMLHttpRequest 对象 var ajax = new XMLHttpRequest(); //规定请求的类型、URL 以及是否异步处理请求。 ajax.open('GET',url,true); //发送信息至服务器时内容编码类型 ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //发送请求 ajax.send(null); //接受服务器响应数据 ajax.onreadystatechange = function () { if (obj.readyState == 4 && (obj.status == 200 || obj.status == 304)) { } };
Spring MVC怎么和AJAX相互调用的?
通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :
(1)加入Jackson.jar
(2)在配置文件中配置json的映射
(3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
ajax几种请求方式?他们的优缺点?
常用的post,get,delete put
###代码上的区别 1:get通过url传递参数 2:post设置请求头 规定请求数据类型 ###使用上的区别 1:post比get安全 (因为post参数在请求体中。get参数在url上面) 2:get传输速度比post快 根据传参决定的。 (post通过请求体传参,后台通过数据流接收。速度稍微慢一些。而get通过url传参可以直接获取) 3:post传输文件大理论没有限制 get传输文件小大概7-8k ie4k左右 4:get获取数据 post上传数据 (上传的数据比较多 而且上传数据都是重要数据。所以不论在安全性还是数据量级 post是最好的选择)
介绍一下XMLHttpRequest对象的常用方法和属性
open(“method”,”URL”) 建立对服务器的调用,第一个参数是HTTP请求 方式可以为GET,POST或任何服务器所支持的您想调用的方式。第二个参数是请求页面的URL。send()方法,发送具体请求abort()方法,停止当前请求readyState属性 请求的状态 有5个可取值0=未初始化 ,1=正在加载2=以加载,3=交互中,4=完成responseText 属性 服务器的响应,表示为一个串reponseXML 属性 服务器的响应,表示为XMLstatus 服务器的HTTP状态码,200对应ok 400对应not found
什么是JSON?
JSON是一种轻量级的数据交换格式。
Mybatis
{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;Mybatis在处理时,就是把{}替换成变量的值。使用#{}可以有效的防止SQL注入,提高系统安全性。
通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
SpringAOP
IoC 和 AOP
IoC
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦,
好处是可维护性比较好,非常容易进行单元测试
原始情况是对象和对象之间有依赖关系,这样代码维护起来很不方便
大致步骤为:
-
开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 <bean> 标签、在 Java 类上使用 @Component 注解等。
-
Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
-
当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用CgLib ,这时候Spring AOP会使用 CgLib 生成一个被代理对象的子类来作为代理
如果被代理的类是实现接口的,生成一个PROXY子类,实现这个接口
没有实现这个接口,动态生成一个子类,继承这个父类
JDK动态代理
字节码增强
Spring 中的单例 bean 的线程安全问题了解吗?
单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
解决方法:在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
Spring 框架中用到了哪些设计模式?
-
工厂设计模式 : Spring使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 -
代理模式 : Spring AOP 功能的实现。
-
单例模式 : Spring 中的 Bean 默认都是单例的。
-
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
引入自己实现的beanfactory
BeanFactory
//1.定义一个properties对象
private staitc Properties props= new Properties();
//2.使用静态代码块给对象赋值
static{ try{ InputStream in=BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");//正确 //InputStream in = new FileInputStream("src/bean.properties"); //绝对不能用,WEB工程一旦发布,就没有src目录了!!! props.load(in); } catch(Exception e) { throw new ExceprionInInitializerError(e); } }
//高级方法:
private static ResourceBundle bundle = ResourceBundle.getBundle("bean");
//3.定义一个容器,用于存放我们要使用的对象
private static Map<String,Object> beans = new HashMap<String,Object>();
//使用静态代码块初始化Map
static{ try{ //1.读取配置文件中所有的配置:key的部分 Enumeration<String> keys = bundle.getKeys(); //2.遍历keys while(keys.hashMoreElements()) { //3.取出一个Key String key=keys.nextElement(); //4.根据key获取beanPath String beanPath=bundle.getString(key); //5.根据beanPath反射创建类对象 Object value = Class.forName(beanPath).newInstance(); //6.把key和value存入map中 beans.put(key,value); } }catch(Exception e) { throw new ExceptionInInitializerError("创建容器失败") } }
//4.取对象:根据bean的唯一标识获取对象
public static Object getBean(String beanName) { return beans.get(beanName); }
没有线程安全问题
Spring bean生命周期
SpringMVC+Spring+Mybatis集成
SpringMVC是如何扫描配置文件的
什么是Spring MVC?
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型-视图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
MVC是什么?
mvc是一种设计模式(设计模式就是日常开发中编写代码的一种好的方法和经验的总结)。模型(model)-视图(view)-控制器(controller),三层架构的设计模式。用于实现前端页面的展现与后端业务数据处理的分离。
mvc设计模式的好处
1.分层设计,实现了业务系统各个组件之间的解耦,有利于业务系统的可扩展性,可维护性。
2.有利于系统的并行开发,提升开发效率。
SpringMVC 工作原理(流程)
-
客户端(浏览器)发送请求,直接请求到
DispatcherServlet
。 -
DispatcherServlet
根据请求信息调用HandlerMapping
,解析请求对应的Handler
。 -
解析到对应的
Handler
(也就是我们平常说的Controller
控制器)后,开始由HandlerAdapter
适配器处理。 -
HandlerAdapter
会根据Handler
来调用真正的处理器开处理请求,并处理相应的业务逻辑。 -
处理器处理完业务后,会返回一个
ModelAndView
对象,Model
是返回的数据对象,View
是个逻辑上的View
。 -
ViewResolver
会根据逻辑View
查找实际的View
。 -
DispaterServlet
把返回的Model
传给View
(视图渲染)。 -
把
View
返回给请求者(浏览器)
Spring MVC常用的注解有哪些?
@Component 和 @Bean 的区别是什么?
-
作用对象不同:
@Component
注解作用于类,而@Bean
注解作用于方法、 -
@Component
通常是通过路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我们需要用它的时候还给我。 -
@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,只能通过@Bean
来实现。
Autowire 和 @Resource 的区别
-
@Autowire
和@Resource
都可以用来装配bean,都可以用于字段或setter方法。 -
@Autowire
默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false。 -
@Resource
默认按名称装配,当找不到与名称匹配的 bean 时才按照类型进行装配。名称可以通过 name 属性指定,如果没有指定 name 属性,当注解写在字段上时,默认取字段名,当注解写在 setter 方法上时,默认取属性名进行装配。
@Configuration :配置类注解
@Configuration
表明在一个类里可以声明一个或多个 @Bean
方法,并且可以由 Spring 容器处理,以便在运行时为这些 bean 生成 bean 定义和服务请求,
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
@Component, @Repository, @Service 的区别
@Component
是一个通用的Spring容器管理的单例bean组件。而@Repository
, @Service
, @Controller
就是针对不同的使用场景所采取的特定功能化的注解组件。
因此,当你的一个类被@Component所注解,那么就意味着同样可以用@Repository
, @Service
, @Controller
来替代它,同时这些注解会具备有更多的功能,而且功能各异。
最后,如果你不知道要在项目的业务层采用@Service
还是@Component
注解。那么,@Service
是一个更好的选择。
@Controller
在Spring MVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。
@Controller 用于标记在一个类上,使用它标记的类就是一个Spring MVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式:
-
在Spring MVC 的配置文件中定义MyController 的bean 对象。
-
在Spring MVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller 控制器。
@RequestMapping
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@ResponseBody
作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
@Autowired
,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired用来消除 set ,get方法。
@Autowired扫描过程
扫描当前类中标注@Autowired的属性和方法;
再查找父类中注@Autowired的属性和方法,依次遍历;
@Resource
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
@Resource装配顺序:
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
@Service
相当于在xml配置:
<bean id="courseDAO" class="com.hzhi.course.dao.CourseDAOImpl" scope="prototype"> </bean>
@Configration
@Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源
Spring MVC的控制器是不是单例模式
答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段
Spring MVC怎么和AJAX相互调用的?
通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :
(1)加入Jackson.jar
(2)在配置文件中配置json的映射
(3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
怎样在方法里面得到Request,或者Session?
答:直接在方法的形参中声明request,Spring MVC就自动把request对象传入。
Spring是怎么扫描配置文件的
组件扫描(Component-Scan)
-
通过配置组件扫描,可以使得spring自动扫描package,而不必在spring的配置文件中逐一声明各个
<bean>
-
在配置组件扫描时,指定的包是“根包”,即例如指定了
cn.tedu.spring
,spring不只会扫描这个包,还会扫描它的各个层级子包,例如:cn.tedu.spring.dao
-
直接在spring的配置文件中开启组件扫描即可
<context:component-scan base-package="cn.tedu.spring"></context:component-scan>
-
注意: 仅仅开启组件扫描
spring
是不会自动管理bean的,而是自动的扫描package
,要想自动管理bean,那么还需要配置注解
注解
-
在类的声明上方添加
@Component
注解,可以是的spring知道这个类是一个组件,需要进行管理,所以如过某个类需要被Spring管理,应该将这个类放在被扫描的包中,并且添加注解 -
由Spring扫描到的组件(由
@Component
注解标记的类),会由Spring自动设置Bean Id
,值为将类名首字母小写的名称,例如组件类的名称是UserDao
,则配置的Bean的id是userDao
,如果需要自定义Bean,那么可以直接在注解中设置,比如@Component("id")
前提
-
一定要是在开启组件扫描的包下使用注解,否则将不会扫描到配置的注解
常用注解
-
可以混用,暂时这几个注解没有差异,完全功能相同,但是我们还是要根据规则使用
-
@Component
: 通用注解 -
@Service
: 用于对业务逻辑类的注解(Service层) -
@Controller
: 用于对控制器类的注解 -
@Repository
:用于对持久层处理类的注解(Dao层
设计模式(单例模式,工厂模式,代理模式)
单例模式
这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。实现方式:构造方法私有化
哪里用到
Spring MVC的控制器是不是单例模式
答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段
饿汉
public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
-
因为类只加载一次,并且它在类加载的时候,它已经实例化了自己,虽然说类加载的慢一点,但是他是线程安全的。
-
这种模式适合于要求对象初始化非常快,而且占用内存非常小的时候。
懒汉
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if(singleton == null) { return new Singleton(); } return singleton; } }
这种做法线程是不安全的,从宏观角度看,两个线程同时进入判断条件里,这样可能会造成创建出多个对象,占用大量的内存,所以我们还要改进代码,就是要给它加一个线程锁,并发执行。
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if(singleton == null) { synchronized (singleton) { if(singleton == null) { return new Singleton(); } return singleton; } } return singleton; } }
工厂模式
beanfactory
我理解的工厂模式,就是父类声明一个接口,子类实现这个接口创建具体的对象
简单工厂模式(静态)
简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码逻辑将会非常复杂。
工厂方法模式
此模式中,通过定义一个抽象的核心工厂类,并定义创建产品对象的接口,创建具体产品实例的工作延迟到其工厂子类去完成。这样做的好处是核心类只关注工厂类的接口定义,而具体的产品实例交给具体的工厂子类去创建。当系统需要新增一个产品是,无需修改现有系统代码,只需要添加一个具体产品类和其对应的工厂子类,使系统的扩展性变得很好,符合面向对象编程的开闭原则。
抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
此模式是对工厂方法模式的进一步扩展。在工厂方法模式中,一个具体的工厂负责生产一类具体的产品,即一对一的关系,但是,如果需要一个具体的工厂生产多种产品对象,那么就需要用到抽象工厂模式了。
代理模式
Spring AOP用到
如果被代理的类是实现接口的,生成一个PROXY子类,实现这个接口
没有实现这个接口,动态生成一个子类,继承这个父类
JDK动态代理
字节码增强
Redis
什么是Redis?简述它的优缺点?
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。
比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。
另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
Redis支持哪几种数据类型?
String、List、Set、Sorted Set、hash
(一)String
这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。
(二)hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
(三)list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适---取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。
(四)set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
(五)sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。
redis可以存储对象吗?
`redis本身没有直接储存对象的方法,但是可以将对象转换成字符串、序列化为二进制、转换对象为map的方式存储对象
2、redis是基于内存实现数据的存储,而SQL是基于磁盘实现数据的存储,因此redis访问是非常快速的。
redis如何解决高并发问题?
redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力。
如果redis宕机了,或者链接不上,怎么办?
①配置主从复制,配置哨兵模式(相当于古代门派的长老级别可以选择掌门人的权利),一旦发现主机宕机,让下一个从机当做主机。
②集群,加机器
②如果最坏的情况,只能关闭Redis连接,去往数据库连接。但由于数据量大,这样SQL数据库也会宕掉的。
缓存穿透
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
缓存雪崩
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
(一)给缓存的失效时间,加上一个随机值,避免集体失效。
(二)使用互斥锁,但是该方案吞吐量明显下降了。
(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点
-
I 从缓存A读数据库,有则直接返回
-
II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
-
III 更新线程同时更新缓存A和缓存B。
微服务
微服务是一种架构模式或者是一种架构风格,它提倡将单一应用程序划分为一组小的服务,每个服务运行在自己独立的进程中,服务之间互相协调、互相配合为用户提供最终价值。
Linux
ps 命令显示运行的进程,还会显示进程的一些信息如pid, cpu和内存使用情况等:
-A :所有的进程均显示出来 -a :不与terminal有关的所有进程 -u :有效用户的相关进程 -x :一般与a参数一起使用,可列出较完整的信息 -l :较长,较详细地将PID的信息列出
top 命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器:
top [参数] -b 批处理 -c 显示完整的治命令 -I 忽略失效过程 -s 保密模式 -S 累积模式 -i<时间> 设置间隔时间 -u<用户名> 指定用户名 -p<进程号> 指定进程 -n<次数> 循环显示的次数
kill 命令用于终止进程,参数:
kill -signal PID 1:SIGHUP,启动被终止的进程 2:SIGINT,相当于输入ctrl+c,中断一个程序的进行 9:SIGKILL,强制中断一个进程的进行 15:SIGTERM,以正常的结束进程方式来终止进程 17:SIGSTOP,相当于输入ctrl+z,暂停一个进程的进行
Tomcat工作模式?
Tomcat是一个JSP/Servlet容器。其作为Servlet容器,有三种工作模式:独立的Servlet容器、进程内的Servlet容器和进程外的Servlet容器。
进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类:
Tomcat作为应用程序服务器:请求来自于前端的web服务器,这可能是Apache, IIS, Nginx等;
Tomcat作为独立服务器:请求来自于web浏览器;
跨域问题
1.加@CrossOrigin注解(有点LOW)
2.写一个配置文件
@Configuration
cors配置对象CorsConfiguration
3.httpClient
计算机网络
网络地址
A类地址的第一组数字为1~126。注意,数字0和 127不作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机,不能传送。
范围:1.0.0.1到126.255.255.254
B类地址的第一组数字为128~191。
范围:128.0.0.1到191.255.255.254
C类地址的第一组数字为192~223。
范围:192.0.0.1到223.255.255.254
TCP三次握手和四次挥手
三次握手
一、发送端标志位SYN=1,seq=x
二、接收端发送SYN=1,ACK,seq=y, ack=x+1
三、发送端发送ACK,seq=x+1,ack=y+1;
四次挥手
一、发送端标志位FIN=1,seq=u
二、接收端发送ACK,seq=v, ack=u+1
三、接收端发送FIN=1,ACK=1,seq=w,ack=u+1
四、发送端发送ACK=1,seq=u+1,ack=w+1
为什么TCP连接的时候是3次?2次不可以吗?
因为需要考虑连接时丢包的问题,如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。
为什么连接3次,关闭四次
因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
GET和POST区别
说道GET和POST,就不得不提HTTP协议,因为浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。
HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。
1. GET在浏览器回退时是无害的,而POST会再次提交请求。
2. GET产生的URL地址可以被Bookmark,而POST不可以。
3. GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4. GET请求只能进行url编码,而POST支持多种编码方式。
5. GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
6. get方式提交数据的大小(一般来说1024字节),http协议并没有硬性限制,而是与浏览器、服务器、操作系统有关,而POST理论上来说没有大小限制,http协议规范也没有进行大小限制,但实际上post所能传递的数据量根据取决于服务器的设置和内存大小。
7. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
8. GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
9. GET参数通过URL传递,POST放在Request body中。
什么是对称加密与非对称加密
对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;
而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢
什么是cookie
cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。
什么是session
session是依赖Cookie实现的。session是服务器端对象
session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。
cookie与session区别
存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。
Servlet接口中有哪些方法及Servlet生命周期探秘
在Java Web程序中,Servlet主要负责接收用户请求HttpServletRequest,在doGet(),doPost()中做相应的处理,并将回应HttpServletResponse反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。
Servlet接口定义了5个方法,其中前三个方法与Servlet生命周期相关:
void init(ServletConfig config) throws ServletExceptionvoid service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOExceptionvoid destory()java.lang.String getServletInfo()ServletConfig getServletConfig()
生命周期:
Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;
请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;
当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。
init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。
如果客户端禁止 cookie 能实现 session 还能用吗?
Cookie 与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。
但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。
最后
以上就是稳重花卷为你收集整理的校招时总结的一些Java知识的全部内容,希望文章能够帮你解决校招时总结的一些Java知识所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复