2025届_愿永随风
技术栈测试
1、学Java看的哪个资料:学校上过Java课,但是忘记了,本来学的也不扎实,没有写过代码
- 重载,重写,泛型懂吗?
不懂 - Java集合这块学过了吗?
忘记什么是集合了 - HashMap源码,JVM,JUC是否看过:
没有
2、数据结构与算法看的哪个资料:
只上过课,教材是严蔚敏清华计算机系列 - 链表,栈,队列,二叉树能自己手写吗:
知道概念,不会手写 - AVL树,红黑树,线段树学过吗:
没学过 - 冒泡,快速,归并排序能手写吗:
大一学的,概念都快忘记了,冒泡可以手写,但需要时间 - 递归,二分,贪心,回溯,动规,枚举这几种算法学过哪几种:
递归和贪心只停留在知道,其他的不太清楚 - LeetCode 大概刷了多少题:
10道,还是用的Python
3、框架与中间件 -
servlet,cookie,session学过吗:
- SSM学过了吗:
- redis 学过吗:
-
SpringBoot项目做过几个:
上面的我不知道是什么
4、MySQL看的哪些资料: -
sql 熟练吗:学过还记得但是不熟练,要使用可能还需要复习一下
- 索引底层原理懂吗:不知道
5、计算机基础
* 计算机网络看什么资料:上课学习过,没有看过资料
* 操作系统看什么资料:没学过操作系统
* 计算机组成原来看的什么资料:上课学习过,只记得很难
学习规划(2024.6.12)
一些链接入口
课程大纲: 所有课程大纲入口 ,PS:你要的东西,课程大纲基本都会有哈,需要学啥记得来课程大纲看看。
你的大致情况分析与规划
基础这块略差,考虑到你学历还不错,简单学习几个月找个实习问题不大,所以目前我们先按照最核心的过一遍,也就是说,先做项目,做完才能找实习。
所以我们的第一个目的,就是先成功做一两个项目,然后写到简历上,之后全力准备面试,为了学习的更快,你要按照我的节奏来,到时候你觉得看不懂,再跟我反馈,因为做一个项目,乱七八糟的东西太多了,什么 javaweb ssm springboot maven mysql redis 等等,太多了,具体安排如下:
具体学习安排
1、Java理论:这个是必须要学的了,先学习 Java基础,之后在学习 Java进阶,按照最低要求学即可,就是我标注不看的可以不看,需要看的就侧重理解+通过文档会使用就行。集合源码分析看不懂也没事,会基本使用就行。
划重点:为了学习更快,请学习的时候,按照我网站的说明来,每个目录都会说明需要掌握哪些,该跳过的就跳过哈,按照最低的要求来学就行,项目实战就不做了,练习题也可以不做哈,学完忘了不用担心。
配套练习(必做):你学到对应模块的时候,可以在牛客网这里刷刷题:Java语法刷题,相当于熟悉一下基本语法。视频的练习你可做可不做,但是这个配套练习必须做哈。
时间推荐:25~30天。
2、项目:我们先全力做出一个项目,你直接按照这个路线学习就行 web学习路线整合(框架+项目),先做一个支付项目,后面根据时间看看要不要做第二个项目,做的话可以做论坛项目或者弹幕项目,第二个比较难。不过做完第一个就准备找找日常实习了。
下面是一些说明,你要跟着我说的来:
Java web:javaweb很杂,但是需要学,你只需要知道有这么个东西就可以了,比如里面的 html css js,了解基本即可,tomcat servlet 等,也是。反正你想做就做,不想做就简单了解是个什么玩意就行,总之快速过一下。
ssm:mybatis也是了解是个什么玩意,跟着视频配置会最基本的操作就行,spring 重要一些,要知道 ioc aop 是个啥玩意。然后 springmvc 的话,了解下 mvc 是个什么意思就行,然后就可以跳过了。建议五六天过一下。
时间推荐:25~30天。
3、八股文:八股文太多了,直接跟着 八股文高频题库(必看) 刷吧,重点学习这几个。
Java基础:就Java常见概念,看高频题Java基础部分
Java集合:重点了解下 hashmap +arraylist和lingkedlist区别就行
mysql:重点看下索引+事务基本几个要素就行
redis:看那几个标星高的就行
反正你先拿下这几个,这样去面试就有了基本盘,之后再刷高频题里的其他,一般 八股文高频题库(必看) 里面的内容搞定,中小厂没问题,往届很多人拿这个验证过的了。
时间推荐:20天
4、算法:你可以跟着我的课程 拿捏算法面试课程 刷,主要刷一下基础题+中等题就行,一开始你可以用Python刷,但是后面等你Java学的 差不多了,建议用Java刷,可以顺便熟悉语法。
时间:每周刷15道。
学习顺序
一般下半年找个时间问题不大,但是要在9月份开始找的话,可能就会着急一些,到时候我在根据你的学习情况调整吧。
学习顺序的话,可以按照 1=》2=〉3来学习就行,然后学习的期间,也一直学习刷算法题4,当然,你也可以先学习完1,再来刷算法题,直接用Java刷。。
就行先安排这么多,然后记得来每周打卡学习进度哈,也可以星球每天打卡哦,我会看的,比如像他们这样:0. 每周打卡做的比较好的成员示例,你也可以每天打卡或者星球打卡吧,我会看的
有什么问题记得随时私聊帅地
第二阶段安排
其实你已经把很多东西都大致过了一下了,接下来要面中大厂,所以需要更加深入的学习,可以先学习下面的内容:
1、框架与项目:支付项目比较简单,你在做两个,就做论坛+弹幕系统,里面有:web学习路线整合(框架+项目)。
时间:20天。
2、核心八股文:先系统刷一下 mysql + redis + JVM这些,刷完就可以按照面经来刷了,你应该也看过部分的了
(1)JVM入门=》Java并发:学习 图解JVM学习指南 ,学完后面有时间再看书,没时间就这个其实也够了,之后学习 Java并发,然后Java并发有点难,你看前面十几章节就行,有个概念就行,因为比较难。
(2)mysql=>redis:mysql 你看 MySQL原理剖析课程说明,重点看标🌟的那些。redis 的话刷一下Redis原理剖析阅读指南(新),刷标🌟的那些即可。
时间推荐:20天,可以和1同步学习
3、算法刷题:接下来只需要把算法高频题库搞定就行,其实和你之前说的大部份重复。
时间:每周 15+ 道题。
4、复习:复习的话,和之前一样,也是这两个
第一轮复习(必须完成):刷高频题,按照 八股文高频题库(必看) 进行第一轮复习,核心学的差不多,就准备去投递简历了。
时间:20天。
第二轮复习(非必需):之后按 面试突击专题学习指南(必看) 进行第二轮复习,这个时候你已经能够应付挺多八股文了,但是八股文学无止境,之后学完有时间再回归系统学习,我会给你推荐对应的书籍和专栏。
时间:10天。
5、优质文章:有空可以刷一刷 3. 优质文章补充,里面都是很好的知识点补充。
学习顺序
1+2+3都可以同时学,2学完就学习4=》5.
就行先安排这么多,然后记得来每周打卡学习进度哈,比如像他们这样:0. 每周打卡做的比较好的成员示例
有什么问题记得随时私聊帅地
每天打卡记录
Day1 2024年6月12日
阅读学习路线,开始学习课程
1. Java语言的跨平台性是指编译好的class文件可以在多个系统下运行,它依靠的是各个系统都安装了JVM(包含在jdk里),不同的系统有不同版本的JVM。一次编译,到处运行。
编译指令:javac 运行指令:java
编译型语言C:显著特点是直接编译成机器码,并生成单独平台的可执行文件,一次编译,无限次运行
解释型语言Java:编译得到.class文件(字节码),可以在多平台解释,解释成机器码再运行。一次编译,到处运行。
2. jdk = jre【JVM+java运行所需的核心类库】 + Java开发工具【java,javac等】
如果只需要运行.class。那么只需要jre
3. 配置好了jdk环境,能够运行java入门程序,后面应该学起来更快一点
4. 算法面试:先想,确认边界,先讲思路,再写代码。可以请求要提示
Day2 2024年6月13日
sublime太难用,重新安装了IDEA,听java课程:
- 整型,long常量声明需要加上L或者l,否则默认int
byte,short的赋值,应该用范围内数字直接赋值,而不可以用int的变量赋值,否则报错 - 浮点数 = 符号位+尾数位+指数位
浮点数float声明需要加f或者F,否则默认double
浮点数不可以使用 = = 判断相等,而应该使用相减小于阈值来判断是否相等 - Java8 api中文版使用说明
- char用单引号,String用双引号
char可以是整数,输出的是对应的unicode码的字符
如果char是字符,可以用(int)强类型转换为unicode码数字
char如果和数字加减,如果不转换那么结果将是整数,如果字符转换那么将是字符 - 字符串存储:字符->码值->二进制->存储
读取就是反过来
ASCII:一个字节,实际可以表示256,但是只用了128个字符
Unicode:统一2个字节,但是浪费了空间,前128个和ASCII是一致的,因此兼容
utf-8:字母一个字节,汉字三个字节。是Unicode的一种改进
gbk:字母1字节,汉字2字节
Day3 2024年6月14日
- 自动类型转换:低精转高精,反过来就会报错
多种类型混合计算,系统将所有类型统一为最高的再计算
char 和 byte,short不能互相转换,但是可以一起计算,不过会统一成int再计算。就算单种,只要有三者之一,就会统一成int
布尔类型不参与自动转换 - 强制类型转换:有意地容量大转成容量小,数据溢出,精度损失
强转的时候注意优先级char和int的强转注意
字符串与基本数据类型转换:
基本数据类型转字符串:a+””
字符串转基本数据类型:解读基本数据类型对应的包装类
String s = “123”
int num = Integer.parseInt(s)
double num = Double.parseDouble(s)
字符串转字符就是提取出第一个字母:s.charAt(0) - 运算符
10/4如果不转换将是整数,如果计算完了才转换也将不是2.5而是2.0
取模:a % b = a – a / b * b
++中的临时变量:
i=i++->temp=i,i=i+1,temp=i
i=++i->i=i+1,temp=i,i=temp
instanceof:判断是否是子串
逻辑与、短路与:针对是否需要判断第二个条件有区别,短路与&&效率高,因为如果第一个条件已经false,那么第二个不用再判断,也不会再判断 - 命名规范
包小写,类名接口名大驼峰,变量名方法名小驼峰,常量名全大写加下划线分割单词 - switch
注意穿透性质,因此必须有break
必须用byte,short,int,char,String,enum中的一种 - break、continue 标签,但是尽量不用
没有标签则默认退出最近的一层 - 数组
创建:
动态初始化:数据类型[] 名字 = new 数据类型[数量]
动态初始化:先声明再初始化,数据类型[] 名字;数组名 = new 数据类型[数量]
静态初始化:数据类型[] 名字 = {元素1,元素2,元素3,…}
基本数据类型是值拷贝,数组是引用机制,地址拷贝
array.length:返回数组长度
Day4 2024年6月15日
- 字符串相等:
string1.equals(string2)->返回布尔值 - 二维数组:
初始化和一维一样
不过动态初始化时,列数可以不确定,不同,
int[][] array = new int[3][];
然后用循环单独开空间:
for(int i = 0;i < 3;i++){
array[i] = new int[i+1];
for…//循环赋值
}
Day5 2024年6月17日
- 复习键盘输入:
import java.util.Scanner
Scanner sc = new Scanner(System.in);
int a = sc.nextInt(); - 牛客网做题做到面向对象
- JVM内存机制:
值类型就是基本数据类型(如 int、double 等),而引用类型是指除了基本的变量类型之外的所有类型(如通过 class 定义的类型)
栈:基本的变量类型
堆:引用类型
方法区:常量池,类加载 - 属性 = 成员变量 = 字段field:
可以使基本数据类型也可以是引用类型,比如数组,字符串
如果定义时没赋值会有默认值 - 创建对象流程:
加载类信息
堆中分配空间,默认初始化
地址赋值
指定初始化 - 方法执行时也是在栈开辟独立空间并使用,遇到return以后返回到调用的地方,同时方法的空间销毁释放。main方法(栈)执行完毕后,程序退出
- 方法定义时的参数为形参,调用时的参数为实参,注意二者类型,个数,顺序必须一致!方法不能嵌套定义。
8.方法的调用:
同一个类里,可以直接调用。
跨类调用,也正常需要创建然后调用。内存方面也是在栈完成方法 - 基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!
Day6 2024年6月18日
- 浅克隆和深克隆:
浅克隆:浅克隆是指创建一个新对象,然后将当前对象的所有字段值拷贝到这个新对象中。如果字段是基本数据类型的变量(例如int、char等),则会直接复制这些值。如果字段是引用类型(例如对象、数组等),则只复制引用,不会复制引用对象本身。
深克隆:深克隆是指不仅创建一个新对象,并且复制当前对象所有字段的值,还要递归地复制所有引用对象,确保新对象完全独立于原始对象。 - 递归(难)
如果传输的是引用类型,那么会共享数据
递归必须向退出递归逼近,否则就是无限递归,出现死龟 - 方法重载(overload)
函数名相同,但是形参不同(类型、个数、顺序,参数名无要求,返回什么无要求)。目的是相似的功能共用函数名,更方便
Day7 2024年6月19日
- 可变参数
实参可以是任意多个(也可以是0)
实参类型相同
可变参数实参可以是数组,本质是数组
可变参数可以与普通参数一起在形参列表,但必须保证可变参数在最后
可变参数只能有一个 - 变量作用域
全局变量(属性):整个类
局部变量(例如成员变量):只有在定义的代码块内能使用
全局变量可以不赋值,有默认值
但是局部变量必须赋值 - 构造器:
可重构,无返回值,也不需要写返回值。如果不写会有默认构造器
创建对象过程:
堆中开空间
开完空间默认初始化各个变量
根据赋值再次显式初始化
执行构造器,构造器初始化变量(可能涉及常量池)
栈中创建引用 - this
可以理解为每个对象都有一个自己的独立的属性,指向自己。this和对象的hashcode是一样的。它只能在类的方法里面使用
this的作用是避免因为不同变量作用域的规则造成混乱,用于区分属性和局部变量
this(参数列表)专门用来在一个构造器中访问另一个构造器,这条语句必须放在构造器的第一条语句 - IDEA快捷键与模板(这里真的打开新世界了)
删除行、重复行、自动补全、添加删除注释
导入包、快速格式化代码、快速运行程序
生成构造器、查看层级关系、快速定位方法、自动分配变量名
快速打印、fori、main模板! - 包:本质就是文件夹
- 访问修饰符:
public:对所有公开
protected:对同类,子类,同包公开
默认:对同类和同包公开
private:只对同类公开
每当要使用到类(只能用默认/public修饰)、方法、变量时,我们都需要根据上面的规则来思考、判断能否访问到?
Day8 2024年6月20日
面向对象编程
面向对象三大特征:封装、继承、多态
- 封装
目的:1.隐藏方法细节,便于使用。2.可以验证数据,确认合理性
步骤:属性私有化、公共set方法用于赋值(数据验证)、公共get方法用于获取值(权限判断)
setter and getter
从这里开始,构造器最好是使用set方法来完成构造!!! - 继承
extends:
父类、超类、基类
子类、派生类:
子类继承父类的所有属性、方法,但是父类私有的不能访问。
子类必须调用父类构造器完成父类初始化
当创建子类对象,子类构造器默认调用父类的无参构造器,如果父类没有无参构造器,需要在子类构造器里用super指定父类构造器。 - super关键字
super.用来访问父类的属性、方法,但不能访问父类私有的
super()访问父类的构造器,但是必须在子类的构造器里,而且必须是第一句!
目的:父类属性父类初始化,子类属性子类初始化
如果子类和父类没有重名的属性、方法,在使用时用super,this,直接使用三者一样。但是,如果有同名的,super表示使用父类的,this或者什么都不写直接用方法,表示使用本类的。
属性、方法从子往父逐级查找! - 方法重写
子类的某个方法的名称、参数和父类完全一样,而且返回类型和父类完全一样或者是父类返回类型的子类(例如子类String,父类Object),那么就称子类的该方法为父类的那个方法的重写。
子类方法重写不能缩小父类方法的访问范围。
重载和重写:
同:前后方法名必须一样
异:重载在本类,形参列表必须不同,返回类型无要求,对修饰符无要求;重写在父类和子类,形参列表必须相同,返回类型一致或者被包含,修饰符要求不能缩小父类的访问范围。
Day9 2024年6月21日
- 刷牛客封装和继承部分
- 多态
目的是提高代码的复用性和维护
方法的多态:重写、重载都能体现多态
对象的多态:向上转型和向下转型
对象的编译类型和运行类型可以不一致
编译类型一旦定义,不能够改变
运行类型是可以变化的
编译类型看=号左边,运行类型看右边 - 向上转型
前提是子父类继承关系。
父类的引用可以指向子类,编译类型是父类,但是运行类型是子类。这被称为向上转型
本质是父类的引用指向了子类的对象!!!
语法:父类 名字 = new 子类();
注意!- 可以调用父类所有成员(但是仍需遵守访问权限),但是不能调用子类特有的成员(即如果子类有和父类重写的,那么也可以调用)
2.最终运行的效果还是看子类(运行类型)的具体实现。即从子类开始查找,找到就调用
- 可以调用父类所有成员(但是仍需遵守访问权限),但是不能调用子类特有的成员(即如果子类有和父类重写的,那么也可以调用)
- 向下转型
向下转型后,就可以调用子类的所有成员了。本质是强转。
语法:子类型 新的子类名字 = (子类型) 父类的名字;
要求:
强转的要求是父类的引用本身就指向了目标子类。例如原本父类指向猫,那么强转只能转成猫。 - 属性没有重写之说
属性的值纯粹看编译类型!!! - 比较操作符
a instanceof AA:返回布尔值,用于判断该引用名字a的运行类型是否是AA类或者其子类 - 复习自动类型转换链
byte,short,int,long,float,double
char,int,long,float,double - Java的动态绑定机制(重点)
当调用对象方法时,该方法会和对象的运行类型绑定!
但是,当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用!!!
Day10 2024年6月23日
- = =和equals
“= =”如果是基本类型:比较数值
如果是引用类型:比较地址
注意如果String不是new出来的,用= =是可以判断二者是否相等的,因为都在堆的常量池里。 - equals
只能用来判断引用类型,是类的一个方法
Object判断是否是同一对象
String已经重写过,改成了用来比较内容 - hashcode
是类的方法
目的是为了提高具有哈希结构的容器的效率
两个引用如果指向同一对象,那么哈希值一定相同,否则大概率不一样(但是有可能一样)
哈希值主要是根据地址来的,但不等同于地址
hashcode()也可以重写。有时候也需要重写。 - toString
类的方法
默认返回:全类名+@+hashcode
全类名:包名加类名
重写toString方法,一般可以直接输出属性
并且,当直接输出一个对象时,等价于直接调用它的toString方法! - finalize方法
类的一个方法,当某个对象(类)没有引用时,jvm认为该对象是一个垃圾对象,就会调用回收机制销毁它,相当于释放空间给别人用。但是销毁前,会先调用finalize方法。
如果不重写该方法,那么会默认调用Object类的finalize方法。
Day11 2024年6月24日
- 刷牛客对象的多态:
Class.forName(className).newInstance()创建名字为className的对象
实例的方法:getClass()表示获取实例的类
类方法:getSimpleName()表示获得类名字 - 类变量,又叫静态变量
static修饰,目的是使用在需要被所有的类的实例共享时。
实际上,在内存空间中,类的所有实例的该静态变量指向同一空间,jdk8及以后,静态变量在堆里的Class实例之后
并且静态变量在类加载的时候就生成了!
定义方式:修饰符+static+变量类型+名字
访问方式:类名.变量名,因为加载类的时候就生成了静态变量,因此访问不需要创建实例
访问范围依然看修饰符,和正常规则一样 - 类方法,又叫静态方法
定义方式:修饰符+static+返回变量类型+名字(){}
访问方式:类名.变量名,因为加载类的时候就生成了静态变量,因此想访问不一定必须创建实例
好处是不需要创建实例就可以使用方法,当做一种工具来使用。创建自己的工具类时使用
静态方法和普通方法都是随着类的加载而加载,都放在方法区
类方法中没有this和super参数,不能使用!正确方法是使用类名.调用
普通方法不能用类名调用!
静态方法只能访问静态方法和属性,但是普通方法可以访问静态成员或者非静态成员
Day12 2024年6月27日
- main方法
是Jvm调用的,不需要创建对象就能调用,所以是static
main的传参,是从执行时候传数组进去。java 类 参数1 参数2 …
idea可以用配置来传参 - 代码块
可以理解为只有方法体的方法,对构造器的补充机制。静态代码块,普通代码块
可以有修饰符,但只能是static。
代码块的调用顺序优先于构造器细节:
- 静态代码块,作用是对类初始化,随着类的加载而执行,加载只有一次,所以只会执行一次。只能调用静态成员!
- 普通代码块则是每次创建对象随构造器执行,注意只是创建时调用。所有成员都可以调用
- 类什么时候加载?
new一个对象时
创建子类对象时,父类也会先被加载,然后加载子类。
使用类的静态成员时 - 普通代码块实际隐藏在构造器里面的最前面,但在super后,这是后面5,6点的根源
- 单个类创建时候执行顺序:
首先静态。静态属性初始化和静态代码块按照书写顺序执行
其次普通。普通属性和普通代码块也是同4一样
最后调用构造器 - 重点:第5点的延伸,如果不是单个类,而是有继承的,那么创建子类时,加载顺序如下:
- 父类静态(静态代码块和属性初始化顺序按书写顺序,定义顺序)
- 子类静态(代码块和属性一样按定义顺序)
- 父类普通(代码块和属性一样按定义顺序)
- 父类构造器
- 子类普通
- 子类构造器
本质上,类是先加载,再创建,先加载父类,再加载子类,这就导致了a和b。
然后,创建的时候,先加载子类的构造器,而构造器先有super,于是找到父类构造器,而父类构造器也先有super和普通代码块,然后才开始自己的构造器部分。于是有了c、d。
执行完以后,才回到子类的普通代码块和自己的构造器内容部分。于是有了e、f - 设计模式——单例模式
整个软件系统中,某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法- 饿汉式步骤:
- 私有化构造器,防止用户自己new实例
- 在类的内部程序员自己创建一个静态实例(属性)
- 提供一个公共的静态方法,返回我们的实例
2和3必须都是静态的不然无法返回
因为可能造成创建了对象但是并没有使用
- 懒汉式步骤:
- 私有化构造器
- 定义静态实例但是不创建
- 提供公共静态方法,如果实例为空就创建实例。返回实例
3.区别: - 创建时机不同
- 懒汉式会有多线程问题,多个线程都到判断的时候,可能会导致每个线程都创建自己的从而破坏单例
- 饿汉式可能会导致资源浪费了
- 饿汉式步骤:
-
final关键字,跟在修饰符后面
- 修饰类名:使某个类不会被继承
- 修饰方法:使得方法不会被重写
- 修饰属性:变成常量,不会被修改(这里变量最好是全大写,单词之间用下划线间隔)
细节:
- 必须赋初值:位置可以是定义时、构造器、代码块
- 如果是静态属性,只能在静态代码块或者定义时赋值
- final不能修饰构造器
- final和static最好一起使用,可以使得效率更高,因为底层优化过,类不会被加载
- 包装类(Double,Integer)和String都是final类,不能被继承
Day13 2024年6月28日
- 抽象类
父类有不确定性的方法,考虑设计成抽象方法。就是只声明了,但是没有方法体的方法。然后需要用abstract来修饰 class 和方法。
abstrace class 名字{}
修饰符 abstract 类型 方法名字(){}
细节:- 抽象类不能实例化,即父类不能被实例化
- 抽象类不一定必须有抽象方法,但是如果有抽象方法,那么必须声明抽象类
- abstract只能修饰类与方法
- 抽象类本质也是类,普通类该有的都可以有
- 抽象类的子类必须实现抽象类的抽象方法,否则它也必须是抽象类
- private,final,static都不可以和abstract同时使用
- 模板设计模式
模板类,定义抽象方法,并使用它
子类继承模板,并调用父类的模板方法,重写抽象方法,利用动态绑定机制,使用模板
Day14 2024年7月1日
- 接口
定义接口、实施接口、使用接口
interface 接口名{
属性;
方法;
}
class 类名 implements 接口名{
自己属性;
自己方法;
必须实现接口的抽象方法;
}
jdk8后可以有默认或者静态实现方法,但是前提是必须有default或者static修饰
总的来说,接口只能有三种方法:抽象,静态,默认
细节:- 接口不能被实例化
- 接口类的所有抽象方法都是public,并且可以省略abstract修饰。
- 接口的抽象方法必须全部实现
- 抽象类使用接口可以不用实现方法
- 一个类可以实现多个接口,用逗号隔开即可
- 接口中的属性,都是默认是public、静态并且final的
- 可以直接用接口名访问属性
- 接口可以继承别的接口,甚至可以继承多个。但是不能继承类
- 接口的修饰符只能是默认或者public
类的继承是不可以继承多个的,这样的需求就诞生了接口。当子类继承了父类就拥有了父类的功能,但是当需要扩展功能,可以通过接口来使用。接口是对继承的一种补充机制。
接口和继承的对比:- 继承主要在于复用性,接口在于设计规范、灵活
- 继承 is a,接口 like a
- 接口拥有代码解耦能力
接口的多态:
- 向上转型:接口类型虽然不能实例化,但是是可以指向实现了它的类的对象的!
- 多态数组:
1.首先多态数组可以和普通数组一样创建,例如
接口[N] 名字 = new 实现类[N];
2.其次,需要访问实现的类的独有的方法或者属性时,仍然是向下转型
(实现类 名字).方法 - 接口的多态传递:也就是接口会继承接口,如果类实现了某个接口,那么等价于类实现了其父接口
Day15 2024年7月4日
- 内部类
内部类与外部类
类的五大成员:[属性、方法、构造器、代码块、内部类]
Day16 2024年7月6日
- 内部类
- 局部内部类
定义在外部类的局部位置如方法,本质仍是一个类
可以访问外部类的所有成员,直接访问即可!因为同类的访问权限最高
不能添加访问修饰符,但可以加final
作用域:仅仅在方法中可以使用
外部类在方法中可以直接使用内部类创建对象实例并使用
如果局部内部类和外部类的变量重名了,而内部类的方法要使用外部类的那个变量,则使用 外部类名.this.变量 - 匿名内部类(核心!!!)
可以基于类或者基于接口,语法就是直接写:
new 某个类(参数列表){};(实际上是继承)
new 某个接口(){};(实际上是实现)
定义在局部(方法中),本质更像一个对象
本质是实例,new的时候,底层会分配一个匿名类实现接口或者继承类,名字是:外部类名dollar01。也就是说,它的编译类型其实是接口或者类,但是运行类型是:外部类名dollar01
当new的是一个类的时候,还是需要满足构造器的参数列表要求的
调用:- 可以接受后使用
- 直接使用
直接可以直接访问外部类的私有成员
如果内外有重名成员,访问遵循就近原则- 成员内部类
- 写在外部类成员的位置,没有修饰符
- 可以访问外部类的任意成员
- 修饰符都可以写,因为本质是一个成员
- 外部其他类访问内部类:2种方式
- 先创建外部类实例outer01,再outer01.new Inner();
- 在外部类写一个返回内部类实例的方法
Outer.Inner a =
5.如果内外部重名,处理方法和之前写的一样
- 静态内部类
- 和成员内部类一样的,写在成员位置,可有任意修饰符,无非多一个static
- 只能访问外部类的所有静态成员
- 外部类访问静态内部类:创建对象访问
- 外部其他类访问静态内部类?
- 方式1:直接利用静态特性创建:
Outer.Inner a = new Outer.Inner() - 法二:编写方法返回实例
- 如果有重名,访问外部需要用Outer.属性名。注意这里不能用this,因为静态的东西都不能用this!
- 局部内部类
Day17 2024年7月8日
- 枚举类enum
枚举成员的本质是创建的对象实例- 自定义类实现枚举
私有化构造器,本类内部创建枚举对象,对对象使用publ+static+final并大写 - enum关键字实现枚举
class直接改成enum
其余的什么构造器啥的都不用变
创建对象的语句改成:参数名(构造器参数列表的实参);
如果有多个常量,使用逗号间隔,不用另外写语句
定义的常量对象必须写在enum类的最前面 - javac编译 javap反编译
- Enum对象的常用方法
- name()输出名字
- ordinal()输出次序,即从0开始的编号
- values()输出枚举的内容数组
- valueof接受字符串,匹配字符串是否是已有枚举对象,如果找到就返回枚举对象
- compareTo比较编号是否相等。本质是编号相减,返回的是相减的结果
- Enum不能继承,但是能实现接口
- 自定义类实现枚举
- 注解:
- @Override:重写,作用是如果写了编译器就会去检查是否真的重写了,如果重写则通过,没有重写就会报错
- 修饰注解的注解,叫元注解
- @Deprecated修饰某个元素表示该元素已经过时,即不再推荐使用但是仍可以使用
- 版本升级过渡使用
- SuppressWarnings:不显示警告。在{“”}内填写。例如”all”
- 异常
异常处理,try-catch
语法和逻辑错误不是异常
两种异常,都在Throwable包下面:
error:严重错误,难以预料、不太可能出现的错误。崩溃。栈溢出,内存溢出。
exception:可以预料,能够处理的错误,分为两大类:
运行时异常RunTimeException:空指针,算数异常,数组越界,类型转换,数字格式异常
编译时异常:例如找不到文件
常见的运行时异常:- NullPointerException:空指针异常,即内容为空
- ArryIndexOutOfBoundsException:数组越界
- ClassCastException:发生在类对象间的转换错误
- NumberFormatException:数据类型错误
- ArithmeticException:算术错误
Day18 2024年7月9日
编译异常
1. IOException:操作文件时发生的异常
2. SQLException:操作数据库时异常
3. FileNotFoundException:文件无法找到
4. ClassNotFoundException:找不到类异常
try-catch-finally处理机制示意图
try是一个块,catch可以捕获异常,当发生异常时,系统封装成exception对象e,并交给catch。catch块只有捕获异常才会执行,但是finally不管try是否有异常被捕获,都会执行。
Day19 2024年7月10日
- throws抛出异常,即有异常不处理,和try catch二选一
最终可以抛到JVM,它会直接输出异常并终端程序
如果没有显式使用trycatch,会默认采用throws - try-catch细节
- 如果异常发生,那么try里异常后的代码不会再运行,而是直接蹦入catch快
- finally一般用于关闭连接,释放资源
- catch可以有多个,用于捕捉不同的异常。catch括号里的异常类不同即可。但是要求子类异常写在前面,父类异常写在后面。
- try-finally可以直接用,不用catch,本质上没有捕获异常。其目的是无论是否有异常,都要执行业务逻辑。
- 如果有 finally,那么无论是否发生异常,都有执行 finally 的语句,即使 catch 里面有 return 语句,那么也会执行 finally
- throws异常
- 抛出的异常可以是发生的异常也可以是其父类,还可以是异常列表(即逗号连接)
- 编译异常,必须显式处理,例如io文件流。而对于运行时异常,不显式处理那么默认抛出异常
- 子类重写父类方法,要求抛出异常相同或者子类的抛出为父类的子类
- 一般情况下自定义异常继承运行时异常,好处是很方便。
- try catch会影响性能,因为堆栈展开、异常对象的创建、上下文信息的捕获
- throws:方法声明处,后面跟异常类型名
throw:手动抛出异常,后面跟异常对象名 - 包装类
8种基本数据类型的引用类型。数字类型是继承了Number - Integer详解
装箱:基本数据类型到包装类
拆箱:包装类到基本数据类型
jdk5以后,就有自动装箱拆箱了- 自动装箱:Integer integer = n1;(本质是调用Integer.valueOf(n1))
- 自动拆箱:int n2 = integer;(本质是调用integer.intValue)
其他包装类用法类似
Integer转String- String str = i + “”
- = i.toString()
- = String.valueOf(i)
String转Integer - Integer integer = Integer.parseInt(str)
- Integer integer = new Integer(str)
Integer和Character类常用方法:
MIN_VALUE MAX_VALUE
Character: isDigit,isLetter,isUpperCase,isLowerCase,isWhitespace,toUpperCase,toLowerCase
面试题:
Integer m = 1;
Integer n = 1;
sout(m = = n);//True,因为本质是调用Integer.valueOf(int),其会判断是否-128-127,是则会返回之前缓存有过的对象,否则创对象
Integer m = 128;
Integer n = 128;
sout(m = = n)//False
= =可以用来判断Integer和int,并且只要有一方是基本数据类型就是判断数值,两方都是引用类型就会比较地址。
Day20 2024年7月11日
- String详解
String实现了Serializable,可以串行化,可以在网络中传输
实现了Comparabel,说明可以比较大小
一个字符占两个字节
字符串类有超级多构造器
String是final类,不能被继承
字符串类有一个属性,用于存放字符串内容,本质是char[]。它是final的,不能被修改,即value地址不能被修改,不能指向新的地址,但是内容可以变化(其实数组都是这样?)但是引用是可以变的,上面的内容指的是常量池中的String是不可更改的 - String的创建方式
- String str = “name”;
这种方式会先看常量池是否有name串,有则指向,无则创建后栈的str指向它的常量池地址 - String str = new String(“name”);
这种方式会先在堆中开辟对象,然后栈的str指向堆里的对象地址,对象里面有value,value去找常量池是否有name串,有则指向常量池的地址,无则创建。相比于方式1多了一个堆的过渡
intern()方法会最终返回value地址的那个字符串(即常量池),即使你是用方法二创建的
- String str = “name”;
- 经典面试题
- String str = “name”;
str = “haha”;
创建了两个对象 - String str = “name” + “age”;
编译器会优化,等价于String str = “nameage”因此只有一个对象 - String a = “abc”;
String b = “hello”;
String c = a + b;
共创建了4个对象,但是,其中a、b各自指向常量池中的一个,而c本质是通过StringBuilder对象创建的一个String对象!!常量池最后仍然没有“abchello” 即如果d = “abchello”,那么c = = d返回false。底层是通过堆中的StringBuilder完成的。
重要规则:”abc”+”hello”常量相加,是在池中。而只要有一个变量相加,是在堆中通过StringBuilder完成,并不涉及常量池,并且会有StringBuilder中间对象!!! - 数组放在堆里!
方法调用:基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!方法调用是在栈里新建形参的。引用类型传递地址,因此形参会指向地址。 String传参的更改对原String其实没有影响(注意原因是仅仅形参地址改变,对象可能并未改变)。
- String str = “name”;
关于创建对象的问题,这个链接很清楚了。
Day21 2024年7月12日
- String常用方法:
- equals
equalsIgnoreCase
length
indexOf:获取子字符或子字符串在字符串第一次出现的位置,从0开始数的。没有返回-1
lastIndexOf:the last time a char appear in a String
substring:get the specific substring in a string.如果只有一个参数,那么是从n开始截取后面的。如果两个,第一个是指定开始,第二个是指定结束,左闭右开!。
trim:去前后空格
charAt:获取索引处的字符,注意不能用str[0] - toUpperCase
toLowerCase
concat:拼接后返回新的串
replace:将第一个参数的内容替换为第二个参数的内容//注意必须用新的变量接受
split:用参数为标准,进行分割并返回串组。参数可以是串或字符。注意用数组接受,并且如果有特殊字符要用转义符\
toCharArray:将字符串转成字符数组
compareTo:比较两个字符串大小:如果逐个计算出不为0的差值,则返回不为0的差值。如果逐个计算的差值全部为0,那么计算字符串长度差
format:格式化字符串。占位符%s(字符串),%d(整数),%.2f(小数并四舍五入),%c(字符)。内容由后面的内容顺序替换
- equals
Day22 2024年7月13日
- StringBuffer
直接父类AbstractStringBuilder,可以串行化,本质不是final,因此存放在堆中
StringBuffer是final类,不能被继承
每次更新不用更新地址,而是直接更新内容。不过当空间不够时,就需要更新地址。所以效率高于String - StringBuffer构造器
1号:StringBuffer stringbuffer = new StringBuffer()可以有参数,如果参数为空,则默认创建大小为16的构造器
2号:StringBuffer stringbuffer = new StringBuffer(“hello”);先开辟一个length+16的空间,再append“hello”。 - 二者转换
String转buffer- 构造器2号
- 先创建buffer,再使用append方法
buffer转string: - 使用StringBuffer提供的toString
- 使用String的构造器,new String(stringbuffer)
- 常用方法
s.append:直接在原变量上加,不需要接收
s.delete(start, end):左闭右开
s.replace(start, end, string):索引区间的内容替换成string:
s.indexOf、lastIndexOf
s.insert(start, string):插入string,使得索引位置处为string,后面的顺序后移 - StringBuilder
不是线程安全的,即存在多线程问题,但是其本质是buffer在单线程的简易替换,两者API一样,因此如果是单线程,尽量优先使用builder
主要是使用append和insert方法
也继承了AbstractStringBuilder,可以串行化。final类,不可被继承。数据仍然存放在父类,堆里。
方法都没有synchronized互斥处理,因此无法多线程使用。 - String,buffer,builder区别
如果字符串有大量修改,单线程用builder,多线程用buffer,如果很少修改并存在复用,用string - Math类常用方法
abs pow ceil floor round sqrt random(注意此处指定a到b的随机数怎么写?a + random*(b-a)) max min求两个数最大最小值 - Arrays类常用方法
toString
sort:正常是直接从小到大排列,但是可以使用匿名类完成从大到小,本质是重写比较规则:如果返回值为正数就交换顺序
sort(arr,new Comparator(){
@Override
public int compare(object o1, Object o2){
int in1 = (Integer)o1;
int in2 = (Integer)o2;
return in2-in1
}
}
)
binarySearch:要求顺序排列,用二分法查找索引,如果没有返回负值
copyOf(arr, arr.length):拷贝数组,需要用新的变量来接收,第二个是拷贝的数量,第二个参数可以大于length,实现扩容
fill(arr, 99):用第二个参数替换arr里的所有元素
equals:完全一致就返回true
asList:将数组转成List - System
exit():退出程序
arraycopy:五个参数,第一个是源数组,第二个是从源数组的哪个索引开始,第三个是目标数组,第四个是目标数组的希望拷贝到的开始索引,第五个是拷贝多少个数据到目标数组。
currentTimeMillens:从现在到1970指定时间的时间差数,单位是毫秒 - BigInteger
用于保存非常大的数
new BigInteger(“2888888888888888888888888888888888888”);
注意用字符串引起来
加减乘除需要用相应的方法,add,subtract,mutiply,divide
但是方法必须传入的也是BigInteger - BigDecimal
用于保存精度非常高的小数
同上,也是需要调用方法才能加减乘除。
不过,除法可能抛出无限小数异常,解决方法是divide传入第二个参数BigDecimal.ROUND_CEILING。它将保留分子的小数精度。 - 日期类
- Date类
Date d1 = new Date()获取系统时间
//SimpleDateFormat创建对象可以指定格式
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy年MM月dd日 hh:mm:ss E”);
String format = sdf.format(d1);
sout(format);
此外,可以用sdf.parse把你指定的字符格式转成Date类
Date()构造器还可以传入long格式,变成从1970开始的时间 - Calendar类
无法通过new获得,而是通过getInstace()取得对象,再通过对象获取字段。c.get(Calendar.YEAR)
有很多字段YEAR,MONTH(需要+1,因为从0开始数),DAY_OF_MONTH,HOUR(HOUR_OF_DAY24小时制),MINUTE,SECOND,DAY_OF_WEEK自由组合 - 第三代日期
LocalDate年月日,LocalTime时分秒,LocalDataTime年月日时分秒
LocalDateTime ldt = LocalDateTime.now()
获得方式:
ldt.getYear()
getMonth()
getMonthValue()等等等- 格式化时间 DateTimeFomatter
DateTimeFomatter dtf = DateTimeFomatter.ofPattern(“yyyy年MM月dd日 hh:mm:ss E”);
String format = dtf.format(ldt); - Instant 时间戳
- 格式化时间 DateTimeFomatter
- Date类
- 经典题
- String翻转。i,j,用到toCharArray,最后需要return new String(chars);
保护机制的重要编程技巧!!!:想正确的情况,再取反就是错误的,需要抛出异常 - 用户注册范式
闯关式:需要用到Character.isDigit
- String翻转。i,j,用到toCharArray,最后需要return new String(chars);
Day23 2024年7月14日
- 集合
数组不灵活,不能扩容,要求类型全部一致,增删很麻烦
集合,增删改查很容易。- 集合的框架体系
单列集合:
Iterable:Collection
Collection:List/Set
List:ArrayList/Vector/LinkedList
Set:HashSet(LinkedHashSet)/TreeSet
双列集合:
Map:HashMap/HashTble/TreeMap
HashTable:Porperties
HashMap:LinkedHashMap - Collection接口类是实现特性
add:可以装任意的对象,但是不会是基本数据类型了,因为会自动装箱
remove:可以传入指定对象,或者索引都可以
contains:返回是否存在的布尔值
size:返回有多少个元素
isEmpty:是否空?
clear:清空
addAll:传入集合
containsAll:传入集合,判断是否存在
removeAll:传入集合,删除集合 - Iterable迭代器
Iterator iterator = arraylist.iterator();
while(iterator.hasNext){
Object object = iterator.next();//编译类型可以是运行类型的父类
}
快捷键,itit
当退出迭代器,游标指向最后一个元素,如果要再次便利需要重置迭代器
iterator = arraylist.iterator(); - 强化for循环,底层本质是简化版迭代器。也可以直接在数组使用。
- 集合的三种遍历方式:
原始迭代器、强化for、原始for用get
- 集合的框架体系
- List接口及其子类
- 特性
添加、存放、取出元素有序,并且可以重复
支持顺序索引,从0开始,可以用get(index)方法取出 - 常用方法
add(index, obj);插入
add(index, Collection)插入集合
indexOf(obj)
lastIndexOf(obj)
remove(index)
set(index, obj):相当于替换
subList(start,end):左闭右开,子集合 - ArrayList注意事项
- 空值是可以放进去的
- 底层是由数组来实现的
- 基本等同于Vector,但是是线程不安全的,多线程不建议使用ArrayList。源码方法没有synchronized修饰。但是执行效率高。
- ArrayList源码分析
本质是维护了一个Object数组elementData,构造器初始化可以传一个数表示大小
扩容机制:如果初始化没有指定大小,那么初始化为0,然后在第一次add时扩容为10,然后每次容量不够的时候,都会扩容为之前的1.5倍。如果指定了大小,那么容量不够时,需要扩容时直接按指定的1.5倍扩容
transient表示瞬间、短暂的,不会被序列化。 - Vector注意事项
底层也是对象数组,支持线程同步、安全的,有synchronized修饰。适合多线程,但是相比效率不高 - ArrayList源码分析
扩容机制和ArrayList基本一样,区别就是- 扩容倍数不是1.5而是2倍
- 调用无参构造器的话,不是初始化为0,而是开辟空间为10,相当于调用参数为10的参数构造器
- LinkedList
底层不是数组,而是双向链表,first,last,prev,next,item
线程不安全,没有实现同步和互斥
添加、删除通过链表完成,效率很高
add:增加元素
源码分析:
加入的元素放最后,
第一个元素加入,创建node,头尾都指向它,prev,next为空
之后每次加入都放最后,同时调整之前节点的next。
remove:有三种,传入index,传入对象,不传入(会默认删除第一个)
set(index, obj)
get(index) - 对比ArrayList LinkedList
改查用ArrayList
增删用LinkedList
二者都是单线程
- 特性
Day24 2024年7月15日
- Set接口
无序,无索引,无重复元素
可以迭代器、可以强化for,但是不能通过索引的普通for来获取
取出顺序不是添加顺序,但是其是固定的,不会每次取出都不一样 - HashSet类
- 可以存放空值,但是只能存放一个
- 没有重复元素,这里重复指的是
- 底层是HashMap,其底层是数组+链表+红黑树
数组是Node数组,Node[]其实就是table表
Node有item和Node next两个元素 - 存储算法:
底层本质是HashMap
添加元素时,计算Hash值,转成索引值
找到存储数据表,查看索引值是否为空,如果为空则直接放入
如果不为空,则使用equals方法比较是否相同,相同就放弃掉,如果不相同,则挂载到那个位置后面。
如果table数大于等于64个了,并且某一条链表大于等于了8个,则会对这条链表进行红黑树化 - 扩容机制:
第一次添加时直接扩容到16,临界值是16* 0.75(加载因子)=12,如果超过了12,则会* 2=32,此时的临界值仍然是乘0.75 = 24。以此类推
如果table数大于等于64个了,并且某一条链表大于等于了8个,则会对这条链表进行红黑树化,只要不满足上面的情况,如果链表大于等于8时还变长,或者table达到阈值,则会使用数组扩容机制
- LinkedHashSet
是HashSet子类
底层是一个LinkedHashMap,双向链表加数组
每次add都会通过双向链表接在一起
也有next,只不过相比于父类多了一个before,after - Map
Map 和 Collection并列存在
保存双列元素,key-value
key和value可以是任意引用类型,存放在node节点中
key不能有重复,但是value可以有重复,二者都可以是null
大部分时候使用String作为key
key和value单向一对一,一个key只有一个value
常用方法:
put(key, value)
get(key) - HashMap为了方便程序员的遍历,还有EntrySet内部类
该集合存放Entry类型的集合,而一个entry对象就有k,v
entry类型是EntrySet,每个entry的定义类型是Map.Entry,但是实际上存放的是Node,这是因为node类实现了entry接口,node使用entry接口的好处是便于遍历,因为其提供了方法getKey(),getValue()
entrySet实际上不存放kv,而是存放kv的引用。
还有hashmap.keyset() 是Set类型,hashmap.values()是Collection类型
Day25 2024年7月19日
- HMap常用方法
put(key, value)
remove(key, value)
get(key, value)
size:多少对
isEmpty():是否为空
clear
containsKey:键是否存在 - 遍历方式
containsKey keySet values entrySet keyset.iterator
增强for、迭代器
把keySet取出、把values取出、entrySet
2×3=6种遍历方式 - HMap小结
key不能重复,但是value可以重复,如果添加相同的key那么会覆盖原来的value
方法上没有实现同步和synchronized,因此线程不安全 - 扩容机制
和hashset完全一样。创建对象时,加载因子默认0.75
添加key-val时,使用key的hash寻找table的索引,如果索引为空则加入,否则判断索引处的key和添加的key是否equal,如果equal就替换value,否则根据链表或者红黑树规则添加,如果添加时的容量不够就扩容
只有当一条链表大于8个并且table大于64才会扩容
第一次添加时直接扩容为16,临界值就是乘0.75 - HashTable
底层是HashTable Entry
也是k-v关系,但是kv都不可以为null,如果key重复同样会替换
线程安全,因为方法有做synchronized
扩容机制:
创建时初始化开辟11个位置,加载因子0.75
扩容条件是计数大于临界值,扩容时乘2加一 - 集合小结
一组对象:Collection- 允许重复:list
- 增删多:Linkedlist底层双向列表
- 改查多:ArrayList底层object可变数组
- 不允许重复:set
- 无序HashSet底层是HashMap
- 排序TreeSet底层就是TreeMap
- 插入和取出顺序一致:LinkedHashSet底层是HashMap加双向列表
一组键值对:Map
键无序:HashMap底层是数组加链表加红黑树,jdk7就是数组加链表
键排序:TreeMap底层是内部类Entry
键插入与取出顺序一致:LinkedHashMap数组加双向链表
读取文件:Properties
- 允许重复:list
- TreeSet
底层是TreeMap
本身无序,但是创建时可以传入比较器(匿名内部类)从而实现顺序
但是比较器认为相同的不会再被加入 - TreeMap
和TreeSet区别就是键值对。
本身无序,但是创建时可以传入比较器(匿名内部类)从而实现顺序
但是比较器认为相同的不会再被加入 - Collections工具类
常用方法:
reverse(List)
shuffle(List):随机打乱
sort(List):升序排列,可以在LIst后面跟一个比较器
swap(List, indexi, indexj):交换顺序
max(List) min(List),也可以跟一个比较器,指定最大的
frequency(LIST, obj)
copy(dest, src):需要提前扩容
replaceAll(List, “tom”, tangmu):后面的替换前面的
Day26 2024年7月20日
泛型
- 作用:对集合类的数据类型进行约束,遍历时取出和存入不用再类型转换,很高效
- 快速入门:ArrayList表示存放到集合里的都是Dog类型,如果存入的不是要求类型,编译器就会报错,而且遍历时就可以直接取出Dog类型,不用取出Object类型再向下转型了。泛型可以保证编译不报错运行就不会ClassCastException
- 泛型的使用:是在类声明时通过标识表示类中某个属性的类型,例如:
Class Person{
//类中可以灵活使用E
}
定义创建对象的时候,编译的时候就已经传入了,如果有问题就会报错
泛型可以写多个,例如 - 使用细节:
- 泛型也可以在类型和接口中使用,但是类型必须是引用类型
- 指定泛型后,可以传入该类型或子类类型
- 泛型也可以使用简化形式(推荐)
List list1 = new ArrayList(); - 如果不人工指定泛型,默认是Object泛型
- 自定义泛型类
就是自己写一个类,自定义一个泛型
属性、方法可以使用泛型
使用泛型的数组,不能初始化
静态成员、方法不能使用类的泛型 - 接口泛型
接口中,静态成员也不能使用泛型
泛型接口的类型,在继承接口或者实现接口时指定
没指定泛型类型,默认为Object,建议就算是Object也写上去 - 自定义泛型方法
public void fly(T t, R r){}
一般定义了,在传参就要用
在泛型方法调用的时候通过传参数的形式传类型
如果修饰符后没有自定义泛型,那么方法是普通方法,只是使用了类的泛型 - 泛型的继承和通配符
泛型不允许继承
允许任意泛型
允许A的子类
允许A的父类 - JUnit
用于测试代码单元
线程
- 概念:
进程:运行的程序,有生命周期
线程:线程是进程创建的,是进程的一个实体,一个进程有多个线程
单线程、多线程
并发:同一时间,多个任务交替进行,貌似同时(单核CPU)
并行:同一时刻,多个任务同时进行,多核CPU
并发和并行可以同时存在 - 创建线程的两种方式
- 继承Thread类,重写run方法
使用start()来开启线程,会自动使用run
当一个类继承了Thread(实现了Runnable接口),就可以当成一个线程来使用
当启动进程,会开启一个main线程,main线程会从0开始开启子线程,子线程不会阻塞main线程,二者会根据CPU个数选择并发或者并行
为什么用start()不用run()?答:不会开线程,而是相当于在main里面执行普通方法,串行化,线程会阻塞!
底层是start调用start0,start0才是真正实现多线程的,是本地方法,由JVM机调用,底层是c、c++实现,JVM机具体的方法由不同操作系统算法实现。
start调用start0后线程只是变成可运行状态,具体还要由CPU统一调度。 - 实现Runnable接口(推荐)
这是由于Java单继承,如果已经继承其他的,想要开一个线程就只能实现Runnable接口。适用于多线程共享资源
Thread thread = new Thread(Dog);
thread.start();
这就开启了线程,并且会自动调用Dog的run方法。
底层是使用了设计模式之代理模式。
- 继承Thread类,重写run方法
Day27 2024年7月21日
多线程
Thread.currentThread().getName()输出当前线程
接口的方式好处是不用静态都可以实现资源共享
多线程容易出现安全问题–售票超卖问题
1. 终止线程
1. 通知方式:set loop为false。由一个线程修改另一个线程的变量,从而实现通知另一个线程退出
2. 常用方法
1. interrupt一般在线程休眠期间使用,抛出一个中断异常。
2. setPriority
3. getPriority
4. yield:礼让,让出CPU。但是不一定礼让成功,因为可能CPU资源本来就很丰富
5. join:插队,优先执行
主线程调用t2.join()会使用所有资源优先执行t2,t2执行完才会继续执行main
2. 用户线程和守护线程
用户线程也叫工作线程,所有任务执行完或者通知结束
守护线程用来为用户线程服务,当所有工作线程结束,守护线程自动结束。例如:垃圾回收机制
设置守护线程方式:在线程启动前,手动设置
myThread.setDaemon(true)
3. 线程的生命周期的七大状态
Thread.State:t.getState()
New
Ready/Running:可运行状态,取决于内核调度器,yeild是将Running转成Ready
TimedWaiting:sleep()
Waiting:t.join()
Blocked:等锁
Terminated:程序终止
4. 线程同步机制(解决卖票超卖)
线程同步:保证数据(内存地址)在同一时刻只有一个线程访问或修改。
使用方法:synchronized互斥锁,必须拿到括号里的才能执行代码块
1. 代码块锁synchronized(this){} synchronized(obj){}
2. 方法锁:public synchronized void sell()保证同一时刻只能有一个使用sell方法,默认锁this,也可以是其他对象,任意的都可以。
锁静态方法,锁加在类上,Sell.class
要保证锁的是同一个对象
Day28 2024年7月22日
- 线程死锁
A先o1,再o2.
B先o2,再o1。二者卡死 - 释放锁
sleep,yeild不会释放锁 - 文件基本操作
文件就是保存数据的地方
文件在程序中是用流的形式来操作的- 创建文件
- 方式1
String filePath;
File file1 = new File(filePath);//这里只在内存里
file1.createNewFile();//这里才真正在磁盘里有了文件 - 方式2
File parentFile = new File(“e:\”);
String fileName = “file2.txt”;
File file2 = new File(parentFile, fileName);
file2.createNewFile(); - 方式3
String parentPath = “e:\”;
String sonPath = “file3.txt”;
File file3 = new File(parentPath, sonPath);//这里只在内存里
file3.createNewFile();//这里才真正在磁盘里有了文件
- 方式1
- 常用方法
file.
getFileName()
getAbsolutePath
getParent
length():获得字节大小
exists()
isFile
isDirectory - 目录方法
mkdir:创建单级目录
mkdirs:创建多级目录
delete:只能删除一个文件或者空目录
Java里可以把目录看成一个文件
MySQL学习
- 创建文件
- SQL类型
DDL数据库、表、列
DML数据增删改
增INSERT into
删delete from 表明 where
改UPDATE 表名 set 列=内容 where - DQL,查询数据
select 列名,最好不要使用* 号
from
where
group by
having
order by
limit- 基础查询
select 列名,最好不要使用* 号
distinct关键字加在列名前,表示去重
as关键字接在列名后可以取名字,也可以不写as改成空格 - 条件查询,where后
大于、
大于等于、
大于等于且小于等于(and,between and)
日期也可以用比较符号和between and,字符串‘1990-08-01’
等于用一个等号= 不等!=
或者or
in (1,2,3)表示是否等于其中之一
null值不能用=,而是用is,is not - like模糊查询
姓马的name like ‘马 % ‘
第二个字是花name like ‘_ 花 % ‘
名字中包含德的name like ‘ % 德 % ‘ - 排序查询
order by age ASC/DESC, math asc/dsc - 分组查询
聚合函数,列为整体,纵向计算,不统计null的值参与计算!!!
count()
max()
min()
sum()
avg()
group by
男女各自的平均分,人数
having用于分组后的结果过滤
where,having区别:执行时间,能否使用聚合函数作为条件
执行顺序where 聚合函数 having - 分页查询 limit
limit 起始索引(从0开始动态计算,公式为(当前页码-1)* 每页条数),查询条数(每页显示几条)
- 基础查询
- 主键 外键
主键:非空且唯一
外键:让两个表建立连接
constrant foreign key reference
先建立主表,再建立从表 - 多表查询
- 内连接:
隐式内连接 where
显式内连接inner join 表 on 条件 - 外连接
左外连接
右外连接 - 子查询,嵌套查询
单行单列 直接用比较的 子查询放到条件里
多行单列 可以直接用in 子查询放到条件里
多行多列 子查询放到FROM后边作为虚拟表
- 内连接:
- 事务
数据库的一组操作,必须一起成功,或者有一个失败那就一起失败了
开启事务-回滚事务-提交事务
begin
rollback 会回滚到开启事务的状态- 事务的四大特征
A原子性:同时成功、失败
C一致性:事务完成,所有数据保持统一状态
I隔离性:只有提交,各个窗口才会统一
D持久性:一旦提交或者回滚,那么对数据的改变就是持久性的
- 事务的四大特征
- JDBC,java控制数据库
本质是sun公司的一套标准接口,各个数据库公司自己写自己的实现代码(也就是驱动),面向接口编程!
步骤:- 创建工程,导入jar包
- 注册驱动
Class.forName(“com.mysql.jdbc.Driver”);//加载驱动,反射机制:forName 方法是反射机制的重要组成部分,常用于动态加载类、创建实例、调用方法和访问字段。 - 获取连接:
String url = “jdbc:mysql://127.0.0.1:3306/db1?useSSL=false”;
String usrn = “root”;
String psw = “1234”;
Connection conct = DriverManager.getConnection(url, usrn, psw); - 定义SQL语句
String sql = “”; - 获取执行SQL对象
Statement stmt = conct.createStatement(); - 执行sql
int count = stmt.executeUpdate(sql);//返回受影响行数 - 处理结果
sout(count); - free资源
stmt.close();
conct.close();
Day29 2024年7月23日
- 用jdbc实现回滚:
conct.setAutoCommit(false);
conct.commit();
conct.rollback(); - 执行SQL:
executeUpdate(sql)//执行DDL,DML语句 - ResultSet
executeQuery()执行DQL语句,返回ResultSet对象
next()方法:光标下移一行,并返回是否有效行
getXXX():获取数据 - SQL注入
解决方式:PreparedStatement 对象
conct.preparedStatement(sql),返回PreparedStatement对象
对对象用setXXX
?获取对象的时候传递sql - 数据库连接池
是个容器,负责数据库连接
好处,资源重用,提升响应速度,避免链接遗漏
druid数据库连接池,可以通过文件配置例如用户,密码,连接个数
步骤:导入jar包,定义配置文件,加载配置文件,获取数据库连接池对象,获取连接Maven
- 管理和构建Java项目的工具
统一的项目结构
标准的构建流程:编译,测试,打包,发布
提供了一套依赖管理机制:第三方包(手动的话需要下载,复制,加入工作环境),可以直接在pom.xml里写 - Maven生命周期
clean
default:编译,测试,打包,安装
site:产生报告,发布站点
同一套生命周期内,运行后面的会自动执行前面的 - 常用指令
mvn clean 对项目进行清理,删除target目录下编译的内容
mvn compile 编译项目源代码
mvn test 对项目进行运行测试
mvn package 打包文件并存放到项目的target目录下,打包好的文件通常都是编译后的class文件
mvn install 在本地仓库生成仓库的安装包,可供其他项目引用,同时打包后的文件放到项目的target目录下 - 依赖项和依赖范围
MyBatis
- 持久层框架,简化JDBC开发
持久层:负责将数据保存到数据库的那一层代码。与数据库相关的代码
还有表现层和业务层
框架:半成品软件,可重用,通用 - JDBC缺点
硬编码,手动设参,手动封装结果 - MyBatis简化了JDBC大部分代码,只需要使用xml即可
- Mapper代理开发
resultMap - 增删改查
HTML
超文本、标记语言
网页:结构HTML、表现css、行为JS前端三件套
基础标签:h1,font,b,i,u,center,p,br,hr
图像视频:img(src,width,height),audio(src,controls),vidieo
px,%
连接: a target
列表:ol/ul li
表格:table, tr, td/th
布局:div,span
表单标签:form(action,method:get,post)
input:有很多种
select,textareaCSS
表现,美化JS
跨平台,面向对象脚本语言- 引入
内部、外部 - 变量
var全局,可重复定义
let局部,不可重复
const 相当于final - 数据类型
- 运算符
= = = 和 = = - 流程控制语句
- 管理和构建Java项目的工具
拿捏算法开始学习:
时间复杂度-递归调用
n-1调用1次的:n
n/2的:logn
每层调用两次:2的n次方
每层调用两次还n/2:n 二叉树遍历
复杂的,例如n/2,调用两次,每次调用还有操作:nlogn 快速排序
Day30 2024年7月24日
拿捏算法
1. 二分查找
704 二分查找
69 x的平方根、27 移除元素、11 盛最多水的容器:双指针
209 长度最小子数组:滑动窗口
1. HTTP
b/s架构:动态资源,静态资源,数据库
http超文本传输协议:基于tcp,请求-响应模型,无状态的协议(使用会话技术解决)
2. Web服务器作用:
封装HTTP协议,简化开发
可以对外提供浏览
3. Tomcat:是一个轻量级Web服务器,支持Servlet/jsp少量JavaEE规范,也叫Web容器。
4. Servlet
动态资源
导入,创建,配置,访问
servlet对象由web服务器创建,方法也由web服务器调用
加载、初始化、请求、销毁
HttpServlet:我们写的类继承它来完成doGet,doPost开发,本质是打包好的servlet,也是使用其service方法
url配置:(符合多种时,越精确优先级越高)
1. 精确匹配
2. 目录匹配/user/ *
3. 扩展名匹配( * .do)
4. 任意匹配(/,/* )
5. request,response
获取请求数据,是一个对象
获取响应数据,也是一个对象
6. 会话
会话:浏览器与服务器的建立连接,一次会话中包含多次请求和响应
会话跟踪:维护浏览器状态的方法,判断是否来自同一浏览器,并且共享数据
http无状态,每次都设置为新请求
客户端会话跟踪技术:cookie,每次请求携带cookie
服务端:session
Day31 2024年7月25日
- 拿捏算法
递归的学习:三要素:目的,结束条件,递推关系
链表:思路简单,细节易错:头节点是否被删除,next.next是否存在
203 移除链表节点
876 链表的中间节点(两种写法,基于偶数是返回中间偏前面还是偏后面)
22 链表中倒数第k个节点
19 删除链表中倒数第k个节点 - cookie
K-V格式,代码写到idea里了
实现是基于HTTP协议,请求头:cookie 响应头:set-cookie
setMaxAge(int)控制保存时间
存中文,URL编码器 - session
服务端会话跟踪
代码写到idea
session是基于cookie实现的
钝化:服务器关闭,会把数据写到硬盘
活化:服务器重启,会把数据加载进session,但是地址会改变了
销毁:默认30分钟内自动销毁或者session.invalidate()
Spring
- IOC容器
IOC是宏观目标,设计理念
DI依赖注入是具体实现,微观实现,Java中利用反射技术实现对象注入
对象-代理人-消费者
xml配置即可 - AOP面向切面编程
Springboot
开箱即用,约定大于配置
URL传参
web三层结构:controller,sevice,DAO
配置文件properties,yml
开始做电商项目
Day32 2024年7月26日
拿捏算法:
5道题
继续电商+支付
Day33 2024年11月6日
12月23日第一段实习结束,现在开始继续准备力扣、八股、项目,谋划寒假实习
开始看MySQL八股
1. MySQL数据库结构:
分为server和存储引擎,存储引擎默认InnoDB
server层:
1. 连接器
1. mysql -hip -Pport -u$user -p
2. mysql是客户端工具,权限更新不会影响已有连接,时间过长无操作自动断开
3. 连接复杂 所以尽量使用长连接 导致内存占用疯涨(因为临时内存管理在连接对象里,需要断开才会释放,如果内存占用过大会被系统杀死,出现 “异常重启”)
解决方案:1. 定期或者处理完大操作重启 2. 5.7或更高版本使用mysql_reset_connection初始化连接
2. 查询缓存
效率高但如果更新表会清除缓存,所以不建议使用
可以通过query_cache_type 设置成0关闭,1开启来按需使用,2表示只要select 中明确指定SQL_CACHE才缓存
MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了
3. 分析器 — 要做什么
词法分析:识别每个单词的含义
语法分析:分析是否语法错误,要干什么
4. 优化器 — 该怎么做
优化索引、查找等等
5. 执行器
判断是否有表格权限,没有则deny,不然就逐行判断并返回查询
查询缓存返回结果的时候,也有权限验证
(重叠在一起的日期表示都在完成下面的学习这是为了不打乱整理的知识)
Day34 2024年11月19日
Day35 2024年11月20日
Day36 2024年11月22日
索引底层
什么是MySQL索引?
为了提高查询数据效率,就像书本目录一样。
实现方式:
1. 哈希表:算哈希值-存数据-如果地址冲突就往后接着,拉链表。优点:新增快 缺点:区间查询必须全部扫一遍 应用场景:等值查询,nosql引擎,memcached
2. 有序数组:优点:等值查询或者范围查询都可以,二分法,logn 缺点:新增太麻烦,得一个一个挪 应用场景:只适用于静态存储引擎
3. 二叉搜索树: 查找logn 更新logn
缺点:索引要上磁盘,二叉太少了不够,使用 N叉树
索引在存储引擎实现
Mysql索引为什么用B+树?相关知识
1. SQL查询较慢,可以看索引以及建立索引,索引一般用B+树,时间大概是log(n),hash存储索引平均时间就是o1
2. 二叉排序,左边比根小,右边比根大,左右子树都二叉排序,中序遍历是从小到大。但是如果插入序列有序,那么就会退化成链表。而平衡树(红黑树是其中一种)就是为了解决这个问题,保证树的平衡性。平衡性是为了降低树的高度,提高查找效率,Java-TreeSet就是底层就是红黑树。
3. 什么是B树?B树的阶(路数)就是拥有的最大子节点的个数,每个子节点数=节点键的个数+1。因此不限制路数,则会导致退化成有序数组,插入删除,查找会越来越慢。B树用来处理文件或者数据库索引,因为它们是存在硬盘上,如果数据量大,不一定能一次加载到内存。所以每次加载B树一个节点,一步一步往下找。例如如果只能一次性加载2个数,那么我们令键值=2,节点=阶数=3即可。
4. 内存中,红黑树优于B树,但是磁盘操作中B数更优。
5. 什么是B+树?1每个中间节点键和子树个数一样(b树差1) 2就是B树的叶子结点之间加指针形成有序链表,并且所有的数据都在叶子节点 3所有中间节点的元素都存在于子节点,且在子节点元素中是最大或者最小。
6. B+树的优点是如果需要查找多条数据,那么B树需要局部中序遍历,可能夸层访问,而B+树由于所有数据都在叶子,只需要找到首尾就可通过链表取出数据。
7. 为什么不用hash表?之所以索引底层用B+树,是因为数据库经常会选多条数据,这时候由于B+索引有序,并且链表相连,只需快速查到首尾,便可获得所有数据。而hash单条确实快。此外数据库索引一般在磁盘,数据量大无法一次装入内存,B+能数据分批加载,同时树高度低,效率高。
索引的模型?
有主键索引和非主键索引,非主键索引将查到主键再到主键索引里面去查找,这叫回表,因此需要多扫描一个索引树,尽量使用主键查询。
索引维护?
1. 尽量用自增主键做索引,原因是:
1. 性能:每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂
2. 存储:主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。
2. 什么时候可以不用自增主键呢:只有一个索引;该索引必须是唯一索引,这就是典型的 KV 场景。由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。
什么是回表?如何避免或者优化?
1. 普通索引树会有回表过程,例如
select * from T where k between 3 and 5是先在k索引树搜索3再去主键树搜索所有数据,因为只有主键树才有完整数据
2. 解决方案:
覆盖索引:将k索引树的查询结果覆盖我们的查询需求,例如ID主键select ID from T where k between 3 and 5。好处:可以减少树的搜索次数,显著提升查询性能。
例如:根据身份证号查询市民信息的需求,我们只要在身份证号字段上建立索引就够了。而再建立一个(身份证号、姓名)的联合索引,是不是浪费空间?如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间。但是,索引字段的维护总是有代价的,所以需要权衡。
3. 最左前缀原则
(a,b)的联合分布,是根据a来排序的,因此相当于有了a的索引。
在建立联合索引的时候,如何安排索引内的字段顺序?
1. 第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。此外有空间原则,例如name空间肯定大于age,那么我们建立(name,age)和age肯定优于(age,name)和(name)
4. MySQL 5.6 引入了索引下推
可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。例如联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是 10 岁的所有男孩”
没有索引下推就先搜名字,再回表搜所有信息并比对age
但索引下推会利用联合索引里age的信息,提前判断age是否符合
什么是B+树?
本质是多叉查找树
1. 1每个中间节点键和子树个数一样(b树差1) 2就是B树的叶子结点之间加指针形成有序链表,并且所有的数据都在叶子节点 3所有中间节点的元素都存在于子节点,且在子节点元素中是最大或者最小。
2. 根节点的最大元素也是整个B+树的最大元素
3. 什么是卫星数据?就是B树和B+树中某个节点中的数据,当然B+只有叶子节点才有卫星数据
需要补充的是,在数据库的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。
B+树有什么优点?
单元素查询:
1. 中间节点没卫星数据,所以磁盘可容纳更多节点元素,更加矮胖,IO更少
2. B树由于有时在中间节点,有时在叶子节点,因此不稳定,而B+稳定叶子
范围查询:例如3-11
1. B树:先自顶向下找到3,再中序遍历到11,很繁琐
2. B+树:自定向下找到3,再链表到11
综上,3大优点:IO次数少,查询性能稳定,范围查询更快
为什么用B+或者B而不用二叉查找树或者hash?
1. 文件索引放在磁盘,不仅要考虑查找效率,还要考虑磁盘的寻址加载次数哦(或IO次数)
补充:在把磁盘里的数据加载到内存中的时候,是以页为单位来加载的,而我们也知道,节点与节点之间的数据是不连续的,所以不同的节点,很有可能分布在不同的磁盘页中。在内存的运算速度是非常快的,至少比磁盘的寻址加载速度,快了几百倍。矮胖最优
B+树是怎么分裂的?
B+ 树分裂时是从叶子节点开始的,接着是其父节点,一层一层的往上分裂,直到每个节点的子节点数不超过 B+ 树的阶数为止。
其中每个结点在分裂时,都是选取节点的中间大小元素,将这个元素向上移至父节点,然后检查父节点所在的节点数是否超过 B+ 树的阶数,不超过则结束,超过则继续重复这个过程。
索引优化
- 什么是索引区分度?
count(distinct 具体的列) / count(*)不同值的个数/所有的个数 -
字段加索引,你是否在自己的项目中用过呢?
比如支付模块的支付信息表,包含有订单号和支付状态两个字段。由于经常需要通过订单号去查它的支付状态,可以单独给订单号创建一个普通索引,但考虑到每次只是由订单号查支付状态这一个字段,可以创建一个订单号和支付状态的联合索引,也就是覆盖索引,这样就省去了使用普通索引的回表操作,提高查询效率。 -
什么样的字段适合加索引?
第一个是要考虑索引的区分度,尽量选择区分度高的字段作为索引。第二个是需要考虑这个字段的查询频率。对于频繁增删改的字段,可以创建索引来加快查询速度。第三个是要考虑字段的长度,过长会占用空间,过短可能会导致索引区分度降低。比如身份证号只取前 6 位这种情况。 -
mysql怎么创建索引?
对于普通索引和联合索引,都是CREATE INDEX 索引名 ON 表名(列名, ...)
对于唯一索引,多了一个UNIQUE关键字,CREATE UNIQUE INDEX
如果想删除索引的话,是DROP INDEX 索引名 ON 表名 -
字段加了索引,查找的时候一定会走索引吗?
不一定会走索引(或即使走索引也不一定会走最佳的索引)。可能会因为人为没有写好 SQL 导致索引失效。比如在 SQL 中使用函数,LIKE 查询并且把通配符 % 放在开头,使用联合索引但没有遵循最左匹配原则等等,都是会导致索引失效的。 -
如果排除人为的情况,sql 正确书写,那就一定会走索引吗?(PS:走不走索引,是经过优化器权衡预测的,所以这里需要回答系统是如何预测的)
也不一定,因为走不走索引,走哪个索引都是由优化器权衡预测的。优化器会选择一个最优的执行方案,用最小的代价去执行语句。其中扫描行数是影响执行代价的因素之一,它是由 MySQL 进行采样统计得到的估算值,有时候误差可能很大,导致优化器认为执行的代价也很大,进而就不会走最佳的索引。除了扫描行数,是否使用到了临时表,是否排序等因素也会被考虑在内。 -
如果我想要强制走某个索引,能实现吗?
可以,最简单的办法就是使用 force index 语句,这个建立在我们确定某个索引一定是最佳的,但系统会走错这种情况。缺点是变更的及时性差,等到出问题时再加上 force index,再测试和发布,整个过程不够敏捷。
其次就是在数据库内部解决,考虑根据情况修改语句,引导 MySQL 使用我们期望的索引。
还有就是新建一个更合适的索引,来提供给优化器做选择,或删除走错的索引。 -
一条 sql 执行的很慢,我们可以怎么来排查原因?
偶尔很慢,可能SQL本身没问题:- 数据库在刷新脏页:例如 redo log 写满了需要同步到磁盘(详细展开说:数据库插入一条数据、或者要更新一条数据的时候,数据库会在内存中把对应字段的数据更新了,但是更新之后,这些更新的字段并不会马上同步持久化到磁盘中去,而是把这些更新的记录写入到 redo log 日记中去,等到空闲的时候,在通过 redo log 里的日记把最新的数据同步到磁盘中去。不过,redo log 里的容量是有限的,若更新又很频繁,这个时候 redo log 很快就会被写满了,这个时候就只能暂停其他操作,全身心来把数据同步到磁盘中去的,而这个时候,就会导致我们平时正常的SQL语句突然执行的很慢
- 拿不到锁:这条语句涉及到的表,别人在用,并且加锁了,我们拿不到锁,只能慢慢等待别人释放锁了。或者,表没有加锁,但要使用到的某个一行被加锁了
要判断是否真的在等待锁,我们可以用 show processlist这个命令来查看当前的状态show full processlist
一直很慢: - 没有索引。
- 有索引但没有用到索引:例如字段左边涉及了运算,或者字段进行了函数操作。
- 系统自己选错索引:系统会自己判断走不走索引,通过索引区分度来判断,一个索引的基数(就是区分度)越大,意味着走索引查询越有优势。系统通过采样的方式,来预测索引的基数的,所以有可能失误。也可以强制:
select * from t force index(a) where c < 100 and c < 100000;
查询索引的基数和实际是否符合:show index from t;
重新统计索引基数:analyze table t;
既然会预测错索引的基数,这也意味着,当我们的查询语句有多个索引的时候,系统有可能也会选错索引
- 为什么使用模糊匹配会失效,你能给我解释一下底层原理吗?
这种情况一般发生在使用 LIKE 关键字时,将通配符 % 放在了开头,导致索引用不上,只能进行全表扫描,效率很低。
原理是 InnoDB 索引底层是 B+ 树,对于字符串是遵循最左前缀原则,字母从左向右顺序排序和匹配,如果通配符在最前面会导致无法根据B+树查找,只能全部扫描
JVM-垃圾回收基本盘
为什么要有垃圾回收?
在传统的 C/C++ 语言中,开发人员需要手动分配和释放内存,这样就容易导致人为的忘记释放内存的情况,造成内存泄漏(说人话就是有个没有用处的垃圾一直占着空间,系统又不能把这空间分给别人)。Java 的垃圾回收机制可以把帮忙解决这个问题,它可以自动检测并回收不再使用的对象,也就是垃圾,可以减少内存泄漏的风险。JVM垃圾回收线程
什么时候触发垃圾回收?哪些能回收?哪些不能?
1. 系统运行创建的对象都是优先分配在新生代里的,然后如果新生代里的对象越来越多,都快满了,此时就会触发垃圾回收,把新生代没有人引用的对象给回收掉,释放内存空间。
2. 有两种方法,第一种-可达性分析法:
从gc roots出发看对象可不可达,局部变量、静态变量可以作为gc roots
有哪些可以作为GC root? 当前活跃的栈帧指向堆里的对象引用(方法的局部变量)、类的静态变量,Java 本地方法所引用的对象都可以是 GC Roots,类的实例变量不是GC Roots
第二种-引用计数法:引用计数法的思路很简单,当对象被引用时给计数器 + 1,当对象引用失效时计数器值 – 1。当计数器为零时,说明对象不再被使用,可以回收。缺点:如果对象存在循环依赖,那就无法定位该对象是否改被回收,比如 A 引用 B,B 引用 A 这种情况
Java中的引用类型有哪些?
1. 引用和垃圾回收的关系,Java里有不同的引用类型,分别是强引用、软引用、弱引用和虚引用。
2. 强引用,就是最普通的代码,一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。
3. 接着是软引用,就是把“ReplicaManager”实例对象用一个“SoftReference”软引用类型的对象给包裹起来了,此时这个“replicaManager”变量对“ReplicaManager”对象的引用就是软引用了。软引用就是说有的对象可有可无,如果内存实在不够了,可以回收他。
4. 接着是弱引用,类似下面的代码。这个其实非常好解释,你这个弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉。
其实这里比较常用的,就是强引用和软引用
finalize()方法可以在最后让对象自己引用自己
什么是垃圾回收算法?有什么问题,如何优化?
标记清除算法 –优化– 标记复制法 –优化– 标记整理法(Eden survivor)
标记清楚法:最简单,标记+清除
标记复制法:为了避免内存碎片化,避免内存浪费。把内存分两块,每次只用一块,每次回收时,先标记,再把不能回收的复制到另一半,再把需要回收的清除。
缺点:每次只能用一半空间
优化:标记整理法:Eden区 和 Survivor区
Eden 80% survivor 10%+10%,平时用的就是1个Eden和survivor,占90%。每次回收,都是把Eden和1个survivor的转移到另一个空着的survivor,然后清空回收。
垃圾回收的位置?
垃圾回收主要是发生在堆上,尤其是在新生代中,一次垃圾回收通常可以回收 70% 到 99% 的内存空间。但其实也会对方法区进行垃圾回收。对方法区主要回收的是废弃的常量和不再使用的类,一般回收的空间比较少。
年轻代与老年代连环炮
什么是年轻代?什么是老年代?他们在哪里?区分有什么作用?
年轻代:创建和使用完之后立马就要回收的对象放在里面
老年代:创建之后需要一直长期存在的对象放在里面
二者都是对象,因此在堆结构里
区分的作用是他们需要不同的垃圾回收算法。主要是有两个原因,一个是大部分对象的生命周期都很短,只有少部分对象可能会存活很长时间。另一个是垃圾回收会导致 stop the world,也就是应用会暂时停止访问。为了使 stop the world 持续的时间尽可能短以提高并发式 GC 所能应付的内存分配速率,有些垃圾收集器就将对象分成两类,存活时间短的对象所处的区域叫年轻代,存活时间长的对象叫老年代。JDK 8 及以前的垃圾收集器都是有这个分代概念的。
哪些对象会进入老年代?
1. 一般有两种情况,第一种是先创建的对象太大了,就会直接进入老年代,另一种是对象的年龄太老了,每发生一次 Minor GC,存活的年龄就 +1,达到默认值 15 就会自动晋升成老年代
2. 动态年龄判定规则,某次minor gc后,存活对象超过了S区的一半,就会自动判断,比如年龄为1岁,2岁,3岁,4岁的对象的大小总和超过了Survivor的50%,此时4岁以上的对象全部会进入老年代
什么时候年轻代GC?
一般在年轻代的 Eden 区空间不足时,就会触发年轻代 GC,也就是 Minor GC。
什么时候老年代GC?
1. minor gc前,如果老年代可用内存小于以往 minor gc 的平均大小,那么直接触发来腾空间
2. minor gc后,如果已经没有空间给需要晋升老年代的对象了,那么也腾一次空间
3. 如果老年代内存使用率超92%,直接old gc
总的来说就是老年代空间不够了那就GC
垃圾回收器
常用的垃圾回收器有哪些?
CMS,G1,还有比较早的 Serial,Serial Old,Parallel New,Parallel Old
CMS垃圾回收器介绍一下
CMS垃圾回收器通常用于老年代,解决STW系统卡死,采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理的。
四个阶段:
1. 初始标记:STW标记直接被GC roots引用的对象,注意:没有实例对象
2. 并发标记:没有STW,系统程序同时工作,最耗时,需要不停追踪每个对象是不是根源被GC roots引用,是否需要回收
3. 重新标记:2阶段结束,由于2阶段一边标记一边创建或者垃圾化,肯定有很多没被标记的存活和垃圾,那么3阶段再次STW重新更新标记(速度很快,只需要更新2阶段遗漏的即可)
4. 并发清理:没有STW,系统正常运行,GC来清理垃圾即可,较耗时,可能有浮动垃圾。
优点是将耗时的阶段全部取消STW从而不影响系统性能!
为什么老年代的垃圾回收速度会比新生代的垃圾回收速度慢很多倍?到底慢在哪里?
1. 新生代存活少,不需要追踪很多对象,存活放入S区即可
2. 老年代存活多,追踪量大,而且清理也是清理零零散散区域的垃圾
3. 老年代最后还要处理内存碎片,还得STW,更慢了
讲讲G1垃圾回收器?
支持同时回收新生和老年代,最大的特点是把堆划分成多个大小相等的Region,我们可以设置垃圾回收的预期停顿时间,例如1小时内的STW不超过1分钟。G1会判断回收的价值,例如需要多久能回收?回收的垃圾有多大?
1. 在G1对应的内存模型中,新生代和老年代各自的内存区域是不停的变动的,由G1自动控制,但是一般新生代不超过60%
2. 新生代依然有E区和S区,回收依然要STW,也是标记整理法,回收时间也是E区满了
3. G1的新生代怎么就能变老年?
1. 对象在新生代躲过了很多次的垃圾回收,达到了一定的年龄了
2. 动态年龄判定规则
大对象不能直接变老了,而是有大对象区域,也是不固定的,空的就能放呗
4. 详细说说新生代和老年代的回收?
RSet 是 G1 回收器中用来解决垮代引用问题的一块存储空间。每个 Region 都会有一小块区域作为 Rset,记录着其它 Region 引用了当前 Region 的对象关系。对于年轻代的 Region,它的 RSet 只保存了来自老年代的引用。对于老年代的 Region,它的 RSet 也只会保存老年代对它的引用。
1. 新生代:扫描GC roots直接关联的对象,然后标记老年代里被引用的对象(也不能被删除),复制对象并清除对象
2. 老年代,先初始标记,然后并发标记全部的存活,然后重新标记遗漏的,最后清除(与cms不同的是这里也是要STW的),主要回收年轻代和部分回收高价值的老年代区域。
CMS和G1的区别?
3个方面:1.内存结构 2. 具体方法 3. 对象代不同
java代码是如何运行起来的?
.java -java文件打包编译字节码文件- .class -类加载器加载到JVM -JVM字节码执行引擎 – main()开始
说说JVM的类加载机制?加载-验证-准备-解析-初始化-使用-卸载
1. 什么时候加载类?main所在的类直接加载,其他的要用就加载
2. 验证、准备、解析:
验证就是看.class是不是符合JVM规范,也就是能不能跑。
准备就是给类和类变量分空间,赋初值
解析就是把符号引用换成直接引用,涉及JVM底层
3. 初始化:完成类变量赋值代码的运行,包括静态代码块的执行
什么时候初始化?比如“new ReplicaManager()”来实例化类的对象,如果父类还没初始化,那么必须先初始化他的父类
类加载器你知道的有哪些?
启动类加载器,用于加载JDK里的核心类库 \lib
扩展类加载器,加载JDK里的扩展类库 \lib\ext
应用程序类加载器,加载我们自己写的类到内存里,本质是加载ClassPath环境变量所指定路径中的类
自定义类加载器,常用的例如商业级加密字节码文件,防反编译窃取代码
你知道类加载器有双亲委派机制吗?
知道,就是子加载器会先让父加载器去加载需要加载的类,如果没找到,那就自己加载。这是为了防止多层的加载器重复加载了。
而父子结构是:父:启动 – 扩展 – 应用程序 – 自定义
JVM内存空间讲一讲?
Day37 2025年1月16日
开始进行仿牛客网项目
1. 技术架构:
– Springboot
– SSM 即 Spring/Spring MVC/MyBatis
– Redis是Nosql数据库操作内存、Kafka:最好的消息队列服务器、Elasticsearch:最好的搜索引擎
– Spring Security权限、Spring Actuator监控
2. 开发环境:
– 构建工具:Apache Maven
– 集成开发工具:IntelliJ IDEA
– 数据库:Mysql Redis
– 应用服务器:Apache Tomcat
– 版本控制工具:Git
3. Apache Maven
可以构建项目、管理jar包
Maven仓库:存放构件的位置:本地:~/.m2/repository 远程仓库:中央、镜像、私服 优先看本地有没有
4. Spring Boot
起步依赖、自动配置、端点监控
5. Spring
1. Spring Framework:
1. Spring Core:IoC控制反转,依赖注入 AOP面向切面编程 @Autoweired
2. Spring Data Access:Transactions/Spring MyBatis
3. Web Servlet: Spring MVC
4. Integration: Email/Scheduling/AMQP/Security
2.Spring Boot
3.Spring Cloud 微服务
4.Spring Cloud Data Flow 微服务数据连接
6. Spring MVC
1. HTTP协议:超文本传输协议,用来传输HTML等内容的应用层协议,规定了浏览器和服务器之间如何通信以及通信的数据格式.
规定的浏览器和服务器通信步骤:
1. 建立一个TCP连接
2. 浏览器发送一个HTTP报文:包括 方法,路径,http版本,头,数据表
3. 服务器返回报文:包括 版本,状态码,状态消息,头,响应体
4. 关闭资源
2. Spring MVC:
1. 服务端的三层架构: 表现层,业务层,数据访问层,浏览器请求服务端会先到表现层,然后表现层去业务层,业务层再去数据层找数据,找完返回给表现层,表现层再给服务端给浏览器。
2. 而 MVC 说的是表现层里的三层设计模式:Model,View,Controller三个组件
3. 通常浏览器直接访问的是Controller,Controller将业务层获得的数据封装到 model,传给View层,生成html返回给浏览器。
4. 服务端有个核心组件叫:前端控制器,DispatcherServlet,负责管理 MVC 的组件。逻辑如下:浏览器请求会直接给到前端控制器DispatcherServlet,它通过路由找到对应Controller,controller获得数据通过model封装后返回给前端控制器,它给View层,然后view生成视图后返回前端控制器,最后前端控制器响应给浏览器。因此这么来看,前端控制器是调度controller/view的,而model只是作为一个工具。
3. Thymeleaf
一个模板引擎,生成动态HTML文件,以html为模板(自然模板)
模板文件+model 通过模版引擎成为 html
代码基础知识:controller层:RequestMapping:path, method
Get RequestParam:name,required,defaultValue/PathVariable
Post
响应:@ResponseBody
html数据 ModelAndView:mav.addObject setViewName(path)
json 数据:作用是实现Java后端和JS前端兼容
7.MyBatis
核心组件:
1. SqlSessionFactory
2. SqlSession
3. 主配置文件-xml
4. Mapper接口,就是DAO接口,mybatis习惯说mapper
5. Mapper映射器,编写sql并将SQL和实体类映射的文件。xml
代码:
一般写select,需要加resultType
写insert,需要加paramType
写update,不用加
Day38 2025年1月19日
算法题:
1. 相交链表:注意移动tmp1 = tmp1 null ? headB : tmp1.next;
而不是tmp1 = tmp1.next null ? headB : tmp1.next;就算不相交,他们也一定会同时变成空!
2. 反转链表:用递归!!!记住就可以
3. 回文链表:思路,找到中点,反转后半,再对比
Day39 2025年1月20日
发送邮件:
邮箱设置:启用客户端SMTP服务
Spring Email:导入jar包,配置邮箱参数
算法题:
4. 两数相加(链表):思路是,维护一个carry变量表示当前位的进位,如果两数不对齐,高位可以补0!
Day40 2025年1月22日
cookie:存在客户端,信息量少,安全性低
session:本质还是cookie,但是存在服务端,安全但增加了服务器压力,并且多台服务器分布式会导致问题。
解决方案:黏性session、同步session、共享session、数据库集群(session存到Redis里)
nginx 代理,负载均衡。
Day41 2025年1月24日
过滤敏感词:前缀树:特点是查找效率高,消耗内存大
应用:字符串检索,词频统计,字符串排序
根节点是空,每个节点是一个字符,每一层都是一位字符(例如多个单词的第一个字符都放在第一层),当然每层内,字符不能重复
AJAX:异步的js和xml,能够将增量更新在页面,而不需要刷新整个页面
Day42 2025年2月9日
kafka 分布式流媒体平台:消息系统-性能最好的消息队列
高吞吐量、消息持久化、高可靠、高扩展
broker zookeeper
topic partition offset
leader replica, follower replica
Day43 2025年2月10日
Elasticsearch 性能最好的专业搜索引擎
分布式、restful风格搜索引擎
支持各种数据类型
搜索速度快、可以提供实时搜索服务
便于水平扩展,每秒处理PB级海量数据
对比mysql
索引(database)、类型(table)、文档(row)、字段(column)
7.0废弃掉了类型,因此索引对应table
集群、节点、分片、副本
HyperLogLog
采用一种基数算法,完成独立总数的统计
占空间小
不精确统计算法,误差0.81%
Bitmap
不是一种独立的数据结构,实际上就是字符串
支持安位存取数据,可以看成 byte 数组
适合存储大量连续数据的布尔值
线程池:
JDK线程池
– ExecutorService
– ScheduledExecutorService
Spring线程池
– ThreadPoolTaskExecutor
– TheadPoolTaskScheduler
分布式定时任务
– Spring Quartz
优化网站性能
本地缓存:
– 数据存在应用服务器,性能最好
– 常用缓存工具:Ehcache, Guava, Caffeine
分布式缓存:
– 将数据缓存在NoSql数据库上,跨服务器
– 常用缓存工具:Redis, MemCache
多级缓存:
– 一级缓存 – 二级缓存 DB
– 避免缓存雪崩(缓存失效,大量请求直达DB),提高系统可用性