java基础

Java 基础

语言特性

优点

① 平台无关,摆脱硬件束缚,“一次编写,到处运行”。

② 安全的内存管理和访问机制,避免大部分内存泄漏和指针越界。

③ 热点代码检测和运行时编译优化,程序随运行时长获得更高性能。

④ 完善的应用程序接口,支持第三方类库。


平台无关⭐

JVM: 编译器生成与计算机体系结构无关的字节码,字节码文件不仅能在任何机器解释执行,还能动态转换成本地机器码,转换由 JVM 实现。JVM 是平台相关的,屏蔽了不同操作系统的差异。

语言规范: 基本数据类型大小有明确规定,如 int 永远 32 位,而 C/C++ 可能是 16 位、32 位,或编译器开发商指定的其他大小。数值类型有固定字节数,字符串用标准 Unicode 格式。


JDK 和 JRE

JDK: Java Development Kit,开发工具包。提供了编译运行 Java 程序的各种工具,包括编译器、JRE 及常用类库,是 JAVA 核心。

JRE: Java Runtime Environment,运行时环境,运行 Java 程序的必要环境,包括 JVM、核心类库、核心配置工具。


值调用和引用调用

按值调用指方法接收调用者提供的值,按引用调用指方法接收调用者提供的变量地址。

Java 总是按值调用,方法得到的是参数的副本,传递对象时实际上传递的是对象引用的副本。

  • 方法不能修改基本数据类型的参数,例如传递了一个 int 值 ,改变 int 值不会影响实参。

  • 方法可以改变对象参数的状态,但不能让对象参数引用新的对象。例如传递了一个 int 数组,改变数组内容会影响实参,而改变其引用并不会让实参引用新的数组对象。


浅拷贝和深拷贝

浅拷贝只复制当前对象的基本数据类型及引用变量,没有复制引用变量指向的实际对象。修改克隆对象可能影响原对象。

深拷贝完全拷贝基本数据类型和引用数据类型,修改克隆对象不会影响原对象。


反射

在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射,缺点是破坏了封装性及泛型约束。


Class 类

在程序运行期间,Java 运行时系统为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法,保存这些信息的类就是 Class,这是一个泛型类。

获取 Class 对象:① 类名.class 。② 对象的 getClass方法。③ Class.forName(类的全限定名)


注解⭐

注解是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能,例如 @Override 标识一个方法是重写方法。

元注解是自定义注解的注解,例如:

@Target:约束作用位置,值是 ElementType 枚举常量,包括 METHOD 方法、VARIABLE 变量、TYPE 类/接口、PARAMETER 方法参数、CONSTRUCTORS 构造方法和 LOACL_VARIABLE 局部变量等。

@Rentention:约束生命周期,值是 RetentionPolicy 枚举常量,包括 SOURCE 源码、CLASS 字节码和 RUNTIME 运行时。

@Documented:表明注解应该被 javadoc 记录。


泛型

泛型本质是参数化类型,解决不确定对象具体类型的问题。

泛型的好处:① 类型安全,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。

泛型用于编译阶段,编译后的字节码文件不包含泛型类型信息,因为虚拟机没有泛型类型对象,所有对象都属于普通类。例如定义 List<Object>List<String>,在编译后都会变成 List


JDK8 新特性

**lambda 表达式:**允许把函数作为参数传递到方法,简化匿名内部类代码。

**函数式接口:**使用 @FunctionalInterface 标识,有且仅有一个抽象方法,可被隐式转换为 lambda 表达式。

**方法引用:**可以引用已有类或对象的方法和构造方法,进一步简化 lambda 表达式。

**接口:**接口可以定义 default 修饰的默认方法,降低了接口升级的复杂性,还可以定义静态方法。

**注解:**引入重复注解机制,相同注解在同地方可以声明多次。注解作用范围也进行了扩展,可作用于局部变量、泛型、方法异常等。

**类型推测:**加强了类型推测机制,使代码更加简洁。

**Optional 类:**处理空指针异常,提高代码可读性。

**Stream 类:**引入函数式编程风格,提供了很多功能,使代码更加简洁。方法包括 forEach 遍历、count 统计个数、filter 按条件过滤、limit 取前 n 个元素、skip 跳过前 n 个元素、map 映射加工、concat 合并 stream 流等。

**日期:**增强了日期和时间 API,新的 java.time 包主要包含了处理日期、时间、日期/时间、时区、时刻和时钟等操作。

**JavaScript:**提供了一个新的 JavaScript 引擎,允许在 JVM上运行特定 JavaScript 应用。


异常🌙

所有异常都是 Throwable 的子类,分为 Error 和 Exception。

Error 是 Java 运行时系统的内部错误和资源耗尽错误,例如 StackOverFlowError 和 OutOfMemoryError,这种异常程序无法处理。

Exception 分为受检异常和非受检异常,受检异常要显式处理,否则编译出错,非受检异常是运行时异常,继承 RuntimeException。

受检异常:① 无能为力型,如字段超长导致的 SQLException。② 力所能及型,如未授权异常 UnAuthorizedException,程序可跳转权限申请页面。常见受检异常还有 FileNotFoundException、ClassNotFoundException、IOException等。

非受检异常:① 可预测异常,例如 IndexOutOfBoundsException、NullPointerException、ClassCastException 等,这类异常应该提前处理。② 需捕捉异常,例如进行 RPC 调用时的远程服务超时,这类异常客户端必须显式处理。③ 可透出异常,指框架或系统产生的且会自行处理的异常,例如 Spring 的 NoSuchRequestHandingMethodException,Spring 会自动将异常自动映射到合适的状态码。


数据类型

基本数据类型
数据类型内存大小默认值取值范围
byte1 B(byte)0-128 ~ 127
short2 B(short)0-215 ~ 215-1
int4 B0-231 ~ 231-1
long8 B0L-263 ~ 263-1
float4 B0.0F±3.4E+38(有效位数 6~7 位)
double8 B0.0D±1.7E+308(有效位数 15 位)
char英文 1B,中文 UTF-8 占 3B,GBK 占 2B。‘\u0000’‘\u0000’ ~ ‘\uFFFF’
boolean单个变量 4B / 数组 1Bfalsetrue、false

JVM 没有 boolean 的字节码指令,单个 boolean 变量用 int 代替,boolean f = false 就是用 ICONST_0 即常数 0 赋值。boolean 数组会编码成 byte 数组。

自动装箱是将基本数据类型包装为一个包装类对象,例如向一个泛型为 Integer 的集合添加 int 元素;自动拆箱是将一个包装类对象转换为基本数据类型,例如将一个 Integer 对象赋值给一个 int 变量。比较两个包装类数值要用 equals


String⭐

String 类和其存储数据的 value 字节数组都是 final 修饰的。对 String 对象的任何修改实际都是创建新对象再引用,并没有修改原对象。

字符串拼接方式

① 直接用 + ,底层用 StringBuilder 实现。只适用小数量,如果在循环中使用 + 拼接,相当于不断创建新的 StringBuilder 对象再转换成 String 对象,效率极差。

② 使用 String 的 concat 方法,该方法使用 Arrays.copyOf 创建一个新的字符数组 buf 并将当前字符串 value 数组的值拷贝到 buf,之后调用 getChars 方法用 System.arraycopy 将拼接字符串的值也拷贝到 buf,最后用 buf 作为构造参数 new 一个新的 String 对象返回。效率稍高于直接使用 +

③ 使用 StringBuilder 或 StringBuffer,两者的 append 方法都继承自 AbstractStringBuilder,该方法首先使用 Arrays.copyOf 确定新的字符数组容量,再调用 getChars 方法用 System.arraycopy 将新的值追加到数组。StringBuilder 是 JDK5 引入的,效率高但线程不安全,StringBuffer 使用 synchronized 保证线程安全。


面向对象

面向对象

面向过程是过程化思维,代码松散,强调流程化,开发时软件维护困难,耦合严重;面向对象更适合解决大规模问题,强调高内聚、低耦合,先抽象模型定义共性行为,再解决问题。

封装是对象功能内聚的表现形式,在抽象基础上决定信息是否公开及公开等级。主要任务是对属性、数据、敏感行为实现隐藏,使对象关系变得简单,降低耦合。

继承用来扩展类,子类可继承父类的部分属性和行为,使模块具有复用性。

多态以封装和继承为基础,根据运行时对象实际类型使同一行为具有不同表现形式。多态指在编译层面无法确定最终调用的方法体,在运行期由 JVM 动态绑定,调用合适的重写方法。由于重载属于静态绑定,本质上重载结果是完全不同的方法,因此多态一般专指重写。


重载和重写

重载指方法名称相同,但参数列表不同,是行为水平方向不同实现。对编译器来说,方法名称和参数列表组成了一个唯一键,称为方法签名,JVM 通过方法签名决定调用哪种重载方法。不管继承关系多复杂,重载在编译时可以确定调用哪个方法,因此属于静态绑定。重载顺序:① 精确匹配。② 基本数据类型自动转换成更大表示范围。③ 自动拆箱与装箱。④ 子类向上转型。⑤ 可变参数。

重写指子类实现接口或继承父类时,保持方法签名完全相同,实现不同方法体,是行为垂直方向不同实现。元空间有一个方法表保存方法信息,如果子类重写父类的方法,方法表中的方法引用会指向子类。重写方法访问权限不能变小,返回类型和抛出的异常类型不能变大。


Object 类⭐
方法说明
equals检测对象是否相等,默认使用 == 比较,可以重写该方法自定义规则。规范:自反性、对称性、传递性、一致性、对于任何非空引用 x,x.equals(null) 返回 false。
hashCode每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals 未必相同。
toString默认打印表示对象值的一个字符串。
clone默认声明为 protected,只能由本类对象调用,且是浅拷贝。一般重写 clone 方法需要实现 Cloneable 接口并声明为 public,如果没有实现 Cloneable 接口会抛出 CloneNotSupport 异常。
finalizeGC 判断垃圾时,如果对象没有与 GC Roots 相连会被第一次标记,之后判断对象是否有必要执行 finalize 方法,有必要则由一条低调度优先级的 Finalizer 线程执行。虚拟机会触发该方法但不保证结束,防止方法执行缓慢或发生死循环。只要对象在 finalize 方法中重新与引用链相连,就会在第二次标记时移出回收集合。由于运行代价高且具有不确定性,在 JDK9 标记为过时方法。
getClass返回对象所属类的 Class 对象。
wait阻塞持有该对象锁的线程。
notify唤醒持有该对象锁的线程,notify 随机唤醒一个线程,notifyAll 唤醒全部线程。

内部类

内部类可对同一包中其他类隐藏,内部类方法可以访问定义这个内部类的作用域中的数据,包括 private 数据。

内部类是一个编译器现象,与虚拟机无关。编译器会把内部类转换成常规的类文件,用 $ 分隔外部类名与内部类名,其中匿名内部类使用数字编号,虚拟机对此一无所知。

静态内部类: 属于外部类,只加载一次。作用域仅在包内,可通过 外部类名.内部类名 直接访问,只能访问外部类所有静态属性和方法。HashMap 的 Node 节点,ReentrantLock 中的 Sync 类都是静态内部类。

成员内部类: 属于外部类的每个对象,随对象一起加载。不可以定义静态成员和方法,可访问外部类的所有内容。

局部内部类: 定义在方法内,不能声明访问修饰符,只能定义实例成员变量和实例方法,作用范围仅在声明类的代码块中。

匿名内部类: 只用一次的没有名字的类,可以简化代码,创建的对象类型相当于 new 的类的子类类型。用于实现事件监听和其他回调。


访问权限控制符
访问权限控制符本类包内包外子类任何地方
public
protected×
××
private×××

接口和抽象类

接口和抽象类对实体类进行更高层次的抽象,仅定义公共行为和特征。

语法维度抽象类接口
成员变量无特殊要求默认 public static final 常量
构造方法有构造方法,不能实例化没有构造方法,不能实例化
方法抽象类可以没有抽象方法默认 public abstract,JDK8 支持默认/静态方法,JDK9 支持私有方法。
继承单继承多继承

抽象类是 is-a 关系,接口是 can-do 关系。与接口相比,抽象类通常是对同类事物相对具体的抽象。

抽象类是模板式设计,包含一组具体特征,例如汽车的底盘、控制电路等是抽象出来的共同特征,但内饰、显示屏、座椅可以根据不同级别配置存在不同实现。

接口是契约式设计,是开放的,定义了方法名、参数、返回值、抛出的异常类型,谁都可以实现它,但必须遵守约定。例如所有车辆都必须实现刹车这种强制规范。

接口是顶级类,抽象类在接口下面的第二层,对接口进行组合,然后实现部分接口。当纠结定义接口和抽象类时,推荐定义为接口,遵循接口隔离原则,按维度划分成多个接口,再利用抽象类去实现,方便扩展和重构。


集合

ArrayList⭐

ArrayList 是容量可变列表,使用数组实现,扩容时会创建更大的数组,把原有数组复制到新数组。支持对元素的随机访问,但插入与删除速度慢。ArrayList 实现了 RandomAcess 接口,如果类实现了该接口,使用索引遍历比迭代器更快。

elementData 是 ArrayList 的数据域,被 transient 修饰,序列化时调用 writeObject 写入流,反序列化时调用 readObject 重新赋值到新对象的 elementData。原因是 elementData 容量通常大于实际存储元素的数量,所以只需发送真正有值的元素。

size 是当前实际大小,小于等于 elementData 的大小。

modCount 记录了 ArrayList 结构性变化的次数,继承自 AbstractList。expectedModCount 是迭代器初始化时记录的 modCount 值,每次访问新元素时都会检查 modCount 是否等于 expectedModCount,不等将抛出异常。这种机制叫 fail-fast,所有集合类都有。


LinkedList⭐

LinkedList 本质是双向链表,与 ArrayList 相比增删速度更快,但随机访问慢。除继承 AbstractList 外还实现了 Deque 接口,该接口具有队列和栈的性质。成员变量被 transient 修饰,原理和 ArrayList 类似。

包含三个重要的成员:size、first 和 last。size 是双向链表中节点的个数,first 和 last 分别指向首尾节点。

优点:可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率高。


Set

Set 元素不重复且无序,常用实现有 HashSet、LinkedHashSet 和 TreeSet。

HashSet 通过 HashMap 实现,HashMap 的 Key 即 HashSet 存储的元素,所有 Key 都使用相同的 Value ,一个 Object 类型常量。使用 Key 保证元素唯一性,但不保证有序性。HashSet 判断元素是否相同时,对于包装类型直接按值比较,对于引用类型先比较 hashCode,不同则代表不是同一个对象,相同则比较 equals,都相同才是同一个对象。

LinkedHashSet 继承自 HashSet,通过 LinkedHashMap 实现,使用双向链表维护元素插入顺序。

TreeSet 通过 TreeMap 实现的,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。


TreeMap⭐

TreeMap 基于红黑树实现,增删改查的平均和最差时间复杂度均为 O(logn) ,最大特点是 Key 有序。Key 必须实现 Comparable 接口或 Comparator 接口,所以 Key 不允许为 null。

TreeMap 依靠 Comparable 或 Comparator 排序,如果实现了 Comparator 就会优先使用 compare 方法,否则使用 Comparable 的 compareTo 方法,两者都不满足会抛出异常。

TreeMap 通过 putdeleteEntry 实现增加和删除树节点。插入新节点的规则有三个:① 需要调整的新节点总是红色的。② 如果插入新节点的父节点是黑色的,不需要调整。③ 如果插入新节点的父节点是红色的,由于红黑树不能出现相邻红色,进入循环判断,通过重新着色或左右旋转来调整。


HashMap ⭐

JDK8 前底层使用数组加链表,JDK8 改为数组加链表/红黑树,节点从 Entry 变为 Node。主要成员变量包括 table 数组、元素数量 size、加载因子 loadFactor。

table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,Node/Entry 节点包含四个成员变量:key、value、next 和 hash。

数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key 的 hash 值一样,就会发生哈希冲突,被放到同一个链表上,为使查询效率尽可能高,键的 hash 值要尽可能分散。

默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。


JDK8 之前

hash:计算元素 key 的散列值

① 处理 String 类型时,调用 stringHash32 方法获取 hash 值。

② 处理其他类型数据时,提供一个随机值 hashSeed 作为计算初始量,执行异或和无符号右移使 hash 值更加离散。


indexFor:计算元素下标

将 hash 值和数组长度-1 进行与操作,保证结果不超过 table 范围。


get:获取元素的 value 值

key 为 null,调用 getForNullKey 方法:

  • size=0 表示链表为空,返回 null。
  • size!=0 说明存在链表,遍历 table[0] 链表,如果找到了 key=null 的节点则返回其 value,否则返回 null。

key 不为 null,调用 getEntry 方法:

  • size=0 表示链表为空,返回 null 值。
  • size!=0,首先计算 key 的 hash 值,然后遍历该链表的所有节点,如果节点的 key 和 hash 值都和要查找的元素相同则返回其 Entry 节点。 如果找到了对应的 Entry 节点,调用 getValue 方法获取其 value 并返回,否则返回 null。

put:添加元素

key 为 null,直接存入 table[0]。

key 不为 null,计算 key 的 hash 值,调用 indexFor 计算元素下标 i,遍历 table[i] 链表:

  • key 已存在,更新 value 然后返回旧 value。
  • key 不存在,将 modCount 加 1,调用 addEntry 方法增加一个节点并返回 null。

resize:扩容数组

当前容量达到了最大容量,将阈值设置为 Integer 最大值,之后扩容不再触发。

当前容量没达到最大容量,计算新的容量,将阈值设为 newCapacity x loadFactor最大容量 + 1 的较小值。创建一个容量为 newCapacity 的 Entry 数组,调用 transfer 方法将旧数组的元素转移到新数组。


transfer:转移元素

遍历旧数组的所有元素,调用 rehash 方法判断是否需要哈希重构,如果需要就重新计算元素 key 的 hash 值。

调用 indexFor 方法计算元素存放的下标 i,利用头插法将旧数组的元素转移到新数组。


JDK8

hash:计算元素 key 的散列值

如果 key 为 null 返回 0,否则就将 key 的 hashCode 方法返回值高低16位异或,让尽可能多的位参与运算,让结果的 0 和 1 分布更加均匀,降低哈希冲突概率。


put:添加元素

调用 putVal 方法添加元素:

  • 如果 table 为空或不存在元素就进行扩容,否则计算元素下标位置,不存在就调用 newNode 创建一个节点。
  • 如果存在元素且是链表类型,如果首节点和待插入元素相同,直接更新节点 value。
  • 如果首节点是 TreeNode 类型,调用 putTreeVal 方法增加一个树节点,每一次都比较插入节点和当前节点的大小,待插入节点小就往左子树查找,否则往右子树查找,找到空位后执行两个方法:balanceInsert 方法,插入节点并调整平衡、moveRootToFront 方法,由于调整平衡后根节点可能变化,需要重置根节点。
  • 如果都不满足,遍历链表,根据 hash 和 key 判断是否重复,决定更新 value 还是新增节点。如果遍历到了链表末尾则添加节点,如果达到建树阈值 7,还需要调用 treeifyBin 把链表重构为红黑树。
  • 存放元素后将 modCount 加 1,如果 ++size > threshold ,调用 resize 扩容。

get :获取元素的 value 值

调用 getNode 方法获取 Node 节点:

  • 如果数组不为空且存在元素,先比较第一个节点和要查找元素,如果相同则直接返回。

  • 如果第二个节点是 TreeNode 类型则调用 getTreeNode 方法进行查找。

  • 都不满足,遍历链表根据 hash 和 key 查找,如果没有找到就返回 null。

  • 如果节点不是 null 就返回其 value,否则返回 null。


resize:扩容数组

重新规划长度和阈值,如果长度发生了变化,部分数据节点也要重新排列。

重新规划长度

① 如果当前容量 oldCap > 0 且达到最大容量,将阈值设为 Integer 最大值,终止扩容。

② 如果未达到最大容量,当 oldCap << 1 不超过最大容量就扩大为 2 倍。

③ 如果都不满足且当前扩容阈值 oldThr > 0,使用当前扩容阈值作为新容量。

④ 否则将新容量置为默认初始容量 16,新扩容阈值置为 12。

重新排列数据节点

① 如果节点为 null 不进行处理。

② 如果节点不为 null 且没有 next 节点,通过节点的 hash 值和 新容量-1 进行与运算计算下标存入新的 table 数组。

③ 如果节点为 TreeNode 类型,调用 split 方法处理,如果节点数 hc 达到 6 会调用 untreeify 方法转回链表。

④ 如果是链表节点,需要将链表拆分为 hash 值超出旧容量的链表和未超出容量的链表。对于hash & oldCap == 0 的部分不需要做处理,否则需要放到新的下标位置上,新下标 = 旧下标 + 旧容量。


线程不安全

JDK7 存在死循环和数据丢失问题。

数据丢失:

  • 并发赋值被覆盖:createEntry 方法中,新添加的元素放在头部,使元素可以被更快访问,但如果两个线程同时执行到此处,会导致数据覆盖。

  • 新表被覆盖: 如果多线程同时 resize ,每个线程都会 new 一个数组,这是线程内的局部对象,线程间不可见。迁移完成后resize 的线程会赋值给 table 线程共享变量,可能会覆盖其他线程的操作,在新表中插入的对象都会被丢弃。

死循环: 扩容时 resize 调用 transfer 使用头插法迁移元素,虽然 newTable 是局部变量,但原先 table 中的 Entry 链表是共享的,问题根源是 Entry 的 next 指针并发修改,某线程还没有将 table 设为 newTable 时用完了 CPU 时间片。

JDK8 在 resize 方法中完成扩容,并改用尾插法,不会产生死循环,但并发下仍可能丢失数据。可用 ConcurrentHashMap 或 Collections.synchronizedMap 包装同步集合。


IO 流

BIO

BIO 是同步阻塞式 IO,JDK1.4 前的 IO 模型。服务器实现模式为一个连接请求对应一个线程,服务器需要为每一个客户端请求创建一个线程,如果这个连接不做任何事会造成不必要开销。可以通过线程池改善,称为伪异步 IO。适用连接数目少且服务器资源多的场景。


NIO

NIO 是 JDK1.4 引入的同步非阻塞 IO。服务器实现模式为多个连接请求对应一个线程,客户端连接请求会注册到一个多路复用器 Selector ,Selector 轮询到连接有 IO 请求时才启动一个线程处理。适用连接数目多且连接时间短的场景。

同步是指线程还是要不断接收客户端连接并处理数据,非阻塞是指如果一个管道没有数据,不需要等待,可以轮询下一个管道。

核心组件:

  • Selector: 多路复用器,轮询检查多个 Channel 的状态,判断注册事件是否发生,即判断 Channel 是否处于可读或可写状态。使用前需要将 Channel 注册到 Selector,注册后会得到一个 SelectionKey,通过 SelectionKey 获取 Channel 和 Selector 相关信息。

  • Channel: 双向通道,替换了 BIO 中的 Stream 流,不能直接访问数据,要通过 Buffer 来读写数据,也可以和其他 Channel 交互。

  • Buffer: 缓冲区,是一块可读写数据的内存。Buffer 三个重要属性:position 下次读写数据的位置,limit 本次读写的极限位置,capacity 最大容量。

    • flip 将写转为读,底层实现原理把 position 置 0,并把 limit 设为当前的 position 值。
    • clear 将读转为写模式(用于读完全部数据的情况,把 position 置 0,limit 设为 capacity)。
    • compact 将读转为写模式(用于存在未读数据的情况,让 position 指向未读数据的下一个)。
    • 通道方向和 Buffer 方向相反,读数据相当于向 Buffer 写,写数据相当于从 Buffer 读。

    使用步骤:向 Buffer 写数据,调用 flip 方法转为读模式,从 Buffer 中读数据,调用 clear 或 compact 方法清空缓冲区。


AIO

AIO 是 JDK7 引入的异步非阻塞 IO。服务器实现模式为一个有效请求对应一个线程,客户端的 IO 请求都是由操作系统先完成 IO 操作后再通知服务器应用来直接使用准备好的数据。适用连接数目多且连接时间长的场景。

异步是指服务端线程接收到客户端管道后就交给底层处理IO通信,自己可以做其他事情,非阻塞是指客户端有数据才会处理,处理好再通知服务器。

实现方式包括通过 Future 的 get 方法进行阻塞式调用以及实现 CompletionHandler 接口,重写请求成功的回调方法 completed 和请求失败回调方法 failed


java.io

主要分为字符流和字节流,字符流一般用于文本文件,字节流一般用于图像或其他文件。

字符流包括了字符输入流 Reader 和字符输出流 Writer,字节流包括了字节输入流 InputStream 和字节输出流 OutputStream。字符流和字节流都有对应的缓冲流,字节流也可以包装为字符流,缓冲流带有一个 8KB 的缓冲数组,可以提高流的读写效率。除了缓冲流外还有过滤流 FilterReader、字符数组流 CharArrayReader、字节数组流 ByteArrayInputStream、文件流 FileInputStream 等。


序列化

Java 对象在 JVM 退出时会全部销毁,如果需要将对象持久化就要通过序列化实现,将内存中的对象保存在二进制流中,需要时再将二进制流反序列化为对象。对象序列化保存的是对象的状态,因此属于类属性的静态变量不会被序列化。

常见的序列化有三种:

  • Java 原生序列化

    实现 Serializabale 标记接口,兼容性最好,但不支持跨语言,性能一般。

    序列化和反序列化必须保持序列化 ID 的一致,一般使用 private static final long serialVersionUID 定义序列化 ID,如果不设置编译器会根据类的内部实现自动生成该值。

  • Hessian 序列化

    支持动态类型、跨语言,对象序列化的二进制流可以被其它语言反序列化。特性:① 自描述序列化类型,不依赖外部描述文件。② 语言无关,支持脚本语言。③ 协议简单,比 Java 原生序列化高效。

  • JSON 序列化

    JSON 序列化就是将数据对象转换为 JSON 字符串,在序列化过程中抛弃了类型信息,反序列化时只有提供类型信息才能准确进行。相比前两种方式可读性更好,方便调试。

序列化通常使用网络传输对象,容易遭受攻击,Jackson 和 fastjson 都出现过反序列化漏洞,因此不需要进行序列化的敏感属性应加上 transient 关键字。transient 的作用是把变量生命周期仅限于内存,不会写到磁盘,变量会被设为对应数据类型的零值。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值