Skip to content

Java 基础

标识符

  1. 用于给类、接口、方法、变量、常量、枚举起名字的字符序列

  2. 命名规则:

    1. 包名: 域名导致, 全部小写
    2. 类和接口: 大驼峰原则
    3. 变量: 小驼峰原则
    4. 常量: 下划线命名, 字母全大写
  3. 注意事项:

    1. 不能与关键字重名
    2. 不能已以数字开头

数据类型

  1. 类型:
整数类型字节范围默认值
byte1128-128 ~ 1271270
short2215-2^{15} ~ 21512^{15}-10
int4231-2^{31} ~ 23112^{31}-10
long8263-2^{63} ~ 26312^{63}-10L
小数类型字节默认值
float4 (大于 8 个字节的 long 类型)0.0f
double8 (大于 4 个字节的 float 类型)0.0d
字符类型字节范围默认值
char265535'\u0000'
布尔类型字节范围默认值
boolean未知true/falsefalse
  1. 隐士类型提升: 当小的范围的数据类型和大的范围的数据类型在运算时, 会先将小的范围的数据类型的数据,提升为大的范围的数据类型,再进行运算。运算的结果还是大的范围的数据类型。
  2. 常量优化机制: 给一个变量赋值,如果等号的右边是常量的表达式并且没有一个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表示范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。

Switch条件

  1. JDK 1.5 之前: byte short char int
  2. JDK 1.5 : 枚举
  3. JDK 1.7 : String

循环嵌套: laber标签

java
inter:
for (int i = 0; i < 23; i++) {
    System.out.println("---------");
    for (int j = 0; j < 34; j++) {
        System.out.println("###########");
        break inter;
    }
}

数组的初始化

  1. 动态初始化(在运行期陆续完成数组元素的赋值)
    • 使用的创建数组的格式: 数据类型[] 数组名称 = new 数据类型[数组容积]
  2. 静态初始化(在数组一创建的时候,就完成数组元素的赋值)
    1. 数据类型[] 数组名称 = new 数据类型[]{元素1, 元素2, ......, 元素n}
    2. 数组第二种定义格式的简写形式: 数据类型[] 数组名称 = {元素1, 元素2, ......, 元素n}

JVM的内存划分

区域名称作用
寄存器给 CPU 使用, 与开发无关
本地方法栈JVM 在操作系统功能时使用, 与开发无关
方法区存储可运行的 class 文件
堆内存存储对象或数组, new 创建的对象都在堆内存
方法栈方法运行时使用的内存, 比如: main 方法运行, 进入方法栈中执行

面向对象

  1. 特点: 继承 封装 多态
  2. 封装: 隐藏事物的属性和实现细节,对外提供公共的访问方式。
  3. 继承: 子类继承父类的特征和行为, 使得子类对象(实例)具有父类的实例域和方法
  4. 多态: 为不同数据类型实体提供统一的接口, 或者使用一个单一的符号来表示多个不同的类型

局部变量与成员变量

比较项局部变量成员变量
定义定义在方法声明(形参)/方法中定义在类中方法外
内存栈内存方法的栈桢堆内存
生命周期方法调用->存在, 方法结束->消失对象创建->存在, 对象消失->消失
初始状态必须赋初值有默认值

成员变量初始化顺序

默认初始化 > 显式初始化 > 构造代码块赋值 > 构造方法初始化

静态

  1. 静态变量: 随着类的加载而加载, 存储在静态方法区
  2. 注意: 静态不能访问非静态

静态变量和非静态变量

比较项静态变量非静态变量
归属属于类属于对象
内存方法区>静态区堆内存
生命周期随类加载, 随类消失随对象创建, 随对象消失
访问通过类, 通过对象通过对象

this和super

  1. this: 表示本类对象的引用, super: 表示父类对象的引用

  2. this()和super():

    1. 子类的构造方法,一定要先访问父类的构造方法

    2. this 语句和 super 语句必须在构造方法的第一行, super 语句在第一句是为了保证父类的构造方法必须先执行, this 语句在第一句是因为间接的访问了 super, super 必须先执行, 在构造方法中, this 语句和 super 语句不能共存

    3. this 语句和 super 语句不能出现在其他非构造方法的成员方法中

重载和重写的区别

重载重写
范围同一个类中子父类中
方法名相同相同
参数列表不能相同(类型、个数、顺序)必须相同(类型、个数、顺序)
返回值无关<= 父类
权限修饰符无关>= 父类(父类为为 private 则无法重写)
异常无关抛出的异常必须 <= 父类

final的作用

修饰位置作用
变量变量变成常量
方法方法不能被重写
类不可被继承

权限修饰符

本类本包其他类其他包中的子类其他包的其他类
privateXXX
defaultXX
protectedX
public

抽象类和接口的区别

抽象类接口
方法可以有抽象方法, 也可以有方法实现只有抽象方法(JDK8 之前)
实例变量可以是 final 也可以不是默认是 public static final
实现类只能继承一个类(可以不用全部实现抽象方法)可以实现多个接口(要实现全部接口)
抽象/接口方法默认是 default (JDK8 之前是 protected)默认是 public abstract
设计层面对类抽象 -> 模版设计对行为抽象 -> 行为规范

==和equals的区别

  1. == 既可以用于基本数据类型, 也可以用于引用数据类型, equals 只能用于引用数据类型
  2. == 对应基本数据类型比较的是值, 对于引用数据类型比较的是地址值, equals 在重写之前比较的也是对象的地址值, 在重写之后比较的是属性值

throws和throw的区别

比较项throwsthrow
位置用在方法声明后面,跟的是异常类名用在方法体内,跟的是异常对象
作用表示声明异常, 调用该方法有可能会出现这样的异常, 也有可能没有异常出现,起到了一个警告的作用, 有可能出现异常, 也有可能不出现异常表示手动抛出异常对象,只要执行语句必然有异常抛出

数组和集合的区别

  1. 相同点: 两者都是数据存储容器,可以存储多个数据

  2. 不同点:

    数组:

    • 数组的长度是不可变的

    • 数组可以存基本数据类型和引用数据类型

    集合:

    • 集合的长度是可以改变的

    • 只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类的对象

数组和链表

  1. 数组: 查询快, 增删慢
  2. 链表: 查询慢, 增删快

ArrayList源码分析

  1. 创建 ArrayList 的时候 -> 底层维护一个长度为 10 的 Object 数组, 当前对象刚创建出来时, 数组是一个空数组

  2. 插入数据时, 如果长度不够会进行扩容 -> 以 1.5 倍的形式扩容

泛型的注意事项

  1. 定义非静态泛型方法:
    1. 可以使用定义在类上的泛型
    2. 可以使用定义在方法上的泛型
  2. 定义静态泛型方法
    1. 只能使用定义在方法上的泛型
    2. 不能用类上定义的泛型: 因为类上的泛型只有在创建对象的时候才能确定, 而静态方法可以直接通过 类名.方法名 调用, 如果可以使用类上的泛型, 那么泛型就会不确定, 因此无法使用类上的泛型

HashSet保证元素唯一性

  1. 重写 hashCode 方法: 相同的对象有相同的 hash 值, 不相同的对象尽量有不同的 hash 值, 根据对象的成员变量的值来计算生成 hash 值

  2. 重写 equals 方法: 比较的不是对象的地址值, 而是对象的属性值

HashSet保证元素唯一性过程

  1. 调用 hashCode 方法, 返回 hash 值
  2. 比较容器中是否存在和这个 hash 值相同的元素
    1. 如果存在一样的就调用 equals 方法, 如果为 true, 就说明元素一样, 不进行添加, 如果为 false, 说明不一样, 就添加
    2. 如果 hash 值不一样, 就直接添加进去

LinkedHashSet

LinkedHashSet 集合时 HashSet 的一个子集, 它能够保证元素顺序的一致性

TreeSet保证元素唯一性

  1. 实现 Comparable 接口: 重写 compareTo 方法
  2. 构造方法接受 Comparator 实现类对象: 重写 compare 方法

LinkedHashMap

  1. LinkedHashMap: 存储数据采用的哈希表结构 + 链表结构
  2. 通过链表结构可以保证元素的存取顺序一致
  3. 通过哈希表结构可以保证的键的唯一 、不重复, 需要重写键的 hashCode() 方法、 equals() 方法

哈希值

  • 哈希值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值

HashMap底层原理

  1. JDK 1.7 之前
  • 结构: 数组 + 链表

  • 源码分析:

1. 首先判断数组长度是否为 0 -> 如果为 0 则进行桶扩容
2. 如果不为 0, 通过 hashCode 方法返回哈希值(结合路由寻址算法)计算对象的储存位置
3. 判断该位置是否为空, 如果为空 -> 直接插入
	3.1 如果不为空, 遍历该位置的所有元素与对象的哈希值进行对比, 如果相同则调用 equals 方法进行对比
	3.2 对链表进行遍历, 然后插入(头插法)
4. 查看容量是否足够, 如果不够 -> 进行桶扩容
5. 结束
  1. JDK 1.8 开始
  • 结构: 数组 + 链表 + 红黑树

    1. 链表的长度小于 8, 并且数组的长度小于 64: 数组 + 链表

    2. 链表的长度大于等于 8, 并且数组的长度大于等于 64: 数组 + 红黑树

  • 源码分析:

1. 首先判断数组长度是否为 0 -> 如果为 0 则进行桶扩容
2. 如果不为 0, 通过 hashCode 方法返回哈希值(结合路由寻址算法)计算对象的储存位置
3. 判断该位置是否为空, 如果为空 -> 直接插入
	3.1 如果不为空, 遍历该位置的所有元素与对象的哈希值进行对比, 如果相同则调用 equals 方法进行对比
	3.2 判断该位置是否为树结点
	3.3 如果不是, 对链表进行遍历, 判断该位置元素个数是否大于 8, 大于 -> 转成红黑树进行插入
	3.4 小于 -> 直接插入(尾插法)
4. 查看容量是否足够, 如果不够 -> 进行桶扩容
5. 结束
  • Hash 算法优化: 将 hash 值右移 16 位之后, 与原来的 hash 值进行异或 -> 使高低 16 位都能参与运算
  • 寻址算法优化: 将取模运算通过 (length - 1) & hash, hash 碰撞概率降低

对象的序列化

  1. 实现方式: ObjectInputStream
  2. serialVersionUID: 保证对象被修改后, 序列化/反序列化不出错
  3. transient: 被修饰的成员变量无法被序列化

多线程的实现方式

  1. Thread 继承: 继承 Thread 类, 重写 run 方法
  2. Runnable 接口: 实现 Runnable 接口, 重写 run 方法, 在 Thread 构造中传入实现类对象
  3. Callable 接口: 实现 Callable 接口, 重写 call() 方法, 创建实例对象放入 FutureTask 构造方法, 在 Thread 构造中传入 FutureTask 实例
  4. 自定义线程池:

线程安全问题

  1. 同步代码块 -> synchronized(任意对象): 效率低, 耗费资源
  2. 同步方法:
  • 非静态
java
同步成员方法:
修饰符 synchronized 返回值类型 方法名(方法参数) {
	方法体;
}
非静态同步方法的锁对象就是 this,谁调用这个方法,锁对象就是谁。
  • 静态
java
同步静态方法:
修饰符 static synchronized 返回值类型 方法名(方法参数) {
	方法体;
}
静态的同步方法的锁对象是[类名.class], 也叫作当前类的字节码对象。
  1. Lock 锁: ReentrantLock(), 创建一个 ReentrantLock 的实例
  • 加锁: void lock()
  • 解锁: void unlock()

死锁的实现

java
Thread thread1 = new Thread() {
    @Override
    public void run() {
        while (true) {
            synchronized ("筷子a") {
                System.out.println("我只拿到了筷子a, 还差个筷子b");
                synchronized ("筷子b") {
                    System.out.println("我有一双筷子了, 可以吃饭了~~~~");
                }
            }
        }
    }
};
Thread thread2 = new Thread() {
    @Override
    public void run() {
        while (true) {
            synchronized ("筷子b") {
                System.out.println("小黑拿到了筷子b, 还差个筷子a");
                synchronized ("筷子a") {
                    System.out.println("小黑有了一双筷子, 可以吃饭了~~~~");
                }
            }
        }
    }
};

thread1.start();
thread2.start();

解决方案:

  1. 尽量一个线程只获取一个锁
  2. 一个线程只占用一个资源
  3. 使用定时锁,至少能保证锁最终会被释放。

线程生命周期

线程状态过程:

​ 新建new(线程对象被创建) -> 就绪runnable(调用了 start 方法, 就绪状态: 创建了线程, 等待 cpu 调用) -> 运行running(获取 cpu 执行权限, 开始执行 run 方法)

​ -> 阻塞boloked(线程因为某种原因放弃了 cpu 执行权, 暂停运行, 进入阻塞状态)

​ -> 死亡dead(run 或 call 正常执行完, 出现异常, 调用 stop 方法)

自定义线程池

java
// 核心线程数量
int corePoolSize = 1;
// 最大线程数
int maxPoolSize = 10;
// 线程最大存活时间
int keepActiveTime = 10;
// 时间单位
TimeUnit seconds = TimeUnit.SECONDS;
// 任务队列
BlockingDeque<Runnable> deque = new LinkedBlockingDeque<>(10);
// 线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 任务拒绝策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 手动创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepActiveTime, seconds, deque, threadFactory, handler);

for (int i = 0; i < 20; i++) {
    poolExecutor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是一个任务: " + Thread.currentThread().getName());
        }
    });
}

poolExecutor.shutdown();

枚举的实现

注意: 枚举可以有构造方法, 但是构造方法默认为 private

java
public enum PersonEnum {
    MAN("张三", 23),
    WOMAN("李思思", 44);

    private String name;
    private int age;

    PersonEnum() {
    }

    PersonEnum(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PersonEnum{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    public void show() {
        System.out.println("我的名字叫: " + name);
    }
}

网络编程

网络编程三要素: ip 地址, 端口, 协议

ip地址:

  1. ipv4: 长度 4 个字节, 每一个字节一组
  2. ipv6: 长度 16 个字节, 两个字节一组

端口: 应用程序的唯一标识

协议:

  1. UDP 协议: 无连接通信协议, 即在数据传输时, 数据的发送端和接收端不建立逻辑连接(只管发送, 不管接受)
  2. TCP 协议: 面向连接的通信协议, 即传输数据之前, 在发送端和接收端建立逻辑连接, 然后再传输数据(三次握手)
    1. 第一次握手: 客户端向服务端发送一个 SYN 报文(SYN = 1), 并指明客户端的初始化序列号 ISN(x), 即图中的 seq = x, 表示本报文段所发送的数据的第一个字节的序号, 此时客户端处于"在发送连接请求后等待匹配的连接请求"状态;
    2. 第二次握手: 服务器收到客户端的 SYN 报文之后, 会发送 SYN 报文作为应答(SYN = 1), 并且指定自己的初始化序列号 ISN(y), 即图中的 seq = y, 同时会把客户端的 ISN + 1 作为确认号 ack 的值, 表示已经收到了客户端发来的的 SYN 报文, 希望收到的下一个数据的第一个字节的序号是 x + 1, 此时服务器处于"在收到和发送一个连接请求后等待对连接请求的确认"的状态;
    3. 第三次握手: 客户端收到服务器端响应的 SYN 报文之后, 会发送一个 ACK 报文, 也是一样把服务器的 ISN + 1 作为 ack 的值, 表示已经收到了服务端发来的的 SYN 报文, 希望收到的下一个数据的第一个字节的序号是 y + 1, 并指明此时客户端的序列号 seq = x + 1(初始为 seq = x, 所以第二个报文段要 +1), 此时客户端处于"代表一个打开的连接, 数据可以传送给用户"状态;

OSI七层模型

  1. 应用层: 为应用程序提供服务
  2. 表示层: 数据格式转换, 数据加密
  3. 会话层: 建立, 管理和维护会话
  4. 传输层: 建立, 管理和维护客户端到服务端的连接
  5. 网络层: ip 选址及路由选择
  6. 数据链路层: 提供介质访问和链路管理
  7. 物理层: 物理层

TCP五层模型

  1. 应用层: (应用层, 表示层, 会话层)
  2. 传输层: 四层交换机, 四层的路由器
  3. 网络层: 路由器, 三层交换机
  4. 数据链路层: 网桥, 以太网交换机, 网卡
  5. 物理层: 中继器, 集线器, 双绞线

类加载过程

  1. 步骤: 加载 -> 连接 -> 初始化
  2. 过程
    1. 加载: 将 class 文件读入内存, 并创建一个 Class 对象(字节码对象)
    2. 连接: 验证是否有正确的内部结构, 并和其他类进行协调, 为类的静态成员分配内存, 并设置为默认值, 将类的二进制数据中的符号引用替换为直接引用
    3. 初始化: 调用类加载器的构造器方法
  3. 类的初始化时机:
    1. 创建类的实例
    2. 类的静态成员使用
    3. 类的静态方法调用
    4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    5. 初始化某个类的子类
    6. 直接使用java.exe命令来运行某个主类[调用一个类的主方法]

双亲委派机制

  • 双亲委派机制是指当一个类加载器收到一个类加载请求时, 该类加载器首先会把请求委派给父类加载器, 每个类加载器都是如此, 只有在父类加载器在自己的搜索范围内找不到指定类时, 子类加载器才会尝试自己去加载

JDK8新特性

  1. JDK8 : 默认方法, 静态方法
  2. JDK9 : 私有方法
1. 类或接口继承两个接口或一个类一个接口, 当默认方法同名时, 必须重写默认方法(如果想调用父类方法或父接口默认方法, 则是用 父接口名.super.方法名(参数) | super.方法名(参数))
	注意: 当继承一个类或一个接口时, 如果父类同名方法是 public, 默认使用父类方法(亲爹 > 干爹), 不是 public 时, 需要重写
	
2. 普通私有方法:只能提供给默认方法调用使用

3. 静态私有方法:默认方法和静态方法都可以调用

4. 静态方法只能使用静态的私有方法,不能使用普通的私有方法

匿名内部类和Lambda区别

  1. Lambda 表达式只能是接口, 匿名内部类可以使抽象类, 也是可以接口
  2. 接口中只有一个抽象方法时可以用 Lambda 表达式, 多于一个只能用匿名内部类
  3. 匿名内部类编译之后会产生一个单独的 .class 字节码文件, Lambda 表达式编译之后不会产生一个单独的 .class 字节码文件, 对应的字节码会在运行的时候动态生成

JDBC连接数据库的步骤

  1. 加载 JDBC 驱动程序
  2. 提供一个 JDBC 连接的 url, 创建数据库的连接
  3. 通过连接获取一个数据库操作对象(Statement)
  4. 定义 sql 语句, 并执行获取结果集
  5. 释放资源(关闭结果集对象 -> 关闭数据库操作对象 -> 关闭连接)

Error和Exception区别

  1. Exception 是 Java 程序运行中可预料的异常情况, 我们可以获取到这种异常, 并且对这种异常进行业务外的处理

  2. Error 是 Java 程序运行中不可预料的异常情况, 这种异常发生以后, 会直接导致 JVM 不可处理或者不可恢复的情况, 所以错误不可能抓取到, 比如 OutOfMemoryError、NoClassDefFoundError等

重定向和请求装发区别

区别重定向请求转发
位置客户端完成(可以在不同的服务器下完成)服务器完成(必须在同一台服务器完成)
浏览器请求发送次数两次以上一次
url地址栏发生变化地址栏的地址不变
是否共享 request不共享数据(经过重定向后, request 内的对象将无发)共享数据(以前的)
第二次请求发起者浏览器服务器
第二次请求路径绝对路径相对路径
速度相对慢一点

HashMap HashTable和ConcurrentHashMap

  1. HashMap: 线程不安全, key值可以为null, 底层的实现是 数组 + 链表/红黑树

  2. HashTable: 线程安全, 当有线程访问时, 使用 synchronized 将整个表进行独占, 效率较低; key 值不可以为 null; 底层实现是 数组 + 链表

  3. ConcurrentHashMap: 线程安全, 采用 CAS 和 synchronized 操作, 每个线程只对访问数据的桶进行加锁, 提高了并发效率; key 值不能为 null, 底层的实现是 数组 + 链表/红黑树

Servlet的三大作用域

  1. request: 每一次请求都是一个新的 request 对象, 如果在 web 组件之间需要共享同一个请求中的数据, 只能使用请求转发
  2. session: 每一次会话都是一个新的 session 对象, 如果如果需要在一次会话中的多个请求之间需要共享数据, 只能使用 session
  3. application: 应用对象, Tomcat 启动到关闭, 表示一个应用, 在一个应用中有且只有一个 application 对象, 作用于整个 Web 应用, 可以实现多次会话之间的数据共享

Servlet作用域生命周期

  1. request: 只限于一次请求

  2. session: 一次会话(多次请求)

    1. 开始: 用户向服务器发送请求的时候
    2. 结束:
      1. 客户端: 丢失 JsessionId 值的时候(关闭浏览器)
      2. 服务器端: 关闭服务器、超过会话的不活动周期时间
  3. application: 项目的加载到卸载

Session和Cookice

  1. 作用范围: Cookie 保存在客户端(浏览器), Session 保存在服务器端
  2. 存取方式: Cookie 只能保存 ASCII, Session 可以存任意数据类型, 一般情况下我们可以在 Session 中保持一些常用变量信息, 比如说 UserId 等
  3. 有效期: Cookie 可设置为长时间保持, 比如我们经常使用的默认登录功能, Session 一般失效时间较短, 客户端关闭或者 Session 超时都会失效
  4. 隐私策略: Cookie 存储在客户端, 比较容易遭到不法获取, 早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取, Session 存储在服务端, 安全性相对 Cookie 要好一些
  5. 存储大小: 单个 Cookie 保存的数据不能超过 4K, Session 可存储数据远高于 Cookie

重定向原理

  1. 浏览器向服务器发送 HTTP 请求
  2. 服务器接收到请求后, 如果调用 response.sendRedirect() 方法, 表示资源已被移走, 则发送一个 302 的状态码和 location 的响应头, 在 location 响应头指明要转发的地址. 302: 临时重定向
  3. 浏览器在接收到 302 状态码后, 会读取 location 响应头的内容, 并将地址栏的值赋为 location 响应头的内容, 从而再向服务器发出第二次请求, 由于是二次请求, 所以重定向不能获得封装在 request 中的属性信息

JSP九大内置对象

page, request, session, application, response, out, config, pageContext, exception

Http和Https区别

  1. https 协议需要到 CA 申请证书, 一般免费证书较少, 因而需要一定费用
  2. http 是超文本传输协议, 信息是明文传输, https 则是具有安全性的 ssl 加密传输协议
  3. http 和 https 使用的是完全不同的连接方式, 用的端口也不一样, 前者是 80, 后者是443
  4. http 的连接很简单, 是无状态的, Https 协议是由 SSL + Http 协议构建的可进行加密传输、身份认证的网络协议, 比 http 协议安全(无状态的意思是其数据包的发送、传输和接受都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息)

什么是反射?

反射是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法, 对于任意一个对象, 都能够调用它的任意一个方法和属性, 这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制

反射的实现方式

  1. Class.forName("类的路径")
  2. 类名.class
  3. 对象名.getClass()
  4. 通过类加载器 xxxClassLoader.loadClass() 传入类路径获取
  5. 基本类型的包装类, 可以调用包装类的 Type 属性来获得该包装类的 Class 对象

反射机制的优缺点

  1. 优点: 可以动态执行, 在运行期间根据业务功能动态执行方法、访问属性, 最大限度发挥了 java 的灵活性
  2. 缺点: 对性能有影响, 这类操作总是慢于直接执行 java 代码

跨域与同源策略

  1. 同源策略: 同源策略是浏览器的一个重要的安全策略, 它用于限制一个源要加载的 js 脚本与另外一个源的内容进行交互, 它能够隔绝恶意文档, 减少被攻击的媒介
  2. 同源: 如果两个 URL 的协议、主机名和端口号都是相同的, 那么这两个 URL 就是同源的
  3. 当一个请求 url 的协议、域名、端口三者之间的任意一个与当前页面 url 不同即为跨域
  4. 解决方案: Jsonp, Cors, Nginx 代理, 过滤器, 拦截器, Spring 注解, Tomcat 配置

基本类型和包装类型区别

  1. 默认值不一样
  2. 包装类可用于泛型, 基本类型不可以
  3. 基本类型效率更高: 基本类型在堆栈中直接存储的具体数值, 而包装类型则存储的是堆中的引用(占用内存多)
  4. 包装类型都实现了 Number 接口和 Compareble 接口

进程和线程的区别?

  1. 进程是内存给程序分配内存空间的单位, 一个应用程序可以有多个进程, 进程也是程序的一次执行过程
  2. 线程是进程执行的一个执行单元, 负责当前进程中程序的执行, 一个进程中至少有一个线程, 一个进程中可以有多个线程的, 这个应用程序则称为多线程程序

总结: 一个程序运行后至少有一个进程, 一个进程中可以包含多个线程

sleep和wait的区别,notify和notifyall的区别

  1. sleep 和 wait 区别:
    1. sleep 是 Thread 的方法, wait 是 Object 中的方法
    2. sleep 方法可以在任何地方进行调用, wait 方法只能在 synchronized 方法或 synchronized 块中使用
    3. sleep 只是让出 cpu 不会导致锁行为的改变, wait 方法不仅会让出 cpu 还会释放已经占有的同步资源锁
  2. notify 和 notifyall 区别:
    1. 两个概念:
      • 锁池(entrylist): 假设线程 a 已经拥有了某个对象的(不是类)的锁, 而其他线程 b、c 想要调用这个对象的 synchronized 方法(块), 要先获取该对象的锁对象拥有权, 但是锁对象被线程 a 持有, 因此线程 b、c 就会被堵塞, 线程 b、c 就要去一个地方进行等待, 那么这个等待的地方就是锁池;
      • 等待池(waitset): 假设线程 a 调用了某个对象的 wait 方法, 线程 a 就是释放该对象的锁, 同时线程 a 进入到该对象的等待池中, 等待池中的对象不会去竞争该对象的锁;
    2. notifyall 会让所有处于等待池中的线程全部进入到锁池中去竞争获取锁的机会;
    3. notify 只会随机选取一个处于等待池中的线程进入锁池中去竞争获取锁的机会;

Get和Post请求的区别

  1. GET 在浏览器回退时是无害的, 而 POST 会再次提交请求;
  2. GET 请求只能进行 url 编码, 而 POST 支持多种编码方式;
  3. GET 请求参数会被完整保留在浏览器历史记录里, 而 POST 中的参数不会被保留。
  4. GET 请求在 URL 中传送的参数是有长度限制的, 而 POST 没有;
  5. 对参数的数据类型, GET 只接受 ASCII 字符, 而 POST 没有限制;
  6. GET 比 POST 更不安全, 因为参数直接暴露在 URL 上, 所以不能用来传递敏感信息;
  7. GET 参数通过 URL 传递, POST 放在 Request body 中

String相关知识

  1. AbstractStringBuilder 权限修饰符为 default, 只能被同包下的类继承, 因此无法使用自定义类对其继承
  2. String StringBuilder StringBuffer 均被 final 修饰, 无法进行继承