总结

基础

  • 修饰符范围
修饰符 范围
private 只允许本类访问
缺省(default) 允许本类,同包访问
protected 允许本类,同包,子类访问
public 允许本类,同包,子类,全局访问
  • 包装类型的缓存

Byte,Short,Integer,Long存在[-128,127]的缓存,Character存在[0,127]的缓存,Boolean直接返回True、False

  • 自动拆箱装箱
1
2
Integer i = 10 //等价于 Integer i = Integer.valueof(10)
int n = i //等价于 int n = i.intValue()
  • 静态变量、成员变量、局部变量

静态变量:被static修饰的成员变量,全局只有一份(指的内存分配),所有实例共享,只分配一次内存,类加载时分配

成员变量:成员变量如果被static修饰则为静态变量属于类,反之属于实例,实例化时分配内存,存放在堆中,可以被public,private,static 等修饰符所修饰

局部变量:局部变量则存在于栈内存,局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

  • 重载、重写

重载发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期
  • 面向对象三大特征

封装:对象属性隐藏在对象内部,复杂封装对外提供简单入口

继承:不同对象之间公有特征抽取成父类,子类继承可自行扩展

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。

多态:父类引用指向子类实例,程序运行期间才能确定调用的方法属于哪个实例

  • 接口、抽象类

同:

  1. 不能之间实例化
  2. 都包含抽象方法(方法默认是 public abstract),没有方法体,在子类中实现

异:

  1. 设计目的:接口是对类的行为约束,抽象类是代码复用,强调所属关系
  2. 类单继承,接口多实现
  3. 接口中的成员变量都是常量(默认省略public static final)
  4. 抽象类中可以有非抽象方法
  • 深浅拷贝

区别在于拷贝目标内部的引用类型,浅拷贝直接复制内存地址,深拷贝重新开辟空间

引用拷贝:多个引用指向堆中同一对象

  • ==和equals()

== 对于基本类型和引用类型的作用效果是不同的:

​ 对于基本数据类型来说,== 比较的是值。

​ 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

Objectequals() 方法:

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

equals() 方法存在两种使用情况:

类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。

类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

  • 为什么重写equals()时必须重写hashcode()

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

总结

  1. equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  2. 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。
  • String StringBuffer StringBuilder 的区别

String是不可变的:底层是私有且被final修饰的char[],不会被继承

String的+运算符实际是使用StringBuffer的append(),但是如果在循环中使用+就会创建多个StringBuffer对象

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappendinsertindexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

  • 字符串常量池

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

1
2
3
4
5
6
// 在堆中创建字符串对象”ab“
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
  • 代理模式

1.静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

1.定义发送短信的接口

1
2
3
public interface SmsService {
String send(String message);
}

2.实现发送短信的接口

1
2
3
4
5
6
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

3.创建代理类并同样实现发送短信的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SmsProxy implements SmsService {

private final SmsService smsService;

public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}

@Override
public String send(String message) {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method send()");
smsService.send(message);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method send()");
return null;
}
}

4.实际使用

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}

2.动态代理

2.1JDK动态代理

代理接口

1.定义发送短信的接口

1
2
3
public interface SmsService {
String send(String message);
}

2.实现发送短信的接口

1
2
3
4
5
6
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

3.定义一个 JDK 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DebugInvocationHandler implements InvocationHandler{
/** * 代理类中的真实对象 */
privatefinalObject target;
publicDebugInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy,Method method,Object[] args) throwsInvocationTargetException,IllegalAccessException{
//调用方法之前,我们可以添加自己的操作
System.out.println("before method "+ method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method "+ method.getName());return result;}
}

4.获取代理对象的工厂类

1
2
3
4
5
6
7
8
9
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}

5.实际使用

1
2
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

2.2CGLIB动态代理

代理类

1.添加依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>xxx</version>
</dependency>

2.编写基础类

1
2
3
4
5
6
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

3.自定义 MethodInterceptor(方法拦截器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}

4.获取代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

5.使用

1
2
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

数据库

索引

提高数据检索效率,降低数据库IO成本,通过索引对数据排序,减少CPU负载

索引底层使用B+树,优点:

  1. 阶数更多,路径更短
  2. 磁盘读写代价低,非叶子节点只存储指针,只有叶子节点存储数据
  3. 叶子节点是双向链表,便于扫库和区间查询
  • B树和B+树的区别
  1. B树叶子和非叶子节点都会存放数据,B+树仅叶子节点存储数据,查询效率更高
  2. B+树的叶子是双向链表便于区间查询
  • 聚簇索引和非聚簇索引

聚簇索引是指叶子节点存放一整行数据,一般是主键不用我们创建,可以根据主键key进行查询

非聚簇索引是指叶子节点除了索引字段的数据不存放数据,只存放主键key

当我们查询非索引字段的数据,通过非叶子节点的key找到叶子节点,再通过主键key去聚簇索引获取数据,这个过程叫做回表查询

  • 覆盖索引

需要查询的字段存在索引,可直接命中,不需要回表查询

  • 索引创建原则
  1. 表中数据超过10万
  2. 索引字段查询频繁,比如作为查询条件,排序字段
  3. 创建索引一般使用聚合索引,减少回表查询频率
  4. 控制索引数量
  • 最左匹配原则

索引abc_index:(a,b,c)是a,b,c三个字段的联合索引,下列sql执行时都无法命中索引abc_index;

1
2
3
select * from table where c = '1';

select * from table where b ='1' and c ='2';

以下三种情况却会走索引:

1
2
3
4
5
select * from table where a = '1';

select * from table where a = '1' and b = '2';

select * from table where a = '1' and b = '2' and c='3';

如果查询条件与索引最左侧的字段相匹配,会使用索引一直向右匹配查询,直到遇到范围查询

  • 索引失效
  1. 没有遵循最左匹配原则
  2. 模糊查询,如果 % 号在前面也会导致索引失效
  3. 在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效
  4. 使用联合索引,中间使用了范围查询,右边的条件索引也会失效
  • SQL优化
  1. 避免索引失效
  2. 不使用select *指明字段
  3. 遵循最左匹配原则
  4. 关联查询尽量使用inner join,少使用left join right join,必须使用时使用小表作为驱动
  • MYSQL日志
  1. binlog归档日志: 记录所有涉及更新数据的逻辑操作,MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性
  2. redolog事务日志: 恢复数据,保持数据持久性,记录数据页的物理修改
  3. undolog回滚日志: 记录逻辑日志,当事务回滚,通过逆操作恢复数据

SQL基础

  1. DISTINCT去重关键字

  2. ORDER BY先排序的字段放在前,后排序的字段放在后

  3. 字段类型:

    1. 整形:TinyInt int bigInt
    2. 浮点:float double decimal
    3. 字符:char varchar text
  4. UNSIGNED表示无符号,可以将数值正值上限提高一倍

  5. charvarchar的区别

    1. char是定长字符串,varchar是变长字符串
    2. char没有使用完空间,补满空格,检索时去掉空格
    3. varchar用1到2个字节记录字符串长度
  6. Boolean类型使用TinyInt(1)

  7. 存储引擎

    默认使用InnoDB,只有它支持事务

  8. MyIsam(5.5之前)InnoDB的区别

    前者支持表级锁,后者支持行级锁

    前者不支持事务,后者支持事务

    前者不支持外键,后者支持外键

  9. Mysql事务(逻辑上的一组操作,要么都执行,要么都不执行)

    • 事务的四性(ACID)

      A:原子性,最小的执行单元,不允许分割,成功一起,失败一起

      C:一致性,执行事务前后数据一致

      I:隔离性,并发访问数据库时事务是隔离的

      D:持久性,一个事务提交后。对数据库的影响是持久的

      ADI是手段,保证了C一致性

    • 事务的隔离级别

      • 并发导致的问题:

        1.脏读:另一事务读取一个事务修改并未提交的数据,之后这个事务回滚了

        2.丢失修改:事务1,2执行相同操作丢失一次修改

        3.不可重复读:事务1修改A=20 =》A=19,2事务在修改前后读到了A=20和A=19两次不同的数据

        4.幻读:事务1读表,事务2插入一行数据,之后事务1进行再次查询多出了数据叫幻读

      • 四种隔离级别

        读未提交:导致脏读,幻读,不可重复读

        读已提交:导致幻读,不可重复读

        可重复读:导致幻读

        可串行化:事务依次执行,隔离性好,存在性能问题

集合