Java核心类库
一、常用类的概述和使用
常用的包
● java.lang包 – 该包是Java语言的核心包,并且该包中的所有内容由Java虚拟机自动导入。
如:System类、String类、…
● java.util包 – 该包是Java语言的工具包,里面提供了大量工具类以及集合类等。
如:Scanner类、Random类、List集合、…
● java.io包 – 该包是Java语言中的输入输出包,里面提供了大量读写文件相关的类等。
如:FileInputStream类、FileOutputStream类、…
● java.net包 – 该包是Java语言中的网络包,里面提供了大量网络编程相关的类等。
如:ServerSocket类、Socket类、…
● java.sql 包 – 该包是Java语言中的数据包,里面提供了大量操作数据库的类和接口等。
如:DriverManager类、Connection接口、…
Object类
● java.lang.Object类是Java语言中类层次结构的根类,也就是说任何一个类都是该类的直接或者间接子类。
● 如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。Object类定义了“对象”的基本行为, 被子类默认继承。
Object类常用方法
toString()方法
//没有重写toString 输出地址值
public class TestToString {
public static void main(String[] args) {
Person p = new Person();
System.out.println(p);//输出String.Person@154617c (对象地址字符串)
}
}
class Person extends Object{
String name = "张三";
int age = 18;
}
//重写Object类中的toString方法
public class TestToString {
public static void main(String[] args) {
Person p = new Person();
System.out.println(p); //输出姓名:张三 年龄:18
}
}
class Person extends Object{
String name = "张三";
int age = 18;
@Override
public String toString() {
return "姓名:" + this.name + " 年龄:" + this.age;
}
}
hashcode()方法
hashcode()方法返回该对象的哈希码值.
public class TestHashcode{
public static void main(String[] args) {
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1.hashCode() == p2.hashCode());//false 比较哈希码值
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestHashcode{
public static void main(String[] args) {
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1.hashCode() == p2.hashCode());//true
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
equals()方法
//equals()方法比较的是地址值
public class TestEquals {
public static void main(String[] args) {
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1.equals(p2));//false
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//重写equals()方法,比较对象值
public class TestEquals {
public static void main(String[] args) {
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
System.out.println(p1.equals(p2));//true
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
注意:
● 两个对象的hashcode相同,这两个对象不一定相同
● 如果两个对象相同,那么hashcode一定相同
● 当两个元素调用equals方法相等时证明这两个元素相同,重写hashCode方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并避免重复元素的出现。
String类重写了equals()方法
//String类重写equals()方法源码
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public class Test {
public static void main(String[] args) {
//下面代码会创建几个对象?存放在哪里?
String s1 = "hello"; // 1个对象 常量池
//String(String original)会根据参数指定的字符串内容来构造对象,新创建对象为参数对象的副本
String s2 = new String("hello");// 2个对象 1个在常量池 1个在堆区
String s3 = "hello";
String s4 = "hello";
String s5 = new String("hello");
String s6 = new String("hello");
System.out.println(s3 == s4);//比较地址 true
System.out.println(s3.equals(s4));//比较内容 true
System.out.println(s5 == s6);//比较地址 false
System.out.println(s5.equals(s6));//比较内容 true
System.out.println(s3 == s5);//比较地址 false
System.out.println(s3.equals(s5));//比较内容 true
String s7 = "abcd";
String s8 = "ab" + "cd";//常量优化机制 "abcd"
System.out.println(s7 == s8);//比较地址 true
System.out.println(s7.equals(s8));//比较内容 true
String s9 = "ab";
String s10 = s9 + "cd";//s9为变量 所以s10为新对象
System.out.println(s7 == s10);//比较地址 false
System.out.println(s7.equals(s10));//比较内容 true
}
}
包装类(Wrapper)
包装类的分类
Integer类
java.lang.Integer类内部包装了一个int类型的变量作为成员变量,主要用于实现对int类型的包装并提供int类型到String类之间的转换等方法。
常用常量常用方法
装箱: Integer i = Integer.valueOf(123);
拆箱: int ia = i.intValue;
自动装箱池: -128~127
Double类
java.lang.Double类型内部包装了一个double类型的变量作为成员变量,主要用于实现对double类型的包装并提供double类型到String类之间的转换等方法。
常用常量
常用方法
Boolean类
java.lang.Boolean类型内部包装了一个boolean类型的变量作为成员变量,主要用于实现对boolean类型的包装并提供boolean类型到String类之间的转换等方法。
常用常量
常用方法
Character类
java.lang.Character类型内部包装了一个char类型的变量作为成员变量,主要用于实现对char类型的包装并提供字符类别的判断和转换等方法。
常用常量
常用方法
包装类(Wrapper)的使用总结
● 基本数据类型转换为对应包装类的方式
调用包装类的构造方法或静态方法即可
● 获取包装类对象中基本数据类型变量数值的方式
调用包装类中的xxxValue方法即可
● 字符串转换为基本数据类型的方式
调用包装类中的parseXxx方法即可
数学处理类
Math类
java.lang.Math类主要用于提供执行数学运算的方法,如:对数,平方根。
常用方法
BigDecimal类
由于float类型和double类型在运算时可能会有误差,若希望实现精确运算则借助java.math.BigDecimal类型加以描述。
常用方法
BigInteger类
若希望表示比long类型范围还大的整数数据,则需要借助java.math.BigInteger类型描述。
常用方法
String类
● java.lang.String类用于描述字符串,Java程序中所有的字符串字面值都可以使用该类的对象加以描述,如:“abc”。
● String类由final关键字修饰,表示该类不能被继承。
● 从jdk1.9开始该类的底层不使用char[]来存储数据,而是改成 byte[]加上编码标记,从而节约了一些空间。
● String类描述的字符串内容是个常量不可更改,因此可以被共享使用。如:
String str1 = “abc”; ---- 其中"abc"这个字符串是个常量不可改变。
str1 = “123”; ---- 将“123”字符串的地址赋值给变量str1。
改变str1的指向并没有改变指向的内容
常量池
由于String类型描述的字符串内容是常量不可改变,因此Java虚拟机将首次出现的字符串放入常量池中,若后续代码中出现了相同字符串内容则直接使用池中已有的字符串对象而无需申请内存及创建对象,从而提高了性能。
String类常用构造方法
String类常用成员方法
正则表达式
正则表达式本质就是一个“规则字符串”,可以用于对字符串数据的格式进行验证,以及匹配、查找、替换等操作。该字符串通常使用^运算符作为开头标志,使用$运算符作为结尾标志,当然也可以省略。
正则表达式的规则
正则表达式相关的方法
个人认为比较好用的正则表达式在线生成工具:
https://regex101.com/
可变字符串类和日期相关类
可变字符串类
● 由于String类描述的字符串内容是个常量不可改变,当需要在Java代码中描述大量类似的字符串时,只能单独申请和存储,此时会造成内存空间的浪费。
● 为了解决上述问题,可以使用java.lang.StringBuilder类和java.lang.StringBuffer类来描述字符序列可以改变的字符串,如:“ab”。
● StringBuffer类是线程安全的类,效率比较低。
● StringBuilder类是非线程安全的类,效率比较高。
StringBuilder类
常用构造方法
常用成员方法
注意: 作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
StringBuilder默认扩容算法=原始容量*2+2
//既然StringBuilder类额对象本身可以修改,为什么还有返回值呢?
//为了连续调用
sb.reverse().append("1").append("2").insert(0,"3").delete(0,1).reverse();
//如何实现StringBuilder类型和String类型之间的转换
String str = sb.toString();
StringBuilder sb1 = new StringBuilder(str);
//String StringBuilder StringBuffer之间效率谁高
String < StringBuffer < StringBuilder
Java8之前日期类
System类
Java.lang.System类中提供了一些有用的类字段和方法。
常用方法
Date类
java.util.Date类主要用于描述特定的瞬间,也就是年月日时分秒,可以精确到毫秒。
常用方法
SimpleDateFormat类
java.text.SimpleDateFormat类主要用于实现日期和文本之间的转换。
常用方法
Calendar类
● java.util.Calender类主要用于描述特定的瞬间,取代Date类中的过时方法实现全球化。
● 该类是个抽象类,因此不能实例化对象,其具体子类针对不同国家的日历系统,其中应用最广泛的是GregorianCalendar(格里高利历),对应世界上绝大多数国家/地区使用的标准日历系统。
常用方法
Java8日期类
● Java 8通过发布新的Date-Time API来进一步加强对 日期与时间的处理。
● java.time包:该包日期/时间API的基础包。
● java.time.chrono包:该包提供对不同日历系统的访问。
● java.time.format包:该包能够格式化和解析日期时间对象。
● java.time.temporal包:该包包含底层框架和扩展特性。
● java.time.zone包:该包支持不同时区以及相关规则的类。
LocalDate类
java.time.LocalDate类主要用于描述年-月-日格式的日期信息,该类不表示时间和时区信息。
常用方法
LocalTime类
java.time.LocalTime 类主要用于描述时间信息,可以描述时分秒以及纳秒。
常用方法
LocalDateTime类
java.time.LocalDateTime类主要用于描述ISO-8601日历系统中没有时区的日期时间,如2007-12-03T10:15:30。
常用方法
Instant类
java.time.Instant类主要用于描述瞬间的时间点信息。
常用方法
DateTimeFormatter类
java.time.format.DateTimeFormatter类主要用于格式化和解析日期。
常用方法
二、 集合类库
集合的概述
● 当需要在Java程序中记录单个数据内容时,则声明一个变量。
● 当需要在Java程序中记录多个类型相同的数据内容时,声明一个一维数组。
● 当需要在Java程序中记录多个类型不同的数据内容时,则创建一个对象。
● 当需要在Java程序中记录多个类型相同的对象数据时,创建一个对象数组。
● 当需要在Java程序中记录多个类型不同的对象数据时,则准备一个集合。
集合的框架结构
● Java中集合框架顶层框架是:java.util.Collection集合 和 java.util.Map集合。
● 其中Collection集合中存取元素的基本单位是:单个元素。
● 其中Map集合中存取元素的基本单位是:单对元素。
Collection集合
java.util.Collection接口是List接口、Queue 接口以及Set接口的父接口,因此该接口里定义的方法既可用于操作List集合,也可用于操作Queue集合和Set集合。
常用方法
Iterator接口
● java.util.Iterator接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素。
● java.util.Collection接口继承Iterable接口,因此所有实现Collection接口的实现类都可以使用该迭代器对象。
常用方法
public class IteratorTest {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<>();
arr.add("Tom");
arr.add("jack");
arr.add("Gary");
//获取迭代器
Iterator<String> it = arr.iterator();
//遍历
while (it.hasNext()){
System.out.println(it.next());
}
}
}
for each循环
语法格式
for(元素类型 变量名 : 数组/集合名称) {
循环体;
}
List集合
● java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序。
● 该集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
● 其中ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
● 其中LinkedList类的底层是采用双向链表进行数据管理的,访问不方便,增删元素方便。
● 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
● 其中Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的数据结构,叫做栈(last in first out LIFO)。
● 其中Vector类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低,以后开发中基本不用。
常用方法
Queue集合
● java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
● 该集合的主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
● 该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势。
常用方法
泛型机制
● 通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
● 为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。
● 泛型只在编译时期有效,在运行时期不区分是什么类型。
底层原理
● 泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,从而使得集合中所有的E被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。
自定义泛型方法
● 泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化。
● 泛型方法的格式:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }
● 在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。
通配符
● <?> 无限制通配符:表示我们可以传入任意类型的参数。
● <? extends E> 表示类型的上界是E,只能是E或者是E的子类。
● <? super E> 表示类型的下界是E,只能是E或者是E的父类。
Set集合
● java.util.Set集合是Collection集合的子集合,与List集合平级。
● 该集合中元素没有先后放入次序,且不允许重复。
● 该集合的主要实现类是:HashSet类 和 TreeSet类以及LinkedHashSet类。
● 其中HashSet类的底层是采用哈希表进行数据管理的。
● 其中TreeSet类的底层是采用红黑树进行数据管理的。
● 其中LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
常用方法----参考Collection集合
元素放入HashSet集合的原理
● 使用元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。
● 若该位置没有元素,则将该元素直接放入即可。
● 若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
● 若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较。
● 若相等则添加元素失败,否则将元素直接放入即可。
Map集合
● java.util.Map<K,V>集合中存取元素的基本单位是:单对元素,其中类型参数如下:
K - 此映射所维护的键(Key)的类型,相当于目录。
V - 映射值(Value)的类型,相当于内容。
● 该集合中key是不允许重复的,而且一个key只能对应一个value。
● 该集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Hashtable类、Properties类。
● 其中HashMap类的底层是采用哈希表进行数据管理的。
● 其中TreeMap类的底层是采用红黑树进行数据管理的。
● 其中LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
● 其中Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作为key或者value的数值。
● 其中Properties类是Hashtable类的子类,该对象用于处理属性文件,key和value都是String类型的。
● Map集合是面向查询优化的数据结构, 在大数据量情况下有着优良的查询性能。
● 经常用于根据key检索value的业务场景。
常用方法
元素放入HashMap集合的原理
● 使用元素的key调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算在数组中的索引位置。
● 若该位置没有元素,则将该键值对直接放入即可。
● 若该位置有元素,则使用key与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
● 若key与已有元素的哈希值相同,则使用key调用equals方法与已有元素依次比较。
● 若相等则将对应的value修改,否则将键值对直接放入即可。
** 相关的常量**
● DEFAULT_INITIAL_CAPACITY : HashMap的默认容量是16。
● DEFAULT_LOAD_FACTOR:HashMap的默认加载因子是0.75。
● threshold:扩容的临界值,该数值为:容量*填充因子,也就是12。
● TREEIFY_THRESHOLD:若Bucket中链表长度大于该默认值则转化为红黑树存储,该数值是8。
● MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,该数值是64。
Collections类
java.util.Collections类主要提供了对集合操作或者返回集合的静态方法。
常用方法
三、异常与IO流
异常机制
概念
● 异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。
● java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类。
● 其中Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了等。
● 其中Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0作为除数等。
异常的分类
● java.lang.Exception类是所有异常的超类,主要分为以下两种:
RuntimeException - 运行时异常,也叫作非检测性异常
IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常。
● 其中RuntimeException类的主要子类:
ArithmeticException类 - 算术异常
ArrayIndexOutOfBoundsException类 - 数组下标越界异常
NullPointerException - 空指针异常
ClassCastException - 类型转换异常
NumberFormatException - 数字格式异常
● 注意:
当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。
异常框架结构
异常的捕获
//异常捕获语法格式
try {
编写可能发生异常的代码;
}catch(异常类型 引用变量名) {
编写针对该类异常的处理代码;
}
...
finally {
编写无论是否发生异常都要执行的代码;
}
注意事项
①当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
②懒人的写法:catch(Exception e) { }
③finally通常用于进行善后处理,如:关闭已经打开的文件等。
执行流程
//执行流程
try {
a;
b; - 可能发生异常的语句
c;
}catch(Exception e) {
d;
}finally {
e;
}
//当没有发生异常时的执行流程:a→b→c→e;
//当发生异常时的执行流程:a→b→d→e;
异常的抛出
在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行。
//异常抛出语法格式
访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,...{ 方法体; }
如:
public void show() throws IOException{}
注意:子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。
开发经验分享
● 若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
● 若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。
自定义异常
实现流程
a.自定义xxxException异常类继承Exception类或者其子类。
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法。
File类
File类概念
java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,如:大小等。
File类常用方法
IO流
IO流概念
● IO就是Input和Output的简写,也就是输入和输出的含义。
● IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。
IO流分类
● 按照读写数据的基本单位不同,分为 字节流 和 字符流。
其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
● 按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
● 按照流的角色不同分为节点流和处理流。
其中节点流主要指直接和输入输出源对接的流。
其中处理流主要指需要建立在节点流的基础之上的流。
IO流体系结构
IO流框架图
IO流详解
FileWriter类
java.io.FileWriter类主要用于将文本内容写入到文本文件。
常用方法
FileReader类
java.io.FileReader类主要用于从文本文件读取文本数据内容。
常用方法
FileOutputStream类
java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。
常用方法
FileInputStream类
java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。
常用方法
BufferedOutputStream类
java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统。
常用方法
BufferedInputStream类
java.io.BufferedInputStream类主要用于描述缓冲输入流。
常用方法
BufferedWriter类
java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。
常用方法
BufferedReader类
java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。
常用方法
PrintStream类
java.io.PrintStream类主要用于更加方便地打印各种数据内容。
常用方法
PrintWriter类
java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。
常用方法
OutputStreamWriter类
java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。
常用方法
InputStreamReader类
java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。
常用方法
DataOutputStream类
java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。
常用方法
DataInputStream类
java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据。
常用方法
ObjectOutputStream类
● java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
● 只能将支持 java.io.Serializable 接口的对象写入流中。
● 类通过实现 java.io.Serializable 接口以启用其序列化功能。
● 所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
常用方法
ObjectInputStream类
● java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
● 所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
常用方法
序列化版本号
序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
transient关键字
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。
开发经验分享
当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断。
RandomAccessFile类
java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。
常用方法
四、多线程
概念
程序和进程
● 程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
● 进程 - 主要指运行在内存中的可执行文件。
● 目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量级的,也就是新建一个进程会消耗CPU和内存空间等系统资源,因此进程的数量比较局限。
线程
● 为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。
● 多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。
线程的创建
Thread类概念
● java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
● Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。
创建方式
● 自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
● 自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。
Thread类相关方法
执行流程
● 执行main方法的线程叫做主线程,执行run方法的线程叫做新线程/子线程。
● main方法是程序的入口,对于start方法之前的代码来说,由主线程执行一次,当start方法调用成功后线程的个数由1个变成了2个,新启动的线程去执行run方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。
● 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束。
● 两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。
开发经验分享
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。
线程的生命周期
● 新建状态 ---- 使用new关键字创建之后进入的状态,此时线程并没有开始执行。
● 就绪状态 ---- 调用start方法后进入的状态,此时线程还是没有开始执行。
● 运行状态 ---- 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
● 消亡状态 ---- 当线程的任务执行完成后进入的状态,此时线程已经终止。
● 阻塞状态 ---- 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。
线程的编号和名称
Thread类常用的方法
线程同步机制
概念
● 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
● 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
● 异步操作:多线程并发的操作,各自独立运行。
● 同步操作:多线程串行的操作,先后执行的顺序。
解决方案
问题描述:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。
解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。
经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。
实现方式
使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性。
//使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
}
//使用同步方法的方式实现所有代码的锁定。
直接使用synchronized关键字来修饰整个方法即可,如:
public synchronized void xxx(){…}
该方式等价于:
synchronized(this) { 整个方法体的代码 }
注意
使用synchronized保证线程同步应当注意:
● 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
● 在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
● StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
● Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
● Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
死锁
//线程一执行的代码:
public void run(){
synchronized(a){ //持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
}
//线程二执行的代码:
public void run(){
synchronized(b){ //持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
}
注意:在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
使用Lock(锁)实现线程同步
● 从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
● java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
● 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。
常用方法
lock与synchronized方式的比较
● Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
● Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
● 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。
Object类常用的方法
线程池
实现Callable接口
● 从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
● 常用的方法如下:
FutureTask类
● java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果。
● 常用的方法如下:
线程池
● 线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
● 原理:在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池相关类和方法
● 从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。
● 其中Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如下:
其中ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下:
五、反射
Class类
概念
● java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。
● 该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。
获取Class对象的方式
● 使用数据类型.class的方式可以获取对应类型的Class对象。
● 使用引用/对象.getClass()的方式可以获取对应类型的Class对象。
● 使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。
● 使用Class.forName()的方式来获取参数指定类型的Class对象。
● 使用类加载器ClassLoader的方式获取指定类型的Class对象。
Class类常用方法
Constructor类
概念
java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息
Class类常用方法
Constructor类常用方法
Field类
概念
java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。
Class类常用方法
Field类的常用方法
Method类
概念
java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息。
Class类常用方法
Method类常用方法
获取其它结构信息
Java基础知识请看
https://blog.csdn.net/qq_50048558/article/details/119597726?spm=1001.2014.3001.5501