高频知识点总结 - 01
1.关于i++和++i
1.1 对字节码的分析
i = i++ =》 先将i取出到操作数栈中,然后让i在局部变量表中自增,自增完以后,将操作数栈中的i赋值给i,所以总体来说,i是自增了的,但是在操作数栈中的i是没有变化的,在后面赋值的时候,i又变回了原值
i = ++i =》 是先自增后再将i取出到操作数栈中,最后赋值,所以i最后的值是变化的。
1.2 实例
public class i_increase {
public static void main(String[] args) {
int i = 1;
i = i++; // i = 1
int j = i++; // j = 1
int k = i + ++i * i++; // i = 2 => 3 ==> 2 + 3 * 3 = 11
System.out.println("i:"+ i); // i = 4,上面的i++执行
System.out.println("j:"+ j);
System.out.println("k:"+ k);
}
}
// 结果
i:4
j:1
k:11
2.关于类的初始化
在实例化子类对象会先加载父类的静态变量,然后加载子类的静态变量,接着会加载父类的非静态变量,然后静态代码块,最后无参构造。
在 JVM 中子类的初始化一定会先进行父类的初始化。
- super()
- 非静态变量,从上到下执行
- 无参构造
2.1 实例
/*
* 分析
* - 在Son的main方法加载的时候就会初始化静态变量,根据从上到下的顺序
* - 先父类
* - 再子类 ==》 (5)(1)(10)(6)
* - 在实例化对象的时候的指向顺序
* - super() (父类同样以下面的方法执行)
* - 非静态变量,从上到下的顺序执行
* - i = test() (要考虑到多态,实例化的是子类,则会调用子类的方法)
* - 非静态代码块
* - 无参构造 (最后) ==》(9)(3)(2)(9)(8)(7)
* */
public class Father {
private int i = test();
private static int j = method();
static {
System.out.println("(1)");
}
Father() {
System.out.println("(2)");
}
{
System.out.println("(3)");
}
public int test(){
System.out.println("(4)");
return 1;
}
public static int method(){
System.out.println("(5)");
return 1;
}
}
public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.println("(6)");
}
Son(){
System.out.println("(7)");
}
{
System.out.println("(8)");
}
public int test(){
System.out.println("(9)");
return 1;
}
public static int method(){
System.out.println("(10)");
return 1;
}
public static void main(String[] args) {
System.out.println("==初始化的数字==");
Son s1 = new Son();
System.out.println();
Son s2 = new Son();
}
}
// 结果:
(5)(1)(10)(6)
====
(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
3.方法的参数传递机制 - 值传递
3.1 实参是基本数据类型
传递数据值
3.2 实参是引用数据类型
传递地址
特殊的类型:String、包装类等对象如果更改,不会下在原有地址上更改,而是会创建一个新的对象。
3.3 实例
/*
* 分析
* - 基本数据类型,是直接的值传递 ==》 i = 1
* - 引用数据类型是地址传递
* - String和包装类等特殊对象,不会发生实参改变
* - String的引用类型会在常量池中新创建一个,形参的地址改变了,但是实参的不会
* - 数组引用是地址传递,会将数组中的值改变,因为实参引用的地址没有改变,所以也会导致实参的引用改变
* - 类的引用和数组是同一个道理,引用对象中的值改变了,实参的引用地址没有改变则也会导致实参的引用值改变
* 总结
* - 基本数据类型会直接传递值,而引用数据类型会传递地址(类、接口、数组)
* - 在形参中如String和包装类等如果改变的值不是在原有地址上的改变而是重新创建一个
* 则会因为实参的引用地址没有改变而导致实参不会发生变化,而其他引用类型,如类、接口、数组
* 在传递地址的后,形参是在原地址上进行改变,则会导致实参的引用的值发生改变
* */
public class ParameterPass {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 200;
int[] arr = {1,2,3,4,5};
MyData myData = new MyData();
change(i,str,num,arr,myData);
System.out.println(i); // i = 1
System.out.println(str); // hello
System.out.println(num); // 200
System.out.println(Arrays.toString(arr));
System.out.println(myData.a);
}
public static void change(int j, String s, Integer n, int[] arr, MyData m){
j += 1;
s += "world"; // 在堆中新建了一个string对象,方法中的引用地址改变
arr[0] += 1; // 传递数组[0]的地址,地址上的值发生了改变,引用没变
m.a += 1; // 传递的是引用地址,对象类中的值发生了改变,没有创建新的对象
}
}
class MyData{
int a = 10;
}
// 结果
1
hello
200
[2, 2, 3, 4, 5]
11
4.成员变量和局部变量
就近原则:在没有具体指定变量的属于时,采用就近原则
4.1 变量的分类
-
成员变量:类变量(静态变量)、实例变量
-
局部变量(作用域)
非静态代码块的执行:每一次对象的实例化都会执行一次
方法的调用:调用一次执行一次
4.2 堆、栈、方法区
4.2.1 堆
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配。在Java虚拟机规范中的描述是:所有对象实例以及数组都要在堆上分配。
4.2.2 栈
通常说的栈,是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用类型(reference类型,它不等同与对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
4.2.3 方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
4.3 生命周期
4.3.1 局部变量
每一个线程,每一次执行调用都是新的生命周期
4.3.2 实例变量
随着对象的创建而初始化,随着对象的被回收而消亡,每一个实例对象都有自己的实例变量且独立
4.3.3 类变量(静态变量)
随着类的初始化而初始化,随着类的卸载而消亡,该类的所有实例化对象,共享类变量。
4.4 实例
/*
* 分析
* - 在类的初始化的时候就会初始s ==》 s = 0
* - 在实例化对象的时候会执行非静态代码块
* - 非静态代码块中的是局部变量且它的作用域为这个代码块中,
* - 在同名的情况下如果没有指明具体的变量名,具有就近原则 ==》 在非静态代码块中的 i = 2
* 没有同名的则会找成员变量 ==》 j = 1 s = 1 这个地方实例化了两次静态变量是共享的 ==》 s = 2
* - 执行具体的方法
* - 同理在同名的情况下没有指明变量属性则应用就近原则 ==》局部变量:j = 11 成员变量:i = 1 静态变量(类变量):s = 3
* 这里的v1对象执行了两次,第二次时 ==》局部变量:j = 21 成员变量:i = 2 静态变量(类变量):s = 4
* - v2又是一个新的实例对象,成员变量不共享,静态变量共享 ==》局部变量:j = 31 成员变量:i = 1 静态变量(类变量):s = 5
*
* 注:
* - 如果想消除就近原则的影响则可以
* - 如果是本类中,可以加this.来区分
* - 内部类中可以加类名.来区分
* - 局部变量只能有final来修饰
*
* 输出结果
* 2,1,5
* 1,1,5
* */
public class Variables {
static int s;
int i;
int j;
{
int i = 1;
i++; // 就近原则,局部变量中的i++
j++;
s++;
}
public void test(int j){
j++; // 就近原则,局部变量中的j++
i++;
s++;
}
public static void main(String[] args) {
Variables v1 = new Variables();
Variables v2 = new Variables();
v1.test(10);
v1.test(20);
v2.test(30);
System.out.println(v1.i+","+v1.j+","+v1.s);
System.out.println(v2.i+","+v2.j+","+v2.s);
}
}
5.Spring Bean的作用域
可以在配置文件中设置作用域(scope),也可以在注解中添加作用域 @Component(“SingletonBean”)注解是告诉Spring这是一个bean。@Scope(“prototype”) 注解是告诉 Spring 该 bean 的作用域是 prototype。
5.1 区别
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,是bean作用域范围的默认值。在容器创建的时候会自动创建一个Bean实例 |
prototype | 在需要的时候才会创建一个bean实例(getbean),并且每次调用创建的都是不同的实例对象 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
application | 限定一个Bean的作用域为ServletContext 的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
6.事务的传播行为
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,被调用的事务方法应该如何进行。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
6.1 Spring 定义了七种传播行为
常用的为前面两种,第一种被调用的会继续在当前的事务中执行,第二种则会先将当前事务进行挂起,然后自己开启自己的新事务,自己的事务执行完毕以后则会继续执行调用者的事务。因为事务的原子性如果是第一种因为在当前的事务中执行的,这个过程中的执行有一个不成功都会回滚,但如果是第二种,被调用者自己开启的事务,则不会影响整体。
7.数据库事务并发问题
7.1 脏读(更新但没有提交)
当前事务读到了其他事务已经更新但还没有提交的值,其他事务可能各种原因最后导致了没有提交成功,那么当前事务读到的值就是无效的值,就是脏读
7.2 不可重复读(更新且提交)
一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现)
事务1第一次读取到的某个值为20,事务2修改了这个值为30,事务1第二次读的时候值为30,和第一次读的不一致,不可重复读,没有重复。
7.3 幻读(更新且提交)
一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)
第一次读取只有一个值,其他事务增加了值,第二次读的时候,发现有多个值,这就是幻读。
8.事务的隔离级别
-
MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。
-
MySQL的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。
-
隔离级别比较:可串行化>可重复读>读已提交>读未提交
-
隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交
由此看出,隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL默认的隔离级别也是可重复读。
8.1 读未提交(READ UNCOMMITTED)
在读未提交隔离级别下,事务A可以读取到事务B修改过但未提交的数据。
可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别。什么问题都没有解决。
8.2 读已提交(READ COMMITTED)
在读已提交隔离级别下,事务B只能在事务A修改过并且已提交后才能读取到事务B修改的数据。
读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别。
8.3 可重复读(REPEATABLE READ)
在可重复读隔离级别下,事务B只能在事务A修改过数据并提交后,自己也提交事务后,才能读取到事务B修改的数据。
可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。MVCC 方式下可以解决幻读问题。
1.提问:为什么上了写锁(写操作),别的事务还可以读操作?
因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。
8.4 可串行化(SERIALIZABLE)
各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)。
8.5 四种隔离级别比较
9.GIT分支相关命令
# 切换分支,没有则会创建
git checkout -b <分支名>
# 合并分支
#先切换到主分支
git checkout master -> git merge <分支名>
# 删除分支
#先切换到主分支
git checkout master -> git branch -D <分支名>
9.1 Git工作流
1.注
开发在develop下创建新的分支开发,开发完成以后并入develop中,测试通过并入主分支上线,然后再将通过的并入到develop中。
主分支遇到问题,复制分支,解决以后在并入主分支
10.MySQL索引优化
10.1 索引
它可以简单理解为排好序的快速查找数据结构
10.2 优势
提高数据检索的效率,降低数据库的IO成本。
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
10.3 劣势
虽然索引大大提高了查询速度,但却会降低更新表的速度,因为更新表的时候,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段。
10.4 什么时候建索引?
-
主键自动建立唯一索引
-
频繁作为查询条件的字段应该创建索引
-
查询中与其他表关联的字段,外键关系建立索引
-
单键/组合索引的选择问题,组合索引性价比较高
-
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
-
查询中统计或者分组字段
10.5 哪些情况不要创建索引?
-
表记录太少
-
经常增删改的表或字段
-
Where条件里用不到的字段不创建索引
-
过滤性不好的不适合创建索引,具有模糊意思的字段