Java编程基础(整理)
Java教程官方文档:https://docs.oracle.com/javase/tutorial/index.html
JDK11官方文档:https://docs.oracle.com/en/java/javase/11/
《Java编程思想》学习笔记:https://www.cnblogs.com/bingmous/p/15643695.html
《Java从入门到精通》学习笔记:https://www.cnblogs.com/bingmous/p/15643734.html
Java语言概述
- 软件开发:系统软件、应用软件
- 人机交互:GUI(图形化界面)、CLI(命令行方式)
- dos常用命令:
# 进入其他盘符直接输入 盘符:,如d:
# 查看文件dir
# 创建目录md 删除目录rd 删除文件del 后面都可以跟多个参数,如md d1 d2。可以使用通配符如del *.txt。删除目录必须目录为空,del后面跟目录表示删除目录下所有文件,需要输入y确认,但不会删除该目录下的目录。
# 切换目录cd xxDir .表示当前目录 ..表示父目录 /表示根目录
# 创建文件使用echo xxx > 1.txt,创建的文件非空包含echo和>之间的内容
# 查看命令帮助 命令后加 /?
- 计算机编程语言:机器语言、汇编语言、高级语言
- C面向过程,C++面向过程/面向对象,Java跨平台的纯面向对象,.NET跨语言的平台,Python,Scala,JS都是面向对象
- Java、C/C++都是强类型语言,Python、JS是弱类型语言,解释型语言
- Java语言概述
- 版本迭代:大版本JDK5(2004),JDK8(2014),JDK11(2018.9)
- 应用领域:Java Web开发(后台开发),大数据开发,Android应用程序开发(客户端开发)
- 特点:面向对象(类、对象,封装、继承、多态),健壮性(去除C语言的指针,自动垃圾回收),跨平台(一次编译,到处运行,归功于JVM)
- 开发环境:JDK,JRE,JVM,是包含关系(注意安装路径不要包含中文、空格),配置环境变量JAVA_HOME(如tomcat自动查找),path
- HelloWorld:只能有一个public class,且与文件名相同,可以由多个class,入口是main()方法
- 注释:单行注释//,多行注释
/*xx*/
,文档注释/**xx*/
(可以被javadoc解析)。良好的代码编程风格
-开发工具:文本编辑(UltraEdit,EditPlus,TextPad,NotePad),集成开发环境(eclipse,idea)
基本语法
- Java关键字和保留字:官网参考
- 标识符:变量名、类名、接口名、包名、方法名
- 规则:
英文字母,0-9,_,$
组成,不可以数字开头,不可以使用关键字和保留字,区分大小写,无长度限制,不能包含空格 - 规范:包全部小写,类、接口用驼峰,方法名、变量名用小驼峰,常量名字母都大写+下划线;做到见名知意
- 详见:《代码简洁之道》
- 规则:
- 变量:先声明后赋值再使用,否则编译不通过;使用变量访问数据;在其作用域内生效,{}内;同一作用域不能重复定义;
- 数据类型:
- 基本数据类型(8种,byte/shot/int/long(分别是1248个字节,float/double(4、8个字节),char(2个字节),boolean)
- 引用数据类型(3种,class,interface,
数组[]
,它们的顶级父类都是Object,包括Xxx[]
);字符串属于class类型中,也是引用类型 - java默认整数是int,浮点数是double;long需要加后缀l或L,float需要加后缀f或F;float精度是8位,double精度是16位
- char:java使用Unicode编码,必须使用''进行赋值;可以使用
\
表示转义字符或者使用\uxxxx
使用十六进制表示字符;Unicode只能在内存中表示世界上所有字符,但无法序列化(序列化后无法识别出原内容表达的含义),可以使用utf-8编码序列化 - boolean:true、false,无null;jvm规范中指明使用int表示boolean,true用1表示,false用0表示
- 基本类型转换:
- 自动类型转换:运算时小容量的会自动转为大容量的;大容量转小容量需要使用强制类型转换
- byte、short、char之间不会转换,在计算时都会首先转换为int
- 任何基本数据类型和字符串运算(+),基本数据类型都会转化为String类型
- 进制:计算机内部都是以补码形式存储,整数的补码是本身,负数的补码是取反加一;(为什么用补码?让符号位也参与计算,计算机只有加法,让计算机识别符号位会使电路非常复杂);
- 进制之间的转换:十进制、二进制、八进制、十六进制(二进制作为中转,十进制除二取余获取二进制)
- 数据类型:
- 运算符:算术运算符(
+,-,*,除以/,取余%,前++,后++,前--,后--,字符串+
),赋值运算符(=
,支持连续赋值,扩展赋值运算符:+=,-=,*=,/+,%=
),比较运算符(==,!=,<,>,<=,>=,instanceof
),逻辑运算符(&,&&,|,||,!,^
),位运算符(<<,>>,>>>,&,|,^,~
),三元运算符((条件表达式) ? 表达式1 : 表达式2
)%
后的符号与被取模的符号相同- 扩展赋值运算符不会改变操作数的类型,
如byte b1=1; b1+=100;
,如果是b1=b1+1
就不行,因为运算之后是int类型。 &&
和||
只能操作布尔类型,且会短路<<
和>>
如果没有溢出分别表示扩大两倍、缩小两倍,>>>
表示无符号右移,- 运算符的优先级:使用括号控制即可
- 流程控制:if-else,switch-case-default,for,while,do-while
- switch:只能使用byte,short,int,char,String,enum;注意是否添加break;
- 循环执行顺序:初始条件,循环条件,循环体,迭代体,do-while一定会执行一次循环体
- break/continue:break可以跳出外层的循环(continue也可以使用),使用break label; 在外部循环前定义该循环标签为label: label: for...
- 质数输出的几种优化:提前退出,内循环上限小于等于开方后的值,使用标签continue外循环(少了判断该数是不是质数的flag)
数组
- 概念:数组名,索引,元素,数组的长度(长度一旦确定不能修改),通过索引访问数组
- 数组是引用数据类型,数组中的元素可以是任意类型
- 创建数组对象会在内存中开辟一整块连续的空间,数组名引用的是连续空间的首地址
- 使用:
- 数组初始化:静态初始化,动态初始化,默认值(整型为0,引用类型为null,boolean为false)
- 数组中涉及到的常用算法:
- 数组的复制、反转、查找(线性查找、二分查找)
- 排序算法(内部排序、外部排序):十大内部排序算法,学习算法与数据结构时参考
- 常见异常:索引越界,空指针
Arrays工具类,操作数组的工具类
排序、查找、填充、复制...
面向对象
三条主线(Java类及类的成员、面向对象的三大特征、其它关键字)
大处着眼,小处着手
- 面向过程和面向对象:
- 面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做
- 面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
- 面向对象的三大特征:封装、继承、多态
- Java语言的基本元素:类和对象
- 对象的创建和使用:new关键字,生命周期(没有引用了就成为了垃圾),匿名对象(没有赋值给某个变量)
- 类的成员之一:属性,默认值与数组中默认值相同
- 类的成员之二:方法,重载方法,可变形参方法,方法参数的值传递机制,递归方法
- 可变形参:使用...,相当于使用数组,传参也可以使用数组,必须是形参列表的最后一个参数且只能有一个。Object...可以接收任意类型任意数量的参数
- 形参传递机制:值传递,基本数据类型传递数据本身,引用数据类型传递地址值(String不可变,即使修改也不会改变原来的值)
- System.out.println(),如果打印
char[]
输出的是本身,而不是地址值,源码调用进行了遍历输出
- 面向对象特征之一:封装
- 私有化属性,提供getXxx, setXxx方法实现对属性的操作
- 四种权限修饰:class只能使用public和默认权限,其他(属性、方法、构造器、内部类)都可以使用
- 类的成员之三:构造器
- 用来创建对象,初始化对象的信息
- 可以有多个,如果没有定义,有一个默认空参的
- 关键字:this
- 表示当前对象,修饰、调用属性、方法、构造器;(一般参数名与属性名相同时使用)
- 在构造器内部调用其他构造器只能放在首行,只能调用一次,不能循环调用
- 关键字:package,import(导入类、接口),import static(导入静态结构)
- 面向对象特征之二:继承
- 好处:减少代码冗余,提高复用;便于功能扩展;为多态的使用提供前提
- 体现:一旦子类A继承父类B,那么子类A就获取了父类中声明的结构(属性、方法)
- 其他特点:单继承;所有类都直接或间接继承Object类
- 重写:权限不能降低;不能重写private方法;返回值可以是父类返回值的子类;返回值是基本数据类型的必须相同;抛出的异常必须不大于父类抛出的异常;要么都是非static的,要么都是static的
- 关键字:super
- 表示当前对象的父类对象,修饰、调用属性、方法、构造器;(一般子类重写父类方法或有同名属性调用父类方法或属性时使用super.调用父类,对于没有重写的,没有重名的,this和super都相同)
- 在构造器内部调用父类构造器只能放在首行,与this相同,因此只能调用一次,都没有显式调用的会默认调用super();在类的多个构造器中,至少有一个调用了super(),不管是显式还是默认,至多可以有n-1个调用了this();如果有多个父类,super调用方法就近向父类中找
- 子类实例化的全过程:
- 从结果上看,子类继承父类之后,就获取了父类中声明的属性和方法,创建子类的对象,在堆空间中,就会加载所有父类中声明的属性;从过程上来看,当我们通过子类的构造器创建子类对象时,一定直接或间接的调用父类的构造器,直到调用了Object的空参构造器为止。
- 虽然调用了父类的构造器,但是只是创建了一个对象
- 面向对象特征之三:多态
- 父类的引用指向子类的对象;有类的继承,有方法的重写
- 当调用子父类同名方法时,实际执行的是子类重写父类的方法。在编译阶段,调用的是父类的方法,在运行阶段,调用的是子类的方法。
- 向下转型:可以使用instanceof判断,避免向下转型异常,如果是instanceof它的父类,也是true
- 子父类中同名同参数的要么都是static的,要么都不是
- Object类的使用(常用类的使用)
- clone()(克隆一个对象时使用,参考原型模式),equals()(判断两个对象是否完全相等),finalize()方法(对象被回收之前会调用),getClass()(获取对象的运行时类),hashCode()(获取对象的哈希值),notify()/notifyAll()/wait()(多线程中使用),toString()
==
与equals(),==
是运算符,可以用来判断基本数据类型是否相等;判断引用数据类型地址是否相等。equals()在Object中是用==
判断的。- toString():当输出一个引用的时候,实际调用的就是toString()方法
- 单元测试
- junit4:类是public的;有无参构造器;单元测试方法是public的且没有返回值;单元测试方法上面加一个@Test注解
- 包装类(常用类的使用):
- 8种基本类型都有对应的包装类,6中数值类型的包装类继承自Number
- 基本数据类型、包装类、String相互转换:(基本数据类型与包装类:构造器(或自动装箱)、.xxxValue()(或自动拆箱)),(基本数据类型(或包装类)与String:使用 + ""拼接为字符串、String.valueOf(xxx))
- 要转为String就调用String中的方法,String要转为包装类或基本数据类型就调用包装类中的方法
- 关键字:static
- 可以修饰:属性、方法、代码块、内部类
- 随着类的加载而加载,早于对象创建,由于类只会加载一次,因此类变量只有一份,存在方法区的静态域中
- 单例模式:5种写法
- main方法:
public static void main(String[] args)
,args是启动时的参数
- 类的成员之四:代码块
- 初始化类、对象;只能使用static修饰;静态代码块随着类的加载而执行,初始化类的信息,只执行一次;非静态代码块随着对象的创建而执行,new一次执行一次;可以定义多个代码块;
- 执行顺序:由父及子,静态先行,代码块先于构造器,代码块初始化等价于显式初始化(先定义先执行);注意,静态变量与静态代码块、非静态变量与非静态代码块谁写前面谁先执行(注意如果没有赋值,即时代码块写上面也不会再赋值为默认值;即默认初始化 - 显式初始化/代码块 - 构造器初始化);多个静态代码块和非静态代码块都是按顺序执行;代码块都要先于构造器执行
- 关键字:final
- 修饰:类、方法、变量;类不可被继承(String,System,StringBuffer)、方法不能被重写(Object.getClass())、变量值不可变(final变量必须赋值,包括非静态变量、静态变量、局部变量、形参,非静态变量必须显式赋值<赋默认值/代码块/构造器,构造器是对象初始化的最后一道防线>);static与final可以共同修饰属性、方法,这个属性叫做全局常量(接口中的属性都是的)
- 关键字:abstract
- 修饰类、方法:类不能实例化;方法没有方法体;(包含抽象方法的类一定是抽象类)
- 不能修饰属性、构造器等结构,不能修饰private方法、静态方法(static的不是重写)、final方法、final类;不能实例化
- 匿名对象、匿名子类对象的写法
- 模板方法模式:其中的抽象类的抽象方法也叫钩子方法,具体执行时,挂哪个子类就执行哪个子类的实现代码,不用实际去调,只需要调模板方法,模板方法中会调钩子方法
- 关键字:interface
- 接口就是规范,定义的是一组规则,体现了现实中的“如果你是/想要...(接口中的能力), 你就必须...(实现接口)”;继承是一个“是不是”的关系,而接口实现则是“能不能”的关系
- jdk7及以前: 属性默认是全局变量(public static final都可以省略),方法默认是抽象方法public abstract;jdk8:还可以定义静态方法、默认方法,必须使用static、default关键字
- 接口不能定义构造器,不能实例化,只能让类实现接口;可以实现多个接口,弥补了java单继承的局限性;接口可以多继承;接口体现多态性(不能实例化,只能被实现,类似于抽象类,然后利用多态性调用)
- 匿名实现类的写法
- 抽象类与接口异同:
- 代理模式:利用接口实现静态代理,还有动态代理(jdk代理,cglib代理);工厂模式:创建者和调用者分离,即将创建对象的具体过程隔离起来,达到提高灵活性的目的。->工厂方法模式:定义一个接口,每种对象的创建都分别实现接口,做成一个factory。->抽象工厂模式:与工厂方法模式的区别就在于需要创建对象的复杂程度上,比如系统中有多个产品族,各个产品族之间有一定的共性
- 继承父类并实现接口后,
- 父类与接口中有相同属性,在子类中调用时会有歧义,编译报错,如果调用父类的属性,使用super.xxx,如果调用接口中的属性,使用Xxx.xxx;
- 多个完全相同的抽象方法同时实现:如果继承多个接口,方法是同名同参数的(继承中没有同名的,仅接口中的方法冲突,如果继承中有同名的,看方法是否完全一样,不一样则编译报错),返回值也相同,且都不是static的(如果有加static那么就只能使用接口调用了),那么实现接口时算都实现了,如果同名方法返回值不同,则有歧义,编译报错;
- 如果继承与实现有同名同参数的方法(继承是非抽象的方法,实现是默认方法),在子类没有重写此方法时,调用父类的方法(继承优先);如果仅仅是多个接口中的default方法冲突,则编译报错 -->接口冲突
- 如果实现类要调用接口中的默认方法,使用
接口名.super.method()
- 总结:继承与实现中,如果属性重复,编译报错(有歧义),如果方法重复,继承优先;在多个接口实现中,如果默认方法重复(且继承中没有同名同参同返回值方法,如果有,继承优先),编译报错(有歧义),如果抽象方法同名同参数返回值不同,编译报错(有歧义)
- 类的内部成员之五:内部类
- 当一个事物的内部,还有一个部分需要完整的结构进行描述,而这个内部的完整结构又只为外部事物服务,那这个内部的完整结构最好使用内部类。
- 成员内部类vs局部内部类(类似于成员变量<类内,静态、非静态>vs局部变量<方法内、代码块内、构造器内>)
- 成员内部类:
- 一方面,作为外部类的成员(类比成员变量):调用外部类的结构
外部类名.this.
,可以被static修饰,可以被4种权限修饰。 - 另一方面,作为一个类:定义属性、方法、构造器等;可以被final、abstract修饰
- 一方面,作为外部类的成员(类比成员变量):调用外部类的结构
- 局部内部类:要使用类外部的变量要求必须是final的,因此不能在局部内部类的方法中修改变量的值
- 用法:
- 创建静态成员内部类对象:
外部类名.内部类名 var = new 外部类名.内部类名()
,类比静态变量,属于类,每个对象都共享 - 创建非静态成员内部类对象:
外部类名.内部类名 var = 外部类对象实例.new 内部类名()
,类比成员变量,属于对象,每个对象有一份 - 成员内部类区分属性:内部类属性
this.
,外部类属性外部类名.this.
- Thread(枚举类等)、Integer(-128~127的缓存)源码有成员内部类
- 局部内部类:在使用上类似局部变量,先定义在使用
- 创建静态成员内部类对象:
异常处理
- 在程序执行中发生的不正常的情况称之为异常:Error(JVM无法解决的严重问题,jvm内部错误、资源耗尽等,如StackOverflow、OOM,一般不编写针对性代码进行处理)、Exception(其他因编程错误或偶然的外部因素导致的一般性问题,可以使用针对性的代码进行处理)
- 分类:Error,Exception(IOExecption,ClassNotFoundException,
RunTimeException
),顶级父类是Throwable类 - 异常处理机制一:try-catch-finally,catch中子类异常声明在父类上面,否则报错;finally,声明一定会被执行的代码,即使try/catch中有return语句或catch中出现异常或throw异常,都在其之前执行finally语句
- 异常处理机制二:throws,写在方法声明处,指明方法执行时可能抛出的异常,异常后续代码不再执行
- 注意:方法重写时,子类抛出的异常必须小于等于父类抛出的异常,当使用多态时执行子类方法可以catch到
- 手动抛出异常:throws是处理异常的一种方式,throw是生成一个异常对象
- 如何自定义异常类:继承于现有结构,RuntimeException(运行时异常,不需要显式处理)、Exception,提供重载的构造器,提供全局常量serialVersionUID
多线程
-
基本概念:
-
程序、进程、线程
程序:是为完成特定的任务、用某种语言编写的一组指令的集合。一段静态的代码
进程:是正在一个运行的程序,是操作系统资源分配的基本单位
线程:是程序内部的一条执行流程,一个进程可以执行多个线程,线程是调度和执行的基本单位,每个线程拥有独立的运行栈和程序计数器
一个java应用程序java.exe至少要有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程 -
并行、并发
多个cpu同时执行多个任务
一个cpu通过时间片轮转的方式同时执行多个任务 -
使用多线程的优点
提高应用程序的响应,对于图形化界面更有意义,可增强用户体验
提高cpu的利用率
改善程序结构,将复杂的程序分为多个线程,独立运行,利于理解和修改
-
-
创建线程方法一:
- 继承Thead类,重写run方法,创建子类对象,执行start();start()方法启动当前线程,运行run方法;start()方法只能调用一次,若创建多个线程,需要创建多个子类对象,分别执行start()
-
Thread类有关的方法
静态方法:
Thread.currentThread(),返回当前线程的对象
Thread.sleep(long millitime),让当前线程睡眠,进入阻塞状态,结束后,等到cpu分配资源后继续执行
一般方法:
start(),启动当前线程,调用当前线程的run()方法
run(),需要重写Thread类的此方法,将需要执行的操作声明在此方法中
join(),线程a调用线程b的join()方法之后,线程a阻塞,直到执行完线程b,线程a才结束阻塞状态
yield(),释放当前cpu的执行权,有可能下一刻又分配到了cpu的执行权
getName(),获取线程名字
setName(String name) 设置线程的名字,如果在线程里面可以通过Thread.currentThread().setName()设置
stop 已过时,执行此方法时,强制结束当前线程
isAlive 判断当前线程是否存活
线程的优先级相关的方法
getPriority(), setPriority(int p),1-10,MIN_PRIORITY=1,MAX_PRIORITY=10,NORM_PRIORITY=5
# java的调度方法:
#同优先级的线程组成先进先出队列(先到先服务),使用时间片策略;对于高优先级,使用优先调度的抢占式策略;只是从概率上讲,并不是高优先级的先执行,再执行低优先级的
- 创建线程方法二:
-
实现Runable接口,实现run()方法,创建实现类的对象,将此对象传递给Thread类的构造器,天然支持共享数据,因为可以将同一个runnable接口实现类的对象传递给多个Thread类的构造器,所有线程使用同一个对象;如果是Thread的子类,共享数据需要使用static的属性,使所有线程(Thead的子类)共享变量。
-
总之:一个新的线程必须有一个Thead类或子类的对象
-
两种创建方式的比较:优先使用实现Runnable接口,它没有java类单继承的局限性,实现的方式更适合处理多个线程共享数据的情况,天然的支持数据共享(多个线程可以用同一个对象)。联系:Thead类也实现了Runnable接口。相同点:都需要重写run()方法,将线程要执行的逻辑声明在run()中
-
- 线程的生命周期
- Thread类的内部枚举类State中,
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINTED
;new Thread还没有start()的状态是NEW,线程正在运行是RUNNABLE,等待同步锁会进入BLOCKED,调用了wait/join会进入WAITING,带有超时时间的wait/join/sleep会进入TIMED_WAITING,线程结束为TERMINTED - 新建,就绪,运行,阻塞,死亡;
- 新建->就绪(调用start()方法)
- 就绪<->运行(根据cpu是否分配时间片,调用yield()会主动释放执行权)
- 运行<->阻塞(sleep(), b.join(), 等待同步锁, wait(), suspend()挂起)及对应的阻塞->就绪(sleep时间到,join结束,获得同步锁,notify()/notifyAll(),resume())
- 运行->死亡(正常执行完,调用stop(),错误/异常且没处理)
- Thread类的内部枚举类State中,
- 线程的同步
- 使用同步代码块
- 使用同步方法:如果操作同步数据的代码块都在同一个方法里
- 注意:使用同步时锁要唯一,同步方法中,非静态方法锁为this,静态方法锁为类本身
- 使用ReentrantLock:
- lock使用显式锁,synchronized是隐式锁,出了作用域自动释放。
- lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供了更多的子类)
- 优先使用顺序:lock,同步代码块,同步方法
- 单例模式懒汉式线程安全的写法(双检锁)
- 线程的通信
- 三个方法:使用wait/notify/notifyAll
说明:这三个方法必须使用在同步块或同步方法中(lock不行,有别的实现方式),这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,这三个方法是定义在Object类中的。 - wait表示当前使用同步监视器的线程等待,并释放锁,进入生命周期WAITING(wait()带时间参数进入TIMED_WAITING),等待被同步监视器notify()/notifyAll()
- sleep()和wait()的异同:
- 相同点:都使线程进入BLOCKED状态。
- 不同点:
- 声明位置不同,sleep声明在Thread类中是静态的,wait声明在Object类中;
- 使用要求不同,sleep可以在任何场景下使用,wait只能在同步代码块或同步方法中使用;
- 如果都在同步代码块或同步方法中使用:sleep不释放锁,wait释放锁
- 三个方法:使用wait/notify/notifyAll
- 创建线程方法三:
- 实现Callable接口:可以有返回值、可以抛出异常、支持泛型
- 实现步骤:
1,创建一个实现了Callable接口的类,将此线程需要执行的操作声明在call()中
2,创建Callable接口实现类的一个对象
3,将Callable接口实现类的对象传递给FutureTask构造器中,创建FutureTask的对象
4,将FutureTask的对象传递给Thread类的构造器中,创建Thread类的对象,调用start()方法启动线程
5,使用FutureTask的get()方法获取Callable实现类中call方法的返回值
- 创建线程方法四:
- 使用线程池:思路:提前出创建好多个线程,放入线程池,使用时直接获取,使用完放回池中,避免频繁创建销毁线程,实现重复利用。好处:提高响应速度(减少创建线程的时间),降低资源消耗(重复利用线程池中的线程,不需要每次都创建),便于线程管理(线程池的一些参数,核心线程数、最大线程数、队列长度、允许线程空闲时间)
- 线程池会先创建核心大小,并执行任务,如果有新的任务来会放入队列,如果队列满会创建最大线程数内的线程进行处理,如果最大线程数也满了,会调用handler。如果队列没有满,会放入队列,并不会创建线程马上执行。
- 创建过程:
1,推荐创建方式:new ThreadPoolExecutor(1, 2, 100L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1));
,不推荐使用Executors.newFixedThreadPool(10)
,其内部队列是Integer.MAX_VALUE
如果线程泄漏容易OOM,而且不可以设置handler
2,执行指定线程的操作,需要提供实现Runnable(execute调用)或Callable(submit调用)接口的类的对象
3,关闭连接池,service.shutdown(),如果不用了要关闭
- 线程中池的常用方法
- shutdown()/shutdownNow(),都是关闭线程池,不可再提交任务,会发送中断信号,但是后者会移除在队列中还未执行的任务
- awaitTermination(),等待所有任务执行完成,必须在方法执行前执行shutdown(),否则会一直阻塞直到超时或当前线程收到中断信号,所在使用前必须执行shutdown()
Java常用类
String类
final类型的类,代表不可变的字符序列,对String类型变量的任何修改都会重新在常量池中新创建一个字符串。
- 定义:定义字符串的两种方式,一种使用字面量直接赋值,另一种使用new(创建了两个对象,堆中一个对象,对象的value属性指向方法区的一个
char[]
),(JDK11改为byte[]
) - 拼接:常量与常量的拼接结果在常量池,且常量池中不会有相同内容的常量;只有其中有一个是变量,结果在堆中;如果拼接的结果调用intern()方法,返回值就在常量池中。
- 字面常量+字面常量:直接返回常量池中的值的地址
- 变量+字面常量:会在堆中new一个对象,对象中的value指向常量池中的值(如果变量加了final,那么变量就变成了常量,结果还是在常量池)
- 字符串的intern()方法:返回值直接返回常量池中的值的地址
- 值传递:String作为方法形参时,方法内部改变变量值不会改变外部的值,String是不可变的(可以当成内基本数据类型是值传递),内部使用的是形参,是一个新变量,是String的值传递,并不是地址传递。
- 存储位置:JDK6(方法区,常量池在方法区),JDK7(方法区,常量池在堆中),JDK8(方法区改叫元空间,常量池在元空间,如果说在方法区也没毛病),方法区是规范说法,具体实现是永久区、non-heap、元空间
- String常用方法
查询:length(), charAt(), isEmpty(), endsWith(), startsWith(),contains(), indexOf(), lastIndexOf()
比较:equals(), equalsIgnoreCase(), compareTo(),
变换:toLowerCase(), toUpperCase(), trim(), subString()(左闭右开),
替换: replace()(替换所有), replaceAll()(正则表达式匹配然后替换), replaceFirst()(正则表达式匹配替换第一个),
匹配:match()(正则表达式匹配),
切片:split()(正则表达式切分),
与基本类型、包装类的转换
String -> 基本类型、包装类:调用包装类的parseXxx(str)
基本类型、包装类 -> String:调用String类的String.valueOf(xxx)
与字符数组的转换
String -> 字符数组:调用String的toCharArray()
字符数组 -> String:调用String的构造器new String()
与字节数组的转换,编码,解码
String -> 字节数组:调用String的getBytes()
字节数组 -> String :调用String的构造器new String()
与StringBuffer,StringBuilder的转换
调用StringBuffer,StringBuilder的构造器new StringBuffer(),new StringBuilder()
调用StringBuffer,StringBuilder的toString()
StringBuffer,StringBuilder
与String区别:可变,StringBuffer线程安全,StringBuider线程不安全,效率StringBuider > StringBuffer > String。
底层都是char[] value
存储,StringBuffer和StringBuilder默认初始容量16(多出16),扩容:原始容量*2+2(不够则是现有大小本身)
,尽量使用构造器时确定容量避免频繁扩容
常用方法:
- 增:append()
- 删:delete()
- 改:setCharAt(), replace(), reverse()
- 查:charAt()
- 插:insert()
- 长度:length()
日期时间API
JDK8之前:
- System.currentTimeMillis(),距离1970年1月1日的毫秒数,时间戳
- java.util.Date,java.sql.Date:子父类关系(sql是util下的子类),sql.Date转换为util.Date直接强转即可,利用多态。util.Date转为sql.Date可以使用util.Date的getTime()得到时间戳,利用sql.Date的构造器创建sql.Date
- 注意两个方法的使用:getTime()获取时间戳,toString()
- Calendar抽象类,日历类,
Calendar.getInstance()
获取实例,get方法,set方法,add方法用于获取、设置、修改时间;getTime方法,setTime方法用于与Date的转换
JDK8:
现有的Date,又有了Calendar代替Date,也有许多问题:可变性,像日期这样的类应该是不可变的;偏移量,Date偏移量从1900年开始,月份都是从0开始;格式化,格式化又得用Date;无法处理闰秒;非线程安全;所以有很多问题。
LocalDate
,LocalTime
,LocalDateTime
,最后一个常用,表示时间日期。now()获取当前时间,of()设置时间,getXxx获取事件,withXxx设置时间,plusXxx增加时间,minuxXxx减少时间Instant
,瞬时,类似于Date,LocalDateTime类似于Calendar,now()获取当前时间DateTimeFormatter
,格式化,类似于SimpleDateFormatter,ofPattern()实例化,formatter()格式化, parse()解析
比较器Comparable、Comparator
- 实现Comparable接口,自然排序,在任何位置都可以比较,重写compareTo()方法,a.compareTo(b),实现类的所有对象都可以调用,使该接口实现类的对象都具有了可比较的特征
- 实现Comparator接口,定制排序,只比较一次,重写compare()方法,当元素的类型没有实现Comparable接口,或者排序规则不适用当前操作时,使用Comparator,是一个可以用于比较两个对象的比较器。一般只用一次使用匿名内部类
System,Math,BigInteger,BigDecimal
- System,标准输入、输出流、错误流,
System.currentTimeMillis()
获取时间,System.gc()
通知垃圾回收,System.getenv()
获取环境变量,System.getProperties()
获取属性 - Math,绝对值,三角函数,平方根,a的b次幂...
- BigInteger,任意精度整型大数的运算,BigDecimal任意精度浮点型大数的运算
枚举类与注解
枚举类Enum
- 自定义枚举类:私有化构造器;属性为private final(不可变);提供public static final的枚举对象;其他诉求(get方法、toString方法)
- 使用enum定义枚举类(本质上是Enum的子类):
- 使用enum关键字代替class
- 先声明多个对象,用逗号隔开
- 提供构造器(可选)
- 其他方法(toString, get/set)
- Enum类中的三个常用方法toString(), values(), valuesOf():values获取所有的枚举对象,valuesOf通过名称获取枚举对象
- 使用enum定义枚举类实现接口:可以在每一个枚举对象后面重写接口中的方法;也可以在最外边定义方法,这些方法每个对象都可以调用,结果相同
注解Annotation
其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息,代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证和部署。
Annotation可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在Annotation的"name=value"中
未来的开发模式都是基于注解的,框架 = 注解 + 反射 + 设计模式
- javaSE中,编译时检查的三个基本注解:@Override,@Deprecated,@SuppressWarnings
- 自定义注解参考@SuppressWarnings,自定义注解必须配上注解的信息处理流程才有意义(反射),一般都会有Retention/Target
- 使用@interface关键字,自动继承java.lang.annotation.Annotation接口
- 成员变量以无参方法声明,方法名和返回值定义了该成员的名字和类型,称为配置参数,类型只能是八种基本数据类型、String、Class、Enum、Annotation及以上数组类型
- 可以在定义时使用default指定初始值
- 如果只有一个成员,建议使用参数value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值,格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有定义成员的Annotation称为标记,包含成员变量的称为元数据Annotation
- 元注解:对现有注解进行解释说明的注解
- @Retention:用于指定修饰的Annotation的声明周期,生命周期枚举类为RetentionPolicy,SOURCE/CLASS(默认)/RUNTIME,只有声明为RUNTIME才能通过反射获取
- @Target:用于指定被修饰的Annotation能用于修饰哪些程序元素,枚举类为ElementType
- @Documented(使用较少):表示所修饰的注解在被javadoc解析是保留下来(保留该注解到文档上)。要求Retention是RUNTIME
- @Inherited(使用较少):被它修饰的Annotation将具有继承性,如果某个类使用了被@Inherited修饰的Annotation,则子类将自动具有该注解
- @Repeatable:可重复注解(jdk1.8),同一个注解可在同一个地方标记多次。定义:在MyAnnotation上声明
@Repeatable(MyAnnotations.class)
,定义注解MyAnnotations为MyAnnotation[] values()
;MyAnnotation与MyAnnotations的@Target和@Retention等元注解都要相同,因为这两个注解都用了 - 类型注解:@Target的参数多了两个(jdk1.8),TYPE_PARAMETER(表示可以写在类型参数的声明语句中,也即是泛型),TYPE_USE(表示该注解能写在使用类型的任何语句中,泛型、异常、强转)
Java集合框架(Java Collections Framework)
相关api:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/package-summary.html
官方介绍:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-index.html
集合和数组都是对多个数据存储操作的结构,也称为Java容器。数组:初始化后长度确定,类型确定,提供的方法有限。
Arrays也是java集合框架的一部分。
Java集合框架可以分为Collection和Map,Collection存储一个一个对象,没有直接实现类(常用子接口List有序可重复,Set无序不重复);Map存储一对一对数据,有直接实现类
- Collection接口
- List接口:ArrayList,LinkedList,Vector
- Set接口:HashSet,LinkedHashSet,TreeSet
- Map接口:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
Collection接口中的方法
Collection接口中的常用方法:
- add(), addAll(), size(), isEmpty(), clear(),
- contains(), containsAll(), remove(), removeAll(), retainAll(), (包含,删除,差集,交集,都会调用equals()方法)
- equals(), hashCode(),
- toArray()(与Arrays.asList()相互转换,返回固定大小的原数组),
- iterator(),返回一个迭代器Iterator,使用hasNext()和next()遍历Collection, 使用remove()删除(要在next后调且只能调一次)。
注意:remove只删第一个,removeAll和retainAll分别删除和保留所有调用集合存在于另外一个集合的元素;向Collection接口的实现类的对象中添加元素时,一般元素的所在类要重写equals()方法
说明:向Collection接口中添加元素,要求元素所在类重写equals()方法,不涉及到存储,不用重写hashCode()方法
迭代器接口Iterator、Iterable
Collection实现了Iterable接口,该接口聚合了Iterator接口,实现类如ArrayList内部返回Iterator时是内部类Itr,Itr实现了Iterator接口。增强for循环利用的是迭代器
Collection子接口之一:List接口
List集合中元素有序、可重复,接口常用实现类有ArrayList,LinkedList,Vector(不常用)
List接口常用实现类源码分析:
- ArraysList:
- List接口的主要实现类,底层使用Object数组存储,线程不安全,效率高
- jdk7初始容量为10,扩容是1.5倍(不够则是现需要大小本身)
- jdk8初始容量为0,第一次add容量设为10,之后扩容1.5倍(不够则是现需要大小本身),延迟了数组创建,节省内存
- LinkedList:插入、删除效率高,底层是双向链表,first,last分别指向头尾,Node节点为私有内部静态类
- Vector:List接口的古老实现类,线程安全,效率低,初始默认为10,扩容为2倍
List接口中的常用方法:除了继承Collection的方法外,List集合里还添加了一些根据索引
操作集合元素的方法,因为List是有序的,Collection兼顾List和Set
- 一些重载Collection接口的方法(带索引的):add(), addAll(), remove(), get(), indexOf(), lastIndexOf(), set(), subList()
- 增、删、改、查、插、长度、遍历
注意:subList()返回的是原list的视图,返回的类型是SubList,原list不可以有对数据的结构性修改,比如添加、删除一个元素,否则subList在进行方法调用过程中会报ConcurrentModificationException。对subList的任何修改都会反映到原list上。
remove(1)删除的是索引为1的,如果要删除对象使用Integer,List存储的都是对象,基本数据类型进行了自动类型提升。
Collection子接口之二:Set接口
Set集合中元素无序、不可重复,接口常用实现类有HashSet,LinkedHashSet(HashSet子类),TreeSet
-
无序性:不等于随机性,指的是存储的数据在底层数组中不是按照数组索引的顺序添加,而是根据数据的Hash值决定
-
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
- 添加元素过程:首先调用hashCode()计算哈希值,通过某种算法计算出存储位置,如果此位置没有元素,则添加到此位置;如果有元素,比较哈希值,如果哈希值不相等则元素不同添加到该位置的链表中(哈希值不相同一定不是同一个元素,哈希值相同时元素相同的必要不充分条件),如果哈希值相等,调用equals()方法,如果返回true,添加失败,否则添加成功。
-
重写hashCode()里用31乘:系数越大,哈希值越大,冲突越小,也不能太大;31是质数,减少冲突;31只占用5bit,数据溢出概率小;31可以用左移5位-1,计算效率高;
- 重写原则:多次调用返回形同的值;equals()返回true时,hashCode()也返回true;用equals()笔比较的字段,都应该用来计算哈希值。一般自动生成即可
向Set里面添加数据要重写hashCode()方法和equals()方法,要保持一致性,即相等的对象必须有相等的哈希值
Set接口方法:没有额外定义新的方法,使用的都是Collection中声明过的方法;向Set接口实现类中添加元素,要求元素所在类重写hashCode()和equals()方法(要判断是否重复,hashCode()是为了提高效率设计的)
Set接口常用实现类:
- HashSet:线程不安全,底层是数组,jdk7初始默认16,jdk8初始为0,第一次添加时为16,底层实现是HashMap,value为PRESENT,一个Object对象(HashMap里会调用value的hashCode()和equals()方法,避免出错),可以存储null
- LinkedHashSet:HashSet的子类,记录了元素添加顺序(增加了一对指针,指向添加的前一个和后一个元素,遍历效率高),底层是数组+链表
- TreeSet:可以按照指定元素进行排序,底层是红黑树,不能添加null,只能添加同类的对象,且该类实现了Comparable接口,或使用Comparator定制排序。比较标准是compareTo()不再是equals()
Map接口
Map接口常用实现类:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
- HashMap,是Map的主要实现类,可以存放null的key和value,(jdk8是数组+链表+红黑树)
- LinkedHashMap,是HashMap的子类,频繁遍历效率高,增加了一对指针before,after记录添加顺序
- TreeMap,底层是红黑树,不可存储null的key,插入的key必须是同一个类的对象,能够自然排序或者实现定制排序,需要key元素实现hashCode()和equals()方法。内部有SubMap等类,SubMap实现SortedMap接口
- Hashtable,Map的古老实现类(现有的Hashtable后有的Map接口),线程安全,不能存放null的key和value
- Properties,Hashtable的子类,key和value都是String,常用来处理配置文件
源码分析:
- HashMap:
- jdk7是数组+链表(新元素放链表最前面),初始容量为16
- jdk8是数组+链表+红黑树(链表元素个数大于8且容量大于64转化为红黑树,否则使用扩容,提高查询效率,新元素放链表最后面),一开始不创建数组,第一次添加元素时,初始容量为16,负载因子默认为0.75f(临界值=容量*负载因子,兼顾数组利用率和链表长度,太小,频繁扩容,链表较小,太大,链表较长),元素数量大于临界值且当前位置不为空才会扩容为2倍,某个链表元素数量大于8且容量大于64转化为红黑树,否则扩容为2倍,带参数的构造器,返回一个不小于参数的2的x次幂,比如参数为负数、0、1都返回1,参数为2、3、4返回2、4、4,参数为5、6、7、8,都返回8...。
- 所以在使用HashMap时,如果数据量较大,还是传入数据大小作为容量参数尽量减少扩容比较好?
- LinkedHashMap:内部使用节点继承了HashMap里面的Node(jdk8),增加了before、after指针
Map接口常用方法:
- put(), putAll(), remove(), clear(), get(), containsKey(), containsValue(), isEmpty(), size(), equals()
- keySet(), values(), entrySet()(返回Map.Entry类型的entry,Map的内部接口,有getKey(), getValue()方法)
- 增put()、删remove()、改put()、查get()、长度size()、遍历(keySet(), values(), entrySet())
key所在的类要重写equals()和hashCode()方法(本质上hashCode()是为了存储高效设计的)
TreeMap使用:key必须是同一种对象,重写hashCode()和实现Comparable接口(或定制排序)
Properties使用:使用Properties
Collections工具类,操作List、Set、Map等集合的工具类
Arrays是操作数组的工具类,也是属于java集合框架下的
- 从数组转化为List使用Arrays.asList()
- 从List转化为Array使用toArray()
Collections常用静态方法:
- 排序操作:reverse(), shuffle()随机打乱, sort(), swap()
- 查找替换:min(), max(), frequency()出现次数, copy(), replaceAll()
- copy()需要dest的size不小于src,可以使用Arrays.asList(
new Object[src.size()]
)创建一个List(这个list是Arrays的内部类,不可进行结构上的改变操作)
- copy()需要dest的size不小于src,可以使用Arrays.asList(
- synchronizedXxx(), 返回相对应的线程安全的集合,内部对方法都增加了Synchronized同步代码块,锁为this
- 如果使用Iterator, Spliterator or Stream遍历时,需要用户自己在外部保证同步
泛型(Generic)
设计背景:
- 集合容器类在设计阶段、声明阶段不能确定存的到底是什么类型的对象,所以jdk5之前,集合容器类型只能设计为Object,之后可以通过泛型解决。因为这个时候只有元素的类型是不确定的,如何管理,如何保存都是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型,如
Collection<E>
,这个E就是类型参数,即泛型。 - 参数化类型,保证编译器没有发出警告,运行时就不会产生ClassCastException,同时代码更简洁、健壮(能够在编译时检查错误)
定义:
- 所谓泛型,就是允许在定义类、接口、方法时通过一个标识符表示类中某个属性的类型或者某个方法的返回值及参数类型,这个类型参数将在使用时(如继承或实现接口、用这个类型声明变量、创建对象、被调用时)确定(即传入实参时确定)
- 泛型方法:在返回值前面使用<>,与泛型类、接口无关,指明泛型方法的泛型参数,在调用该方法时根据实参类型传入
- 如果没有在形参中使用泛型参数,那么它的意义在于方法中第一次使用泛型参数进行赋值(强转)时,后面所有使用泛型参数变量时,都必须是统一的。
- 泛型的意义在于参数化类型,统一类型(类型被参数化了,那么同一个参数的类型是统一的),底部都是Object
注意:
- 泛型是编译时操作,会根据泛型进行编译器检查,编译之后会进行泛型擦除,将泛型都换成Object。如果没有使用泛型,则默认是Object,但不等价于Object。经验:要用都用,要不用都不用
- 泛型的类型必须是类,不能是基本数据类型
- 子类继承泛型类时,可以直接指明父类的泛型参数类型,也可以不指明,也可以全部保留或部分保留父类泛型,此时子类也要声明保留的父类泛型参数
- 泛型类型不同的引用不能相互赋值,虽然在编译器是两种类型,但是在运行时只有一种类型
- 静态方法不能使用类的泛型,可以从面向对象的角度解释,泛型参数只有实例化时才确定,静态方法是类方法,早于对象的创建。但是可以使用泛型方法
- 异常类不能是泛型类,因为catche时的泛型不一定是异常类
- 创建泛型类型的对象时不能使用
new T[]()
,可以使用T[] t = (T[]) new Object[]
强转,这个t之后在使用的时候以T类型使用(想象一下泛型都会被擦除为Object,只是在编译期间通过泛型做一些统一参数的约束)
泛型的嵌套,HashMap的entrySet()返回的是EntrySet extends AbstractSet<Map.Entry<K,V>>
,父类是AbstractSet<E>
泛型在继承上的体现:
- 如果A是B的子类,那么
G<B>
是不能赋值给G<A>
的,即不具备子父类关系,是并列关系List<String>
不能赋值给List<Object>
否则泛型就失去了意义,(数组String[]与Object[]是有子父类关系的,因为是原生类型)
- 通配符:
G<B>
和G<A>
的公共父类可以声明为G<?>
,比如List<?>
,此时不能向其中添加数据(null除外),如果读取数据只能读取为Object,<?>
可以理解为(-无穷,+无穷)
<?>
不能用在泛型类、接口、方法的声明上,不能用在创建对象上new ArrayList<?>
- 有限制的通配符
<? extends A>
,小于等于A的类,可以理解为(-无穷,A]
,读取数据可以读取为A,不能写入,因为里面存储的都是小于等于A的,有多小是不确定的<? super A>
,大于等于A的类,可以理解为[A,+无穷)
,读取数据只能为Object,写入数据可以写A及小于A的类型,因为里面存储的都是大于等于A的- 在声明泛型类、接口、方法上可以使用
<T extends A>
或<T super A>
,<?>
相关的只能作为形参,不能定义各种泛型
IO流
File类
- File类的三个构造器:File(String), File(String, String), File(File, String)
- 常用方法:
- 获取属性:getAbsolutePath(), getPath(), getName(), getParent(), length(), lastModified()
- 列出文件:list(), listFiles()
- 重命名:要想为true,file1.renameTo(file2), file1必须存在,file2必须不存在,file1会被移动到file2下
- File类的判断方法:isDirectory(), isFile(), exists(), canRead(), canWrite(), isHidden()
- File类的创建:createNewFile(), makdir(), makdirs()递归创建目录
- File类的删除:delete(),要求删除的目录下没有文件或目录,否则删不掉,必须递归删除。具体使用可参考其他包下的FileUtils类写工具类
IO流原理及流的分类
三种分类方式:输入流,输出流;字节流,字符流;节点流,处理流(在已有流的基础上)
- 字节流基类:InputStream,OutputStream;字符流基类:Reader,Writer,共四个抽象基类
- 节点流:FileInputStream,FileOutputStream,FileReader,FileWriter,在抽象基类前面加File,直接处理文件,也叫文件流,共四个文件流,对应四个抽象基类
- 缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,在节点流前面加Buffered,处理流的一种,提高读取效率,共四个缓冲流,对应四个文件流
- 转换流
具体说明:
- 字节流 四个抽象基类
public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable
public abstract class Reader implements Readable, Closeable
public abstract class Writer implements Appendable, Closeable, Flushable - 文件流:两个字节流分别继承各自的抽象基类;而两个字符流分别继承InputStreamReader、OutputStreamWriter,它们又分别继承抽象基类Reader、Writer
public class FileInputStream extends InputStream
public class FileOutputStream extends OutputStream
public class FileReader extends InputStreamReader,(InputStreamReader extends Reader)
public class FileWriter extends OutputStreamWriter,(OutputStreamWriter extends Writer) - 缓冲流(处理流):两个缓冲字节流继承了FilterInputStream、FilterOutputStream,它们又分别继承了抽象基类InputStream、OutputStream,两个缓冲字符流继承各自的抽象基类
public class BufferedInputStream extends FilterInputStream,(FilterInputStream extends InputStream)
public class BufferedOutputStream extends FilterOutputStream,(FilterOutputStream extends OutputStream )
public class BufferedReader extends Reader
public class BufferedWriter extends Writer - 转换流(处理流):这两个是字符流,看后缀,都是操作的字符(字符读取,字符写出),操作的是
char[]
public class InputStreamReader extends Reader
public class OutputStreamWriter extends Writer
说明:
在文件流中,字符流FileReader、FileWriter继承了InputStreamReader、OutputStreamWriter,它们是转换流,FileReader在读取时,是通过InputStreamReader将字节流FileInputStream转换为字符流(以特定的字符集解码为字符);同样,FileWriter在写出时,是通过OutputStreamWriter将字符流FileWriter转换为字节流(以特定的字符集编码为字节)。所以底层都是字节流(二进制文件,一个字节一个字节读写),在程序层面使用时,读时通过转换流转换为字符流,写时通过转换流转换为字节流。
文件流
四个文件流:
- FileReader:
read(char[])
,返回读到的字节数;读到末尾返回-1,(注意将char[]
转换成String时调用String(char[], int, len))
- FileWriter:
write(char[] cbuf, 0, len)
,写出,不需要文件存在,默认覆盖,参数传true表示追加 - FileInputStream:
read(byte[] buffer)
,返回读取的字节个数,读到末尾返回-1 - FileOutputStream:
write(byte[] buffer, 0, len)
,写出,相比于Reader/Writer只是换成字节数组
总结:标准使用步骤: 1,File类实例化,2,流实例化(可嵌套处理流),3,数据的读入写出(使用带数组参数的),4,资源的关闭
注意:
保证流一定会关闭,使用try-catch-finally处理,注意判断流是否存为null,不为null调用close()。
提供具体的流之前,要判断文件是否存在,读文件要求文件一定存在。关闭外层流自动关闭内层流。一般包一层缓冲流提高效率
缓冲流(处理流之一)
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 处理流的一种,提高读取效率,内部提供了缓冲区,默认8M,可以传参数设置,将字符数组、字节数组内的数据先放入缓冲区,等缓冲区满了再flush()写出,从而减少写出的次数,增加读取、写出的效率。
- 以节点流为参数,在外层将节点流包一层
- 缓冲输出流都有flush()方法,表示将缓冲区数据强制刷新出去
- 缓冲字符流其他新方法:br.readLine()可以一次读一行,不包含换行符;bw.newLine()可以写出换行符
转换流(处理流之二)
InputStreamReader、OutputStreamWriter
-
从操作实际文件的角度分析,读取时,如果使用了字节流,使用InputStreamReader转换成字符流读入。写出时,如果使用了字符流,使用OutputStreamWriter转换成字节流写出。(本质上,FileReader、FileWriter直接读取文件和写出文件时也是利用了父类转换流进行了转换才读入为字符读出为字节的)
-
为什么没有读入的时候字符流转换成字节流呢?因为字符流内部还是用的字节流进行的读取、写出,字符流分别继承了InputStreamReader和OutputStreamWriter,将读取的字节转换为字符,将要写出的字符转换为字节
-
作用:提供字符流与字节流的转换,字节->字符为解码的过程,字符->字节为编码的过程
标准输入、输出流(处理流之三)
System.in是一个InputStream,一般从键盘读取
System.out是一个PrintStream,一般输出到控制台
System的setIn()和SetOut()可以修改流的位置
打印流(处理流之四)
PrintStream、PrintWriter
- 提供了一系列重载的print()和print()方法,将基本数据、Object等数据以字节形式写出。也可以指定输出的位置(System.out是打印流的一个对象),内部依然使用了BufferedWriter,OutputStreamWriter,将输入数据转换成字节流,再输出到文件或控制台等节点
注意:
- 不会抛出IOException
- 都有自动flush()功能
- PrintStream打印的所有字符都使用平台默认的字符编码转换为字节。在需要写入字符而不是字节的情况下,应该使用PrintWriter类
- System.out返回的是PrintStream
字节写出的话不需要考虑编码,因为不涉及到字符,字节写出都是一个个
\uxxxx
,字符写出是使用了字符编码的,如果使用utf-8,可以是多个字节表示一个字符
- 具体说明:
public class PrintStream extends FilterOutputStream implements Appendable, Closeable(FilterOutputStream extends OutputStream)
public class PrintWriter extends Writer
数据流(处理流之五)
DataInputStream、DataOutputStream
读出或写入基本数据类型的变量或字符串,写入和读出的顺序必须相同
对象流(处理流之六)
ObjectInputStream,ObjectOutputSteam,对象序列化、反序列化
用于存储和读取基本数据类型或对象的处理流
注意:
- 对象所属的类必须是可序列化的(实现Serializable接口),类的所有属性也必须是序列化的
- 并且添加一个静态常量
private static final long serialVersionUID = xxL
- java在反序列化的时候会判断这个serialVersionUID,如果和之前写出的对象不同,会反序列化失败。如果不写,java运行时会自动根据类的内部细节生成,如果类的实例变量变了,那么这个自动生成的serialVersionUID肯定会变,所以如果不写修改了之后就无法反序列化
- static和transient修饰的变量是不可序列化的,为默认值,不会保存的,可以将不想序列化的属性加transient
- 实现Externalizable接口的类的序列化可以自定义序列化的属性,包括transient修饰的属性,但static修饰的不可序列化
随机存储文件流(RandomAccessFile)
既可以作为输入流,也可以作为输出流,有四种access模式:r,rw,rwd,rws,(rwd表示随时更新内容到磁盘,rws表示更新内容和元数据,会有至少一次的low-level I/O operation)
特点:
- 直接继承了Object,实现了DataOutput, DataInput接口,所以既有读也可以写
- 支持文件的任意位置读、写,内部含有一个记录指针,表示当前读的位置,getFilePointer()获取当前位置,seek()将指针定位到指定位置。可以以此实现断点下载和多线程同时下载的功能
- 当模式为r时,要求文件必须存在,否则有异常。当模式为rw时,如果文件不存在会自动创建(在new RandomAccessFile()时就会创建)
具体说明:
public class RandomAccessFile implements DataOutput, DataInput, Closeable
NIO中的Path、Paths、Files类的使用
java NIO(New IO,Non-Blocking IO)是从java 1.4开始引入的一套新的IO api,可以替代标准的Java IO api,NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO,NIO将以更加高效的方式进行文件的读写操作
Java api提供了两套NIO,一套是针对标准输入输出NIO,一套是网络编程NIO
- java.nio.channel.Channel
- FileChannel,处理文件
- SocketChannel,TCP网络编程的客户端的Channel
- ServerSocketChannel,TCP网络编程的服务器端的Channel
- DatagramChannel,UDP网络编程中发送端和接收端的Channel
jdk7中,java对NIO进行了极大的扩展,增强了对文件处理和文件系统的支持,以至于称为NIO.2
- Path接口:早期java只提供File来访问文件系统,但File功能有限,性能也不高,而且大多数方法在出错时仅返回失败,并不会提供异常信息。NIO.2为了弥补这个不足,引入了Path接口,代表一个与平台无关的路径,描述了目录结构中的位置,Path可以看成是File的升级版,实际引用的资源可以不存在。
- Files、Paths工具类
- Files工具类包含了大量静态的工具方法操作文件
- Paths工具类包含两个返回Path的静态工厂方法,Paths.get(String first, String...),Paths.get(URI uri),其实是调用的Path.of()方法,源码也说了,建议使用Path.of(),get()方法以后可能会废弃
API Note: It is recommended to obtain a Path via the Path.of methods instead of via the get methods defined in this class as this class may be deprecated in a future release.
NIO总结:
- 获取Path使用Path.of(),可以看成File的升级版,与平台无关,实际路径可以不存在
- 操作文件:Files工具类,创建文件、目录、硬链接、软连接、删除、拷贝、移动、查看文件信息(可读、可写、可执行、是隐藏文件、符号链接)、文件所在磁盘信息、列出文件、递归列出文件、直接读取流、写出流
- 读写可以使用第三方jar包,如apache commons-io,apache commons官网https://commons.apache.org/
网络编程
网络通信需要ip地址和端口号唯一定位某个主机的某个进程,ip地址和端口组成Socket(套接字)
在java中,ip地址使用InetAddress类表示,通过InetAddress.getByName()
获取一个ip地址的实例对象,参数可以是ip地址和域名。两个常用方法getHostName()
获取主机名,getHostAddress()
获取ip地址
TCP建立连接需要三次握手,释放连接需要四次挥手
TCP编程
客户端:
- 创建Socket,指定服务器端的ip和端口
- 从socket获取输入流
- 往输出流中写数据
- 关闭资源
服务端:
- 创建ServerSocket,指定端口
- 执行accept()获取socket
- 从socket获取输入流
- 从输入流中读取数据
- 关闭资源
UDP编程
客户端:
- 创建DatagramSocket获取socket
- 创建DatagramPacket,存储UDP报文的包,指定数据和端口
- 执行socket.send(packet)发送数据
- 关闭资源
服务端:
- 创建DatagramSocket获取socket,指定端口
- 创建DatagramPacket,存储UDP报文的包,接收数据
- 执行socket.receive(packet),将报文接收在packet中
- 通过packet获取接收的报文数据
- 关闭资源
URL编程
后面使用HttpClient替代HTTPURLConnection
- 全局资源定位符,对应网络上某一资源地址,通过url可以访问网络上的资源
- 传输协议://主机名:端口//资源地址#片段名?参数列表
- java可以通过url获取网络上的资源
使用:
- 创建URL,指定url
- 执行url.openConnection(),获取到一个连接
- 执行connection.connect(),进行连接
- 根据connection获取资源
- 关闭连接
Java反射机制
反射机制允许程序在执行期间借助于Reflection api取得任何类的内部信息,并能直接操作任意对象的内部属性和方法
java里面的一切类或类型在jvm加载之后都成为Class的一个实例对象(也称为运行时类),也就是说,所有的类在jvm里面都对应着一个实例,这些实例都是Class的实例。换句话说,Class的实例对应这个一个运行时类
理解Class类并获取Class的实例
Class类
- 每个对象都由一个对应的Class类型的对象所生成,这个Class对象记录了类的结构有关的信息,对应一个.class文件
- Class本身也是一个类,它的对象只能由系统创建
- 一个加载的类在jvm中只有一个Class实例,对应一个.class文件
- 每个类的实例都会记录自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被加载结构
- Class是reflection的根源,对任何想要动态加载、运行的类,都必须先获得相应的Class对象
获取Class实例的四种方式
- 已知类调用class属性
- 已知类的对象,调用对象的getClass()方法,每个对象都可以获取它有哪个Class实例所生成
- Class.forName(),传入类的全类名
- 通过某个类获取它的类加载器,使用类加载器load()
类的加载与ClassLoader的理解
类的加载:
- 加载,链接,初始化
- 主动引用会初始化类:
- 虚拟机启动初始化main方法所在的类
- new一个对象
- 调用类的静态成员和静态方法
- 对类反射调用
- 初始化一个类,如果父类没有初始化,先初始化父类
- 被动引用不会初始化类(初始化后才有类的结构),比如调用static final字段的变量,调用父类的static变量只会加载父类
- 当访问静态域,只要真正声明这个域的类才会被初始化,如果是通过子类引用父类的静态变量不会导致子类的初始。比如类静态属性A.b,b是父类中的,那么只会加载父类,比如访问A.M,M是static final的,不会初始化父类和A类
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发类的初始化(static final修饰的)
ClassLoader:
- 作用:将class字节码加载到内存中,将这些静态数据转换成方法区的运行时结构,然后在堆中生成一个代表这个类的Class对象,作为方法区中类数据的入口(比如new一个对象,调用方法,会先找到这个Class对象,然后根据这个Class对象找到方法区的运行时结构中对应的方法代码进行执行)
- 类被加载后会被缓存一段时间,jvm也是可以回收这些对象的
- 类加载器:引导类加载器(加载核心类库),扩展类加载器(加载扩展类),系统类加载器(加载自定义类)
创建运行时类的对象
- 使用
clazz.newInstance()
,从jdk9之后被弃用了,要求有空参构造器,权限够,一般是public - replaced by
clazz.getDeclaredConstructor().newInstance()
,使用指定构造器创建(参数是可变形参,可以没有参数,即空参构造器),会抛出NoSuchMethodException
异常更准确
获取运行时类的完整结构、指定结构
所有运行类的结构都可以获取,比如
- 获取类的属性,属性相关的权限修饰符、属性类型、变量名,(不包括静态变量,因为静态变量是类加载)等
- 获取类的方法,方法相关的权限修饰符、参数类型、注解、返回值、方法名等
- 获取类的构造器
- 获取父类 及带泛型的父类的泛型
- 获取接口
- 获取所在的包
- 获取类的注解...
总结:
- 获取完整结构使用getXxxs(),返回所有public的,包括父类。或者使用getDeclaredXxxs(),返回当前类所有权限的,不包括父类。一般使用后者
- 获取指定使用getXxx(name),要求必须是public的,可以获取父类的。或者使用getDeclaredXxx(name, args),获取当前类的指定名称指定参数的方法。一般使用后者
- 调用时,注意使用getDeclaredXxx()获取到后,setAccessible(true),保证权限可以访问
- 调用静态方法,参数随便传无所谓,因为是通过Class可以直接调用
- 设置属性值使用set,获取使用get。调用方法使用invoke(),返回值就是调用对象的方法的返回值
应用:动态代理
被代理类实现接口,创建被代理类对象,动态代理通过反射获取同样实现接口的匿名实现类对象,也就是代理类对象(将对象强转为接口),当这个代理类对象调用接口中的方法a时,在handler中都调用invoke()方法,会传入方法a的方法实例、调用参数、代理类对象本身,所以在handler中要聚合进被代理类对象,并在invoke()方法通过反射调用被代理类对象的a方法(通过方法实例Method)才能实现代理类对象调用方法,并调用被代理对象的对应方法(自己反射调用)。
Java8新特性
java8最重要的的改变,一个是lambda表达式,一个是Stream api
- 接口中可以有静态方法,默认方法(越来越像了)
- 新的时间日期api(LocalDateTime,Instant,DateTimeFormatter...)
- 可重复注解(@Repeatable),类型注解(ElementType支持泛型)
- 集合底层变化(一开始不创建,用的时候再创建;红黑树)
lambda表达式、函数式接口、方法引用
lambda表达式:
- lambda是一个匿名函数,可以把lambda理解为一段可以传递的代码。函数式接口都可以使用lambda表达式进行改写(只有一个抽象方法的接口叫函数式接口,因为只有一个方法,所以不用显式声明了,省略为lambda)
- 本质是作为函数式接口匿名实现类的对象(实例),借助接口才存在的 没有接口就没有意义了,接口必须是函数式接口(只有一个抽象方法)
- 使用总结
- 参数 有一个参数就写参数本身 其他都写括号 可以不写参数类型
- 方法体 只有一条语句可以省略大括号 有很多条则不能省略大括号
函数式接口:
- 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。(Comparator接口中有个equals()方法,这个方法不是抽象的,是有默认实现的,接口也是继承自Object,接口也是一个特殊的类,在Object类中有equals()方法的具体实现,因此在Comparator接口中,只有一个抽象方法compare())
- 可以使用注解
@FunctionalInterface
在编译器检查当前接口是否符合要求 - java内置四大函数式接口
- 消费型、供给型、函数型、断定型
- 其他函数式接口参考java.util.function包下
方法引用:
- 当要传递给lambda体的操作已经有实现的方法了,可以使用方法引用(
对象::非静态方法
,类::静态方法
) - 当方法体中第一个参数作为调用者出现,其他参数作为调用方法的参数出现,而这种调用已经有代码实现,可以使用方法引用(
类::静态方法
) - 本质:方法引用本质上就是lambda表达式的简写形式,用现有代码替换要实现的功能的lambda方法体,lambda表达式、方法引用本质都是函数式接口的实例,作为实例提供给方法参数或被调用进行使用
构造器引用、数组引用
- 当方法引用中,是使用new创建对象时,可以使用构造器引用或数组引用(创建数组)
总结:
- lambda表达式是函数式接口的一个实例,在一些特殊情况下可以使用方法引用、构造器引用、数组引用代替lambda表达式,所以都是函数式接口的一个实例
Stream api
Stream api(java.util.stream)将真正的函数式编程风格引入到java中。
- 自己不存储元素,也不会改变源对象,会返回一个持有结果的新Stream,Stream操作是延迟执行的,等到需要结果的时候才执行。
- 使用
- 创建Stream:四种方式,从集合、数组创建,Stream.of(),创建无限流
- Stream的中间操作:筛选、切片、排序
- Stream的终止操作:匹配与查找,规约reduce,收集collect(),在并行流中使用Collectors.xxx不存在线程安全的问题
Optional类
为了在程序中避免出现空指针异常而创建的,对对象进行了封装,也是一个容器,如果为空,内部存储一个EMPTY,如果不为空,存储对象本身
- Optional.ofNullable(T t) 传入有可能为空的对象,如果为空不会报异常
- orElse(T t),如果不为空,返回自己,如果为空,返回参数里面提供的对象
- or(),参数为供给者,如果不为空,返回自己的Optional,如果为空,返回参数提供的Optional
- ifPresent(),如果不为空 执行提供者的代码
- ifPresentOrElse(),如果不为空 执行提供者的代码 否则执行Runnable的代码
- isPresent(),不为空返回true
- isEmpty(),为空返回true
- get(),不为空返回对象,为空抛异常
其他版本新特性
jdk9
- 模块化系统:在java模块的顶层目录声明一个module.info.java文件,声明哪些包可以被其他模块访问,需要依赖哪些别的模块的包。本质上是在package上又包了一层,指定哪些部分可以暴露哪些部分隐藏
- 交互式编程环境REPL,jShell.exe
- 语法改进:
- 允许接口定义私有方法
- 钻石操作符可以跟匿名内部类,以前必须指明类型,现在可以推断
- jdk8中资源的自动关闭要去在try后面的小括号里声明及初始化,jdk9中可以在try后的小括号里声明已经初始化过的资源,此时的资源是final的。多个资源用分号隔开
- String底层存储使用
byte[]
加编码标记 - 快速创建只读集合:jdk8创建只读集合可以使用
Collections.unmodifiableXxx()
,但是当原集合修改时,这个不可变集合的内容也会修改,jdk9中提供了of()方法创建不可变集合,如List.of(),不允许添加null元素,如果是Map.of(),k和v都不能为null,Arrays.asList()不能只传递一个null元素,但是可以传一个只有一个null元素的数组。 - InputStream:有了一个非常有用的方法transformTo(),可以直接将输入流写出到输出流,不用再创建数组读取再写出了,这个方法内部封装了这些操作
- Stream api:新方法,takeWhile、dropWhile、ofNullable、iterator()
- Optional:可以使用stream()也获取为一个流
jdk10
- 局部变量类型推断,使用var表示局部变量。注意var不是一个关键字,是一个类型名,在编译后会替换为实际的类型,只发生在编译阶段
- 快速创建只读集合:增加了List.copyOf()从现有集合重新拷贝一份创建为不可变集合。如果本身就是只读的,直接返回,不复制
jdk11
- 新增一系列字符串处理方法
- Optional新增了一些方法,整理在了前面
- 局部变量类型推断,可以使用注解修饰
- 新增HttpClient api,支持同步和异步
- 更简化的编译运行程序,直接java xxx.java 就可以编译运行,但是第一个类必须有main方法,不能识别别的文件里面的类
- ZGC
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15841404.html