不定时更新,素材来自网络,都附注有超链接,侵删
联系邮箱:**********
我是2022届双非软件工程应届生,目前在准备秋招,总结了一篇不错的八股文,如果你正好需要可以关注一下,共同学习;超链接如下:
附带我的求职过程:渣本大三求职记
牛客这篇幅有限,全文放语雀文档了
目录
Java基础知识
面向对象
接口
重写与重载
Java数据类型
局部变量与成员变量对于基本数据类型与引用类型的存储
什么是构造方法
类之间几种常见的关系
泛型
IO流
面向字符的流
BIO NIO AIO
适用场景
同步和异步
阻塞与非阻塞
同步阻塞与同步非阻塞
异步阻塞与异步非阻塞
数组初始化后默认值
反射
正则表达式
Java的位运算
JDK、JRE与JVM的关系
Java体系结构包括四个独立但相关的技术
关键字
标识符
权限修饰符
for 与 foreach
abstract 与 implement
switch
final
static
super
String、StringBuffer、StringBuilder
new String 与String
栈与堆的区别
==和equals比较
instanceof运算符
Scanner,BufferedReader,InputStreamReader 简介与对比
函数取值方法
集合
java.util.Collection [I]
List和Map区别
java.util.Map [I]
哪些集合类是线程安全的?
多线程
线程和进程区别
多进程和多线程区别
进程间通信和线程间通信区别
线程状态
守护线程和用户线程
进程状态
创建多线程
序列化
Thread与Runnable
执行流程
线程安全
线程池
异常处理
RuntimeException和非RuntimeException的区别
throw与throws
try catch }finally
死锁
常用方法
throw和throws区别
start与run方法
sleep与wait
volatile
发布与逸出
synchronized使用与原理
Sychronized的自旋锁、偏向锁、轻量级锁、重量级锁
synchronized与Lock的区别:
sychronized和ReentrantLock
CAS与synchronized的使用情景
乐观锁与悲观锁
多线程与事务
框架与后台传输
Spring 事务
AOP IOC
Spring装配Bean的两种方式:
Aspect Oriented Program 面向切面编程
IOC控制反转
applicationContext & BeanFactory区别
Bean 定义
Bean的生命周期
注解
@Bean
@Component
@ComponentScan
@Autowired
@Autowired 与@Resource的区别(详细)
@Configuration
forward与redirect
Servlet (了解)
状态码
项目
验证码
图片上传
拦截器
1.实现HandlerInterceptor接口自定义拦截器
2.重写addInterceptor方法实例化LoginHandlerInterceptor
常用算法
基础概念
时间复杂度
什么是稳定排序
刷题算法
二分
贪心
冒泡改进版
CPU调度算法
JVM
运行时数据区
java堆、栈、堆栈,常量池的区别
垃圾回收
什么是GC
GC收集器有哪些?
判断是否回收
垃圾收集算法
内存分配与回收
类加载机制
双亲委派
数据库
NoSQL四大分类
Redis
数据类型
Redis是单线程
操作案例
MySQL
事务四大特性ACID
范式通俗理解
数据类型优化
执行SQL相关
索引
脏读、不可重复读、幻读
事务隔离级别
SQL注入
mybatis是如何做到防止sql注入的
MySQL连接数
设计模式
设计模式的六大原则
单例模式
1.懒汉式
2.懒汉式(加锁)
3.饿汉式
观察者模式
工厂模式
桥接模式
装饰者模式
计算机网络
TCP与UDP
http与https
三次握手
get请求与post请求
Cookie 与 Session
Linux简单命令
创建进程
查询日志
查看进程
文件操作
ls — List
2.mkdir — Make Directory
3.pwd — Print Working Directory
4.cd — Change Directory
5.rmdir— Remove Directory
6. rm— Remove
7. cp— Copy
8. mv— Move
12.grep
13.find
14.tar
15. gzip
16. unzip
通过关键词查找文件
vim编辑机
yum 命令
yum 语法
yum常用命令
Git
git与svn
工作流程
具体使用
忽略上传
使用码云(gitee)上传
传统软件架构与互联网软件架构
传统行业
互联网行业
能力要求
行业关注点
待总结
————————————————
版权声明:本文为CSDN博主「不会起名字啦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/weixin_50776418/article/details/118722981
Java基础知识
锁的实现原理
面向对象
面向对象基本概念 面向对象通俗举例蛋炒饭与盖饭
概论:面向对象是把一组数据结构和处理他们的方法组成对象;把具有相同行为的对象归纳成类;
通过封装隐藏类的内部细节;通过继承使类得到泛化;通过多态实现基于对象类型的动态分派
吃饭
- 面向过程:买菜,洗菜,做饭,洗手吃饭,洗碗
- 面向对象:点外卖,吃饭,扔外卖盒
两者区别:对比面向过程,面向对象更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),以及如何更好按需分配 面向对象更加易于复用,扩展和维护; 面向过程直接高效
面向过程优点:流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析。
效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。
java是面向对象的,但是不是所有的都是对象,基本数据类型就不是对象,所以才会有封装类的;
封装:明确标识出允许外部使用的所有成员函数和数据项;内部细节对外部调用透明,外部调用无需修改或关心内部实现
1.JavaBean的属性私有,提供get set对外访问,因为属性的赋值或者获取逻辑只能由JavaBean本身决定,而不能由外部糊涂乱改
private String name; public void setName(String name){ this.name = "java 是最好的语言"+name; } //该name有自己命名规则,明显不能由外部直接赋值
2.orm框架:操作系统库,我们不需要关心连接是如何建立的,sql如何执行,只需引入mybatis调方法即可
继承:继承基类方法,并做出自己的改变和/或扩展子类共性的方法或者直接使用父类的属性,而无需自己再定义,只需扩展自己个性化的;继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段; 访问修饰符决定了是否可继承
多态:基于对象所属类的不同,外部相同类型变量对同一方法的调用,实际执行逻辑不同
继承,方法重写,父类引用指向子类对象 .前提条件:必须有子父类关系。
作用:提高了代码的扩充性和可维护性
牛客例题
父类类型 变量名 = new 子类对象 变量名.方法名()
无法直接调用子类特有功能(需要向下转型)
- 多态的转型分为向上转型和向下转型两种
- 向上转型:多态本身就是向上转型过的过程使用格式:父类类型 变量名=new 子类类型();适用场景:当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作。
- 向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型使用格式:子类类型 变量名=(子类类型) 父类类型的变量;适用场景:当要使用子类特有功能时。成员变量:编译看左边,执行看左边; 成员方法:编译看左边,执行看右边。向下转型可以调用子类类型中所有的成员,不过需要注意的是如果父类引用对象指向的是子类对象,那么在向下转型的过程中是安全的,也就是编译是不会出错误。但是如果父类引用对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现我们开始提到的 Java 强制类型转换异常,一般使用 instanceof 运算符来避免出此类错误。
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么 可以有构造函数,但不能实例化
牛客例题 牛客例题2 牛客例题3
接口
牛客例题 牛客例题2 牛客例题3 JAVA基础——接口(全网最详细教程) Java之implements
为什么要用接口:接口被用来描述一种抽象。因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。 接口也被用来实现解耦。 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,接口的静态成员变量要用static final public 来修饰 \接口中的方法都是抽象的,是没有方法体的,可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法。
可以直接把接口理解为*100%的抽象类*,既接口中的方法*必须全部*是抽象方法。(JDK1.8之前可以这样理解)
牛客例题4 类实现多个接口的时候,只需要一个implements,多个接口通过逗号进行隔开,先继承类再实现接口
重写与重载
Java中重载与重写 牛客例题 牛客例题2
Java数据类型
Java基本数据类型 Java 基本数据类型 及 == 与 equals 方法的区别 牛客例题 牛客例题2 牛客例题3 牛客例题4
实线可以直接转,虚线直接的转换可能损失精度
位移运算符
& ( " and " ) | ( " or " ) ^ ( " xor " ) ~ ( " not " )
局部变量与成员变量对于基本数据类型与引用类型的存储
- 对于局部变量来说,不论是基本数据类型还是引用类型,他们都会先在栈中分配一块内存,对于基本类型来说,这块区域包含的是基本类型的内容;而对于引用类型来说,这块区域包含的是指向真正内容的指针,真正的内容被手动的分配在堆上。
- 对于成员变量来说,不论是基本数据类型还是引用类型,他们都会存储在堆内存或者方法区中;成员变量可细分为静态成员变量和普通成员变量,静态成员变量类属于类,类可以直接访问,存储在方法区中;普通成员变量属于类对象,必须声明对象之后,通过对象才能访问,存储在堆中。
基本类型的变量数据和对象的引用都是放在栈里面的,对象本身放在堆里面,显式的String常量放在常量池,String对象放在堆中
什么是构造方法
什么是构造方法? Java构造方法 牛客例题 牛客例题2 牛客例题3 牛客例题4
构造方法的声明: 修饰符 class_name(类名) (参数列表){ 逻辑代码 }
- 构造⽅法的⽅法名和类名⼀致(包括⼤⼩写)
- 构造⽅法没有返回值类型(连void都没有)
- 构造⽅法可以重载
- 构造⽅法不可以⼿动调⽤,只能在创建对象的时,jvm⾃动调⽤
- 构造⽅法在创建对象时只能调⽤⼀次
当⼀个类中,没有定义构造⽅法 系统会⾃动提供⼀个公开的 ⽆参的构造⽅法 当类中已经定义了构 造⽅法,系统不再提供⽆参公开构造,如果需要使⽤⽆参的构造 那么必须⾃⼰定义出来 ⼀般开发如果 定义了有参的构造 都会再定义⼀个⽆参的构造
构造方法不能被 static、final、synchronized、abstract 和 native(类似于 abstract)修饰。构造方法用于初始化一个新对象,所以用 static 修饰没有意义。构造方法不能被子类继承,所以用 final 和 abstract 修饰没有意义。
构造函数的作用是创建一个类的实例。用来创建一个对象,同时可以给属性做初始化。当程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化。
类之间几种常见的关系
“USES-A”关系 依赖关系,A类会用到B类,这种关系具有偶然性,临时性;在代码中的体现为:A类方法中的参数包含了B类 “HAS-A”关系 表示组合。是整体与部分的关系,同时它们的生命周期都是一样的 (接口) “IS-A”关系 表示继承。父类与子类
泛型
泛型概述 牛客例题 牛客例题2
- 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)
- 泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型
泛型使用过程中操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法
使用泛型的好处
1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
所以泛型只是提高了数据传输安全性,并没有改变程序运行的性能
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个 java 类型
- K V (key value) 分别代表 java 键值中的 Key Value
- E (element) 代表 Element
泛型擦除后是作为Object而存在的,而基础数据类型并没有继承自Object,所以编译器不允许将基础类型声明为泛型类型。最近版本的编译器当涉及基础类型作为泛型参数时,编译器会自动进行拆箱和装箱,所以编译器不会报错
IO流
输入输出(IO)是指计算机同任何外部设备之间的数据传递。常见的输入输出设备有文件、键盘、打印机、屏幕等。数据可以按记录(或称数据块)的方式传递,也可以 流的方式传递 。
* IO流: * 流向 * 输入流 读数据 FileReader Reader * 输出流 写数据 FileWriter Writer * 数据类型 * 字节流 * 字节输入流 读数据 InputStream * 字节输出流 写数据 OutputStream * 字符流 * 字符输入流 读数据 Reader * 字符输出流 写数据 Writer
字节流传输需要刷新缓冲区,字符流无需刷新缓冲区,直接转换
- 二进制文件只能使用字节流进行复制
- 文本文件可以用字节流也可以用字符流进行复制
字节流
InputStream |-- FileInputStream (基本文件流) |-- BufferedInputStream |-- DataInputStream
|-- ObjectInputStream
字符流
Reader |-- InputStreamReader (byte->char 桥梁) |-- BufferedReader (常用) Writer |-- OutputStreamWriter (char->byte 桥梁) |-- BufferedWriter |-- PrintWriter (常用)
牛客例题 处理Unicode字符
面向字符的流
牛客例题 牛客例题2 牛客例题3 牛客例题4
BIO NIO AIO
JAVA中BIO、NIO、AIO的分析理解 什么是BIO、NIO和AIO?
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO(或 None Blocking IO)同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非阻塞IO ,异步 IO 的操作基于事件和回调机制。
适用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
NIO 比 BIO 把一些无效的连接挡在了启动线程之前,减少了这部分资源的浪费。因为我们都知道每创建一个线程,就要为这个线程分配一定的内存空间。``AIO 比 NIO 进一步改善是,将一些暂时可能无效的请求挡在了启动线程之前,比如在 NIO 的处理方式中,当一个请求来的话,开启线程进行处理,但这个请求所需要的资源还没有就绪,此时必须等待后端的应用资源,这时线程就被阻塞了。
——————————————————————————————————————————————
以下了解即可,理解性记忆
同步和异步
- 同步——指的是用户进程触发 IO 操作并等待或者轮询的去查看 IO 操作是否就绪。
- 异步——异步是指用户进程触发IO操作以后便开始做自己的事情,而当 IO 操作已经完成的时候会得到 IO 完成的通知(异步的特点就是通知)
区别:O 操作主要分为两个步骤,即发起 IO 请求和实际 IO 操作,同步与异步的区别就在于第二个步骤是否阻塞。若实际 IO 操作阻塞请求进程,即请求进程需要等待或者轮询查看 IO 操作是否就绪,则为同步 IO;若实际 IO 操作并不阻塞请求进程,而是由操作系统来进行实际 IO 操作并将结果返回,则为异步 IO。
阻塞与非阻塞
- 阻塞——所谓阻塞方式就是指,当视图对文件描述符或者网络套接字进行读写时,如果当时没有东西可读,或者暂时不可写,程序就进入等待状态,直到有东西读或者写
- 非阻塞——所谓的非阻塞方式就是指,当视图对文件描述符或者网络套接字进行读写时,如果没有东西可读,或者不可写,读写函数马上返回,无须等待
区别:O 操作主要分为两个步骤,即发起 IO 请求和实际 IO 操作,阻塞与非阻塞的区别就在于第一个步骤是否阻塞。若发起 IO 请求后请求线程一直等待实际 IO 操作完成,则为阻塞 IO;若发起 IO 请求后请求线程返回而不会一直等待,即为非阻塞 IO。
阻塞和非阻塞是针对于进程在访问数据的时候,根据 IO 操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
同步阻塞与同步非阻塞
- 同步并阻塞 IO——服务器实现模式一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程进行处理,如果这个连接不做任何事情,就会造成不必要的线程开销,当然可以通过线程池(Thread-Pool)程机制改善
- 同步非阻塞——同步非阻塞,服务器实现模式一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程处理。用户进程也需要时不时地询问IO操作是否就绪,这就要求用户进程不停的去询问。
异步阻塞与异步非阻塞
- 异步阻塞——应用发起一个 IO 操作以后,不需要等待内核 IO 操作完成,等待内核完成 IO 操作以后会通知应用程序,这其实就是异步和同步的关键区别,同步必须等待或者主动去询问 IO 操作是否完成。那为什么说阻塞呢?因为此时是通过 select 系统调用来完成的,而 select 函数本身的实现方式就是阻塞的,但采用 select 函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select 属于同步操作。因为 select 之后,进程还需要读写数据),从而提高系统的并发性。
- 异步非阻塞——此种方式下,用户进程只需要发起一个IO操作便立即返回,等 IO 操作真正完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据处理就好了,不需要进行实际的 IO 读写操作,因为真正的 IO 操作已经由操作系统内核完成了。
数组初始化后默认值
牛客例题 牛客例题2
牛客例题 数组的复制的效率System.arraycopy>clone>Arrays.copyOf>for循环
反射
反射小白入门 反射总结
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。 在程序运行状态中,对于任意一个类或对象,都能够获取到这个类的所有属性和方法(包括私有属性和方法),这种动态获取信息以及动态调用对象方法的功能就称为反射机制
优点
- 可以在程序运行过程中,操作这些对象;
- 可以解耦,提高程序的可扩展性。
获取Class对象的三种方式
- 【Source源代码阶段】 Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象; 多用于配置文件,将类名定义在配置文件中,通过读取配置文件加载类。
- 【Class类对象阶段】 类名.class:通过类名的属性class获取; 多用于参数的传递
- 【Runtime运行时阶段】对象.getClass():此方法是定义在Objec类中的方法,因此所有的类都会继承此方法。 多用于对象获取字节码的方式
牛客例题
正则表达式
简书 牛客例题
[://] 表示匹配 :// 中的任何一个字符,也就是匹配 : 或 /
[htps] 表示匹配 htps 中的任何一个字符,[htps]+ 表示一次或多次匹配前面的字符或子表达式,所以 [htps]+ 可以匹配 https
Java的位运算
JDK、JRE与JVM的关系
JDK = JRE + 开发工具
JRE = JVM + 类库
JDK
Java Development Kit 是用于开发 Java 应用程序的软件开发工具,包括了 Java 运行时的环境(JRE)、解释器(Java)、编译器(javac)、Java 归档(jar ——一种软件包文件格式)、文档生成器(Javadoc)等工具。
JRE
Java Runtime Enviroment 提供 Java 应用程序执行时所需的环境,由 Java 虚拟机(JVM)、核心类、支持文件组成。
JVM
Java Virtual Machine(Java 虚拟机)有三层含义,分别是:
- JVM规范要求
- 满足 JVM 规范要求的一种具体实现(一种计算机程序)
- 一个 JVM 运行实例,在命令提示符下编写 Java 命令以运行 Java 类时,都会创建一个 JVM 实例。
Java程序的开发过程为:
- 我们利用 JDK (调用 Java API)编写出 Java 源代码,存储于 .java 文件中
- JDK 中的编译器 javac 将 Java 源代码编译成 Java 字节码,存储于 .class 文件中
- JRE 加载、验证、执行 Java 字节码
- JVM 将字节码解析为机器码并映射到 CPU 指令集或 OS 的系统调用。
Java体系结构包括四个独立但相关的技术
- Java程序设计语言
- Java.class文件格式
- Java应用编程接口(API)
- Java虚拟机
我们再在看一下它们四者的关系:
当我们编写并运行一个Java程序时,就同时运用了这四种技术,用Java程序设计语言编写源代码,把它编译成Java.class文件格式,然后再在Java虚拟机中运行class文件。当程序运行的时候,它通过调用class文件实现了Java API的方法来满足程序的Java API调用
牛客例题
关键字
对象的初始化方法:
new初始化 静态工厂 newInstance() 反射Class.forname() clone() 反序列化
其中new初始化和反射用到了构造方法,静态的newInstance()只能调用无参构造器
牛客例题
abstract continue for new switch default if package synchronized do goto private this break double implements protected throw byte else import public throws case enum instanceof return transient catch extends int short try char final interface static void class finally long strictfp volatile const float native super while boolean assert
标识符
牛客例题
- 1.不能数字开头 2.标识符用$,_,字母,数字组成 3.不能用java关键字,保留字(关键字都是小写的)
- 4.不能用true,false,null来定义标识符 5.java大小写敏感 6.没有长度限制
权限修饰符
Java中各类修饰符 牛客例题 牛客例题2
for 与 foreach
foreach适用于只是进行集合或数组遍历,for则在较复杂的循环中效率更高。
foreach不能对数组或集合进行修改(添加删除操作),如果想要修改就要用for循环。 所以相比较下来for循环更为灵活。
for能对集合进行动态删除
public static void main(String[] args) { Set<Integer> set = new HashSet<>(); for (int i = 0; i < 10; i++) { set.add(i); System.out.print(set.remove(i) + "- "); } System.out.print("\n" + set.toString()); }
foreach会报错
abstract 与 implement
- 一个抽象类可以是public、private、protected、default,接口只有public;
- 一个抽象类中的方法可以是public、private、protected、default,接口中的方法只能是public和default
- abstract不能与final并列修饰同一个类;abstract 不能与private、static、final或native并列修饰同一个方法
- 抽象方法不能有方法体,抽象方法不能使用private修饰符,也不宜使用默认修饰符(default)接口 不**可以 实例化 。 通过接口实现类创建对象
Java抽象类、接口能否有构造方法 趣说abstract 简书 abstract牛客例题 牛客例题2 牛客例题3 牛客例题(抽象类)
构造方法作用:对类进行初始化
结论:Java中接口只有常量定义,没有变量声明,不能有构造方法; 抽象类可以有构造方法
原因:
- 一、接口可以理解为“完全抽象类”,只包含常量和抽象方法;接口中的方法默认被 public 、abstract 修饰,不能有方法体,所以接口中不能有构造方法。 一个类实现了一个接口,则必须实现所有方法,否则必须标志abstract (弱的所属关系is-a)
- 二、抽象类可以有构造方法 (强的所属关系is-a)抽象类中可以有抽象方法和普通方法,不能使用new创建它的实例;含有抽象方法的类必须声明为抽象类,抽象类扩展的非抽象子类中,必须实现所有的抽象方法普通方法可以有方法体,构造方法是没有返回值的方法,在new实例化对象时被调用。所以抽象类可以有构造方法。
一个子类只能继承一个父类,但可以实现任意个数的接口,某种程度上接口比抽象类更加灵活
外部抽象类不允许使用static声明,而内部的抽象类可以使用static声明。
使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。
abstract class A{ //static定义的内部类属于外部类 static abstract class B{ public abstract void print(); } } class C extends A.B{ public void print(){ System.out.println("**********"); } } public class TestDemo { public static void main(String[] args) { //向上转型 A.B ab = new C(); ab.print(); } }
switch
byte short int char 枚举 在JDK1.7版本以后,Switch语句也可以接string类型
final
浅谈Java中的final关键字 java中的Static、final、Static final各种用法 牛客例题
可用来修饰类,方法,变量
1.final修饰变量,则等同于常量 2.final修饰方法中的参数,称为最终参数。
3.final修饰类,则类不能被继承 (太监类)
4.final修饰方法,则方法不能被重写。5.final 不能修饰抽象类
6.final修饰的方法可以被重载 但不能被重写 (不能继承)
- 当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类
- 使用final方法的原因有两个:第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。 注:类的private方***隐式地被指定为final方法
- 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变
即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i;java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响
static
深入理解static关键字
static修饰的变量没有发生变化是因为static作用于成员变量只是用来表示保存一份副本,其不会发生变化。怎么理解这个副本呢?其实static修饰的在类加载的时候就加载完成了(初始化),而且只会加载一次也就是说初始化一次,所以不会发生变化!
static是不允许用来修饰局部变量 静态变量只能在类主体中定义,不能在方法中定义
- 静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中JVM只为静态变量分配一次内存空间。
- 实例变量: 每次创建对象都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
牛客例题 静态方法中不能调用对象的变量,因为静态方法在类加载时就初始化,对象变量需要在新建对象后才能使用
牛客例题2 静态变量和静态块的初始化顺序是靠他们俩的位置决定的
牛客例题3 static成员函数既可以通过类名直接调用,也可以通过对象名进行调用
牛客例题4 静态变量只能在类主体中定义,不能在方法中定义
牛客例题5 被动引用不会出发子类初始化 1.子类引用父类的静态字段,只会触发子类的加载、父类的初始化,不会导致子类初始化 2.通过数组定义来引用类,不会触发此类的初始化 3.常量在编译阶段会进行常量优化,将常量存入调用类的常量池中, 本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
牛客例题6 静态变量会默认赋初值,局部变量和final声明的变量必须手动赋初值
修饰符大汇总
super
super的作用 牛客例题
- 调用父类被子类重写的方法;
- 调用父类被子类重定义的字段(被隐藏的成员变量);
- 调用父类的构造方法;
如果子类没有重写父类的方法,调用父类的方法用不用super关键字结果都一样。 如果子类重写父类的方法,调用父类的方法必须用super关键字
super关键字详解
this与super
牛客例题
- this是引用。this也保存内存地址,this也指向任何对象,不能调用static静态成员变量
- super 不是引用。super也不保存内存地址,super也不指向任何对象。super 只是代表当前对象内部的那一块父类型的特征。
- this的作用其中一个就是在一个构造方法中调用另一个构造方法,格式为this(参数);
super可以访问父类中public、default、protected修饰的成员变量,实例方法,构造方法格式如下 super.属性名 【访问父类的属性】 super.方法名(实参) 【访问父类的方法】 super(实参) 【调用父类的构造方法】
super()在无参构造的使用
为什么要有无参构造
在子类的构造方法中编译器会自动在子类构造函数的第一句加上 super(); 来调用父类的无参构造器;此时可以省略不写。如果想写上的话必须在子类构造函数的第一句,可以通过super来调用父类其他重载的构造方法,只要相应的把参数传过去就好 牛客例题
String、StringBuffer、StringBuilder
String、StringBuffer与StringBuilder之间区别 牛客例题 牛客例题2
- String由final修饰,是不可变的,每次操作都会产生新的String对象
- StringBuffer和StringBuilder都是在原对象上操作的,可改变 (常量池可追加字符串)
- StringBuffer线程安全,StringBuilder线程不安全;StringBuffer方法都是synchronized修饰
性能:StringBuilder > StringBuffer > String
场景:经常需要改变字符串内容时使用后面两个;单线程环境下优先使用StringBuilder,多线程使用共享变量时使用StringBuffer;
new String 与String
java堆、栈、堆栈,常量池的区别 String不new和new的区别 Java的String类常量池 图解 牛客例题 牛客例题2 牛客例题3 牛客例题4
栈区存引用和基本类型,不能存对象,而堆区存对象。 ==是比较地址,equals()比较对象内容。
String str1 = “ABC”;可能创建一个或者不创建对象,如果常量池中有“ABC”,则不创建对象,直接指向那个地址;如果”ABC”这个字符串在java 常量池里不存在,会在常量池里创建一个创建一个String对象(“ABC”),然后str1指向这个内存地址,无论以后用这种方式创建多少个值为”ABC”的字符串对象,始终只有一个内存地址被分配,之后的都是String的拷贝,Java中称为“字符串驻留”,所有的字符串常量都会在编译之后自动地驻留。
String str2 = new String(“ABC”);至少创建一个对象,也可能两个。如果常量池有“ABC”这个字符串,new关键字意味着将在heap中创建一个str2的String对象,value引用至“ABC”。如果这个字符串(ABC)在java 常量池里不存在,还会在常量池里创建这个String对象“ABC”(创建了第二个对象),即创建了两个对象。
栈与堆的区别
程序内存布局场景下,堆与栈表示两种内存管理方式:
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
==和equals比较
- ==对比的是栈中的值,基本数据类型是变量值,引用数据类型是堆中内存对象的地址;直接比较的两个对象的堆内存地址,如果相等,则说明这两个引用实际是指向同一个对象地址的
- equals:object中默认也是采用==比较,通常会重写
== 是java提供的等于比较运算符,用来比较两个变量指向的内存地址是否相同.而equals()是Object提供的一个方法.Object中equals()方法的默认实现就是返回两个对象==的比较结果.但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写. ==和equals 牛客例题 牛客例题
instanceof运算符
牛客例题
用来判断,instanceof 左边对象是否为instanceof 右边类的实例,返回一个boolean类型值。还可以用来判断子父类的所属关系。
//测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据 String s = "I AM an Object!"; boolean isObject = s instanceof Object;
应用场景:需要用到对象的强制类型转换时,需要使用instanceof进行判断。
可以判断 一个类的实例 一个子类的实例 一个实现指定接口的类的实例
instanceof详解
Scanner,BufferedReader,InputStreamReader 简介与对比
Scanner 类和BufferedReader 类的区别:Scanner类是读取并转换输入流的,而BufferedReader类是直接读取输入流,并不做转换;由此BufferedReader类读取的速度要比Scanner类读取的速度快;由于BufferedReader类读取输入流不进行转换,Scanner类也可以通过一个BufferedReader对象来实例化;
函数取值方法
牛客例题
- 大数取整(四舍五入) Math.round 取最接近整数,如果遇到一样近,则取最大值 Math.round(10.5) = 11 Math.rond(-10.5) = -10
- 向上取整 Math.ceil 无脑进位(向大方向) Math.ceil(10.1) = 11 Math.ceil(-9.6)= 9
- 向下取整 Math.floor 无脑退位(向负方向) Math.floor(10.6) = 10 Math.floor(-9.2)=-10
集合
牛客例题
线程安全的类有hashtable concurrentHashMap synchronizedMap
总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合;
链接:https://www.nowcoder.com/questionTerminal/709828a56a4443d899d89ae95b7c2940 来源:牛客网
java.util.Collection [I]
Collection 接口常用的方法
牛客例题
boolean add(E e) | 向集合中添加一个元素,如果集合对象被添加操作改变了,则返回 true。E 是元素的数据类型 |
boolean addAll(Collection c) | 向集合中添加集合 c 中的所有元素,如果集合对象被添加操作改变了,则返回 true。 |
void clear() | 清除集合中的所有元素,将集合长度变为 0。 |
boolean contains(Object o) | 判断集合中是否存在指定元素 |
boolean containsAll(Collection c) | 判断集合中是否包含集合 c 中的所有元素 |
boolean isEmpty() | 判断集合是否为空 |
Iterator<E>iterator() | 返回一个 Iterator 对象,用于遍历集合中的元素 |
boolean remove(Object o) | 从集合中删除一个指定元素,当集合中包含了一个或多个元素 o 时,该方法只删除第一个符合条件的元素,该方法将返回 true。 |
boolean removeAll(Collection c) | 从集合中删除所有在集合 c 中出现的元素(相当于把调用该方法的集合减去集合 c)。如果该操作改变了调用该方法的集合,则该方法返回 true。 |
boolean retainAll(Collection c) | 从集合中删除集合 c 里不包含的元素(相当于把调用该方法的集合变成该集合和集合 c 的交集),如果该操作改变了调用该方法的集合,则该方法返回 true。 |
int size() | 返回集合中元素的个数 |
Object[] toArray() | 把集合转换为一个数组,所有的集合元素变成对应的数组元素。 |
Collection:单列集合的根接口
Map:双列集合的根接口,用于存储具有键(key)、值(value)映射关系的元素。
List:元素有序 可重复
- ArrayList:类似一个长度可变的数组 。适合查询,不适合增删
- LinkedList:底层是双向循环链表。适合增删,不适合查询。
Set:元素无序,不可重复
- HashSet:根据对象的哈希值确定元素在集合中的位置
- TreeSet: 以二叉树的方式存储元素,实现了对集合中的元素排序
|—java.util.List [I] |—java.util.ArrayList [C] 非同步,实现了可变大小的元素数组 |—java.util.LinkedList [C] 非同步 |—java.util.Vector [C] 同步 |—java.util.Stack [C] |—java.util.Set [I] 不允许有相同的元素 |—java.util.HashSet [C] |—java.util.SortedSet [I] |—java.util.TreeSet [C]
牛客例题 牛客例题2
LinkedHashSet继承于HashSet、又基于 LinkedHashMap 来实现
TreeSet使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
HashSet存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得
ArrayList 中RandomAccess
实现RandomAccess接口的List(ArrayList)可以通过for循环来遍历数据比使用iterator遍历数据更高效,未实现RandomAccess接口的List(LinkedList)可以通过iterator遍历数据比使用for循环来遍历数据更高效。
简书 RandomAccess判断是否支持快速访问
当一个List拥有快速访问功能时,其遍历方法采用for循环最快速。而没有快速访问功能的List,遍历的时候采用Iterator迭代器最快速。
当我们不明确获取到的是Arraylist,还是LinkedList的时候,我们可以通过RandomAccess来判断其是否支持快速随机访问
集合List列表 牛客例题 牛客例题
ArrayList与LinkedList区别
codeGym
- arrayList使用的是数组数据结构,可以以O(1)时间复杂度对元素进行随机访问;LinkedList使用的是(双)链表结构查找某个元素的时间复杂度是O(n)
- arrayList更适合于随机查找操作,linkedList更适合增删改查,时间复杂度根据数据浮动
- 两者都实现了List接口,但LinkedList额外实现了Deque接口,因此Linked还可以当作队列使用
- LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素
在添加操作中数组如果不涉及扩容,添加速率比较快;链表指定索引添加的话和索引值相关;在插入操作中(指定索引添加),两者性能要根据实际使用情况评判。
//遍历ArrayList public static void main(String args[]){ List<String> list = new ArrayList<String>(); list.add("luojiahui"); list.add("luojiafeng"); //方法1 Iterator it1 = list.iterator(); while(it1.hasNext()){ System.out.println(it1.next()); } //方法2 for(Iterator it2 = list.iterator();it2.hasNext();){ System.out.println(it2.next()); } //方法3 for(String tmp:list){ System.out.println(tmp); } //方法4 for(int i = 0;i < list.size(); i ++){ System.out.println(list.get(i)); } }
ArrayList、LinkedList与vector区别
ArrayList 和 Vector 的区别是什么?
- 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
- 性能:ArrayList 在性能方面要优于 Vector。
- 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
使ArrayList线程安全方法: 1、继承Arraylist,然后重写或按需求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用ArrayList的方法。2、List list = Collections.synchronizedList(new ArrayList());
Array 和 ArrayList 有何区别?
- Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
- Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
- Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。
此部分来自:Java-interview
ArrayList敖丙
List和Set区别
- List:有序,按对象进入顺序保存对象,允许添加重复对象,允许添加多个null元素对象;可以使用iterator取出所有元素逐一遍历;可以使用get(int index)获取指定下标的元素
- Set:无序,最多允许一个null元素存在,元素对象不可重复。取元素时只能使用iterator接口取得所有元素,再进行遍历操作。
list方法常用的实现类有:
ArrayList、LinkedList 和 Vector。ArrayList最常用,提供使用索引(index)访问,定位、查询效率高;而LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适,Vector 表示底层数组,线程安全,效率低被边缘化~
Set方法中常用的实现类有:
HashSet、LinkedHashSet 以及 TreeSet。最常用的是基于 HashMap 实现的 HashSet;另外TreeSet 还实现了 SortedSet 接口(支持排序),因此 TreeSet 是一个可根据 compare() 和compareTo()方法进行排序的有序容器。
遍历方式 List 支持for循环,也就是通过下标来遍历,也可以用迭代器(Iterator),但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
* Map和Collection区别: * Map是双列集合:常用语处理有对应关系的数据,key不可以重复(夫妻对集合) * Collection:单列集合,Collection有不同的子体系,有的允许重复有索引有序,有的不允许重复且无序(单身汉集合)
List和Map区别
List , Set, Map都是接口,前两个继承至Collection接口(Collection接口下还有个Queue接口,有PriorityQueue类),Map为独立接口
(1)List下有ArrayList,Vector,LinkedList
(2)Set下有HashSet,LinkedHashSet,TreeSet
(2)Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
List :存储有序单列数据的集合,可重复
Map:存储双列数据的集合,通过键值对存储数据,存储的数据是无序的,Key值不能重复,value值可以重复
java.util.Map [I]
牛客例题
Map 遍历Map可以使用增强for循环 可以用 Map.Entry 面向对象实现(更常用) Map.Entry详解
//定义Hashmap集合 键值对为String类型 HashMap<String,String> hash = new HashMap<String,String>(); hash.put("IS1","lucy"); hash.put("IS2","tom"); hash.put("IS3","Li"); //方式一:获取key,通过key来获取value Set<String> keys = hash.keySet(); for (String k : keys){ String n = hash.get(k); System.out.println(k + "==" + n); } System.out.println("---------------"); //方式二:通过对象获取键值对 Map.Entry实现 Set<Map.Entry<String,String>> entries = hash.entrySet(); for (Map.Entry<String,String> entry : entries){ String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "==" + value); }
Map和SortedMap是接口,不能直接new对象
- -----HashTable 同步,实现一个key--value映射的哈希表,key和value都不允许出现null值
- -----HashMap 非同步,允许出现null-null键值对;将键映射到值的对象,一个映射不能包含重复的键;每个键最多只能映射到一个值 使用put()建立映射关系,key存在时候覆盖对应值
- -----WeakHashMap 改进的HashMap,实现了“弱引用”,如果一个key不被引用,则被GC回收
- -----TreeMap:用来存储键值映射关系,不能出现重复的键key,所有的键按照二叉树的方式排列
注:
List接口中的对象按一定顺序排列,允许重复 Set接口中的对象没有顺序,但是不允许重复 Map接口中的对象是key、value的映射关系,key不允许重复
|—java.util.SortedMap [I] |—java.util.TreeMap [C] |—java.util.Hashtable [C] |—java.util.HashMap [C] |—java.util.LinkedHashMap [C] |—java.util.WeakHashMap [C]
codeGym关于hashmap
HashMap与HashTable
https://www.cnblogs.com/williamjie/p/9099141.html https://blog.csdn.net/yang13563758128/article/details/86655574
程序员小灰
- HashMap线程不安全,没有synchronized;HashTable线程安全
- Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
- HashMap允许key和value为null,HashTable不允许
- HashMap继承自AbstractMap类。但二者都实现了Map接口。Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类(见其源码中的注释)。父类都被废弃,自然而然也没人用它的子类Hashtable了。
- 遍历方式: Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 Enumeration接口的功能和Iterator接口的功能是重复的,主要区别其实就是Iterator可以删除元素,但是Enumration却不能
- .HashMap不能保证元素的顺序,而LinkedHashMap可以保持数据的插入顺序,TreeMap可以按照键值进行排序(可自定比较器)
HashMap的key可以重复么 hashmap总结 hashmap底层
key不能重复 value可以重复
底层实现 | 数组+链表/红黑树 | 数组+链表/红黑树 | 数组+链表 |
线程安全 | 不安全 | 安全(Synchronized修饰Node节点) | 安全(Synchronized修饰整个表) |
效率 | 高 | 较高 | 低 |
扩容 | 初始16,每次扩容成2n | 初始16,每次扩容成2n | 初始11,每次扩容成2n+1 |
是否支持Null key和Null Value | 可以有一个Null key,Null Value多个 | 不支持 | 不支持 |
Hashmap使用拉链法解决哈希冲突
牛客网Enumeration接口和Iterator接口的区别
底层:数组+链表
默认长度16;jdk8开始链表高度达到8,数组长度超过64,链表将转变为红黑树,元素以内类Node节点存在
- 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
- 如果没有产生hash冲突,直接创建Node存入数组;产生hash冲突的话,先进性equal比较,相同则取代;不同则判断链表高度插入链表,当链表高度到8,数组长度超过64,转为红黑树;长度低于6则重新转回链表
- key为null,存在下标0位置
hashTable涉及到数组扩容
HashMap使用键/值得形式保存数据 HashMap允许将null用作键 HashMap允许将null用作值
Collection与Collections
牛客例题 百度 Collection和Collections的区别及Collections常用方法
- Collection 是一个集合接口。提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式
- Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架
可以使用 Collections.unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 java.lang.UnsupportedOperationException 异常
请你说说Iterator和ListIterator的区别
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。ListIterator是一个更强大的Iterator子类型 Iterator 只能向前移动,而 ListIterator 可以双向移动 ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
简单说明一下什么是迭代器
Java迭代器的查找操作和位置变更是紧密相连的。只能顺序next()或者反序previous()依次遍历。不能像get(index)那样随机访问
- Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口;
- 每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作;
- 有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException 异常. 但是可以通过Iterator接口中的remove()方法进行删除;
牛客例题
Iterator 支持从源集合中安全地删除对象,只需在 Iterator 上调用 remove() 即可。这样做的好处是可以避免 ConcurrentModifiedException ,当打开 Iterator 迭代集合时,同时又在对集合进行修改。有些集合不允许在迭代时删除或添加元素,但是调用 Iterator 的remove() 方法是个安全的做法。
hashCode与equals
进行对比值操作
https://www.jianshu.com/p/443e678291eb 牛客例题 hashCode()定义在JDK的Object.java 中,java中任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),特点:能根据“键”快速检索对应的“值”
hashCode:在对象加入HashSet时,会先计算对象的hashCode值来判断对象加入位置,如果对应位置没有值,hashSet会假设对象没有重复;如果有值,将调用equal()方法检查两个对象是否真的相同,如果相同,hashSet不让其加入操作执行;如果不同将被散列到其他位置,从而减少调用equal次数,提升运行速度
- 如果两个对象相等,hashcode一定相同;对两个对象分别调用equal方法 都返回true
- 两个对象有相同的hashcode值也不一定相等;equal方法被覆盖过的话,hashcode方法也必须被覆盖
- hashCode()的默认行为是对堆上对象产生独特值;如果没有重写hashCode(),则改class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
HashMap因为key也是唯一的,HashMap对象是根据其Key的hashCode来定位存储位置,并使用equals(key)获取对应的Value,所以在put时判断key是否重复用到了hashcode和equals,若重复了则会覆盖。
ConcurrentHashMap原理(jdk7和jdk8版本区别)
jdk7:ReentrantLock+Segment+HashEntry
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry有时一个链表
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表头部 锁:Segment分段锁
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样
jdk8:synchronized+CAS+HashEntry+红黑树
接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发
锁的粒度就是HashEntry(首节点);因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
哪些集合类是线程安全的?
Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 jdk 1.5 之后随着 java.util.concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。
牛客例题 牛客例题2
多线程
多线程编程优缺点 多线程面试 代码实现
响应性:对于用户图形界面尤其有用,当用户点击一个按钮执行耗时操作时(比如复制一个巨大的文件),应用程序依旧可以通过其他线程响应用户的操作
资源共享:进程只能通过共享内存和消息传递之类由程序员显示安排的技术共享资源;线程默认共享他们所属进程的内存和资源,系统允许一个应用程序在同一地址空间内有多个不同活动线程,一个线程的数据可以直接为其它线程所用;
经济:通常情况下创建一个进程所需的内存和资源相比线程要多得多,且进程的销毁操作也需要系统进行资源回收;线程能够共享所属线程资源,因此在创建和切换操作上更加经济
可伸缩性:多处理器体系结构更适合多线程,能够支持多个任务并行执行;多CPU系统更加有效,操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上
线程和进程区别
参考博客
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。
根本区别:进程是操作系统资源分配和调度的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
管程:Monitor——一种加锁实现的同步机制,保障同一时间只有一个线程可以访问被保护(加锁)的数据和代码; JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象; 其和Java对象一并创建和销毁,底层由C++实现
执行线程要求先成功持有管程,然后执行方法,最后方法完成(正常与非正常)时释放管程;方法执行期间,执行线程持有管程,其他任何线程都无法再获取到同一管程;如出现异常,且方法内部无法进行处理,那么这个同步方法所持有的管程将在异常抛到同步方法边界之外自动释放
多进程和多线程区别
- 多进程:操作系统中同时运行的多个程序
- 多线程:在同一个进程中同时运行的多个任务
举个例子,多线程下载软件,可以同时运行多个线程,但是通过程序运行的结果发现,每一次结果都不一致。 因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。
进程间通信和线程间通信区别
通信方式差异
一、进程间的通信方式
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 有名管道 (namedpipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量(semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( messagequeue ) :消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 (sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
- 套接字(socket ) :套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。
二、线程间的通信方式
锁机制:包括互斥锁、条件变量、读写锁
- 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
- 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量 信号机制(Signal):类似进程间的信号处理 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
线程状态
java线程+并发机制 线程的5种状态总结 牛客例题
线程有五个状态:
- 创建状态:线程被创建
- 就绪状态:线程一旦被创建,等待CPU的调用—形成就绪队列
- 运行状态:就绪状态的线程被CPU选中被执行,当时间片轮转调度到期后 线程由运行状态进行就绪状态
- 等待状态:线程运行过过程中由于缺少资源或者其他原因将中途被迫退出 ,即进入等待状态。当满足资源条件的时候,线程由等待状态进行就绪状态,重新等待CPU的调度(就绪状态不能回到等待状态)
- 结束状态:线程正常执行完毕,进入结束状态(等待状态可以直接进入结束状态 如果发生死锁,计算机不加与干预,系统根据策略将等待状态的进程直接转到结束状态 —非正常结束)
并发 牛客例题
A,CopyOnWriteArrayList适用于写少读多的并发场景
B,ReadWriteLock即为读写锁,他要求写与写之间互斥,读与写之间互斥,
读与读之间可以并发执行。在读多写少的情况下可以提高效率
C,ConcurrentHashMap是同步的HashMap,读写都加锁
D,volatile只保证多线程操作的可见性,不保证原子性
守护线程和用户线程
线程的daemon属性为true时表示守护线程,false表示用户线程(默认为false) 当用户线程全部结束时意味程序需要完成的业务已全部结束,系统只剩下守护进行,Java虚拟机会自动退出
守护线程:后台默认完成的一系列系统***,比如垃圾回收;是一种特殊线程
用户线程:系统的工作线程,完成系统需要完成的业务操作
进程状态
牛客例题
创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
执行状态:进程处于就绪状态被调度后,进程进入执行状态
阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用
终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行
创建多线程
多线程总结 多线程实现及常用方法 多线程创建的三个方法对比
1.继承Thread类,重载run方法; 2.实现Runnable接口,实现run方法 3.实现Callable接口
Java多线程实现方式主要有四种:
- 继承Thread类 实现Runnable接口
- 实现Callable接口通过FutureTask包装器来创建Thread线程
- 使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
序列化
牛客例题
序列化是把对象转换为字节序列的过程,为了存储在磁盘上或者进行网络传输。 反序列化是把存储在磁盘或网络节点上的字节序列恢复为对象的过程。 这是java进程之间通信的方式
序列化:将数据结构转换称为二进制数据流或者文本流的过程。序列化后的数据方便在网络上传输和在硬盘上存储。
反序列化:与序列化相反,是将二进制数据流或者文本流转换称为易于处理和阅读的数据结构的过程。本质其实还是一种协议,一种数据格式,方便数据的存储和传输。
Thread与Runnable
csdn 简书
- 效果上没区别,写法上的区别而已。
- Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。
写法上的区别无非就是你是new Thead还是new你自定义的thread,如果你有复杂的线程操作需求,那就自定义Thread,如果只是简单的在子线程run一下任务,那就直接实现runnable,当然如果自己实现runnable的话可以多一个继承(自定义Thread必须继承Thread类,java单继承规定导致不能在继承别的了)
说一下 runnable 和 callable 有什么区别?
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
执行流程
牛客例题
线程安全
线程安全与线程不安全
线程池
简书 csdn(带例子)
线程池示意图
任务流程图
主要思想:在进程开始时创建一定数量的线程,并加到池中等待服务器收到请求后被唤醒去完成相应请求,一旦完成了服务便返回池中继续等待工作;如果池内没有可用线程,那么服务器会持续等待到有空线程为止。
优点:使用现有的线程服务比等待创建一个线程更快 ,过于频繁创建/销毁线程对处理效率影响较大
允许采用不同策略运行任务 比如:延时执行、定时循环执行的策略等
线程池限制了任何时刻可用的线程总数,对不能支持高并发线程的系统十分重要 避免导致系统资源不足引起阻塞
线程池
//corePoolSize核心线程数 maximumPoolSize=核心线程数+非核心线程数 //五个参数的构造函数 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) //六个参数的构造函数-1 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六个参数的构造函数-2 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) //七个参数的构造函数 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的
CachedThreadPool()
可缓存线程池:
- 线程数无限制
- 有空闲线程则复用空闲线程,若无空闲线程则新建线程
- 一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
源码:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
通过我上面行云流水谈笑风生天马行空滔滔不绝的对各种参数的说明,这个源码你肯定一眼就看懂了,想都不用想(下面三种一样啦)
FixedThreadPool()
定长线程池:
- 可控制线程最大并发数(同时执行的线程数)
- 超出的线程会在队列中等待
创建方法:
//nThreads => 最大线程数即maximumPoolSize ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads); //threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看! ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮
ScheduledThreadPool()
定长线程池:
- 支持定时及周期性任务执行。
创建方法:
//nThreads => 最大线程数即maximumPoolSize ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } //ScheduledThreadPoolExecutor(): public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
SingleThreadExecutor()
单线程化的线程池:
- 有且仅有一个工作线程执行任务
- 所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
源码:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
还有一个Executors.newSingleThreadScheduledExecutor()结合了3和4,就不介绍了,基本不用。
作者:LiuZh_ 链接:https://www.jianshu.com/p/210eab345423 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
异常处理
Throwable、Error、Exception、RuntimeException 区别 联系 常见的几种RuntimeException
CodeGym
我们可以将异常运行时情况分为两组:**Throwable**
- 程序无法恢复并继续正常运行的情况。
- 可以恢复的情况。
第一组包括涉及从Error类下降的异常的情况。这些是由于JVM故障、内存溢出或系统故障而发生的错误。它们通常表示软件无法修复的严重问题。在 Java 中,编译器不会检查此类异常的可能性,因此称为未检查异常。该组还包括RuntimeException,它们是从Exception类派生并由JVM在运行时生成的异常。它们通常是由编程错误引起的。这些异常也没有被检查(unchecked) 在编译时,因此您不需要编写代码来处理它们。第二组包括在您编写程序时可以预见的异常情况(因此您应该编写代码来处理它们)。此类异常称为受检异常。谈到异常时,Java 开发人员的大部分工作就是处理这种情况。
RuntimeException和非RuntimeException的区别
Error : 系统级别的错误,如栈溢出 内存溢出之类,此类错误一般情概况保证程序能安全退出即可
Exception : 分为 RuntimeException 和 非RuntimeException RuntimeException: 程序员的错误,程序的错误,需要修改程序 如: 空指针异常 类型转换错误 数组越界 非RuntimeException: 外部环境导致程序的异常,和程序无关 如:在读取外部文件的时候,出现文件找不到的情况
java异常和错误的基类Throwable,包括Exception和Error
throw与throws
牛客例题
- throw用于抛出异常。
- throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象。
- throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象。
- try是用于检测被包住的语句块是否出现异常,如果有异常,则捕获异常,并执行catch语句。
- cacth用于捕获从try中抛出的异常并作出处理。
- finally语句块是不管有没有出现异常都要执行的内容。
throws 关键字和 throw 关键字在使用上的几点区别如下:
- throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。
- 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。
- throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
try catch }finally
当一个try块中抛出异常时,JVM 会在下一个catch块中寻找合适的异常处理程序。如果catch块具有所需的异常处理程序,则控制传递给它。如果没有,则JVM 会进一步查找catch块链,直到找到合适的处理程序。执行一个catch块后,控制转移到可选finally块。如果catch没有找到合适的块,JVM 会停止程序并显示stack trace(当前的方法调用堆栈),在第一次执行之后finally如果存在则阻止
public static void main(String[] args) { int result = test(); System.out.println(result); } public static int test() { int t = 0; try { return t; } finally { ++t; } }
控制台: 0
死锁
死锁面试题(什么是死锁,产生死锁的原因及必要条件)
必要条件:
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
常用方法
throw和throws区别
throw:表示方法内抛出某种异常对象 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出 即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错 执行到 throw 语句则后面的语句块不再执行 throws:方法的定义上使用 throws 表示这个方法可能抛出某种异常 需要由方法的调用者进行异常处理
start与run方法
Java Thread 的 run() 与 start() 的区别 牛客例题 牛客例题2 牛客例题3 牛客例题4
调用start()后,线程会被放到等待队列,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,执行本线程的线程体。run方法运行结束,此线程随即终止。
- start()方法来启动线程,真正实现了多线程运行,不能多次重复启动一个线程;无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;
- run方法只是thread的一个普通方法调用,还是在主线程里执行,可以在同一线程重复调用;程序要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码,因此run()没有达到多线程的目的。run()方法必须是public访问权限,返回值类型为void
start是异步非阻塞调用,run是同步阻塞调用,多线程要求就是异步非阻塞调用,所以start才能体现多线程本质
实现并启动线程有两种方法 1、写一个类继承自Thread类,重写run方法。用start方法启动线程 继承Thread类实现重写的run方法 2、写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动 实现Runnable接口,实例化Thread后调用run方法
sleep与wait
牛客例题 牛客例题2
在调用sleep()方法的过程中,线程不会释放对象锁。 sleep()是Thread的方法
而当调用wait()方法的时候,是Object类的方法;线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
volatile
为了提高处理速度,JVM会对代码进行编译优化,也就是指令重排序优化,并发编程下指令重排序会带来一些安全隐患:如指令重排序导致的多个线程操作之间的不可见性
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
- 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序;
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
满足三点时才应该使用volatile
- 对变量的写入操作不依赖变量当前值,或只有单个线程更新变量值
- 该变量不会与其他变量同时纳入不变性条件中
- 访问变量时不需要加锁
java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序
volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障
- 保证变量的内存可见性 - 禁止指令重排序
- volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如booleanflag;或者作为触发器,实现轻量级同步。
- volatile属性的读写操作都是无锁的,它不能替代synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁_上,所以说它是低成本的。
- volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序。
- volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见,volatile属性不会被线程缓存,始终从主 存中读取。
- volatile提供了happens-before保证,对volatile变量v的写入happens-before所有其他线程后续对v的读操作。
- volatile可以使得long和double的赋值是原子的。
- volatile可以在单例双重检查中实现可见性和禁止指令重排序,从而保证安全性
- volatile可以看做是轻量版的synchronized,volatile不保证原子性,但是如果是对一个共享变量进行多个线程的赋值,而没有其他的操作,那么就可以用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了。
- 当我们用 volatile 关键字修饰共享变量时就可以做到以下两点
- 当线程修改变量时,会强制刷新到主内存中当线程读取变量时,会强制从主内存读取变量并且刷新到工作内存中
线程局部存储TLS(thread local storage)
牛客例题
线程释放锁资源
join()底层就是调用wait()方法的,wait()释放锁资源,故join也释放锁资源
读锁与写锁
发布与逸出
发布:使对象能够在当前作用域之外的代码中使用 对象逸出:一种错误的发布,当一个对象还没构建完成时,就被其他线程所见。
避免逸出的方法 发布与逸出
常见对象逸出:
//内部可变状态逸出 在发布一个对象时,该对象的非私有域中引用的所有对象同样会被发布 class UnsafeStates{ private String[] states = {"AK","AL",...}; public String[] getStates(){ return states; } }
// 隐式的使用this引用逸出 在ThisEscape发布EventListener时因为其内部类实例包含对ThisEscape的引用,因此在发布同时也隐 //含发布了ThisEscape实例本身 public class ThisEscape{ public ThisEscape(EventSource source){ source.registerListener( new EventListener(){ public void onEvent(Event e){ dosomething(e); } }) } }
隐式逸出可能导致的问题:本应该是私有的变量被发布(试想你的银行密码被发布到网上); 对象未完全构造便被发布
//使用工厂模式防止this引用在构造函数过程中逸出 发布完整的构造函数(对象) public class SafeListener{ private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ dosomething(e); } }; } public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } }
synchronized使用与原理
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
sychronized实现原理 说一下你对Sychronized锁的了解 牛客例题
Sychronized的自旋锁、偏向锁、轻量级锁、重量级锁
锁只能进行升级不能降级
synchronized升级流程简图
sychronized
- 偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到了
2.轻量级锁:由偏向级锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了与重量级锁区分凯;轻量级锁底层是通过自旋来实现的,并不会阻塞线程
如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁导致线程阻塞
自旋锁:线程在获取锁的过程中不会去阻塞线程,也就无所谓的唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过CSA获取预期的一个标记;如果没有获取到,则继续循环获取,获取到了则表示获取到了锁,这个过程一直在运行,相对而言没有使用太多的操作系统资源,比较轻量级
synchronized与Lock的区别:
- synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
- synchronized会自动释放锁,而Lock必须手动释放锁。
- synchronized是不可中断的,Lock可以中断也可以不中断。
- 通过Lock可以知道线程有没有拿到锁,而synchronized不能。
- synchronized能锁住方法和代码块,而Lock只能锁住代码块。
- Lock可以使用读锁提高多线程读效率。
- synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。
sychronized和ReentrantLock
相同点:都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)
性能:自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized
CAS算法 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。;是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数
需要读写的内存值 V 进行比较的值 A 拟写入的新值 B 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试(查询)。
CAS与synchronized的使用情景
- 简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少)
- synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
乐观锁与悲观锁
图解悲观锁,乐观锁 乐观锁与CAS实现
乐观锁
认为数据的变动不会太频繁,因此线程在读取数据时不进⾏加锁,它允许多个事务同时对数据进行变动,在准备写回数据时,先去查询原值,操作的时候⽐较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执⾏读取流程
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁
乐观锁一般会使用版本号机制或CAS算法实现。 版本号与CAS算法详解
存在的问题 ABA 问题 循环时间长开销大 只能保证一个共享变量的原子操作
悲观锁
悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能变动,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized和ReentrantLock等独占锁就是悲观锁思想的实现
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,因此悲观锁适合应用在写为居多的场景下。 ———————————————— 版权声明:本文为CSDN博主「JavaGuide」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_34337272/article/details/81072874
多线程与事务
事务的隔离性是基于不同的连接的
单个线程Thread持有一个数据库连接Connection ,这个连接上可以有多个事务
假设事务中有三个操作数据库的dao方法,如果想用一个事务来管三个操作dao的方法,那么这三个dao的方法必须基于同一个Connection连接,此线程A会获得数据库连接池中的数据库连接ConnectionA
两个线程分别获得不同的数据库连接
框架与后台传输
Spring事务实现分析
Spring 事务
Spring 本身并不实现事务,Spring事务 的本质 还是 底层数据库 对事务的支持,没有 数据库 事务的支持,Spring事务就不会生效。
Spring 事务 提供一套抽象的事务管理,并且结合 Spring IOC 和 Spring AOP,简化了应用程序使用数据库事务,通过声明式事务,可以做到对应用程序无侵入的实现事务功能。例如 使用JDBC 操作数据库,想要使用事务的步骤为:
1、获取连接 Connection con = DriverManager.getConnection()
2、开启事务con.setAutoCommit(true/false);
3、执行CRUD
4、提交事务/回滚事务 con.commit() / con.rollback();
5、关闭连接 con.close();
011609 167601 161022 162412
牛客例题
AOP IOC
AOP IOC W3school
相对于EJB(Enterprise JavaBean,EJB)来说,Spring提供了更加轻量级和简单的编程模型
为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
- 基于POJO的轻量级和最⼩侵⼊性编程;
- 通过依赖注⼊和⾯向接⼝实现松耦合;
- 基于切⾯和惯例进⾏声明式编程;
- 通过切⾯和模板减少样板式代码。
耦合具有两⾯性(two-headed beast)。⼀⽅⾯,紧密耦合的代码难以测试、难以复⽤、难以理解,并且典型地表现出“打地⿏”式的bug特性(修复⼀个bug,将会出现⼀个或者更多新的bug)。另⼀⽅⾯,⼀定程度的耦合⼜是必须的——完全没有耦合的代码什么也做不 了。为了完成有实际意义的功能,不同的类必须以适当的⽅式进⾏交互。总⽽⾔之,耦合是必须的,但应当被⼩⼼谨慎地管理
自动装配实现依赖注入,可以在不改变所依赖的类的情况下,修改依赖关系
Spring装配Bean的两种方式:
1.通过xml实现装配 2.通过使⽤Java来描述配置
package com.springinaction.knights.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.springinaction.knights.BraveK night; import com.springinaction.knights.K night; import com.springinaction.knights.Quest; import com.springinaction.knights.SlayDragonQuest; @Configuration public class K nightConfig { @Bean public K night knight() { return new BraveK night(quest()); } @Bean public Quest quest() { return new SlayDragonQuest(System.out); } }
Spring通过应⽤上下⽂(Application Context)装载bean的定义并把它们组装起来
Aspect Oriented Program 面向切面编程
在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。⾯向切⾯往往被定义为促使软件系统实现关注点分离的⼀项技术
- 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
- 所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面;在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP
关注点(例如⽇志和安全)的调⽤经常散布到各个模块中,⽽这些关注点并不是模块的核⼼业务
利⽤AOP,系统范围内的关注点覆盖在它们所影响组件之上
AOP 的目的
牛客例题 核心业务还是要OOP来发挥作用,与AOP的侧重点不一样,前者有种纵向抽象的感觉,后者则是横向抽象的感觉, AOP只是OOP的补充,无替代关系
让关注点代码与业务代码分离 AOP使用场景 AOP能够确保POJO的简单性
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来使这些服务模块化,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
前置通知,在连接点方法前调用 | |
环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法,后面会讲 | |
@After | 后置通知,在连接点方法后调用 |
返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常 | |
异常通知,当连接点方法异常时调用 |
AOP实现
Spring AOP的两种实现技术 Spring实现AOP的四种方式 Spring AOP实现原理
实现AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
- 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring提供了4种实现AOP的方式: 1.经典的基于代理的AOP 2.@AspectJ注解驱动的切面 3.纯POJO切面 4.注入式AspectJ切面
IOC控制反转
《Inversion of Control Containers and the Dependency Injection pattern》
IOC: 控制反转 即控制权的转移,将我们创建对象的方式反转了,以前对象的创建时由我们开发人员自己维护,包括依赖关系也是自己注入。使用了spring之后,对象的创建以及依赖关系可以由spring完成创建以及注入,反转控制就是反转了对象的创建方式,从我们自己创建反转给了程序创建(spring)
由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找
DI: Dependency Injection 依赖注入 spring这个容器中,替你管理着一系列的类,前提是你需要将这些类交给spring容器进行管理,然后在你需要的时候,不是自己去定义,而是直接向spring容器索取,当spring容器知道你的需求之后,就会去它所管理的组件中进行查找,然后直接给你所需要的组件. 实现IOC思想需要DI做支持 注入方式: 1.set方式注入 2.构造方法注入 3.字段注入 注入类型: 1.值类型注入 2.引用类型注入
IOC目的
- 1.降低组件之间的耦合度,实现软件各层之间的解耦.
- 2.可以使容器提供众多服务如事务管理消息服务处理等等。当我们使用容器管理事务时,开发人员就不需要手工 控制事务,也不需要处理复杂的事务传播
- 3.容器提供单例模式支持,开发人员不需要自己编写实现代码.
- 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
- 5.容器提供众多的辅佐类,使这些类可以加快应用的开发.如jdbcTemplate HibernateTemplate
applicationContext & BeanFactory区别
Spring⾃带了多个容器实现,可以归为两种不同的类型:
- bean⼯⼚(由org.springframework.beans.factory.BeanFactory接⼝定义)是最简单的容器,提供基本的DI⽀持。
- 应⽤上下⽂(由org.springframework.context.ApplicationContext接⼝定义)基于BeanFactory构建,并提供应⽤框架级别的服务,例如从属性⽂件解析⽂本信息以及发布应⽤事件给感兴趣的事件监听者
BeanFactory接口 (1) spring的原始接口,针对原始接口的实现类功能较为单一;最简单的容器,它主要的功能是为依赖注入 (DI) 提供支持 (2)BeanFactory接口实现类的容器,特点是每次在获得对象时才会创建对象
ApplicationContext接口
Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文;是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean
(1)每次容器启动时就会创建容器中配置的所有对象 (2)提供了更多功能 (3)多种类型的应⽤上下⽂
- AnnotationConfigApplicationContext:从⼀个或多个基于Java的配置类中加载Spring应⽤上下⽂。
- AnnotationConfigWebApplicationContext:从⼀个或多个基于Java的配置类中加载Spring Web应⽤上下⽂。
- ClassPathXmlApplicationContext:从类路径下的⼀个或多个XML配置⽂件中加载上下⽂定义,把应⽤上下⽂的定义⽂件作为类资源。
- FileSystemXmlapplicationcontext:从⽂件系统下的⼀个或多个XML配置⽂件中加载上下⽂定义。
- XmlWebApplicationContext:从Web应⽤下的⼀个或多个XML配置⽂件中加载上下⽂定义
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory
Bean 定义
被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。
Spring从两个⾓度来实现⾃动化装配Bean:
- 组件扫描(component scanning):Spring会⾃动发现应⽤上下⽂中所创建的bean。
- ⾃动装配(autowiring):Spring⾃动满⾜bean之间的依赖。
Bean的作用域及生命周期
Bean的生命周期
传统的Java应⽤中,bean的⽣命周期很简单。使⽤Java关键字new进⾏bean实例化,然后该bean就可以使⽤了。⼀旦该bean不再被使 ⽤,则由Java⾃动进⾏垃圾回收
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
实例化 -> 属性赋值 -> 初始化 -> 销毁
bean装载到Spring应⽤上下⽂中的⼀个典型的⽣命周期过程
1.Spring对bean进⾏实例化; 2.Spring将值和bean的引⽤注⼊到bean对应的属性中; 3.如果bean实现了BeanNameAware接⼝,Spring将bean的ID传递给setBean-Name()⽅法; 4.如果bean实现了BeanFactoryAware接⼝,Spring将调⽤setBeanFactory()⽅法,将BeanFactory容器实例传⼊; 5.如果bean实现了ApplicationContextAware接⼝,Spring将调⽤setApplicationContext()⽅法,将bean所在应⽤上下⽂的引⽤传进来 6.如果bean实现了BeanPostProcessor接⼝,Spring将调⽤它们的post-ProcessBeforeInitialization()⽅法; 7.如果bean实现了InitializingBean接⼝,Spring将调⽤它们的after-PropertiesSet()⽅法。类似地,如果bean使⽤init-method声明了初始化⽅法,该⽅法也会被调⽤; 8.如果bean实现了BeanPostProcessor接⼝,Spring将调⽤它们的post-ProcessAfterInitialization()⽅法; 9.此时,bean已经准备就绪,可以被应⽤程序使⽤了,它们将⼀直驻留在应⽤上下⽂中,直到该应⽤上下⽂被销毁; 10.如果bean实现了DisposableBean接⼝,Spring将调⽤它的destroy()接⼝⽅法。同样,如果bean使⽤destroy-method声明了销毁⽅法,该⽅法也会被调⽤
Spring组成
Spring从两个⾓度来实现⾃动化装配Bean:
- 组件扫描(component scanning):Spring会⾃动发现应⽤上下⽂中所创建的bean。
- ⾃动装配(autowiring):Spring⾃动满⾜bean之间的依赖。
注解
@Bean
@Bean注解会告诉Spring这个⽅法将会返回⼀个对象,该对象要注册为Spring应⽤上下⽂中的bean。⽅法体中包含了最终产⽣bean实例 的逻辑。
@Bean public CompactDisc sgtPeppers() { return new SgtPeppers(); } /**默认情况下,bean的ID与带有@Bean注解的⽅法名是⼀样的。在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成⼀个不同 的名字的话,那么可以重命名该⽅法,也可以通过name属性指定⼀个不同的名字:**/ @Bean(name="lonelyHeartsClubBand") public CompactDisc sgtPeppers() { return new SgtPeppers(); }
@Component
这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean 还可以为bean设置不同ID
还有另外⼀种为bean命名的⽅式,@Named注解为bean设置ID(Java Dependency Injection Java依赖注⼊规范)
还需要显式配置⼀下Spring,从⽽命令它去寻找带有@Component注解的类,并为其创建bean
//如果想将这个bean标识为lonelyHeartsClub,那么你需要将SgtPeppers类的@Component注解配置为如下所⽰: @Component("lonelyHeartsClub") public class SgtPeppers implements CompactDisc { ... }
@ComponentScan
默认规则,它会以配置类所在的包作为基础包(basepackage)来扫描组件 ;可以通过basePackages属性设置多个基础包
@Configuration //扫描当前指定的basePackages数组中的包 @ComponentScan(basePackages={"soundsystem", "video"}) public class CDPlayerConfig {}
除了将包设置为简单的String类型之外,@ComponentScan还提供了另外⼀种⽅法,那就是将其指定为包中所包含的类或接⼝:
@Configuration @ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class}) public class CDPlayerConfig {}
basePackages属性被替换成了basePackageClasses。同时,我们不是再使⽤String类型的名称来指定包,为basePackageClasses属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包
@Autowired
@Autowired是Spring特有的注解。如果你不愿意在代码中到处使⽤Spring的特定注解来完成⾃动装配任务的话,那么你可以考虑将其替换为@Inject(来源于Java依赖注⼊规范)
构造器装配:构造器上添加@Autowired注解表明当Spring创建CDPlayerbean的时候,会通过这个构造器来进⾏实例化并且会传⼊⼀个可设置给CompactDisc类型的bean。
@Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; //将required属性设置为false时,Spring会尝试执⾏⾃动装配,但是如果没有匹配的bean的话Spring将会让这个bean处于未装配的状态 @Autowired(required=false) public CDPlayer(CompactDisc cd) { this.cd = cd; } }
方法装配:在属性的Setter⽅法上
@Autowired 与@Resource的区别(详细)
知乎
@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。如果查询的结果不止一个,那么@Autowired会根据名称来查找。如果我们想使用按名称装配,也可以结合@Qualifier注解一起使用。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
推荐使用@Resource注解在字段上,这样就不用写setter方法了.并且这个注解是属于J2EE的,减少了与Spring的耦合,这样代码看起就比较优雅
@Configuration
进⾏显式配置的时候,有两种可选⽅案:Java和XML
JavaConfig显式配置Spring 关键在于为其添加@Configuration注解,表明这个类是⼀个配置类,该类应该包含在Spring应⽤上下⽂中如何创建bean的细节
forward与redirect
请求转发与重定向
1.从地址栏显示来说
- forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
- redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2.从数据共享来说
- forward:转发页面和转发到的页面可以共享request里面的数据.
- redirect:不能共享数据.
3.从运用地方来说
- forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
- redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
4.从效率来说
forward:高. redirect:低.
Servlet (了解)
牛客例题 牛客例题2
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源
牛客例题 牛客例题2
HttpServlet容器响应Web客户请求流程如下:
1)Web客户向Servlet容器发出Http请求;2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息; 4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet还是doPost,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息; 7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户
状态码
状态码分类:
- 1XX- 信息型,服务器收到请求,需要请求者继续操作。
- 2XX- 成功型,请求成功收到,理解并处理。
- 3XX - 重定向,需要进一步的操作以完成请求。
- 4XX - 客户端错误,请求包含语法错误或无法完成请求。
- 5XX - 服务器错误,服务器在处理请求的过程中发生了错误。
301状态码:被请求的资源已永久移动到新位置
401:请求要求身份验证
403:服务器已经理解请求,但拒绝他
404、请求失败,请求所希望得到的资源未被在服务器上发现
503:由于临时的服务器维护或者过载,服务器无法处理请求。
常用算法
基础概念
时间复杂度
算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。
- T(n)的上界与输入大小无关,则称其具有常数时间,记作O(1)时间
- T(n) =O(logn),则称其具有对数时间
- 的时间复杂度为O(n),则称这个算法具有线性时间
- T(n) = O(nlog n),则称这个算法具有线性对数时间
不是时间复杂越低的越好,要考虑数据规模,如果数据规模很小 甚至可以用O(n^2)的算法比 O(n)的更合适
==
什么是稳定排序
保证排序前两个相等的数其在序列的前后位置顺序与排序后它们的前后位置顺序一致。形式化解释如下:一列数中,如果Ai = Aj,Ai位于Aj的前置位,那么经过升降序排序后Ai仍然位于Aj的前置位。 体现程序健壮性
如果在排序的输出中保持相等键或数字的原始顺序,则该算法称为排序算法
- 不稳定排序:选择排序、快速排序、希尔排序、堆排序
- 稳定排序:冒泡排序、插入排序、归并排序和基数排序
快速排序 常见排序