java学习笔记

Java

Java运行原理图

定义常量

自定义常量

1
final float PI=3.14f

字面值常量

运算符

  • 在java中,浮点数运算具有不精确性,浮点数本身是非精确存储

  • 字符串可以用 + 拼接

  • 字符串与整型+运算,结果仍是字符串

  • 字符与整型+运算结果为ASCII码的运算结果

逻辑运算

  • 短路与:&& 短路或:| |
  • 与:& 或:|
1
2
3
4
5
6
7
8
int a=10,b=10;
System.out.println(a>20 & (++b) > 10); // false
System.out.println(b); // 11

System.out.println(a>20 && (++b) > 10); // false
System.out.println(b); // 10 短路作用,根据 a>20 直接判断为false,&&之后不再运行

//短路或 类似

位运算

计算机正数存储原码,负数存储补码便于运算。

  • 正数:原码、反码、补码相同
  • 负数:原码(符号位为1)、反码(符号位不变,其余取反)、补码(反码加一)

-9: 原码 1000 0000 0000 1001 反码 1111 1111 1111 0110 补码 1111 1111 1111 0111

-7 1000 0000 0000 0111 1111 1111 1111 1000 1111 1111 1111 1001

-9 & -7 1000 0000 0000 1111 1111 1111 1111 0000 1111 1111 1111 0001

~ 按位取反 ^ 按位异或 << 左移(高位抛弃) >>右移 (高位符号位补齐,低位抛弃) >>>>无符号右移

三元运算符

? : 规则: 布尔表达式 | 7>8 ? 表达式1 :表达式2

1
System.out.println(a>b?a:b);

类型转换

隐式类型转换

​ 小类型转换为大类型会自动转换: byte -> short , char -> int -> long -> float -> double

1
2
3
4
5
虽然float比long小,但因为存储方法不一致导致 float 里面可以存储的数据范围比 long 要大( java 中 float 4个字节  的范围:3.4e+38 - 1.4e-45 ; long 占用 8 个字节 范围:-2^63 - +2^63  )

使用情况:
1. 赋值语句
2. 算术表达式
1
2
3
4
5
6
7
8
9
public static void main(String[] args){
byte a=10;
short b = a;
int c = b;
long d = c;

float e = d;
double f = e;
}

显式类型转换

强制类型转换:大类型数据赋值给小类型数据。

1
2
3
int a=10;
double b;
a = (double) b;

流程控制

语句块

语句块的执行顺序从上到下

语句块可以访问外面定义的变量,外面不能访问语句块内部定义变量

流程控制

控制程序中语句执行的顺序

  • 条件语句

    1
    2
    3
    if (bool表达式){
    // 语句,bool为 true 执行。
    }

    if( ){ ​ //语句 }else{ ​ //语句 }

    if(){

    }else if(){

    }else{

    }

  • Switch 语句

    1
    2
    3
    4
    5
    6
    7
    8
    switch(variable){        // variable 可以是字符串
    case 1:
    break;
    case 2:
    break;
    default:
    break;
    }
  • while 语句

    1
    2
    3
    4
    5
    6
    7
    while(条件){
    // 语句
    }
    //赋值语句可以放在条件中
    while ((input = fileInputStream.read(b)) != -1) {
    //...
    }
  • do - while 先执行一遍循环体

    1
    2
    3
    do{
    // 循环体
    }while(布尔表达式)
  • for 循环

    1
    2
    3
    for(init ; condition ; update){
    // 循环体
    }

字符串 (引用数据类型 - reference type)

字符串变量声明在栈中。

字符串本身无法被修改,变量指向字符串存储的地址,只能分配新的内存再使变量指向其地址。

字符串常量

存放在方法区的运行常量区中,不可修改,栈中的变量指向内容相同的字符串常量时,不在开辟新的空间

1
String str = "abc";

字符串变量

new 得到的对象存放在堆中,堆中保存String传入的字符串地址,变量 str 则保存堆中对象的地址

1
2
String str = new String("abc");
String str = new String(new char[] {'a','s','d'}); // "asd"

字符串比较

若同为字符串变量或常量,则比较引用;若二者不同则比较字符串本身

  • 例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    String str1 = "abc";
    String str2 = "abc";
    String str3 = new String("abc");
    String str4 = "abcd";

    // 比较引用
    System.out.println(str1 == str2); //true
    System.out.println(str2 == str3); //false
    System.out.println(str1 == str4); //false

    // 比较字符串
    str1.equals(str3); // true

字符串 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
String str = "abc";

str.length(); //长度

// 查找子字符串的位置,不存在返回-1
str.indexOf(int c); // 传递一个字符 'A' == 65
str.indexOf(String str);
str.lastIndexOf(String str); // 从后向前查找子串位置
str.charAt(int c); //获取指定位置字符

// 获取子字符串
str.substring(int beginIndex); //从 c 开始的位置截取子串
str.substring(int beginIndex, int endIndex);

//判断字符串的开始和结尾是否是
str.startsWith(String prefix);
str.endsWith(String suffix);

//比较
str.equals(String str);
str.equalsIgnoreCase(String str); //忽略大小写

str.compareTo(String str); //从字符串开头,相同字符跳过,直至不同的字符并返回该字符ASCII码的差,若比较的字符串长度不同,且其中一个字符串是另一个的前缀,返回长度差
------------------------------------------------------------------------
EXAMPLE:
String str = "abcDde";
System.out.println(str.compareTo("abc")); // 3
System.out.println(str.compareTo("abcd")); // -32
------------------------------------------------------------------------

str.contains(String str); //判断是否包含
str.isEmpty(); //判断是否为空 即 str = ""; 而不是 str = null;空对象

// 转换
str.toLowerCase(); //小写
str.toUpperCase(); //大写

// 分割
String[] strs = str.split("\\."); // 按照输入对字符串分割,返回字符串数组 java中的存储为 \. 故\\.
for(String s : strs){
System.out.println(s);
}
str.trim(); // 去除前边和后边的空格

// 字符串替换
str.replace(char oldChar, char newChar);
str.replace(String oldStr, String newStr);

数组

引用类型和基本类型变量创建的不同

  • 所有局部变量都存放在栈里,在方法里创建的变量都是局部变量

  • 基本类型不管是否赋值,都已经分配了空间

  • 引用类型,声明只在栈分配内存(存储引用)、初始化在堆或者方法区里

声明

1
2
3
4
5
int[] array = new int[3]; //按照默认值初始化0
int[] array = new int[]{3,32,34};
int[] array = {3,32,34};

array.length; //长度属性

数据类型默认值

基本类型是0 (0 0.0 0所代表的字符 false)

引用类型是null , null数组不能访问其属性及内容。

数组名的赋值只赋值引用

赋值

1
2
import java.util.Arrays;
Arrays.fill(int[], fromIndex, toIndex, val);

特殊创建

1
2
3
int[][] array = new int[10][];
array[0] = new int[]{1,2,3};
array[1] = new int[]{4,5,6};

方法

java中引用类型传入方法会同步修改,对指针进行封装

1
2
3
4
5
6
7
8
public static void method(int [] a){
a = new int[10];
}

public static void main(String[] args){
int a[] = {1,2,3};
method(a); // 方法中将引用复制到方法局部变量a,对引用a初始化,不影响 main 的数组a
}

方法重载

相同方法名,不同参数

方法重写 override

子类对父类,参数相同,方法名相同

帮助文档生成

必须 public 类才能生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
javadoc -d 要生成的文件夹名 xxx.java

/**
* 这是一个处理数组的静态工具类
*/
public class ArrayTool {
/**
*
* @param array shuzu
* @param value zhi
* @return 返回索引
*/
public static int find_index(float[] array, float value){}
}

类和面向对象

类中的属性未初始化不可使用,默认为null;

面向对象程序设计以对象为核心,该方法认为程序由一系列对象组成。类是对现实世界的抽象,包括表示静态属性的数据和对数据的操作,对象是类的实例化。对象间通过消息传递相互通信,来模拟现实世界中不同实体间的联系。在面向对象的程序设计中,对象是组成程序的基本模块。

面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程架构。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念是类和对象。

匿名对象:

​ new Object(); //适用只使用一次

成员变量

在类中定义的变量,不占任何内存。在其创建对象时分配空间,堆。

局部变量则在栈中存储,方法调用结束销毁,且必须初始化

可将对象作为类的成员变量

垃圾回收机制 (GC)

堆是垃圾回收的主要区域,可以分为新生代和老年代(tenured)。新生代用于存放刚创建的对象以及年轻的 对象,如果对象一直没有被回收,生存得足够长,老年对象就会被移入老年代。

标记-清除算法(Mark-Sweep)从根节点开始标记所有可达对象,其余没标记的即为垃圾对象,执行清除。但回收后的空间是不连续的。

复制算法(copying)将内存分成两块,每次只使用其中一块,垃圾回收时,将标记的对象拷贝到另外一块中,然后完全清除原来使用的那块内存。复制后的空间是连续的。

复制算法适用于新生代,因为垃圾对象多于存活对象,复制算法更高效。 标记-压缩算法(Mark-compact)适合用于老年代的算法(存活对象多于垃圾对象)。

构造方法

1
2
3
4
5
public Object(){
//对成员变量赋值,与c++构造函数类似;可以对构造方法重载
}

private Object(){} //当工具类都为静态方法,使构造方法私有化,防止对其进行实例化

this指针

this 代表当前类,可以通过 this 访问方法和成员变量

1
2
3
4
5
6
class Object{
datatype attribute;
datatype method(datatype attribute){
this.attribute = attribute;
}
}

private & public

私有化与公有化

1
2
3
4
5
6
7
8
9
// 将属性全部私有,利用get、set方法对属性操作,体现了封装性及安全性
private datatype attribute;
public datatype getAttribute(){
return attribute;
}

public void setAttribute(datatype attribute){
this.attribute = attribute;
}

static关键字(静态变量)

  • 可修饰成员变量和成员方法

  • 修饰成员变量表示静态变量,静态变量对所有对象共用,在内存中保存一份

  • 修饰成员变量表示静态方法,静态方法只能访问静态变量(在静态区中存储) 可以把成员变量作为参数传递给静态方法。

  • 静态成员变量通过类名访问;Object.variable;其实例化的所有对象值一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Object{
    public static String name;
    public int age;
    public static void method(Object ob){
    // 通过参数对对象进行访问;
    }
    }

    public class Main{
    public static void main(String[] args){
    Object object = new Object();
    Object.name = "asdf";
    }
    }
  • static 随着类的加载而加载,优先于对象存在。所有对象共享(可以通过对象或者类来访问)

  • main 放法 为特殊的静态方法

  • main(String[] args); 中 args 用来传递参数

    1
    java Object 参数

  • static 实现工具类

    工具类不需要存储数据,不需要实例化,利用 static 构造静态方法

静态代码块

1
2
3
4
5
6
7
//放在类里,只会执行一次
class Object{
static{
pi = 3.14; //因为只运行一次,里面的变量必须是静态变量;
}
private static float pi;
}

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class person{
private String name;
private int age;
private char gender;

public Person(int age, String name, char gender) {
this.age = age;
this.gender = gender;
this.name = name;

}
}

class Teacher extends Person{
private int classNumber;
private int rank;

public Student(int age, String name, int classNumber,
char gender, int rank) {
super(age, name, gender); //通过 super() 调用父类的构造方法, 且必须在第一行
this.classNumber = classNumber;
this.rank = rank;
}
}

tips:

  • java不支持多继承,即同时继承多个类,但支持多层继承

  • 子类不能访问父类的私有成员,但子类包含父类的私有成员不能直接访问

  • 子类和父类中有同名的变量,就近原则

  • 可以通过super访问父类的 public 变量、方法和构造方法;this 访问当前类的成员变量,方法,构造

  • super 和 this 调用构造方法必须在第一行

  • 子类构造子类对象时,会调用父类的构造放法,如果父类没有无参数构造方法,则必须在子类构造方法显式的调用父类的有参数构造方法(利用 super 和 this)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class person{
private String name;
private int age;
private char gender;

public Person(int age, String name, char gender) {
this.age = age;
this.gender = gender;
this.name = name;

}
}

class Teacher extends Person{
private int classNumber;
private int rank;

public Student(){} //报错,父类无无参数构造方法
}
  • this 通常用于存在多个构造方法时,在其中一个构造方法中调用同类的其他构造方法,从而简洁代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class person{
    private String name;
    private int age;
    private char gender;
    public Person(int age, String name){
    this.age = age;
    this.name = name;
    }

    public Person(int age, String name, char gender) {
    this(age, name);
    this.gender = gender;
    }
    }

方法重写(override)或覆盖

子类的方法名、参数、返回值和父类相同

意义:子类和父类有相同功能,但是子类有自己的特定内容和特定实现方式。为了见名知意,保持父类的名字,自己重新实现方法体。

tips:

  • 不能重写private方法, 否则相当于在子类创建新的方法
  • 重写的时候访问权限不能更低(public 最高)

方法重载(overload)

  • 同一个类中,相同的方法名,参数不同,返回值不要求

final 关键字

  • final 类 (表示类不可继承)

    1
    final class Object{}
  • fianl 方法(表示这个方法不能被子类重写)

    1
    public final void method(){}
  • final 变量 (常量)

    1
    final int con;

java 类包(package)

通过包对类进行管理,避免重名问题。不同包里可以包含同名的类

包的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package xx.xx.xx; 
public class Object{}

//命名规则,以域名开头,都小写
com.liuyang.xxx;

// 包文件自动生成,再把源文件放到路径下
javac -d . xxxx.java

//运行包文件
java com.liuyang.xxx.xxxx

//调用
import com.liuyang.xxx.xxxx.Object;

权限修饰符

应用:类、成员变量、成员方法

default 在同一个包里使用,不能在包外使用;在成员函数定义时,没有加访问限制符的函数,其访问等级为default

父类中的 protected 成员变量在子类可以直接访问

image

内部类

内部类寄生在外部类对象,外部类对象中可以有多个内部类对象,内部类对象只有一个外部类对象

内部类不能单独存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class InnerClass {
public static void main(String[] args) {
Outer outer1 = new Outer("outer");
outer1.test(1.2f, "sss").method();

Outer outer2 = new Outer("outer2", 1.2f, "ddd");
outer2.inner.method();
}
}

// 外部类 不能直接访问内部类成员
class Outer {
String name;
public Inner inner; //内部类实例化

public Outer(String name){
this.name = name;
}
public Outer(String name, float weight, String innerName) {
this.name = name;
inner = new Inner(weight,innerName);
}
public Inner test(float weight, String name) {
inner = new Inner(weight, name);
return inner;
}


// 内部类 public default protect private static
class Inner {
private float weight;
private String name;

public Inner(float weight, String name) {
this.name = name;
this.weight = weight;
}

public void method() {
System.out.println("this a inner class");
System.out.println(this.name);
System.out.println(this.weight);
System.out.println("外部类:" + Outer.this.name); //内部类访问外部类成员
}
}
}

从外部类直接访问public内部类

1
2
3
4
5
Outer.Inner inner = new Outer().new Inner();
//--------------------------------------------

Outer out = new Outer();
Outer.Inner inner = out.new Inner();

局部内部类

方法内定义的局部类

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args){
class Person{
private int age;
public Person(int age){
this.age = age;
}
public void show(){
System.out.println(age);
}
}
Person p1 = new Person(12);
p1.show();
}

抽象匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 抽象类
abstract class Plane{
private float speed;
private int id;
public Plane(float speed, int id){
this.id =id;
this.speed =speed;
}
public void show(){
System.out.println(this.speed+" "+this.id);
};
abstract void print();
}
========================================================================================

public class NonNameInnerClass {
public static void main(String[] args){
// 匿名内部类
new Plane(1.2f,12){
void show(){
System.out.println(this.speed+" "+this.id);
}
}.show();

// 有名内部类
Plane plane = new Plane(1.2f,12) {
@Override
void print() {
this.show();
}
};
plane.show();
}
}

多态

多态指同一个实体同时具有多种形式。它是面向对象程序设计(OOP)的一个重要特征。如果一个语言只支持类而不支持多态,只能说明它是基于对象的,而不是面向对象的。C++中的多态性具体体现在运行和编译两个方面。运行时多态是动态多态,其具体引用的对象在运行时才能确定。编译时多态是静态多态,在编译时就可以确定对象使用的形式。

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。即父类的引用在运行过程中存在不同的状态

1
2
3
4
BaseClass bc = null;
bc = SubClass1();
bc = SubClass2();
// bc在运行过程中有多种状态

java多态的前提:

  • 继承
  • 重写
  • 父类对象的引用指向子类对象

继承关系:IS-A;组合关系Has-A;依赖关系 USE-A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Animal {
int num = 10;
static int age = 20; // 静态成员变量
public void eat() {
System.out.println("动物吃饭");
}

public static void sleep() { // 静态方法
System.out.println("动物在睡觉");
}

public void run(){
System.out.println("动物在奔跑");
}
}

// cat is a kind of animal
class Cat extends Animal {
int num = 80;
static int age = 90;
String name = "tomCat";
public void eat() {
System.out.println("猫吃饭");
}
public static void sleep() {
System.out.println("猫在睡觉");
}
public void catchMouse() {
System.out.println("猫在抓老鼠");
}

}
------------------------------------------------------------------------
//测试用类
class Test {
public static void main(String[] args) {
Animal am = new Cat(); // 父类对象的引用指向子类对象
am.eat(); //--> 猫吃饭
am.sleep(); //--> 动物在睡觉 // 静态方法
am.run(); //-->动物在奔跑 // 未重写方法

am.catchMouse(); // 报错 不能使用子类特有的属性和方法
System.out.println(am.name); //报错 不能使用子类特有的属性和方法

System.out.println(am.num); // 10
System.out.println(am.age); // 20
}
}

子类 Cat 重写了父类Animal的非静态成员方法am.eat();的输出结果为:猫吃饭。 子类 Cat 重写了父类(Animal)的静态成员方法am.sleep();的输出结果为:动物在睡觉 未被子类(Cat)重写的父类(Animal)方法am.run()输出结果为:动物在奔跑

成员变量 编译看左边(父类),运行看左边(父类) 成员方法 编译看左边(父类),运行看右边(子类)。动态绑定 静态方法 编译看左边(父类),运行看左边(父类)。 (静态和类相关,算不上重写,所以,访问还是左边的) 只有非静态的成员方法,编译看左边,运行看右边

1
BaseClass bc = new SubClass();

继承的弊端

不能使用子类特有的成员属性和子类特有的成员方法。

解决: 强制转换

1
Cat cat = (Cat) animal;

抽象类

抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为

1
abstract void fun();

抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。

因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象, 抽象类就是为了继承而存在的.

包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:


  1. 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

  2. 抽象类不能用来创建对象;

  3. 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

  在其他方面,抽象类和普通的类并没有区别。


类特殊方法重写(override)

toString方法

1
2
3
4
5
6
7
8
9
10
11
12
Object ob = new Object();
System.out.println(ob.toString()); //打印对象ob的地址
System.out.println(ob); //打印对象ob的地址

// 对 toString() override 打印需要的参数
@Override
public String toString() {
return "B{" +
"c=" + c +
", d='" + d + '\'' +
"} " ;
} // B{c=12, d='dxwec'}

equals方法

1
2
3
4
5
6
public boolean equals(Object o){
if(o == this) return true;
if(o == null || o.getClass() != this.getClass()) return false;
This_Object o1 = (This_Object)o;
return o1.variable == this.variable && string.equals(o1);
}

接口(interface)

软件工程中,接口泛指供别人调用的方法或者函数,Java语言设计者的初衷,它是对行为的抽象

接口的形式如下:

1
[public] interface InterfaceName{}
  • 接口内只能是抽象方法(接口是完全抽象化的类,里面没有完整的方法),方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现
  • 接口中可以含有变量,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误) ,静态常量
  • 接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:

1
2
class ClassName implements Interface1,Interface2,[....]{
}
  • 允许一个类遵循多个特定的接口
  • 一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法

接口和类的区别

  1. 语法层面上的区别

  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2.设计层面上的区别

  • 抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。

  • 继承是一个 "IS-A"的关系,而 接口实现则是 "HAS-A"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

  • 抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。

  • 对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
abstract class Door {
public abstract void open();
public abstract void close();
}

interface Door {
public abstract void open();
public abstract void close();
}

// 对门添加 alarm() 功能
/**
Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。
*/

abstract class Door{
public void open();
public void close();
}

interface Alarm{
public void alarm();
}

class AlarmDoor extends Door implements Alarm{
public void open(){
// specific implementation
}
public void close(){
// specific implementation
}
public void alarm(){
// specific implementation
}
}

接口与匿名内部类

1
2
3
4
5
6
7
8
9
10
public class NonNameInnerClass {
public static void main(String[] args){

new Alarm(){ // 定义实现 Alarm 接口的子类
public void alarm(){ //必须加 public, interface里的方法默认 public
System.out.println("alarm");
}
}.alarm();
}
}

常用类

Scanner

1
2
3
4
5
6
7
8
9
10
11
import java.utils.Scanner;
Scanner scanner = new Scanner(System.in);
scanner.next(); //输入字符串 遇到空格停止扫描
scanner.nextLine(); // 输入字符串 遇到 "\r" 停止扫描
scanner.nextInt();
scanner.next().charAt(0); //输入字符

// haxNext**() 是否有下一个,返回 boolean 类型
scanner.hasNext(); //输入数据,检测是否有下一个字符串,返回
scanner.hasNextInt(); // 是否有下一个整型
scanner.hasNextFloat();

StringBuffer && StringBuilder

创建的字符串可以被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
StringBuffer sb = new StringBuffer();// 可以指定容量

sb.append("sd")
.append(".com")
.append(".cn"); // 使用该方法进行字符串的连接,将比String更加节约内容
sb.insert(int offset,String str); //在StringBuffer对象中插入内容,然后形成新的字符串
sb.deleteCharAt(int p); //删除指定位置的字符,然后将剩余的内容形成新的字符串。
sb.delete(int start,int end) //该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间

sb.reverse(); //将StringBuffer对象中的内容反转
sb.setCharAt(int index, char ch) // 该方法的作用是修改对象中索引值为index位置的字符为新的字符ch

sb.capacity(); //返回缓冲区容量,默认16;

sb.substring(int start, int end); //截取包含start,不包含end索引值
sb.replace(int start, int end, String ch); //替换 包含start,不包含end索引值 的字符串

区别

执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多

StringBuilder和StringBuffer一样,都是继承自抽象类AbstractStringBuilder类,也是一个可变的字符序列。StringBuilder和StringBuffer非常相似,甚至有互相兼容的API,不过,StringBuilder不是线程安全的,这是和StringBuffer的主要区别

当 StringBuffer 的缓冲区满之后,java会申请2倍大小的空间把原来的值赋值过去。

1
2
3
4
String str="abc"+"de";
StringBuilder stringBuilder = newStringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());

  这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

1
String str="abcde";

是完全一样的,所以会很快,而如果写成下面这种形式

1
2
3
String str1="abc";
String str2="de";
String str=str1+str2;

那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

字符的存储,JDK9之前是char存储,JDK9之后是byte存储

####互相转换

1
2
3
String str= "qwert";
StringBuffer sb = new StringBuffer(str); //String转换为StringBuffer
String str2 = sb.toString(); //StringBuffer转换为String

Arrays

静态工具类

method:

1
2
3
4
5
import java.util.Arrays;
int[] array = {1,2,3,3,6}
Arrays.sort(array); // 排序
Arrays.toString(array); //返回 String [1,2,3,3,6]
Arrays.BinarySearch(array, 3); // 二分查找,数组有序

Math

1
2
3
4
5
6
7
8
Math.E;        //自然对数e
Math.abs(); //取绝对值
Math.round(3.4);//四舍五入
Math.aqurt(); //平方根
Math.pow(double a, double b); //第一个参数的值,该值是第二个参数的幂
Math.ceil(double); // 向上取整
Math.floor(); //向下取整
Math.random(); //生成随机数,[0,1)

BigDecimal & BigInteger

BigDecimal任意精度整数非标度值和32位整数标度组成

BigInteger由不可变的任意长度整数

1
2
BigDecimal bd = new BigDecimal(String str);  // 小数字符串
BigDecimal bd = BigDecimal.valueOf(0.01); //

Tips:BigDecimal bd = new BigDecimal(0.02); // 0.0200000000000000004163336: $ $

1
2
3
bd.divide(BigDecimal divisor,int scale, int roundingMode);
bd.multiply(BigDecimal nultiplicand);
bd.subtract(BigDecimal subtrahend);
1
2
BigInteger bigInteger = new BigInteger(String str);
//其他方法与BigDecimal 类似

Date && Calendar

时间类,DateCalendar 取代

1
2
3
Date d = new Date();
Date d = new Date(long date);//使用给定的毫秒时间值构造Date对象。date - 自1970年1月1日00:00:00 GMT以来的毫秒数不超过8099年的毫秒表示。
d.toString(); //以日期转义格式yyyy-mm-dd格式化日期。
1
2
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());

包装类

存于栈上

包装类 基本类
Byte byte
short
Integer int
Long long
double
char
boolean
1
2
3
Integer.parseInt(String str); // 整数字符串转为int
Integer.valueOf(int i); //Returns an Integer instance representing the specified int value.
Integer.valueOf(String i); //Returns an Integer object holding the value of the specified String.

集合类 Collection

java.util 库提供了一套相当完整的集合类(collection classes)来解决这个问题,其中基本的类型有 List 、Set 、Queue 和 Map。这些类型也被称作容器类(container classes),集合还有一些其它特性。例如,Set 对于每个值都只保存一个对象,Map 是一个 关联数组,允许将某些对象与其他对象关联起来。Java 集合类都可以自动地调整自己 的大小。

ArrayList LinkedList Vector // 元素可重复,有序

HashSet TreeSet LinkedSet //不可重复,无序

HashMap TreeMap LinkedMap //

基本概念

  • 集合(Collection): 一个独立元素的序列,这些元素都服从一条或多条规则。List 必须以插入的顺序保存元素,Set 不能包含重复元素,Queue 按照排队规则来确 定对象产生的顺序(通常与它们被插入的顺序相同)。
  • 映射(Map):一组成对的“键值对”对象,允许使用键来查找值。map 允 许我们使用一个对象来查找另一个对象,它也被称作关联数组(associative array), 因为它将对象和其它对象关联在一起;或者称作字典(dictionary),

案例-集合打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.*;

/**
* 集合的打印
*/
public class PrintingCollections {
static Collection<String> fill(Collection<String> collection){
collection.add("rat"); //Map.put(key, value)
collection.add("cat"); //添加一个所想要添加的值并将它与一个键(用来查找值)相关联。
collection.add("dog");
collection.add("dog");
return collection;
}
static Map<String,String> fill(Map<String,String> map){
map.put("rat","Fuzzy");
map.put("cat","Rags");
map.put("dog","Bosco");
map.put("dog","Spot");
return map;
}
public static void main(String[] args) {
System.out.println(fill(new ArrayList<>()));
System.out.println(fill(new LinkedList<>()));
System.out.println(fill(new HashSet<>()));
System.out.println(fill(new TreeSet<>()));
System.out.println(fill(new LinkedHashSet<>()));
System.out.println(fill(new HashMap<>()));
System.out.println(fill(new TreeMap<>()));
System.out.println(fill(new LinkedHashMap<>()));
}

}
/* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[rat, cat, dog]
[cat, dog, rat]
[rat, cat, dog]
{rat=Fuzzy, cat=Rags, dog=Spot}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*/

结论:

  • Collection && Map 区别在于集合中的每个 “槽”(slot) 保存的元素个数。Collection 类型在每个槽中只能保存一个元素,Map 在每个槽中存放了两个元素,即键和与之关联的值。
  • ArrayList 和 LinkedList 都是 List 的类型,从输出中可以看出,它们都按插入顺序保存元素。HashSet ,TreeSet 和 LinkedHashSet 是 Set 的类型。从输出中可以看到,Set 仅保存每个相同项中的一个,并且不同的 Set 实现存储元素的方式也不同。
  • HashSet ,TreeSet 和 LinkedHashSet 是 Set 的类型。从输出中可以看到,Set 仅保存每个相同项中的一个
  • Set是检索元素的最快方法, TreeSet 按比较结果的升序保存对象; LinkedHashSet 按照被添加的先后顺序保存对象。
  • HashMap 实现使用了非常快速的算法来控制顺序,键和值不是插入顺序保存在 HashMap 中
  • TreeMap 通过比较结果的升序来保存键
  • LinkedHashMap 在保持 HashMap 查找速度的同时按键的插入顺序保存键。

List

List 接口在 Collection 的基础上添加了 许多方法,允许在 List 的中间插入和删除元素。

有两种类型的 List :

  • 基本的 ArrayList ,内部通过数组实现,擅长随机访问元素,但在 List 中间插入和删除元素时速度较慢。

  • LinkedList ,链表通过代价较低的在 List 中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList 对于随机访问来说相对较慢,但它具有比 ArrayList 更大的特征集。

    1
    2
    3
    LinkedList linkedList = new LinkedList();
    linkedList.addFirst();
    linkedList.addLast();

ArrayList && Vector

ArrayList && Vector 几乎一样,区别在于 Vector 线程安全,因此效率低于ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//constructors
ArrayList al = new ArrayList(); //默认容量为10;
ArrayList al = new ArrayList(int initialCapacity); //给定初始化容量
ArrayList al = new ArrayList(Collection <? extends E> c);

//使用范型,指定集合可以保存的类型,使用泛型,从 List 中获取元素不需要强制类型转换。因为 List 知道它持有什么类型,因此当调用 get() 时,它会替你执行转型。
ArrayList<Apple> apples = new ArrayList<>();

// methods
al.add(E element); //在列表末尾加入元素 element
al.add(int index, E element);
al.addAll(Collection <? extends E> c); // 将指定集合加入列表的末尾
al.remove(Object o); //在列表中删除第一个给定元素
al.removeAll(Collection <? extends E> c);
al.get(int index);
//ubList() 方法可以轻松地从更大的列表中创建切片,对所返回列表的更改都将会反映在原始列表中
al.subList();
al.isEmpty();
al.contains(); //是否包含指定元素

//转数组
Object[] ob = al.toArray(); //Object 类型元素在for循环时可强制转换其他类型
al.toArray(T[] a); // a - 要存储列表元素的数组

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*##########################################################*/

ArrayList al = new ArrayList();
al.add("a");
al.add("b");

Object[] s = al.toArray();

String[] a = new String[2];
al.toArray(a);

for(Object o: al){
System.out.println((String)o); // 强制转换为String
}
/*##########################################################*/
ArrayList<Apple> apples = new ArrayList<>();
for (int i=0;i<10;i++){
apples.add(new Apple(i));
}

for(Apple a:apples){
a.showInfo();
}

Apple[] apples1 = apples.toArray(Apple[]::new); //必须加范型才可使用, 转换为Apple数组

Arrays.asList() 方法接受一个数组或是逗号分隔的元素列表(使用可变参数), 并将其转换为 List 对象。Collections.addAll() 方法接受一个 Collection 对象,以 及一个数组或是一个逗号分隔的列表,将其中元素添加到 Collection 中。下边的示例展示了这两个方法,以及更通用的 addAll() 方法,所有 Collection 类型都包含该方 法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/** Arrays.asList(); 转化为 List 对象,继承于 Collection 

Collection 的构造器可以接受另一个 Collection,用它来将自身初始化。因此,可以 使用 Arrays.asList() 来为这个构造器产生输入。
但是,Collections.addAll() 运行得更快,而且很容易构建一个不包含元素的 Collection ,然后调用 Collections.addAll(),因此这是首选方式。
*/

Collection<Integer> collection = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
// Integer 数组
Integer[] moreInts = {7,8,9,10};
List<Integer> list = Arrays.asList(moreInts);
list.set(0,-10);
collection.addAll(list);
System.out.println(collection.toString());

####Stack 栈

堆栈是 “后进先出”(LIFO)集合。它有时被称为叠加栈(pushdown stack),因为 最后 “压入”(push)栈的元素,第一个被 “弹出”(pop)

Java 1.0 中附带了一个 Stack 类,结果设计得很糟糕,Java 6 添加了 ArrayDeque ,其中包含直接实现堆栈功能的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.ArrayDeque;
public class StackTest {
public static void main(String[] args) {
Deque<String> stack = new ArrayDeque<>();

for(String s : "My dog has fleas".split(" "))
stack.push(s);

while(!stack.isEmpty())
System.out.print(stack.pop() + " "); }
}
/* Output:
fleas has dog My
*/

Set 集合

Set 不保存重复的元素。Set 最常见的用途是测试归属性,可以很轻松地询问某个对象是否 在一个 Set 中。因此,查找通常是 Set 最重要的操作,因此通常会选择 HashSet 实 现,该实现针对快速查找进行了优化。

Set 具有与 Collection 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 List 那样。实际上,Set 就是一个 Collection ,只是行为不同

由 HashSet 维护的顺序与 TreeSet 或 LinkedHashSet 不同,因为它们的实现具有不同的元素存储方式。TreeSet 将元素存储在红-黑树数据结构中,而 HashSet 使用散列函数。LinkedHashSet 因为查询速度的原因也使用了散列,但是使用了链表来维护元素的插入顺序。

1
2
3
4
5
6
Random rand = new Random(47); 
Set<Integer> intset = new HashSet<>();
//在 0 到 29 之间的 10000 个随机整数被添加到 Set 中
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);

Map 映射

保存键值对 Key , Value、Key不可重复

衍生类:HashMap、TreeMap、LinkedMap

HashMap && HashTable

相同点:

实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用

不同点:

1、Hashtable是早期提供的接口,HashMap是新版JDK提供的接口。

2、Hashtable继承Dictionary类,HashMap实现Map接口。

3、Hashtable线程安全,HashMap线程非安全。

4、Hashtable不允许null值,HashMap允许null值

1
2
3
4
5
6
7
8
9
10
Map<Integer, Integer> map = new HashMap<>();
Integer value = map.get(key);
// 不存在的key,返回 null
map.put(key,value==null? 1 : value+1);
// 遍历不支持 foreach
map.keySet() //返回 Set 类型
map.values() //返回 Collection 类型
for(Object key:map.keySet()){
System.out.println(map.get(key));
}

Queue 队列

范型

利用范型自我实现ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.liuyang.Tools;

public class MyArrayList<T> {
private Object[] data;
private int capacity;
private int size = 0;

public MyArrayList() {
data = new Object[10];
capacity = 10;
}

public MyArrayList(int capacity) {
data = new Object[capacity];
this.capacity = capacity;
}

public int size() {
return size;
}

private void capacityExpend() {
if (size >= capacity) {
capacity = capacity * 2 + 1;
Object[] objects = new Object[capacity];
if (size + 1 >= 0)
System.arraycopy(data, 0, objects, 0, size);
data = objects;
}
}

public void add(T t) {
capacityExpend();
data[size] = t;
size++;
}

public T get(int i) {
if (i > size)
return null;
return (T) data[i];
}

public String toString() {
StringBuilder s = new StringBuilder("[");
for (Object o :
data) {
s.append(o).append(" ");
}
s.append("]");
return s.toString();
}

public T[] toArray() {
return (T[]) data;
}
}

Iterators 迭代器

目标:不重写代码就可以应用于不 同类型的集合

迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底 层结构。

迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。 可以看到一些对迭代器有些奇怪的约束。例如,Java 的 Iterator 只能单向移动。这个 Iterator 只能用来:

  1. 使用 iterator() 方法要求集合返回一个 Iterator。Iterator 将准备好返回序列中的第一个元素。
  2. 使用next()方法获得序列中的下一个元素。
  3. 使用hasNext()方法检查序列中是否还有元素。
  4. 使用remove()方法将迭代器最近返回的那个元素删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化列表,元素为Apple
List<Apple> list = new ArrayList<>();
for(int i=0;i<10;i++){
list.add(new Apple(i));
}

// Iterator 迭代, 迭代器初始化一次,使用一次
Iterator<Apple> it = list.iterator();
while(it.hasNext()){
Apple apple = it.next();
apple.showInfo();
}
it.remove(); //删除由 next() 生成的最后一个元素
System.out.println("size: "+list.size());

/*Output:
Apple ID: 0
Apple ID: 1
Apple ID: 2
Apple ID: 3
Apple ID: 4
Apple ID: 5
Apple ID: 6
Apple ID: 7
Apple ID: 8
Apple ID: 9
size: 9
*/

Iterator 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有 时会说:迭代器统一了对集合的访问方式。

ListIterator

  • ListIterator 是一个更强大的 Iterator 子类型,它只能由各种 List 类生成。
  • It- erator 只能向前移动,而 ListIterator 可以双向移动。
  • 还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 set() 方法替换它访问过的最近一个元素。
  • 通过调用 listIterator() 方法来生成指向 List 开头 处的 ListIterator ,还可以通过调用 listIterator(n) 创建一个一开始就指向列表索 引号为 n 的元素处的 ListIterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
ListIterator<Apple> it = list.listIterator();
//输出 toString,nextIndex,previousIndex
while (it.hasNext()){
System.out.println(it.next()+
","+it.nextIndex()+
","+it.previousIndex());
}
System.out.println();

while (it.hasPrevious()){
System.out.println(it.previous().toString());
}
System.out.println();
// 从 3 开始替换list中的元素
it = list.listIterator(3);
while (it.hasNext()){
Apple apple = it.next();
it.set(new Apple(apple.getId()+1));
}
System.out.println(list);

/* Output:
Apple ID: 0,1,0
Apple ID: 1,2,1
Apple ID: 2,3,2
Apple ID: 3,4,3
Apple ID: 4,5,4
Apple ID: 5,6,5
Apple ID: 6,7,6
Apple ID: 7,8,7
Apple ID: 8,9,8

Apple ID: 8
Apple ID: 7
Apple ID: 6
Apple ID: 5
Apple ID: 4
Apple ID: 3
Apple ID: 2
Apple ID: 1
Apple ID: 0

[Apple ID: 0, Apple ID: 1, Apple ID: 2, Apple ID: 4, Apple ID: 5, Apple ID: 6, Apple ID: 7, Apple ID: 8, Apple ID: 9]
*/

异常处理

try{}catch{} 语句

Java 中所有异常类继承自 Java.lang.Exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try{
//...
}catch(IndexOutOfBoundsException e){
e.printStackTrace(); //打印错误信息,但不终止程序
}catch(ClassCastException e){
// 强制类型转换错误
}finally{
//...
}

// 或者在方法中抛出错误
public static void main (String[] args) throws Exception {
//...
}

拆箱 && 装箱

装箱:Integer i = 10; 自动包装机制把基本类型 10 封装为Integer类型

拆箱:i = i + 100 Integer 拆箱转为 基本类型 int,再加法运算.

文件操作

文件: 文本文件、非文本文件(二进制文件)

文件操作对象包括文件和文件夹(目录)

Java的文件操作通过File对象实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//File 对象构造方法 
File file = new File(File parent, String child);//从父抽象路径名和子路径名(文件名)字符串创建新的 File实例
File file = new File(String pathname);//将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File file = new File(String parent, String child)//从父路径名字符串和子路径名字符串创建新的 File实例

//方法
boolean canExecute()
//测试应用程序是否可以执行此抽象路径名表示的文件。
boolean canRead()
//测试应用程序是否可以读取此抽象路径名表示的文件。
boolean canWrite()
//测试应用程序是否可以修改此抽象路径名表示的文件。
int compareTo(File pathname)
//按字典顺序比较两个抽象路径名。
boolean createNewFile()
//当且仅当具有此名称的文件尚不存在时,以原子方式创建由此抽象路径名命名的新空文件。
static File createTempFile(String prefix, String suffix)
//在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。
static File createTempFile(String prefix, String suffix, File directory)
//在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
boolean delete()
//删除此抽象路径名表示的文件或目录。
boolean equals(Object obj)
//测试此抽象路径名与给定对象的相等性。
boolean exists()
//测试此抽象路径名表示的文件或目录是否存在。

//get类
File getAbsoluteFile()
//返回此抽象路径名的绝对形式。
String getAbsolutePath()
//返回此抽象路径名的绝对路径名字符串。
File getCanonicalFile()
//返回此抽象路径名的规范形式。
String getCanonicalPath()
//返回此抽象路径名的规范路径名字符串。
String getName()
//返回此抽象路径名表示的文件或目录的名称。
String getParent()
//返回此抽象路径名父项的路径名字符串,如果此路径名未指定父目录,则返回 null 。
File getParentFile()
//返回此抽象路径名父项的抽象路径名,如果此路径名未指定父目录,则返回 null 。
String getPath()
//将此抽象路径名转换为路径名字符串,即返回定义的路径名字符串
int hashCode()
//计算此抽象路径名的哈希码。


boolean isAbsolute()
//测试此抽象路径名是否为绝对路径。
boolean isDirectory()
//测试此抽象路径名表示的文件是否为目录。
boolean isFile()
//测试此抽象路径名表示的文件是否为普通文件。
boolean isHidden()
//测试此抽象路径名指定的文件是否为隐藏文件。
long lastModified()
//返回上次修改此抽象路径名表示的文件的时间。
long length()
//返回此抽象路径名表示的文件的长度。
String[] list()
//返回一个字符串数组,用于命名此抽象路径名表示的目录中的文件和目录。
String[] list(FilenameFilter filter)
//返回一个字符串数组,用于命名由此抽象路径名表示的目录中的文件和目录,以满足指定的过滤器。
File[] listFiles()
//返回一个抽象路径名数组,表示此抽象路径名表示的目录中的文件。
File[] listFiles(FileFilter filter)
//返回一个抽象路径名数组,表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[] listFiles(FilenameFilter filter)
//返回一个抽象路径名数组,表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
static File[] listRoots()
//列出可用的文件系统根目录。

boolean mkdir()
//创建此抽象路径名指定的目录。
boolean mkdirs()
//创建此抽象路径名指定的目录,包括任何必需但不存在的父目录。多级创建
boolean renameTo​(File dest)
//重命名此抽象路径名表示的文件。
boolean setExecutable​(boolean executable)
//一种方便的方法,用于设置此抽象路径名的所有者执行权限。
boolean setExecutable​(boolean executable, boolean ownerOnly)
//设置此抽象路径名的所有者或每个人的执行权限。
boolean setLastModified​(long time)
//设置此抽象路径名指定的文件或目录的上次修改时间。
boolean setReadable​(boolean readable)
//一种方便的方法,用于设置此抽象路径名的所有者读取权限。
boolean setReadable​(boolean readable, boolean ownerOnly)
//设置此抽象路径名的所有者或每个人的读取权限。
boolean setReadOnly()
//标记此抽象路径名指定的文件或目录,以便仅允许读取操作。
boolean setWritable​(boolean writable)
//一种方便的方法,用于设置此抽象路径名的所有者写入权限。
boolean setWritable​(boolean writable, boolean ownerOnly)
//设置此抽象路径名的所有者或每个人的写入权限。
Path toPath()
//返回从此抽象路径构造的java.nio.file.Path对象。
String toString()
//返回此抽象路径名的路径名字符串。
URI toURI()
//构造一个表示此抽象路径名的 file: URI。

Tip:

可以通过renameTo()方法实现文件和文件夹的剪切操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/** 文件拷贝
* @param path 文件路径
* @param targetPath 目标文件路径
*/
public static void fileCopy(String path, String targetPath) {
try (FileInputStream fileInputStream = new FileInputStream(path);
FileOutputStream outputStream = new FileOutputStream(targetPath);) {
int sigh = 0;
//数组缓冲区
byte[] b = new byte[1024];
while ((sigh = fileInputStream.read(b)) != -1) {
outputStream.write(b, 0, sigh);
}
} catch (IOException e) {
e.printStackTrace();
}

}

/** 文件移动
* @param path 文件路径
* @param targetPath 目标文件路径
*/
public static boolean fileMove(String path, String targetPath) {
String[] s1 = path.split("/");
String[] s2 = targetPath.split("/");
file = new File(path);
if (s1[s1.length - 1].equals(s2[s1.length - 1])) {
String name = file.getPath();
File file1 = new File(name + "`");
file.renameTo(file1);
(new File(name)).mkdirs();
return file1.renameTo(new File(targetPath));
}

return file.renameTo(new File(targetPath));
}

/**
* 递归删除文件
* @param path 待删除文件目录
* @return 返回bool值
*/
public static boolean fileDelete(String path) {
File file = new File(path);
if (delete(file))
return file.delete();
else
return false;
}

private static boolean delete(File file) {
File[] files = file.listFiles();
boolean result = false;
if (files == null)
return file.delete();

for (File f : files) {
if (f.isFile())
result = f.delete();
else
result = delete(f) && f.delete();
}
return result;
}

###数据流(IO流)基类

  • 字节流 可以读取任意类型数据

    ​ 抽象基类: InputStream、OutputStream

  • 字符流 只可以读取文本数据

    ​ 抽象基类: Reader、Writer

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

结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

IO字节流

IO流操作结束必须调用 close() 方法

FileOutputStream

文件输出流是用于将数据写入FileFileDescriptor的输出流。 文件是否可用或是否可以创建取决于底层平台。 特别是某些平台允许一次仅打开一个文件以供写入FileOutputStream (或其他文件写入对象)。 在这种情况下,如果涉及的文件已经打开,则此类中的构造函数将失败。

FileOutputStream用于写入诸如图像数据的原始字节流。 要编写字符流,请考虑使用FileWriter

Note:

要释放此流使用的资源,应直接或通过通过(Java7 以后)try-with-resources调用close() 。 子类负责清理子类获取的资源。

构造方法

1
2
3
4
5
6
7
8
9
10
FileOutputStream​(File file)    
//创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream​(FileDescriptor fdObj)
//创建要写入指定文件描述符的文件输出流,该文件描述符表示与文件系统中实际文件的现有连接。
FileOutputStream​(File file, boolean append)
//创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream​(String name)
//创建文件输出流以写入具有指定名称的文件。
FileOutputStream​(String name, boolean append)
//创建文件输出流以写入具有指定名称的文件。append 如果是 true ,那么字节将被写入文件的末尾而不是开头

实例方法

变量和类型 方法 描述
void close() 关闭此文件输出流并释放与此流关联的所有系统资源。
FileChannel getChannel() 返回与此文件输出流关联的唯一FileChannel对象。
FileDescriptor getFD() 返回与此流关联的文件描述符。
void write(byte[] b) 将指定字节数组中的 b.length字节写入此文件输出流。
void write(byte[] b, int off, int len) 将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流。
void write(int b) 将指定的字节写入此文件输出流。

FileInputStream

实例方法

变量和类型 方法 描述
int available() 返回可以从此输入流中读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
void close() 关闭此文件输入流并释放与该流关联的所有系统资源。
FileChannel getChannel() 返回与此文件输入流关联的唯一[FileChannel]对象。
FileDescriptor getFD() 返回 FileDescriptor对象,该对象表示与此FileInputStream正在使用的文件系统中的实际文件的连接。
int read() 从此输入流中读取一个字节的数据,末尾返回-1
int read(byte[] b) 从此输入流中读取最多 b.length字节的数据到字节数组b中,返回每次读取长度
int read(byte[] b, int off, int len) 从此输入流 len最多 len字节的数据读入一个字节数组。
long skip(long n) 跳过并从输入流中丢弃 n字节的数据。

字节数组使用

数组大小不可过大,否则会导致内存不足,读取到数组时按数组大小一次读取,合适的数组大小会提升性能。

1
2
3
4
5
6
7
8
9
10
11
//read
int input = 0;
byte[] b = new byte[1024];
while ((input = fileInputStream.read(b)) != -1) {
System.out.println(input);
ArrayTool.print(b, 0, input);
}

// write
byte[] b = "中国🇨🇳我爱你".getBytes();
fileOutputStream.write(b);

关闭方法

JAVA的流不单在内存中分配了空间,也在操作系统占有了资源,比如占用的端口,文件句柄,网络操作数据库应用等。java的垃圾回收机制GC是能从内存中回收不使用的对象,但对操作系统分配的资源是无法干预的,所以就要调用close()方法来通知系统来释放这个资源。

Java7 以前

1
2
3
4
5
6
7
8
9
10
InputStream is = null;
try{
//...
}finally{
if(is != null){
try{
is.close();
}catch(IOException){}
}
}

Java7 以后,使用try-with-resources,将需要关闭的流对象放在try的()中创建

1
2
3
4
5
6
7
8
9
10
11
try(InputStream fileInputStream = new FileInputStream(text)) {
int input;
while (true){
input = fileInputStream.read(); //指针到达末尾返回 -1
if (input == -1)
break;
System.out.print((char)input);
}
}catch (IOException e){
e.printStackTrace();
}

BufferInputStream

BufferedInputStream向另一个输入流添加功能 - 即缓冲输入并支持markreset方法的功能。

创建BufferedInputStrea会创建内部缓冲区阵列。 当读取或跳过来自流的字节时,会根据需要从包含的输入流中一次多个字节向内部缓冲区重新填充。 mark操作会记住输入流中的一个点,并且reset操作会导致在从包含的输入流中获取新字节之前重新读取自最近的mark操作以来读取的所有字节。

buffer 性能较高,使用字节数组的性能略低但差别不大。

变量和类型 方法 描述
int available() 返回可以从此输入流中读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。
void close() 关闭此输入流并释放与该流关联的所有系统资源。
void mark(int readlimit) 标记此输入流中的当前位置,随后对reset方法的调用会在最后标记的位置重新定位此流,以便后续读取重新读取相同的字节。 readlimit- 标记位置变为无效之前可读取的最大字节数限制 | |boolean|markSupported()| 测试此输入流是否支持markreset方法。 | |int|read()| 从输入流中读取下一个数据字节。 | |int|read(byte[] b, int off, int len)| 从给定的偏移量开始,将此字节输入流中的字节读入指定的字节数组。 | |void|reset()| 将此流重新定位到上次在此输入流上调用mark方法时的位置 | |long|skip(long n)| 见的总承包skip的方法InputStream` 。

BufferOutputStream 包装流

该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以将字节写入基础输出流,而不必为写入的每个字节调用底层系统,节约性能。

对OutputStream类进行包装,内部设置缓冲区

构造方法

FileOutputStream 继承自 OutputStream,也可作为参数

构造器 描述
BufferedOutputStream(OutputStream out) 创建新的缓冲输出流以将数据写入指定的基础输出流。
BufferedOutputStream(OutputStream out, int size) 创建新的缓冲输出流,以使用指定的缓冲区大小将数据写入指定的基础输出流。

实例方法

变量和类型 方法 描述
void flush() 刷新此缓冲的输出流。
void write(byte[] b, int off, int len) 将从偏移量 off开始的指定字节数组中的 len字节写入此缓冲输出流。
void write(int b) 将指定单个字节写入此缓冲区的输出流,减少对硬盘的io次数

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @param path 待删除文件目录
* @append true 则写入文件的末尾而不是开头
*/
public static void fileWrite(String path, String string, boolean append){
file = new File(path);
if(file.isDirectory()){
System.out.println("Can not be a directory");
return;
}
try(BufferedOutputStream bufferedOutputStream =
new BufferedOutputStream(new FileOutputStream(path, append))) {
byte[] b = string.getBytes();
bufferedOutputStream.write(b,0,b.length);
}catch (IOException e){
e.printStackTrace();
}
}

IO字符流

抽象基类: System.io.write、System.io.read

用于写入字符流的抽象类。 子类必须实现的唯一方法是write(char [],int,int),flush()和close()。 但是,大多数子类将覆盖此处定义的一些方法,以提供更高的效率,附加功能或两者兼而有之。

OutputStreamWriter

包装流

从字符流到字节流的桥接器:使用指定的charset将写入其中的字符编码为字节。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。

每次调用write()方法都会导致在给定字符上调用编码转换器。 生成的字节在写入底层输出流之前在缓冲区中累积。

注意: 传递给write()方法的单个字符不会被缓冲。

为了获得最高效率,考虑在BufferedWriter中包装OutputStreamWriter,以避免频繁的转换器调用。 例如:

1
Writer out = new BufferedWriter(new OutputStreamWriter(System.out)); 

构造方法

构造器 描述
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定charset的OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs) 创建使用给定charset的OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc) 创建使用给定charset编码器的OutputStreamWriter。

实例方法

变量和类型 方法 描述
void flush() 刷新流。
String getEncoding() 返回此流使用的字符编码的名称。
void write(char[] cbuf, int off, int len) 写一个字符数组的一部分。
void write(int c) 写一个字符。
void write(String str, int off, int len) 写一个字符串的一部分。
1
2
3
4
5
6
7
8
9
10
// 方法写
//@param charset 编码
public static void write(String path, char[] chars,String charset, int off, int length, boolean append) {
try (OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(new FileOutputStream(path, append),charset)) {
outputStreamWriter.write(chars, off, length);
} catch (IOException e) {
e.printStackTrace();
}
}

InputStreamReader

InputStreamReader是从字节流到字符流的桥接器:它使用指定的[charset]读取字节并将其解码为字符。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。

为了获得最高效率,在BufferedReader中包装InputStreamReader。 例如:

1
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 
构造器 描述
InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader。
InputStreamReader(InputStream in, String charsetName) 创建一个使用指定charset的InputStreamReader。
InputStreamReader(InputStream in, Charset cs) 创建一个使用给定charset的InputStreamReader。
InputStreamReader(InputStream in, CharsetDecoder dec) 创建一个使用给定charset解码器的InputStreamReader。
变量和类型 方法 描述
String getEncoding() 返回此流使用的字符编码的名称。
int read() 读一个字符。
int read(char[] cbuf, int offset, int length) 将字符读入数组的一部分。
boolean ready() 判断此流是否可以读取。

字符读取**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static String read(String path, String charset){
try (InputStreamReader inputStreamReader =
new InputStreamReader(new FileInputStream(path),charset)){
if (!inputStreamReader.ready())
return null;
char[] chars = new char[1024];
StringBuilder stringBuilder = new StringBuilder();
int sigh;
while ((sigh = inputStreamReader.read(chars)) != -1){
for (int i=0;i<sigh;i++){
stringBuilder.append(chars[i]);
}
}
return stringBuilder.toString();

}catch(IOException e){
e.printStackTrace();
}
return null;
}

FileWriter && FileReader

  • java.lang.Object

    • java.io.Writer

        • java.io.OutputStreamWriter
            • java.io.FileWriter
        • java.io.InoutStreamReader

            • Java.io.FileReader

与父类类似,封闭性更好,创建简单

调用时默认使用平台的charset

FileWriter

构造器 描述
FileWriter(File file) File写一个 FileWriter ,使用平台的 [default charset]
FileWriter(FileDescriptor fd) 构造一个 FileWriter给出的文件描述符,使用该平台的 [default charset]
FileWriter(File file, boolean append) 在给出要写入的 FileWriter下构造 File ,并使用平台的 [default charset构造] 一个布尔值,指示是否附加写入的数据。
FileWriter(File file, Charset charset) 构造一个FileWriter给予File编写和[charset]
FileWriter(File file, Charset charset, boolean append) 构造FileWriter给出File写入, [charset] 和一个布尔值,指示是否附加写入的数据。
FileWriter(String fileName) 构造一个 FileWriter给出文件名,使用平台的 [default charset]
FileWriter(String fileName, boolean append) 使用平台的 [default charset] 构造一个 FileWriter给定一个文件名和一个布尔值,指示是否附加写入的数据。
FileWriter(String fileName, Charset charset) 构造一个FileWriter给出文件名和[charset]。
FileWriter(String fileName, Charset charset, boolean append) 构造一个FileWriter给定一个文件名, [charset] 和一个布尔值,指示是否附加写入的数据。

FileReader

构造器 描述
FileReader(File file) 使用平台 FileReader ,在 File读取时创建一个新的 FileReader。
FileReader(FileDescriptor fd) 使用平台 [default charset] 创建一个新的 FileReader ,给定 FileDescriptor进行读取。
FileReader(File file, Charset charset) 创建一个新的FileReader ,给出File读取和[charset]。
FileReader(String fileName) 使用平台 [default charset] 创建一个新的 FileReader ,给定要读取的文件的 [名称] 。
FileReader(String fileName, Charset charset) 给定要读取的文件的名称和FileReader ,创建一个新的[FileReader]

BufferedWriter && BufferedReader

BufferedWriter

BufferedWriter将文本写入字符输出流,缓冲字符,以便有效地写入单个字符,数组和字符串。

可以指定缓冲区大小,或者可以接受默认大小。 对于大多数用途,默认值足够大。

提供了一个newLine()方法,它使用平台自己的行分隔符概念,由系统属性line.separator定义。 并非所有平台都使用换行符(' n')来终止行。 因此,调用此方法终止每个输出行比直接编写换行符更为可取。

构造器 描述
BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz) 创建一个使用给定大小的输出缓冲区的新缓冲字符输出流。
1
2
3
4
BufferedWriter bufferedWriter = 
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path));

BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path);
变量和类型 方法 描述
void flush() 刷新流。
void newLine() 写一个行分隔符。
void write(char[] cbuf, int off, int len) 写一个字符数组的一部分。
void write(int c) 写一个字符。
void write(String s, int off, int len) 写一个字符串的一部分。

BufferedReader

由Reader构成的每个读取请求都会导致相应的读取请求由基础字符或字节流构成。 因此,建议将BufferedReader包装在任何read()操作可能代价高昂的Reader上,例如FileReaders和InputStreamReaders。 例如,

1
2
BufferedReader in
= new BufferedReader(new FileReader("foo.in"));

将缓冲指定文件的输入。 如果没有缓冲,read()或readLine()的每次调用都可能导致从文件中读取字节,转换为字符,然后返回,这可能是非常低效的。

使用DataInputStreams进行文本输入的程序可以通过用适当的BufferedReader替换每个DataInputStream来进行本地化。

构造器 描述
BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流。
变量和类型 方法 描述
Stream lines() 返回 Stream ,其元素是从此 BufferedReader读取的行。
void mark(int readAheadLimit) 标记流中的当前位置。
boolean markSupported() 判断此流是否支持mark()操作。
int read() 读一个字符。
int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。
String readLine() 读一行文字。
boolean ready() 判断此流是否可以读取。
void reset() 将流重置为最新标记。
long skip(long n) 跳过字符。

并发

• 并发是关于正确有效地控制对共享资源的访问。

• 并行是使用额外的资源来更快地产生结果。

将一个程序转换成多个独立运行的子任务,像这样的每个子任务都叫作一个“线程”(Thread)。编写程序时,可将每个线程都想象成独立运行,而且都有自己的专用CPU。一些基础机制实际会为我们自动分割CPU的时间。我们通常不必关心这些细节问题,所以多线程的代码编写是相当简便的。

Java 并发的核心机制是 Thread 类,在该语言最初版本中,Thread (线程)是由 程序员直接创建和管理的。随着语言的发展以及人们发现了更好的一些方法,中间层机制 - 特别是 Executor 框架 - 被添加进来,以消除自己管理线程时候的心理负担(及错误)


Thread(线程)

将任务关联到处理器的软件概念,当创建一个 Thread 时,JVM 将分配一大块内存到专为线程保留的特殊区域上,用于提供运行任务时所需 的一切,包括:

• 程序计数器,指明要执行的下一个 JVM 字节码指令。 • 用于支持 Java 代码执行的栈,包含有关此线程已到达当时执行位置所调用方法的信息。它也包含每个正在执行的方法的所有局部变量 (包括原语和堆对象的引用)。 每个线程的栈通常在 64K 到 1M 之间 。

• 第二个则用于 native code(本机方法代码)执行的栈

• thread-local variables (线程本地变量)的存储区域

• 用于控制线程的状态管理变量


Thread 类

每个线程都有优先权。 具有较高优先级的线程优先于具有较低优先级的线程执行。 每个线程可能也可能不会被标记为守护进程。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护进程线程。

当Java虚拟机启动时,通常会有一个非守护进程线程(通常调用某个指定类的名为main的方法)。 Java虚拟机继续执行线程,直到发生以下任一情况:

  • 已调用类Runtimeexit方法,并且安全管理器已允许执行退出操作。
  • 通过调用run方法返回或抛出超出run方法传播的异常,所有非守护程序线程的线程都已死亡。

有两种方法可以创建新的执行线程

一种是将类声明为Thread的子类。 此子类应覆盖类Threadrun方法。 然后可以分配和启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
//. . .
}
}

//以下代码将创建一个线程并开始运行:
PrimeThread p = new PrimeThread(143);
p.start();

另一种方法是声明一个实现Runnable接口的类。 该类然后实现run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,然后启动。 此其他样式中的相同示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
. . .
}
}

//以下代码将创建一个线程并开始运行:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

每个线程都有一个用于识别目的的名称。 多个线程可能具有相同的名称。 如果在创建线程时未指定名称,则会为其生成新名称

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 创建线程用例
public class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
System.out.println("MyThread: "+i);
}
}
}

//测试类
public class ThreadTest {
public static void main(String[] args) {
test1();
}
@Test
public static void test1(){
MyThread myThread = new MyThread();
myThread.start();
ThreadTest.start();
}
public static void start(){
for (int i=0;i<100;i++){
System.out.println("MainThread: "+i);
}
}

}
/* output
MainThread: 0
MyThread: 0
MainThread: 1
MyThread: 1
MyThread: 2
MainThread: 2
MyThread: 3
MainThread: 3
MyThread: 4
MainThread: 4
MyThread: 5
MainThread: 5
MyThread: 6
MainThread: 6
MyThread: 7
MainThread: 7
MyThread: 8
MainThread: 8
MyThread: 9
MainThread: 9
MyThread: 10
MainThread: 10
MyThread: 11
MainThread: 11
MyThread: 12
MainThread: 12
*/

  • 测试类为父线程,Mythread类创建子线程

  • 子线程和父线程同时运行,因此结果具有随机性,打印出的数据顺序非线性。

字段

变量和类型 字段 描述
static int MAX_PRIORITY 线程可以拥有的最大优先级 (10)
static int MIN_PRIORITY 线程可以拥有的最低优先级。(1)
static int NORM_PRIORITY 分配给线程的默认优先级。(5)
构造器 描述
Thread() 分配新的 Thread对象。
Thread(Runnable target) 分配新的 Thread对象。
Thread(Runnable target, String name) 分配新的 Thread对象。
Thread(String name) 分配新的 Thread对象。
Thread(ThreadGroup group, Runnable target) 分配新的 Thread对象。
Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组。
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组,并具有指定的 堆栈大小
Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) 分配新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组,具有指定的stackSize,并且如果inheritThreadLocalstrue ,则继承inheritable thread-local变量的初始值。
Thread(ThreadGroup group, String name) 分配新的 Thread对象。
变量和类型 方法 描述
static int activeCount() 返回当前线程thread group及其子组中活动线程数的估计值。
void checkAccess() 确定当前运行的线程是否具有修改此线程的权限。
protected Object clone() 抛出CloneNotSupportedException,因为无法有意义地克隆线程。
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
static void dumpStack() 将当前线程的堆栈跟踪打印到标准错误流。
static int enumerate(Thread[] tarray) 将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中。
static Map getAllStackTraces() 返回所有活动线程的堆栈跟踪映射。
ClassLoader getContextClassLoader() 返回此线程的上下文 ClassLoader
long getId() 返回此Thread的标识符。
String getName() 返回此线程的名称。
int getPriority() 返回此线程的优先级。
StackTraceElement[] getStackTrace() 返回表示此线程的堆栈转储的堆栈跟踪元素数组。
Thread.State getState() 返回此线程的状态。
ThreadGroup getThreadGroup() 返回此线程所属的线程组。
Thread. UncaughtExceptionHandler getUncaughtExceptionHandler() 返回此线程由于未捕获的异常而突然终止时调用的处理程序。
static boolean holdsLock(Object obj) 当且仅当当前线程在指定对象上保存监视器锁时,返回 true
void interrupt() 中断此线程。
static boolean interrupted() 测试当前线程是否已被中断。
boolean isAlive() 测试此线程是否存活。
boolean isDaemon() 测试此线程是否为守护程序线程。
boolean isInterrupted() 测试此线程是否已被中断。
void join() 等待这个线程死亡。
void join(long millis) 此线程最多等待 millis毫秒。
void join(long millis, int nanos) 此线程最多等待 millis毫秒加上 nanos纳秒。
static void onSpinWait() 表示调用者暂时无法进展,直到其他活动发生一个或多个操作为止。
void run() 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
void setContextClassLoader( ClassLoader cl) 为此Thread设置上下文ClassLoader。
void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。
static void setDefaultUncaughtExceptionHandler( Thread.UncaughtExceptionHandler eh) 设置当线程由于未捕获的异常而突然终止时调用的默认处理程序,并且没有为该线程定义其他处理程序。
void setName(String name) 将此线程的名称更改为等于参数 name
void setPriority(int newPriority) 更改此线程的优先级。
void setUncaughtExceptionHandler( Thread.UncaughtExceptionHandler eh) 设置当此线程由于未捕获的异常而突然终止时调用的处理程序。
static void sleep(long millis) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static void sleep(long millis, int nanos) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。
String toString() 返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。
static void yield() 向调度程序提示当前线程是否愿意产生其当前使用的处理

常用方法

start

线程开始执行; Java虚拟机调用此线程的run方法,不可重复启动

currentThread

返回对当前正在执行的线程对象的引用。

1
Thread mainThread = Thread.currentThread();

**getPriority && setPriority

返回此线程的优先级

1
thread.getPriority();

sleep && join

Thread.sleep() 仅代表当前线程等待时间

join(0)时,表示等将该线程加入主线程中,共用一个CPU核心,待该线程执行结束或死亡,才继续执行下一线程

线程 start()后再调用 join()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void sleep(int millisecond){
try {
Thread.sleep(millisecond);
}catch (InterruptedException e){
e.printStackTrace();
}
}
//
public static void join(Thread thread, int millisecond){
try {
thread.join(millisecond);
}catch (InterruptedException e){
e.printStackTrace();
}
}

setDaemon

设置守护线程

Java 中的线程可以分为两种:守护线程和普通线程。在 JVM 刚启动时,它创建的所有线程,除了主线程(main thread)外,其他的线程都是守护线程(比如:垃圾收集器、以及其他执行辅助操作的线程)。

当创建一个新线程时,新线程将会继承它线程的守护状态,默认情况下,主线程创建的所有线程都是普通线程。

当我们希望创建一个线程来执行一些辅助的工作,但是又不希望这个线程阻碍 JVM 的关闭,在这种情况下,我们就需要使用守护线程了。

区别

守护线程与普通线程唯一的区别是:当线程退出时,JVM 会检查其他正在运行的线程,如果这些线程都是守护线程,那么 JVM 会正常退出操作,但是如果有普通线程还在运行,JVM 是不会执行退出操作的。当 JVM 退出时,所有仍然存在的守护线程都将被抛弃,既不会执行 finally 部分的代码,也不会执行 stack unwound 操作,JVM 会直接退出

  1. thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
  2. 在Daemon线程中产生的新线程也是Daemon的。
  3. 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
  4. Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Thread thread1 = new MyThread(Thread.MAX_PRIORITY,"t1");
Thread thread2 = new MyThread(Thread.NORM_PRIORITY,"t2");
// 线程2设置为守护线程
thread2.setDaemon(true);

thread1.start();
//线程1 加入主线程,保证thread1在thread2之前执行
join(thread1,0);
thread2.start();
/* output 守护线程未执行,jvm 直接退出
t1finished time: 1583768160516

Process finished with exit code 0
*/

interrupt

修改线程为中断状态,不会直接终止线程,通知线程主动释放所占用的系统资源,并终止自己。

stop()方法则直接杀死线程,因此被弃用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 线程类
public class MyThread extends Thread{
public MyThread(){}
public MyThread(int p, String name){
super(name);
setPriority(p);
}

@Override
public void run() {
super.run();
// 如果为中断状态,则返回
for (int i=0;i<100;i++){
if (interrupted()){
System.out.println("interrupt");
return;
}
System.out.println(getName() + " "+i);
}
System.out.println(getName()+" finished time: "+System.currentTimeMillis());
}
}
// 测试
public static void test1() {
Thread.currentThread().setPriority(10);
Thread thread1 = new MyThread(Thread.MAX_PRIORITY,"t1");
thread1.start();
sleep(16);
thread1.interrupt();
}
/*
t1 42
t1 43
t1 44
t1 45
t1 46
t1 47
t1 48
t1 49
t1 50
interrupt
*/

Runnable 接口

Runnable接口应由任何其实例由线程执行的类实现。 该类必须定义一个名为run的无参数的方法。

此接口旨在为希望在活动时执行代码的对象提供通用协议。 例如, Runnable由类Thread实现。 活动只是意味着一个线程已经启动但尚未停止。

此外, Runnable提供了一个类活动而不是Thread 。 实现RunnableThread通过实例化Thread实例并将其自身作为目标传递而无需子类化Thread 。 在大多数情况下,如果只打算覆盖run()方法而不使用其他Thread方法,则应使用Runnable接口。

Runnable 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyRunnableThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (interrupted()) {
System.out.println("interrupt");
return;
}
// To-Do
}
System.out.println((Thread.currentThread()).getName() + " finished time: " + System.currentTimeMillis());
}
}
//匿名内部类加Runnable
public static void main(String[] args) {
    Runnable runnable = new Runnable() {
@Override
public void run() {
// TO_DO
}
};
new Thread(runnable).start();
// 调用
public static void test2(){
MyRunnableThread t = new MyRunnableThread();
Thread myRunnableThread = new Thread(t,"tt1");
myRunnableThread.start();
}

利用Runnable实现进程共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyRunnableThread implements Runnable {
private StringBuilder data = new StringBuilder();
//静态信号量,控制线程执行
private static boolean s = false;
public StringBuilder getData() {
return data;
}
@Override
public void run() {
while (s==true){}
s = true;
for (int i = 0; i < 100; i++) {
if (interrupted()) {
System.out.println("interrupt");
return;
}
data.append(Thread.currentThread().getName());
}
//线程执行完毕修改静态信号量
s = false;
System.out.println((Thread.currentThread()).getName() + " finished time: " + System.currentTimeMillis());
}
}


MyRunnableThread myRunnableThread = new MyRunnableThread();
Thread myRunnableThread1 = new Thread(myRunnableThread,"t1");
Thread myRunnableThread2 = new Thread(myRunnableThread,"t2");
myRunnableThread1.start();
myRunnableThread2.start();
sleep(1000);
System.out.println(myRunnableThread.getData());
/*
t1t1t1t1t1t1t1t1t1t1t1t1t1t1t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2
*/

线程加锁

多线程环境下,对临界资源加锁,设置临界区

临界区保证同时只能有一个线程访问

synchronized

需要利用一个类实例加锁标记

1
2
3
4
5
6
7
8
9
private final Object lock = new Object(); // Runnable
private static final Object lock = new Object(); // Thread
synchronized (lock) {
// 临界区
}
// 或
synchronized (this) {
// 临界区
}

锁方法

1
2
3
private synchronized void sellTicket() {
// TO_DO
}

案例

机票售卖点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class TicketThread implements Runnable {
// 机票数量
private static int tickets = 10;
// 剩余机票
private static int num = 0;
// 锁
private final Object lock = new Object();

// 构造函数
TicketThread(int t) {
tickets = t;
}

@Override
public void run() {
int count = 0;
while (true) {
synchronized (lock) {
if (tickets<=0)
break;
count++;
num++;
System.out.println(Thread.currentThread().getName() + " --卖出第 " + num + " 张票");
tickets--;
}
}

System.out.println(Thread.currentThread().getName() + " 售罄 卖出了:" + count + " 剩余:" + tickets);
}
}


// 主程序
public static void main(String[] args) {
// 初始机票 10000
TicketThread ticketThread = new TicketThread(10000);
// 设置 10 个售卖点
for (int i = 0; i < 10; i++) {
new Thread(ticketThread, "t" + i).start();
}
}

/* output
t7 售罄 卖出了:1253 剩余:0
t1 售罄 卖出了:574 剩余:0
t8 售罄 卖出了:962 剩余:0
t4 售罄 卖出了:989 剩余:0
t2 售罄 卖出了:1210 剩余:0
t6 售罄 卖出了:855 剩余:0
t9 售罄 卖出了:1176 剩余:0
t0 售罄 卖出了:491 剩余:0
t3 售罄 卖出了:1168 剩余:0
t5 售罄 卖出了:1322 剩余:0
*/

ReentrantLock

ReentrantLock 类实现加锁

1
private ReentrantLock reentrantLock = new ReentrantLock();

使用中,若加锁临界区异常,会导致无法解锁

1
2
3
4
5
6
reentrantLock.lock();
try{
// TO_DO
}finally{ // 最后始终会解锁
reentrantLock.unlock();
}

线程安全类

StringBuffer、Vector

线程不安全

StringBuilder、ArrayList


线程组

多线程中,为了方便管理一批线程,我们使用ThreadGroup来表示线程组,通过它对一批线程进行分类管理 。(默认情况下,A线程创建的线程B,B线程是属于A线程的线程组的。)

此外,线程组还可以包括其他线程组。线程组形成一个树,其中除初始线程组之外的每个线程组都有父节点。

允许线程访问有关其自己的线程组的信息,但不允许访问有关其线程组的父线程组或任何其他线程组的信息。

构造方法

构造器 描述
ThreadGroup​(String name) 构造一个新的线程组。
ThreadGroup​(ThreadGroup parent,String name) 创建指定父线程组,名为name的新线程组

注意:

不要把后台线程组和后台线程(守护线程)混为一谈,后台线程组的特性是最后一个线程执行完或最后一个线程被销毁时,后台线程组自动销毁,线程组只是为了统一管理线程的一个方式

常用方法

变量和类型 方法 描述
int activeCount() 返回此线程组及其子组中活动线程数的估计值。
int activeGroupCount() 返回此线程组及其子组中活动组数的估计值。
void checkAccess() 确定当前运行的线程是否具有修改此线程组的权限。
void destroy() 销毁此线程组及其所有子组。
int enumerate​(Thread[] list) 将此线程组及其子组中的每个活动线程复制到指定的数组中。
int enumerate​(Thread[] list, boolean recurse) 将此线程组中的每个活动线程复制到指定的数组中。
int enumerate​(ThreadGroup[] list) 复制到此线程组及其子组中每个活动子组的指定数组引用。
int enumerate​(ThreadGroup[] list, boolean recurse) 复制到此线程组中每个活动子组的指定数组引用。
int getMaxPriority() 返回此线程组的最大优先级。
String getName() 返回此线程组的名称。
ThreadGroup getParent() 返回此线程组的父级。
void interrupt() 中断此线程组中的所有线程。
boolean isDaemon() 测试此线程组是否为守护程序线程组。
boolean isDestroyed() 测试此线程组是否已被销毁。
void list() 将有关此线程组的信息打印到标准输出。
boolean parentOf​(ThreadGroup g) 测试此线程组是线程组参数还是其祖先线程组之一。
void setDaemon​(boolean daemon) 设置线程组为后台线程(当后台线程组中最后一个线程执行完成活着最后一个线程被销毁,后台线程将自动销毁)
void setMaxPriority​(int pri) 设置组的最大优先级。
String toString() 返回此Thread组的字符串表示形式。
void uncaughtException​(Thread t,Throwable e) t代表抛出异常的线程,e代表抛出的异常,该方法处理此线程组里所有线程抛出的未处理的异常

用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void main(String[] args) {
// 创建线程组 “liuyang”
ThreadGroup threadGroup = new ThreadGroup("liuyang");
TicketThread ticketThread = new TicketThread(1000);
for (int i = 0; i < 10; i++) {
// Runnable 创建线程加入线程组
new Thread(threadGroup,ticketThread,i+" 售票点").start();
}
// 打印线程组信息
threadGroup.list();
}

/* output
java.lang.ThreadGroup[name=liuyang,maxpri=10]
Thread[0 售票点,5,liuyang]
Thread[1 售票点,5,liuyang]
Thread[2 售票点,5,liuyang]
Thread[3 售票点,5,liuyang]
Thread[4 售票点,5,liuyang]
Thread[5 售票点,5,liuyang]
Thread[6 售票点,5,liuyang]
Thread[7 售票点,5,liuyang]
Thread[8 售票点,5,liuyang]
Thread[9 售票点,5,liuyang]

3 售票点 卖出了:100 还有:0
9 售票点 卖出了:109 还有:0
6 售票点 卖出了:215 还有:0
5 售票点 卖出了:66 还有:0
1 售票点 卖出了:18 还有:0
4 售票点 卖出了:78 还有:0
0 售票点 卖出了:73 还有:0
7 售票点 卖出了:65 还有:0
2 售票点 卖出了:139 还有:0
8 售票点 卖出了:137 还有:0
*/

通过线程组处理异常

定义一个会抛出异常的线程

1
2
3
4
5
6
public class TicketThread implements Runnable {
@Override
public void run() {
// 除 0 错误
int i=3/0;
}

重写下线程组中的void uncaughtException(Thread t, Throwable e)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ThreadGroup threadGroup = new ThreadGroup("liuyang"){
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
System.out.println(t.getName()+" 抛出 "+ e);
}
};

TicketThread ticketThread = new TicketThread(1000);
for (int i = 0; i < 10; i++) {
new Thread(threadGroup,ticketThread,i+" 售票点").start();
}
/* output
3 售票点 抛出 java.lang.ArithmeticException: / by zero
7 售票点 抛出 java.lang.ArithmeticException: / by zero
6 售票点 抛出 java.lang.ArithmeticException: / by zero
1 售票点 抛出 java.lang.ArithmeticException: / by zero
8 售票点 抛出 java.lang.ArithmeticException: / by zero
2 售票点 抛出 java.lang.ArithmeticException: / by zero
4 售票点 抛出 java.lang.ArithmeticException: / by zero
5 售票点 抛出 java.lang.ArithmeticException: / by zero
9 售票点 抛出 java.lang.ArithmeticException: / by zero
0 售票点 抛出 java.lang.ArithmeticException: / by zero
*/

Timer 定时器

本质上也是线程

  • 线程的工具,用于在后台线程中安排将来执行的任务。可以将任务安排为一次性执行,或者以固定间隔重复执行。

  • 此类是线程安全的,多个线程可以共享单个Timer对象,而无需外部同步

  • Java 5.0引入了java.util.concurrent包,其中一个并发实用程序是ScheduledThreadPoolExecutor,它是一个线程池,用于以给定的速率或延迟重复执行任务。这实际上是对一个更灵活的替代Timer/TimerTask组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask(只实现Runnable)。使用一个线程配置ScheduledThreadPoolExecutor使其等效于Timer

构造器 描述
Timer() 创建一个新计时器。
Timer(boolean isDaemon) 创建一个新的计时器,其关联的线程可以指定为 run as a daemon
Timer(String name) 创建一个新的计时器,其关联的线程具有指定的名称。
Timer(String name, boolean isDaemon) 创建一个新的计时器,其关联的线程具有指定的名称,并且可以指定为 run as a daemon

方法

变量和类型 方法 描述
void cancel() 终止此计时器,丢弃当前计划的任何任务。
int purge() 从此计时器的任务队列中删除所有已取消的任务。
void schedule(TimerTask task, long delay) 在指定的延迟后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始,每隔 period 执行一次
void schedule(TimerTask task, Date time) 计划在指定时间执行指定的任务。
void schedule(TimerTask task, Date firstTime, long period) 从指定时间开始,为重复的 固定延迟执行安排指定的任务。
void scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后开始,为重复的 固定速率执行安排指定的任务。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定时间开始,为重复的 固定速率执行安排指定的任务。

计时器任务 TimerTask

可由Timer一次性或重复执行的 [任务] 。计时器任务不可重复使用

用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.liuyang.Tool.ThreadTool;
class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("task");
}
}

// 延迟1000 millisecond 并隔同样时长执行一次
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTimerTask(), 100, 100);
ThreadTool.sleep
}

网络编程

  • IP地址范围

链路本地地址旨在用于在单个链路上进行寻址,以用于诸如自动地址配置,邻居发现或不存在路由器的目的。

站点本地地址旨在用于在站点内部进行寻址,而无需全局前缀。

全球地址在互联网上是独一无二的。

  • IP地址的文本表示

IP地址的文本表示是特定于地址族的。

  • 主机名解析

主机名到IP地址解析是通过使用本地机器配置信息和网络命名服务(如域名系统(DNS)和网络信息服务(NIS))的组合来完成的。 默认情况下,正在使用的特定命名服务是本地计算机配置的一个。 对于任何主机名,将返回其对应的IP地址。

反向名称解析意味着对于任何IP地址,都会返回与IP地址关联的主机。

InetAddress类提供了将主机名解析为其IP地址的方法,反之亦然。

TCP/IP网络模型

TCP/IP 是互联网相关的各类协议族的总称,比如:TCP,UDP,IP,FTP,HTTP,ICMP,SMTP 等都属于 TCP/IP 族内的协议。TCP/IP模型是互联网的基础,它是一系列网络协议的总称。这些协议可以划分为四层,分别为链路层、网络层、传输层和应用层。

  • 链路层:负责封装和解封装IP报文,发送和接受ARP/RARP报文等。
  • 网络层:负责路由以及把分组报文发送给目标网络或主机。
  • 传输层:负责对报文进行分组和重组,并以TCP或UDP协议格式封装报文。
  • 应用层:负责向用户提供应用程序,比如HTTP、FTP、Telnet、DNS、SMTP等。

在网络体系结构中网络通信的建立必须是在通信双方的对等层进行,不能交错。 在整个数据传输过程中,数据在发送端时经过各层时都要附加上相应层的协议头和协议尾(仅数据链路层需要封装协议尾)部分,也就是要对数据进行协议封装,以标识对应层所用的通信协议。

TCP/IP 中代表性传输层协议—-TCP 和 UDP

  • TCP

    TCP协议全称是传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 仅支持单播传输,即点对点传输

    面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础

    TCP 建立连接:

    第一次握手

    客户端向服务端发送连接请求报文段SYN。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。

    第二次握手

    服务端收到连接请求报文段后,如果同意连接,则会发送一个应答SYN-ACK,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。

    第三次握手

    当客户端收到连接同意的应答后,还要向服务端发送一个确认报文ACK。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。

    TCP 断开连接

    TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK

    第一次握手

    若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求FIN

    第二次握手

    B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。

    第三次握手

    B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求FIN,然后 B 便进入 LAST-ACK 状态。

    第四次握手

    A 收到释放请求后,向 B 发送确认应答ACK,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。

  • UDP

    UDP协议全称用户数据报协议,与TCP协议一样用于处理数据包,是一种无连接的协议。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,报文发送之后,无法得知其是否安全完整到达。UDP 提供了单播,多播,广播的功能

  • 区别

UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅8字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输

InetAddress

此类表示Internet协议(IP)地址,

InetAddress类具有用于存储成功和不成功主机名解析的缓存。

默认情况下,安装安全管理器时,为了防止DNS欺骗攻击,可以永久缓存正主机名解析的结果。 未安装安全管理器时,默认行为是缓存有限(依赖于实现)时间段的条目。 主机名解析失败的结果将在非常短的时间(10秒)内缓存,以提高性能。

变量和类型 方法 描述
boolean equals(Object obj) 将此对象与指定的对象进行比较。
byte[] getAddress() 返回此 InetAddress对象的原始IP地址。
static InetAddress[] getAllByName(String host) 根据主机的名称,根据系统上配置的名称服务返回其IP地址数组。
static InetAddress getByAddress(byte[] addr) 给定原始IP地址返回 InetAddress对象。
static InetAddress getByAddress(String host, byte[] addr) 根据提供的主机名和IP地址创建InetAddress。
static InetAddress getByName(String host) 根据主机名称确定主机的IP地址。
String getCanonicalHostName() 获取此IP地址的完全限定域名。
String getHostAddress() 返回文本表示中的IP地址字符串。
String getHostName() 获取此IP地址的主机名。
static InetAddress getLocalHost() 返回本地主机的地址。
static InetAddress getLoopbackAddress() 返回环回地址。
int hashCode() 返回此IP地址的哈希码。
boolean isAnyLocalAddress() 用于检查InetAddress是否为通配符地址的实用例程。
boolean isLinkLocalAddress() 用于检查InetAddress是否为链接本地地址的实用程序例程。
boolean isLoopbackAddress() 用于检查InetAddress是否为环回地址的实用例程。
boolean isMCGlobal() 用于检查多播地址是否具有全局范围的实用例程。
boolean isMCLinkLocal() 用于检查多播地址是否具有链接范围的实用例程。
boolean isMCNodeLocal() 用于检查多播地址是否具有节点范围的实用例程。
boolean isMCOrgLocal() 用于检查多播地址是否具有组织范围的实用例程。
boolean isMCSiteLocal() 用于检查多播地址是否具有站点范围的实用程序例程。
boolean isMulticastAddress() 用于检查InetAddress是否为IP多播地址的实用程序例程。
boolean isReachable(int timeout) 测试该地址是否可达。
boolean isReachable(NetworkInterface netif, int ttl, int timeout) 测试该地址是否可达。
boolean isSiteLocalAddress() 用于检查InetAddress是否为站点本地地址的实用程序例程。
String toString() 将此IP地址转换为 String

Socket 套接字

应用通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。为了区别不同的应用程序进程和连接,计算机操作系统就可以为应用程序与TCP/IP协议交互提供套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

Socket是一种进程通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。Socket是对TCP/IP协议的封装,它是一组接口。这组接口当然可以由不同的语言去实现。它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部

Socket是面向客户/服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。通过Socket建立通信连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket.

套接字之间的连接过程

分为三个步骤:服务器监听,客户端请求,连接确认。

  1. 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
  2. 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  3. 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

DatagramSocket --UDP

发送和接收数据报套接字

数据报套接字是分组传送服务的发送或接收点。 在数据报套接字上发送或接收的每个数据包都是单独寻址和路由的。 从一台机器发送到另一台机器的多个分组可以被不同地路由,并且可以以任何顺序到达

构造器 描述
DatagramSocket() 构造一个数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket(int port) 构造一个数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr) 创建绑定到指定本地地址的数据报套接字。
protected DatagramSocket(DatagramSocketImpl impl) 使用指定的DatagramSocketImpl创建未绑定的数据报套接字。
DatagramSocket(SocketAddress bindaddr) 创建绑定到指定本地套接字地址的数据报套接字。
变量和类型 方法 描述
void bind(SocketAddress addr) 将此DatagramSocket绑定到特定的地址和端口。
void close() 关闭此数据报套接字。
void connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP地址+端口号)。
void disconnect() 断开连接。
DatagramChannel getChannel() 返回与此数据报套接字关联的唯一DatagramChannel对象(如果有)。
InetAddress getInetAddress() 返回此套接字连接的地址。
InetAddress getLocalAddress() 获取套接字绑定的本地地址。
int getLocalPort() 返回此套接字绑定到的本地主机上的端口号。
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址。
T getOption(SocketOption name) 返回套接字选项的值。
int getPort() 返回此套接字连接的端口号。
int getReceiveBufferSize() 获取此 DatagramSocket的SO_RCVBUF选项的值,即该平台在此 DatagramSocket上用于输入的缓冲区大小。
SocketAddress getRemoteSocketAddress() 返回此套接字连接到的端点的地址,如果未连接则返回 null
int getSendBufferSize() 获取此 DatagramSocket的SO_SNDBUF选项的值, DatagramSocket上的输出的缓冲区大小。
int getTrafficClass() 获取从此DatagramSocket发送的数据包的IP数据报头中的流量类或服务类型。
boolean isBound() 返回套接字的绑定状态。
boolean isClosed() 返回套接字是否关闭。
boolean isConnected() 返回套接字的连接状态。
void receive(DatagramPacket p) 从此套接字接收数据报包。
void send(DatagramPacket p) 从此套接字发送数据报包。
void setBroadcast(boolean on) 启用/禁用SO_BROADCAST。
static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac) 设置应用程序的数据报套接字实现工厂。
DatagramSocket setOption(SocketOption name, T value) 设置套接字选项的值。
void setSendBufferSize(int size) DatagramSocket上的输出的缓冲区大小设置为指定值。
void setSoTimeout(int timeout) 使用指定的超时启用/禁用SO_TIMEOUT,以毫秒为单位。
void setTrafficClass(int tc) 在IP数据报头中为从此DatagramSocket发送的数据报设置流量类或服务类型八位字节。

####案例1-- 单向发送

设置多个 客户机对其发送 socket、 客户机类 implement Runnable 接口,多线程执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UDP_Send implements Runnable {
private byte[] data;
private InetAddress address; // 目标主机地址
private int port; // 目标主机端口

UDP_Send(String string, InetAddress address, int port) {
this.data = string.getBytes();
this.address = address;
this.port = port;
}

@Override
public void run() {
// 客户机 Socket,端口系统分配
try (DatagramSocket datagramSocket = new DatagramSocket()) {

DatagramPacket dp = new DatagramPacket(data, data.length, address, port);
datagramSocket.send(dp);

} catch (IOException e) {
e.printStackTrace();
}
}
}

设置一个 主机循环接收 socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UDP_Receive {
public static void main(String[] args) {
// 设置监听的端口号
if (args == null)
return;
// 接收输入端口
int port = Integer.parseInt(args[0]);
// 主机绑定到端口 port,监听
try (DatagramSocket ds = new DatagramSocket(port)) {
// 创建数据报包对象,存储接收的数据
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
// 循环监听端口,接收数据
while (true) {
try {
ds.receive(dp);
System.out.println("主机[" + port + "]% 客户机地址: " + dp.getSocketAddress() +
" 客户机发送信息:" + new String(dp.getData(), 0, dp.getLength()));
}catch (IOException e){
e.printStackTrace();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

设置 UDP_Main 类进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UDP_Main {
// 静态变量 本地 ip
public static String local_address = "127.0.0.1";

public static void main(String[] args) throws UnknownHostException {
// 设置主机端口为 1086
int port = 1086;
// 创建 InetAddress 对象
InetAddress address = InetAddress.getByName(local_address);
// 创建客户机线程组
ThreadGroup threadGroup = new ThreadGroup("liuyang");
// 创建 10 个客户机线程
for (int i=0;i<10;i++) {
Thread thread = new Thread(threadGroup,
new UDP_Send("我是客户机 "+i,address,port), "客户机"+i);
thread.start();
}
// 打印线程组信息
threadGroup.list();
// 调用主机,传递参数
UDP_Receive.main(new String[]{String.valueOf(port)});
}
}

输出结果

java.lang.ThreadGroup[name=liuyang,maxpri=10] Thread[客户机0,5,liuyang] Thread[客户机1,5,liuyang] Thread[客户机2,5,liuyang] Thread[客户机3,5,liuyang] Thread[客户机4,5,liuyang] Thread[客户机5,5,liuyang] Thread[客户机6,5,liuyang] Thread[客户机7,5,liuyang] Thread[客户机8,5,liuyang] Thread[客户机9,5,liuyang]

主机[1086]% 客户机地址: /127.0.0.1:57939 客户机发送信息:我是客户机 0 主机[1086]% 客户机地址: /127.0.0.1:57932 客户机发送信息:我是客户机 1 主机[1086]% 客户机地址: /127.0.0.1:57936 客户机发送信息:我是客户机 5 主机[1086]% 客户机地址: /127.0.0.1:57938 客户机发送信息:我是客户机 9 主机[1086]% 客户机地址: /127.0.0.1:57937 客户机发送信息:我是客户机 3 主机[1086]% 客户机地址: /127.0.0.1:57941 客户机发送信息:我是客户机 6 主机[1086]% 客户机地址: /127.0.0.1:57933 客户机发送信息:我是客户机 8 主机[1086]% 客户机地址: /127.0.0.1:57935 客户机发送信息:我是客户机 4 主机[1086]% 客户机地址: /127.0.0.1:57934 客户机发送信息:我是客户机 7 主机[1086]% 客户机地址: /127.0.0.1:57940 客户机发送信息:我是客户机 2

案例2 -- 双向发送

分别设置发送线程和接收线程

  1. 发送线程,参数:发送数据,发送ip,发送端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SendThread implements Runnable{
private byte[] data;
private InetAddress address;
private int port;

SendThread(String string, InetAddress address, int port) {
this.data = string.getBytes();
this.address = address;
this.port = port;
}

@Override
public void run() {
// 发送Socket,端口系统分配
try (DatagramSocket datagramSocket = new DatagramSocket()) {
DatagramPacket dp = new DatagramPacket(data, data.length, address, port);
datagramSocket.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
  1. 接收线程, 参数:监听的端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ReceiveThread implements Runnable{
private int port;

public ReceiveThread(int port){
this.port = port;
}
@Override
public void run() {
try (DatagramSocket ds = new DatagramSocket(port)) {
// 创建数据报包对象,存储接收的数据
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
while (true) {
try {
ds.receive(dp);
System.out.println(">> ReceivePort[" + port + "]% SendInfo:" + dp.getSocketAddress() +
"\n" + new String(dp.getData(), 0, dp.getLength()));
} catch (IOException e) {
e.printStackTrace();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
  1. UDP_Service 类,对接收和发送线程包装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class UDP_Service {
private String pcName; // 主机名
private InetAddress targetAddress; // 目标ip
private int port; // 本机监听端口
private int targetPort; // 发送目标端口
private ThreadGroup threadGroup; //线程组

/**
* @param pcName 主机名
* @param targetAddress 目标ip
* @param port 监听端口
* @param targetPort 目标端口
*/
public UDP_Service(String pcName, String targetAddress, int port, int targetPort) {
this.pcName = pcName;
toInetAddress(targetAddress);
this.port = port;
this.targetPort = targetPort;
threadGroup = new ThreadGroup(pcName);
}

/**
* 地址转换
* @param address ip字符串
*/
private void toInetAddress(String address) {
try {
this.targetAddress = InetAddress.getByName(address);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}

/**
* 发送 数据
* @param content 数据内容
*/
public void send(String content) {
new Thread(threadGroup, new SendThread(content, targetAddress, targetPort),
pcName + "发送").start();
}

public void receive() {
Thread receive = new Thread(threadGroup, new ReceiveThread(port)
, pcName + "监听");
// 设置 监听 为守护线程,否则进程无法退出
receive.setDaemon(true);
receive.start();
}

/**
* 接收并发送
*/
public void udpChat() {
receive();
String content;
while (!(content = Scan.scanString()).equals("exit")) {
send("[" + pcName + "]:" + content);
}
}
}
  1. 客户机和主机实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UDP_Client {
public static void main(String[] args) {
UDP_Service udp_service = new UDP_Service("Client",
"127.0.0.1",1087,1088);
udp_service.udpChat();
}
}

public class UDP_Host {
public static void main(String[] args) {
UDP_Service udp_service = new UDP_Service("Host",
"127.0.0.1",1088,1087);
udp_service.udpChat();
}
}
  1. 测试效果
  • 主机

你好我是主机

>>ReceivePort[1088]% SendInfo:/127.0.0.1:50749

[Client]:我是客户机

那你能帮帮我吗?

>>ReceivePort[1088]% SendInfo:/127.0.0.1:49685 [Client]:爬

  • 客户机

>> ReceivePort[1087]% SendInfo:/127.0.0.1:56760 [Host]:你好我是主机 我是客户机

>> ReceivePort[1087]% SendInfo:/127.0.0.1:64583

[Host]:那你能帮帮我吗? 爬

Socket --TCP

该类实现客户端套接字(也称为“套接字”),套接字是两台机器之间通信的端点.

构造函数

描述
Socket() 创建一个未连接的套接字,系统默认类型为SocketImpl。
Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号。
Socket(String host, int port, InetAddress localAddr, int localPort) 创建套接字并将其连接到指定远程端口上的指定远程主机。
Socket(InetAddress address, int port) 创建流套接字并将其连接到指定IP地址处的指定端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 创建套接字并将其连接到指定远程端口上的指定远程地址。
Socket(Proxy proxy) 创建一个未连接的套接字,指定应该使用的代理类型(如果有),而不管其他任何设置。

常用方法

变量和类型 方法 描述
void bind(SocketAddress addr) 将此DatagramSocket绑定到特定的地址和端口。
void close() 关闭此数据报套接字。
void connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP地址+端口号)。
void disconnect() 断开插座。
boolean getBroadcast() 测试是否启用了SO_BROADCAST。
DatagramChannel getChannel() 返回与此数据报套接字关联的唯一DatagramChannel对象(如果有)。
InetAddress getInetAddress() 返回此套接字连接的地址。
InetAddress getLocalAddress() 获取套接字绑定的本地地址。
int getLocalPort() 返回此套接字绑定到的本地主机上的端口号。
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址。
int getPort() 返回此套接字连接的端口号。
OutputStream getOutputStream() 返回此套接字的输出流。
SocketAddress getRemoteSocketAddress() 返回此套接字连接到的端点的地址,如果未连接则返回 null
boolean isBound() 返回套接字的绑定状态。
boolean isClosed() 返回套接字是否关闭。
boolean isConnected() 返回套接字的连接状态。
void receive(DatagramPacket p) 从此套接字接收数据报包。
void send(DatagramPacket p) 从此套接字发送数据报包。
void setBroadcast(boolean on) 启用/禁用SO_BROADCAST。
void setSoTimeout(int timeout) 使用指定的超时启用/禁用SO_TIMEOUT,以毫秒为单位。
void setTrafficClass(int tc) 在IP数据报头中为从此DatagramSocket发送的数据报设置流量类或服务类型八位字节。

案例3 --TCP 点对点全双工

Socket 套接字,该类通过SeverSocket 建立TCP连接,然后即可全双工传输

因此,必须将 发送 和 接收 设置为2个线程,才能实现实时发送

发送线程,传入参数为 Socket 对象

服务端同时对多个客户端发送消息,设置输入临界区,保证对客户端的轮访

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TCP_SendThread extends Thread {
private Socket socket;
private String pcName;
public static final Object object = new Object();

public TCP_SendThread(Socket socket, String pcName) {
super();
this.socket = socket;
this.pcName = pcName;
}

@Override
public void run() {

try (OutputStream outputStream = socket.getOutputStream()) {
String string;
Scanner scanner = new Scanner(System.in);
while (true) {
synchronized (object) {
System.out.print("To "+socket.getPort()+":");
if (socket.isClosed() || (string = scanner.nextLine()).equals("exit"))
break;
}
string = ">> [" + pcName + "] " + string;
try {
outputStream.write(string.getBytes());
// 保证线程能轮流抢占
Thread.sleep(100);
} catch (Exception e) {
break;

}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

接收线程,参数Socket 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TCP_ReceiveThread extends Thread {
private Socket socket;
public TCP_ReceiveThread(Socket socket){
super();
this.socket = socket;
}
@Override
public void run() {
try {
System.out.println("连接到 " + socket.getPort() + " 本机:"+ socket.getLocalPort());
InputStream inputStream = socket.getInputStream();
int sigh;
byte[] data = new byte[1024];
while (true) {
try {
if ((sigh = inputStream.read(data, 0, data.length)) != -1)
System.out.println(new String(data, 0, sigh));
} catch (SocketException e) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

构建 TCP_IOSocket 类

实现对以上两个线程的包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class TCP_IOSocket {
private String address;
private int targetPort;
private String pcName;
private int port;
private boolean isServer;


public TCP_IOSocket(String pcName, boolean isServer,
String address, int targetPort, int port) {
this.address = address;
this.port = port;
this.pcName = pcName;
this.targetPort = targetPort;
this.isServer = isServer;
}
// 创建调用发送和接收线程
public void start(Socket socket){
Thread re = new TCP_ReceiveThread(socket);
Thread se = new TCP_SendThread(socket,pcName);
re.setDaemon(true);
re.start();
se.start();
}


public void tcpIoSocket() {
ServerSocket serverSocket = null;
try {
Socket socket;
if (isServer) {
serverSocket = new ServerSocket(port);
// 与多客户机建立连接
while (true) {
try {
socket = serverSocket.accept();
start(socket);
}catch (IOException e){
break;
}
}
} else {
socket = new Socket(address, targetPort, InetAddress.getByName(address), port);
start(socket);
}
} catch (
IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null)
try {
serverSocket.close();
} catch (Exception ignored) {
}
}
}
}

客户机与主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TCP_Client {
public static void main(String[] args) {
String pcName = "Client";
boolean isServer = false;
String address = "127.0.0.1";
int targetPort = 1090;
int port = 1089;
TCP_IOSocket tcp_ioSocket = new TCP_IOSocket(pcName, isServer,
address, targetPort, port);
tcp_ioSocket.tcpIoSocket();
}
}

public class TCP_Host {
public static void main(String[] args) {
String pcName = "Host";
boolean isServer = true;
String address = "127.0.0.1";
int targetPort = 1089;
int port = 1090;
TCP_IOSocket tcp_ioSocket = new TCP_IOSocket(pcName, isServer,
address, targetPort, port);
tcp_ioSocket.tcpIoSocket();
}
}

测试结果

服务器

客户端1

客户端2

枚举类型

每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,枚举类型拥有的实例在编写的时候,就已经确定下,不能通过其他手段进行创建,且枚举变量在jvm有且只有一个对应的实例.

  1. 枚举实例必须在 enum关键字声明的类中显式的指定(首行开始的以第一个分号结束)
  2. 除了1, 没有任何方式(new,clone,反射,序列化)可以手动创建枚举实例
  3. 枚举类不可被继承
  4. 枚举类是线程安全的
  5. 枚举类型是类型安全的(typesafe),方法参数自动会对传入类型进行检查, static final定义的常量则不具备 类型安全的特点.
  6. 无法继承其他类(已经默认继承Enum)
  1. 常规用法
1
2
3
public enum RoleTypeEnum {
TEACHER, STUDENT, PRINCIPLE
}

我们为沉默枚举创建了三个值,分别是教师、学生、校长。这段代码实际上调用了3次Enum(String name, int ordinal)(ordinal单词的意思为顺序),也就是:

1
2
3
new Enum<Chenmo>("TEACHER", 0);
new Enum<Chenmo>("STUDENT", 1);
new Enum<Chenmo>("PRINCIPLE", 2);

遍历

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
for (RoleTypeEnum e : RoleTypeEnum.values()
) {
System.out.println(e);
}
}
/* output
TEACHER
STUDENT
PRINCIPLE
*/

使用枚举常量比使用final static来实现常量定义,枚举的内存消耗比后高不止两倍. 应该严格避免在Android上使用枚举.

类的加载

我们运行写好的 java 文件时,编译器会先将 java 文件编译为 .class 字节码,再由类加载器将 .class文件加载到 JVM 中,生成 Class 对象

类加载过程:

类从被加载到虚拟机内存中开始,到卸载出内存为止,包括:加载 -> 连接 -> 初始化 三个阶段

1、加载

(1)通过一个类的全限定名来获取其定义的二进制字节流

(2)将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构

(3)在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口。

相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段,程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载。

2、链接

链接需要检查、准备和解析,先检查载入class文件数据的正确性然后给类的静态变量分配存储空间最后将符号引用转成直接引用。

(1)验证

​ 确保被加载的类的正确性

(2)准备

准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。在这个阶段主要是类变量和初始值两个关键词:

  • 类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中,

  • 这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值

(3)解析

​ 虚拟机将常量池中的符号引用转化为直接引用的过程。

  • 符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好
  • 直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。

3、初始化

类加载机制的最后一步,在这个阶段,java程序代码才开始真正执行

在初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化

  • JVM初始化步骤

    1、假如这个类还没有被加载和连接,则程序先加载并连接该类

    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

    3、假如类中有初始化语句,则系统依次执行这些初始化语句

  • 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

    1、创建类的实例,也就是new的方式

    2、访问某个类或接口的静态变量,或者对该静态变量赋值

    3、调用类的静态方法

    4、反射(如 Class.forName(“com.shengsiyuan.Test”))

    5、初始化某个类的子类,则其父类也会被初始化

    6、Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类

类加载器

虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类

Java语言系统自带有三个类加载器

  • Bootstrap ClassLoader (启动类加载器):最顶层的加载类,主要加载核心类库,也就是我们环境变量下面${JAVA_HOME}/lib下的rt.jar、resources.jar、charsets.jar和class等。JVM内部 C++ 语言实现,开发者不允许直接操作(它不是继承自ClassLoader的)
  • Extention ClassLoader (扩展的类加载器):加载目录${JAVA_HOME}\lib\ext目录下的jar包和class文件。JAVA 层面实现,开发者可以直接操作。
  • Appclass Loader (系统类加载器):也称为SystemAppClass。 加载当前应用的classpath的所有类,它也是Java程序默认的类加载器,Java层面实现,开发者可以直接操作。

基本上所有的类加载器都是 ClassLoader 类的一个实现,除了 bootStrap 这个系统内部的加载器之外,也就是说除了它所有的类加载器都有一个父类加载器,可以通过图中的方法得到。当然,我们也可以自己在Java程序里通过继承 ClassLoader 来实现自己的类加载器。

双亲委派原则

当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务

采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象

  • 可以避免重复加载,父类已经加载了,子类就不需要再次加载
  • 更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心api,会带来相关隐患

每一个Java类都维护一个指向定义它的类加载器的引用,可以通过getClassLoader() 就可以获取父类加载器

ExtentionClassLoader加载器的父类加载器返回Null,因为bootStrap 加载器是C++写的,并不存在这么一个java类的实体。但在逻辑上是它的父类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ClassLoaderDemo {
public static void main(String[] args) {

ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
while (classLoader != null) {
System.out.println(classLoader.toString());
/**
* 获取上一级的类加载器
*/
classLoader = classLoader.getParent();
}
}
}

委托机制

类的加载工作由 ClassLoader 和其子类负责,JVM在运行时会产生三个 ClassLoader,默认使用 AppClassLoader 状态应用程序的类。Java装载类使用 全盘委托机制

  • 全盘负责 是指一个 ClassLoader 装载一个类时,除非显式的使用另外一个 ClassLoader,否则该类所依赖的类也都是由这个 ClassLoader 装入。
  • 委托机制 是先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的子类装载器路径中查找并装载目标类

自定义加载类

  • JVM已经提供了默认的类加载器,但是有一些弊端,比如只能加载指定目录下的jar包或者class文件,如果我们想加载其它位置的 jar或者class文件 时,比如网络上的某 class通过动态加载到内存来使用,这样的场景默认的类加载器就不能给我们提供帮助了,所以就要自己定义 ClassLoader

  • 自定义类的加载器继承 ClassLoader 然后重写父类的findClass方法,之所以只重写这个方法是因为JDK已经在loadClass中帮我们使用了ClassLoader搜索类的算法,当在loadClass方法中找不到类时,loadClass方法就会调用findClass方法来搜索类

定义方式:

(1)遵守双亲委派模型:继承ClassLoader,重写findClass()方法。

(2)破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。 通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。

反射

定义

JAVA 反射机制指:在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods

用途

某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。

获取类名三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.liuyang.Reflect;

public class User {
// ...
}

// 通过实例
User user = new User();
Class cl = user.getClass();

// 通过类,不常用
Class cl = User.class;

// 通过Class.forName()
Class cl = Class.forName("com.liuyang.Reflect.User");

获取构造函数及成员变量

设置User类,对其进行反射。并重写to_String()便于测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.liuyang.Reflect;

public class User {
private int id;
private String userName;
private String password;
public String email;
public int age;

public User() {}

private User(int id) {
this.id = id;
}

public User(int id, int age, String userName, String password) {
this.id = id;
this.userName = userName;
this.password = password;
this.age = age;
}

public void show(String course) {
System.out.println(id + " - " + userName + ": " + password + "学了: " + course);
}

public void study() {
System.out.println("Studying");
}

private String study(String course) {
System.out.println("Studying " + course);
return "Finished";
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}

通过反射得到构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 根据类名,得到类的对象
Class cl = Class.forName("com.liuyang.Reflect.User");
// 得到 所有 公有构造方法,返回构造数组
Constructor[] constructors = cl.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("-----------------------------------------------");


// 得到指定构造方法
Constructor publicConstructor = cl.getConstructor(int.class, int.class, String.class, String.class);
// 输入参数,实例化
System.out.println(publicConstructor.newInstance(2, 12, "dd", "wdr3434rewf324234"));
System.out.println("-----------------------------------------------");


// 得到所有的构造方法,包括私有方法
Constructor[] constructors1 = cl.getDeclaredConstructors();
for (Constructor c : constructors1) {
System.out.println(c);
}
System.out.println("-----------------------------------------------");


// 得到私有的构造方法
Constructor privateConstructor = cl.getDeclaredConstructor(int.class);
// 设置访问权限
privateConstructor.setAccessible(true);
// 实例化
System.out.println(privateConstructor.newInstance(210));


/* Output
public com.liuyang.Reflect.User(int,int,java.lang.String,java.lang.String)
public com.liuyang.Reflect.User()
-----------------------------------------------
User{id=2, userName='dd', password='wdr3434rewf324234', email='null', age=12}
-----------------------------------------------
public com.liuyang.Reflect.User(int,int,java.lang.String,java.lang.String)
private com.liuyang.Reflect.User(int)
public com.liuyang.Reflect.User()
-----------------------------------------------
User{id=210, userName='null', password='null', email='null', age=0}

获得public成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 得到公有成员变量
Field[] objects = cl.getFields();
for (Field field : objects) {
System.out.println(field);
}
System.out.println("-----------------------------------------------");


// 得到指定的 public 成员变量
Field ageField = cl.getField("age");
// 通过反射的方式获取 user.age --> age.user
System.out.println(ageField.getInt(new User(2,13,"dwsd","ded")));


/* Output
User{id=210, userName='null', password='null', email='null', age=0}
-----------------------------------------------
public java.lang.String com.liuyang.Reflect.User.email
public int com.liuyang.Reflect.User.age
-----------------------------------------------
13
*/

获得所有成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 得到全部成员变量属性
Field[] allFields = cl.getDeclaredFields();
for (Field field: allFields){
System.out.println(field);
}
System.out.println("-----------------------------------------------");

// 得到指定私有成员变量属性
Field userNameField = cl.getDeclaredField("userName");
userNameField.setAccessible(true);
System.out.println(userNameField.get(new User(102,18,"liuyang","123456780")));

/* Output
private int com.liuyang.Reflect.User.id
private java.lang.String com.liuyang.Reflect.User.userName
private java.lang.String com.liuyang.Reflect.User.password
public java.lang.String com.liuyang.Reflect.User.email
public int com.liuyang.Reflect.User.age
-----------------------------------------------
liuyang
*/

反射获取成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 创建 User 对象
User user = new User(102,18, "liuyang","123456780");

// public 成员方法, 包括父类
Method[] publicMethods = cl.getMethods();
for (Method method: publicMethods){
System.out.println(method);
}
System.out.println("-----------------------------------------------");

// 指定成员方法
Method showMethod = cl.getMethod("show", String.class);
showMethod.invoke(user, "数学");
System.out.println("-----------------------------------------------");

// 取得所有成员方法,不包括父类
Method[] allMethods = cl.getDeclaredMethods();
for (Method method:allMethods)
System.out.println(method);
System.out.println("-----------------------------------------------");

// 取得指定的 私有 成员方法
Method studyMethod = cl.getDeclaredMethod("study", String.class);
// 取得访问权限
studyMethod.setAccessible(true);
Object re = studyMethod.invoke(user,"数学");
System.out.println(re);

/* Output
public java.lang.String com.liuyang.Reflect.User.toString()
public void com.liuyang.Reflect.User.study()
public void com.liuyang.Reflect.User.show(java.lang.String)
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------------------------------------------
102 - liuyang: 123456780学了: 数学
-----------------------------------------------
public java.lang.String com.liuyang.Reflect.User.toString()
public void com.liuyang.Reflect.User.study()
private java.lang.String com.liuyang.Reflect.User.study(java.lang.String)
public void com.liuyang.Reflect.User.show(java.lang.String)
-----------------------------------------------
Studying 数学
Finished

相关类

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法

class

代表类的实体,在运行的Java应用程序中表示类和接口

  • 获得类相关的方法
方法 用途
asSubclass(Class clazz) 把传递的类的对象转换成代表其子类的对象
Cast 把对象转换成代表类或是接口的对象
getClassLoader() 获得类的加载器
getClasses() 返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className) 根据类名返回类的对象
getName() 获得类的完整路径名字
newInstance() 创建类的实例
getPackage() 获得类的包
getSimpleName() 获得类的名字
getSuperclass() 获得当前类继承的父类的名字
getInterfaces() 获得当前类实现的类或是接口
  • 获得类中属性相关的方法
方法 用途
getField(String name) 获得某个公有的属性对象
getFields() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对象
getDeclaredFields() 获得所有属性对象
  • 获得类中注解相关的方法
方法 用途
getAnnotation(Class annotationClass) 返回该类中与参数类型匹配的公有注解对象
getAnnotations() 返回该类所有的公有注解对象
getDeclaredAnnotation(Class annotationClass) 返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations() 返回该类所有的注解对象
  • 获得类中构造器相关的方法
方法 用途
getConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?> parameterTypes) 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获得该类所有构造方法
  • 获得类中方法相关的方法
方法 用途
getMethod(String name, Class...<?> parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes) 获得该类某个方法
getDeclaredMethods() 获得该类所有方法
  • 类中其他重要的方法