CC 咖啡猫的工作空间 Coding Space
  1. 字节码:JVM 可以理解的代码就叫做字节码,即.class文件,只面向虚拟机
  2. 源代码到运行的过程: .java -> javac编译 -> .class -> 解释器&JIT(热点代码使用JIT,一般代码使用解释器) -> 机器码(机器可理解的代码) -> 运行
  3. 基本数据类型:
  • 6种数字类型
    • byte(1个字节)、short(2个字节)、int(4个字节)、long(8个字节,定义时必须加上L后缀,否则将作为整型解析)
    • float(4个字节)、double(8个字节,定义时必须加上F或D后缀,否则将无法通过编译)
    • char(2个字节,定义时必须加上单引号)
    • boolean(1个字节,true或false)
  • 说明:byte(-128~127)、short、int、long能表示的最大正数都减 1 了。这是为什么呢?这是因为在二进制补码表示法中,最高位是用来表示符号的(0 表示正数,1 表示负数),其余位表示数值部分。所以,如果我们要表示最大的正数,我们需要把除了最高位之外的所有位都设为 1。如果我们再加 1,就会导致溢出,变成一个负数。
  1. 浮点数与精度(定点数:指的是约定小数点位置固定不变):
  • float a = 2.0f - 1.9f; float b = 1.8f - 1.7f; // a=0.100000024,b=0.099999905
  • 十进制下的 0.2 就没办法精确转换成二进制小数
  • 计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况
  • BigDecimal 工具类
  1. Java值传递
  • 方法定义使用的参数:实参(用于传递给函数/方法的参数,必须有确定的值)、形参(用于定义函数/方法,接收实参,不需要有确定的值)
  • 实参传递给方法的方式:值传递(方法接收的是实参值的拷贝,会创建副本)、引用传递(方法接收的直接是实参的地址,而不是实参内的值)
  • Java只有值传递,传递对象时,方法拿到的是引用地址的副本。通过该副本修改对象内部属性,会同步影响方法外的原对象(因为指向的是同一块堆内存)
  1. 成员变量与局部变量
  • 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数。成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰,但是,成员变量和局部变量都能被 final 所修饰。
  • 存储位置:成员变量存储在堆内存中(随对象存在,生存时间长),局部变量存储在栈内存中(随栈帧存在,方法执行结束时销毁)。
  • 默认值:成员变量有默认值,局部变量必须显示设置初始值,否则编译时会报错
  1. 访问权限修饰符
  • public:任何类都可以访问
  • protected:本类内、子类内、同一个包内可以访问,其他类不能访问
  • default:本类内、同一个包内可以访问,其他类不能访问(跨包的子类也不能访问父类的成员变量和成员方法)
  • private:本类内可以访问,其他类不能访问
  1. 静态方法和实例方法
  • 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制
  • 调用方式:静态方法通过类名调用,实例方法必须通过对象调用
  1. 重载与重写
  • 重载发生在同一个类中,多个方法名相同,参数列表不同,返回值类型可以相同也可以不同;重写则是一个子类继承父类方法,并重写该方法(方法名相同,参数列表相同,返回值类型相同)
  • 访问权限修饰符:子类重写父类方法时,不能使访问权限修饰符更宽,但可以更窄(public > protected > default > private)
  1. 对象的相等和引用相等
  • 对象的相等一般比较的是内存中存放的内容是否相等
  • 引用相等一般比较的是他们指向的内存地址是否相等
  1. Object类
  • 常用native方法:getClass()、equals()(重写equals方法时必须重写hashCode方法)、hashCode()、toString()、finalize()(实例被垃圾回收时触发的操作)
  • 线程方法:notify()、notifyAll()、wait()(3个wait方法)
  1. == 和 equals()
  • == 运算符:对于基本数据类型来说,比较的是值,对于对象类型来说,比较的是对象的内存地址
  • equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等
  1. String类
  • String、StringBuffer、StringBuilder的区别:String是immutable(不可变对象),StringBuffer和StringBuilder都是mutable(可变对象),都是继承自AbstractStringBuilder,StringBuffer是线程安全的(该类对方法加了同步锁或者对调用的方法加了同步锁),StringBuilder是非线程安全的
  • 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
  • 建议:操作少量的数据,适用String;单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder;多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
  • Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符(StringBuilder类进行拼接)。
  • String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
  • String不可变的原因:
    1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
    2. String 类的构造方法被 final 修饰,不允许被继承,进而避免了子类破坏 String 不可变。
  • 字符串常量池:JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
  • new String("abc"); 这句话创建了几个字符串对象?会创建 1 或 2 个字符串对象(常量池+堆)。
    • 字符串常量池中不存在 "abc":会创建 2 个 字符串对象。一个在字符串常量池中,由 ldc 指令触发创建。一个在堆中,由 new String() 创建,并使用常量池中的 "abc" 进行初始化。
    • 字符串常量池中已存在 "abc":会创建 1 个 字符串对象。该对象在堆中,由 new String() 创建,并使用常量池中的 "abc" 进行初始化。
  • intern() 方法:调用时,如果常量池中已经存在相同内容的字符串,则返回常量池中已有对象的引用;否则,将该字符串添加到常量池并返回其引用
  1. 异常
  • Exception和Error:两者都继承自 Throwable 类,Exception 是运行时异常(程序本身可以处理的异常,可以通过 catch 来进行捕获),Error 是非运行时异常(异常发生时,Java 虚拟机一般会选择线程终止)
  • Checked Exception:即受检查异常,必须处理,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译(比如需要加上catch捕获或者throws声明)。常见的错误类型:IOException、ClassNotFoundException、SQLException
  • Unchecked Exception:即非受检查异常,不需要处理,即使不处理不受检查异常也可以正常通过编译,Java 虚拟机会自动处理,比如 NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException
  • 建议默认使用 Unchecked Exception,只在必要时才用 Checked Exception
  1. 反射:在程序运行时,动态地加载类、创建对象、调用方法和访问字段
  • 使用场景(优点):灵活和动态性、框架开发的基础(比如使用反射来实现依赖注入、动态代理与AOP、对象关系映射(ORM框架)、注解处理等)。
  • 缺点:性能开销、安全性问题,反射可以绕过 Java 语言的访问控制机制(如访问 private 字段和方法),破坏了封装性,可能导致数据泄露或程序被恶意篡改。此外,还可以绕过泛型检查,带来类型安全隐患)
  1. 代理:
  • 静态代理:手动代理,通过创建代理类同样实现代理目标的接口,将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法(不灵活)。
  • 动态代理是一种非常强大的设计模式,它允许我们在不修改源代码的情况下,对一个类或对象的方法进行功能增强。分为JDK代理和CGLIB代理(第三方的代码生成库,spring中使用,不能代理final类)
  1. 注解:主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。注解常用的解析方法(编译期直接扫描如@Override、@Deprecated等、运行期通过反射处理如Spring框架的@Component)
  2. 序列化
  • 序列化:将数据结构或对象转换成可以存储或传输的形式,通常是二进制字节流,也可以是 JSON, XML 等文本格式
  • 反序列化:将在序列化过程中所生成的数据转换为原始数据结构或者对象的过程
  • 我们很少或者说几乎不会直接使用 JDK 自带的序列化方式(jdk自带不支持跨语言、性能差、存在安全问题)、比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
  1. IO
  • IO流:字节流(InputStream、OutputStream,处理二进制文件,如图片、视频等)与字符流(Read、Writer,处理文本)
  • 功能增强:字节(BufferedInputStream/BufferedOutputStream)/(BufferedReader/BufferedWriter)字符缓冲流、打印流、随机访问流(RandomAccessFile)、对象流(ObjectInputStream、ObjectOutputStream,序列化)
  • I/O 流为什么要分为字节流和字符流?(不管是文件读写还是网络发送接收,信息的最小存储单元都是字节)
    1. 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时
    2. 如果不知道编码类型的话,使用字节流的过程中很容易出现乱码问题
  • Java IO中3种常见的模型:BIO(同步阻塞IO,阻塞直到处理完成)、NIO(同步非阻塞IO,即多路复用模型,selector多路复用器,netty框架使用)、AIO(异步IO模型,基于事件和回调机制实现)
  • NIO详解
    1. 三个核心组件(【客户端 <-> 缓冲区 <-> 通道】多条路汇总 -> 选择器 -> 线程)
    • 缓冲区:在传统的 BIO 中,数据的读写是面向流的,分为字节流和字符流。在 Java 1.4 的 NIO 库中,所有数据都是用缓冲区处理的,这是新库和之前的 BIO 的一个重要区别
    • 通道:Channel 是一个双向的、可读可写的数据传输通道,NIO 通过 Channel 来实现数据的输入输出
    • 选择器:允许一个线程处理多个 Channel,基于事件驱动的 I/O 多路复用模型。所有的 Channel 都可以注册到 Selector 上,由 Selector 来分配线程来处理事件
    1. NIO零拷贝:零拷贝是指计算机执行 IO 操作时,CPU 不需要将数据从一个存储区域复制到另一个存储区域,从而可以减少上下文切换以及 CPU 的拷贝时间(主要解决操作系统在处理 I/O 操作时频繁复制数据的问题)
    2. 零拷贝的常见实现技术有: mmap+writesendfile、sendfile + DMA gather copy