Java基础笔记

JAVA

目录参考链接: Java 工程师成神之路 | 2019正式版

一、基础篇

1 面向对象

1.1 面向对象

面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。

1.1.1 三大特性
  • 封装
    隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
  • 继承
  • 多态
1.1.2 五大基本原则
  • 单一职责原则SRP(Single Responsibility Principle)
  • 开放封闭原则OCP(Open-Close Principle)
  • 里式替换原则LSP(the Liskov Substitution Principle LSP)
  • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
    高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
  • 接口分离原则ISP(the Interface Segregation Principle ISP)

1.2 平台无关性

1.2.1 Java 如何实现的平台无关

通过编译成字节码,在JVM上执行

1.2.2 JVM 还支持哪些语言

Kotlin、Groovy、JRuby、Jython、Scala

1.3 值传递

  • 值传递
    方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。

  • 引用传递
    也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;
    在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。

1.4 封装、继承、多态

1.4.1 什么是多态、方法重写与重载
  • 多态
    多态是指一个程序中同名的不同方法共存的情况。
    方法的重写Overriding和重载Overloading是Java多态性的不同表现。

  • 重载
    方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。
    重载Overloading是一个类中多态性的一种表现。
    重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。

  • 重写
    父类与子类之间的多态性,对父类的函数进行重新定义。父类与子类对同一个方法的具有不同实现。参数的类型和个数以及返回类型都必须相同。

1.4.2 Java的抽象类和接口
  • 抽象类
    如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。抽象类主要是对多个子类的相同属性的抽象
    Java中只能单继承,也就是说一个类只有一个父类,即只有一个爸爸。

  • 接口
    多个类具有相同功能或方法,而不涉及相同属性时,对相同方法的抽象定义。

  • 抽象类和接口的区别
    1.抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    2.抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    3.接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
    4.一个类只能继承一个抽象类,而一个类却可以实现多个接口。

1.4.3 Java的继承与实现
  • 继承
    子类通过继承抽象类或父类,拥有抽象类或父类的属性和方法。
  • 实现
    类通过implements实现接口中的方法,并且必须全部实现。

1.5 构造函数与默认构造函数

构造函数的主要作用是完成对象的初始化工作。默认构造函数不包含任何参数。当类中无构造方法时,隐式存在。有参数的构造函数在定义对象时把参数传给对象的域。
构造函数的名称必须与类名相同,包括大小写;
构造方法可以重载,以参数的个数,类型,顺序。

1.6 类变量、成员变量和局部变量

  • 类变量
    即静态变量,被static修饰,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;存放在方法区
  • 成员变量
    定义在方法外,类中。必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。
    成员变量在所在类被实例化后,存在堆内存中。
  • 局部变量
    在方法中定义,只存在具体的方法中,作用域为整个方法。
    局部变量在所在方法调用时,存在栈内存空间中。

静态变量可以实现让多个对象共享内存。

2 Java 基础知识

2.1 基本数据类型

  • 整型
    byte, short, int, long
  • 浮点型
    double, float
  • 字符型
    char
  • 布尔型
    boolean
基本数据类型长度
byte8
short16
int32
long64
float32
double64
char16(无符号)
booleantrue or false
2.1.1 什么是浮点型

相对于定点数而言,浮点数利用指数使小数点的位置可以根据需要而上下浮动,从而可以灵活地表达更大范围的实数。

2.1.2 单精度和双精度

单精度和双精度精确的范围不一样,在内存中占用的字节不一样
Java中单精度32位,双精度64位
单精度
双精度

2.1.3 为什么不能用浮点型表示金额

浮点数不精确的根本原因在于尾数部分的位数是固定的,一旦需要表示的数字的精度高于浮点数的精度,那么必然产生误差!这在处理金融数据的情况下是绝对不允许存在的。参考链接
JDK提供了一个BigDecimal的类,这个类可以表示任意精度的数字。

2.2 语法糖

语法糖可以看做是编译器实现的的一些“小把戏”,这些“小把戏”可能会使得效率“大提升”。
语法糖虽然不会提供实质性的功能改进,但是他们或能提高效率,或能提示语法的严谨性,或能减少编码出错的机会。
参考:深入理解Java虚拟机

2.2.1 自动拆装箱

自动拆装箱和遍历循环(Foreach循环)是Java语言中使用得最多的语法糖。

  • 包装类
    包装类型出现的原因 :Java是一个面向对象的语言,基本类型并不具有对象的性质,为了与其他对象“接轨”就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。参考链接

  • 自动装箱
    将基本数据类型自动转换成对应的包装类。

  • 自动拆箱
    就是将包装类自动转换成对应的基本数据类型。

  • Integer 的缓存机制
    JVM对小数(-128~127)是直接在初始化时存放在内存中,当Integer对象初始化直接使用的是内存中的对象。

包装对象的数值比较,不能简单的使用==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。

前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。

如果一个for循环中有大量拆装箱操作,会浪费很多资源。

关于自动拆装箱详细介绍文章

2.3 String类

2.3.1 字符串的不可变性

从数据结构上看:String的实质是基本类型char数组,数组在初始化申请空间时便已经确定,改变一个对象数组长度,只能重新申请新的数组,将指针指向新的数组。String类对字符串修改操作实质亦是如此。
从源码中定义上看,char数组使用了final关键字修饰。

2.3.2 substring 的原理

当截取长度不为0或者不等于原字符串,返回一个新的String对象,其中数组value的内容为目标截取子串。

// JDK1.8
public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
2.3.2 replaceFirst、replaceAll、replace 区别

从替换效果来看:replaceFirst只会替换第一个,replaceAll和replace会替换整个字符串中所有出现的。
从底层源码分析:replace只做字符匹配,从前往后查找。而replaceFirst、replaceAll调用Pattern类做正则匹配替换

2.3.3 String 对“+”的重载

Java本身是支持对运算符重载,Java中String类的“+”运算在实现过程中调用StringBuilder类实现的。

2.3.4 String.valueOf 和 Integer.toString 的区别
  • String.valueOf
    String的valueof方法是将各种类型转换成String.
    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

对整数的转换是直接调用Integer.toString()方法

    public static String valueOf(int i) {
        return Integer.toString(i);
    }
  • Integer.toString
    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

所以实质上对整型的转换没区别

2.3.5 switch 对 String 的支持

在JDK 7 之前,switch 只能支持 byte、short、char、int 这几个基本数据类型和其对应的封装类型;switch后面的括号里面只能放int类型的值,但由于byte,short,char类型,它们会 自动 转换为int类型(精精度小的向大的转化),所以它们也支持。
JDK 7之后,switch开始String的支持。

2.3.6 字符串池(String Pool)

创建String字符串对象时,采用字面值的方式创建一个字符串时,首先在JVM的字符串池(严格来说叫做常量池,因为JVM并不只是存放字符串对象)中查找是否存在该对象,存在直接将引用指向该对象,否则将会创建新的字符串对象到字符串池中。
字符串池的优缺点:字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。参考链接

2.3.7 intern

在jdk1.7之前,字符串常量存储在方法区的PermGen Space。在jdk1.7之后,字符串常量重新被移到了堆中。
通俗来说:调用了intern()方法时,会强制先去常量池中查找字符串对象,返回其指针,当不存在时则创建,同时加入常量池。

    /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
     public native String intern();

详细阅读 Java-String.intern的深入研究

2.4 Java 中的关键字

  • transient
    如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。即用transient关键字标记的成员变量不参与序列化过程。
    常见在集合框架中理解Java集合框架里面的的transient关键字
    HashMap中实现序列化和反序列化的原因
    在HashMap要定义自己的序列化和反序列化实现,有一个重要的因素是因为hashCode方法是用native修饰符修饰的,也就是用它跟jvm的运行环境有关,即不同的jvm虚拟机对于同一个key产生的hashCode可能是不一样的,所以数据的内存分布可能不相等了。

  • instanceof
    实质为二元操作符(运算符),作用是判断其左边对象是否为其右边类的实例,返回boolean值。

  • final
    final可以修饰变量,方法和类,用于表示所修饰的内容一旦赋值之后就不会再被改变,当修饰实例对象时,指向对象的引用不会改变,但是实例的内容其实是可以改变的。
    详细参考

  • static

  • volatile
    volatile具有可见性、有序性,不具备原子性。
    表明声明的变量每次操作时必须从主内存中读写,使得其在多线程之间可见。
    使用volatile声明的变量,编译器会保证其有序性。

  • synchronized
    同步关键字,作用:
    (1)确保线程互斥的访问同步代码
    (2)保证共享变量的修改能够及时可见
    (3)有效解决重排序问题。

  • const
    保留关键字

2.5 集合类

2.5.1 ArrayList、LinkedList和Vector

都继承AbstractList

  • 存储结构
    ArrayList和Vector基于数组的按顺序存储
    LinkedList基于链表的存储结构

  • 线性安全
    ArrayList和LinkedList线程不安全

集合存储结构线程安全效率
ArrayList数组适合在末尾增加删除元素或者查找指定位置
LinkedList链表适合在指定位置插入、删除元素
Vector数组适合在末尾增加删除元素或者查找指定位置

在单线程的条件下,不建议使用Vector,因为额外的锁开销较大,毕竟synchronized为重量级同步。

2.5.2 Collections内部类SynchronizedList 和 Vector
集合同步方式
SynchronizedList同步代码块
Vector同步方法
  • 区别
    1)SynchronizedList有很好的扩展和兼容功能。可以将所有的List的子类转成线程安全的类。
    2)使用SynchronizedList的时候,进行遍历时要手动进行同步处理。其中listIterator和listIterator(int index)并没有做同步处理,使用SynchronizedList进行遍历的时候要手动加锁。
    3)SynchronizedList可以指定锁定的对象。
    4)add方法的扩容机制不一样。

同步代码块和同步方法的区别: 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
参考链接

2.5.3 HashMap、Hashtable和ConcurrentHashMap

参考链接

2.5.4 List和Set
集合特点
List可重复,保持元素增加顺序
Set唯一,不可重复,且无序

关于List和Set的子类比较:参考链接

  • Set 如何保证元素不重复
    HashSet内部元素使用HashMap,Set的值作为key,所有value为“PRESENT”,Map中保证key唯一。
2.5.5 Java8 Stream

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

详细介绍:Java 8 中的 Streams API 详解

2.5.6 apache 集合处理工具类的使用

参考链接:Apache Commons 工具类介绍及简单使用

2.5.7 Collection 和 Collections
  • Collection 是一个集合接口(集合类的一个顶级接口)
    提供了对集合对象进行基本操作的通用接口方法。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
    Collections详细介绍:Java中Collection和Collections的区别
2.5.8 Arrays.asList()

错误一: 将基本数据类型数据的数组作为参数
关于源码定义:

    /**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

其中需要注意参数类型T,T必须是Object的数组对象,所以传入基本数据类型数组时,asList 的真正得到的参数就不是数组中的元素,而是数组对象本身。

System.out.println(Arrays.asList(new int[]{1, 2}));
[[I@16b98e56]

解决方案:使用包装类数组

错误二:试图修改 List 的大小
在源码定义的注释中写到:创建的是一个固定的list。
asList返回的是其内部类ArrayList,数据元素数组使用了final修饰。所以使用asList得到的list不能够增删操作。
解决方案:创建一个真正的 ArrayList;
参考链接:Arrays.asList使用指南 | Java Array to List Examples

2.5.9 Enumeration 和 Iterator
  • Enumeration
public interface Enumeration<E> {

    boolean hasMoreElements();

    E nextElement();
}
  • Iterator
public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
  • JDK8 注释内容:
    Iterators differ from enumerations in two ways:
    1)Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
    2)Method names have been improved.

  • 主要区别
    1)在接口功能上,Iterator能够修改集合,可以删除元素。Enumeration则不支持;
    2)Iterator的实现类中支持fail-fast机制,而Enumeration不支持。

2.5.10 fail-fast 和 fail-safe
  • fail-fast
    是一种错误检测机制。在集合中,如HashMap中,有modCount变量,表示集合修改次数,当集合遍历时,会记录expectedmodCount,如果集合发生改变即modCount != expectedmodCount时,如新增或删除了数据,则中断遍历,抛出异常。
    java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

  • fail-safe
    采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

参考链接:Java快速失败(fail-fast)和安全失败(fail-safe)区别

2.5.11 CopyOnWriteArrayList、ConcurrentSkipListMap
  • CopyOnWrite容器
    CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
    参考链接:聊聊并发-Java中的Copy-On-Write容器

  • ConcurrentSkipListMap
    ConcurrentSkipListMap的底层是通过跳表来实现的。跳表(SkipList)是一种随机化的数据结构,通过“空间来换取时间”的一个算法,建立多级索引,实现以二分查找遍历一个有序链表。时间复杂度等同于红黑树,O(log n);实现却远远比红黑树要简单.

2.6 枚举

  • 用法
    • 常量
    • switch
    • 向枚举中添加新方法
    • 覆盖枚举的方法
    • 实现接口
    • 使用接口组织枚举

参考链接:枚举常见的七种用法

2.6.1 枚举与单例
  • 枚举实现单例:
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}  
  • “双重校验锁”实现单例:
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
} 

上面的双重锁校验的代码之所以很臃肿,是因为大部分代码都是在保证线程安全。为了在保证线程安全和锁粒度之间做权衡,代码难免会写的复杂些。但是,这段代码还是有问题的,因为他无法解决反序列化会破坏单例的问题。
枚举可避免反序列化破坏单例,具体详细介绍查看参考链接为什么我墙裂建议大家使用枚举来实现单例

2.6.2 Java 枚举如何比较

当调用equals()方法时,实质使用的也是==,所以枚举的比较实际上是比较两个枚举对象的内存地址

public final boolean equals(Object other) {
    return this==other;
}
2.6.3 switch 对枚举的支持

JDK1.5后开始支持,并且switch判断的是枚举类的ordinal,即枚举值的序列值。参考链接:JDK语法糖之switch字串与枚举支持

2.6.4 枚举的线程安全性问题

Enum中属性和方法都是static类型的,因为static类型的属性会在类被加载之后被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。详细介绍深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

2.7 IO

2.7.1 流

流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道。

根据流传输的方向:

  • 输入流
    输入流是相对程序而言的,外部传入数据给程序需要借助输入流。
  • 输出流
    输出流是相对程序而言的,程序把数据传输到外部需要借助输出流。

根据流传输过程单位:

  • 字节流
    传输过程中,传输数据的最基本单位是字节的流。
  • 字符流
    传输过程中,传输数据的最基本单位是字符的流。

本质其实就是基于字节流读取时,去查了指定的码表。

字符编码方式不同,有时候一个字符使用的字节数也不一样,比如ASCLL方式编码的字符,占一个字节;而UTF-8方式编码的字符,一个英文字符需要一个字节,一个中文需要三个字节。

字节数据是二进制形式的,要转成我们能识别的正常字符,需要选择正确的编码方式。我们生活中遇到的乱码问题就是字节数据没有选择正确的编码方式来显示成字符。

从本质上来讲,写数据(即输出)的时候,字节也好,字符也好,本质上都是没有标识符的,需要去指定编码方式。

但读数据的时候,如果我们需要去“看数据”,那么字节流的数据需要指定字符编码方式,这样我们才能看到我们能识别的字符;而字符流,因为已经选择好了字符编码方式,通常不需要再改了(除非定义的字符编码方式与数据原有的编码方式不一致!)

在传输方面上,由于计算机的传输本质都是字节,而一个字符由多个字节组成,转成字节之前先要去查表转成字节,所以传输时有时候会使用缓冲区
详细介绍:Java:字节流和字符流(输入流和输出流)

2.7.2 字节流和字符流的区别
  • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
  • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
  • 字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件
2.7.3 同步、异步、阻塞、非阻塞

同步和异步:与消息的通知机制有关

  • 同步
    在结果未返回前,一直等待。
  • 异步
    异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

阻塞与非阻塞:与线程等待消息(无所谓同步或者异步)时的状态有关

  • 阻塞
    阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
    与同步调用对比,同步很多时候当前线程还是激活的。
  • 非阻塞
    非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
2.7.4 Linux 五种IO模型
  • 同步阻塞IO
    应用程序执行一个系统调用,应用程序会一直阻塞,直到系统调用完成为止(数据传输完成或发生错误),调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态。同步阻塞 I/O 模型的典型流程

  • 同步非阻塞I/O
    在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK)
    同步非阻塞 I/O 模型的典型流程
    非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成(轮询)。

  • 异步阻塞IO
    另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。
    异步阻塞IO模型的典型流程(select)

  • 异步非阻塞IO
    异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
    异步非阻塞IO模型的典型流程
    在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。
    详细介绍:使用异步 I/O 大大提高应用程序的性能

2.7.5 BIO、NIO 和 AIO

Java中提供的IO有关的API,在文件处理的时候,其实依赖操作系统层面的IO操作实现的。

  • BIO 同步阻塞I/O模式
    在数据读写时,线程必须停止等待其完成后继续。一般表现为进程或线程等待某个条件,如果条件不满足,则一直等下去。
    同步阻塞I/O模式

  • NIO 同步非阻塞的I/O模型
    应用进程与内核交互,目的未达到之前,不再一味的等着,而是直接返回。然后通过轮询的方式,不停的去问内核数据准备有没有准备好。如果某一次轮询发现数据已经准备好了,那就把数据拷贝到用户空间中。
    同步非阻塞的I/O模型
    应用进程通过 recvfrom 调用不停的去和内核交互,直到内核准备好数据。如果没有准备好,内核会返回error,应用进程在得到error后,过一段时间再发送recvfrom请求。在两次发送请求的时间段,进程可以先做别的事情。

  • AIO 异步I/O模型
    映射到Linux操作系统中,这就是异步IO模型。应用进程把IO请求传给内核后,完全由内核去操作文件拷贝。内核完成相关操作后,会发信号告诉应用进程本次IO已经完成。
    异步I/O模型
    用户进程发起aio_read操作之后,给内核传递描述符、缓冲区指针、缓冲区大小等,告诉内核当整个操作完成时,如何通知进程,然后就立刻去做其他事情了。当内核收到aio_read后,会立刻返回,然后内核开始等待数据准备,数据准备好以后,直接把数据拷贝到用户控件,然后再通知进程本次IO已经完成。
    详细介绍:漫话:如何给女朋友解释什么是Linux的五种IO模型?

2.7.6 Netty

Netty介绍:

2.8 反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能为Java语言的反射机制。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
参考链接:Java高级特性——反射

反射机制的缺点:反射的开销较大。

2.8.1 反射与工厂模式

Spring中的IoC的实现原理就是工厂模式加反射机制。
(待补充)

2.8.2 Class类

Class类的实例表示正在运行的Java应用程序中的类和接口。 即,对所有JVM中的类的一个描述记录。Class类无公共构造函数,所有实例在类加载器加载类的时候自动创建对应实例。

Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。参考链接:Java中Class类详解、用法及泛化

2.8.3 java.lang.reflect.*

java.lang.reflect包提供了用于获取类和对象的反射信息的类和接口。反射API允许对程序访问有关加载类的字段,方法和构造函数的信息进行编程访问。它允许在安全限制内使用反射的字段,方法和构造函数对其底层对等进行操作。

类名类别作用
AccessibleObjectClassField,Method和Constructor类对象的基类
ArrayClass通过反射创建一个arrays对象
ConstructorClass提供了一个类的单个构造函数的信息和访问权限
ExecutableClass超类
FieldClass提供有关类或接口的单个字段的信息和动态访问

以后再看源码整理吧,找了一个详细的参考链接:JAVA中反射机制六(java.lang.reflect包)

2.9 代理

  • 静态代理
    在程序运行前就已经存在的编译好的代理类是为静态代理
  • 动态代理
    在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能是为动态代理,即代理类的字节码将在运行时生成并载入当前的ClassLoader。

代理模式的目的是为真实业务对象提供一个代理对象,使用代理对象控制真实业务对象,满足在不同业务逻辑下使用不同真实业务对象。
换种理解方式:在调用者与被调用者中间加入了代理对象,增加了一层,实现一些额外的功能,如AOP中before、after等

代理对象的作用
1)代理对象存在的价值主要用于拦截对真实业务对象的访问;
2)代理对象具有和目标对象(真实业务对象)实现共同的接口或继承于同一个类;
3)代理对象是对目标对象的增强,以便对消息进行预处理和后处理;
4)实现延迟加载。

2.9.1 动态代理与反射机制

动态代理的特点是在运行中创建代理类,通过反射机制获取动态代理类的构造函数。
代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。参考链接深入Java机制(一)–反射机制和动态代理机制

2.9.2 代理的实现方式
2.9.3 AOP

面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待。
spring aop介绍:Spring AOP详细介绍

2.10 序列化

对象序列化:是一个将对象状态转换成字节流的过程,可以将其保存到磁盘文件中或者通过网络发送到任何其他程序中使用。
反序列化:从字节流中创建对象的相反过程。
而创建的字节流是与平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。

2.10.1 为什么序列化

数据在磁盘中存储或者网络传输中,基本都是二进制的字节或字节流,无法保存一个实质的对象格式存储,同时,不同机器计算的hashcode是不相同的,所以序列化过程是既方便存储或传输,同时也是屏蔽不同机器的差异。

2.10.2 序列化底层原理

序列化算法一般会按步骤做如下事情:
1)将对象实例的类元数据输出;
2)递归输出类的超类描述直到不再有超类;
3)类元数据输出结束hou后,从最顶层的超类开始输出对象实例的实际数据值;
4)从上至下递归输出实例的数据。
详细介绍:Java序列化机制原理

2.10.3 父类的序列化

情景:个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就需要有默认的无参的构造函数。 在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都 是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。参考链接:深入理解JAVA序列化

2.10.4 序列化与单例模式

在反序列化过程中,调用的是类的无参构造函数,创建了新的对象,这个对象与单例模式中的对象并非同一个,破坏了单例结构。
解决方案:

  • 增加readResolve()方法:无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。详细介绍:深度解析单例与序列化之间的爱恨情仇~
  • 使用枚举类实现单例(推荐使用):详细阅读Enum(枚举)
2.10.5 Protobuf

What are protocol buffers?
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

什么是protocol buffers?
Protocol Buffers是一种平台无关、语言无关、可扩展机制的序列化数据结构。相比于XML,更小,更快和更简洁。您可以定义数据的结构化结构,然后使用特殊生成的源代码轻松地将结构化数据写入和读取各种数据流,并使用各种语言。

**ProtoBuf 是否就等同于 XML 和 JSON 呢,它们是否具有完全相同的应用场景呢?**详细介绍:深入 ProtoBuf - 简介

2.10.6 为什么说序列化并不安全

因为序列化的本质是将对象转换成二进制流,对于接受端,如果无法对反序列化后的对象做完整性校验,就无法得知对象是否被篡改等,即是否安全。
防御反序列化攻击:最常见的例子之一就是JWT。
参考链接:不安全的反序列化

2.11 注解(Annotation)

注解其实就是代码里的特殊标记,它用于替代配置文件:传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。

2.11.1 元注解(meta-annotation)
  • @Target
    描述注解的使用范围
  • @Retention
    描述注解保留的时间范围
  • @Documented
    描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
  • @Inherited
    使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)
    参考链接:JAVA核心知识点–元注解详解
2.11.2 自定义注解

了解一下@Autowired、@RequestMapping、@RequestParam等Spring框架中注解原理,感受下吧。
AOP+反射+自定义注解案例:自定义注解以及其结合AOP和反射的使用

2.12 JMS

Java Message Service,Java消息服务是指两个应用程序之间进行异步通信API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持JAVA应用程序开发。

2.12.1 JMS 优势
  • 异步
    JMS天生就是异步通信,客户端获取消息不需要主动发送请求,消息会自动发送给可用的客户端。
  • 可靠
    JMS保证消息只会递送一次。大家都遇到过重复创建消息问题,而JMS能帮你避免该问题。
2.12.2 JMS消息传送模型

在JMS API出现之前,大部分产品使用“点对点”和“发布/订阅”中的任一方式来进行消息通讯。JMS定义了这两种消息发送模型的规范,它们相互独立。任何JMS的提供者可以实现其中的一种或两种模型,这是它们自己的选择。JMS规范提供了通用接口保证我们基于JMS API编写的程序适用于任何一种模型。

  • 点对点消息传送模型
    在点对点消息传送模型中,应用程序由消息队列,发送者,接收者组成。每一个消息发送给一个特殊的消息队列,该队列保存了所有发送给它的消息(除了被接收者消费掉的和过期的消息)。点对点消息模型有一些特性:
    1)每个消息只有一个接收者;
    2)消息发送者和接收者并没有时间依赖性;
    3)当消息发送者发送消息的时候,无论接收者程序在不在运行,都能获取到消息;
    4)当接收者收到消息的时候,会发送确认收到通知(acknowledgement)。

点对点消息传送模型

  • 发布/订阅消息传递模型
    在发布/订阅消息模型中,发布者发布一个消息,该消息通过topic传递给所有的客户端。在这种模型中,发布者和订阅者彼此不知道对方,是匿名的且可以动态发布和订阅topic。topic主要用于保存和传递消息,且会一直保存消息直到消息被传递给客户端。发布/订阅消息模型特性:
    1)一个消息可以传递给多个订阅者;
    2)发布者和订阅者有时间依赖性,只有当客户端创建订阅后才能接受消息,且订阅者需一直保持活动状态以接收消息。
    3)为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。
    发布/订阅消息传递模型
    详细参考链接:JMS(Java消息服务)入门教程

2.11 JMX

Java Management Extensions,Java管理扩展是一个为应用程序植入管理功能的框架。用户可以在任何Java应用程序中使用这些代理和服务实现管理。
关于JMX详细介绍:JMX超详细解读

2.11.1 JMS 与 JMX 区别

看了JMS和JMX,总感觉两个有点类似,都是发布消息。
实质上,JMS是两个或者多个应用程序之间通信,定制了两个标准通信API;主要是用于发送与接收消息。
而JMX是为(单个或目标)应用程序、设备、系统等植入管理功能。例如,对系统某些配置项都放在配置文件properties中,可使用JMX在线动态修改,无需再重新读取配置。

2.12 泛型

泛型是JDK 1.5 的一项新增特性,它的本质是参数化类型(Parametersized Type)的应用,也就是说所有操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

2.12.1 KTVE
  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ? - 表示不确定的java类型

S、U、V - 2nd、3rd、4th types
Object跟这些标记符代表的java类型有啥区别呢?
Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。
参考链接:Java泛型中E、T、K、V等的含义

2.12.2 泛型与继承

(看了很多博客,觉得还是讲的不清楚,根本不能整理描述出来…)

2.12.3 泛型三张用法
  • 泛型类
  • 泛型方法
  • 泛型接口
2.12.4 限定通配符和非限定通配符
  • 通配符类型
    ,表示T类型应该是绑定类型及其子类型(subType),T和绑定类型可以是类或者接口。
    通配符用?表示,如:
Couple<? extends Employee>

表示Couple的泛型类型是Employee或者其子类。

  • 子类型限定:extends
public static <T extends Comparable<T>> T max(T[] array) {...}

前者 <T extends Comparable> 中T是预定义的类型参数。通过extends限定了操作的对象必须是能实现Comparable接口的类。

public static void printWife(Couple<? extends Employee> couple) {...}

对printWife方法参数Couple的类型限定,必须是Employee及其子类。

? extends Employee自身不能单独使用,可以理解为只能寄生在其他泛型类中,作为泛型类一个具体的类型参数,通常用于定义阶段。

public static ? extends Employee printWife() {...} // 错误
public static void printWife(? extends Empoyee employee) {...} // 错误

使用通配符来定义方法返回值和方法的参数类型在Java中是不允许的!

  • 超类型限定:super
    同样是用?表示,必须在泛型类的类型参数中使用。
Couple<? super Manager>
  • 无限定通配符
    无限定通配符去除了超类型和子类型的规则,仅仅用一个?表示,并且也只能用于指定泛型类的类型参数中。如Couple<?>的形式。

详细链接:JAVA泛型•通配符限定

2.12.5 List、List<?>、List的区别

每个泛型定义一个原生类型(raw type),即不带任何类型参数的类型名称。例如,与List<String>对应的原生类型是List。
泛型的子类型化的原则:List<String>类型是原生类型List的一个子类型,而不是参数化类型List<Object>的子类型

  • List
    原始类型,其引用变量可以接受任何对应List的参数化类型, 包括List<?>,并且可以添加任意类型的元素。但其缺点在于不安全性、不便利性、不表述性(不应该使用原生类型的原因)。
  • List<?>
    通配符类型,其引用变量,同样可以接受任何对应List<E>的参数化类型,包括List,但不能添加任何元素,保证了安全性和表述性。但不具有表述性,从中取出的元素时Object类型,要通过手动转换才能得到原本的类型。
  • List<Object>
    实际类型参数为Object的参数化类型,其引用变量可以接受List,可以添加元素,但不能接受除了其本身外的任何参数化类型(泛型的子类型化原则)。
引用变量的类型类别可接受类型是否能添加元素安全性便利性表述性
List原始类型List<E>, List<?>可以
List<?>通配符类型对应List的参数化类型,List不能
List<Object>实际类型参数为Object的参数化类型List, List<Object>可以

详细参考链接:关于List、List<?>、List的区别

2.13 单元测试

2.14 正则表达式

2.15 常用的 Java 工具库

2.16 API & SPI

  • API
    提供了某些功能的实现,相当于为开发人员提供了工具
  • SPI
    指定了一套实现的标准,开发人员需要安装标准去编码实现,完成功能的封装模块化。

详细介绍:高级开发必须理解的Java中SPI机制

2.17 异常

2.18 时间处理

2.19 编码方式

3 阅读源代码

4 Java 并发编程

二、底层篇

1 JVM

1.1 JVM 内存结构

1.2 Java 内存模型

1.3 垃圾回收

1.4 JVM 参数及调优

1.5 Java 对象模型

Java对象模型

1.6 HotSpot

1.7 虚拟机性能监控与故障处理工具

2 类加载机制

3 编译与反编译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值