Java基础
Synchronized和Lock
Synchronized有三种用法,分别是
-
Synchronized修饰普通实例方法,锁是当前实例对象
-
Synchronized修饰静态方法,锁是当前类的class对象
-
Synchronized修饰代码块,锁是括号里的对象
Lock锁的使用
Lock是jdk1.5引入的一个接口,他的主要实现类有
- ReenTrantLock():可重入锁,最常用的一种实现类
- ReentrantReadWriteLock.ReadLock():读锁
- ReentrantReadWriteLock.WriteLock():写锁
Synchronized和Lock的区别
- Synchronized是Java关键字,Lock是Java的一个接口
- Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁(tryLock())
- Synchronized会自动释放锁,Lock必须通过unlock()去手动释放锁
- 锁的中断机制不同,Synchronized修饰的线程,其他等待资源的线程会一直等待,Lock锁的线程,其他线程可能不会一直等待
- Synchronized是重入锁,非公平锁,Lock也是可重入锁,默认是非公平锁,但是可以通过在new对象的时候,参数列表指定false,让它成为公平锁
- Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
volatile关键字
volatile关键字,是一个类型修饰符,被volatile修饰的变量会禁止指令重排
-
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
-
禁止进行指令重排序。(实现有序性)
-
volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
List集合怎么去重
-
利用set去重
Set set2 = new HashSet(); List newList3 = new ArrayList(); set2.addAll(list); newList3.addAll(set2); System.out.println("set和list转换去重:" + newList3);
-
遍历后判断,再添加到另一个集合中
List newList2 = new ArrayList(); for (Integer integer : list) { if(!newList2.contains(integer)){ newList2.add(integer); } }
-
双重for循环去重
for (int i = 0; i < list.size(); i++) { for (int j = 0; j < list.size();j++ ) { if (i != j && list.get(i) == list.get(j)) list.remove(j); } }
讲一下序列化
序列化就是将Java对象转化成字节序列的过程,通过字节流ObjectOutputStream
反序列化是将字节序列转化为Java对象的过程,通过字节流ObjectInputStream
序列化的话有个前提,就是被序列化的Java对象需要先实现Serializable接口,不然的话就会报异常(NoSerializableException)。
Arraylist为什么线程不安全
因为ArrayList里面的方法没有进行并发处理,没有使用synchronized进行修饰,这样的话就会导致线程不安全,比如说往Arraylist里面添加很多数据的时候,在多线程环境下就会造成数据丢失的现象,这是因为Arraylist的add方法在将数据添加到集合中的时候他是有步骤的,并不是原子操作,扩容的话他也是通过将一个当前数组元素复制到另外一个容量更大的数组中去,这样的话,就一定会造成数据的丢失。
线程池
创建线程池是通过 Executors工厂类来创建的,可以创建四种线程池
- 定长线程池(FixThreadPool)
使用无界队列LinkedBlockingQuene作为线程池的工作队列
该线程池中的线程数量始终不变,当有新的任务提交时,线程池中若有空闲线程时,会立即执行,如果没有空闲线程,则会将这个任务暂存到一个任务队列中,等到线程池中有空闲线程时,便会处理任务队列中的线程,适用于负载比较重的的服务器 - 单线程池(SingleThreadExecutor)
使用无界队列LinkedBlockingQuene作为线程池的工作队列
只会创建一个线程执行任务,当有多个任务被提交到该线程池中时,会被暂存在一个任务队列中,按先进先出的顺序执行。适用于需要保证执行顺序的任务,并且在任意时间点没有多线程活动的场景 - 可缓存线程池(CachedThreadPool)
使用没有容量的SynchronousQueue作为线程池的工作队列
线程池的线程数量不确定,会根据任务数量动态调整线程的数量。适用于执行很多的短期异步任务的小程序,或负载较轻的服务器 - 可缓存周期定长线程池(ScheduledThreadPool)
使用DelayQueue作为任务队列,适用于给定延迟之后的运行任务或者需要周期执行的任务
线程池都有哪几种工作队列?(重要)
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
spring的隔离级别和传播行为
spring的事务隔离级别有五个,分别是
-
default: 表示默认使用数据库的事务隔离级别
-
read-unCommit(读未提交):表示其他事务可以看到某个事务修改了但是没有提交的数据,可能造成脏读、幻读、不可重复读
-
read-Commit(读提交):Oracle的默认隔离级别。表示其他事务不能看到某个事务未提交的数据,可以防止脏读,但是不能防止幻读和不可重复读
-
Repeatable-read(可重复读):**mysql的默认隔离级别。**表示当事务多次读取同一数据时,保证他的值和事务开始的内容是一样的,可以防止脏读,不可重复读,但是可能会出现幻读。
-
Serializable(序列化):这是最可靠但是花费最昂贵的事务隔离级别。可以防止脏读幻读不可重复读。
spring的传播行为有七个,分别是
- required(必需的):表示如果存在当前事务,就加入,没有就新建一个事务
- Mandatory(强制的):表示如果存在当前事务,就加入,如果没有,就抛出异常
- never(从不):表示以非事务的方式运行,如果存在当前事务,就抛出异常
- not-support(不支持):表示以非事务的方式运行,如果存在当前事务,就将当前事务挂起
- requires-new(需要新建):表示新建一个事务,如果存在当前事务,就将当前事务挂起
- supports(支持):表示支持当前事务,如果不存在当前事务,就以非事务的方式运行
- Nested(嵌套):表示如果存在当前事务,就嵌套在当前事务中运行,如果外层事务失败,会回滚内层事务,但是如果内层事务失败,则不会引起外层事务回滚。如果不存在,就新建一个事务运行
数据库优化
具体场景
-
有1000万条数据要插入到数据库中,有什么思路
首先,如果要将这么多数据插入到数据库中,如果一条一条的插的话,那么这样频繁的开关连接肯定是不行的,如果是这样的话,就可以在一条sql语句中插入多条数据这样可以减少连接次数,然后的话更改了数据的话肯定要提交事务,提交事务的话可以进行分批处理,就是不要每插入一次就提交一下事务,再者就是尽量有序插入,这样的话主要是为了减少以后索引的维护压力 -
数据库中有1000万条数据库,我要查询比如说a字段为10的,和年龄是10或者15的,讲一下怎么查询
首先对于查询的字段可以添加索引,然后的话,使用了索引,就要优化自己的sql语句,不要用到or和in
可以使用union去把查询的结果并起来,这样的话就不会导致索引不可用。然后对于数据库本身的话也是可以考虑的一方面,可以通过更改数据库引擎,来寻找最优的方式。mysql默认引擎是innoDB,Oracle没有引擎的概念,oracle是将数据处理分为两大类,分别是联机事务处理和联机分析处理。
-
一次插入10条数据怎么操作?(Mybatis里面怎么操作)
可以直接通过insert语句插入,每条字段之间通过“,”隔开。在Mybatis中插入的话,可以通过
先把要插入的值放进一个lsit集合中,再使用foreach标签来循环遍历这个list集合插入。
索引为什么能提高查询效率
索引本质上是一个数据结构,在Mysql中是B+树,B+树是一种平衡树(一颗空树或者说它左右两颗子树的高度查绝对值不会超过1,并且左右两个子树都是平衡树),在不使用索引时,数据库中的记录都是存储在页中
每个数据页组成了一个双向链表,同时每个数据页中的数据又组成了一个单向链表,所以要查询数据的话,就需要遍历这些链表,因为链表的这个数据结构的特性,自然速度快不了。
当使用的索引后,因为索引底层是一颗平衡树,这个平衡树就像一个"目录",我们可以通过"目录",就能快速的定位到我们要查找的对应记录在具体的哪个页上面,然后再继续通过这个”目录“,可以快速的找到这个页上面的具体记录。(具体是通过平衡树的算法==》二分查找法)
数据库怎么查询重复数据
select * from people where id in (
select id from people group by id having count (id)>1)
session执行流程和原理?
- 用户第一次请求服务器时,服务器端会生成一个sessionid
- 服务器端将生成的sessionid返回给客户端,通过set-cookie
- 客户端收到sessionid会将它保存在cookie中,当客户端再次访问服务端时会带上这个sessionid
- 当服务端再次接收到来自客户端的请求时,会先去检查是否存在sessionid,不存在就新建一个sessionid重复1,2的流程,如果存在就去遍历服务端的session文件,找到与这个sessionid相对应的文件,文件中的键值便是sessionid,值为当前用户的一些信息
- 此后的请求都会交换这个 Session ID,进行有状态的会话。
session存储的数据存在哪里?
session.setAtturbute()是通过键值对的方式存储数据的,所以底层是存放在键值对里面的
mybatis的#{}和${}有什么区别?为什么 ${}会存在
mybatis在处理#{}时,会对sql进行预编译,将#{}替换成问号,这样可以有效防止sql注入
mybatis在处理${}时,会直接将我们输入的值进行字符串拼接,所以可能会发生sql注入
但是 $ {}既然会存在,那就肯定会有它的意义,所以说当我们需要sql注入的时候,它就能发挥它的作用了。比如在我们使用mybatis查询数据时使用order by动态参数排序时就应该使用${}。
mybatis关系映射
数据结构–链表和数组、树
数据结构分为线性结构和非线性结构
- 线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
- 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构,顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
- 常见线性结构有:数组、队列、链表和栈
- 非线性结构
非线性结构包括:二维数组、多维数组、广义表、树结构、图结构
链表是以节点的形式来存储的,每个节点包含date域,next域:指向下一个节点。链表分为带头节点的链表和没有头节点的链表
finalized()方法
protected void finalize() throws Throwable {
try {
stream.close();
} catch (IOException e) {
} finally {
stream = null;
super.finalize();
}
}
GC负责调用finalize()方法。这个方法不需要程序员手动调用。JVM的垃圾回收期负责调用这个方法。finalize()只需要重写,重写完将来自动有人来调用。
finalize()方法的执行时期:当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法。
前端面试题
如何使用js和jquery设置div的宽和高
JS:
<script type="text/javascript">
var demo = document.getElementById('demo');
demo.style.width = '200px';
demo.style.height = '200px';
</script>
jquery:
$("#div1").css("height","320px");
$("#div1").css("width","480px");
如何隐藏div
通过css:display-none
将div的宽度和高度设置为0
opacity来设置不透明级别,设置为透明的
js有哪些数据类型
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Object类型
springboot和springmvc的区别
在了解两个东西或者是两个技术的区别时,首先得知道他们是什么,对于springmvc来说,根据官方文档的解释,springmvc是一个基于servlet API搭建的原始web框架,属于springweb模块的一部分,而springboot是spring发展到一定程度的产物。它可以说是一个全新的框架,也可以说是一种新的编码规范,springboot的产生,简化了框架的使用,“约定大于配置”,是springboot的宗旨,我们不需要在使用spring众多框架时,去配置大量而繁琐的配置文件了,只需要使用约定好的注解。
对于springboot和springmvc的区别
首先,
- springmvc是基于spring的一个mvc框架,主要为了简化web应用的开发。springboot实现了自动配置,降低了项目搭建的复杂度
- springmvc要自己配置web服务器启动,springboot内置了web服务器,不需要自己配置
- springmvc需要在web.xml中注册前端控制器和编写springmvc配置文件,而springboot不需要任何配置文件
spring的JDBC和Java原生态的JDBC有什么区别
spring的JDBC是对Java原生态JDBC进行了简单的封装,我们在spring中使用jdbc,只需要关注sql语句编写和返回的结果集就好了,像连接的创建和关闭,不用我们自己去管理,spring帮我们完成(通过IOC)
Redis缓存的理解
Redis是一个开源的,基于key-value的数据结构存储系统,支持多种类型的数据结构,比如String、数组、list、set、map。
Redis的缓存是存放在内存中的,可以通过设置一个定时器,定时将缓存中的数据进行持久化
mybatis的执行流程(工作原理)
// 1.加载配置文件 通过InputStream
InputStream is = Resources.getResourceAsStream("mybatis.xml");
// 2.创建sqlSession工厂
SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(is);
// 3.创建sqlSession
SqlSession session = sf.openSession();
// 4.创建mapper接口对象 动态生成mapper接口实现类
DeptMapper mapper = session.getMapper(DeptMapper.class);
// 5.利用mapper对象进行持久化操作
Dept dept = mapper.selectByPrimaryKey("30");
System.out.println(dept);
// 6.关闭资源
session.close();
- 首先通过Resources去加载配置文件,返回一个io流
- 创建sqlSession
- 创建sqlSession
- 通过sqlSession调用mapper接口对象(通过Java反射机制)
- 利用mapper对象进行持久化操作
- 关闭资源
mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式
通过Java的反射机制创建对象,同时使用反射给对象的属性逐一赋值并返回,哪些关联不到映射关系的对象是无法赋值的,所以会返回null。
可以使用resultMap标签逐一定义列名和对象属性名之间的映射关系,第二种就是使用sql列起别名的方式
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,那么B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
虽然Mybatis解析xml文件是按顺序解析的,但是被引用的B标签还是可以被定义在任何地方,Mybatis都能够正确识别
原理是,Mybatis在解析A标签的时候,如果发现引用了B标签,但是B标签还没有解析到,那么这个时候Mybatis会将A标签标记为未解析状态,然后继续往下解析其他内容,当所有标签解析完毕,Mybatis会重新解析那么被标记为未解析状态的标签。
红黑树
红黑树是一种含有红黑节点的自平衡的二叉查找树,他具有以下性质:
- 每个节点要么是红色,要么是黑色
- 根节点是黑色
- 每个叶子节点是黑色
- 每个红色节点的两个子节点一定都是黑色
- 任意一结点到每个叶子结点的路径都包含数量相同的黑节点
对于自平衡,在红黑树进行插入和删除等可能会破坏树的平衡的操作时,需要重新自处理达到平衡状态。它是依靠以下三种方式达到自平衡的
- 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
- 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
- 变色:结点的颜色由红变黑或者由黑变红
CAS了解吗?Java中是怎么实现的
CAS(Compare and swap)比较交换是设计并发算法时用到的一种技术。Java实现可以使用java.util.concurrent.atomic包中的一些原子类来使用CPU中的这些功能。
join方法的作用
join方法的作用是让调用该方法的线程执行完run()方法后,再执行join()方法后面的代码。
即将两个线程合并,实现线程同步。
Java的缺点
Java是解释性语言,由于Java需要将代码编译成字节码文件,然后再通过解释器解释成机器码,然后垃圾回收的过程中又需要消耗额外的CPU时间,运行速度效率极低,不支持底层操作
笔试题
文件复制
// 指定文件要复制的文件
File file = new File("F:\\test1\\output.txt");
// 复制到哪里
File target = new File("F:\\test1\\copy1.txt");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 输入流
fis = new FileInputStream(file);
// 输出流
fos = new FileOutputStream(target);
// 指定一个byte数组,读取一个字节数组长度和写出一个字节数组长度
byte[] bytes = new byte[1024];
// read方法读到末尾会返回-1
int len =0 ;
while ((len = fis.read(bytes))!=-1){
// 写入指定文件中
fos.write(bytes);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fos!=null)fos.close();
if (fis!=null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
单例模式
//懒汉式实现单例模式
public class SingleTon {
private SingleTon(){}
public static SingleTon s;
public static synchronized SingleTon getInstance(){
if (s==null){
s = new SingleTon();
}
return s;
}
}
//饿汉式实现单例模式
public class SingleTon {
private SingleTon(){}
public static SingleTon s = new SingleTon();
public static SingleTon getInstance(){
return s;
}
}
排序算法
冒泡排序
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j]>arr[j+1]){
arr[j] = arr[j]+arr[j+1];
arr[j+1] = arr[j]-arr[j+1];
arr[j] = arr[j]-arr[j+1];
}
}
}
System.out.println(Arrays.toString(arr));
选择排序
for (int i = 0; i < arr.length-1; i++) {
for (int j = i; j < arr.length-1; j++) {
if (arr[i]>arr[j+1]){
arr[i] = arr[i] + arr[j+1];
arr[j+1] = arr[i] - arr[j+1];
arr[i] = arr[i] - arr[j+1];
}
}
}
System.out.println(Arrays.toString(arr));
插入排序
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j]<arr[j-1]){
arr[j] = arr[j]^arr[j-1];
arr[j-1] = arr[j]^arr[j-1];
arr[j] = arr[j]^arr[j-1];
}
}
}
System.out.println(Arrays.toString(arr));
快速排序
二分查找
int[] arr = {10,20,30,40,50,60,70,80};
int target = 30;//目标值
int min = 0;//最小值0
int max = arr.length;//最大值:数组长度
while(min<max){//
int mid = (min + max)/2;//中间值
if (arr[mid] < target){
min = mid;
}
if (arr[mid] > target){
max = mid-1;
}
if (arr[mid] == target){
System.out.println(mid);
break;
}
}
爱数面经
数组链表各有什么区别
-
从逻辑结构上来说,两者都是线性数据结构,不同的是数组是顺序存储结构,链表是链式存储结构
-
在内存中,数组是一块连续的区域,而链表则不要求连续
-
链表是通过指针来连接元素,而数组是把元素按次序依次存储
-
数组因为是连续的内存区域,所以可以通过内存地址直接找到该地址上的元素,查找效率高,但是增删效率低。而链表因为是不连续的内存空间,所以查找元素的话,必须从头节点开始遍历,查找效率低,但是增删效率高
-
数组一旦定义大小,则不能更改,不利于扩展,而链表理论上没有大小,只要内存够大,就可以一直增删
如何将一个单链表不借助别的数据结构,原地实现反转
可以定义三个指针,分别指向当前节点A,下个节点B,下下个节点C。
然后对整个链表进行遍历(条件是当下个节点不为空)
遍历时,如下首先记录下下个节点C,然后节点B的指针断开并指向A。然后移动进入下一组。
A -> B -> C ->D -> E
A <- B 、 C ->D -> E
代码实现
/**
* 使用三个指针原地反转单链表
*/
public static ListNode reverseList(ListNode head){
if(head == null) return null;
ListNode a = head; //当前节点A
ListNode b = head.next; //下个节点B
ListNode temp; //下下个节点C
//头结点的指针先清空
head.next = null;
//有可能链表只有一个节点,所以需要看b是否为null
while(b != null){
temp = b.next; // 记录C节点
b.next = a; // a->b 反向
if(temp == null){
break;
}
a = b; //移动到下一个节点
b = temp;
}
return b == null ? a : b;
}
测试代码
public static void main(String[] args) {
ListNode a1 = new ListNode(5);
ListNode a2 = new ListNode(4);
ListNode a3 = new ListNode(30);
ListNode a4 = new ListNode(78);
ListNode a5 = new ListNode(99);
a1.next = a2;
a2.next = a3;
a3.next = a4;
a4.next = a5;
//反转单链表
ListNode node = reverseList(a1);
//打印输出结果
while (node != null){
System.out.print(node.val);
node = node.next;
System.out.print(node != null ? "->" : "");
}
}
栈和队列的特点,如何用两个栈实现一个队列的顺序输出
栈和队列都属于线性表的范畴,栈是先进后出的,队列是先进先出的
用两个栈实现一个队列的顺序输出,可以先将数据存入到栈一中,比如说顺序是ABCDE,那么再将这个栈1元素出栈添加到栈2中,最后栈2再出栈入队到队列中,这样就可以实现利用两个栈实现一个队列的顺序输出。
红黑树(特点)
红黑树是一种含有红黑结点的自平衡的二叉查找树。
性质:
- 每个结点要么是红色,要么是黑色
- 根节点是黑色
- 每个叶子结点是黑色
- 每个红色结点的两个子节点一定都是黑色
- 任意一结点到每个叶子结点的路径都包含数量相同的黑结点
红黑树再增删元素时会破坏树的平衡,它时通过左旋、右旋、变色这三个操作来达到自平衡的。
HashMap查找的时间复杂度
O(1)
hashmap查找元素的步骤大致为
- 首先根据key的hashcode得到hash值
- 然后再根据hash值,找到对应的hash桶,源码实现是通过
hash &(数组.length()-1)
- 在hash桶上查找元素
hash桶的话,如果形成链表,那么链表的查询效率是非常低的,必须通过遍历链表的形式查找,然后为什么说HashMap查找的时间复杂度是O(1)呢,这其实是一种理想状态。在jdk1.8中,hashmap中链表长度大于8时会将链表变成红黑树来提高查询效率,但是链表长度大于8这个概率也是非常低的,在hashmap文档中有这样一段描述,说hash桶中的元素数量是呈泊松分布的,链表长度大于8的概率是千万分之一,所以我们通常认为hashmap的时间复杂度是O(1)的。这其实是个概率学问题。
什么是死锁?
死锁就是当多个线程在运行过程中,由于竞争资源激烈或者因为彼此通信而造成的一种阻塞现象。比如说A线程持有B线程想要的资源,而B线程又持有A线程想要的资源,这个时候AB线程就会因为无法得到对应的资源而互相等待,造成死锁
死锁的四个必要条件
- 互斥条件:一个资源只能同时被一个线程占用
- 请求与保持条件:一个线程因请求被占用资源而发生阻塞时,对持有的资源保持不放
- 不剥夺条件:一个线程所获得的资源在未使用完之前,不能被另外的线程强行剥夺
- 循环等待条件:当发生死锁时,等待的线程会一直等待下去
怎么避免死锁
- 可以设置加锁顺序
比如说你要获取C锁,必须先获取A和B锁才能获取C锁 - 设置加锁时限
这个就是说当一个线程尝试去获取某个资源的时候,如果获取不到,就主动放弃等待同时释放掉自己所持有的锁,然后随机等待一段时间再尝试去获取锁 - 死锁检测
利用数据结构方面知识,设计一个死锁检测算法。然后还可以通过JDK自带的监控工具去监控jvm中的堆栈信息
避免死锁的经典算法
银行家算法
线程调度的常用方法
sleep():线程睡眠,同时让出cpu资源
join():线程加入,相当与插队,比如说当A线程调用B.join()时,会先执行B线程,再来执行A线程,底层实现是通过wait方法来实现的。
yield():线程释放cpu资源,由运行状态变为就绪状态,以允许具有相同优先级的其他线程获得运行机会,但是也不一定能达到让步效果,因为可能由运行状态变成就绪状态后,再竞争资源的时候,还是它先抢到资源
JVM内存模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-53re2udp-1617108966695)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1603985865694.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ofdpPvly-1617108966697)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1607498260274.png)]
JVM垃圾回收
在Java虚拟机中,本地方法栈、Java栈和程序计数器都是线程私有的,栈帧随着方法的进入做进栈和出栈的动作,实现了内存的自动清理,而堆和方法区是线程共享的,这部分的内存分配是动态的,所以GC垃圾回收主要发生在堆和方法去
堆内存划分:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AzjAue9-1617108966699)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1604200068507.png)]
- 伊甸园区(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
- 幸存者区(Survivor):从伊甸园幸存下来的对象会被暂时挪到这里。
- 老年区(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进老年区时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wR5t09Gf-1617108966701)(C:\Users\TP007\Desktop\Java图片\GC回收流程.png)]
- 新生代GC(Minor GC):对新生区进行垃圾回收
- 老年代GC(Major GC):对老年区进行垃圾回收( 对象进入老年代(大的直接 / 小的晋升)
- 大对象:需要大量连续内存空间的Java对象。
- 长期存活:多次Minor GC后仍然存活的对象。
- 全局GC(Full GC):Full GC 是针对整个新生代、老年代、元空间的全局范围的 GC。
GC(垃圾回收)流程:
- 1、现在有一个新对象产生,那么对象一定需要内存空间,于是现在需要为该对象进行内存空间的申请。
- 2、首先会判断伊甸园区是否有内存空间,如果此时有充足内存空间,则直接将新对象保存到伊甸园区。
- 3、但是如果此时伊甸园区的内存空间不足,那么会自动执行Minor GC操作,将伊甸园区无用的内存空间进行清理。清理之后会继续判断伊甸园区空间是否充足?如果充足,则将新的对象直接在伊甸园区进行内存空间分配。
- 4、如果执行Minor GC之后伊甸园区空间依然不足,那么这个时候会进行存活区判断,如果存活区有剩余空间,则将伊甸园区的部分活跃对象保存在存活区,随后继续判断伊甸园区的内存空间是否充足,如果充足,则进行内存空间分配。
- 5、如果此时存活区也没有内存空间了,则继续判断老年区,如果此时老年区的空间充足,则将存活区中的活跃对象保存到老年区,而后存活区应付出现空余空间,随后伊甸园区将部分活跃对象保存地存活区中,最后在伊甸园区为新对象分配内存空间。
- 6、如果这个时候老年代内存空间也满了,那么这个时候将产生Major GC(Full GC)。然后再将存活区中的活跃对象保存到老年区,从而腾出空间,然后再将伊甸园区的部分活跃对象保存到存活区,最后在伊甸园区为新对象分配内存空间。
- 7、如果老年代执行Full GC之后依然空间依然不足,产生OOM(OutOfMemoryError)异常。
常用设计模式
设计模式分为三类,共23种。分别是
创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构性模式:适配器模式、桥接模式、组合模式、装饰模式、外观模式、亨元模式、代理模式
行为型模式:访问者模式、模板模式、策略模式、状态模式、观察者模式、备忘录模式、中介者模式、迭代器模式、解释器模式、命令模式、责任链模式
数据库三范式
第一范式:所谓第一范式是指数据库的每一列都是不可分割的,即同一列中不能有多个值
第二范式:第二范式是在第一范式的基础上实现的,保证了每条记录的唯一性(实现–>主键)
第三范式:第三范式是在第二范式的基础上实现的,要求数据库表中不能包含其他表中非主键字段
排序算法稳定性,常见排序算法
假定在待排序的记录序列中,存在多个具有相同的关键字记录,若经过排序,这些记录的相对次序保持不变,即在原来的序列中,r[i] = r[j],r[i] 在 r[j] 的前面,而在排序后的序列中,r[i] 仍然在 r[j] 之前,则称这种排序算法是稳定的;否则就是不稳定的。
(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改 变,所以冒泡排序是一种稳定排序算法。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。
(5)归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个元素(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程中,稳定是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
(6)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
(7)希尔排序(shell)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
(8)堆排序
我们知道堆的结构是节点i的孩子为2i和2i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
综上,得出结论: 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
用栈实现队列,用队列实现栈
栈是先进后出的,队列是先进先出的,所以需要使用两个栈来实现队列
linux简单命令(连接远程)
数据库怎么删除重复数据
亚信面经
jvm内存模型(结构)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euEk75qd-1617108966703)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1616590236829.png)]
根据JVM规范,JVM被分为五个区域
多线程创建方式
多线程创建的方式一般来说有四种,分别是
-
继承Thread类
//1.创建一个继承Thread类的子类 public class ThreadDemo extends Thread{ //2.重写run方法 @Override public void run() {//run()表示一个线程的执行逻辑 String name = Thread.currentThread().getName(); for (int i = 0; i < 50; i++) { System.out.println(name+"==="+i); } } //3.创建这个类的对象 ThreadDemo t = new ThreadDemo(); //4.调用start()方法 t.start();
-
实现runnable接口,重写run方法
//1.创建一个类实现runnable接口 class TestRunnable implements Runnable{ //2.重写run方法 @Override public void run() { for (int i = 0; i < 30; i++) { String name = Thread.currentThread().getName(); System.out.println(name+"=="+i); } } //3.创建一个实现runnable接口的类的对象 TestRunnable testRunnable = new TestRunnable(); //4.创建一个Thread类的对象,将实现runnable接口的类的对象丢到Thread类对象的参数列表里 Thread t = new Thread(testRunnable);//代理对象 //5.调用Thread类对象的start方法 t.start(); //也可以直接通过以下方式创建一个线程,直接在Thread类对象的构造参数列表里面new一个Runnable接口对象,实现其run方法 Thread t1 = new Thread(new Runnable() { @Override public void run() { } }); //还可以通过lamada表达式实现 Thread t1 = new Thread(()->{ for (int i = 0; i < 10; i++) { String name = Thread.currentThread().getName(); System.out.println(name+"=="+i); } });
-
实现callable接口,重写call方法
//1.创建一个实现callable接口的类 public class CallableDemo implements Callable<Integer> { //2.实现其call方法(注意:有返回值!) @Override public Integer call() throws Exception {//类似于run() String name = Thread.currentThread().getName(); Integer res = 0;//求奇数和 for (int i = 1; i < 101; i++) { System.out.println(name +"===="); if (i%2!=0) res +=i; } System.out.println(name+"求的奇数和是"+res); return res; } //3.创建一个实现了callable接口的类对象 CallableDemo cd = new CallableDemo(); // FutureTask 可以将Callable类型进行包装 FutureTask<Integer> ft = new FutureTask<>(cd); // FutureTask底层间接继承了Runnable接口,所以可以把FutureTask对象放到Thread参数列表里 //4.新建一个Thread类对象,将FutureTask对象丢到参数列表 Thread t1 = new Thread(ft,"线程1111111111"); //5.调用start方法 t1.start();
-
利用线程池创建线程
/* Executors工厂类:可以通过此类获得一个线程池 ExecutorService:线程池接口 es.execute():线程池执行的逻辑 */
锁升级(Synchronized锁升级)
锁升级也称锁优化,锁有四种状态,分别从低到高,分别是无锁状态、偏向锁状态、轻量锁状态、重量锁状态
在了解锁升级时,需要先了解一下对象在内存中的布局,这样有利于我们学习。首先对象是存放在堆内存中的,而对象大致可以分为三个部分,分别是对象头、实例数据(变量)和填充字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAJlDz2m-1617108966703)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1616584762243.png)]
- 对象头:对象头主要由两部分组成,分别是对象运行时的数据(Mark Word)、类型指针(数组对象还有一个数组长度)
其中Mark word就记录了对象和锁有关的信息,当这个对象被synchronized修饰的时候,围绕锁进行的一系列操作都会在Mark Word中被记录 - 实例数据:就是对象真正存储的数据区,其中包括了这个对象的属性和值
- 填充字节:没有什么实质性的意义。因为对象的大小必须是8字节的整数倍,所以这一部分用来补充字节
其中,Mark Word在不同的锁状态下存储的内容不同。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67HuCpiB-1617108966704)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1616585549632.png)]
偏向锁的升级
当线程1访问代码块并获取锁对象时,会先在java对象头和栈帧中记录偏向的锁的threadID
因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致。
如果一致(还是线程1获取锁对象),则无需使用CAS(比较交换)来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
轻量级锁的升级
一个线程(线程1)在获取轻量级锁时,会进行以下操作
- 先把锁对象的对象头的Mark Work复制一份到线程1的栈帧中创建的用于存储锁记录的空间(DisplacedMarkWord)
- 然后使用CAS把锁对象头中的内容替换为线程1中存储的锁记录
如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。(竞争激烈)
但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
锁升级是一个不可逆的过程,只能升级,不能降级。
volatile关键字
volatile关键字有两个作用
- 内存可见性
- 禁止指令重排序
HashMap(讲烂了,不想写了)
Linux常用命令
TCP连接
传输控制协议/网络协议是指在不同网络间实现信息传输的协议簇。。。。
三次握手和四次挥手
所谓三次握手和四次挥手,其实就是客户端和服务端建立连接和关闭连接。
TCP头部有六个标志位
- **FLAG(紧急指针)**此标志用于将输入数据标识为紧急状态,这样的话进入段不需要等待,可以直接发送并处理
- ACK确认(ACKNOWLEDGEMENT),这个标志用于确认数据包的成功接收
- PUSH:存在推送标志,在数据包到达接收端时,会立即传送给应用程序。这个标志在数据传输开始和结束被非常频繁的使用,用来影响数据在两端的处理方式
- RST(复位标志):这个标志表示连接复位请求,用来复位哪些错误的连接,也被用来拒绝错误和非法的数据包
- SYN(同步标志):这个标志在三次握手建立连接时有效。
- FIN(断开标志):带有该标志的数据包用来结束一个TCP会话,但对应端口仍处于开放状态,准备接收后续数据
三次握手是建立连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCKAt89O-1617108966705)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1616673608453.png)]
第一次握手
:首先客户端向服务端发送一个数据包,数据包中包含了SYN标志位值为1(SYN=1),同时声明自己的消息序列(seq number),进入SYN-SEND状态第二次握手
:服务端收到这个数据包后,发现SYN=1,知道这是一个连接请求,于是将这个序列号保存起来,然后发送ACK表示自己收到了客户端的连接请求,同时发送SYN并声明自己的消息序列(seq number),进入SYN-RCVD状态- 客户端收到服务端发送过来的数据包后,对服务端中的ACK标志进行校验发现和自己之前发送的匹配,同时服务端发送过来的SYN也=1,知道服务端同意这次请求。之后将SYN-SEND状态变为ESTABLISHED状态
第三次握手
:然后客户端再向服务端发送一个包,回复服务端刚才的包,其中包含了ACK标志位和消息序列(seq number)。整明自己有发送信息的能力- 最后服务端收到这个数据包后,通过对这个数据包中的ACK标志位和消息序列(seq number)进行分析,知道客户端收到了自己发送的数据包。这样,一个TCP连接就建立起来了
四次挥手是关闭连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLlMkQzY-1617108966706)(C:\Users\TP007\AppData\Roaming\Typora\typora-user-images\1616673666055.png)]
第一次挥手
:当客户端的数据都传输完后,客户端向服务端发送连接释放报文,其中其中包括FIN标志位FIN=1,序列号seq number(这个序列号的值跟客户端发送的数据大小有关),当客户端发送完这个数据包后就不能发送其他数据了,但是还可以接收数据第二次挥手
:服务端接收到客户端发送过来的释放连接报文后,回复一个确认报文,其中包含ACK标志位(ACK=1),序列号seq number(这个序列号跟服务端发送的数据大小有关),这个时候服务器就处于关闭等待状态,这个状态可能要持续一段时间,因为服务端可能还有数据没有传输完。第三次挥手
:当服务端的数据传输完之后,会向客户端发送一个连接释放报文,其中就包括了FIN标志位和ACK标志位。第四次挥手
:客户端收到服务端的这个连接释放报文后,由其中的FIN标志位知道服务端要释放连接了,于是发送一个确认报文,其中包含ACK标志位和一个序列号。当服务端收到这个确认报文后就会立马释放TCP连接,但是客户端要经过2MSL(最长报文寿命的两倍时长)后才释放TCP连接,所以服务端释放连接会比客户端早一点
为什么TCP连接的时候是3次?2次不可以吗?
不可以,有一句话叫存在即合理,这个三次握手是当初那些开发者先驱者失败无数次总结出来的经验。那么为什么不可以呢,当第二次握手的时候,如果服务端发送的数据包丢失的话,也就是说客户端并没有收到这个数据包,那么这个时候,服务器端已经做好的收发数据的准备,但是客户端呢,并不知道服务端已经准备好了,所以不会去给服务端发送数据,也会忽略服务端发送过来的其他数据。
如果是三次握手的话,即使发生了丢包的话也不会有问题,比如说第三次握手中,客户端发送的确认报文丢失,那么服务端迟迟未收到客户端发送过来的确认报文,那么就会重新发送第二次握手的数据报文。
为什么TCP连接的时候是3次,关闭的时候却是4次?
因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
什么是HTTP,HTTP 与 HTTPS 的区别
HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范
区别 | HTTP | HTTPS |
---|---|---|
协议 | 运行在 TCP 之上,明文传输,客户端与服务器端都无法验证对方的身份 | 身披 SSL( Secure Socket Layer )外壳的 HTTP,运行于 SSL 上,SSL 运行于 TCP 之上, 是添加了加密和认证机制的 HTTP。 |
端口 | 80 | 443 |
资源消耗 | 较少 | 由于加解密处理,会消耗更多的 CPU 和内存资源 |
开销 | 无需证书 | 需要证书,而证书一般需要向认证机构购买 |
加密机制 | 无 | 共享密钥加密和公开密钥加密并用的混合加密机制 |
安全性 | 弱 | 由于加密机制,安全性强 |
常用HTTP状态码
Java对象的组成
泛微面经
Java异常框架
首先Java的异常体系包括Exception和error两个分支,而这两个分支或者说这两个类都继承于同一个父类,叫Throwable。其中Exception表示异常,是我们程序代码的逻辑性或者语法出现问题时出现的报错,其中又分为检查性异常和非检查性异常。
检查性异常代表的是我们在编写程序代码时必须处理的,不处理的话编译不通过,也就是程序报错不能运行
非检查性异常,又叫运行时异常,是指我们在编写代码时可以不对他进行处理,但是运行过程中可能会由于代码逻辑性问题导致出现异常。
而Error是指错误,这个是代表JVM底层出现了问题,直接导致了JVM停止工作,一般在程序处理的范畴之外。
Java异常处理机制一般有捕获和抛出两种方式。
捕获是指通过try…catch来捕获异常,在try代码块中是可能出现异常的代码或者必须处理的异常代码,而catch是对可能出现的异常进行的一些处理操作。
抛出是通过throws/throw关键字将异常往上抛,这个往上抛是将这个方法可能出现的异常抛给调用他的方法去处理,一般的话,如果一直往上抛,就会交给Java虚拟机去处理。
cookie和session是什么
cookie实际上就是一段保存在客户端的文本信息,当客户端向服务端发送第一个请求时,服务端响应时就会通过响应头部将cookie传输给客户端,然后客户端就会将cookie存起来。
session是代表会话,一种记录客户状态的机制,是存在服务器上的。默认情况下,一个浏览器独占一个session对象。
cookie和session是虽然是一一对应的,但是还是有些区别的,比如说
cookie存放在客户端,session存放在服务端
cookie因为存放在客户端,所以不是很安全,别人可以通过分析本地cookie进行cookie欺骗,而session存放在服务端,因此相应比较安全
cookie有大小限制,根据浏览器不同大小也不同,一般来说是不能超过4k。
值类型和引用类型的区别(Java)
首先Java提供了两种不同类型的数据,引用类型和基本数据类型,基本数据类型又称为原生类,总共有八种,分别是整型byte、short、int、long,浮点类型float、double、字符类型char、布尔类型boolean,其他的数据类型都是引用数据类型,比如说数组、类、接口这些都是引用数据类型。
而值类型和引用数据类型的区别的话,首先他们在JVM内存储的区域不一样,值类型存储在栈中,内存会随着栈帧的入栈和出栈自动释放,而引用类型数据存储在堆中,由JVM负责GC。
值类型和引用类型分别在jvm哪个区域
值类型在栈中,引用类型在堆中
String是值类型嘛
不是,String不是基本数据类型,他是引用数据类型,存放在堆中的字符串常量池中。
servlet生命周期
servlet生命周期主要包含以下三个阶段,分别是初始化、处理请求、销毁,而这三个阶段分别对应servlet的三个方法,分别是init(),service(),destroy()。
servlet默认在客户端发送第一次请求时加载,但是我们也可以通过在web.xml中通过load-on-start来排至让让在web容器启动时加载,加载完成后会创建一个servlet实例并调用init()方法,init()方法只会在web容器启动时调用一次。
当收到客户端请求时,服务器会产生一个新的线程去处理,同时执行servlet中的service方法
当web容器关闭时,servlet会调用自身的destroy方法进行销毁。然后后续由JVM负责GC。
事务隔离级别
事务隔离级别的话有四个,分别是
read-uncommit(读未提交):表示其他事务可以看到某个事务修改了但是未提交的数据,可能导致脏读、幻读、不可重复读
read-commit(读提交):Oracle的默认事务隔离级别。表示其他事务只能看到某个事务已经提交的数据,可以避免脏读,但是不能避免幻读和不可重复读
repeatable-read(可重复读):MYSQL的默认隔离级别。表示当事务多次读取同一数据时,保证他的值和事务开始的时候的内容是一样的,可以防止脏读、不可重复读,但是可能出现幻读
Serializable(序列化):这是最可靠但是花费最昂贵的事务隔离级别。可以防止脏读幻读不可重复读。
值类型和引用类型的区别(Java)
首先Java提供了两种不同类型的数据,引用类型和基本数据类型,基本数据类型又称为原生类,总共有八种,分别是整型byte、short、int、long,浮点类型float、double、字符类型char、布尔类型boolean,其他的数据类型都是引用数据类型,比如说数组、类、接口这些都是引用数据类型。
而值类型和引用数据类型的区别的话,首先他们在JVM内存储的区域不一样,值类型存储在栈中,内存会随着栈帧的入栈和出栈自动释放,而引用类型数据存储在堆中,由JVM负责GC。
值类型和引用类型分别在jvm哪个区域
值类型在栈中,引用类型在堆中
String是值类型嘛
不是,String不是基本数据类型,他是引用数据类型,存放在堆中的字符串常量池中。
servlet生命周期
servlet生命周期主要包含以下三个阶段,分别是初始化、处理请求、销毁,而这三个阶段分别对应servlet的三个方法,分别是init(),service(),destroy()。
servlet默认在客户端发送第一次请求时加载,但是我们也可以通过在web.xml中通过load-on-start来排至让让在web容器启动时加载,加载完成后会创建一个servlet实例并调用init()方法,init()方法只会在web容器启动时调用一次。
当收到客户端请求时,服务器会产生一个新的线程去处理,同时执行servlet中的service方法
当web容器关闭时,servlet会调用自身的destroy方法进行销毁。然后后续由JVM负责GC。
事务隔离级别
事务隔离级别的话有四个,分别是
read-uncommit(读未提交):表示其他事务可以看到某个事务修改了但是未提交的数据,可能导致脏读、幻读、不可重复读
read-commit(读提交):Oracle的默认事务隔离级别。表示其他事务只能看到某个事务已经提交的数据,可以避免脏读,但是不能避免幻读和不可重复读
repeatable-read(可重复读):MYSQL的默认隔离级别。表示当事务多次读取同一数据时,保证他的值和事务开始的时候的内容是一样的,可以防止脏读、不可重复读,但是可能出现幻读
Serializable(序列化):这是最可靠但是花费最昂贵的事务隔离级别。可以防止脏读幻读不可重复读。
mysql、Oracle分页
MYSQL分页:使用limit关键字,两个参数1.从哪开始 2.多少条
select 字段 from table limit 页数*页面大小,页面大小
Oracle分页:使用rownum(伪列)关键字
select * from
(select * rownum r from table where rownum <=页数*页面大小)
where r > (页数-1)*页面大小