Java基础
一、变量
1.Java变量
1.内存中的一个存储区域;
2.该区域的数据可在同一类型范围内不断变化;
3.变量是数据中最基本的存储单位,变量格式包含变量类型、变量名称、存储的值。
基本数据类型:
整型:byte、shor、int、long
浮点型:float、double
字符型:char - 存储单个字符,例如 ‘a’ 或 ‘B’。Char 值用单引号括起来
布尔型:boolean - 存储具有两种状态的值:真或假
2.声明(创建)变量
//基本数据类型的定义演示代码
class VariableTest1 {
public static void main(String[] args) {
byte b1=12;
//①byte范围:-128~127
byte b2=-128;
//byte b3=128;编译不通过,超过范围了。
short s1=128;
int i1=1234;
long l1=99999999L;
//声明long型变量,必须以“l”或“L”结尾。
double d1=123.4;
float f1=12.3F;
//定义float类型变量时,变量要以“f”或“F”结尾。
//如果不加"f",则系统会默认该变量为double类型。
System.out.println(f1);
//通常,定义浮点型变量时,使用double型。
char c1='a';
//c1='ab';(编译不通过)
//①定义char型变量,通常使用一对' ',内部只能一个字符。
char c2='1';
char c3='中';
boolean bb1 = true;
boolean isMarried = true;
}
}
3.自动类型提升
结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
byte、char、short --> int --> long --> float --> double
特别地:当byte、char、short三种类型的变量做运算时,结果为int型。
说明:此时的容量大小指的是,表示数的范围的大和小。比如float容量要大于long的容量。
4.强制类型转换
自动类型提升运算的逆运算。
(1)需要使用强转符:()
(2)注意点:强制类型转换,可能导致精度损失。
二、控制结构
JAVA的基本控制结构
在Java语言中有三种基本控制结构:
- 顺序结构
- 选择结构
- 循环结构
1.顺序结构
顺序结构是JAVA语言的基本结构,也是最简单的算法结构。程序按照从上到下的顺序一行一行的执行,中间没有任何判断和跳转。
例如:
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要输出的值");
String str=scanner.nextLine();
System.out.println("输出:"+str);
scanner.close();
2.选择结构
选择结构根据条件不同执行不同的语句,主要有if……else和switch—case两种语句。
2.1if语句
在实际使用时,常常将if语句划为单选择结构、双选择结构、多选择结构以及嵌套使用四种情况。
2.2switch—case多选择语句
多选择结构另一个实现方式就是switch-case语句,switch-case语句判断一个变量与一系列常量中某个值是否相等,每一个常量都是一个分支。
3.循环结构
在Java语言中,有三种循环结构:while循环,do-while循环和for循环。
3.1while循环
语法结构:
while(i<=100){
sum+=i;
i++;
}
先对布尔表达式的值进行判断,在决定是否执行循环语句。
3.2do-while循环
语法结构:
do {
System.out.println(i);
i++;
}while(i<0);
相对于while语句,do-while语句无论布尔表达式的值是什么,都会执行一次循环语句并且循环至少执行一次。
3.3for循环
语法结构:
for(int i = 0;i < 100;i++){
System.out.println(i);
}
for循环扩展
语法结构:
int[] number={10,20,30,40};
for(int x:number){ //x==声明类型 number==表达式
System.out.println(x);//10,20,30,40
}
该用法主要用于数组和集合。
声明类型:声明新的局部变量,该变量类型必须是数组元素的类型匹配,其作用域限定在循环体内。
表达式:表达式的值是要访问的数组名也可以是返回值为数组的方法。
三、数组
数组是一个容器对象,保存一个固定数量的单一类型的值。当数组创建时,数组的长度就确定了。创建后,其长度是固定的。数据在里面的每个项称为元素,每个元素都用一个数组下标关联。下标从零开始,第9个元素的下标就是8。
1.数组的创建和初始化
格式: 类型名 [ ] 数组名=new 类型名[数组长度];
动态初始化: 直接指定数组中的元素个数。
public class Array{
pubic void static main(String[] args){
byte[] anArrayOfBytes = new byte[10];
shrot[] anArrayOfShorts = new short[10];
int[] anArrayOfInts = new int[10];
long[] anArrayOfLongs = new long[10];
float[] anArrayOfFloats = new float[10];
double[] anArrayOfDoubles = new double[10];
char[] anArrayOfChars = new char[10];
boolean[] anArrayOfBooleans = new boolean[10];
}
}
静态初始化: 不指定具体的元素个数,直接将具体的内容进行指定。
public class Array{
pubic void static main(String[] args){
byte[] anArrayOfBytes = new byte[]{1,2,3};
short[] anArrayOfShorts = new short{1,2,3,4};
int[] anArrayOfInts = new int[10]{1,2,3,4};
long[] anArrayOfLongs = new long{1111111,222222,333333};
float[] anArrayOfFloats = new float{1.1,2.2,3.3};
double[] anArrayOfDoubles = new double{1.1,2.2,3.3};
char[] anArrayOfChars = new char{'a','b','c'};
boolean[] anArrayOfBooleans = new boolean{false,true};
}
}
注意: 静态初始化虽然没有设置具体的数组大小,但是会按照{}中的元素个数设置数组长度。
如果没有对数组中的元素初始化则有默认值:
类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
String | null |
2.数组的使用
数组元素的访问: 直接数组名[元素下标]可以直接访问或者修改元素内容。
遍历数组:
public class Array{
public static void main(String[] args){
int[] arr = new int[5]{1,2,3,4,5};
for(int i=0;i<arr.length;i++){
System.out.println(i);
}
}
}
四、Java常用API
1.Scanner的使用
Scanner类的功能: 可以实现键盘输入数据,到程序当中。
/*
获取键盘输入的一个int数字:int num = sc.nextInt();
获取键盘输入的一个字符串:String str = sc.next();
*/
import java.util.Scanner;
public class DemoScanner {
public static void main(String[] args) {
// 2. 创建
// 备注:System.in代表从键盘进行输入
Scanner sc = new Scanner(System.in);
// 3. 获取键盘输入的int数字
int num = sc.nextInt();
System.out.println("输入的int数字是:" + num);
// 4. 获取键盘输入的字符串
String str = sc.next();
System.out.println("输入的字符串是:" + str);
}
}
2.Random的使用
Random类的功能: Random类用来生成随机数字。
/*
获取一个随机的int数字(范围是int所有范围,有正负两种):int num = r.nextInt()
获取一个随机的int数字(参数代表了范围,左闭右开区间):int num = r.nextInt(3)
实际上代表的含义是:[0,3),也就是0~2
*/
//生成70-90的随机数代码
public class RandomDemo {
public static void main(String[] args) {
Random r1 = new Random();
int a = 70;
for(int i = 0;i < 100;i++){
int num = r1.nextInt(21);
int sum = num + a;
System.out.println("生成的随机数是" + sum);
}
}
}
3.String的使用
1.String的构造方法
/*
创建字符串的常见3+1种方式。
三种构造方法:
public String():创建一个空白字符串,不含有任何内容。
public String(char[] array):根据字符数组的内容,来创建对应的字符串。
public String(byte[] array):根据字节数组的内容,来创建对应的字符串。
一种直接创建:
String str = "Hello"; // 右边直接用双引号
注意:直接写上双引号,就是字符串对象。
*/
public class DemoString {
public static void main(String[] args) {
// 使用空参构造
String str1 = new String(); // 小括号留空,说明字符串什么内容都没有。
System.out.println(str1);
// 根据字符数组创建字符串
char[] charArray = { 'a', 'b', 'c' };
String str2 = new String(charArray);
System.out.println(str2);
// 根据字节数组创建字符串
byte[] byteArray = { 1,2,3};
String str3 = new String(byteArray);
System.out.println("第3个字符串:" + str3);
// 直接创建
String str4 = "HelloWorld";
System.out.println(str4);
}
}
字符串的特点:
- 字符串的内容永不可变。【重点】
- 正是因为字符串不可改变,所以字符串是可以共享使用的。
- 字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组。
五、异常和处理
1.异常概述与异常体系结构
异常: Java语言当中,将程序执行过程中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
Error: Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽特重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
√空指针访问
√试图读取不存在的文件
√网络连接中断
√数组角标越界
分类: 编译时异常和运行时异常。
1.1运行时异常
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
1.2编译时异常
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。对于这类异常,如果程序不处理,可能会带来意想不到的结果。
2.常见异常
java.lang.RuntimeException:
ClassCastException
ArrayIndexOutOfBoundsException
NullPointerException
ArithmeticException
NumberFormatException
InputMismatchException
java.io.IOExeption:
FileNotFoundException
EOFException
java.lang.ClassNotFoundException
java.lang.InterruptedException
java.io.FileNotFoundException
java.sql.SQLException
3.异常处理
异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
过程二:“抓”:可以理解为异常的处理方式:① try-catch-finally ② throws
关于异常对象的产生:
① 系统自动生成的异常对象
② 手动的生成一个异常对象,并抛出(throw)
3.1try-catch-fainally
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
finally{
//一定会执行的代码
}=
try-catch-fainally说明:
- finally是可选的。
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码
- catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错 - 常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()
- 在try结构中声明的变量,再出了try结构以后,就不能再被调用
- try-catch-finally结构可以嵌套
体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
体会2: 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
针对于编译时异常,我们说一定要考虑异常的处理。
3.2throws
声明抛出异常是Java中处理异常的第二种方式。
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
声明抛出异常举例:
public void readFile(String file) throws FileNotFoundException {
// 读文件的操作可能产生FileNotFoundException类型的异常
FileInputStream fis = new FileInputStream(file);
//关闭fis时可能产生NullPointerException类型的异常
fis.close();
}
throw说明
-
"throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
类型时,就会被抛出。异常代码后续的代码,就不再执行! -
体会:try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。 -
开发中如何选择使用try-catch-finally 还是使用throws?
3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws
的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
4.重写方法声明抛出异常的原则
重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对methodA()方法的调用-异常的捕获按父类声明的异常处理。
如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
5.手动抛出异常
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
IOException e = new IOException();
throw e;
可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String(“want to throw”);
6.用户自定义异常类
//1.自定义异常类需要继承Exception或者RuntimeException
//2.提供序列版本号
//3.提供重载的构造器
public class EcDef extends Exception {
static final long serialVersionUID = -3387516163124229948L;
public EcDef(){
}
public EcDef(String msg){
super(msg);
}
}
7.总结
异常处理5个关键字
异常处理机制一:try-catch-finally
异常处理机制二:throws
手动抛出异常:throw
关键字 | 作用 |
---|---|
try | 执行可能产生异常的代码 |
catch | 捕获异常 |
finally | 无论是否发生异常,代码总是被执行 |
throws | 异常的处理方式:声明方法可能抛出的异常类 |
throw | 异常的生成阶段:手动抛出异常对象 |
一首小悟结束异常处理(转自尚硅谷)
世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;
世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;
世界上最真情的相依,是你在try我在catch.无论你发神马脾气,我都默
默承受,静静处理。到那时,再来期待我们的finally。
六、集合
1.集合框架的概述
1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储。
2.1数组在存储多个数据方面的特点:
—旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;0bject[] arr2;
2.2数组在存储多个数据方面的缺点:
一旦初始化以后,其长度就不可修改。
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。>获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
Java集合可分为Collection 和 Map两种体系
集合框架
Collection接口:单列数据,定义了存取一组对象的方法的集合
|—List:元素有序,可重复的 -->又称之为“动态数组”
|—ArrayList、LinkedList、Vector
|—Set:元素无序,不可重复的集合 -->“高中讲的"集合”
|—HashSet、LinkedHashSet、TreeSet
Map接口:双列数据,保存具有映射关系"key-valued对"的集合。–>“函数”:y = f(x)
|—HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
2.Collection接口中的方法的使用
/**
* 1、添加
* add(Object obj)
* addAll(Collection coll)
* 2、获取有效元素的个数
* int size()
* 3、清空集合
* void clear()
* 4、是否是空集合
* boolean isEmpty()
* 5、是否包含某个元素
* boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
* boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
* 6、删除
* boolean remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
* boolean removeAll(Collection coll):取当前集合的差集
* 7、取两个集合的交集
* boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
* 8、集合是否相等
* boolean equals(Object obj)
* 9、转成对象数组
* Object[] toArray()
* 10、获取集合对象的哈希值
* hashCode()
* 11、遍历
* iterator():返回迭代器对象,用于集合遍历
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
//1.1add(Object e):将元素添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Ton"));
//2.size():获取添加的元素的个数
System.out.println(coll.size());
//1.2addAll(Collection coll2):将coll2中的元素添加到当前的集合
Collection coll2 = new ArrayList();
coll2.add("CC");
coll2.add(456);
coll.addAll(coll2);
System.out.println(coll.size());
System.out.println(coll);
//3clear():清空集合元素
//coll.clear();
//4.isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());
//5.1contains(Object obj):判断当前集合中是否包含obj
//我们在判断时会调用obj对象所在类的equals()。
//通常自定义类需要重写equals()方法
boolean contains = coll.contains(456);
System.out.println(contains);//true
System.out.println(coll.contains(new String("Ton")));//true
System.out.println("*****************");
//5.2containsAll(Collection coll3):判断形参coll3中的所有元素是否都存在于当前集合中。
Collection coll3 = new ArrayList();
coll3.add("AA");
coll3.add("BB");
coll3.add(123);//自动装箱
coll3.add(new Date());
coll3.add(new String("Ton"));
coll3.add("CC");
System.out.println(coll);
System.out.println(coll3);
System.out.println(coll.containsAll(coll2));
}
@Test
public void test2(){
//6.1remove(Object obj)从当前集合中删除obj元素
Collection coll = new ArrayList();
coll.add("AA");
coll.add(false);
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Ton"));
//
// boolean remove = coll.remove(123);
// System.out.println(remove);
// System.out.println(coll);
//6.2removeAll(Collection coll1):从当前集合中移除coll1中所有的元素
// Collection coll1 = Arrays.asList(123,456);
// System.out.println(coll.removeAll(coll1));
// System.out.println(coll);
//7.retainAll()交集:获取当前集合和coll2集合的交集,并返还给当前集合
// Collection coll2 = Arrays.asList(123,"AA",234,345);
// coll.retainAll(coll2);
// System.out.println(coll);
//8.equals():要想返回true,需要当前集合和形参集合的元素都相同。
Collection coll3 = new ArrayList();
coll3.add("AA");
coll3.add(false);
coll3.add(123);//自动装箱
coll3.add(new Date());
coll3.add(new String("Ton"));
System.out.println(coll.equals(coll3));
//9.Object[] toArray()集合转化为数组
Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
//拓展:数组转换为集合 调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"aa", "bb", "cc"});
System.out.println(list);
//10.hashCode()返回当前对象的hash值
System.out.println(coll.hashCode());
}
}
3.迭代器Iterator接口
使用 Iterator 接口遍历集合元素
1)Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
2)GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
3)Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
4)Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力.如果需要创建Iterator 对象,则必须有一个被迭代的集合。
5)集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认初始游标都在集合的第一个元素之前。
/**
* 集合元素的遍历操作,使用迭代器Iterator接口
*/
public class IteratorTest {
@Test
public void test1(){
//11.iterator():返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中测试。
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Ton"));
coll.add(false);
Iterator iterator = coll.iterator();
//hasNext()判断是否还有下一个元素
while (iterator.hasNext()){
//next():1.指针下移 2.将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
}
}
Iterator 中 remove() 的用法
内部定义了remove(),可以在遍历的时候删除集合中元素。此方法不同于集合直接调用remove()
注意:
lterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报llegalStateException。| l
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Ton"));
coll.add(false);
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
if("Ton".equals(obj)){
iterator.remove();
}
}
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
foreach循环的使用方法
/**
* jdk5.0新增加了foreach循环,用于遍历集合、数组
*/
public class foreachTest {
//foreach 遍历集合
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Ton"));
coll.add(false);
//for( 集合元素的类型 局部变量 : 集合对象 )
//内部仍然调用了迭代器
for(Object obj: coll){
System.out.println(obj);
}
}
//foreach 遍历数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
System.out.println(i);
}
}
}
4.List接口
List接口概述:
鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。
4.1ArrayList
作为List接口的主要实现类:线程不安全的,效率高;底层使用Object[]存储
4.2LinkedList
对于频繁的插入和删除操作,使用此类比ArrayList效率高:底层使用双向链表存储
4.3Vectory
作为List接口的古老实现类:线程安全,效率低;底层使用Object[]存储
/**1.List的框架结构
* Collection接口:单列数据,定义了存取一组对象的方法的集合
* |---List:元素有序,可重复的  -->又称之为“动态数组”
* |---ArrayList:作为List接口的主要实现类:线程不安全的,效率高;底层使用Object[]存储
* |---LinkedList:对于频繁的插入和删除操作,使用此类比ArrayList效率高:底层使用双向链表存储
* |---Vector:作为List接口的古老实现类:线程安全,效率低;底层使用Object[]存储
*
* 2.ArrayList的源码分析:
* 2.1 jdk 7情况下
* ArrayList list = new Arraylist();//底层创建了长度是10的object[]数组elementData
* list.add(123); l /eLementData[e] = new Integer(123);
* ...
* list.add(11);//如果此次的添加导致底层eLementData数组容量不够,则扩容。
* 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
*
* 结论:建议开发中使用带参的构造器: ArrayList list = new ArrayList(int capacity)
* 2.2 jdk 8中Arraylist的变化:
* ArrayList list = new ArrayList();//底层object[] elementData初始化为{}.并没有传概念长度是10的数组。
*
* list.add(123); //第一次调用了add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
* ...
* 后续的添加与扩容操作与jdk7无异。
* 2.3小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,
* jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
* 3.LinkedList的源码分析:
* LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为NULL
* list.add(123);//将123封装到Node当中,创建了Node对象。
*
* 其中,Node定义为:体现了LinkedList的双向链表
* private static class Node<E> {
* item;
* Node<E> next;Node<E> prev;
* Node (Node<E> prev, E eLement, Node<E> next){
* this.item = element;
* this.next = next;this.prev = prev;
* }
* }
*
* 总结:常用方法
* 增: add(object obj)
* 删: remove(int index) / remove(object obj)改: set(int index, object ele)
* 查: get(int index)
* 插: add(int index,0bject eLe)长度: size()
* 遍历;Iterator迭代器方式
* 增强for循环
* 普通的循环
*/
public class ListTest {
/*List中的常用方法
void add(int index, object ele ):在index位置插入eLe元素
boolean addALL(int index, Collection ele): 从index位置开始将eLes中的所有元素添加进来
object get(int index):获取指定index位置的元素
int index0f(0bject obj):返回obj在集合中首次出现的位置
int lastIndexof(object obj):返回obj在当前集合中末次出现的位置
object remove(int index):移除指定index位置的元素,并返回此元素
object set(int index,object ele):设置指定index位置的元素为eLe
List sublist(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
*/
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",12));
list.add(456);
System.out.println(list);//[123, 456, AA, Person{name='Tom', age=12}, 456]
//void add(int index, object ele ):在index位置插入eLe元素
list.add(1,"BB");
System.out.println(list);//[123, BB, 456, AA, Person{name='Tom', age=12}, 456]
//boolean addALL(int index, Collection eles): 从index位置开始将eLes中的所有
//元素添加进来object get(int index):获取指定index位置的元素
List list1 = Arrays.asList(1,2,3);
list.addAll(list1);
System.out.println(list.size());
//object get(int index):获取指定index位置的元素
System.out.println(list.get(0));//123
}
@Test
public void test2() {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom", 12));
list.add(456);
//int index0f(0bject obj):返回obj在集合中首次出现的位置,如果不存在返回-1
int index = list.indexOf(456);
System.out.println(index);// 1
//int lastIndexof(object obj):返回obj在当前集合中末次出现的位置,如果不存在返回-1
int index1 = list.lastIndexOf(456);
System.out.println(index1);// 4
//object remove(int index):移除指定index位置的元素,并返回此元素
Object remove = list.remove(0);
System.out.println(remove);//123
//object set(int index,object ele):设置指定index位置的元素为eLe
System.out.println(list);//[456, AA, Person{name='Tom', age=12}, 456]
list.set(1, "CC");
System.out.println(list);//[456, CC, Person{name='Tom', age=12}, 456]
//List sublist(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开子集合
List list1 = list.subList(2, 4);//list本身无变换 , 区间为[2,4)
System.out.println(list1);//[Person{name='Tom', age=12}, 456]
}
}
5.Set接口
Set接口概述:
Set接口是Collection的子接口,set接口没有提供额外的方法。
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法。
5.1HashSet
HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。HashSet按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet具有以下特点:
不能保证元素的排列顺序>HashSet不是线程安全的>集合元素可以是null
HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Objectobj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
5.2LinkedHashSet
LinkedHashSet是 HashSet的子类 LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet插入性能略低于 HashSet,但在迭代访问Set里的全部元素时有很好的性能。
LinkedHashSet不允许集合元素重复。
5.3TreeSet
1.向Treeset中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序和定制排序
3.自然排序中,比较两个对象是否相同的标准为: compareTo()返回e.不再是equals().
/**
* 1. Set接口的框架:
* |----collection接口:单列集合,用来存储一个一个的对象
* |----Set接口:存储无序的、不可重复的数据-->高中讲的"集合”
* |----HashSet:作为set接口的主要实现类;线程不安全的;可以存储null值
* |----LinkedHashSet:作为HashSet的子类,遍历其内部数据时可以按照添加的顺序去遍历
* 对于比较频繁的遍历,LinkedHashSet效率高于HashSet.
* |----TreeSet:可以按照添加对象的指定属性,进行排序。
*
* 1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
* 2.要求:向Set中添加的数据,其所在的类一定要重写hashcode()和equals()
* 要求:重写的hashCode()和equals()尽可能保持一致性。l
*
*
*/
public class SetTest {
/*
set:存储无序的、不可重复的数据以HashSet为例说明:
1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加。
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
二、添加元素的过程:Hashset为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素α添加成功。--->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素α添加成功。--->情况2
如果hash值相同,进而需要调用元素α所在类的equals()方法:
equals()返回true,元素α添加失败
equals()返回false,则元素α添加成功。--->情况2
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 ∶元素a放到数组中,指向原来的元素。
jdk 8∶原来的元素在数组中,指向元素a
HashSet底层:数组+链表的结构
*/
@Test
public void test1() {
Set set = new HashSet();
set.add(123);
set.add("CC");
set.add("AA");
set.add(new User("Tom", 18));
set.add(new User("Tom", 20));
set.add(456);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
//CC AA User{name='Tom', age=20} User{name='Tom', age=18} 456 123
}
}
//LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录了此数据的前一个
//数据和后一个数的数据
//优点:对于比较频繁的遍历,LinkedHashSet效率高于HashSet.
@Test
public void test2() {
Set set = new LinkedHashSet();
set.add(123);
set.add("CC");
set.add("AA");
set.add(new User("Tom", 18));
set.add(new User("Tom", 20));
set.add(456);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
//123 CC AA User{name='Tom', age=18} User{name='Tom', age=20} 456
}
}
}
5.Map接口
Map接口概述
双列数据,存储key-value对的数据 —类似于高中的函数
5.1HashMap
作为Map的主要实现类: 线程不安全的,效率高;存储null的key和value
5.2LinkedHashMap
保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构的基础上添加的一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
5.3TreeMap
保证按照添加的key-value对进行排序,实现排序遍历。按照key进行自然排序和定制排序。底层使用红黑树。
/** 1.Map实现类的结构:
* |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数
* |----HashMap:作为Map的主要实现类: 线程不安全的,效率高;存储null的key和value
* |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
* 原因:在原有的HashMap底层结构的基础上添加的一对指针,指向前一个和后一个元素。
* 对于频繁的遍历操作,此类执行效率高于HashMap。
* |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。//按照key进行自然排序和定制排序。
* 底层使用红黑树。
* |----Hashtable:作为古老的实现类:线程安全的,效率低;不能存储null的key和value
* |----Properties:常用来处理配置文件。key和value都是String类型。
*
* HashMap的底层:数组+链表(jdk7及以前)
* 数组+链表+红黑树(jdk 8)
*
* 2.Map结构的理解:
* Map中的key:无序的、不可重复的,使用Set存储所有的key ---->key所在的类要重写equals()和hashCode()
* Map中的value:无序的、可重复的,使用Collection存储所有的value ---->value所在的类要从写equals()
* 一个键值对:key-value构成了一个Entry对象。
* Map中的entry:无序的、不可重复的,使用Set存储所有的entry
*
* 3.HashMap的底层实现原理
* 以jdk7为例说明:
* HashMap map = new HashMap():
* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1);
* 首先,调用key1所在类的hashCode(),计算key1的hash值,此哈希值经过某种算法计算以后,得到在Entry数组的存放位置。
* 如果此位置上的数据为空,此时的key1-value1添加成功
* 如果此位置上的数据不为空,(意味着此位置上存在一个或者多个数据(以链表的形式存在)),比较key1和已经存在的一个或
* 着多个数据的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
* 如果key1的哈希值与已经存在的数据的哈希值
*
* 4、Map中定义的方法:
* 添加、删除、修改操作:
* Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中void putAll(Map m):将m中的所有key-value对存放到当前map中
* Object remove(Object key):移除指定key的key-value对,并返回value void clear():清空当前map中的所有数据
* 元素查询的操作:
* Object get(Object key):获取指定key对应的value boolean containsKey(Object key):是否包含指定的key
* boolean contains value(Object vaLue):是否包含指定的value int size():返回map 中key-vaLue对的个数
* booLean isEmpty():判断当前map是否为空
* boolean equals(object obj):判断当前map和参数对象obj是否相等
* 元视图操作的方法:
* set key set():返回所有key构成的Set集合
* Collection values():返回所有value构成的Collection集合set
* entrySet():返回所有key-value对构成的Set集合
*
*/
public class MapTest {
@Test
public void test1(){
Map map = new HashMap();
map.put(null,null);
}
/*
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回
value void clear():清空当前map中的所有数据
*/
@Test
public void test2(){
Map map = new HashMap();
//添加操作
map.put("AA",123);
map.put(45,123);
map.put("BB",123);
//体现的是修改,将原本key为"AA"的value值改为了56
map.put("AA",56);
System.out.println(map);
//{AA=56, BB=123, 45=123}
Map map1 = new HashMap();
map1.put("CC",123);
map1.put("DD",123);
map.putAll(map1);
System.out.println(map);
//{AA=56, BB=123, CC=123, DD=123, 45=123}
//remove(Object key)
Object value = map1.remove("DD");
System.out.println(value);//123
System.out.println(map1);//{CC=123}
//clear()
map.clear();
System.out.println(map);//{}
}
/*
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean contains value(Object vaLue):是否包含指定的
value int size():返回map 中key-vaLue对的个数
booLean isEmpty():判断当前map是否为空
boolean equals(object obj):判断当前map和参数对象obj是否相等\
*/
@Test
public void test3(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
//Object get(Object key):
System.out.println(map.get(45));//123
//containsKey(Object key)
System.out.println(map.containsKey("BB"));//true
//containsValue(Object vaLue)
System.out.println(map.containsValue(123));
map.clear();
System.out.println(map.isEmpty());//true
}
/*
Map的遍历操作:
set key set():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合set
entrySet():返回所有key-value对构成的Set集合
*/
@Test
public void test4(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",123);
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//遍历所有的value集:values
Collection values = map.values();
for (Object o:values){
System.out.println(o);
}
System.out.println();
//遍历所有的key-value
//方式一:entrySet();
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
}
}
5.4Properties
/**
* |----Properties:常用来处理配置文件。key和value都是String类型。
*/
public class PropertiesTest {
public static void main(String[] args) throws IOException {
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文佳
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println("name = "+ name+",password = "+password);
fis.close();
}
}
6.Collections工具类
colleonons是一个操作Set、List 利l Map等集合的工具类
collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等探作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作:(均为static方法)
reverse(List):反转List中元素的顺序
shuffle(List):对List集合元素进行随机排序
sort(List):根据元素的自然顺序对指定List集合元素按升序排序
sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
查找、替换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
Collections中常用方法的测试
/**
* Collections:操作Collection、Map的工具类
*
* 常用方法:
* 排序操作:(均为static方法)
* reverse(List):反转List中元素的顺序
* shuffle(List):对List集合元素进行随机排序
* sort(List):根据元素的自然顺序对指定List集合元素按升序排序
* sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
* swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
*
* 查找、替换:
* Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
* Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
* Object min(Collection)
* Object min(Collection,Comparator)
* int frequency(Collection,Object):返回指定集合中指定元素的出现次数
* void copy(List dest,List src):将src中的内容复制到dest中
* boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
*/
public class CollectionsTest {
@Test
public void test1(){
List list = new ArrayList();
list.add(123);
list.add(789);
list.add(-67);
list.add(951);
list.add(0);
System.out.println(list);//[123, 789, -67, 951, 0]
//reverse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println(list);//[0, 951, -67, 789, 123]
//shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);//[951, 789, 123, -67, 0]
//sort(List):根据元素的自然顺序对指定List集合元素按升序排序
Collections.sort(list);
System.out.println(list);//[-67, 0, 123, 789, 951]
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
Collections.swap(list,1,3);
System.out.println(list);//[-67, 789, 123, 0, 951]
}
}
七、泛型
1.泛型的概念:
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
从JDK1.5以后,Java引入了“参数化类型( Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如: List,这表明该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
2.泛型的使用
在集合中使用泛型:
总结:
1)集合接口和集合类在jdk5.0时都修改为带泛型的结构。
2)在实例化集合类时,可以指明具体的泛型类型
3)指明完以后,在集合或接口中凡是定义类或者接口时
内部结构使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) —>实例化以后:add(Integer e)
4)泛型的类型必须是一个类,不能时基本数据类型,基本数据类型需要用包装类替换
5)如果实例化时没有指明泛型。默认类型为java.lang.Object 类型
/**
* 泛型的使用
* 1.jdk5.0新增的特性
*
* 2.在集合中使用泛型:
* 总结:
* 1)集合接口和集合类在jdk5.0时都修改为带泛型的结构。
* 2)在实例化集合类时,可以指明具体的泛型类型
* 3)指明完以后,在集合或接口中凡是定义类或者接口时
* 内部结构使用到类的泛型的位置,都指定为实例化的泛型类型。
* 比如:add(E e) --->实例化以后:add(Integer e)
* 4)泛型的类型必须是一个类,不能时基本数据类型,基本数据类型需要用包装类替换
* 5)如果实例化时没有指明泛型。默认类型为java.lang.Object 类型
*
* 3.自定义泛型结构:泛型类、泛型接口、泛型方法。
*/
public class GenericTest {
//在集合中未使用泛型之前的情况
@Test
public void test1(){
List list = new ArrayList();
list.add(78);
list.add(35);
list.add(94);
list.add(19);
list.add(56);
list.add(65);
list.add(87);
//问题一:类型不安全
list.add("Tom");
for(Object score : list){
//问题二: 强转时,可能出现ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
//在集合中使用泛型之前的情况
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(35);
list.add(94);
list.add(19);
list.add(56);
list.add(65);
list.add(87);
//编译时,就会进行数据检查,保证数据的安全
//list.add("Tom")
//方式一:
for (Integer score:list){
//避免的强转操作
int stuScore = score;
System.out.println(stuScore);
}
//方式二:
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
//在集合中使用泛型:一HashMap为例
@Test
public void test3(){
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("CC",123);
map.put("Jerry",87);
map.put("rose",123);
map.put("DD",123);
//map.put(123,"dd"); 编译不通过
//泛型的嵌套
Set<Map.Entry<String,Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
String key = next.getKey();
Integer value = next.getValue();
System.out.println(key+"----->"+value);
}
}
}
3.自定义泛型
自定义泛型结构:泛型类、泛型接口、泛型方法。
1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:《E1,E2,E3》
2.泛型类的构造器如下: public GenericClass()。
而下面是错误的: public GenericClass《E》()
3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4.泛型不同的引用不能相互赋值。尽管在编译时ArrayList《String》和ArrayList《Integer》是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7.jdk1.7,泛型的简化操作:ArrayList《Fruit》 flist = new ArrayList<>();
8.泛型的指定中不能使用基本数据类型,可以使用包装类替换。
9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
10.异常类不能是泛型的
11.不能使用new E[]。但是可以:E[]elements =(E[])new Object[capacity]
参考:ArrayList源码中声明: Object[elementData,而非泛型参数类型数组。
12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
public class Order<T> {
String orderName;
int orderID;
T orderT;
public Order(){
}
public Order(String orderName,int orderID,T orderT){
this.orderName = orderName;
this.orderID = orderID;
this.orderT = orderT;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
}
public class GenericTest1 {
@Test
public void test1(){
//如果自定义了泛型类,实例化没有指明类的类型,则认为此泛型类型为Object类型
//要求;如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型
Order ord = new Order();
ord.setOrderT(123);
ord.setOrderT("abc");
//建议:实例化时指明类的泛型
Order<String> order1 = new Order<>("orderAA",1001,"今天很开心");
order1.setOrderT("今天非常的开心");
}
@Test
public void test2(){
SubOrder subOrder = new SubOrder();
//由于子类在继承带泛型的父类时,指明了泛型的类型。则实例化子类对象时,不在需要指明泛型。
subOrder.setOrderT(123);
}
@Test
public void test3(){
ArrayList<String> arrayList1 = null;
ArrayList<Integer> arrayList2 = null;
//泛型不同的引用不能相互赋值。
//arrayList1 = arrayList2;编译不通过
}
}
4.泛型方法
泛型方法:在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系。
换句话说,泛型方法所属的类是不是泛型类都没有关系。
泛型方法时可以声明为静态的。原因:泛型参数时在调用方法时确定的并非实例化时确定的。
//泛型方法
public <E>List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时指明泛型参数的类型
List<Integer> integers = order.copyFromArrayToList(arr);
System.out.println(integers);
}
5.举例 泛型类、泛型方法的使用
//Customer类
public class Customer {
}
//CustomerDAO类
public class CustomerDAO extends DAO<Customer> { //此类对应数据库类中的Customer表
}
//DAO类
/**
* DAO:data(base) access object 数据访问对象
*/
public class DAO<T> {//表的共性操作的类
//添加一条记录
public void add(T t){
}
//删除一条记录
public boolean remove(int index){
return false;
}
//修改一条记录
public void update(int index,T t){
}
//查询一条记录
public T getIndex(int index){
return null;
}
//查询多条记录
public List<T> getForList(List index){
return null;
}
}
//测试类
public class DAOTest {
@Test
public void test(){
CustomerDAO dao1 = new CustomerDAO();
dao1.add(new Customer());
}
}
6.泛型在继承方面的体现
/*
1.泛型在继承方面的体现
类A是类B的父类, G<A> 和 G<B> 二者不具备子父类关系
补充:类A是类B的父类, A<G> 是 B<G> 的父类
*/
public void test(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
List<Object> list1 = null;
List<String> list2 = null;
//此时的list1和list2的类型不具有子父类的关系
// list1 = list2;编译不通过
show1(list1);
show2(list2);
}
public void show1(List<Object> list){
}
public void show2(List<String> list){
}
@Test
public void test2(){
List<String> list1 = null;
ArrayList<String> list2 = null;
list1 = list2;
}
7.通配符的使用
/*
1.使用类型通配符:?
比如: List<?> ,Map<?,?>
List<?>是List<String>、List<Object>等各种泛型List的父类。
⒉.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
唯一的例外是null,它是所有类型的成员。
4.通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个按口,即<=通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=举例:
<?extends Number>(无穷小,Number]
只允许泛型为Number及Number子类的引用调用
<? super Number>[Number ,无穷大)只允许法型为Number及Number父类的引用调用
<?extends comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
*/
/*
2.通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
*/
@Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//
// print(list1);
// print(list2);
//
List<String> list3 =new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加:对于List<?>就不能向其内部添加数据
//除了添加null
// list.add("DD");
// list.add("?")
// list.add(null);
//读取:允许读取,读取的数据类型是Object
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}
/*
有限制条件的通配符的使用
? extends A:
G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的父类
*/
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = null;
List<Person> list4 = null;
List<Object> list5 = null;
list1 = list3;
list1 = list4;
// list1 = list5; 失败
// list2 = list3; 失败
list2 = list4;
list2 = list5;
}
八、IO流
1.File类的使用
java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录File对象可以作为参数传递给流的构造器
1.1常用构造器
public File(String pathname)
以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
绝对路径:是一个固定的路径,从盘符开始
相对路径:是相对于某个位置开始
public File(String parent,String child)
以parent为父路径,child为子路径创建File对象。
public File(File parent,String child)
根据一个父File对象和子文件路径创建File对象
1.2常用方法
File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length():获取文件长度(即:字节数)。不能获取目录的长度
public long lastModified():获取最后一次的修改时间,毫秒值
public String[]list():获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组I
File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
File类的创建功能
public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。
如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():.创建文件目录。如果上层文件目录不存在,一并创建
**注意事项:**如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
File类的删除功能
public boolean delete():删除文件或者文件夹删除
注意事项: Java中的删除不走回收站。要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
/**
* File类的使用
* 1.File类的对象,代表一个文件或一个文件目录(俗称:文件夹)
* 2.File类声明在Java.io包下
* 3.File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
* 并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用T0流来完成。
* 4.后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点”
*/
public class FileTest {
/*
1.如何创建File类的实例
File(String filePath)
File(string parentPath,String childPath)
File(File parentFiLe,string childPath)
2.相对路径:相较于某个路径下,指明的路径
绝对路径:包含盘符在内的文件或者文件目录的路径
3.路径分隔符
windows:\\
UNIX:/
*/
@Test
public void test1(){
//构造器1
File file1 = new File("hello1.txt");//相对于当前Module
File file2 = new File("E:\\JavaProject03\\hello2.txt");//
System.out.println(file1);
System.out.println(file2);
//构造器2
File file3 = new File("E:","JavaProject03");
System.out.println(file3);
//构造器3
File file4 = new File(file3,"hi.txt");
System.out.println(file4);
}
/*
File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length():获取文件长度(即:字节数)。不能获取目录的长度
public long lastModified():获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[]list():获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组I
*/
@Test
public void test2(){
File file1 = new File("hello.txt");
File file2 = new File("E:\\io\\hi.txt");
//文件存在
System.out.println(file1.getAbsolutePath());//E:\JavaProject03\hello.txt
System.out.println(file1.getPath());//hello.txt
System.out.println(file1.getName());//hello.txt
System.out.println(file1.getParent());//null
System.out.println(file1.length());//10
System.out.println(new Date(file1.lastModified()));//Thu Jul 07 17:45:03 CST 2022
System.out.println();
//文件不存在
System.out.println(file2.getAbsolutePath());//E:\io\hi.txt
System.out.println(file2.getPath());//E:\io\hi.txt
System.out.println(file2.getName());//hi.txt
System.out.println(file2.getParent());//E:\io
System.out.println(file2.length());//0
System.out.println(file2.lastModified());//0
}
@Test
public void test3(){
File file = new File("E:\\计算机201-数据库课程设计");
String[] list = file.list();
for (String str : list){
System.out.println(str);
}
File[] files = file.listFiles();
for(File f : files){
System.out.println(f);
}
}
/*
File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中存在,且file2不能在硬盘中存在。
*/
@Test
public void test4(){
File file1 = new File("hello.txt");
File file2 = new File("E:\\io\\hi.txt");
boolean renameTo = file2.renameTo(file1);
System.out.println(renameTo);
}
/*
File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public bo olean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
*/
@Test
public void test5(){
File file1 = new File("hello.txt");
System.out.println(file1.isDirectory());//f
System.out.println(file1.isFile());//t
System.out.println(file1.exists());//t
System.out.println(file1.canRead());//t
System.out.println(file1.canRead());//t
System.out.println(file1.isHidden());//f
}
/*
File类的创建功能
public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。
如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():.创建文件目录。如果上层文件目录不存在,一并创建
File类的删除功能
public boolean delete():删除文件或者文件夹删除
*/
@Test
public void test6() throws IOException {
File file1 = new File("hi.txt");
//文件的创建
if(!file1.exists()){
file1.createNewFile();
System.out.println("创建成功");
}else{
file1.delete();
System.out.println("删除成功");
}
}
@Test
public void test7(){
//文件目录的创建
File file1 = new File("E:\\io\\io1");
boolean mkdir = file1.mkdir();
if(mkdir){
System.out.println("创建成功");
}
}
}
2.IO流原理及流的分类
I/O是lnput/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行。
java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类
按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流
(抽象类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
- Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个
抽象基类派生的。
2.由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
IO流体系
|分类|字节输人流|字节输出流|字符输入流|宁符输出流|
|:-----😐:-----😐:-----😐:-----😐
|抽象基类|InputStream|OutputStream|Reader|Writer|
|访问文件|FilelnputStream|FileOutputStream|FileReader|FileWriter|
|访问数组|ByteArraylnputStream |ByteArrayOutputStream|CharArrayReader|CharArrayWriter|
|访问管道|PipedInputStream|PipedOutputStream|PipedReader|PipedWriter|
|访问宁特串|||StringReader|StringWriter|
|缓冲流|BufferedInputStream|BufferedOutputStream|BufferedReader|BufferedWriter|
|转换流|||InputStreamReader|OutputStreamWritei|
|对象流|ObjectinputStream|ObjectOutputStream|
||FilterInputStream|FilterOutputStream|FilterReader|ilterWriter|
|打印流||PrintStream||PrintWriter
|推回输入流|PushbackInputStream||PushbackReader|
|特殊流|DatalnputStream|DataOutputStream|
3.节点流(或文件流)
/**
* 一、流的分类:
* 1.操作数据单位:字节流、字符流
* 2.数据的流向:输入流、输出流
* 3.流的角色:节点流、处理流
*
* 二、流的体系结构
* 抽象基类 节点流(或文件流) 缓冲流(处理流的一种
* InputStream FileInputStream BufferedInputStream
* OutputStream FiLeOutputStream BufferedOutputStream
* Reader FileReader BufferedReader
* Writer FileWriter BufferedWriter
*/
public class FileReaderWriterTest {
/*
将hello.txt文件内容读入程序,并输出到控制台
说明:
1.read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1.
2.异常的处理,为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3.读入的文件一定要存在,否则就会报FileNotFoundException。
*/
@Test
public void test1() {
FileReader fir = null;
try {
//1.实例化File类的对象,指明操作文件
File file = new File("hello.txt");
//2.提供具体的流
fir = new FileReader(file);
//3.数据的读入
//返回读入的一个字符,如果达到文件末尾返回-1
int data = 0;
while((data = fir.read()) != -1){
System.out.println((char)data);
}
}catch (IOException e){
e.printStackTrace();
}finally {
//4.流的关闭
try {
if(fir != null)
fir.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//对read()操作的升级:使用read的重载的方法
@Test
public void testFileReader2() {
FileReader fr = null;
try {
//1.File类的实例化
File file = new File("hello.txt");
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入的具体操作
//read(char[] cbuf):返回每次读入到数组中的字符的个数
//如果达到文件末尾返回-1
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
//错误的写法
// for(int i;i < cbuf.length;i++){
// System.out.println(cbuf[i]);
// }
//方式一:
//正确的写法
for(int i = 0 ;i < len;i++){
System.out.print(cbuf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
从内存中写出数据到硬盘文件中
说明:
1.输出操作,对应File可以不存在。
如果不存在,在输出的过程中,会自动创建此文件。
如果存在,且append的参数为false,则是对原有文件的覆盖,反之是对原有文件的追加内容。
*/
@Test
public void FileWriterTest() throws IOException {
//1.提供file类的对象,指明写出到的文件
File file = new File("hello1.txt");
//2.提供FileWriter的对象,用于数据的写出
FileWriter fw = new FileWriter(file);
//3.写出的操作
fw.write("I have a dream");
fw.write("You need to have a dream");
//4.流资源的关闭
fw.close();
}
@Test
public void FileWriterReaderTest() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入和写出操作
char chr[] = new char[5];
int len = 0;
while ((len = fr.read(chr)) != -1){
fw.write(chr,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
try {
if (fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.缓冲流
缓冲流:
缓冲流也称为处理流,对文件或者其他目标频繁的操作,效率低,性能差。缓冲流目的是提高程序读取和写出的性能。
缓冲流也分为字节缓冲流(如FileInputStream与FileOutputStream)和字符缓冲流(如FileReader与FileWriter)
缓冲流的作用:
缓冲流的基本原理,是创建流对象时候,会创建一个内置的默认大小的缓冲区数组,通过缓冲区书写.使得性能大大提升。
###5.转换流
转换流提供了在字节流和字符流之间的转侧I
Java API提供了两个转换流:
InputStreamReader:将lnputStream转换为Reader
OutputStreamWriter:将Writer转换为OutputStream
字节流中的数据都是字符时,转成字符流操作更高效。
很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。
/**
* 处理流之二:转换流的使用
* 1.转换流属于字符流
* InputStreamReader 将一个字节的输入流转换为字符的输入流
* OutputStreamReader 将一个字符的输出流转换为字节的输出减
*
* 2.作用提供字节流与字符流的转换
*
* 3.解码:字节、字节数组--->字符数组、字符串
* 编码:字符数组、字符串--->字节、字节数组
*/
6.标准输入输出流
System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
7.打印流
实现将基本数据类型的数据格式转化为字符串输出
打印流: PrintStream和PrintWriter
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
PrintStream和PrintWriter的输出不会抛出IOException异常
PrintStream和PrintWriter有自动flush功能
PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。
在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter类。
System.out返回的是PrintStream的实例
8.数据流
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
DatalnputStream 和 DataOutputStream
分别“套接”在 InputStream和 OutputStream子类的流上
DatalnputStream中的方法
boolean readBoolean() byte readByte()
char readChar() float readFloat()
double readDouble() short readShort()
long readLong() int readInt()
String readUTF() void readFully(byte[] b)
DataOutputStream中的方法
将上述的方法的read改为相应的write即可。
9.对象流
ObjectInputStream和OjbectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化:用ObjectOutputStream类保存基木类型数据或对象的机制
反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。lI当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
序列化是RMI(Remote Method **Invoke)**过程的参数和返回值都必须实现的机制,而RM是JavaEE的基础。因此序列化机制是JavaEE平台的基础
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
接口:
Serializable
Externalizable
对象的序列化
凡是实现Serializable接口的类都有一个表示序列化版木标识符的静态变量:
private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态变量[它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显式声明。
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
/**
* Person需要满足如下的要求,方可序列化
* 1.需要实现接口Serializable
* 2.需要当前类提供一个全局常量:serialVersionUID
* //public static final long serialVersionUID = 475123123L;
* 3.除了当前Person类需要实现Serializable接口之外,
* 还必须保证其内部所有属性也必须是可序列化的。
* 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
*
*/
public class Person implements Serializable {
public static final long serialVersionUID = 475123123L;
private String name;
private 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 +
'}';
}
}
10.随机存储文件流
RandomAccessFile类
RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件:
支持只访问文件的部分内容>可以向已存在的文件后追加内容
RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile类对象可以自由移动记录指针:
long getFilePointer():获取文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到pos位置
/**
* RandomAccessFile的使用
* 1.RandomAccessFile直接继承了java.lang.Object类实现了DataInput和DataOutput 接口
* 2.RandomAccessFile既可以作为一个输入流也可以作为一个输出流
* 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,
* 则在执行过程中自动创建如果写出到的文件存在,
* 则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
*/
public class RandomAccessFileTest {
@Test
public void test1(){
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
raf1 = new RandomAccessFile(new File("sky-space-dark-galaxy.jpg"),"r");
raf2 = new RandomAccessFile(new File("sky-space-dark-galaxy2.jpg"),"rw");
byte[] bys = new byte[1024];
int len = 0;
while ((len = raf1.read(bys)) != -1){
raf2.write(bys,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(raf2 != null) {
try {
raf2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(raf2 != null) {
try {
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
11.NIO.2Path、Paths、Files类的使用
11.1NIO概述:
Java NIO (New lO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IOAPl,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的lO操作。NIO将以更加高效的方式进行文件的读写操作。
JavaAPI中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
|-----java.nio.channels.Channel
√|-----FileChannel:处理本地文件
√|-----SocketChannel:TCP网络编程的客户端的Channel
√|-----ServerSocketChannel:TCP网络编程的服务器端的Channel √|-----DatagramChannel:UDP网络编程中发送端和接收端的Channel
11.2Path、Paths、Files类核心API
早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
在以前IO操作都是这样写的:
import java.io.File;
File file = new File(“index.html”);
但在Java7中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get(“index.html”);
同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。
Paths类提供的静态get()方法用来获取Path对象:
static Path get(String first, String … more):用于将多个字符串串连成路径
**static Path get(URl uri)😗*返回指定uri对应的Path路径
九、网络编程
1.网络编程概述
Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
网络基础
●计算机网络:
把分布在不同地理区域的计算机与专门的外部沿久田通信尘吹石流书人切模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、
共享硬件、软件、数据信息等资源。
●网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
●网络编程中有两个主要的问题:
如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
找到主机后如何可靠高效地进行数据传输
###2.网络通信要素概述
IP和端口号
网络通讯协议
如何实现网络中的主机互相通信:
通信双方地址
IP
端口号
一定的规则(即:网络通信协议。有两套参考模型)
OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
网络通信协议
OSI参考模型 | TCPIP参考模型 | TCPIP参考模型各层对应协议 |
---|---|---|
应用层 | 应用层 | HTTP、FTP、Telnet、DNS… |
表示层 | 应用层 | HTTP、FTP、Telnet、DNS… |
会话层 | 应用层 | HTTP、FTP、Telnet、DNS… |
传输层 | 传输层 | TCP、UDP、… |
网络层 | 网络层 | IP、ICMP、ARP |
数据链路层 | 物理+数据链路层 | Link |
物理层 | 物理+数据链路层 | Link |
3.通信要素1:IP和端口号
IP地址: InetAddress
唯一的标识Internet上的计算机通信实体)
本地回环地址(hostAddress):127.0.0.1 主机名(hostName): localhos
IP地址分类方式1:IPV4和 IPV6
√IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
√IPV6: 128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
特点:不易记忆
端口号标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号
被规定为一个16位的整数0~65535。
端口分类:
公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)
动态/私有端口:49152~65535。
●端口号与IP地址的组合得出一个网络套接字:Socket。
4.通信要素2:网络协议
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?
通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
TCP/IP协议簇
传输层协议中有两个非常重要的协议:
传输控制协议TCP(Transmission Control Protocol)
用户数据报协议UDP(User Datagram Protocol)。\
TCP/IP以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
lP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
TCP协议:
使用TCP协议前,须先建立TCP连接,形成传输数据通道传输前,采用“三次握手”方式,点对点通信,是可靠的TCP协议进行通信的两个应用进程:客户端、服务端。在连接中可进行大数据量的传输
传输完毕,需释放已建立的连接,效率低
UDP协议:
将数据、源、目的封装成数据包,不需要建立连接>每个数据报的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的>可以广播发送
发送数据结束时无需释放资源,开销小,速度快
5.TCP网络编程
/**
* 实现TCP的网络编程
* 例子1:客户端发送服务信息给服务端,服务端将数据显示在控制台上
*
*/
public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream outputStream = null;
try {
//1.创建socket的对象,指明服务端的ip和端口号
InetAddress inet = InetAddress.getByName("localhost");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
outputStream = socket.getOutputStream();
//3.写出数据的操作
outputStream.write("你好我是客户端".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源的关闭
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器的端口号ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8899);
//2.调用accept()表示接收来自客户端的Socket
socket = serverSocket.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写可能会有乱码
// byte[] bytes = new byte[20];
// int len;
// while ((len = is.read(bytes)) != -1){
// String str = new String(bytes,0,len);
// System.out.println(str);
// }
//4.读取输入流的数据
baos = new ByteArrayOutputStream();
byte[] bytes = new byte[5];
int len;
while ((len = is.read(bytes)) != -1){
baos.write(bytes,0, len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源
if(baos != null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 实现TCP的网络编程
* 例子2:客户端发送文件给服务端,服务端将文件保存在本地。
*
* 涉及到的异常因该使用try-catch-finally 处理
*
*/
public class TCPTest2 {
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("localhost"),9090);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("sky-space-dark-galaxy.jpg"));
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1){
os.write(bys,0,len);
}
fis.close();
os.close();
socket.close();
}
@Test
public void server() throws IOException {
ServerSocket ss = new ServerSocket(9090);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("123.jpg"));
byte[] bys = new byte[1024];
int len;
while ((len = is.read(bys)) != -1){
fos.write(bys,0,len);
}
fos.close();
is.close();
socket.close();
ss.close();
}
}
/**
* 实现TCP的网络编程
* 例题3:从客户端发送文件给服务端服务端保存到本地。
* 并返回"发送成功给客户端。并关闭相应的连接。
*/
public class TCPTest3 {
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("localhost"),9090);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("sky-space-dark-galaxy.jpg"));
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1){
os.write(bys,0,len);
}
//关闭数据输出
socket.shutdownOutput();
//接收来自服务器端口的数据,并显示在控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bys2 = new byte[20];
int len2;
while ((len2 = is.read(bys2)) != -1){
baos.write(bys2,0,len2);
}
System.out.println(baos.toString());
fis.close();
os.close();
socket.close();
baos.close();
}
@Test
public void server() throws IOException {
ServerSocket ss = new ServerSocket(9090);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("1234.jpg"));
byte[] bys = new byte[1024];
int len;
while ((len = is.read(bys)) != -1){
fos.write(bys,0,len);
}
//服务端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好照片已经接收到".getBytes(StandardCharsets.UTF_8));
fos.close();
is.close();
socket.close();
ss.close();
os.close();
}
}
6.UDP网络编程
UDP网络通信类
DatagramSocket和 DatagramPacket实现了基于UDP协议网络程序。
UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证JDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP也址和端口号以及接收端的IP地址和端口号。
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和妾收方的连接。如同发快递包裹一样。
/**
* UDP协议的的网络编程
*
*/
public class UDPTest {
//发送端
@Test
public void send() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = new String("我是UDP方式发送的信息");
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
InetAddress localHost = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,localHost,9090);
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receiver() throws IOException {
DatagramSocket ds = new DatagramSocket(9090);
byte[] bytes =new byte[100];
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length);
ds.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
ds.close();
}
}
7.URL编程
URL类:
URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某资源的地址。
它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
通过URL我们可以访问Internet上的各种网络资源,比如最常见的 www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>文件名>#片段名?参数列表>
例如:
http:/192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123>
片段名:即锚点,例如看小说,直接定位到章节
参数列表格式:参数名=参数值&参数名=参数值…
URL类构造器
为了表示URL,java.net中实现了类URL。我们可以通过下面的构造器来初始化一个URL对象:
public URL(String spec):通过一个表示URL地址的字符串可以构造一个URL对象。
例如:URL url = new URL (“http://www. atguigu.com”);
public URL(URL context, String spec):通过基URL和相对URL构造一个URL对象。
例如:URL downloadUrl = new URL(url, “download.htm”)
public URL(String protocol,String host, String file);
例如: new URL(“http”,“www.atguigu.com”, “download. htm”);
public URL(String protocol, String host, int port, String file);
例如: URL gamelan = newURL(“http”, “www.atguigu.com”, 80, “download.html”");
URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用try-catch 语句进行捕获。
URL类常用方法:
●一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
publlc String getProtocol() 获取该URL的协议名
public String getHost() 获取该URL的主机名
public String getPort() 获取该URL的端口号
public String getPath() 获取该URL的文件路径
public String getFile() 获取该URL的文件名
public String getQuery() 获取该URL的查询名
/**
* URL网络编程
* 1.URL:统一资源定位符,对应着互联网的某一资源地址2.格式:
* http://Localhost: 8080 /examples/beauty.jpg ?username=Tom
* 协议 主机名 端口号 资源地址 参数列表
*/
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("https://www.bilibili.com/?spm_id_from=333.1007.0.0");
// public String getProtocol()获取该URL的协议名
System.out.println(url.getProtocol());//https
// public String getHost()获取该URL的主机名
System.out.println(url.getHost());//www.bilibili.com
// public String getPort()获取该URL的端口号
System.out.println(url.getPort());//-1
// public String getPath()获取该URL的文件路径
System.out.println(url.getPath());// /
// public String getFile()获取该URL的文件名
System.out.println(url.getFile());///?spm_id_from=333.1007.0.0
// public String getQuery()获取该URL的查询名
System.out.println(url.getQuery());//spm_id_from=333.1007.0.0
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
十、反射机制
1.Java反射机制概述
1.1Java Reflection
●Reflection(反射)是被犯为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子到类的结构,所以,我们形象的称之为:反射。
1.2Java反射机制提供的功能
在运行时判断任意一个对象所属的资T>在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法>在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法>在运行时处理注解
生成动态代理
2.理解Class类并获取Class实例
哪些类型可以有class对象?
(1) class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2) interface:接口
(3)[]:数组
(4) enum:枚举
(5) annotation:注解@interface
(6) primitive type:基本数据类型
(7) void
3.类的加载与ClassLoader的理解
1.类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化a
学
1 | 2 | 3 |
---|---|---|
类的加载 | 类的链 | 按类的初始化 |
(Load) | (Link) | (Initialize) |
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成 | 将类的二进制数据合并到JRE中 | JVM负责对类进行初始化 |
加载: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时
数据结构,然后生成一个代表这个类的ava.lang.ulas通N这个Class对象。这个加载的入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的
过程需要类加载器参与。
链接: 将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量( static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
1.执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
2.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
3.虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
2.类加载器的作用:
类加载的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
3.ClassLoader
了解:ClassLoader
类加载器作用是用来把类(class)装载进内存的。
4.创建运行时类的对象
5.获取运行时类的完整结构
6.调用运行时类的指定结构
7.反射的应用:动态代理
●代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
最好可以通过一个代理类完成全部的代理功能。
/**
* 静态代理举例
*
* 特点:代理类和被代理类在编译期间,就确定下来了。
*/
interface ClothFactory{
void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类对象
NikeClothFactory nike = new NikeClothFactory();
//创建代理类对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
/**
* 动态代理举例
*
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
/*
要想实现动态代理,需要解决的问题?
门题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
*/
class ProxyFactory{
//调用此方法返回一个代理类的对象,解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//赋值时需要使用被代理类对象赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象调用方法a时就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类的抽象方法,此方法也就作为了背代理类调用的方法
//obj: 被代理类的对象
Object returnValue = method.invoke(obj, args);
//上述方法的返回值就作为当前类中的invoke()的返回值
return returnValue;
}
}
public class ProxyTEST {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("大猪蹄子");
}
}
十一、多线程
1.基本概念:程序、进程、线程
**程序(progran)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
**进程(process)**是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
2.线程的创建和使用
Thread类的有关方法:
ovoid start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作
String getName():返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放奋对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop():强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
线程的调度
调度策略:
1.时间片调度
2.抢占式:高优先级的线程抢占CPU
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略>对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级等级
MAX_PRIORITY: 10
MIN_PRIORITY:1
NORM_PRIORITY: 5
涉及的方法
getPriority():返回线程优先值工
setPriority(int newPriority):改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
3.线程的生命周期线程的同步
JDK中用Thredd.State类定义了线程的儿种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU并临时中止自己的执行;进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4.线程的同步
/**
* 例子:创建三个窗口买票,总票数为100张 使用继承Thread类的方式
*
* 存在线程安全问题:待解决
*
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,
* 其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程在操作ticket的时候其他线程不能参与进来。
* 直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况
* 即使线程a出现了阻塞,也不能被改变
* 4,在Java中通过同步机制解决线程安全问题。
*
* 方式一:同步代码快
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:操作共享数据的代码时需要被同步的代码
* 共享数据:多个线程共同操作的变量
* 同步监视器:俗称:锁。任何一个类的对象都可以充当锁。
* 要求:多个线程必须共用同一把锁
* 方式额:同步方法
*
*/
class Windows extends Thread{
Object obj = new Object();
private static int ticket = 100;
@Override
public void run() {
while (true){
synchronized(obj){
if(ticket > 0){
System.out.println(getName() + ":买票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowsTest {
public static void main(String[] args) {
Windows w1 = new Windows();
Windows w2 = new Windows();
Windows w3 = new Windows();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
2.线程的死锁问题
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
3.Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
解决线程安全方式三:Lock锁
/**
* 解决线程安全方式三:Lock锁 JDK5.0新增
*
* 1.面试题: synchronized 与Lock的异同?相同:二者都可以解决线程安全问题
* 不同: synchronized机制在执行完相应的同步代码以后,自动的释放同步监视
* Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())
*
*
*/
class Window implements Runnable{
private int ticket = 100;
//实例化一个Lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定的方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁的方法unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
5.线程的通信
涉及到的三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器
- notify():一旦执行此方法,就会先后被wait的线程,多个线程被wait,则唤醒优先级高的那个
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- 1.wait()、notify()、notifyAll()只能出现在同步代码块和同步方法中,lock中不行
- 2.wait( )、notify()、notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
否则,会出现ILLegaLMonitorStateException异常- 3.wait()、notify()、notifyALL()三个方法是定义在java.Lang.object类中。
面试题:sleep()和 wait()的异同?
- 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 2.不同点:
1)两个方法声明的位置不同: Thread类中声明sleep() , object类中声明wait()。
2)调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代块或同步方法中调用。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
/**
* 线程通讯的例子:
* 使用两个线程交替打印1-100。线程1,线程2交替打印
*
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器
* notify():一旦执行此方法,就会先后被wait的线程,多个线程被wait,则唤醒优先级高的那个
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
*
* 说明:
* 1.wait()、notify()、notifyAll()只能出现在同步代码块和同步方法中,lock中不行
* 2.wait( )、notify()、notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
* 否则,会出现ILLegaLMonitorStateException异常
* 3.wait()、notify()、notifyALL()三个方法是定义在java.Lang.object类中。
*
* 面试题:sleep()和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同: Thread类中声明sleep() , object类中声明wait()。
* 2)调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代块或同步方法中调用。
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()的线程进入阻塞状态
//会释放同步监视器
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
6.JDK5.0新增线程创建方式
新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API
JDK 5.0起提供了线程池相关APl:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行
Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callablq
void shutdown():关闭连接池
Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
创建线程的方式三:实现Callable接口
/**
* 创建线程的方式三:实现Callable接口。 JDK 5.0
*
*
*
*
*/
//1.创建一个实现Callable的实现类
class numberThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum = sum + i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建callable实现类的对象
numberThread numberThread = new numberThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numberThread);
//5将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的caLL()的返回值。
Object o = futureTask.get();
Integer integer = (Integer) o;
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
创建线程的方式四:线程池
/**
* 创建线程的方式四:
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建>)3.便于线程管理
* corePoolsize:核心池的大小
* maximumPoolsize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*/
class NumberThread5 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2,执行指定线程的操作。需要实现提供Runnable接口或者Callable接口的实现类对象
service.execute(new NumberThread5());//适合用于Runnable
// executorService.submit(Callable callable)//适合用于Callable
//3.关闭线程池
service.shutdown();
}
}