Java API
API
API (Application Programming Interface) :应用程序编程接口,java中的API指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来。
ApI帮助文档
- Javabean类
用于描述一类事物的类 - 测试类
用来检测其他类是否书写正确,带有main方法的类 - 工具类
类名见名知意、创建对象没意义,私有化构造方法,方法定义为静态
字符串
String
String 类在 java.lang 包下,lang包是Java重要的基础包,使用的时候不需要导包
特点
- 字符串不可变,它们的值在创建后不能被更改
- 虽然 String 的值是不可变的,但是它们可以被共享
- 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
String类的构造方法
常用的构造方法
方法名 说明 public String() 创建一个空白字符串对象,不含有任何内容 public String(String original) 根据传入的字符串,创建字符串对象 public String(char[] chs) 根据字符数组的内容,来创建字符串对象 public String(byte[] bys) 根据字节数组的内容,来创建字符串对象 String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc 创建字符串对象两种方式的区别
- 通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同
- 以
""
方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护
字符串的比较
- ==号的作用
- 比较基本数据类型:比较的是具体的值
- 比较引用数据类型:比较的是对象地址值
- equals方法的作用
- public boolean equals(String s) 比较两个字符串内容是否相同、区分大小写(equalIgnoreCse忽略大小写)
- s1.equal(s2) 比较两个字符串内容是否一样
StringBuilder
StringBuilder 可以看成是一个容器,与String不同的是创建之后里面的内容是可变的,提高字符串的效率。
当我们在拼接字符串和反转字符串的时候会使用到
StringBuilder 常用方法(toString的底层原理是new String())
StringJoiner
- StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
- 作用:提高字符串的操作效率,而且代码编写特别简洁。
- JDK8出现的
基本使用://1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
//3.打印结果
System.out.println(sj);//aaa---bbb---ccc
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]
字符串拼接的底层原理
- 右边没有变量
String s = "a"+"b"+"c" ;//触发字符串优化机制,在编译的时候已经是最终结果了,等同于String s = "abc";
- 右边有变量
- jdk8之前,变量和字符串相加一次会在堆内存中产生StringBuilder、String两个对象,影响运行效率
-jdk8之后,对字符串拼接长度预估,创建相应大小数组,然后再变成字符串,但预估同样浪费时间
- jdk8之前,变量和字符串相加一次会在堆内存中产生StringBuilder、String两个对象,影响运行效率
StringBuilder拼接提高效率原理
所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存
- 默认创建一个长度为16的字节数组
- 添加的内容大于16会扩容(容量*2+2)
- 扩容依然不够,会扩曾到需求容量
ArrayList
- 什么是集合
提供一种存储空间可变的存储模型,存储的数据容量可以发生改变 - ArrayList集合的特点
长度可以自动变化满足需求,只能存储引用数据类型,基本数据类型要变成包装类,如。 - 泛型的使用
用于约束集合中存储元素的数据类型
构造方法
方法名 | 说明 |
---|---|
public ArrayList() | 创建一个空的集合对象 |
成员方法
方法名 | 说明 |
---|---|
public boolean add(要添加的元素) | 将指定的元素追加到此集合的末尾 |
public boolean remove(要删除的元素) | 删除指定元素,返回值表示是否删除成功 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
ArrayList存储字符串并遍历
public class ArrayListDemo3 { |
GUI
图形用户接口
- JFrame 窗体框架
- JMenuBar 菜单栏、JMnu 菜单、JMnuItem 菜单项
- JButton 按钮对象
- JLable 管理文字和图片的容器
- JDialog 弹框对象
- JTextFiled 文本输入框,明文显示
- JPasswordFiled 文本输入框,暗文显示
- ImageIcon 图片
- KeyListener 键盘监听 MouseListener 鼠标监听 ActionListener 动作监听(鼠标左键、空格)
Math 数学工具类
常用方法
public static int abs(int a) // 返回参数的绝对值 |
System 系统工具类
常用方法
public static long currentTimeMillis() // 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置) |
runtime
Java中运行时对象,可以获取到程序运行时设计到的一些信息。私有化构造方法,但提供获取对象的方法,有且仅获取一个对象
常用方法
public static Runtime getRuntime() //当前系统的运行环境对象 |
Object
Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了。
常用方法
public String toString() //返回该对象的字符串表示形式(可以看做是对象的内存地址值) |
Objects 工具类
public static String toString(Object o) // 获取对象的字符串表现形式 |
BigInteger
构造方法
public BigInteger(int num, Random rnd) //获取随机大整数,范围:[0 ~ 2的num次方-1] |
- 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
- 如果BigInteger表示的超出long的范围,可以用构造方法获取。
- 对象一旦创建,BigInteger内部记录的值不能发生改变。
- 只要进行计算都会产生一个新的BigInteger对象
常见成员方法底层存储方式public BigInteger add(BigInteger val) //加法
public BigInteger subtract(BigInteger val) //减法
public BigInteger multiply(BigInteger val) //乘法
public BigInteger divide(BigInteger val) //除法
public BigInteger[] divideAndRemainder(BigInteger val) //除法,获取商和余数
public boolean equals(Object x) //比较是否相同
public BigInteger pow(int exponent) //次幂、次方
public BigInteger max/min(BigInteger val) //返回较大值/较小值
public int intValue(BigInteger val) //转为int类型整数,超出范围数据有误
BigDecimal
构造方法
BigDecimal(int val) |
Pattern 正则表达式
Pattern.compile(regex)创建对象
正则表达式就是用来验证各种字符串的规则。它内部描述了一些规则,我们可以验证用户输入的字符串是否匹配这个规则。
字符类,只能匹配一个字符
- [abc]:代表a或者b,或者c字符中的一个。
- [^abc]:代表除a,b,c以外的任何字符。
- [a-z]:代表a-z的所有小写字符中的一个。
- [A-Z]:代表A-Z的所有大写字符中的一个。
- [0-9]:代表0-9之间的某一个数字字符。
- [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
- [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符,等同于[a-d[m-p]]。
逻辑运算符
- &&:并且,取两个范围的交集
- | :或者
- \ :转义字符
预定义字符,能匹配一个字符 - “.” : 匹配任何字符。
- “\d”:任何数字[0-9]的简写;
- “\D”:任何非数字[^0-9]的简写;
- “\s”: 空白字符:[ \t\n\x0B\f\r] 的简写
- “\S”: 非空白字符:[^\s] 的简写
- “\w”:单词字符:[a-zA-Z_0-9]的简写
- “\W”:非单词字符:[^\w]
数量词
- X? : 0次或1次
- X* : 0次到多次
- X+ : 1次或多次
- X{n} : 恰好n次
- X{n,} : 至少n次
- X{n,m}: n到m次(n和m都是包含的)
分组
每组都是有序号的,从1开始。只看左括号,不看右括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等等
捕获分组
正则内部使用,\组号,表示把第几组的东西拿出来再用一次
正则外部使用,$组号String str = "我要学学编编编编程程程程程程";
//需求:把重复的内容 替换为 单个的
//学学 学
//编编编编 编
//程程程程程程 程
// (.)表示把重复内容的第一个字符看做一组
// \\1表示第一字符再次出现
// + 至少一次
// $1 表示把正则表达式中第一组的内容,再拿出来用
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result);非捕获分组
不占用组号
Matche
Pattern对象.matcher(String) //All matchers have the state used by Pattern during a match.
文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。在大串中去找符合匹配规则的子串
时间
- JDK7时间类,时间的比较和计算需要转换毫秒值,比较麻烦,并且由于对象的可变性在多线程环境可能会导致数据的安全问题
- Date
- SimpleDateFormat
- Calendar
- JDK8时间类,时间可以直接运算,并且创建的对象不可变
JDK8时间类类名 作用 ZoneId 时区 Instant 时间戳 ZoneDateTime 带时区的时间 DateTimeFormatter 用于时间的格式化和解析 LocalDate 年、月、日 LocalTime 时、分、秒 LocalDateTime 年、月、日、时、分、秒 Duration 时间间隔(秒,纳,秒) Period 时间间隔(年,月,日) ChronoUnit 时间间隔(所有单位)
包装类
在JDK5以后,包装类可以自动拆箱和装箱,可以看成基本类型
Arrays 数组工具类
Arrays.sort(array,new Comparator<E>(){ //array需为引用类型对象数组 |
lambda表达式
lambda表达式可以用来简化匿名内部类的书写
lambda表达式只能简化函数式接口(有且只有一个抽象方法的接口)的匿名内部类书写
() -> {} 方法形参 -> 方法体
Arrays.sort(array,(Integer o1,Integer o2){ |
集合
Vector属于List集合,被逐渐淘汰了
Collection 集合
- 是单例集合的顶层接口
- JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现,通过多态的方式创建Collection对象
Iterator迭代器遍历
迭代器,集合的专用遍历方式
- 迭代器遍历时,不能用集合的方法进行增加或者删除
- 迭代器遍历完毕,指针不会复位
public class IteratorDemo1 {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) { //判断当前位置是否有元素可以被取出
String s = it.next(); // 获取当前位置的元素,将迭代器对象移向下一个索引位置
//it.remove(); //删除迭代器指向的元素
System.out.println(s);
}
}
}
增强for
- 其内部原理是一个Iterator迭代器
- 实现Iterable接口的类才可以使用迭代器和增强for
- 简化数组和Collection集合的遍历
public class MyCollectonDemo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
//1,数据类型一定是集合或者数组中元素的类型
//2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
//3,list就是要遍历的集合或者数组
for(String str : list){
str = "dwqf"; //str是增强for中的第三方变量,修改并不会影响集合中的数据
System.out.println(str);
}
}
}
lambda表达式
利用forEach方法,再结合lambda表达式的方式进行遍历
public class A07_CollectionDemo7 { |
List
- 有序集合,这里的有序指的是存取顺序
- 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
ListIterator
可以在遍历过程中调用add方法添加元素
ArrayList集合
底层是数组结构实现,查询快、增删慢
LinkedList集合
底层是双向链表结构实现,查询慢、增删快
泛型
泛型类,写在类名后
泛型方法,写在修饰符后
泛型接口,写在接口名后,实现类可以给出确定的类型也可以延续泛型
泛型不具备继承性,但数据具备继承性
通配符
-? extends E
可以传递E或E的子类类型
? super E
可以传递E或E的父类类型
Set
HashSet
- 底层数据结构是哈希表(数组、链表、红黑树)
- 存取无序
- 不可以存储重复元素
- 没有索引,不能使用普通for循环遍历
- 哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值 - 如何获取哈希值
Object类中的public int hashCode():默认使用对象的地址值计算,返回对象的哈希值 - 哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,利用对象的属性计算哈希值,可以实现让不同对象的哈希值相同
LinkedHashSet
在哈希表的基础上增加了双向链表,可以有序存储元素
TreeSet
不重复、无索引、可排序(基于红黑树)
- 默认排序:javabean类实现Comparable接口指定比较规则
- 比较器排序:创建TreeSet对象的时候,传递比较器Comparator的指定规则
Map 集合
HashMap
底层原理和HashSet原理类似,依赖hashCode方法和equals方法保证键的唯一,通过建的值计算哈希值,如果键值一样,则覆盖原有的元素
LinkHashMap
TreeMap
不可变集合
在List、Set、Map接口中,都存在静态的of方法,可以获取一个不可变的集合
Stream
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作
- 一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个Stream流只能有一个终结方法
- 是流水线上的最后一个操作
- 生成的stream流只能使用一次,建议链式编程
- 流里面数据的改动不会影响源数据
方法引用
把被引用的方法当作抽象方法的方法体
- 引用处需要是函数式接口
- 被引用的方法需要已经存在
- 被引用方法的形参和返回值类型需要跟抽象方法一致
- 被引用的方法的功能需要满足当前需求
类名引用类的静态方法对象引用其他类中的成员方法public interface Converter {
int convert(String s);
}
public class ConverterDemo {
public static void main(String[] args) {
//Lambda写法
useConverter(s -> Integer.parseInt(s));
//引用类方法
useConverter(Integer::parseInt);
}
private static void useConverter(Converter c) {
int number = c.convert("666");
System.out.println(number);
}
}this/super引用本类或父类中的成员方法(不能是静态方法)public class PrintString {
//把字符串参数变成大写的数据,然后在控制台输出
public void printUpper(String s) {
String result = s.toUpperCase();
System.out.println(result);
}
}
public interface Printer {
void printUpperCase(String s);
}
public class PrinterDemo {
public static void main(String[] args) {
//Lambda简化写法
usePrinter(s -> System.out.println(s.toUpperCase()));
//引用对象的实例方法
PrintString ps = new PrintString();
usePrinter(ps::printUpper);
}
private static void usePrinter(Printer p) {
p.printUpperCase("HelloWorld");
}
}
引用构造方法类名引用成员方法class Student{
String name;
int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
public Student(String s){
String[] arr = s.split(",");
name = arr[0];
age = Integer.parse(arr[1]);
}
}
import ···
public class Test{
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<> ();
Collections.addAll(liat,"name1,age1","name2,age2","name3,age3");
List<Student> newList = list.steam().map(new Function<String,Student>){
public Student apply(String s){
String[] arr = s.split(",");
return new Student(arr[0],Integer.parse(arr[1]));
}
}
//String -> Student
List<Student> newList2 = list.stream().map(Student::new); //引用构造方法
}
}
独有规则: - 被引用方法的形参需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值保持一致。如果抽象方法没有第二个形参,被引用的方法需要是无参的成员方法
- 抽象方法的第一个参数表示引用方法的调用者,决定了可以引用那些类中的方法引用数组的构造方法
import ···
public class Test{
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<> ();
Collections.addAll(liat,"qqq","hhh","ddd");
List<String> list1 =list.steam().map(new Function<String,String>{
public String apply(String s){
return s.toUpperCase;
}
});
//类名引用成员方法
List<Student> list2 = list.stream().map(String::toUpperCase);
}
}import ···
public class Test{
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5);
Integer[] arr = list.stream().toArray(new IntFunction<Integer[]>{
public Integer[] apply(int value){
return new Integer[value];
}
});
//引用数组的构造方法
Integer[] arr2 = list.stream().toArray(Integer[]::new);
}
}
异常
- Error:严重错误Error,无法通过处理的错误,只能事先避免。
- Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
- RuntimeException及其子类: 编译阶段不会异常提醒,运行时会
- 其他异常:编译时异常
抛出异常throw:throw new 异常类名(参数);
如果异常出现的话,会立刻终止程序,所以我们得处理异常:throw new NullPointerException("要访问的arr数组不存在");
throws:写在方法定义处,表明声明一个异常告诉调用者,使用本方法可能会有那些异常(如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,运行时异常可以省略)
(1) Exception在程序中必须使用try… catch进行处理。
(2) RuntimeException 可以不使用 try… catch进行处理,但是如果有异常产生,则异常将由JVM 进行处理。public class ThrowsDemo2 {
public static void main(String[] args) throws IOException {
read("a.txt");
}
public static void read(String path)throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}
}在方法中使用try-catch的语句块来处理异常。
try{ //创建一种异常后进入catch查找看,后面的代码不会执行
编写可能会出现异常的代码
//此时程序会创建相应的异常对象,这个异常对象可以被catch捕获
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}finally 代码块
- 有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
- 当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。
finally的语法:try...catch....finally
:自身需要处理异常,最终还得关闭资源
- 不能抛出异常,只能try-catch的几种情况
- 重写父类方法时,如果父类的方法没有声明抛出任何检查型异常(checked exception),则子类方法也不能声明抛出新的检查型异常
- 实现接口方法时,如果接口方法没有声明抛出任何检查型异常,则实现类的方法也不能声明抛出新的检查型异常
- lambda表达式和函数式接口,函数式接口的方法不能抛出检查型异常,除非接口方法本身声明抛出异常
- 构造方法
- 静态初始化块,在静态初始化块中,不能抛出检查型异常,只能使用try-catch处理异常。静态初始化块是类加载时执行的代码块,不允许抛出检查型异常,否则会导致类加载失败
- 自定义异常
- 定义异常类
- 写继承关系
- 空参和带参构造
File
文件和目录路径名的抽象表示
IO流
字节流可以读取二进制文件,字符流只能读取纯文本文件
FileOutputStream extends OutputStream
创建一个输出流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件(前提是父级路径存在)。如果有这个文件,会清空这个文件的数据。
写出字节:write(byte[] b)
,每次可以写出数组中的数据,write(int b)
方法,每次可以写出一个字节数据
统中的换行:
* Windows系统里,每行结尾是 回车+换行
,即\r\n
;
* Unix系统里,每行结尾只有 换行
,即\n
;
* Mac系统里,每行结尾是 回车
,即\r
。从 Mac OS X开始与Linux统一。
追加写入:在构造方法后增加true
参数。
FileInputStream extends InputStream
创建一个输入流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
读取字节:read
方法,每次可以读取一个字节的数据,根据ASCII表提升为int类型,读取到文件末尾,返回-1
拷贝
使用read()
和write(int b)
读写一次只能读一个字节,速度太慢,int len read(byte[] buffer)
和write(byte[] buffer,0,len)
可以一次读写多个字节(和数组长度相关)
流的关闭原则:先开后关,后开先关。
字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
- 字符集
- ASCII字符集(128个),存储单位1个字节
- GBK字符集(21003个,包含中日韩汉字,繁体中文),存储单位2个字节,二进制第一位是1,表示两个字节连在一起解码。GBK完全兼容ASCII字符集,存储单位1个字节,二进制第一位是0。
- Unicode字符集,国际标准字符集,将世界上各种语言的每个字符定义一个唯一编码,UTF-8编码规则:用1-4个字节保存,ACSII:1个字节,叙利亚文:2个字节,中东文字:3个字节,其他语言:4个字节
- 编码和解码(java默认UTF-8编解码)
- FileReader
- FileWrite
字符输出流也有一个大小为8KB的缓冲区,只有当缓冲区装满了或者flush()
和close()
才会把数据输出到文件中
缓冲流
缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
- 字符缓冲流特有方法
- BufferedReader:
public String readLine()
: 读一行文字,不会把回车换行读到内存中。 - BufferedWriter:
public void newLine()
: 写一行行分隔符,由系统属性定义符号。
- BufferedReader:
转换流
在IDEA中,使用FileReader
读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8
编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
InputStreamReader:是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。 //可以用FileReader
中传递Charset
的构造方法指定字符编码读取替代
OutputStreamWriter: 是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。 //可以用FileWriter
中传递Charset
的构造方法指定字符编码写入替代
序列化
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
ObjectOutputStream:
序列化操作- 一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。 - 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。
- 该类必须实现
- 写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
- 一个对象要想序列化,必须满足两个条件:
ObjectInputStream
反序列化操作- 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用
ObjectInputStream
读取对象的方法:public final Object readObject ()
: 读取一个对象。 - 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个
InvalidClassException
异常。发生这个异常的原因如下:- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
- 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Employee implements java.io.Serializable { |
打印流
- PrintStream(字节打印流)
平时我们在控制台打印输出,是调用print
,printf
方法和println
方法完成的,这三个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。 - PrintWriter(字符打印流)
方法同上
压缩流和解压缩流
解压本质:把每个ZipEntry对象按照层级拷贝到本地另一个文件夹中
/* |
压缩本质:把每个文件封装成ZipEntry对象按照层级拷贝到压缩包中
public class ZipStreamDemo3 { |
多线程
- 进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行 - 线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序 - 并发:在同一时刻,有多个指令在单个CPU上交替执行
- 并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程实现方式
继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
public class MyThread extends Thread {
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
// my1.run();
// my2.run();
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}
实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
public class MyRunnable implements Runnable {
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"坦克");
Thread t2 = new Thread(my,"飞机");
//启动线程
t1.start();
t2.start();
}
}
实现Callable接口
- 定义一个类MyCallable实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类的对象(表示多线程要执行的任务)
- 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数(管理多线程运行的结果)
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束之后的结果。
public class MyCallable implements Callable<String> {
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//Thread t1 = new Thread(mc);
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
String s = ft.get();
//开启线程
t1.start();
//String s = ft.get();
System.out.println(s);
}
}
线程休眠
线程优先级
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些守护线程
普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
线程同步
安全问题出现的条件
是多线程环境
有共享数据
有多条语句操作共享数据
如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
怎么实现呢?
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
Java提供了同步代码块的方式来解决
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}同步方法:就是把synchronized关键字加到方法上(锁对象为this)
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 说明 ReentrantLock() 创建一个ReentrantLock的实例 加锁解锁方法
方法名 说明 void lock() 获得锁 void unlock() 释放锁
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
什么情况下会产生死锁
- 资源有限
- 同步嵌套
生产者消费者
Object类的等待和唤醒方法
方法名 说明 void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程 阻塞队列
常见BlockingQueue:ArrayBlockingQueue: 底层是数组,有界
LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
public class Cooker extends Thread { |
ExecutorsService 线程池
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点”舍本逐末”了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
package com.itheima.mythreadpool; |
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
public class ThreadPoolExecutorDemo02 { |
控制台输出结果
pool-1-thread-1---->> 执行了任务
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
Volatile
当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题
1,堆内存是唯一的,每一个线程都有自己的线程栈。
2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
3 ,在线程中,每一次使用是从变量的副本中获取的。
Volatile关键字修饰变量: 强制线程每次在使用的时候,都会看一下共享区域最新的值
原子性
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体,给不可分割部分添加锁保证原子性
网络
三要素
IP地址
为每台计算机指定一个标识号
端口
端口号可以唯一标识设备中的应用程序了。也就是应用程序的标识
协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,如TCP、UDP协议等
IP地址:是网络中设备的唯一标识
- IP地址分为两大类
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
- 为节省ip,IPv4又分为公网地址(万维网使用)和私有地址(局域网使用),192.168开头的就是私网IP,多个私网IP共享一个公网IP
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
- DOS常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
- 特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
InetAddress: 此类表示Internet协议(IP)地址
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
端口号:由两个字节表示的整数,取值范围:0-65532,其中0-1023之间的端口号用于一些知名的网络服务或应用
Java中的UDP通信
- UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
- Java提供了DatagramSocket类作为基于UDP协议的Socket
发送数据的步骤
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包
调用DatagramSocket对象的方法发送数据
关闭发送端
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket ds = new DatagramSocket();
//创建数据,并把数据打包
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
byte[] bys = "hello,udp,我来了".getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);
//调用DatagramSocket对象的方法发送数据
//void send(DatagramPacket p) 从此套接字发送数据报包
ds.send(dp);
//关闭发送端
//void close() 关闭此数据报套接字
ds.close();
}
}接收数据的步骤
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345);
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
}
}
}UDP三种通讯方式
单播
单播用于两个主机之间的端对端通信组播
组播用于对一组特定的主机进行通信// 发送端
public class ClinetDemo {
public static void main(String[] args) throws IOException {
// 1. 创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
String s = "hello 组播";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
// 2. 创建数据,并把数据打包(DatagramPacket)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
ds.send(dp);
// 4. 释放资源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 创建接收端Socket对象(MulticastSocket)
MulticastSocket ms = new MulticastSocket(10000);
// 2. 创建一个箱子,用于接收数据
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
// 4. 将数据接收到箱子中
ms.receive(dp);
// 5. 解析数据包,并打印数据
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 6. 释放资源
ms.close();
}
}广播
广播用于一个主机对整个局域网上所有主机上的数据通信// 发送端
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 1. 创建发送端Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
// 2. 创建存储数据的箱子,将广播地址封装进去
String s = "广播 hello";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 发送数据
ds.send(dp);
// 4. 释放资源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10000);
// 2. 创建一个数据包,用于接收数据
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 调用DatagramSocket对象的方法接收数据
ds.receive(dp);
// 4. 解析数据包,并把数据在控制台显示
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 5. 关闭接收端
ds.close();
}
}Java中的TCP通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
TCP发送数据public class Client {
public static void main(String[] args) throws IOException {
//TCP协议,发送数据
//1.创建Socket对象
//细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10000);//三次握手协议,建立可靠连接
//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("aaa".getBytes());
//3.释放资源
os.close();
socket.close(); //四次挥手协议,保证流里面的数据被接收
}
}TCP接收数据
public class Server {
public static void main(String[] args) throws IOException {
//TCP协议,接收数据
//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端的链接,accept方法是阻塞的,作用就是等待客户端连接
Socket socket = ss.accept();
//3.从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
int b;
while ((b = is.read()) != -1){
System.out.println((char) b);
}
//4.释放资源
socket.close();
ss.close();
}
}
反射
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意属性和方法;
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
获取字节码文件对象的三种方式
字节码文件对象获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 获得所有的构造(只能public修饰) |
Constructor<?>[] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
Constructor |
获取指定构造(只能public修饰) |
Constructor |
获取指定构造(包含private修饰) |
//测试类中的代码: |
获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
public class ReflectDemo5 { |
获取成员方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的,包括父类的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 ,不包括父类的 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
public class ReflectDemo6 { |
反射和配置文件结合动态获取
public class ReflectDemo9 { |
配置文件中的信息:
classname=com.itheima.a02reflectdemo1.Student
methodname=sleep
动态代理
无侵入式的给方法增强功能
三要素
1,真正干活的对象
2,代理对象
3,利用代理调用方法
public interface Star { |
public class BigStar implements Star { |
public class ProxyUtil { |
public class Test { |