Java基础

第一章 Java编程环境

1.1 环境配置

    1.  CLASSPATH
    	.;%JAVA_HOME%\lib\dt.jar\;%JAVA_HOME%\lib\tools.jar;
    2.  JAVA_HOME
    	D:\JAVA\path\jdk1.8.0_181
    3.  Path
    	%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

1.1 开发工具

JVM(Java Virtual Machine):Java虚拟机
JRE(Java Runtime Environment):Java运行环境(运行时类库)
JDK(Java Development Kit):Java开发工具包

第二章 Java语法基础

2.0 注释

 为方便程序的阅读,在程序中写的一些说明性文字,用来提高程序的可读性;
 注释不会出现在字节码文件中,及编译器编辑时会跳过注释。
 分类:
 	单行注释://
 	多行注释:/*  */  也可作为行内注释
 	文档注释:以/**开头 以*/ 结尾,包含一些说明性的文字及JavaDoc标签(后期写项目时,可以生成项目的API)

2.1 常量和变量

声明常量:final 数据类型 常量名称[=值]
final double PI=3.1415926
变量:
1.静态变量:static int y=90; y为静态变量,也称类变量
2.实例变量:int x=100; x为实例变量/成品变量,有默认值
当局部变量与成员变量的名字相同时,成员变量将被隐藏

2.1.1 数据类型

image-20200501135823578

数 据 类 型 关 键 字 在内存占用的字节数 取 值 范 围 默 认 值
布尔型 boolean 1个字节(8位) true、false false
字节型 byte 1个字节(8位) -128~127 (byte)0
短整型 short 2个字节(16位) -215~215 - 1 (short)0
整型 int 4个字节(32位) -231~231 - 1 0
长整型 long 8个字节(64位) -263~263 - 1 0L
字符型 char 2个字节(16位) ‘\u0000’~’\uffff’ ‘\u0000’
单精度浮点型 float 4个字节(32位) -3.4028E+38~-1.4013E-45和1.4013E-45~3.4028E+38 0.0F
双精度浮点型 double 8个字节(64位) -1.7977E+308~-4.9E-324和4.9E-324~1.7977E+308 0.0D
2.1.2 标识符和关键字
标识符:数字(0-9)、字母(a-Z)、下画线(“_”)、美元符(“$”)	//首字符不能为 数字
标识符规范:
	1.表示类名的标识符:每个单词首字母大写
	2.表示方法和变量的标识符:第一个单词首字母小写,从第二个单词开始首字母大写
关键字:
	 数据类型:boolean、byte、short、int、long、char、float、double。
	 包引入和声明:import、package。
	 类和接口声明:class、extends、implements、interface。
	 流程控制:if、else、switch、do、while、case、break、continue、return、default、while、for。
	 异常处理:try、catch、finally、throw、throws。
	 修饰符:abstract、final、native、private、protected、public、static、synchronized、transient、volatile。
	 其他:new、instanceof、this、super、void、assert、const*、enum、goto*、strictfp。
2.1.3 常量
  1. 布尔型常量(boolean):true false

  2. 整数常量(int或long):int四个字节,最大2147483647,最小-2147483648

    十进制:十进制整数,如123、-48等。

    八进制:以数字0开头的八进制整数,如017、-021等。

    十六进制:以0x或0X开头的十六进制整数,如0x12a6、0XAB12、-0x1a0等。

  3. 浮点型常量(float或double):

    浮点数:默认为double型,数值后加f为float型(精度是6位)

    科学记数法:e或E前面必须有数字,后边必须是一个正负整数

    浮点数不精确

    如果要使用精确的浮点数运算,用BigDecimal
    	BigDecimal d1 = BigDecimal.valueOf(0.01);
    	BigDecimal d2 = BigDecimal.valueOf(1.00/100);
    	System.out.println(d1.equals(d2));//true
    
  4. 字符型常量(char):占2个字符,单引号

    ’\n‘ : 换行 '\r' : 回车 '\t' : Tab键

    '\ddd' : 3位八进制数代表的ASCII

    '\uxxxx' : 4位十进制数代表的Unicode字符

  5. 字符串常量(String): 双引

用 final 修饰 , 字母全部大写
2.1.4 变量
  1. 布尔类型(boolean)

  2. byte , short , int 和 long 类型:都是有符号整数类型
    byte类型:占用1个字符,取值范围(-128~127),转换为byte时截取后8位
    long类型:占用8个字符

  3. char类型与字符编码:采用Unicode编码(双字节编码 ),字符范围'\u0000'~'\uffff'

  4. float和double类型:
    float浮点数:32位,1位表示数字的符号,8位表示指数(底数为2),23位表示尾数
    double浮点数:64位,1位表示数字的符号,11位表示指数(底数为2),52位表示尾数

  5. 变量分类

1、局部变量 (local variable)
	方法或语句块内部定义的变量,生命周期是从声明位置开始到到方法或语句块执行完毕为止;
	使用变量前必须先声明、初始化(赋值),再使用。
2、成员变量 (也叫实例变量 member variable)
	方法外部、类的内部定义的变量。从属于对象,生命周期伴随对象始终;
	成员变量会自动被初始化,及有默认值。
3、静态变量 (类变量static variable)
	使用static定义。从属于类,生命周期伴随类始终, 从类加载到卸载;
	静态变量会自动被初始化,及有默认值。
2.1.5 类型转换
  1. 宽转换(自动转换):
    byte-->short-->int-->long-->float-->double
    char-->int-->long-->float-->double
  2. 窄转换(强制类型转换):
    double-->float-->long-->int-->short-->char-->byte
    double-->float-->long-->int-->char-->short-->byte
    注:char(16位)转换到short(16位)时,其结果可能会由正数变成一个负数
  3. 宽窄同时转换:byte转换到char时,先byte宽转换到int,再int窄转换到char
  4. 整型常量可以直接赋值给byte/short/char等类型变量,无需强转,只需不超过其范围
  5. 各种类型间置换
    类   型		转到String					从String转出
    boolean		String.valueOf(boolean)		Boolean.parseBoolean(String)
    byte		String.valueOf(int)			Byte.parseByte(String)
    char		String.valueOf(char)		str.charAt(o)
    short		String.valueOf(int)			Short.parseShort(String)
    int			String.valueOf(int)			Integer.parseInt(String)
    long		String.valueOf(long)		Long.parseLong(String)
    float		String.valueOf(float)		Float.parseFloat(String)
    double		String.valueOf(double)		Double.parseDoule(String)
int number = Interger.valueOf("1234");
2.1.6 进制
 十进制,如 99,0
 八进制,要求以 0 开头,如 015
 十六进制,要求以 0x 或 0X 开头, 如 0x15
 二进制,要求 0b 或 0B 开头,如 0b01101
2.1.7 判断类型
方法一:
	str instanceof String  // true,false
方法二:
	str.getClass().getSimpleName()	// String
2.1.8 格式化输出
System.out.println();	// 自带换行
System.out.format("%s",5);	// 不换行
System.out.printf("%s",5);	// 不换行
System.out.print()	// 不换行
2.1.9 控制台输入
import java.util.Scanner;

Scanner s = new Scanner(System.in);
System.out.printf("输入你的名字:");
String name = s.nextLine();	// 字符串
System.out.printf("输入你的年龄:");
int age = s.nextInt();	// 整数
System.out.printf("名字:%-7s年龄:%d",name,age);

2.2 运算符和表达式

2.2.1 运算符
 优先级	  运算符	  类型		描述		   	  目数	  结合性
1(最高)	     .	     			成员运算符	 	双目		从左向右
2		      !	      逻辑	 	逻辑非		     单目	 	 从右向左
2		      ~	      位运算      按位求反		    单目		从右向左
2		    (类型)       			强制类型转换	   单目	   从右向左
3		 new(类型)  		 		内存分配运算符    单目	   从右向左
6		      >>	  位运算      保留符号的右移    双目	 从左向右
6		     >>>	  位运算      不保留符号右移    双目	 从左向右
6		      <<	  位运算      左移		      双目	从左向右
7		instanceof               实例运算符	      双目	从左向右
9		      &	      位运算      位与		      双目	从左向右         
10		     ^	      位运算      位异或		     双目	    从左向右         
11		     |	      位运算      位或		      双目	从左向右     
12		    &&        逻辑        逻辑与    	      双目	从左向右   
13		     ||	      逻辑        逻辑或    	      双目	从左向右    
14		     ?:	  		         条件运算符	      三目	从右向左 
运算规则:
 整数运算:
 	1、如果两个操作数有一个为Long,则结果也为long;
 	2、没有long时,结果为int,即使操作数全为short,byte,结果也是int;
 浮点运算:
 	1、如果两个操作数有一个为double,则结果为double;
 	2、只有两个操作数都是float,则结果才为float;
 取模运算:
 	其操作数可以为浮点数,一般使用整数,结果是“余数”,余数符号和左边操作数相同,
 	如:7%3=1, -7%3=-1, 7%-3=1
  1. 算术运算符:

    “+” :连接”a+b=“+a+b-->a+b=1011,运算”a+b=“+(a+b)-->a+b=21

    ”/“ :用于整型表示取整7/2-->3,用于float,double表示实数相除7.0/2-->3.5

    float c=7/2-->3.0f float c=(float)7/2-->3.5f

    ”%“ : 取余,a%b=c,c与a同正负

    Mtah.pow(x,y):幂运算x^y

  2. 关系运算符:>、>=、<、<=、!=、==(前四种优先级相等高于后两种)

  3. 逻辑运算符:&(与)、|(或)、!(非)、^(异或)、&&(短路与)、||(短路或)

  4. 位运算符:是对操作数按其在计算机内部的二进制表示按位进行操作

    按位求反(~):单目,每一个二进制位都取反 //~10010011-->01101100

    与运算符(&):双目 //0&0=0,0&1=0,1&1=1

    或运算符(| ) :双目 //0|0=0,0|1=1,1|1=1

    异或运算(^) :双目 //00=0,01=1,1^1=0(不相同是1,相同是0)

    保留符号位右移(>>):二进制位全部右移,左边空出的位用最高位的符号位来填充(正负不变)

    不保留符号右移(>>>):全部右移,左边空出的位用0填充,变为正数(正为0,负为1)

    左移运算(<<):二进制位全部左移,右边空出的位用0填充

  5. 赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、>>=、>>>=、<<=

  6. 条件运算符:双目,a>b?true:false

  7. 运算符优先级:逻辑非 > 逻辑与 > 逻辑或

2.2.2 表达式

​ 表达式:符合一定语法规则的运算符和操作数的序列。一个常量,变量也可认为是一个表达式

2.3 流程控制

2.3.1 分支语句
  1. if……else
  2. switch()
2.3.2 循环语句
  1. while()
  2. do{……}while();
  3. for()
2.3.3 流程跳转语句
  1. break 语句:退出循环
  2. continue 语句:跳过本次循环
  3. return 语句:退出本方法
2.3.4 指定跳转

带标签的break和continue

// 打印101到150之间的质数
outer:for (int i=101; i<150; i++) {
	for (int j=2; j<i/2; j++) {
		if(i%j == 0) {
			continue outer; // 跳转到标签outer所在的地方
		}
	}
	System.out.println(i);
}

2.4 数组

注:数组定义时,不能指定其长度

2.4.1 一维数组
数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照定的先后次序排列组合而成。
其中,每一个数据称作一个元素,每个元素可以通过一个索引(下标)来访问它们。
数组的三个基本特点: 
	1.长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
	2.其元素必须是相同类型, 不允许出现混合类型。
	3.数组类型可以是任何数据类型,包括基本类型和引用类型。
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
  1. 数组定义: <类型><数组名>[]; <类型>[]<数组名>;

    例:int a[]; String[] person; int b[100];(错误,定义数组时不能指定其长度)

  2. 初始化:

    int[] array={1,2,3,4,5} // 静态初始化

    int a[]=new int[3]; // 长度为3,默认初始化

    a[0]=1;a[1]=2; // 动态初始化

  3. 长度:arr.length

2.4.2 多维数组
1. 二维数组定义:	<类型><数组名>[][]	<类型>[][]<数组名>
 例:int a[][];	String[][] str;		int b[100][100];(错误,定义数组不能指定长度)
2. 创建二维数组对象:int][][] a=new int[2][3];
3. 初始化:
	int[][] b={{1,2,3},{2,3,4},{3,4,5}};
	int a[][]=new int[2][3];	a[0][0]=12;
2.4.3 数组基本操作(Arrays)
替换:
	1.Arrays.fill(arr,value)	//arr为数组
	2.Arrays.fill(arr,fisrt,end,value)	//fisrt,end索引值,不包括end
排序:
	Arrays.sort(arr)	// 改变原数组
复制数组:
	1.Arrays.copyOf(arr,len)	//长度
	2.Arrays.copyOfRange(arr,first,end)
数组查询:
	1.Arrays.sort(arr)	// 先排序再查询
	2.Arrays.binarySearch(arr,value)	//有返回索引值,无则返回-长度
	2.Arrays.binarySearch(arr,fisst,end,value)
排序:
1.冒泡排序
	int[] arr = {12, 24, 7, 45, 83, 98, 6};
	int temp;
	for(int i=0;i<arr.length-1;i++) {
		for(int j=0;j<arr.length-1-i;j++) {
			if(arr[j] > arr[j+1]) {
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
		System.out.println(Arrays.toString(arr));
	}	
2.二分法查找
	public static int binarySearch(int[] arr, int temp) {
		int low = 0;
		int high = arr.length-1;
		while(low <= high) {
			int mid = (low+high)/2;
			if(arr[mid] == temp) {
				return mid;
			}else if(arr[mid] < temp) {
				low = mid;
			}else {
				high = mid;
			}
		}
		return -1;
	}
3.选择排序
4.反转排序
2.4.4 数组常用方法
  1. 求长度:int a[]={1,2,3,4}; System.out.println(a.length);
2.4.5 foreach
专门用于读取数组或集合中所有的元素,即对数组进行遍历。
int[] a = {1,2,4,5};
// foreach 仅用于读数组元素,无下标
for(int m : a) {
	System.out.println(m);
}

第三章 Java类与对象

3.1 Java语言的类

3.1.1 概念
  1. 类:对相同或相似的各个事物间共同特性的一种抽象,是数据和对数据进行操作的方法的集合体。

  2. 对象:对象是类的实例化,类是对象的一种模板,对象由属性、方法和事件所组成。

  3. 属性:一个对象的属性就是它能被外界或其所处的环境感知或操作的数据(状态)。可简单认为数据成员就是其属性。

  4. 方法:该对象具有的行为,

  5. 事件:对象之间通过事件进行通信,事件是一种特殊的对象,称为“事件对象”,它“封装”了对象间通信所必需的有用信息(事件源、事件性质、发生时间、发生位置等参数)。

    例:对象A与对象B通信时,就会将所有必要的信息包装成一个事件,将该事件传递给B的特定方法,对象B通过该方法接收事件,完成相应的事件处理。

3.1.2 类的定义
  1. 定义:具有相同特性和行为的对象集合。

  2. 类定义的基本格式:[类修饰符] class <类名>{

    ​ [类体]

    ​ }

  3. 成员变量的声明格式:[修饰符] <类型> <变量名>[= 初始值];

    例:private String name = "Tom";

  4. 成员方法的声明格式:[修饰符] <返回值类型> <方法名> (形式参数列表){

    ​ 方法体;

    ​ }

    例:public class Test2 {
            private double x;
            public double y;
            public double radius(){
                return x*y;
            }
        }
    
3.1.3 变量初始化
  1. 默认值

    数据类型			 默认值
    boolean				false
    byte				(byte)0
    short				(short)0
    int					0
    long				0L
    float				0.0f
    double				0.0d
    char c				('\u0000')空
    Point point			null
    String				""
    
3.1.4 类的方法

方法就是一段用来完成特定功能的代码片段

  1. 方法定义:

    [修饰符1 修饰符2] <返回值类型> <方法名> ([形式参数列表]){

    方法体;

    }

  2. 参数传递:方法中所有类型的参数传递都是“值传递”

  3. 递归:直接与间接,调用自身

    例: static int f(int n){
        	if(n==1)
        		return 1;
        	else return n+f(n-1);
         }
    
  4. 方法重载:同名不同参的多个方法,方法重载是编译时多态性的表现

    构成条件:
    	1、不同的含义:形参类型、形参个数、形参顺序不同
    	2、只有返回值不同不构成方法的重载
    	3、只有形参的名称不同,不构成方法的重载
    例:void f(int x){}	void f(int x,int y){}	void f(double x){}
    
3.1.5 instanceof 运算符

用于操作对象实例,检查该对象是否是某一个特定的类型(类或接口)

例:people instanceof Person //对象people是否是Person类型

3.1.6 内存分析
Java虚拟机的内存可以分为三个区域:栈stack、堆heap、方法区method area
栈的特点:
	1、栈描述的是方法执行的内存模型。
	每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等);
	2、JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
	3、栈属于线程私有,不能实现线程间的共享!
	4、栈的存储特性是先进后出 ,后进先出”
	5、栈是由系统自动分配,速度快!栈是一个连续的内存空
堆的特点:
	1、堆用于存储创建好的对象和数组(数组也是对象)
	2、JVM只有一个堆;被所有线程共享
	3、堆是一个不连续的内存空间,分配灵活,速度慢!
方法区(又叫静态区)特点:
	1、JVM只有一个方法区,被所有线程共享!
	2、方法区实际也是堆,只是用于存储类、常量相关的信
	3、用来存放程序中永远是不变或唯一的内容。 (类信息[Class对象]、静态变量、字符串常量等)

无标题

3.2 创建对象

3.2.0 构造器
要点:
	1.通过new关键字调用!!
	2.构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类) ,
	不能在构造器里使用return返回某个值。
	3.如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数。
	如果定义则编译器不会自动添加!
	4.构造器的方法名必须与类名一致!
创建一个对象分 为如下四步:
    1.分配对象空间,并将对象成员变量初始化为0或空
    2.执行属性值的显式初始化
    3.执行构造方法
    4.返回对象的地址给相关的变量
3.2.1 构造方法
  1. 初始化一个对象的最终步骤是去调用这个对象的构造方法

  2. 构造方法法则:

    (1)方法名必须与类名相同

    (2)不要声明返回类型

    (3)不能被static 、final 、syschronized 、abstract 、native修饰

  3. 构造方法过程:

    (1)分配对象的内存空间

    (2)自动调用类的构造方法

    (3)对该对象 的内存空间进行初始化

    (4)为实例变量赋予合适的初始值

  4. 类的初始化时机和顺序:

3.2.2 默认构造方法
  1. 定义:无参数的构造方法称为默认构造方法

  2. 每个类必须至少定义一个构造方法,若无定义,则编译程序会提供一个构造方法

    例:<与类相同的访问控制符><方法名>(){

    ​ super(); //自动调用父类的默认构造方法

    ​ }

3.2.3 构造方法重载
    class Student{
        Student(){}
        Student(String n){}
        Students(String n,int i){}
    }

3.3 this 关键字

this表示创建好的对象

3.3.1 引用成员变量
    class a{
        int x;
        a(int x){
            this.x=x;
        }
    }
3.3.2 引用构造方法
	class a{
        int x;
        a(int x){
            this.x;
        }
        a(){
            this(0);	//引用其他的构造方法
        }
    }
3.3.3 代表自身对象
class c{
    c c1;
    public c(){
        c1=this;	//代表自身对象
    }
}
3.3.4 引用成员方法
class d{
    int x,y;
    d(int x){
        this.x=x;
        this.y=this.add();//引用成员方法
    }
    int add(){
        return (x+1);
    }
}

3.4 静态成员

static修饰符可以用来修饰类的成员变量、成员方法、代码块

3.4.1 静态变量
  1. 类的成员变量分类:静态变量、实例变量

    静态变量:又称类变量,是被 static 修饰的变量

    实例变量:没有被 static 修饰的变量

  2. 区别:

    静态变量只分配一次内存,类内部随意访问

    每创建一个实例,实例变量就会分配一次内存,创建实例后才能访问

3.4.2 静态方法
class Cit{
    private static int sa; //静态成员变量
    private int ia;  //实例成员变量
    static void fun(){}
    //静态方法
	static void sfun(){
      int i = 0;   
      fun();  
      ia = 20;      //错误,不能使用实例变量
      nfun();  //错误,不能调用实例方法
    }
    //实例方法
    void  nfun(){
      int i = 0;
      sa = 15;
      ia = 30;        //正确,可以使用实例变量
      fun();   //正确,可以调用静态方法
    }
} 
public class Dit{
      public static void main(String args[]){
          Cit c=new Cit();   //创建一个对象
          Cit.sfun();  		 //类名+静态方法   
          c.sfun();			 //对象+静态方法
          c.nfun();      	 //只能通过对象+实例方法
      }
}

总结:

  1. 静态方法只能访问静态成员(变量与方法);
  2. 实例方法能访问静态成员和实例成员。
  3. 静态方法不能以任何方式引用 this 和 super 关键字;
3.4.3 静态代码块
public class Code{
    static int i=0;
    public Code(){
        System.out.println("构造方法");
    }
    static{		//静态代码块
        System.out.println("第一个静态代码块");
    }
    static{
        System.out.println("第二个静态代码块");
    }
}

程序运行顺序:

  1. 对变量 i 进行初始化;
  2. 执行第一个静态代码块;
  3. 执行第二个静态代码块;
  4. 创建对象,执行构造方法;

3.5 内部类

内部类定义:将一个类定义在另一个类里。

分类:成员内部类、局部内部类、匿名内部类、静态内部类

内部类的优点:

  1. 每个内部类都能独立的继承一个接口的实现,外部类是否继承对内部类无影响。
  2. 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
  3. 方便编写事件驱动程序。
  4. 方便编写线程代码。
3.5.1 成员内部类
public class Test3 {
	public static void main(String[] args) {
		Outer o=new Outer();
		o.print();
	}
}
class Outer{	//外部类 
	private int a=1;
	class Inner{	//成员内部类
		private int a=10;
		public void print(){
			int a=100;
			System.out.println("局部变量:"+a);
			System.out.println("内部变量:"+this.a);
			System.out.println("外部变量:"+Outer.this.a);
		}
	}
	void print(){
		Inner i=new Inner();	//实例内部类
		i.print();
	}
}

总结:

  1. Outer o=new Outer(); Outer.Inner i=new Outer.Inner();

    <==>Outer.Inner i=new Outer().new Inner();

  2. 外部类中不能直接访问内部类的成员,必须通过内部类的实例去访问

  3. 实例内部类中不能定义静态成员,只能定义实例成员

3.5.2 匿名内部类
  1. 匿名内部类没有名字
  2. 必须继承一个父类或一个接口,但最多只能实现一个接口
  3. 无法定义构造方法,编译时自动生成,并自动调用父类的构造方法
  4. 被匿名内部类访问的局部变量必须是 final 变量,java8会自动将被访问的局部变量修饰为 final
  5. 匿名内部类可以访问外部类的所有成员
例1.
public class Outer{
	Outer(int v){
        System.out.println(”构造方法“);
	}
    void f(){
        System.out.println("from Outer");
    }
    public static void main(String[] args){
        int i=1;
        Outer a=new Outer(i){	//匿名内部类
        	{		//构造代码块
                System.out.println("匿名内部类");
        	}
            void f(){
                System.out.println("在匿名内部类里:"+i);
            }
        }
    }
}
3.5.2 静态内部类

定义:定义在另一个类里面的类,且被关键字 static 修饰的。

public class Test{
    public static void main(String[] args){
        Outer.Inner i=new Outer.Inner();//创建一个内部类对象
    }
}
class Outer{
    public Outer(){}
    static class Inner{	//静态内部类
        public Inner(){}
    }
}

总结:静态内部类能直接访问外部类的所有静态成员

3.5.3 局部内部类

定义:在一个方法体、构造器或初始化块中定义的一个类

public class Test{
    public static void main(String[] args){
        int i=2;
        public Outer getInner(){
            class Inner extends Outer{	//局部内部类
                Inner(){
                    System.out.println("局部内部类");
                }
            }
            return Inner;
        }
    }
}

注:局部内部类就像方法里的局部变量一样,不能有public/protected/private/static 修饰符

3.6 类的打包封装

3.6.1 包(package)机制
  1. 包定义及导入

    定义:package pkg1[.pkg2[.pkg3]];

    导包:import java.awt.AWTEvent; //导入AWTEvent类

    ​ import java.awt.*; //导入java.awt包的所有类,无法导入其子包

3.6.2 创建JAR文件

JDK中有一个jar命令,可以用来对大量的类(.class文件)进行压缩,存为JAR(.jar)文件

进入bin目录下:jar cvf org.jar org

c:创建新的JAR文件 v:生成详细报告并显示的标准输出

f:指定JAR文件名,通常这个参数是必需的 org.jar:生成的JAR文件名

验证打包是否成功:jar tf org.jar

3.7 代码块 {}

​ 分类:普通代码块、构造代码块、静态代码块、同步代码块

3.7.1 普通代码块

定义:在方法或语句中出现的 {} 就称为普通代码块

public class Test{
    public static void main(String[] args){		//方法
        {	//普通代码块
            int i=3;
            System.out.println("普通代码块一"+x);
        }
        int i=1;
        System.out.println("普通语句"+x);
        {	//普通代码块
        	System.out.println("普通代码块二");
        }
    }
}
结果:1.普通代码块一; 2.普通语句;	3.普通代码块二;

执行顺序:普通代码块与一般语句的执行顺序与他们的出现次序一致

3.7.2 构造代码块

定义:直接在类中定义且没有加 static 关键字的 {} 称为构造代码块

public class Test{
	{		//构造代码块
        System.out.println("第一个构造代码块");
	}
	public Test(int i){		//构造方法
        System.out.println("第"+i+“次调用构造方法”);
	}
	{		//构造代码块
        System.out.println("第二个构造代码块");
	}
	public static void main(String[] args){
		new Test(1);
		new Test(2);
		new Test(3);
	}
 }
 结果:
 	1.第一个构造代码块	2.第二个构造代码块	3.第1次调用构造方法
 	4.第一个构造代码块	5.第二个构造代码块	6.第1次调用构造方法
 	7.第一个构造代码块	8.第二个构造代码块	9.第1次调用构造方法

执行顺序:构造代码块在创建对象时被调用,每次创建对象都会被调用,

​ 构造代码块的执行次序先于类构造方法,若多个构造代码块则依次执行

3.7.3 静态代码块

定义:在java中使用 static 关键字声明的代码块{}

性质:1.静态代码块用于初始化类,为类的属性初始化。

​ 2.每个静态代码块只会执行一次

​ 3.在JVM加载类时会执行静态代码块,所以静态代码块优先于主方法执行

​ 4.若有多个静态代码块,则依次执行

public clas Test{
    public static String s1="静态属性";
    static{		//静态代码块
        System.out.println(s1);
        System.out.println("静态代码块1");
    }
    public String s2="非静态属性";
    {		//构造代码块
        System.out.println(s2);
        System.out.println("非静态代码块1");
    }
	public Test(){		//构造方法
        System.out.println("无参构造函数");
	}
	public static void main(String[] args){
        Test t=new Test();
	}
	{		//构造代码块
        System.out.println("非静态代码块2");
    }
    static{		//静态代码块
        System.out.println("静态代码块2");
    }
}
结果:1.静态属性	2.静态代码块1	3.静态代码块2
	 4.非静态属性	5.非静态代码块1	6.非静态代码块2
	 7.主方法		8.无参构造函数

执行顺序 :静态代码块--->非静态代码块--->主方法--->构造函数

3.7.4 同步代码块

定义:使用 synchronized(){} 包裹起来的代码块

在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则数据不一致。同步代码块需要写在方法中。

3.7.5 代码执行顺序
public class Test3 {
	public static void main(String[] args) {
		new Child();
	}
}
class Parent{ 
	static String name = "hello"; 
	{ 		//构造代码块
		System.out.println("父类构造代码块"); 
	} 
	static { //静态代码块
		System.out.println("父类静态代码块"); 
	} 
	public Parent(){ 	//构造方法
		System.out.println("父类构造方法"); 
	} 
} 
class Child extends Parent{ 
	static String childName = "hello"; 
	{ 		//构造代码块
		System.out.println("子类构造代码块"); 
	} 
	static { //静态代码块
		System.out.println("子类静态代码块"); 
	} 
	public Child(){ 	//构造方法
		System.out.println("子类构造方法"); 
	} 
}
结果:1.父类静态代码块	2.子类静态代码块	3.父类构造代码块
	4.父类构造方法	5.子类构造代码块	6.子类构造方法

执行顺序:1.执行父类静态的内容, 2.执行子类的静态的内容

​ 3.执行父类的非静态代码块 4.父类的构造方法

​ 5.执行子类的非静态代码块 6.执行子类的构造方法

总结:静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。

注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。

3.8 综合实例

//	类的定义
public class Myclass {
	public static void main(String[] args) {	//主方法,程序入口,静态,无返回值,形参是数组
		test t=new test(5);	//对象的创建、初始化
		t.show();	//调用public方法
		t.show2();  //默认权限方法
		System.out.println(t.f);  //常量
		System.out.println(test.z);	//静态成员变量
		System.out.println(test.fs);	//静态成员常量
		// ==  equals 的区别:==  比较地址	equals比较值
		String a=new String("a");
		String b=new String("a");
		System.out.println("a=b:"+(a==b));	//a=b:false
		System.out.println("a=b:"+a.equals(b));	//a=b:true
		t=null;	//对象销毁
	}
}
class test{	//类的定义
	int x;	//属性   默认为public
	public int y;	//实例变量
	protected int p1;
	private int p2;
	final int f=0;	//常量
	static int z=1;	//静态成员变量
	final static int fs=10;
	static void st_test(){	//不能有非静态成员变量和方法
		System.out.println((z+fs));
	}
	test(int x){	//构造方法
		this.x=x;	//this修饰的x是指本类的属性x
		this.y=x*x;
	}
	public void show(){	//方法定义
		System.out.println("x="+x);
	}
	public void show(int z){	//方法重载
		System.out.println("x="+(x+z));
	}
	void show2(){	//方法定义  默认是public
		System.out.println("y="+y);
	}
}
class sub_test extends test{	//子类定义  继承test只能继承一个父类
	int a;
	sub_test(int x,int y) {
		super(x);	//调用父类的构造方法
		a=y;
	}	
	void sub_show(){
		System.out.println(super.x);	//访问父类的属性
		super.show();	//访问父类的方法
	}
}
final class final_class{	//不可继承的类	final 不可改变的,最终的 
	final int x=0;	//值不可修改的  常量
	test z;	//两个类有关联关系
	final void show(){	//不可重载的方法
		System.out.println(x);
		sub_test y=new sub_test(5,6);//依赖关系:final_class依赖于sub_test
	}
}

第四章 Java面向对象编程

4.0 继承

4.0.1 定义

定义:用 extends 关键字来声明一个类继承另一个类

格式:<修饰符> class <子类名> extends <父类名> {……}

注:Java只支持继承

继承的概念:
	继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承
方法,使得子类具有父类相同的行为。简单来说,子类能吸收父类已有的属性和行为。除此之外,子类还可以
扩展自身功能。子类又被称为派生类,父类被称为超类。
4.0.2 基类与派生类

基类(父类),派生类(子类)

当创建了一个派生类的对象时,该类包含了一个基类的子对象。

对基类的子对象初始化:在派生类的构造器中调用基类的构造器,而基类的构造器具有执行基类初始化所需的所有能力和知识。

1.无参构造器时,java会自动在派生类的构造器中插入对基类的构造器的调用。

public class Humans {     
    Humans(){  
        System.out.println("我是人!");  
    }   
}  
public class Student extends Humans{  
    Student(){  
        System.out.println("我是学生!");  
    }  
}
public class test {  
    public static void main(String args[]){  
          new Student();  
    }  
} 
结果:1.我是人!	2.我是学生!
总结:总是基类的构造器先被初始化

构造器有参数时,必须使用关键字super现实编写调用基类构造器的代码,并且匹配适当的参数列表

public class Humans {    
    private String name;  
    Humans(String name){
        System.out.println("我是叫"+name+"的人");  
    }  
}
public class Student extends Humans{    
    private String name;  
    Student(String name){  
        super(name);  //调用父类的构造器
        System.out.println("我是学生!");  
    }    
} 
public class test {  
    public static void main(String args[]){  
        new Student("zhangsan");  
    }  
}
结果:1.我是叫zhangsan的人	2.我是学生
实例化派生类时,基类也会被实例化
4.0.3 方法的重写

子类继承了父类的所有的public和protected属性和方法,子类通过重写(覆盖)父类的方法,以实现自己想要的结果。

重写规则:1.被重写的方法必须具有 相同的方法名、 参数列表和相同的返回值类型

2.重写的方法不能比父类中被重写的方法拥有更严格的访问权限,(public>protected>default>private)

3.父类的静态方法不能被子类重写为非静态的方法。

4.方法重写只针对实例方法,父类的静态方法,子类只能隐藏、重载或继承。

    public class Father{
       public void speak(){
           System.out.println(Father);
        }
    }
    public class Son extends Father{
        public void speak(){	//重写父类的方法
            System.out.println("son");
       }
    }
4.0.4 重写与重载的区别

重写:父类与子类之间

重载:在同一个类中,只要求方法名相同,参数、修饰符,返回值可不同

重写:
	指的是子类对父类方法的重新定义,但是子类方法的参数列表和返回值类型,必须与父类方法一致!
所以可以简单的理解,重写就是子类对父类方法的核心进行重新定义。
重载:
	重载,指的是在一个类中有若干个方法名相同,但参数列表不同的情况,返回值可以相同也可以不同的
方法定义场景。也可以简单理解成,同一行为(方法)的不同表现形式。
4.0.5 继承的限制
1. 子类只能继承一个父类
2. private修饰不可直接访问,final修饰不可修改
3. 实例化子类时默认先调用父类的构造方法

4.1 封装

4.1.1 封装的优点
1.提高代码的安全性。
2.提高代码的复用性。
3. “高内聚” :封装细节,便于修改内部代码。提高可维护性。
4.“低糯合”: 简化外部调用。便于调用者使用。便于扩展和协作。
4.1.2 访问控制符
封装的实现一使用访问控制符
四种:private default protected public
private : 同一个类
default : 同一个类,同一个包中
protected : 同一个类,同一个包中,子类
public : 同一个类,同一个包中,子类,所有类
4.1.3 封装的使用
类的属性的处理:
	1、一般使用private访问权限;
	2、提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值
	与读取操作(注意: boolean变量的get方法是is开头!)。
	3、一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。

4.2 多态

多态:指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定

多态的要点:
    1.多态是方法的多态,不是属性的多态(多态与属性无关)。
    2.多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
    3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
多态的实现途径:
	多态的实现途径有三种:重写、重载、接口实现,虽然它们的实现方式不一样,
	但是核心都是:同一行为的不同表现形式。
	1. 重写
		重写,指的是子类对父类方法的重新定义,但是子类方法的参数列表和返回值类型,
	必须与父类方法一致!所以可以简单的理解,重写就是子类对父类方法的核心进行重新定义。
	2. 重载
		重载,指的是在一个类中有若干个方法名相同,但参数列表不同的情况,返回值可以相同
	也可以不同的方法定义场景。也可以简单理解成,同一行为(方法)的不同表现形式。
	3. 接口实现
		接口,是一种无法被实例化,但可以被实现的抽象类型,是抽象方法的集合,多用作定义方法集合,
	而方法的具体实现则交给继承接口的具体类来定义。所以,接口定义方法,方法的实现在继承接口的具体
	类中定义,也是对同一行为的不同表现形式。
4.2.1 对象的类型转换

转换分类:向上转换、向下转换

向上转换:对某个对象的引用转换为对其基类的引用

向下转换:对某个对象的引用转换为对其子类的引用

class A{}
class B extends A{}
A a;
B b1=new B();
a=b1;	//向上转型
B b2=(B)a;	//向下转型

向上转型的对象的特点:

  1. 向上转型对象不能操作子类新增的成员属性和方法。
  2. 向上转型对象可以操作子类继承或隐藏的成员变量,也可以使用子类继承或重写的方法。
  3. 向上转型对象操作子类继承或重写的方法时,就是通知对应子类对象去调用这些方法,如果子类重写了父类的方法,对象的向上转型对象调用这个方法时,一定是调用了这个重写的方法。

4.3 抽象类和接口

4.3.1 抽象方法与抽象类

抽象方法的定义:只声明而没有提供任何实现的方法

格式:abstract <返回类型> <方法名> ([形式参数列表]);

抽象类的意义:为子类提供统一的,规范的模板,子类必须实现相关的抽象方法。

包含抽象方法的类必须是抽象类
抽象类中可以有普通方法
抽象方法没有实现,但在子类中必须实现
抽象类要求子类必须定义具体实现,通过抽象类,我们可以做到严格限制子类的设计、使子类之间更加通用。
抽象类的要点:
1.有抽象方法的类只能定义为抽象类;
2.抽象类不能实例化,即不能用new来实例化抽象类;
3.抽象类可以包含属性、方法、构造方法,但构造方法不能用来new实例,只能被子类调用;
4.抽象类只能用来被继承;
5.抽象方法必须被子类实现。

抽象类:用 abstract 修饰的类称为抽象类

abstract class Animal{		//抽象类
    abstract void run();	//抽象方法
    void sleep(){
        System.out.println("animals sleep");	//非抽象方法
    }
}
class Dog  extends Animal{
    void run(){		//重写抽象方法
        System.out.println("dogs run fast");
    }
}
public class TestAbstract{
    public static void f(Animal a){	//向上转型
        a.sleep();
        a.run();
        Dog d;
        d=(Dog)a;	//向下转型
        d.sleep();
        d.run();
    }
    public static void main(String[] args){
        Dog d=new Dog();
        TestAbstract.f(d);
    }
}
结果:1.animals sleep	2.dogs run fast
	3.animals sleep	4.dogs run fast

抽象类的特性:

  1. 含有抽象方法的类必须声明为抽象类,抽象类必须被继承,抽象方法必须被实现
  2. 抽象类中不是所有的方法都是抽象方法,可以在抽象类中声明并实现方法
  3. 抽象类的子类必须实现父类所有抽象方法后才能实例化,否则子类也成为一个抽象类
  4. 抽象类不能实例化
4.3.2 接口概及特性

1.接口定义:一个抽象类中的所有方法都是抽象的,且数据成员都是 final 的常量

格式:[public]  interface  <接口名> [extends <一系列父亲接口>]{
    <常量或抽象方法的集合>
    <默认方法或类方法的集合>
}

定义接口的详细说明:
1.访问修饰符:只能是public或默认;
2.接口名:和类名采用相同命名机制;
3.extends:接口可以多继承;
4.常量:接口中的属性只能是常量,总是:public static final修饰,不写也是。
5.方法:接口中的方法只能是:public abstract,省略的话,也是public abstract。

要点:
1.子类通过implements来实现接口中的规范;
2.接口不能创建实例,但可以用于声明引用变量类型;
3.一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能用public的。
4.JDK1.7之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
5.JDK1.8后,接口中包含普通的静态方法。

实例:

public interface Car{	//定义接口
    public void drive();
}
class bc implements Car{
    public void drive(){
        System.out.println("奔驰");
    }
}
class bm implements Car{
    public void drive(){
        System.out.println("宝马");
    }
}

2.接口的使用:(implements)

  1. 接口必须有子类,但此时一个子类可使用 implements 实现多个接口
  2. 接口的子类必须重写接口的全部方法
  3. 接口的对象可以利用子类对象的向上转型进行实例化
 例:  
   public interface A {
        public void print();
    }
    interface B{
        public void get();
    }
    class X implements A,B{
        public void get() {
            System.out.println("接口B的抽象方法get");
        }
        public void print() {
            System.out.println("接口A的抽象方法print");
        }
    }
    public class Test4{
        public static void main(String[] args){
            X x=new X();	//创建子类对象
            
            A a=x;		//向上转型
            B b=x;		//向上转型
            a.print();
            b.get();
        }
	}

接口应用典例:

    //定义一个USB的标准
    public interface USB {
        public void install();
        public void work();
    }
    //在电脑上应用use接口
    class Computer{
        public void plugin(USB usb){
            usb.install();
            usb.work();
        }
    }
    //定义手机USB设备
    class Phone implements USB{
        public void install() {
            System.out.println("安装手机驱动程序");
        }
        public void work() {
            System.out.println("手机与电脑传输文件");
        }
    }
    //定义打印机USB设备
    class Print implements USB{
        public void install() {
            System.out.println("安装打印机驱动程序");
        }
        public void work() {
            System.out.println("正在打印文件");
        }
    }
    //定义MP3USB设备
    class MP3 implements USB{
        public void install() {
            System.out.println("安装MP3驱动程序");
        }
        public void work() {
            System.out.println("正在拷贝MP3");
        }
    }
    public class Test5 {
        public static void main(String[] args) {
            Computer com=new Computer();
            com.plugin(new Phone());
            com.plugin(new Print());
            com.plugin(new MP3());
        }
    }
1.安装手机驱动程序	2.手机与电脑传输文件	3.安装打印机驱动程序
4.正在打印文件	5.安装MP3驱动程序	6.正在拷贝MP3

4.4 终止继承 (final)

  1. 用 final 修饰的类不能被继承,没有子类
  2. 用 final 修饰的方法不能被子类的方法重写或隐藏
  3. 用 final 修饰的变量表示变量,只能被赋值一次
  4. 父类中用 private 修饰的方法不能被子类重写,所以 private 类型的方法默认是 final 类型
4.4.1 final 类

设计 final 类的条件:

  1. 不是专门为继承而设计的类,类本身的方法之间有复杂的调用关系,创建子类有可能错误地修改父类实现细节
  2. 出于安全原因,类的实现细节不允许有任何改动
  3. 在创建对象模型时,确信这个类不会被扩展

final class T{主体}

4.4.2 final 方法
  1. 如果父类要保留某些方法,使它们不能被子类继承,则声明为 final 类型
  2. 父类的某个方法是 private ,子类也无法继承该方法,也无法重写或隐藏该方法
  3. 例:final void run()
4.4.3 final 变量
  1. 一个变量被限定为 final ,其实是将它定义为一个符号常量

  2. 例 :final double PI = 3.14159

4.5 修饰符的适用范围

访问控制修饰符:default 、public 、protected、private

非访问控制修饰符:final、abstract、static、synchronized

4.5.1 访问控制修饰符

default(默认):在同一包内可见,使用对象:类、接口、变量、方法

private:在同一类内可见,使用对象:变量、方法、 :不能修饰类

public:对所有类可见,使用对象:类、接口、变量、方法

protected:对同一包的类和所有子类可见,使用对象:变量、方法 :不能修饰类

		private		default		protected		public
类内		√			√			√				√
包内					√			√				√	
子类								√				√
任何类											    √
4.5.2 继承规则

父类中哪些成员能够被子类继承?其遵循如下规则:
(1)构造方法是不能被继承的。
(2)private修饰的成员是不能被继承的。
(3)默认(无修饰符)的成员,可被继承也可不被继承。当子类与父类处于同一包中时,这些成员会被继承;而当子类与父类处于不同包时,这些成员不能被继承。
(4)protected与public修饰的成员总是能被继承的。
(5)能被子类访问的成员,才会被子类继承。
(6)能被子类继承的实例方法,才会被子类重写或重载。
(7)能被子类继承的static方法,才会被子类隐藏或重载。
(8)能被子类继承的数据成员,才会被子类隐藏。

4.6 垃圾回收机制

Garbage Collection
	(1)发现无用信息对象;
	(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。
算法:
	1. 引用计数法(Reference Counting Collector)
	2. tracing算法(Tracing Collector)
	3. compacting算法(Compacting Collector)
	4. copying算法(Coping Collector)
	5. generation算法(Generational Collector)
	6. adaptive算法(Adaptive Collector)
垃圾回收机制的意义
  Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,
它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再
有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。
ps:内存泄露是指该内存空间使用完毕之后未回收,在不涉及复杂数据结构的一般情况下,Java 的内存泄露
表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。

第五章 Java 常用类

5.1 Object 类

java.lang.Object 类是所有Java类的祖先,若一个类没有用 extends 关键字显式声明继承哪个类,则默认继承 Object 类。

Object 类的主要成员方法:equals()、hashCode() 和 toString()

5.1.1 equals() 方法

equals():比较两个变量的值是否相等

== :比较两个引用是否指向同一个对象

==:代表比较双方是否相同。
	1.如果是基本类型则表示值相等;
	2.如果是引用类型则表示地址相等即是同一个对象。
equals:
	对象内容相等
	经常重写equals方法
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Parent other = (Parent) obj;
		if (id != other.id)
			return false;
		return true;
	}
5.1.2 toString()方法

.toString() :返回对象的字符串表示格式

5.2 字符串类

5.2.1 String 类
  1. 提取字符:str.String(str1,i,j); //从i开始取j个字符
  2. 获取长度:str.length();
  3. 查找:
    1.str.indexOf(str1,i); 从i开始查找str1的首次出现位置
    2.str.lastIndexOf(str1,i); 从i开始往前查找str1的最后一次出现位置
    3.str.charAt(i); 获取i处的字符
  4. 截取字符串:
    str.substring(i); //从i开始截取
    str.substring(i,j); //截取[i,j)的字符串
  5. 判断子字符串是否存在:
    str.indexOf(str1); //存在输出索引,不存在输出-1
  6. 判断开始和结尾:
    str.startsWith(str1); //判断是否以str1字符串开头
    str.endsWith(str1); //判断是否以str1字符串结尾
  7. 去除空格:
    str.trim(); //删除前后的空格
  8. 替换字符:
    str.replace("\s",""); //替换字符串中的所有空格
    str.replace(oldstr,newold); //将oldstr字符串替换为newold
  9. 判断字符串是否相等:
    str1.equals(str2); //判断str1与str2字符串是否相等
    str1.equalsIgnoreCase(str2); //忽略大小是否相等
    str1.compareTo(str2); //按字典顺序比较字符串,str1在str2前面,结果为负,后面为正
  10. 字母大小写转换:
    str.toLowerCase(); //所有字符大写改为小写
    str.toUpperCase(); //所有字符小写改为大写
  11. 字符串分割:
    String[] str2 = str1.split(str3); //遇str3分割
    str1.split(str2,m); //分割成m个字符串 . 转义 \ \ .

类   型		转到String					从String转出
boolean		String.valueOf(boolean)		Boolean.parseBoolean(String)
byte		String.valueOf(int)			Byte.parseByte(String)
char		String.valueOf(char)		str.charAt(o)
short		String.valueOf(int)			Short.parseShort(String)
int			String.valueOf(int)			Integer.parseInt(String)
long		String.valueOf(long)		Long.parseLong(String)
float		String.valueOf(float)		Float.parseFloat(String)
double		String.valueOf(double)		Double.parseDoule(String)
5.2.2 StringBuffer类

​ new StringBuffer(32); //初始容量为32位
​ str1.append(str2); //将str2追加到str1后面
​ str.insert(i,char); //在下标为i的位置插入char
​ str.reverse(); //反转
​ str.delete(i,j); //删除从i到j之前的字符串
​ str.replace(i,j,str2); //将i与j之间的字符串替换为str2

5.2.3 StringBuilder类:

​ 单线程中使用
​ new StringBuffer(); //空的再追加

StringBuilder sb = new StringBuilder();
for(int i=0;i<26;i++) {
	sb.append((char)('a'+i)); // 添加
}
sb.reverse();	// 倒序
sb.setCharAt(3, '人');	// 替换
sb.insert(0, 2);	// 指定位置插入
sb.delete(4, 5);	// 删除指定区间[4,5)
System.out.println(sb);
5.2.3 三者关系
  1. 三者转换:
    StringBuffer sbf=new StringBuffer(str); //String->StringBuffer
    StringBuilder sbd=new StringBuilder(str); //String->StringBuilder
    str=sbf.toString(); //StringBuffer->String
    str=sbd.toString(); //StringBuilder->String
    StringBuilder sbd=new StringBuilder(sbf.toString()); //StringBuffer->StringBuilder
    StringBuffer sbf=new StringBuffer(sbd.toString()); //StringBuilder->StringBuffer
    String每操作一次新建一个对象,而StringBuffer,StringBuilder则操作自身
    2. 结论:
    类名 String StringBuilder StringBuffer
    对象类型 字符串常量 字符串变量 字符串常量
    线程安全性 不安全 不安全 安全
    执行效率 低 高 中
    2. 适用场景:
    1.操作少、数据少,用String;
    2.单线程,操作多,数据多,用StringBuilder;
    3.多线程,操作多,数据多,用StringBuffer;

5.3 包装类

5.3.1 基本包装类
基本数据类型		对应的包装类
boolean			Boolean
byte			Byte
char			Character
short			Short
int				Integer
long			Long
float			Float
double			Double
5.3.2 包装类的特点
  1. 所有的包装类都是 final类型,因此不能创建它们的子类

  2. 包装类是不可变类,包装类对象创建后,它所包含的基本数据类型就不能改变

  3. JDK提供了自动装箱和自动拆箱 机制

    Integer oi = 3; // 自动装箱,等价于Integer oi = Integer.valueOf(3);
    int i =oi // 自动拆箱,等价于int i = oi.intValue();
    int v = oi + 1; // 混合运算,等价于int v = Integer.valueOf(oi.intValue()+1);

  4. 可生成该对象基本值的typeValue 方法,例如:a.intValue()

  5. 每个包装类都可以用它对应的基本数据类型作参数来构造其实例,例:

    Boolean b = new Boolean(true); // 或: Boolean b = true;
    Byte bt = new Byte((byte)10); // 或: Byte b = 10;
    Character c = new Character('b'); // 或: Character c = 'b';
    Integer i = new Integer(1); // 或: Integer i =1;
    Float f = new Float(3.14f); // 或: Float f = 3.14 f;、

Integer:
	// 基本类型转换成包装对象
	Integer a = new Integer(3);
	Integer b = Integer.valueOf(30);
	
	// 把包装对象转成基本数据类型
	int c = b.intValue();
	double d = b.doubleValue();
	
	// 把字符串转成包装类对象
	Integer e = new Integer("999");
	Integer f = Integer.parseInt("9988");
	
	// 把包装类对象转成字符串
	String str = f.toString();
缓冲[-128,127]之间的数字
Integer in1 = -128;
Integer in2 = -128;
in1 == in2; // true  因为缓冲区间的数字都已经创建了对象,新建时只是调用该对象

5.4 Math与Random 类

5.4.1 Math 类
常用方法:
方法						描述
abs()					返回绝对值
floor(double d)			返回最大的double值,该值小于等于参数,并等于某个整数
pow(double a,double b)	返回第一个参数的第二个参数次幂的值
random()				返回带正号的double值,该值大于等于0.0且小于1.0
round(float a)			返回最接近参数的int
round(double a)			返回最接近参数的long
min(a,b)				返回两者中较小的一个
max(a,b)				返回两者中较大的一个
ceil(double a)			大于a的最小整数
floor(double a)			小于a的最大整数
sqrt()					平方根
acos,asin,atan,cos,sin,tan	三角函数
toDegrees(double angrad)	弧度->角度
toRadians(double angrad)	角度->弧度
5.4.2 Random 类

作用:生成伪随机数

构造器一:使用默认的种子(当前时间),每次运行结果是随机的;

构造器二:程序员显式传入一个long 型整数的种子,对于同一个种子每次运行生成的结果都一样

常用方法:
方法				描述
nextInt()			返回下一个int类型的伪随机数,其值在Integer.MIN_VALUE~Integer.MAX_VALUE(不含)之间
nextInt(int n)		返回下一个int类型的伪随机数,伪随机数的值大于或等于0并且小于参数n
nextLong()			返回下一个long类型的伪随机数,值在Long.MIN_VALUE~Long.MAX_VALUE之间
nextDouble()		返回下一个double类型的伪随机数,伪随机数的值大于或等于0,并且小于1.0
nextBoolean()		返回下一个boolean类型的伪随机数,伪随机数的值为true或false
import java.util.Random;

例:

public class TestRandom {
	public static void main(String[] args) {
		Random r1 = new Random();
		Random r2 = new Random(100);
		Random r3 = new Random(100);
		System.out.println(r1.nextInt());		// 产生任意大小的随机整数
		System.out.println(r1.nextBoolean());
		System.out.println(r1.nextDouble());
		System.out.println(r1.nextFloat());
		System.out.println(r1.nextLong());
		System.out.println(r1.nextInt(100));	// 产生0至100的随机整数
		System.out.println(r2.nextInt()); 
		System.out.println(r3.nextInt());
	}
}

// 生成[0,99]的随机数
Random r = new Random();
System.out.println(r.nextInt(100));

5.5 日期时间类

基准时间:1970年1月1日 00:00:00 度量单位:毫秒

当前时刻:long now = System.currentTimeMillis();  // 毫秒
Date d = new Date();	// Thu May 07 12:50:25 CST 2020
d.getTime();	// 1588827025083 毫秒
5.5.1 当前时间
long d1 = System.currentTimeMillis(); // 当前时间的毫秒数
for(){}
long d2 = System.currentTimeMillis();// 1588324051691
System.out.println(d2-d1);	// 计算时间差,单位为毫秒
import java.time.*;// 导入日期时间包
import java.time.format.*;	// 导入日期时间格式化包

import java.time.LocalDate;
LocalDate d=LocalDate.now();//获取当前日期 2019-03-25

import java.time.LocalTime;
LocalTime t=LocalTime.now();//获取当前时间 14:48:49.898

import java.time.LocalDateTime;
LocalDateTime dt=LocalDateTime.now();//获取当前日期时间 2019-03-25T14:51:32.427

import java.util.Date;
import java.text.SimpleDateFormat;
Date date = new Date();
SimpleDateFormat f=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String data = f.format(date);	//2020-01-06 20:57:44

5.5.2 DateFormat类和SimpleDateFormat

Dateformat类:
	把时间对象转化成指定格式的字符串。反之,把指定格式的字符串转化成时间对象。
	DateFormat是一个抽象类,一般用它的子类SimpleDateFormat类来实现。
	
	// 把时间对象按照“格式字符串指定的格式”转成相应的时间对象
	DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	String str = df.format(new Date(4000000));
	System.out.println(str);
		
	// 把字符串按照“格式字符串指定的格式”转成相应的时间对象
	DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	Date date = df2.parse("2019-12-25 11:15:29");
	System.out.println(date);
	

image-20200508093913407

5.5.3 Calender日历
// 传入时间可以产生相应的对象
Calendar calendar = new GregorianCalendar(2020,4,8,9,43,42);
int year = calendar.get(Calendar.YEAR); // 获取年份
int month = calendar.get(Calendar.MONTH); // 获取月份
int day = calendar.get(Calendar.DATE); // 获取日期,也可用DAY_OF_MONTH
int weekday = calendar.get(Calendar.DAY_OF_WEEK); // 获取星期
System.out.println(year);	// 2020
System.out.println(month);	// 4 (实际月份-1)
System.out.println(day); // 8
System.out.println(weekday);	// 6 (1-7:1为星期天)
System.out.println(calendar.getTime());	// Fri May 08 09:43:42 CST 2020

// 设置日期的相关元素
Calendar c2 = new GregorianCalendar();
c2.set(Calendar.YEAR, 8012);
System.out.println(c2);
		
// 日期的计算
Calendar c3 = new GregorianCalendar();
c3.add(Calendar.YEAR, -100);
System.out.println(c3.getTime());

// 日期对象和时间对象的转化
Date d4 = c3.getTime();	// 日期对象转为时间对象
Calendar c4 = new GregorianCalendar();
c4.setTime(new Date()); // 时间对象转化成日期对象

public static String printCalendar(Calendar c) {
	// 打印:1918年10月10日 11:23:45 周三
	int year = c.get(Calendar.YEAR);
	int month = c.get(Calendar.MONTH) + 1;
	int date = c.get(Calendar.DATE);
	int h = c.get(Calendar.HOUR);
	int m = c.get(Calendar.MINUTE);
	int s = c.get(Calendar.SECOND);
	int numdays = c.getActualMaximum(Calendar.DATE);	// 本月天数
	int weekday = c.get(Calendar.DAY_OF_WEEK) - 1;
	String[] str = {"日", "一", "二", "三", "四", "五", "六"};
	String data = year + "年" + month + "月" + date + "日" + h + ":" + m + ":" + s + " 周" + str[weekday];
	return data;
}
// 输入一个日期,打印当月日期
public static void printMonth(String str) throws ParseException {
	DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
	Date date = df.parse(str);	// 转成时间对象
	Calendar c = new GregorianCalendar();
	c.setTime(date);	// 转成日期对象
	int day = c.get(Calendar.DAY_OF_MONTH);
	System.out.println("日\t一\t二\t三\t四\t五\t六");
		
	c.set(Calendar.DAY_OF_MONTH, 1); // 将天数改为第一天
	
	// c.get(Calendar.DAY_OF_WEEK)-1 星期几
	for(int i=0;i<c.get(Calendar.DAY_OF_WEEK)-1;i++) {
		System.out.print("\t");
	}
		
	// 获取当月天数
	int num = c.getActualMaximum(Calendar.DATE);
		
	for(int i=1;i<=num;i++) {
		if(day == c.get(Calendar.DAY_OF_MONTH)) {
			System.out.print(c.get(Calendar.DAY_OF_MONTH)+"*\t");
		}else {
			System.out.print(c.get(Calendar.DAY_OF_MONTH)+"\t");
		}
		if(c.get(Calendar.DAY_OF_WEEK) == 7) {
			System.out.println();
		}
		c.add(Calendar.DAY_OF_MONTH, 1);
	}
}
5.5.2 格式化当前时间
/* 以多种不同格式显示当前日期时间	LocalDateTime dt=LocalDateTime.now(); */
//19-3-25 下午3:05
System.out.println(dt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle. SHORT,FormatStyle.SHORT)));

//2019-3-25 15:06:49
System.out.println(dt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle. MEDIUM, FormatStyle.MEDIUM)));

			2019年3月25日 下午03时07分33秒
System.out.println(dt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle. LONG,FormatStyle.LONG)));

//2019年3月25日 星期一
System.out.println(dt.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)));

//2019年3月25日 星期一 下午03时08分48秒
System.out.println(dt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle. FULL,FormatStyle.LONG)));

//2019-03-25-星期一- 03-09- 24
System.out.println(dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-EEEE- hh-mm- ss")));

//2019/03/25/星期一/ 03/09/ 56
System.out.println(dt.format(DateTimeFormatter.ofPattern("yyyy/MM/dd/EEEE/ hh/mm/ ss")));

//2015-10-25T07:35:27
LocalDateTime myDateTime = LocalDateTime.parse("2015-10-25T07:35:27", 
DateTimeFormatter.ISO_LOCAL_DATE_TIME);	
System.out.println(myDateTime);
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
LocalDateTime dt=LocalDateTime.now();//获取当前日期时间 2019-03-25T14:51:32.427
String date = dt.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
System.out.println(date);	// 2020年4月16日星期四
字符串转化为时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
	date = sdf.parse("1997-01-25");
} catch (ParseException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}
5.5.3 日期时间格式化规则
//常用的日期格式转换符

转换符 说明 示例
%te 一个月中的某一天(1-31) 2
%tb 指定语言环境的月份简称 Feb(英文)、二月(中文)
%tB 指定语言环境的月份全称 February(英文)、二月(中文)
%tA 指定语言环境的星期几全称 Monday(英文)、星期一(中文)
%ta 指定语言环境的星期几简称 Mon(英文)、星期一(中文)
%tc 包括全部日期和时间信息 星期二 三月 25 13:37:22 CST 2008
%tY 4位年份 2008
%tj 一年中的第几天(001-366) 085
%tm 月份 03
%td 一个月中的第几天(01-31) 02
%ty 2位年份 08

//时间格式化转换符

转换符 说明 示例
%tH 2位数字的24时制的小时(00-23) 14
%tI 2位数字的12时制的小时(01-12) 05
%tk 2位数字的24时制的小时(0-24) 5
%tl 2位数字的12时制的小时(0-12) 10
%tM 2位数字的分钟(00-59) 05
%tS 2位数字的秒数(00-60) 12
%tL 3位数字的毫秒数(000-999) 920
%tN 9位数字的微秒数(0-999999999) 062000000
%tp 指定语言环境下上午或下午标记 下午(中文)、pm(英文)
%tz 相对于GMT RFC 82 格式的数字时区偏移量 +0800
%tZ 时区缩写形式的字符串 CST
%ts 1970-01-01 00:00:00 至现在经过的秒数 1206426646
%tQ 1970-01-01 00:00:00至现在经过的毫秒数 1206426737453

		常见的日期和时间组合的格式

转换符 说明 示例
%tF "年-月-日"格式(4份年份) 2008-03-25
%tD "月/日/年"格式(2位年份) 03/25/08
%tc 全部日期和时间信息 星期二 三月 25 15:20:00 CST 2008
%tr "时:分:秒 PM(AM)"格式(12时制) 03:22:06 下年
%tT "时:分:秒"格式(24时制) 15:23:50
%tR "时:分"格式(24时制) 15:25

休眠:
Thread.sleep(1000*n) //休眠n秒

5.6 常规类型格式化

常规类型格式化:
	转换符			说明								示例
	%b %B			结果被格式化为布尔型				true
	%h %H			结果被格式化为散列码				A05A5198
	%s %S			结果被格式化为字符串类型			“abcd”
	%c %C			结果被格式化为字符类型				‘a’
	%d				结果被格式化为十进制整数			40
	%o				结果被格式化为八进制整数			11
	%x %X			结果被格式化为十六进制整数			4b1
	%e				结果被格式化为科学记数法的十进制	1.70000e+01
	%a				结果被格式化为带有效位数和指数十六进制	0X1.C00001P4
	%n				结果为特定于平台的行分隔符			
	%%				结果为字面值‘%’	

5.7 正则表达式

5.7.1 基础知识:
	元字符			正则表达式中的写法			意义
	.				.						任意一个字符
	\d				\\d						0-9的任何一个数字
	\D				\\D						任何一个非数字字符
	\s				\\s						空格 或 制表符 或 换行符 或 空白字符
	\S				\\S						非空白字符
	\w				\\w						任意一个 字母 或 数字 或 下划线
	\W				\\W						与\w相反
	
	\p{Lower}		\\p{Lower}				代表小写字母a-z
	\p{Upper}		\\p{Upper}				代表大写字母A-Z
	\p{ASCII}		\\p{ASCII}				ASCII字符
	\p{Alpha}		\\p{Alpha}				字母字符
	\p{Digit}		\\p{Digit}				十进制数字,即0-9
	\p{Alnum}		\\p{Alnum}				数字或字母字符
	\p{Punct}		\\p{Punct}				标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_'{|]~
	\p{Graph}		\\P{Graph}				可见字符:[\p{Alnum}\p{Punct}]
	\p{Print}		\\p{Print}				可打印字符:[\p{Graph}\x20]
	\p{Blank}		\\p{Blank}				空格或制表符:[\t]
	\p{Cntrl}		\\p{Cntrl}				控制字符:[\x00-\x1F\x7F]
	
	[^456]:代表4,5,6之外的任何字符
	[a-r]:代表a-r中的任何一个字母
	[a-zA-Z]:表示任意一个英文字母
	[a-e[g-z]]:代表a-e,或g-z中的任何一个字符)(并运算)
	[a-o&&[def]]:代表字母d,e,f(交运算)
	[a-d&&[^bc]]:代表字母a,d(差运算)
	
	//限定修饰符
	修饰符			意义			  示例
	?		   0次或1次			A? ---->{0,1}
	*			0次或多次			A* ---->{0,}
	+			一次或多次		   A+ ----->{1,}
	{n}			正好出现n次		   A{2}
	{n,}		至少出现n次		   A{3,}
	{n,m}		出现n-m次			A{2,6} 默认贪婪模式,{n,m}? 非贪婪模式
^	与字符串开始的地方匹配
$	与字符串结束的地方匹配
\b	匹配一个单词边界
IGNORECASE忽略大小写模式
	匹配时忽略大小写。
	一默认情况下,正则表达式是要区分大小写的。
SINGLELINE单行模式
	整个文本看作一个字符串,只有一个开头,一个结尾。
	使小数点"."可以匹配包含换行符( \n )在内的任意字符。
MULTILINE多行模式
	每行都是一个字符串,都有开头和结尾。
	在指定了MULTILINE之后,如果需要仅匹配字符串开始和结束位置,可以使用\A和\Z  (^->\A,$->\Z)

image-20200524001325954

5.7.2 常用表达式设计
1. 位置匹配:
    (1)(?=X):与这样的位置相匹配,其右部能匹配X。
    (2)(?!X):与这样的位置相匹配,其右部不能匹配X。
    (3)(?<=X):与这样的位置相匹配,其左部能匹配X。
    (4)(?<!X):与这样的位置相匹配,其左部不能匹配X。
    例如:going geting  --> [a-z]+(?=ing) ---> go,get
2. 常用正则:
(1)数字串:\d+,在Java中表示为“\\d+”。
(2)英文单词:[a-z A-Z]+,在Java中表示为“[a-z A-Z]+”。
(3)一个汉字:[\u4e00- \u9fa5],在Java中表示为“[\\u4e00-\\u9fa5]”。
(4)汉字串:[\u4e00- \u9fa5]+,在Java中表示为“[\\u4e00-\\u9fa5]+”。
(5)IP地址:(\d{1,3}\.){3}\d{1,3},在Java中表示为“(\\d{1,3}\\.){3}\\d{1,3}”。
(6)重复字符压缩:将字符串中连续重复的字符压缩成一个。例如,“aaabbbcccddd11122++***…33”压缩成“abcd12+*.3”。相关代码是:设String s = “”;;则String rs = s.replaceAll(“(.)(\\1)*”, “$1”);。
说明:(.)表示一号组,匹配任何一人字符,\1表示与一号组相同的字符,$1表示在replaceAll()方法中与一号组相匹配的那个子串。
(7)特定子串提取:例如,String s = "%...%CXLL=add1,31,123.12%CXLL=add2,32,124%CXLL=,33,125.12%LL=-121.11";
从中提取出%CXLL=add1,31,123,123.12,%CXLL=add2,32,124.12,%CXLL=,33,125.12。提取的正则式设计成“%CXLL=.*?(?=%)”,Java中表示为“%CXLL=.*?(?=%)”。
邮政编码:^[\u0391-\uFFE5]+$
QQ号码:^[1-9]\d{4,10}$ 
邮箱:^[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\.){1,3}[a-zA-z\-]{1,}$ 
手机号码:^1[3|4|5|8][0-9]\d{8}$
URL:^((http|https)://)?([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ 
18位身份证号:^(\d{6})(18|19|20)?(\d{2})([01]\d)([0123]\d)(\d{3})(\d|X|x)?$
实例:
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

List<String> list = new ArrayList<String>();	// 定义字符串数组
String str1 = "悠哉游哉1231232wfiehiffs12321323jfdkfs";	// 要匹配的字符串
String regex = "(\\d+)";	// 匹配原则
Pattern pattern = Pattern.compile(regex);

// 忽略大小写的写法
// Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
		
Matcher m = pattern.matcher(str1);	// 开始匹配
while(m.find()) {
    list.add(m.group());
}
System.ut.println(list);	// [1231232, 12321323]
5.7.3 正则基本用法
/**
 * 正则表达式基本用法
 * @author Administrator
 */
public class Demo01 {
	public static void main(String[] args) {
		String regex = "^1[23578]\\d{9}$"; // 正则表达式
		// 表达式对象
		Pattern p = Pattern.compile(regex);
		// 创建Mather对象
		Matcher m = p.matcher("15712033456");
		// 匹配
		// 法一: 尝试将整个字符序列与该模式匹配
		boolean temp = m.matches();
		// 法二: 该方法扫描输入的序列,查找与该模式匹面的下一个子序列
		//boolean temp2 = m.find();
		System.out.println(temp);
		
		// 获取匹配结果
		System.out.println(m.group());
	}
}
5.7.4 正则分组
/**
 * 正则表达式分组
 * @author Administrator
 */
public class Demo02 {
	public static void main(String[] args) {
		String regex = "(1[23578]\\d{9})([a-z]*)"; // 正则表达式
		// 表达式对象
		Pattern p = Pattern.compile(regex);
		// 创建Mather对象
		Matcher m = p.matcher("15712033456abadfddf");
		// 匹配
		while(m.find()) {
			System.out.println(m.group(1)); // 15712033456
			System.out.println(m.group(2)); // abadfddf
		}
	}
}
5.7.5 正则其他操作
/**
 * 正则表达式其他操作
 * @author Administrator
 */
public class Demo03 {
	public static void main(String[] args) {
		String regex = "ab"; // 正则表达式
		// 表达式对象
		Pattern p = Pattern.compile(regex);
		// 创建Mather对象
		Matcher m = p.matcher("ab1571qweq20334sfse56abbadfaddf");
		// 替换
		String newStr = m.replaceAll("#");
		System.out.println(newStr);
		
		// 分割
		String str1 = "abd1234fefise15434fjidsfe34jsfief";
		String[] arr = str1.split("\\d+");
		System.out.println(Arrays.toString(arr));
	}
}
5.7.6 爬虫
/**
 * 爬虫
 * @author Administrator
 */
public class WebTest01 {
	
	// 输入网址返回内容
	public static String getURLContent(String urlStr,String charset) {
		StringBuilder sb = new StringBuilder();
		try {
			URL url = new URL(urlStr);
			BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(),charset));
			String temp = "";
			while((temp = br.readLine()) != null) {
				sb.append(temp+"\n");
			}
			br.close();
			return sb.toString();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	// 输入字符串、正则,返回结果
	public static List<String> getMether(String destStr,String regeStr){
		List<String> methers = new ArrayList<String>();
		Pattern p = Pattern.compile(regeStr);
		Matcher m = p.matcher(destStr);
		while(m.find()) {
			methers.add(m.group(1));
		}
		return methers;
	}
	
	public static void main(String[] args) {
		String destStr = "http://www.163.com";
		String str = getURLContent(destStr,"gbk");
		
		String regeStr = "(https?.*?html)"; 
		List<String> methers = getMether(str, regeStr);
		for(String s:methers) {
			System.out.println(s);
		}
	}
}

5.8 数组实用类

5.8.1 复制数组

arraycopy() 方法:

格式:static void arraycopy(arr1,i,arr2,j,len)

5.8.2 数组排序

parallel() 方法:

格式:Arrays.paralleSort(arr); //功能等价于原来的srot()方法,从小到大排序

5.8.3 数组元素的查找

binarySearch() 方法:在已经排序好的数组中查找

格式:Arrays.binarySearch(arr,key) //返回索引值

5.9 File类
// 读取文件、生成文件、删除文件,修改文件
File f = new File("H:\\Java\\projects\\test\\a.txt");
System.out.println(f);	// H:\Java\projects\test\a.txt
		
// 修改文件名
f.renameTo(new File("H:\\\\Java\\\\projects\\\\test\\\\aa.txt"));
		
// 当前路径    --> H:\Java\projects\day003
System.out.println(System.getProperty("user.dir"));
		
File f2 = new File("gg.txt"); // 默认为当前路径下
f2.createNewFile();	// H:\Java\projects\day003下新建文件

File f2 = new File(System.getProperty("user.dir")); // 当前路径
		
System.out.println("File是否存在:" + f2.exists()); // true
System.out.println("File是否是目录:" + f2.isDirectory()); // true
System.out.println("File是否是文件:" + f2.isFile());  // false
System.out.println("File最后修改时间:" + new Date(f2.lastModified()));
// Fri May 08 16:00:34 CST 2020
System.out.println("File的大小:" + f2.length()); // 4096
System.out.println("File的文件名:" + f2.getName()); // day003
System.out.println("File的目录路径:" + f2.getPath()); // H:\Java\projects\day003
System.out.println("File的目录路径:" + f2.getAbsolutePath()); // 绝对路径
File f3 = new File("aa.txt");
f3.createNewFile();	// 创建文件
f3.delete();	// 删除文件
		
File f4 = new File("bb/cc");
boolean flag = f4.mkdir();	// 目录结构中有一个不存在,则不会创建整个目录树
boolean flag = f4.mkdirs(); // 可创建多层目录
// 打印目录树
static void printFile(File file) {
	System.out.println(file.getAbsolutePath()); // 绝对路径
	if(file.isDirectory()) {
		File[] files = file.listFiles(); // 当前目录下所有内容
		for(File f : files) {
			printFile(f);
		}
	}else {
		return ;
	}
}

第六章 Java 语言新特性

6.1 枚举

6.1.1 定义枚举类型

enum 表示枚举类型,ordinal()方法用来返回某个特定enum常量的索引,values()方法用来按照enum常量的声明顺序产生这些常量构成的数组

例:

public enum Season{		//定义一个枚举类
    SPRING,SUMMER,AUTUMN,WINTER;
}
Season season=Season.SUMMER;//创建枚举对象
for(Season s:Season.values()){	//将枚举类中常量组成数组
    System.out.println(s+" 索引:"+s.ordinal());//索引值
}
结果:1. SPRING,0	2.SUMMER,1	3.AUTUMN,2	4.WINTER,3
6.1.2 enum 构造方法

enum 的构造方法必须是private ,否则出错

public enum Orientation {
	EAST("shanghai"),SOUTH("shenzhen"),WEST("xian"),NORTH("beijing");
	private String city;
	Orientation(String city) {	// enum的构造方法,编译程序自动加上private修饰符
		this.city = city;
	}
	public String getCity() {	// enum的普通方法
		return city;
	}
	public static void main(String[] args) {
		Orientation or1 = Orientation.EAST;
		Orientation or2 = Orientation.SOUTH;
		Orientation or3 = Orientation.WEST;
		Orientation or4 = Orientation.NORTH;
		System.out.println(or1.getCity());//shanghai
		System.out.println(or2.getCity());//shenzhen
		System.out.println(or3.getCity());//xian
		System.out.println(or4.getCity());//beijing
	}
}

Season s1=Season.SUMMER <==>Season s2=Season.valueOf("SUMMER")

6.1.3 使用EnumMap

EnumMap是一种特殊的Map,EnumMap在内部表示为数组,将enum实例作为键来调用put()方法

enum Size {	//枚举类
    Small,Medium,Large;
}
public class EnumMapTest {	
	public static void main(String[] args) {
		/** 创建一个键类型为枚举Size的空枚举映射,键对象为枚举Size型,
		值对象为Integer型,参数为键类型的Class对象 */
		Map<Size, Integer> map = new EnumMap<Size, Integer>(Size.class);					
		map.put(Size.Small, 36);
		map.put(Size.Medium, 40);	
		map.put(Size.Large, 42);
		for (Size size : Size.values()) {
			System.out.println(map.get(size));
		}
		for (int value : map.values()) {
			System.out.println(value);
		}
	}
}

6.2 注解

6.2.1 注解定义

注解(Annotation),又称元数据,一种描述数据的数据。

Annotation 是一种应用于为、方法、参数、变量、构造器及包声明中的特殊修饰符。

注解优点:

  1. 提供了一种结构化的、、且具有类型检查能力的新途径,以编写更为健壮的代码
  2. 通过使用注解,程序员可以在不改变原程序逻辑的情况下为代码加入元数据
  3. 用于附属文件的自动生成,例如部署描述符或bean信息类
  4. 用于测试、日志、事务语义等代码 的生成
6.2.2 内置注解
  1. @Override:表示当前的方法将重写父类中的方法,如果不小心拼写错误或者签名对不上父类的方法,编译器就会发出错误提示
  2. @@Deprecated:表示某个程序元素(类、方法等)已过时。当其他程序使用已过时的类、方法时,编译器将会发出警告。
  3. @SuppressWarnings:关闭/抑制指定的编译器警告信息。
1、用来抑制编译时的警告信息
2、与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数值都是已经定义好了的,
我们选择性的使用就好了,参数如吓:
deprecation		使用了过时的类或方法的警告
unchecked		执行了未检查的转换时的警告,如使用集合时未指定泛型
fallthrough		当在switch语句使用时发生case穿透
path			在类路径、源文件路径等中有不存在路径的警告
serial			当在可序列化的类上缺少serialVersion JID定义时的警告
finally			任何finally子句不能完成时的警告
all				关于以上所有情况的警告

@SuppressWarnings("unchecked")
@SuppressWarnings(value={"unchecked", "deprecation"})
// 废除方法
@Deprecated
public static void test001() {
	System.out.println("test001");
}

// 抑制警告
@SuppressWarnings("all")
public static void test002() {
	List list = new ArrayList();
}
常见注解
/**
 * 注解
 */
public class Annotation01 {

	// Override 声明当前的方法式重写父类的方法
	@Override
	public String toString() {
		return super.toString();
	}
	
	
	public void test01() {
		// SuppressWarnings("unused") 抑制警告(有为使用的变量)
		@SuppressWarnings("unused")
		int i;
	}
	
	// 声明该方法已经时过时的方法,不建议使用
	@Deprecated
	publicvoid test02() {
	}
}
注解有作用范围(源码,编译期间,运行期间)
	源码期间有效:  String类之上@Author,@Since,@See 
 		作用:使用命令javadoc命令将当前的源码生成帮助文件, 可以识别String类上的相关的注解
	编译期间有效: @Override  @Deprecated  @Suppresswarning
 		作用:告诉编译器部分信息
	运行期间有效: @Test
  		作用:当我们再当前代码上以Junit方式运行时,Junit会运行方法上包含@Test注解的方法
6.2.3 自定义注解
格式:
  public @interface 注解名称{
     public  属性类型  属性名称1();
     public  属性类型  属性名称2()  default  默认值;
  }
  
自定义注解属性支持的类型:
   基本数据类型(4类8种),String,Class,Annotation(注解类型),枚举类型,
   以及以上类型的一维数组类型

注解作用: 配置作用
配置:开发的时候部分信息不希望写死在程序中,例如数据库的用户名和密码,可以将用户名和密码存放在.txt , 
.properties , .xml文件中,利用程序来读取文件中的内容

框架:一大堆工具类组合,目的:加速项目开发
后期的学习中,框架部分hibernate,spring,struts2很多信息需要配置,提供了2种形式配置
  (xml,注解)
  
什么时候用注解来做配置?
   如果配置信息不会发生的修改,例如servlet路径,建议使用注解的形式
   如果配置信息需要发生频繁的修改,例如数据库的用户名和密码信息,
      建议采用传统方法 (.txt , .properties , .xml)
1_自定义注解@MyTest
   通过元注解@Rentention @Target声明当前注解作用域以及目标对象,如果没有声明,
   在运行期间是无法获取到注解的信息
2_定义UserDao
  创建4个方法addUser delUser uptUser getUser ,在前三个方法上加载注解
3_定义类MyJunit  ,模拟JUnit
  将UserDao.class文件加载到内存,
  获取到字节码文件上所有的方法 
  遍历方法,判断每个方法上是否加载了@MyTest注解
  如果当前方法上设置@MyTest,执行当前的方法
// 自定义注解

// 定义注解的时候,需要通过元注解Retention
// 说明当前自定义注解的作用域(Class,Source,Runtime)

// @Target
// 自定义注解的作用范围

// 在运行期间有效
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnno01 {
	// 该注解定义属性, 默认值
	public long timeout() default -1;

}
public class UserDao {
	
	static {
		System.out.println("加载");
	}
	
	@MyAnno01
	public void add() {
		System.out.println("add");
	};
	
	@MyAnno01
	public void update() {
		System.out.println("update");
	}
	
	@MyAnno01
	public void findAll() {
		System.out.println("findAll");
	};
	
	public void findById() {
		
	};
}
// 运用
public class MyUtil {
	public static void main(String[] args) throws Exception {
		
		// 将UserDao.class 字节码加载到内存
		Class clazz = Class.forName("com.test01.UserDao");

		// 获取字节码对象的所有方法
		Method[] methods = clazz.getMethods();
		
		// 遍历所有的方法
		for (Method method : methods) {
			//System.out.println(method.getName());
			
			// 判断当前的方法上是否有注解
			boolean temp = method.isAnnotationPresent(MyAnno01.class);
			if(temp) {
				// 执行当前和方法
				method.invoke(new UserDao());
			}
		}
	}
}
//定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
	String value();
}

//标注注解
@MyAnno("TestTable")
public class MyAnnoClass1 {
	int show()
	{
		return 1;
	}
}

import java.lang.annotation.Annotation;
public class MyAnnoTest1 {
	public static void main(String[] args) {
		//类的正常功能
		MyAnnoClass1 m=new MyAnnoClass1();
		System.out.println(m.show());

		//类的注解方面的功能
		//解析注解
		  Annotation[] a=MyAnnoClass1.class.getAnnotations();
		  //得到类的注解
		  for(Annotation b:a)
			  System.out.println(b);		  		 		
	}
}
例2:
//定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
	String myvalue();
	String value();
	int number();
}
//标注注解
@MyAnno(myvalue = "myvalue_value", number = 10, value = "value1")
public class MyAnnoClass1 {	
	int show()
	{	return 1;	}
}
public class MyAnnoTest1 {
	public static void main(String[] args) {
		//类的正常功能
		MyAnnoClass1 m=new MyAnnoClass1();
		System.out.println(m.show());
		//类的注解方面的功能
		//解析注解
		  Annotation[] a=MyAnnoClass1.class.getAnnotations();
		  //得到类的注解
		  for(Annotation b:a)
			  System.out.println(b);		  	
			if (MyAnnoClass1.class.isAnnotationPresent(MyAnno.class))
			{
				MyAnno myanno=MyAnnoClass1.class.getAnnotation(MyAnno.class);
				System.out.println(myanno.value());
				System.out.println(myanno.myvalue());
				System.out.println(myanno.number());
			}
	}
}
6.2.4 元注解
  1. @Target:指定被修饰的注解能用于哪些程序元素类型。其参数ElementType表示所适用的元素类型,取值有:CONSTRUCTOR(构造器)、FIELD(成员变量)、LOCAL_VARIABLE(局部变量)、METHOD(方法)、PACKAGE(包)、PARAMETER(参数)、TYPE(类、接口、注解类型或枚举),以及ANNOTATION_TYPE(标准注解)。
  2. @Retention:指定被修饰注解的保存级别。其参数RetentionPolicy表示保存级别,取值有:SOURCE(只保留在源代码中,编译器直接丢弃这种注解)、CLASS(保存在class文件中,但运行时JVM不可获取注解信息)、RUNTIME(保存在class文件中且运行时JVM也可获取注解信息),默认保存级别为CLASS。
  3. @Documented:指定被修饰的注解将被javadoc工具提取成文档。
  4. @Inherited:指定被修饰的注解(这里假设为@Xxx)具有继承性,即如果某个类使用了@Xxx注解,则其子类也将自动被@Xxx修饰。
  5. @Repeatable:用于定义Java 8新增的重复注解。

6.3 lambda 表达式

lambda (->)表达式是一个匿名函数,它有签名和方法但是没有名字

左侧指定了lambda表达式需要的所有参数,右侧指定了lambda体

6.3.1 最简单实例
  1. 不需要参数,返回值为5 ()->5
  2. 接收一个参数,返回其2倍的值 (x)->2*x
  3. 接收2个参数,返回他们的差值 (x,y)->x-y
  4. 接受一个String对象,控制台打印出来 (String s)->System.out.println(s)
6.3.2 基本的Lambda 实例
List<Integer>list = Arrays.asList(1,2,3,4);
//第一种
for(int i=0;i<list.size();i++)
	system.out.println(list.get(i)+"   ");
//第二种
list.forEach(System.out.println);
//第三种
list.forEach(x)->System.out.println(x);
6.3.3 函数式接口

函数式接口是指仅定义了一个抽象方法的接口。

1.定义和使用函数式接口

public interface MyVal{
	double getValue();//唯一(只能有一个)的抽象方法
}
MyVal myVal;
myVal=()->274.83;
system.out.println(myVal.getValue());

第七章 容器与泛型

Java容器大致可分为:Set、List、Queue和Map这4种。

7.1 Collection与Iterator接口

1、Collection接口中的常用方法:

方    法										描    述
boolean add(E e)							向容器中添加一个元素
boolean addAll(Collection<? extends E> c)  	向容器中添加参数中所有的元素
void clear()  								移除容器中的所有元素
boolean contains(Object o) 					判定此 collection 是否包含指定的元素,有则返回 true
boolean containsAll(Collection<?> c)		判定此 collection 是否包含指定 collection 中的所有元素,是则返回 true
boolean isEmpty()							判定此容器是否为空,是则返回true
Iterator<E> iterator()  					返回一个Iterator<T>,用来遍历容器中的所有元素
boolean remove(Object o)   					如果容器中存在此元素,则删除它
Boolean removeAll(Collection<?> c)			删除容器c里包含的所有元素,成功删除一个(或以上)元素则返回true
boolean retainAll(Collection<?> c)  		将此Collection与参数c的交集存入此Collection中
int size()									返回此 collection 中的元素数目
Object[] toArray()							返回包含此 collection 中所有元素的数组

iterator()和toArray()方法都用于获得容器中所有元素,interator()返回一个Iterator对象,toArray()返回一个包含容器中所有元素的数组。

2、Iterator接口中声明了如下方法:、

1. boolean hasNext():判断容器中的元素是否遍历完毕,没有则返回true。
2. next():返回迭代的下一个元素。
3. void remove():从迭代器指向的Collection中移除上一次next()方法返回的元素。
4. void forEachRemaining(Consumer action):这是Java 8为Iterator新增的默认方法,

例:

public class UseCollection {
	public static void main(String[] args) {
		Collection<String> collection = new ArrayList<String>(Arrays.asList(
						"A", "B", "C", "D", "E"));		// 创建容器
		String[] strArray = {"F", "G", "H", "I", "J"};
		Iterator it;	// 声明Iterator接口
		collection.addAll(Arrays.asList(strArray));// 向容器中添加元素
		Collections.addAll(collection, "M", "N", "O", "P", "Q");
		System.out.println(collection);
		for(it = collection.iterator(); it.hasNext();)// 迭代容器中的每一个元素
			System.out.print(it.next() + " ");
		System.out.println();
		it = collection.iterator();
		it.forEachRemaining((obj)->System.out.print(obj + " "));// 用lambda表达式遍历元素
		System.out.println();
		collection.remove("A");		// 移除一个元素
		it = collection.iterator();
		it.forEachRemaining((obj)->System.out.print(obj + " "));
		System.out.println();
		Collection<String> part = new ArrayList<String>(Arrays.asList("B", "C", "D", "E"));
		collection.retainAll(part);	// 保存相同的元素
		for(String str:collection)
			System.out.print(str + " ");
		System.out.println();
		Object[] o = collection.toArray();// 返回一个数组
		System.out.println(Arrays.deepToString(o));
		collection.removeAll(collection);// 移除所有元素
		System.out.println(collection.size());
	}
}

7.2 Collections实用类

Collection<String> c = new ArrayList<>();
		
System.out.println(c.size()); // 大小
System.out.println(c.isEmpty()); // 是否为空
		
c.add("灵儿"); // 添加
c.add("盈盈");
		
System.out.println(c); // [灵儿, 盈盈]
System.out.println(c.size());
		
Object[] objs = c.toArray(); // 取出所有
System.out.println(Arrays.toString(objs));
		
c.remove("盈盈"); // 移除,删地址
System.out.println(c);
		
System.out.println(c.contains("盈盈")); // 是否包含某个元素
c.clear(); // 清空所有
boolean containsAll(Conllection c)   // 本容器是否包含c容器中的所有元素
boolean addAll(Conllection c)	// 将容器c中所有元素增加到本容器
boolean removeAll(Conllection c)  // 移除本容器和容器c中都包含的元素
boolean retainAll(Conllection c) // 取本容器和容器c中都包含的元素,移除非交集元素

List<String> list1 = new ArrayList<>();
list1.add("aa");
list1.add("bb");
list1.add("cc");
		
List<String> list2 = new ArrayList<>();
list2.add("cc");
list2.add("dd");
		
System.out.println("list1:"+list1);
System.out.println("list2:"+list2);
		
list1.addAll(list2);	// 将list2的所有元素添加到list
list1.removeAll(list2); // 将list1与list2的交集从list1中移除
list2.retainAll(list1); // 将list1与list2的交集之外的从list1中移除

System.out.println(list1);
System.out.println(list1.containsAll(list2)); // list1是否包含list2

1、List代表长度可变的线性表,Collections的以下方法适用于List类型。

1. copy(List<? super T> dest, List<? extends T> src):将所有元素从一个列表复制到另一个列表。
2. fill(List<? super T> list, T obj):使用指定元素替换指定列表中的所有元素。
3. nCopies(int n, T o):返回由指定对象的n个副本组成的不可修改的列表。
4. shuffle(List<?> list):使用默认随机源对指定列表进行置换。
5. sort(List<T> list):根据元素的自然顺序对指定列表按升序排序
void sort(list) // 对list容器内的元素排序,升序
void shuffle // 对list容器内的元素进行随机排序
void reverse(list) // 对list容器内的元素进行逆序排序
void fill(list,Object) // 用一个特定的对象重写整个list容器
int binarySearch(list,Object) // 对于顺序的list容器,采用折半查找特定的对象
定义:List list=new ArrayList();
添加:list.add("qqymidi");
	 list.add(100);
大小:list.size();
提取:(String)list.get(i);
	 String s=(String)list.get(10);//不强制转换会报错

7.3 Set (集合)

set对象是无序的,且不允许重复。

7.3.1 HashSet

如果用户定义的类覆盖了Object类的equals()方法但没有覆盖hashCode()方法,这会使HashSet无法正常工作。

Collection c=new HashSet();

c.add("efjdjksjkdk");

public class SxtHashSet {

	HashMap map;
	
	private static final Object PRESENT = new Object();
	
	public int size() {
		return map.size();
	}
	
	public void add(Object o) {
		map.put(o, PRESENT);
	}
	
	public SxtHashSet() {
		map = new HashMap();
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for(Object key:map.keySet()) {
			sb.append(key+",");
		}
		sb.setCharAt(sb.length()-1, ']');
		return sb.toString();
	}
	
	public static void main(String[] args) {
		SxtHashSet set = new SxtHashSet();
		set.add("aaa");
		set.add("bbb");
		set.add("ccc");
		
		System.out.println(set);
	}
}
7.3.2 TreeSet

TreeSet 类实现了SortedSet接口,能对容器中的对象进行排序。

Set s=new TreetSet();
s.add(new String("spring"));
s.add(new String("summer"));
System.out.println(s);
结果:[spring,summer]

1、自然排序:升序

x.compareTo(y):返回0,相等;返回正数,x>y;返回负数,x<y;

2.指定排序

public class TestTreeSet {
	public static void main(String[] args) {
		Set<Integer> set = new TreeSet<>();
		
		set.add(300);
		set.add(200);
		set.add(100);
		
		for(Integer m:set) {
			System.out.println(m);
		}
		
		Set<Emp2> set2 = new TreeSet<>();
		set2.add(new Emp2(100,"张三",800));
		set2.add(new Emp2(200,"李四",500));
		set2.add(new Emp2(300,"王五",600));
		
		for(Emp2 m:set2) {
			System.out.println(m);
		}
	}
}

class Emp2 implements Comparable<Emp2>{
	int id;
	String name;
	double salary;

	@Override
	public String toString() {
		return "Emp2 [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}

	public Emp2(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}

	@Override
	public int compareTo(Emp2 o) {
		if(this.salary > o.salary) {
			return 1;
		}else if(this.salary < o.salary) {
			return -1;
		}else {
			if(this.id > o.id ) {
				return 1;
			}else if(this.id < o.id ) {
				return -1;
			}else {
				return 0;
			}
		}
	}
}

7.4 List(列表)

List是有序、可重复的容器
有序:List中每个元素都有索引标记。可根据元素的索引标记访问元素,从而精确控制。
可重复:List允许重复的元素,满足e1.equals(e2)的元素可重复加入容器。
List接口常用的实现类有3个:ArrayList、LinkedList、Vector
7.4.1 ArrayList

ArrayList是线程不安全的,若要成为线程安全的,可用:

ArrayList底层是用数组实现存储的;
特点:查询效率高,增删效率低,线程不安全。

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
		
list.add(2, "E"); // 指定位置插入
		
list.remove(3); // 指定位置删除
		
list.set(3, "A");  // 替换
		
System.out.println(list.get(3)); // 取值
		
System.out.println(list.indexOf("A")); // 返回下标或-1
System.out.println(list.lastIndexOf("A")); // 从后往前找

​ List list = Collections.synchronizedList(new ArrayList());

List<String> list =new ArrayList<String>();//创建了一个ArrayList类型的容器
list.add(new String("rat"));
System.out.println("3:"+list.contains(str));//容器中含有horse对象,contains()方法返回true
System.out.println("7:"+list.remove(str2));//移除
List<String> sub =list.subList(1, 4);//用subList()方法得到从fromIndex()和toIndex()之间的部分视图
Collections.sort(sub);//排序
System.out.println("12:"+list);//使用指定的随机源对指定列表进行置换
copy.retainAll(sub1);//保留所有同时在copy与sub中的元素
copy.addAll(1,sub1);//将指定的Collection中的所有元素插入列表中的指定位置
Object[] obj=list.toArray();//返回一个数组,其中包含容器中的所有元素
String[] str4 =list.toArray(new String[0]);	//返回数组,返回运行时的类型与参数数组的类型完全相同
public class SxtArrayList2<E> {
	private Object[] elementDate;
	private int size;
	
	private static final int DEFALT_CAPACITY = 10;
	
	public SxtArrayList2() {
		elementDate = new Object[DEFALT_CAPACITY];
	}
	
	public SxtArrayList2(int capacity) {
		elementDate = new Object[capacity];
	}
	
	public void add(Object obj) {
		if(size >= elementDate.length) {
			Object[] newarray = new Object[elementDate.length + (elementDate.length>>1)];
			System.arraycopy(elementDate, 0, newarray, 0, elementDate.length);
			elementDate = newarray;
		}
		elementDate[size++] = obj;
	}
	
	public Object get(int index) {
		return elementDate[index];
	}
	
	public void remove( E element) {
		for(int i=0;i<size-1;i++) {
			if(element.equals(get(i))) {
				remove(i);
				return ;
			}
		}
	}
	
	public void remove(int index) {
		System.arraycopy(elementDate, index+1, elementDate, index, size-index-1);
		elementDate[size-1] = null;
		size--;
	}
	
	@Override
	public String toString() {
		
		return Arrays.toString(elementDate);
	}

	public static void main(String[] args) {
		SxtArrayList2<String> s1 = new SxtArrayList2<>(2);
		s1.add("aa");
		s1.add("bb");
		s1.add("cc");
		s1.add("dd");
		
		System.out.println(s1);
		
		System.out.println(s1.get(2));
	}
}
7.4.2 LinkedList

LinkedList在内部采用双向循环链表实现,插入与删除元素速度快,有addFirst(),addLast(),getFirst(),getLast(),removeFirst(),removeList()方法,线程不安全

public class SxtLinkedList1<E> {
	private Node first;
	private Node last;
	
	private int size;
	
	// 从后面插入元素
	public void add(E element) {
		Node node = new Node(element);
		
		if(first == null) {
			first = node;
			last = node;
		}else {
			node.previous = last;
			node.next = null;
			
			last.next = node;
			last = node;
		}
		size++;
	}
	
	// 指定位置插入元素
	public void add(int index, E element) {
		
		checkRange(index);
		
		Node temp = getNode(index);
		if(temp!=null) {
			Node up = temp.previous;
			Node node = new Node(element);
			
			if(up!=null) {
				up.next = node;
				node.previous = up;
				
				temp.previous = node;
				node.next = temp;
			}
			// 在头部插入元素
			if(index == 0) {
				node.next = first;
				first.previous = node;
				first = node;
			}
			size++;
		}
	}
	
	// 通过索引获取元素
	public E get(int index) {
		
		checkRange(index);
		
		Node temp = getNode(index);
		return temp!=null ? (E)temp.element : null;
	}
	
	// 通过索引删除元素
	public void remove(int index) {
		
		checkRange(index);
		
		Node temp = getNode(index);
		
		if(temp!=null) {
			Node up = temp.previous;
			Node down = temp.next;
			
			if(up != null) {
				up.next = down;
			}
			if(down != null) {
				down.previous = up;
			}
			// 被删除的是第一个元素
			if(index == 0) {
				first = down;
			}
			// 被删除的是最后一个元素
			if(index == size-1) {
				last = up;
			}
			size--;
		}
	}
	
	// 通过索引获取节点
	private Node getNode(int index) {
		
		checkRange(index);
		
		Node temp;
		if(index <= (size>>1)) {
			temp = first;
			for(int i=0;i<index; i++) {
				temp = temp.next;
			}
		}else {
			temp = last;
			for(int i=size-1;i>index; i--) {
				temp = temp.previous;
			}
		}
		return temp;
	}
	
	// 打印
	public String toString() {
		
		StringBuilder sb = new StringBuilder("[");
		Node temp = first;
		while(temp != null) {
			sb.append(temp.element+",");
			temp = temp.next;
		}
		sb.setCharAt(sb.length()-1,']');
		return sb.toString();
	}
	
	private void checkRange(int index) {
		if(index <0 || index > size-1) {
			throw new RuntimeException("索引数字不合法:" + index);
		}
	}
	
	public static void main(String[] args) {
		SxtLinkedList1<String> list1 = new SxtLinkedList1<>();
		list1.add("a");
		list1.add("b");
		list1.add("c");
		list1.add("d");
		System.out.println(list1);
		
		System.out.println(list1.get(1));
//		list1.remove(0);
		
		list1.add(3, "e");
		System.out.println(list1);
	}
}
7.4.3 Vector向量
Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。
如:
	public synchronized int indexOf(Object o,int index){}
7.4.4 栈的实现
public class MyStack {
	LinkedList linkedlist = new LinkedList();
	public void push(Object obj) {
	    linkedlist.addFirst(obj);
    }
    public Object pop() {	// 返回第一个元素,并删除栈中该元素
         return linkedlist.removeFirst();
    }
    public Object peek() {	// 返回栈中第一个元素
         return linkedlist.getFirst();
    }
    public boolean empty() {// 判断栈是否为空
    	 return linkedlist.isEmpty();
    }
    public String toString() { return linkedlist.toString(); }
    public static void main(String[] args) {
    	MyStack ms=new MyStack();
        ms.push("Spring");
	    ms.push("Summer");
 		ms.push("Autumn");
    	ms.push("Winter"); 
    	System.out.println(ms.pop());
    	System.out.println(ms.peek());
    	System.out.println(ms.pop());
    	System.out.println(ms.empty());
    	System.out.println(ms);
    }
}

7.5 Queue(队列)

7.5.1 LinkedList 实现
public class QueueTest {
	public static void printQ(Queue queue) {
		while(queue.peek() != null)
			System.out.print(queue.remove() + " ");	// 返回并移取队列的元素
			System.out.println();
	}
	public static void main(String[] args) {
		Queue<Integer> queue = new LinkedList<Integer>();
		Random rand = new Random(99);
		for(int i = 0; i < 10; i++)
			queue.offer(rand.nextInt(i + 10));
		printQ(queue);	// 输出队列元素
		Queue<Character> qc = new LinkedList<Character>();
		// 将字符串转换为字符数组,放入到队列中
		for(char c : "Brontosaurus".toCharArray())	
			qc.offer(c);
		printQ(qc);
	}
}

offer()方法在允许的情况下将一个元素插入队尾或返回false,

peek()和element()都是在不移除元素的情况下返回队首元素的,但peek()在队列为空时返回null,而element()则抛出异常。

poll()和remove()移除并返回队列的头元素,但poll()在队列为空时返回null,而remove()会抛出异常。

7.5.2 PriortyQueue

PriorityQueue类实现了优先级队列,调用它的offer()方法插入一个对象时,该对象会在队列中被排序,默认按自然顺序,但是可以通过提供Comparator来改变这个顺序。

PriorityQueue确保当调用peek()、poll()和remove()方法时,获取的总是队列中优先级最高的元素。

PriorityQueue队列不允许有null元素,另外它也不是线程安全的(对应的线程安全版本是java.util.concurrent.PriorityBlockingQueue)。

public class PriorityQueueTest{
	public static void main(String[] args) {
		PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>();
		Random rand = new Random(99);
		for(int i = 0; i < 10; i++)
			priorityQueue.offer(rand.nextInt(i + 10));// 将一个随机数插入到随机队列中
		QueueTest.printQ(priorityQueue);// 输出队列的元素
		List<Integer> list = Arrays.asList(20, 18, 16,14, 12, 9, 6, 1, 1, 2, 6, 9, 14, 16, 18, 12, 20);
		priorityQueue = new PriorityQueue<Integer>(list);
		QueueTest.printQ(priorityQueue);
		priorityQueue = new PriorityQueue<Integer>(list.size(), Collections.reverseOrder());// 逆序
		priorityQueue.addAll(list);	// 向队列中加入元素
		QueueTest.printQ(priorityQueue);
		String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
		List<String> strings = Arrays.asList(fact.split(""));// 分裂字符串并返回字符数组
		PriorityQueue<String> stringPQ = new PriorityQueue<String>(strings);
		QueueTest.printQ(stringPQ);
		stringPQ = new PriorityQueue<String>(strings.size(), Collections.reverseOrder());
		stringPQ.addAll(strings);
		QueueTest.printQ(stringPQ);
	}
}
class QueueTest {
	// 输出队列的元素
	public static void printQ(Queue queue) {
		while (queue.peek() != null)
			System.out.print(queue.remove() + " ");	// 获取并移除队列的头元素
		System.out.println();
	}
}
7.5.3 双向队列
class ByQueue<T> {
	private LinkedList<T> queue = new LinkedList<T>();
	public void addFirst(T e) {	// 向队列的头部加入元素
		queue.addFirst(e);
	}
	public void addLast(T e) {// 向队列的尾部加入元素
		queue.addLast(e);
	}
	public T getFirst() {// 返回队列的头部元素
		return queue.getFirst();
	}
	public T getLast() {// 返回队列的尾部元素
		return queue.getLast();
	}
	public T removeFirst() {// 移去并返回队列的头元素
		return queue.removeFirst();
	}
	public T removeLast() {	// 移去并返回队列的尾元素
		return queue.removeLast();
	}
	public int size() {	// 返回队列的元素数
		return queue.size();
	}
	public String toString() {
		return queue.toString();
	}
}
public class ByQueueTest {
	static void fillElement(ByQueue<Integer> queue) {
		for (int i = 1; i < 7; i++)
			queue.addFirst(i);// 在队列的头部加入元素
		for (int i = 20; i < 25; i++)
			queue.addLast(i);// 在队列的尾部加入元素
	}
	public static void main(String[] args) {
		ByQueue<Integer> q = new ByQueue<Integer>();
		fillElement(q);
		System.out.print(q);
		System.out.println();
		while (q.size() != 0)
			System.out.print(q.removeFirst() + " ");// 在队列的头部移除元素
		System.out.println();
		fillElement(q);
		while (q.size() != 0)
			System.out.print(q.removeLast() + " ");	// 在队列的尾部移除元素
	}
}

7.6 Map(映射)

Map接口常用的方法:
Object put(Object key,Objcet value)  // 存放键值对
Object get(Object key)	// 通过键查找值
Object remove(Object key)	// 删除键值对
boolean containsKey(Object key)	// 通过键判断键值对是否存在
boolean containsValue(Object key)	// 通过值判断键对对是否存在
int size()	// 包含键值对的数量
boolean isEmoty()	// Map是否为空
void putAll(Mat t)	// 将t的所有键值对存放到本map对象
void clear()	// 清空本map对象所有键值对
7.6.1 HashMap

HashMap是基于哈希表的Map接口实现,它提供所有的可选的映射操作并允许使用null值和null键。

HashMap是基于HashCode的,若想正确使用它就需要重写hashCode()和equals()方法。HashMap不是线程安全的,若要线程安全,可用:Map m = Collections.synchronizedMap(new HashMap());

例一:
public class TestMap1 {
	public static void main(String[] args) {
		Map<Integer, String> m1 = new HashMap<>();
		
		m1.put(1, "one");
		m1.put(2, "two");
		m1.put(3, "three");
		
		System.out.println(m1.get(1)); // 通过键取值
		System.out.println(m1.size()); // 数量
		System.out.println(m1.isEmpty()); // 是否为空
		System.out.println(m1.containsKey(2)); // 键对应的键值对是否存在
		System.out.println(m1.containsValue("three")); // 值对应的键值对是否存在
		
		Map<Integer, String> m2 = new HashMap<>();
		m2.put(4, "四");
		m2.put(5, "五");
		
		m1.putAll(m2);	// 将m2的键值对复制到m1
		System.out.println(m1);	
		
		m1.put(3, "三");	 // 键如果重复(equals),后面的覆盖前面的
	}
}
例二:
public class TestMap2 {
	public static void main(String[] args) {
		Employee e1 = new Employee(1001, "张三", 10000);
		Employee e2 = new Employee(1002, "李四", 8000);
		Employee e3 = new Employee(1003, "王五", 15000);
		
		Map<Integer, Employee> m1 = new HashMap<>();
		m1.put(e1.getId(), e1);
		m1.put(e2.getId(), e2);
		m1.put(e3.getId(), e3);
		
		Employee emp = m1.get(1001);
		System.out.println(emp.toString());
		System.out.println(m1);
	}
}
// 雇员信息
class Employee{
	private int id;
	private String ename;
	private double salary;
}
例三
public class TestMap3<K, V> {
	
	Node2[] table; // 位桶数组
	int size; // 存放的键值对的个数
	
	
	public static void main(String[] args) {
		TestMap3<String, String> map = new TestMap3<>();
		map.put("班级", "17软件工程2班");
		map.put("班级", "17软件工程");
		map.put("姓名", "吴金元");
		map.put("姓名", "风清扬");
		map.put("性别", "男");
		
		System.out.println(map);
		System.out.println(map.get("姓名"));
		
	}
	
	public TestMap3() {
		table = new Node2[16]; // 长度一般定义为2的整数幂
	}
	
	// 打印HashMap中所有的键值对
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		// 遍历数组
		for(int i=0; i<table.length; i++) {
			Node2 temp = table[i];
			// 遍历列表
			while(temp != null) {
				sb.append(temp.key + "=" + temp.value + ",");
				temp = temp.next;
			}
		}
		sb.setCharAt(sb.length()-1, ']');
		return sb.toString();
	}
	
	// 获取键对应的值
	public V get(K key) {
		
		int hash = myHash(key.hashCode(), table.length);
		Node2 temp = table[hash];
		while(temp !=null) {
			if(temp.key.equals(key)) {
				return (V)temp.value;
			}
			temp = temp.next;
		}
		return null;
	}
	
	// 向Map中加入数据或修改
	public void put(K key, V value) {
		// 定义节点对象
		Node2 node = new Node2();
		node.hash = myHash(key.hashCode(), table.length);
		node.key = key;
		node.value = value;
		node.next = null;
		
		Node2 temp = table[node.hash];
		
		if(temp == null) {
			// 此处数组元素为空,则直接将新节点放入
			table[node.hash] = node;
			size++;
		}else {
			// 此处数组元素不为空,则遍历链表
			while(temp.next != null) {
				
				// 判断键是否重复
				if(key.equals(temp.key)) {
					temp.value = value;
					return ;
				}else {
					temp = temp.next;
				}
			}
			// 新增节点是否与最后一个节点的键重复
			if(key.equals(temp.key)) {
				temp.value = value;
			}else {
				temp.next = node;
				size++;
			}
		}
	}
	
	// 求hash值
	public int myHash(int v, int length) {
		return v&(length-1);
	}
}
7.6.2 HashMap底层原理
底层采用了哈希表,哈希表的基本结构就是“数值+链表”。
public class AlphaDegree {
	public static void main(String[] args) {
		String s = "afasdfassgdfgdfgdfgsdfg";
		char[] num = s.toCharArray();// 将字符串转换为char数组
		int i = num.length - 1;
		HashMap map = new HashMap();// 创建一个HashMap对象
		map.put(num[0], 1);
		for(int k = 1; k <= i; k++) {
			if(map.containsKey(num[k])) {// 如果在容器中已存在该字母,字母数加1
				Integer j = (Integer)map.get(num[k]);
				map.put(num[k], ++j);
			}
			else
				map.put(num[k], 1);	// 如果不存在,将该字母加入到容器中
		}
		// 使用Java 8为Map新增的forEach()方法来遍历Map集合
		map.forEach((key, value)->System.out.print(key + "=" + value + "  "));
	}
}
7.6.2 TreeMap
使用SortedMap接口可以确保键处于排序状态,该接口提供如下方法。
1. Comparator compartor():返回当前Map使用的Comparator,若返回null表示以自然方式排序。
2. firstKey():返回Map中的第一个键。
3. lastKey():返回Map中的最后一个键。
4. SortedMap subMap(fromKey, toKey):生成此Map的子集,由键小于toKey的所有键值对组成。
5. SortedMap tailMap(fromKey):生成此Map的子集,由键大于或等于toKey的所有键值对组成。
public class Statistic {
	public static void main(String[] args) {
		String s = "c789yz45!786*+56abc123456789";
		TreeMap map = new TreeMap();
		char[] arr = s.toCharArray();// 将字符串转换为字符数组
		int i = arr.length;	// 获取字符数组的长度
		int k = 0;
		int p = 0;	// 记下数字字符的开始位置
		int q = 0;	// 记下数字字符的结束位置
		boolean b = false;
		for (int j = 0; j < i; j++) {
			k = arr[j];
			if (47 < k && k < 58) {		
				if (b == false) {
					p = j;	// 是数字字符的开始位置,记下该位置
					b = true;		
				}
				q = j;
				System.out.print(arr[j]);
				if (q != p) {
					map.put(p, q);	// 将开始位置和结束位置放入到Map容器中
				}
			}else{
				b= false;
			}
		}
		System.out.println(map);
		Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();// 返回一个迭代器
		int []array= new int[10];
		int a = 0;
		while(it.hasNext()) {
			Map.Entry entry = it.next();
			int u = (Integer)entry.getKey();// 获取键对象
			int v = (Integer)entry.getValue()+1;	// 获取值对象
			if((v-u)<=3) {
				String s1 = s.substring(u,v);
				array[a++]=Integer.parseInt(s1);
			}else {
				String s2 = s.substring(u,v);
				int g = s2.length();
				int h =0;
				while(h+4<g) {
					String s3 = s2.substring(h, h+4);
					array[a++] = Integer.parseInt(s3);
					h = h+4;
				}
				String s4 = s2.substring(h, g);	// 取出最后不足4个的数字字符
				array[a++] = Integer.parseInt(s4);
			}
		}
		for(int c = 0; c<a; c++) {
			System.out.println(array[c]);
		}
	}
}
public class TestTreeMap {
	public static void main(String[] args) {
		Map<Integer, String> treemap = new TreeMap<>();
		treemap.put(12, "aa");
		treemap.put(4, "bb");
		treemap.put(20, "cc");
		
		// 按照key递增的方式排序
		for(Integer key:treemap.keySet()) {
			System.out.println(key+":"+treemap.get(key));
		}
		
		
		Map<Emp, String> treemap2 = new TreeMap<>();
		treemap2.put(new Emp(100, "张三", 10000), "张三是一个土豪");
		treemap2.put(new Emp(101, "李四", 8000), "李四是一个老板");
		treemap2.put(new Emp(102, "王五", 5000), "王五是一个打工仔");
		treemap2.put(new Emp(103, "赵六", 5000), "赵六是一个穷逼");
		
		for(Emp key:treemap2.keySet()) {
			System.out.println(key+":"+treemap2.get(key));
		}
	}
}

class Emp implements Comparable<Emp>{
	int id;
	String name;
	double salary;
	
	@Override
	public String toString() {
		return "Emp [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}

	public Emp(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	
	@Override
	public int compareTo(Emp o) { // 负数:小于,0:等于,整数:大于
		if(this.salary > o.salary) {
			return 1;
		}else if(this.salary < o.salary) {
			return -1;
		}else {
			if(this.id > o.id ) {
				return 1;
			}else if(this.id < o.id ) {
				return -1;
			}else {
				return 0;
			}
		}
	}
}
7.7.5 Map与Table区别
1、HashMap:线程不安全,效率高,允许key或value为null;
2、HashTable:线程安全,效率低,不允许key或value为null。

7.7 泛型

泛型的本质:就是‘数据类型的参数化’。我们可以把‘泛型’理解为数据类型的一个占位符(形式参数),
即告诉编译器,在调用泛型时必须传入实际类型。
7.7.1 泛型
List<String>mlist1=new ArrayList<String>()
mlist1.add("aaa");
mlist1.add("bbb");
String x=mlist1.get(1);
7.7.2 泛型集合
List<student> list=new ArrayList<student>(5);
student s1=new student();
s1.xm="zhang";
s1.xh="001"
s1.xb="Male";
list.add(s1);
student s2=new student();
s2=list.get(0);
System.out.println(s2.xh+s2.xm+s2.xb);
class student{
    String xh,xm,xb;
}
7.7.3 泛型类
public class MyGeneric{
    public static void main(String[] args){
        Box<String,Integer> b1=new Box<String,Integer>("张山",20);
        b1.setValue(21);
        String x=b1.getKey();
        System.out.println(x+b1.getValue());
    }
}
class Box<K,V>{//泛型类的定义
    private K key;
    private V value;
    public Box(K k,V v){
        key=k;
        value=v;
    }
    public K getKey(){return key;}
    public void setKey(K key){this.key=key;}
    public V getValue(){return value;}
    public void setValue(V value){this.value=value;}
}
7.7.4 泛型接口
public class MyInterface{
    public static void main(String[] args){
        MyCal m=new MyCal();
        System.out.println(m.add(2,3));
    }
}
interface Cal<T>{	//泛型接口的定义
    public T and(T a,T b);
}
class Mycal implements Cal<Integer>{
    public Integer and (Integer a,Integer b){return a+b;}
}

7.8 迭代器

public class TestIterator {
	public static void main(String[] args) {
//		testIteratorList();
//		testIteratorSet();
		testIteratorMap();
	}
	
	// List
	public static void testIteratorList() {
		List<String> list = new ArrayList<>();
		list.add("aa");
		list.add("bb");
		list.add("cc");
		
		// 使用迭代器遍历list
		for(Iterator<String> iter = list.iterator();iter.hasNext();) {
			String temp = iter.next();
			System.out.println(temp);
		}
	}
	
	// Set
	public static void testIteratorSet() {
		Set<String> set = new HashSet<>();
		set.add("aa");
		set.add("bb");
		set.add("cc");

		// 使用迭代器遍历Set
		for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
			String temp = iter.next();
			System.out.println(temp);
		}
	}
	
	// Map
	public static void testIteratorMap() {
		Map<Integer, String> map1 = new HashMap<>();
		map1.put(100, "aa");
		map1.put(200, "bb");
		map1.put(300, "cc");
		
		// 通过迭代器遍历Map
		Set<Entry<Integer,String>> ss = map1.entrySet(); // 遍历键值对集合
		for(Iterator<Entry<Integer,String>> iter = ss.iterator();iter.hasNext();) {
			Entry<Integer,String> temp = iter.next();
			System.out.println(temp.getKey()+":"+temp.getValue());
		}
		
		System.out.println("----------");
		
		// 方法二
		Set<Integer> keySet = map1.keySet(); // 遍历键
		for(Iterator<Integer> iter = keySet.iterator();iter.hasNext();) {
			Integer key = iter.next();
			System.out.println(key+":"+map1.get(key));
		}
	}
}

7.9 表格数据存储

public class TestStoreDate {
	public static void main(String[] args) {
		
		Map<String,Object> map = new HashMap<>();
		map.put("id", 1001);
		map.put("姓名", "张三");
		map.put("薪水", 8000);
		map.put("入职日期", "2018.5.15");
		
		Map<String,Object> map2 = new HashMap<>();
		map2.put("id", 1002);
		map2.put("姓名", "李四");
		map2.put("薪水", 6000);
		map2.put("入职日期", "2018.5.15");
		
		Map<String,Object> map3 = new HashMap<>();
		map3.put("id", 1002);
		map3.put("姓名", "李四");
		map3.put("薪水", 6000);
		map3.put("入职日期", "2018.5.15");
		
		List<Map<String,Object>> table = new ArrayList<>();
		table.add(map);
		table.add(map2);
		table.add(map3);
		
		for(Map<String,Object> row:table) {
			Set<String> keyset = row.keySet();
			for(String key:keyset) {
				System.out.print(key+":"+row.get(key)+"\t");
			}
			System.out.println();
		}
	}
}
public class TestStoreData {
	public static void main(String[] args) {
		User user1 = new User(1001, "张三", 20000, "2018.5.5");
		User user2 = new User(1002, "李四", 30000, "2018.5.6");
		User user3 = new User(1003, "王五", 40000, "2018.5.7");
		
		List<User> list = new ArrayList<>();
		list.add(user1);
		list.add(user2);
		list.add(user3);
		
		for(User u:list) {
			System.out.println(u);
		}
	}
}

class User{
	private int id;
	private String name;
	private double slary;
	private String hiredata;
	public User(int id, String name, double slary, String hiredata) {
		super();
		this.id = id;
		this.name = name;
		this.slary = slary;
		this.hiredata = hiredata;
	}
	@Override
	public String toString() {
		return "[id=" + id + ", name=" + name + ", slary=" + slary + ", hiredata=" + hiredata + "]";
	}
}

第八章 异常处理

8.1 异常概述

Throwable类是Java异常类体系中的根类,它有两个子类:Error和Exception。

1.Error类:系统错误类,代表程序运行时Java系统内部错误,这种错误程序员一般不用关心,因它们通常由硬件或操作系统所引发,一旦发生,程序员除了告知用户并关闭程序之外,别无他法。

2.Exception类:异常类,该类(及其子类)的对象表示的错误往往是因算法考虑不周或由于编程过程中疏忽大意、未考虑到某些特殊情形而引起的,值得程序员认真对待并尽可能地加以处理。

异常分类:
1.用户输入了非法数据
2.要打开的文件不存在
3.网络通信时连接中断,或者JVM内存溢出

public class Test1 {
	public static void main(String[] args) {
		int[] a={1,2,3,4,5};
		int i;
		try{	//可能会产生异常的代码
			for(i=0;i<a.length;i++)
				System.out.println(a[i]);
			System.out.println(a[i]);//i值以超出数组边界
		}
		catch(ArrayIndexOutOfBoundsException e){	//处理异常的代码
			System.out.println("异常原因:"+e);
		}finall{……}		//释放资源的程序代码
	}
}

8.1.1 RuntimeException运行时异常

如被除数为0、数组下标越界、空指针;
这类异常通常是由编程错误导致的

NullPointerException异常
空指针,对象为空

ClassCastException异常
强制转型异常

ArrayIndexOutOfBoundsException异常
数组越界 

NumberFormatException异常
数字格式化异常

8.1.2 异常的处理方式:捕获异常

try-cath-finally

8.2 自定义异常类

public class Test2 {
	public static void main(String[] args) {
		String str;
		System.out.println("请输入字符串:");
		Scanner s=new Scanner(System.in);
		str=s.next();
		try{
			if(str.equals("abc"))
				throw new MyException("输入的字符串不能为'abc'");//抛出异常
		}
		catch(MyException e){
			System.out.println("异常原因:"+e);
		}
	}
}
class MyException extends Exception{//自定义异常类
	String str;
	MyException(String str){
		this.str=str;
	}
	public String toString(){//将异情况转化为字符串
		return str;
	}
}

第九章 输入输出系统

9.1 定义

流:把一组有序的数据序列称为流;

输入流:可从中读出数据的对象称输入流;

输出流:能向其中写入数据的对象称为输出流。

字节流:如果流中最小的数据单元是字节,则称为字节流;

字符流:如果流中最小的数据单元是字符,则称为字符流。

节点流:用于直接操作目标设备所对应的流叫作节点流。

过滤流:通过一个间接流去调用节点流类,以达到更加灵活方便地读/写各种类型的数据,这个间接流就是过滤流。

9.2 字节流

InputStream 表示字节输入流,是抽象类,不能实例化,用来表示那些从不同数据源产生输入的类。

数据源:字节数组、String对象、文件、管道等,每一种数据源都有相应的InputStream子类

2

InputStream中读取数据的方法如下:

    1.abstract int read() throws IOException:读取一个字节数据,并返回读到的数据,若返回-1,表示读到了流的末尾。
    2.int read(byte[] b) throws IOException:从流中读取一定数量的字节,存储在缓冲区数组b中,并以整数形式返回实际读取的字节数,若返回-1,表示读到了流的末尾。
    3.int read(byte[] b, int off, int len) throws IOException:将数据读入一个字节数组,同时返回实际读取字节数,若返回-1,表示读到了流的末尾。
    4.long skip(long n) throws IOException:跳过(放弃)此流中的n个字节,返回跳过的字节数。若n为负,则不跳过任何字节。
    5.int available() throws IOException:返回此流下一个方法调用可不受阻塞地从流中读取(或跳过)的估计字节数
    6.void close():关闭流并释放与之相关的系统资源。

3

OutputStream中写入数据的方法如下:

    1 abstract void write(int b) throws IOException:将b的最低一个字节写入此流,高位字节(3个)丢弃。
    2 void write(byte[] b) throws IOException:将b.length个字节从指定的byte数组写入此流。
    3 void write(byte[] b,int off,int len)throws IOException:将指定byte数组中从偏移量off开始的len个字节写入此流。
    4 void flush()throws IOException:刷新此流并强制写出所有缓冲的输出字节。
    5 void close()throws IOException:关闭此流并释放与之相关的系统资源。
9.1.1 字节数组输入流

ByteArrayInputStream类从内存的字数组中读取数据(数据源是一个字节数组)。

例一:
public class ByteArrayInputStreamDemo {
	public static void main(String[] args) throws IOException {
		String str = "abcdefghijk";
		byte[] strBuf = str.getBytes();	// 把字符串转换为字节数组
		ByteArrayInputStream bais = new ByteArrayInputStream(strBuf);
		int data = -1;	// 从字节数组输入流读取字节
		while ((data = bais.read())!= -1) {
			char upper = Character.toUpperCase((char) data);// 小写转换为大写
			System.out.print(upper + " ");				
		}
		bais.close();
	}
}
程序运行结果:
A B C D E F G H I J K
// 字节数组
// 1. 创建源
byte[] src = "talk is cheap show me the code".getBytes();	
// 2. 选择流
InputStream is = null;
try {
	is = new ByteArrayInputStream(src);
			
	// 3. 操作(分段读取)
	byte[] flush = new byte[5];
	int len = -1;
	while((len = is.read(flush)) != -1) {
		String str = new String(flush, 0, len);
		System.out.println(str);
	}
} catch (IOException e) {
	e.printStackTrace();
}
9.1.2 字节数组输出流

ByteArrayOutputStream():创建一个新的字节数组输出流。缓冲区容量最初是32字节,如有必要可增加。

ByteArrayOutputStream(int size):创建指定大小(字节)缓冲区的字节数组输出流。

class ByteArrayOutputStreamDemo {
	public static void main(String args[]) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		String s = "welcome to use ByteArrayOutputStream.";
		byte buf[] = s.getBytes();	
		baos.write(buf);			// 将指定字节数组中的数据写入此流
		System.out.println(baos.toString());	// 通过解码字节将缓冲区内容转换为字符串输出
		// 创建一个新分配的字节数组,并将缓冲流中的内容复制到该数组中
		byte b[] = baos.toByteArray();		
		for (int i = 0; i < b.length; i++)
			System.out.print((char) b[i]);
	}
}
程序运行结果:
welcome to use ByteArrayOutputStream.
welcome to use ByteArrayOutputStream.
// 字节数组
// 1. 创建源
byte[] dest = null;
		
// 2. 选择流(新增方法)
ByteArrayOutputStream baos = null;
		
try {
	baos = new ByteArrayOutputStream();
			
	// 3. 操作
	String msg = "show me the code";
	byte[] datas = msg.getBytes();
	baos.write(datas, 0, datas.length);
	baos.flush();
		
	// 4. 获取数据
	dest = baos.toByteArray();
	System.out.println(dest.length);
} catch (IOException e) {
	e.printStackTrace();
}
9.1.3 字节流与文件
// 图片--> 字节数组;
// 字节数组--> 图片;
public class IOTest07 {
	public static void main(String[] args) {
		// 图片转成字节数组
		byte[] datas = fileToByteArray("001.jpg");
		System.out.println(datas.length);
		
		// 将字节数组转成文件
		ByteArrayToFile(datas, "002.jpg");
	}
	
	/**
	 * 将文件转成字节数组
	 * @param filePath 文件路径
	 * @return 字节数组
	 */
	public static byte[] fileToByteArray(String filePath) {
		// 1. 创建源与目的地
		File src = new File(filePath);

		// 2. 选择流
		InputStream is = null;
		ByteArrayOutputStream baos = null;
		try {
			is = new FileInputStream(src);
			baos = new ByteArrayOutputStream();

			// 3. 操作(读取文件, 写出到字节数组)
			byte[] flush = new byte[1024 * 10];
			int len = -1;
			while ((len = is.read(flush)) != -1) {
				baos.write(flush, 0, len); // 写出到字节数组中
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 4. 释放资源
			try {
				if(is != null) {
					is.close();	
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * 将字节数组转成文件
	 * @param src 字节数组
	 * @param filePath 文件路径
	 */
	public static void ByteArrayToFile(byte[] src, String filePath) {
		// 1. 创建源
		File dest = new File(filePath);
		
		// 2. 选择流
		OutputStream os = null;
		InputStream is = null;
		try {
			os = new FileOutputStream(dest, true);
			is = new ByteArrayInputStream(src);
			
			// 3. 操作(读取字节数组,写出文件)
			byte[] flush = new byte[1024*10];
			int len = -1;
			while((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
			}
			os.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}  catch (IOException e) {
			e.printStackTrace();
		}finally {
			// 4. 释放资源
			try {
				if(os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
9.1.4 文件输入流

FileInputStream类用于从二进制文件(如图像之类的原始字节流)取数据。

它的构造方法如下:

FileInputStream(File file) throws FileNotFoundException:打开一个到实际文件的连接来创建文件输入流,该文件通过文件系统的File对象指定。
FileInputStream(String name)throws FileNotFoundException:打开一个到实际文件的连接来创建文件输入流,该文件通过文件系统的路径名name指定。
class FileInputStreamDemo {
	public static void main(String[] args) {
		String filename;
		int ch = -1;
		filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t1.txt";
		try {
			FileInputStream fis = new FileInputStream(filename);
			while ((ch = fis.read()) != -1) {	// 从文件输入流读取数据
				System.out.print((char) ch);
			}
			fis.close();// 关闭文件输入流
		} catch (IOException e) {
			System.out.println("File not found");
		}
	}
}
9.1.5 文件输出流

FileOutputStream类用于向二进制文件(如图像之类的原始字节流)写入数据,

构造方法如下:
    1 FileOutputStream(String name) throws FileNotFoundException:创建一个向指定名字的文件中写入数据的流。若文件已存在,原有内容被清除。
    2 FileOutputStream(String name, boolean append) throws FileNotFoundException:创建一个向指定名字的文件中写入数据的流。若第二个参数为true,则以添加方式写入字节,文件中原有的内容不会被清除。
    3 FileOutputStream(File file)throws FileNotFoundException:创建一个向指定File对象表示的文件中写入数据的流。
    4 FileOutputStream(File file, boolean append)throws FileNotFoundException:创建一个向指定File对象表示的文件中写入数据的流。若第二个参数为true,则将字节写入文件末尾处而不是文件开始处。
public class FileOutputStreamDemo {
	public static void main(String[] args) throws IOException {
		int n = 0;
		int num = 0;
		int i = 0;
		String filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t2.txt";
		FileOutputStream fos = null;
		FileInputStream fis = null;
		try {
			fos = new FileOutputStream(filename,true);
			for (n =100;n<=200;n++) {
				if (n % 3 ==0) {
					i++;
					String str = String.valueOf(n);	// 返回整型值的字符串表示形式
					String str1 = str+"   ";	// 两数之间保留一定空隙
					byte[] buff = str1.getBytes();	// 把字符串转换为字节数组
					fos.write(buff); 		
					if(i%10==0) {
						str = "\r\n";		// 按回车键换行
						byte[] buf = str.getBytes();
						fos.write(buf);
					}
				}
			}
			fos.close();
		} catch (FileNotFoundException e1) {
			System.out.println(e1);
		} catch (IOException e2) {
			System.out.println(e2);
		}
	}
}
9.1.6 管理流

PipedOutputStream 向管理中写入数据,PipedInputStream从管理中读取数据,两个都用于完成线程之间的通信

//********** 向管道输出流写数据的线程 ******************
class Sender extends Thread {							// 继承Thread类来创建线程
	private PipedOutputStream out = new PipedOutputStream();
	public PipedOutputStream getPipedOutputStream() {
		return out;
	}
	public void run() {
		String s = "use PipedInputStream and PipedOutputStream to communication.";
		try {
			out.write(s.getBytes());
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
//**********管道输入流读数据的线程 ******************
public class Receiver extends Thread {
	private PipedInputStream in;
	public Receiver(Sender sender) throws IOException {
		in = new PipedInputStream(sender.getPipedOutputStream());
	}
	public void run() {
		try {
			int data;
			while ((data = in.read())!= -1)
				System.out.print((char)data);
			in.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	public static void main(String args[]) throws Exception {
		Sender sender = new Sender();
		Receiver receiver = new Receiver(sender);
		sender.start();									// 启动线程
		receiver.start();
	}
}

9.2 过滤流

9.2.1 字节缓冲流
(1)BufferedInputStream类的构造方法如下。
    1 BufferedInputStream(InputStream in):创建一个BufferedInputStream并保存其参数,同时创建一个内部缓冲区数组并将其存储在其中。
    2 BufferedInputStream(InputStream in, int size):创建具有指定缓冲区大小的BufferedInputStream并保存其参数,同时创建一个长度为size的内部缓冲区数组并将其存储在其中。
(2)BufferedOutputStream类的构造方法如下。
    1 BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    2 BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
public class TestPrime {
	BufferedInputStream bis = null;
	BufferedOutputStream bos = null;
	String filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t3.txt";
	static int s,p;
	boolean isPrime(int n) { 
		for (int i = 2; i <= n / 2; i++)
			if (n % i == 0) return false;
		return true;
	}
	void printPrime(int m) throws IOException {
		bos = new BufferedOutputStream(new FileOutputStream(filename));
		int j = 0;
		for (int i = 2; i <= m; i++) {
			if (isPrime(i)) {
				j++;
				if(j%s==0) {
					String s= String.valueOf(i)+"  ";
					bos.write(s.getBytes());	// 将字符串转换为字节数组
					bos.write("\r\n".getBytes()); // 写入回车换行符	
				}else {
					String s= String.valueOf(i)+"   ";
					bos.write(s.getBytes());
				}
			}
		}
		bos.flush();	// 强制刷新流
		bos.close();	// 关闭输出流
	}
	void getPrime() throws Exception {
		bis = new BufferedInputStream(new FileInputStream(filename));
		int c =bis.read();	// 读取输入流
		while( c!=-1) {
			char ch = (char)c;// 将整型转换为char类型
			System.out.print(ch);
			c = bis.read();	
		}
		bis.close();
	}
	public static void main(String[] args) throws Exception {
		TestPrime pn = new TestPrime();
		p = Integer.parseInt(args[0]);// 将字符串类型转换为整型
		s = Integer.parseInt(args[1]);
		pn.printPrime(p); // 打印出100 之内的所有质数
		pn.getPrime();// 读取文本文件中的p个质数
	}
}
// 字节缓冲流
public class BufferedTest01 {
	public static void main(String[] args) {
		// 1. 创建源 
		File src = new File("H:/Java/projects/IO_study02/abc.txt");
		File dest = new File("H:/Java/projects/IO_study02/bbb.txt");
		// 2. 选择流
		InputStream is = null;
		OutputStream os = null;
		try {
			is = new BufferedInputStream(new FileInputStream(src));
			os = new BufferedOutputStream(new FileOutputStream(dest));
			// 3. 操作(缓冲流的输入与输出)
			byte[] flush = new byte[1024];
			int len = -1;
			while((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
				String str = new String(flush, 0, len, "utf8");
				System.out.println(str);
			} 
			os.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			// 4. 释放资源
			try {
				os.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if(is != null) {
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
9.2.2 数据流类
DataInputStream和DataOutputStream类提供了读/写各种基本数据类型以及String对象的方法。
(1)DataInputStream类的所有读方法都以“read”开头,如下:
    1 readByte():从输入流中读取1个字节,把它转换为byte类型的数据。
    2 readFloat():从输入流中读取4个字节,把它转换为float类型的数据。
    3 readLong():从输入流中读取8个字节,把它转换为long类型的数据。
    4 readUTF():从输入流中读取若干个字节,把它转换为UTF-8编码的字符串。
(2)DataOutputStream类的所有方法都以“write”开头,具体如下。
    1 writeByte():向输出流中写入byte类型的数据。
    2 writeLong():向输出流中写入long类型的数据。
    3 writeFloat():向输出流中写入float类型的数据。
    4 writeUTF():向输出流中写入按UTF-8编码的数据。
public class DataStreamDemo {
	public static void main(String[] args) throws IOException {
		FileOutputStream fos = new FileOutputStream(
				"C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t4.txt");
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		DataOutputStream dos = new DataOutputStream(bos);
		dos.writeByte(75);
		dos.writeLong(10000);
		dos.writeChar('a');
		dos.writeUTF("北京");
		dos.close();
		FileInputStream fis = new FileInputStream(
				"C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t4.txt");
		BufferedInputStream bis = new BufferedInputStream(fis);
		DataInputStream dis = new DataInputStream(bis);
		System.out.print(dis.readByte() + " ");
		System.out.print(dis.readLong() + " ");
		System.out.print(dis.readChar() + " ");
		System.out.print(dis.readUTF() + " ");
		dis.close();
	}
}
/**
 * 数据流
 * 1. 先写出后读取
 * 2. 读取的顺序与写出保持一致
 * DataOutputStream
 * DataInputStream
 * @author Administrator
 *
 */
public class DataTest01 {
	public static void main(String[] args) throws IOException {
		// 写出
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
		
		// 操作数据类型 + 数据
		dos.writeUTF("一把辛酸泪");
		dos.writeInt(18);
		dos.writeBoolean(false);
		dos.writeChar('a');
		dos.flush();
		byte[] datas = baos.toByteArray();
		
		// 读取
		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(datas));
		String msg = dis.readUTF();
		int age = dis.readInt();
		boolean b = dis.readBoolean();
		char c = dis.readChar();
		System.out.println(msg+age+b+c);
	}
}
9.2.3 PrintStream
PrintStream写数据的方法都以“print”开头,如下:
    1 print(int i):向输出流写入一个int类型的数据,按照平台默认的字节编码,将String.valueOf(int i)全部写入这些字节。
    2 print(String s):向输出流写入一个String类型的数据,采用本地操作系统的默认字符编码。
    3 println(int i):向输出流写入一个int类型的数据和换行符。
    4 println(String s):向输出流写入一个String类型的数据,采用本地操作系统的默认字符编码和换行符。
public class PrintStreamDemo {
	public static void main(String[] args) {
		int[][] a = new int[10][10];
		PrintStream ps = null;
		try {
			FileOutputStream fos = new FileOutputStream(
					"C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t5.txt");
			ps = new PrintStream(fos);
			if (ps != null) {
				System.setOut(ps);// 使标准输出重定向
			}
			int i = 0;
			int j = 0;
			for (i = 0; i < 10; i++) {
				a[i][i] = 1;	// 使对角线元素为1
				a[i][0]= 1;		// 使第一列元素为1
			}
			for (i = 2; i < 10; i++)
				for (j = 1; j <= i - 1; j++) {
					a[i][j] = a[i - 1][j - 1] + a[i - 1][j];// 上行同列与前一列两个数之和
				}
			for (i = 0; i < 10; i++) {
				for (j = 0; j <=i; j++) {
					System.out.print(a[i][j] + " \t");
				}
				System.out.println();
			}
		} catch (IOException e) {
			e.printStackTrace();		
		}
	}
}

9.3 字符流

在Java程序中,以下两种方式都能获得本地平台的字符编码类型:
System.getProperty("file.encoding"); // 方式一
Charset cs =Charset.defaultCharset(); // 方式二
System.out.println(cs);

9.3.1 转换流

InputStreamReader将一个字节流中的若干字节解码成字符;而OutputStreamWriter则将字符编码成若干字节后写入一个字节流

(1)InputStreamReader类的构造方法如下:
    1 InputStreamReader(InputStream in):使用当前平台的字符集编码,将字节输入流转换成字符输入流。
    2 InputStreamReader(InputStreamin, StringcharsetName) 
    throws UnsupportedEncodingException:使用指定的字符集编码,将字节输入流转换成字符输入流。
(2)OutputStreamWriter类的构造方法如下:
    1 OutputStreamWriter(OutputStream out):使用当前平台字符集编码,将字节输出流转换成字符输出流。
    2 OutputStreamWriter(OutputStreamout, StringcharsetName) throws 
    UnsupportedEncodingException:使用指定的字符集编码,将字节输出流转换成字符输出流。
public class ConverseStreamDemo {
	public static void main(String[] args) {
		String filename = " C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t6.txt";
		try {
			OutputStreamWriter osw= new OutputStreamWriter(new FileOutputStream(filename));
			osw.write("中国北京");
			System.out.println(osw.getEncoding()); 	// 显示默认字符集编码
			osw.close();
			osw = new OutputStreamWriter(new FileOutputStream(filename, true),"GB2312");
			osw.write("中国北京");
			System.out.println(osw.getEncoding()); 	// 显示指定字符集编码
			osw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			InputStreamReader isr = new InputStreamReader(new FileInputStream(
					filename), "GB2312");
			int c = -1;
			while ((c = isr.read()) != -1)
				System.out.print((char) c);
			System.out.println();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
/**
 * 字符流《===字节流
 * InputStreamReader
 * OutputStremWriter
 * @author Administrator
 *
 */
public class ConvertTest01 {
	public static void main(String[] args) {
		// 操作System.in 和 System.out
		try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));) {
			// 循环获取键盘的输入(exit),输入此内容
			String msg = "";
			while(!msg.equals("exit")) {
				msg = reader.readLine(); // 循环读取
				writer.write(msg); // 循环写出
				writer.newLine();
				writer.flush(); // 强制刷新
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
}
// 获取网络节点并转为字符流
public static void test2() {
	// 操作网络流,下载百度的源代码
	try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL("http://www.baidu.com").openStream(), "utf8"));
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("baidu.html")));) {
		String str = null;
		while ((str = reader.readLine()) != null) {
			writer.write(str);
			writer.newLine();
			System.out.println(str);
		}
		writer.flush();
	} catch (IOException e) {
		e.printStackTrace();
	}
}
9.3.2 FileReader和FileWriter

FileWriter 类用于写字符文件,每次写入一个字符、一个数组或一个字符串。

通常将FileReader对象看成一个以字符为基本单位的无格式字符输入流;

(1)FileReader的构造方法如下。
    1 FileReader(String fileName) throws FileNotFoundException:在给定从中读取数据的文件名的情况下创建一个新的FileReader。
    2 FileReader(File file) throws FileNotFoundException:在给定从中读取数据的File的情况下创建一个新的FileReader。
(2)FileWriter的构造方法如下。
    1 FileWriter(File file, boolean append) throws IOException:根据给定的File对象构造一个FileWriter对象。若第二个参数为true,则将字符以添加方式写入文件末尾处;若为false,则原有文件内容被清除,以便写入新内容。
    2 FileWriter(String fileName, boolean append) throws IOException:根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter对象,若append为false,则原有文件内容被清除。
9.3.3 字符读写
public class IOTest04 {
	public static void main(String[] args) {
		// 1. 创建源
		File src = new File("abc.txt");
		File dest = new File("dest.txt");
		
		// 2. 选择流
		Reader read = null;
		Writer writer = null;
		try {
			read = new FileReader(src);
			writer = new FileWriter(dest, true);
			
			// 操作
			// 字符读入
			char[] flush = new char[1024]; // 缓冲容器
			int len = -1;  // 接收长度
			while((len = read.read(flush)) != -1) {
				String str = new String(flush, 0, len);
				System.out.println(str);
				
				// 读入后直接写出
				writer.write(flush, 0, len);
			}
			
			// 字符写出
            // 方法一
			String s = "当你停下脚步的时候,不要忘了,比你优秀的人还在奔跑!";
			char[] ch = s.toCharArray();
			writer.write(ch);
            
            // 方法二
			String s1 = "当你停下脚步的时候,不要忘了,比你优秀的人还在奔跑!";
			writer.write(s1);
			
            // 方法三
			writer.append("Java").append("Python").append("工程师");
            
            writer.flush(); // 刷新
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 4. 释放资源
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				read.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
9.3.4 字符缓冲流

文本行是以回车/换行结束的字符序列,有时以文本行为单位读取与处理文本更方便。

BufferedReader/BufferedWriter 是带缓冲的字符流,可用于以文本行为单位处理文本的场合。

方    法										功    能
BufferedReader(Reader in)			 		将输入流in转换成带缓冲的字符流,缓冲区大小为系统默认
BufferedReader(Reader in,int sz)			将输入流in转换成带缓冲的字符流,缓冲区大小为sz
String readLine() throws IOException		从输入流中读取一行字符,行结束标志为回车('\r')/换行('\n')或连续的回车换行符('\r''\n')。若读到流结束,则返回null。若流中暂时无数据可读,则该方法进入阻塞状态。注意:返回的字符串中不含行结束符
方    法										功    能
BufferedWriter(Writer out)				将输出流out转换成带缓冲的字符流,缓冲区大小为系统默认
BufferedWriter(Writer out,int sz)		将流out转换成带缓冲的字符流,缓冲区大小为sz
void newLine() throws IOException		写入行结束标记,该标记不是简单的换行符('\n'),而是由系统定义的属性line.separator
public class PrimeNumber {
	BufferedWriter bw =null;
	String filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t8.txt";
	boolean isPrime(int n) { 
		for (int i = 2; i <= n / 2; i++)
			if (n % i == 0)
				return false;
		return true;
	}
	void printPrime(int m) throws IOException {
		bw = new BufferedWriter(new FileWriter(filename));
		int j = 0;
		for (int i = 2; i <= m; i++) {
			if (isPrime(i)) {
				j++;
				String s = String.valueOf(i);
				String s1 = s +"  ";
				bw.write(s1);	// 写入文本文件中
				if(j == 10) {
					j = 0;
					bw.newLine();	// 写入一个行分隔符
				}
			}
		}
		bw.flush();	// 强制刷新流
		bw.close();
	}
	public static void main(String[] args) throws IOException {
		PrimeNumber pn = new PrimeNumber();
		pn.printPrime(100); // 打印出100以内所有的质数
	}
}
// 字符缓冲流的读写
public class BufferedTest02 {
	public static void main(String[] args) {
		// 创建源
		File src = new File("H:/Java/projects/IO_study02/abc.txt");
		File dest = new File("H:/Java/projects/IO_study02/dest.txt");
		
		try(BufferedReader reader = new BufferedReader(new FileReader(src));
				BufferedWriter writer = new BufferedWriter(new FileWriter(dest));) {
			
			// 操作(逐行读取与逐行写入)
			String str = null;
			while((str = reader.readLine()) != null) {
				writer.append(str);
				writer.newLine();
				System.out.println(str);
			}
			writer.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
9.3.5 PrintWriter
1. PrintWriter(Writer out):将任意一个字符输出流out串接成一个PrintWriter对象,不自动刷空流。
2. PrintWriter(Writer out, boolean autoFlush):将任意一个字符输出流out串接成一个PrintWriter对象,若autoFlush为true则自动刷空流。
3. PrintWriter(OutputStream out):将任意一个字节输出流out串接成一个PrintWriter对象,不自动刷空流。
4. PrintWriter(OutputStream out, boolean autoFlush):将任意一个字节输出流out串接成一个PrintWriter对象,若autoFlush为true则自动刷空流。
public class PrintWriterDemo {
	public static void main(String[] s) throws Exception {
		int A = 0;		// 千位上的数
		int B = 0;		// 百位上的数
		int C = 0;		// 十位上的数
		int num = 0;
		String filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t9.txt";
		FileWriter fw = new FileWriter(filename);
		PrintWriter pw = new PrintWriter(fw); 
		for (int i = 1000; i < 10000; i++) {
			A = i / 1000;
			B = i /100 % 10;
			C = i / 10 % 10;
			if (i % 11 == 0 && A == B + C) {
				pw.print(i + "  ");
				if (++num % 7 == 0) {
					pw.println();// 写入回车换行符
				}
			}
		}
		fw.close();
	}
}
/**
 * 打印流
 * PrintWriter
 * @author Administrator
 *
 */
public class PrintTest02 {
	public static void main(String[] args) throws FileNotFoundException {
		PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream("printe.txt")), true);
		pw.println("printwriter");
		pw.print("PrintWriter与PrintStream是兄弟");
		pw.close();
	}
}
9.3.6 PrintStream
/**
 * 打印流
 * PrintStream
 * @author Administrator
 *
 */
public class PrintTest01 {
	public static void main(String[] args) throws FileNotFoundException {
		// 打印流System.out
		PrintStream ps = System.out;
		
		ps.println("打印流");
		ps.println(true);
		
		ps = new PrintStream(new BufferedOutputStream(new FileOutputStream("print.txt")), true);
		ps.println("打印流");
		ps.println(true);
		ps.close();
		
		// 重定向输出端
		System.setOut(ps);
		System.out.println("change");
		
		// 重定向回控制台
		System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)), true));
		System.out.println("已经重定向回来了");
	}
}

9.4 标准 I/O

Java的I/O流并不存在于整个程序运行的生命周期中,通常在I/O操作完毕时就应该适时地关闭流。

为此,java.lang.System类提供三个静态常量:

1. static final InputStream in:“标准”输入流,此流已打开并准备提供输入数据。
	通常此流对应键盘输入或者由主机环境或用户指定的另一个输入源。
2. static final PrintStream out:“标准”输出流,此流已打开并准备接受输出数据。
	通常此流对应显示器输出或者由主机环境或用户指定的另一个输出目标。
3. static final PrintStream err:“标准”错误输出流。此流已打开并准备接受输出错误信息。
	通常此流对应显示器输出或者由主机环境或用户指定的另一个输出目标。
9.4.1 操作步骤
 * 1.创建源
 * 2.选择流
 * 3.操作
 * 4.释放资源
 public static void main(String[] args) {
	// 1.创建源
	File src = new File("abc.txt");
	// 2.选择流
	InputStream is = null;
	try {
		is = new FileInputStream(src);
		// 3. 操作(读取)
		int temp;
		while((temp = is.read()) != -1) {
			System.out.println((char)temp);
		}
//		int data1 = is.read(); // 第一个数据
//		int data2 = is.read(); // 第二个数据
//		System.out.println((char)data1);
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}catch (IOException e) {
		e.printStackTrace();
	} finally {
		// 4.释放资源
		try {
			if(is != null) {
				is.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
9.4.2 分段读取
// 1. 创建源
File src = new File("abc.txt");
		
// 2. 选择流
InputStream is = null;
try {
	is = new FileInputStream(src);
			
	// 3. 操作(分段读取)
	byte[] flush = new byte[1024*1]; // 中间的缓冲容器(几k几k的读)
	int len = -1; // 接收长度
	while((len = is.read(flush)) != -1) {
		// 字节数组--->字符串(解码)
		String str = new String(flush, 0, len);
		System.out.println(str);
	} 
} catch (FileNotFoundException e) {
	e.printStackTrace();
}catch (IOException e) {
	e.printStackTrace();
}finally {
	// 4. 释放资源
	try {
		if(is != null) {
			is.close();	
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}
9.4.3 写出内容
// 1. 创建源
File src = new File("dest.txt"); // 文件可不存在

// 2. 选择流
OutputStream os = null;
try {
	os = new FileOutputStream(src, true); // true为追加
			
	// 3. 操作(写出内容)
	String msg = "In the future you will thank now hard yourself!";
	byte[] bt = msg.getBytes(); // 字符串--->字节数组(编码)
	os.write(bt, 0 ,bt.length);
	os.flush(); // 刷新
} catch(FileNotFoundException e) {
	e.printStackTrace();
}catch (IOException e) {
	e.printStackTrace();
}finally {
	// 4. 释放资源
	try {
		if(os != null) {
			os.close();
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}
9.4.4 文件拷贝
/**
 * 文件拷贝
 * @param srcPath 源文件路径
 * @param destPath 目标文件路径
 */
public static void copy(String srcPath, String destPath) {
	// 1. 创建源
	File src = new File(srcPath); // 源头
	File dest = new File(destPath); // 目的地
	
	// 2. 选择流
	InputStream is = null;
	OutputStream os = null;
	try {
		is = new FileInputStream(src);
		os = new FileOutputStream(dest, true);
		
		// 3. 操作(读取与写出)
		byte[] flush = new byte[1024]; // 中间缓冲容器
		int len = -1; // 接收长度

		while ((len = is.read(flush)) != -1) {
			os.write(flush, 0, len);
			os.flush();
		}
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		// 4. 释放资源 先打开的后关闭
		try {
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

/**
 * try..with..resource
 * 自动释放资源
 * @param srcPath
 * @param destPath
 */
public static void copy2(String srcPath, String destPath) {
	// 1. 创建源
	File src = new File(srcPath); // 源头
	File dest = new File(destPath); // 目的地

	try (InputStream is = new FileInputStream(src);
			OutputStream os = new FileOutputStream(dest, true);){

		// 3. 操作(读取与写出)
		byte[] flush = new byte[1024]; // 中间缓冲容器
		int len = -1; // 接收长度

		while ((len = is.read(flush)) != -1) {
			os.write(flush, 0, len);
			os.flush();
		}
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	} 
}
9.4.5 文件夹拷贝
/**
 * 文件夹的拷贝
 * @param srcPath 源文件夹路径
 * @param destPath 目标文件夹路径
 */
public static void copyFiles(String srcPath, String destPath) {
	File src = new File(srcPath);
	if(src != null && src.exists() == true) {
		if(src.isDirectory()) {
			// 创建文件夹
			String dir = destPath;
			new File(dir).mkdirs();
				
			for(File f : src.listFiles()) {
				String path = dir + File.separatorChar + f.getName();
				copyFiles(f.getAbsolutePath(), path);
			}
		}else {
			// 调用文件拷贝方法
			copy(srcPath, destPath);
		}
	}
}
9.4.6 重新包装标准I/o
用BufferedReader包装System.in,这需要中间类InputStreamReader把System.in转换成Reader。
package org.iostream;
import java.io.*;
public class PackStandardIO {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(	System.in));
		PrintWriter out = new PrintWriter(System.out,true);	// 包装标准输出
		String s;
		while ((s = br.readLine()) != null && s.length() != 0)			
			out.println(s.toUpperCase());		// 把字符串转换为大写
	}
}
程序运行结果:
abcdefghijk
ABCDEFGHIJK
9.4.7 标准 I/O 重定向

在默认情况下,标准输入流从键盘读取数据,标准输出流和标准错误输出流向控制台输出数据。

Java的System类提供了一些简单的静态方法调用,允许对标准输入/输出和错误I/O进行重定向:

1 static void setIn(PrintStream in):对标准输入流重定向。
2 static void setOut(PrintStream out):对标准输出流重定向。
3 static void setErr(PrintStream err):对标准错误输出流重定向。
public class StandardIORedirect {
	public static void main(String[] args) throws IOException {
		PrintStream console = System.out;
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(
		"C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/StandardIORedirect.java"));
		PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(
				"C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t10.txt")));
		System.setIn(in);	// 对标准输入流重定向
		System.setOut(out);
		System.setErr(out);
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String s;
		while ((s = br.readLine()) != null)	// 从BufferedReader类中读取一行数据
			System.out.println(s);					
		out.close(); 
		System.setOut(console);
	}
}
9.4.8 IO工具类
/**
 * 1. 封装拷贝
 * 2. 封装释放资源
 * @author Administrator
 *
 */
public class FileUtils {
	public static void main(String[] args) {
		
		// 文件到文件
//		try {
//			InputStream is = new FileInputStream("abc.txt");
//			OutputStream os = new FileOutputStream("aaa.txt");
//			copy(is, os);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
		
		// 文件到字节数组
		byte[] datas = null;
		try {
			InputStream is = new FileInputStream("001.jpg");
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			copy(is, os);
			datas = os.toByteArray();
			System.out.println(datas.length);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 字节数组到文件
		try {
			InputStream is = new ByteArrayInputStream(datas);
			OutputStream os = new FileOutputStream("003.jpg");
			copy(is, os);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 对接输入输出流
	 * @param is 输入流
	 * @param os 输出流
	 */
	public static void copy(InputStream is, OutputStream os) {
		try {
			// 3. 操作(读取与写出)
			byte[] flush = new byte[1024]; // 中间缓冲容器
			int len = -1; // 接收长度

			while ((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
				os.flush();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 4. 释放资源 先打开的后关闭
			close(is, os);
		}
	}
	
	/**
	 * 释放资源
	 * @param is
	 * @param os
	 */
	public static void close(InputStream is, OutputStream os) {
		try {
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 释放资源
	 * @param ios
	 */
	public static void close(Closeable...ios) {
		for(Closeable io:ios) {
			try {
				io.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
/**
 * 1. 封装拷贝
 * 2. 封装释放资源
 * @author Administrator
 *
 */
public class FileUtils2 {
	public static void main(String[] args) {
		
		// 文件到文件
//		try {
//			InputStream is = new FileInputStream("abc.txt");
//			OutputStream os = new FileOutputStream("aaa.txt");
//			copy(is, os);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
		
		// 文件到字节数组
		byte[] datas = null;
		try {
			InputStream is = new FileInputStream("001.jpg");
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			copy(is, os);
			datas = os.toByteArray();
			System.out.println(datas.length);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// 字节数组到文件
		try {
			InputStream is = new ByteArrayInputStream(datas);
			OutputStream os = new FileOutputStream("003.jpg");
			copy(is, os);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 对接输入输出流
	 * try...with...resource
	 * @param is 输入流
	 * @param os 输出流
	 */
	public static void copy(InputStream is, OutputStream os) {
		try(InputStream is0 = is; OutputStream os0 = os;){
			// 3. 操作(读取与写出)
			byte[] flush = new byte[1024]; // 中间缓冲容器
			int len = -1; // 接收长度

			while ((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
			}
			os.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
9.4.9 装饰器
/**
 * 模拟咖啡
 * 1. 抽象组件:需要装饰的抽象对象(接口或抽象父类)
 * 2. 具体组件:需要装饰的对象
 * 3. 抽象装饰类:包含了对抽象组件的引用以及装饰着共有的方法
 * 4. 具体装饰类:被装饰的对象
 * @author Administrator
 *
 */
public class DecotateTest02 {
	public static void main(String[] args) {
		Drink coffee = new Coffee();
		Drink milk = new Milk(coffee);
		System.out.println(milk.info() + "-->" + milk.cost());
		Drink suger = new Suger(coffee);
		System.out.println(suger.info() + "-->" + suger.cost());
		milk = new Suger(milk);
		System.out.println(milk.info() + "-->" + milk.cost());
	}
}

// 1. 抽象组件
interface Drink{
	double cost();	// 费用
	String info();	// 说明
}

// 2. 具体组件
class Coffee implements Drink{
	private String name = "原味咖啡";
	
	@Override
	public double cost() {
		return 10;
	}

	@Override
	public String info() {
		return name;
	}
}

// 3. 抽象装饰类
abstract class Decorate implements Drink{
	// 对抽象组件的引用
	private Drink drink;
	
	public Decorate(Drink drink) {
		super();
		this.drink = drink;
	}

	@Override
	public double cost() {
		return this.drink.cost();
	}

	@Override
	public String info() {
		return this.drink.info();
	}
}

// 4. 具体装饰类
class Milk extends Decorate{

	public Milk(Drink drink) {
		super(drink);
	}
	
	@Override
	public double cost() {
		return super.cost()*4;
	}

	@Override
	public String info() {
		return super.info() + "加入了牛奶";
	}
}

//4. 具体装饰类
class Suger extends Decorate{

	public Suger(Drink drink) {
		super(drink);
	}
	
	@Override
	public double cost() {
		return super.cost()*2;
	}

	@Override
	public String info() {
		return super.info() + "加入了糖";
	}
}
/**汽车类
 * 装饰器
 * 1、抽象组件
 * 2、具体组件
 * 3、抽象装饰类
 * 4、具体装饰类
 * @author Administrator
 *
 */
public class DecotareTest03 {
	public static void main(String[] args) {
		// 原装车
		Vehicle car = new Car();
		System.out.println(car.status() + ":" + car.cost() + "万元");
		
		// 北斗导航
		Vehicle nav = new Navigation(car);
		System.out.println(nav.status() + ":" + nav.cost() + "万元");
		
		// 无人驾驶
		Vehicle aut = new Automatic(car);
		System.out.println(aut.status() + ":" + aut.cost() + "万元");
		
		// 语音系统
		Vehicle aud = new Audio(car);
		System.out.println(aud.status() + ":" + aud.cost() + "万元");
		
		// 有北斗导航 + 无人驾驶
		Vehicle navaut = new Automatic(nav);
		System.out.println(navaut.status() + ":" + navaut.cost() + "万元");
		
		// 北斗导航 + 无人驾驶 + 语音系统
		Vehicle navautaud = new Audio(navaut);
		System.out.println(navautaud.status() + ":" + navautaud.cost() + "万元");
	}
}

// 抽象组件
interface Vehicle{
	double cost();
	String status();
}

// 具体组件
class Car implements Vehicle{
	
	private double sale = 12;
	private String name = "原装小汽车";

	@Override
	public double cost() {
		return sale;
	}

	@Override
	public String status() {
		return name;
	}
}

// 抽象装饰类
abstract class Decorate2 implements Vehicle{
	// 对抽象组件的引用
	private Vehicle vehicle;
	
	public Decorate2(Vehicle vehicle) {
		super();
		this.vehicle = vehicle;
	}

	@Override
	public double cost() {
		return this.vehicle.cost();
	}

	@Override
	public String status() {
		return this.vehicle.status();
	}
}

// 具体装饰类
class Navigation extends Decorate2{

	public Navigation(Vehicle vehicle) {
		super(vehicle);
	}
	
	@Override
	public double cost() {
		return super.cost() + 5;
	}

	@Override
	public String status() {
		return super.status() + "+北斗导航";
	}
}

//具体装饰类
class Automatic extends Decorate2{

	public Automatic(Vehicle vehicle) {
		super(vehicle);
	}
	
	@Override
	public double cost() {
		return super.cost() + 20;
	}

	@Override
	public String status() {
		return super.status() + "+无人驾驶";
	}
}

//具体装饰类
class Audio extends Decorate2{

	public Audio(Vehicle vehicle) {
		super(vehicle);
	}
	
	@Override
	public double cost() {
		return super.cost() + 10;
	}

	@Override
	public String status() {
		return super.status() + "+语音系统";
	}
}

9.5 文件分割

在生成一个RandomAccessFile对象时,除了要指明文件对象或文件名外,还需指明读/写模式。
例如:RandomAccessFile raf  = new RandomAccessFile("c:/t.dat","r");
表示对文件c:\t.dat进行随机读操作。
又如:RandomAccessFile raf = new RandomAccessFile("c:/t2.dat","rw");
表示对文件c:\t2.dat进行随机读/写操作。
可将二进制文件看成一个“巨大的字节数组”,随机读/写操作就是对这个虚拟的字节数组进行的,而数组下标就是所谓的文件指针,故随机读/写文件的首要操作即移动文件指针,其操作有以下3种。
1 long getFilePointer():获得当前文件指针的位置。
2 void seek(long pos):移动文件指针到指定的位置,从0开始计算位置。
3 int skipBytes(int n):将文件指针向文件末尾移动指定的n个字节,返回实际移动的字节数,若n<0则不发生移动。
public class RandomRW {
	public static void main(String[] args) throws Exception {
		RandomAccessFile raf = new RandomAccessFile("c:/t.dat", "rw");
		final int DOUBLE_SIZE = 8; 
		for (int i = 0; i < 10; i++) { 	// 写入10个Double数值
			raf.writeDouble(i);
			System.out.print("  " + (double) i);
		}
		System.out.println();
		raf.close();
		RandomAccessFile raf1 = new RandomAccessFile("c:/t.dat", "rw");
		raf1.seek(3 * DOUBLE_SIZE);	// 修改第3个double值
		raf1.writeDouble(300);
		raf1.seek(5 * DOUBLE_SIZE);	// 修改第5个double值
		raf1.writeDouble(500);
		raf1.close();
		// 验证是否已修改
		RandomAccessFile raf2 = new RandomAccessFile("c:/t.dat", "r");
		for (int i = 0; i < 10; i++) {
			System.out.print("  " + raf2.readDouble());
		}
		System.out.println();
		raf2.close();
	}
}
class Point {
	private int x;
	private int y;
	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	public String toString() {
		return "[" + x + "," + y + "]";
	}
	//*******在当前位置写点(x,y)对象***************************
	public void writePoint(RandomAccessFile f) throws IOException { 
		f.writeInt(x);
		f.writeInt(y);
	}
	//********在指定的第n位置写点(x,y),n值从0开始************
	public void writePoint(RandomAccessFile f, int n) throws IOException { 
		f.seek(n * 8); 	// 移到第n个点的位置,一个点对象大小是8个字节(两个int型)
		f.writeInt(x);
		f.writeInt(y);
	}
	//********在当前位置读点对象******************************
	public static Point readPoint(RandomAccessFile f) throws IOException {
		int x = f.readInt();
		int y = f.readInt();
		return new Point(x, y);
	}
	//*******在指定的第n个位置读点对象******************************
	public static Point readPoint(RandomAccessFile f, int n) throws IOException { 
		f.seek(n * 8);				
		int x = f.readInt();
		int y = f.readInt();
		return new Point(x, y);
	}	
}
public class RandomPointRW {
	public static void main(String[] args) throws Exception {
		Point pt;
		RandomAccessFile raf = new RandomAccessFile("c:/t1.dat", "rw");
		for (int i = 0; i < 10; i++) {
			pt = new Point(i, i);					// 创建点对象
			pt.writePoint(raf); 					// 点对象写入文件
			System.out.print("  " + pt);
		}
		System.out.println();
		raf.close();
		RandomAccessFile raf1 = new RandomAccessFile("c:/t1.dat", "rw");
		pt = new Point(300, 300);
		pt.writePoint(raf1, 3); 					// 修改第3个点对象值
		pt = new Point(500, 500);
		pt.writePoint(raf1, 5);						// 修改第5个点对象值
		raf1.close();
		//********验证是否已成功修改******************
		RandomAccessFile raf2 = new RandomAccessFile("c:/t1.dat", "r");
		for (int i = 0; i < 10; i++) {
			pt = Point.readPoint(raf2);
			System.out.print("  " + pt);
		}
		System.out.println();
		raf2.close();
	}
}
9.5.1 分割与合并
/**
 * 面向对象思想封装分割,合并文件
 * @author Administrator
 */
public class SplitFile {
	// 源头
	private File src;
	// 目地的(文件夹)
	private String desDir;
	// 所有分割后的文件存储路径
	private List<String> destPaths;
	// 每块大小
	private int blockSize;
	// 块数
	private int size;
	
	public SplitFile(String srcPath, String desDir) {
		this(srcPath, desDir, 1024);
	}
	
	public SplitFile(String srcPath, String desDir, int blockSize) {
		super();
		this.src = new File(srcPath);
		this.desDir = desDir;
		this.blockSize = blockSize;
		this.destPaths = new ArrayList<>();
		
		// 初始化
		init();
	}
	
	// 初始化
	private void init() {
		// 总长度
		long len = this.src.length();
		// 块数
		this.size = (int)Math.ceil(len*1.0/blockSize);
		// 路径
		for(int i=0; i<size; i++) {
			this.destPaths.add(this.desDir + File.separatorChar + "0" + i + "_" + this.src.getName());
		}
	}
	
	@Override
	public String toString() {
		return "SplitFile [src=" + src + ", desDir=" + desDir + ", destPaths=" + destPaths + ", blockSize=" + blockSize
				+ ", size=" + size + "]";
	}

	public SplitFile() {
		super();
	}
	
	public void split() throws IOException {
		// 总长度
		long len = src.length();
		// 起始位置
		int beginPos = 0;
		// 实际大小
		int actualSize = (int) (blockSize > len ? len : blockSize);
		for (int i = 0; i < size; i++) {
			beginPos = i * blockSize;
			if (i == size - 1) {
				actualSize = (int) len;
			} else {
				actualSize = blockSize;
				len -= actualSize; // 剩余量
			}
			splitDetail(i, beginPos, actualSize);
		}
	}
	
	/**
	 * 分块读取 分别保存到文件
	 * @param i 第i块
	 * @param beginPos 第i块的起始位置
	 * @param actualSize 第i块的实际长度
	 * @throws IOException
	 */
	private void splitDetail(int i, int beginPos, int actualSize) throws IOException {
		RandomAccessFile raf = new RandomAccessFile(this.src, "r");
		RandomAccessFile raf2 = new RandomAccessFile(new File(this.destPaths.get(i)), "rw");
		
		// 定起始位
		raf.seek(beginPos);
		// 读取
		byte[] flush = new byte[1024*10];
		int len = -1;
		while ((len = raf.read(flush)) != -1) {
			if(actualSize >= len) {
				raf2.write(flush, 0, len);
				actualSize = actualSize - len;
			}else {
				raf2.write(flush, 0, actualSize);
				break;
			}
		}
		raf2.close();
		raf.close();
	}
	
	/**
	 * 合并文件
	 * @param destPath  合并后的文件路径
	 * @throws IOException
	 */
	public void merge(String destPath) throws IOException {
		
		OutputStream os = new BufferedOutputStream(new FileOutputStream(destPath, true));
		
		for(int i=0; i<destPaths.size(); i++) {
			InputStream is = new BufferedInputStream(new FileInputStream(destPaths.get(i)));
			
			byte[] flush = new byte[1024]; // 中间缓冲容器
			int len = -1; // 接收长度

			while ((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
				os.flush();
			}
			is.close();
		}
		os.close();
	}
	
	/**
	 * 合并文件
	 * SequenceInputStream
	 * @param destPath  合并后的文件路径
	 * @throws IOException
	 */
	@SuppressWarnings("resource")
	public void merge2(String destPath) throws IOException {
		
		OutputStream os = new BufferedOutputStream(new FileOutputStream(destPath, true));
		
		Vector<InputStream> vi = new Vector<InputStream>();
		SequenceInputStream sis = null;
		for(int i=0; i<destPaths.size(); i++) {
			vi.add(new BufferedInputStream(new FileInputStream(destPaths.get(i))));
		}
		sis = new SequenceInputStream(vi.elements());
		
		byte[] flush = new byte[1024]; // 中间缓冲容器
		int len = -1; // 接收长度
		while ((len = sis.read(flush)) != -1) {
			os.write(flush, 0, len);
		}
		os.flush();
		sis.close();
		os.close();
	}
	
	public static void main(String[] args) throws IOException {
		SplitFile sf = new SplitFile("H:/Web/草稿/images/001.jpg", "H:/Java/projects/IO_study03/dest/image1", 1024*100);
		//sf.split();
		//sf.merge("001.jpg");
		sf.merge2("002.jpg");
	}
}

9.6 对象序列化

实施对象的这类操作称为“对象序列化(持久化)”。基本数据类型的包装类、所有容器类甚至Class对象都可以被序列化。但是用户自定义的对象默认是不能序列化的,若要使一个用户自定义类的对象也具备序列化的能力,必须明确实现java.io.Serializable接口,该接口定义为:
public interface Serializable{ }
可见这个接口中未定义任何方法,只是个标记型接口,但只要一个类声明实现了该接口,Java系统就“认为”该类可以序列化,该类的对象也就可以存盘或通过网络传输了。

class Point implements Serializable {
	private int x;
	private int y;
	private transient int z;
	public Point(int x, int y,int z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}
	public String toString() {
		return "(" + x + "," + y + "," + z  + ","+")";
	}
}
public class ObjectSerializableDemo {
	public static void main(String[] args) throws Exception {
		String filename = "C:/Users/Administrator/workspace/MyProject_09/src/org/iostream/t11.obj";
		// 将二进制文件串接成一个对象输出流
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
		for (int k = 0; k < 10; k++) {
			oos.writeObject(new Point(k, 2 * k,3*k));// 将点对象写入文件中
		}
		oos.flush();
		oos.close();
		// 将点对象写入文件中
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
		for (int k = 0; k < 10; k++) {
			Point pt = (Point) ois.readObject();// 从文件中读出点对象
			System.out.print(pt + " ");
		}
		ois.close();
	}
}
/**
 * 对象流
 * 1. 先写出后读取
 * 2. 读取顺序与写出保持一致
 * 3. 不是所有对象都可序列化 Serializable
 * @author Administrator
 *
 */
public class ObjectTest01 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// 写出 ---> 序列化
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(baos));
		
		// 操作数据类型 + 数据
		os.writeUTF("一把辛酸泪");
		os.writeInt(18);
		os.writeBoolean(false);
		os.writeChar('a');
		
		// 对象
		os.writeObject("字符串也是对象");
		os.writeObject(new Date());
		// 自定义类(需Serializable接口)
		Employee emp = new Employee("张三", 3000);
		os.writeObject(emp);
		
		os.flush();
		byte[] datas = baos.toByteArray();

		// 读取 ---> 反序列化
		ObjectInputStream dis = new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
		// 顺序与写出保持一致
		String msg = dis.readUTF();
		int age = dis.readInt();
		boolean b = dis.readBoolean();
		char c = dis.readChar();
		
		// 对象的数据还原
		Object str = dis.readObject();
		Object date = dis.readObject();
		Object empl = dis.readObject();
		
		// 类型转换
		if(str instanceof String) {
			String strObj = (String)str;
			System.out.println(strObj);
		}
		if(date instanceof Date) {
			Date dateObj = (Date)date;
			System.out.println(dateObj);
		}
		if(empl instanceof Employee) {
			Employee empObj = (Employee)empl;
			System.out.println(empObj);
		}
		System.out.println(msg+age+b+c);
		
	}
}

// javabean 封装数据
class Employee implements java.io.Serializable{
	private transient String name; // transient 该数据不需要序列化
	private double salary;
	public Employee() {
		super();
	}
	public Employee(String name, double salary) {
		super();
		this.name = name;
		this.salary = salary;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", salary=" + salary + "]";
	}
}
/**
 * 对象流 写出到文件
 * 1. 先写出后读取
 * 2. 读取顺序与写出保持一致
 * 3. 不是所有对象都可序列化 Serializable
 * @author Administrator
 *
 */
public class ObjectTest02 {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// 写出 ---> 序列化
		ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("object.txt")));
		
		// 操作数据类型 + 数据
		os.writeUTF("一把辛酸泪");
		os.writeInt(18);
		os.writeBoolean(false);
		os.writeChar('a');
		
		// 对象
		os.writeObject("字符串也是对象");
		os.writeObject(new Date());
		// 自定义类(需Serializable接口)
		Employee2 emp = new Employee2("张三", 3000);
		os.writeObject(emp);
		
		os.flush();
		os.close();

		// 读取 ---> 反序列化
		ObjectInputStream dis = new ObjectInputStream(new BufferedInputStream(new FileInputStream("object.txt")));
		// 顺序与写出保持一致
		String msg = dis.readUTF();
		int age = dis.readInt();
		boolean b = dis.readBoolean();
		char c = dis.readChar();
		
		// 对象的数据还原
		Object str = dis.readObject();
		Object date = dis.readObject();
		Object empl = dis.readObject();
		
		dis.close();
		
		// 类型转换
		if(str instanceof String) {
			String strObj = (String)str;
			System.out.println(strObj);
		}
		if(date instanceof Date) {
			Date dateObj = (Date)date;
			System.out.println(dateObj);
		}
		if(empl instanceof Employee) {
			Employee2 empObj = (Employee2)empl;
			System.out.println(empObj);
		}
		System.out.println(msg+age+b+c);
	}
}

9.7 File 类

File类可用来查看文件或目录的信息,还可以创建或删除文件和目录,它的构造方法如下:
1 File(String pathname):以pathname为路径创建File对象。
2 File(String parent, String child):以parent为父路径,child为子路径创建File对象。
Flie类还有以下一些常用方法:
3 boolean canRead():判断能否对File对象所代表的文件进行读。
4 boolean canWrite():判断能否对File对象所代表的文件进行写。
5 boolean exists():判断该File对象所代表的文件或目录是否存在。
6 boolean isDirectory():判断该File对象是否代表一个目录。
7 boolean isFile():判断该File对象是否代表一个文件。
8 boolean createNewFile() throws IOException:如果该File对象代表文件但该文件不存在,则创建它。
9 boolean mkdir():在文件系统中创建由该File对象表示的目录。
10 boolean mkdirs():在文件系统中创建由该File对象表示的目录。如果该目录的父目录不存在,则创建该目录所有的父目录。
9.7.1 名称分割符
名称分割符:
String path = "H:/Java/projects/IO_study01/001.jpg";
System.out.println(File.separatorChar); // \
		
/**建议
 * 1. /
 * 2. 常量拼接
* path = "D:"+File.separator+"java"+File.separator+"001.jpg"
 */
9.7.2 构建File对象
// 构建File对象
		
// 方法一
File src1 = new File(path);
System.out.println(src1.length()); // 文件长度 396032

// 方法二
File src2 = new File("H:/Java/projects/IO_study01", "001.jpg");
		
// 方法二
File src3 = new File(new File("H:/Java/projects/IO_study01"), "001.jpg");
9.7.3 路径
// 相对路径与绝对路径
String path = "H:/Java/projects/IO_study01/001.jpg";

// System.getProperty("user.dir") //当前目录
System.out.println(System.getProperty("user.dir"));
		
// 绝对路径
File src1 = new File(path);
System.out.println(src1.getAbsolutePath()); // 获取绝对路径
		
// 相对路径
File src2 = new File("001.jpg");
System.out.println(src1.getAbsolutePath()); // 获取绝对路径
9.7.4 名称或路径
/**
 * 名称或路径
 * getName()  名称
 * getPath()  相对或绝对
 * getAbsolutePath()	绝对路径
 * getParent()  父绝对路径或父相对路径
 */
File src1 = new File("H:/Java/projects/IO_study01/001.jpg");
System.out.println("名称:" + src1.getName()); // 获取文件或文件夹名称
System.out.println("路径:" + src1.getPath()); // 获取文件或文件夹路径
System.out.println("绝对路径:" + src1.getAbsolutePath()); // 获取文件或文件夹绝对路径
System.out.println("父路径:" + src1.getParent()); // 获取父路径
9.7.5 文件或文件夹状态
/**
 * 文件或文件夹状态
 * 1. 不存在:exists
 * 2. 存在
 * 	  文件:isFile
 * 	  文件夹:isDirector
 */
 File src = new File("001.jpg");
 System.out.println("是否存在:" + src.exists()); // 是否存在
 System.out.println("是否为文件:" + src.isFile()); // 是否为文件
 System.out.println("是否为文件夹:" + src.isDirectory()); // 是否为文件夹
9.7.6 其他信息
 * 其他信息
 * length() 文件字节数
 * createNewFile() 不存在则创建
 * delete() 删除文件
 
 File src = new File("H:/Java/projects/IO_study01/001.jpg");
 System.out.println(src.length()); // 字节长度
 
 File src = new File("io.txt");
 boolean flag = src.createNewFile(); // 创建文件
 System.out.println(flag); // true
		
 flag = src.delete(); // 删除文件
 System.out.println(flag);
9.7.7 创建目录
创建目录
1. mkdir() 确保上级目录存在
2. mkdirs() 上级目录可不存在,统一创建
File dir = new File("test/io");
		
boolean d = dir.mkdir(); // 创建单级目录
System.out.println(d); // false
		
boolean d2 = dir.mkdirs(); // 创建多级目录
System.out.println(d2); // true
9.7.8 列出下一级
 * 列出下一级
 * 1. list() 列出下级名称
 * 2. listFiles() 列出下级File对象
 * 3. listRoot() 列出所有的盘符
 File dir = new File("H:/Java/projects/IO_study01/");
		
String[] subNames = dir.list(); // 列出下级名称
for(String s:subNames) {
	System.out.println(s);
}
		
System.out.println("--------");
File[] subFiles = dir.listFiles(); // 列出下级对象
for(File f:subFiles) {
	System.out.println(f.getAbsolutePath());
}
		
File[] roots = dir.listRoots(); // 列出所有的盘符对象
for(File f:roots) {
	System.out.println(f.getAbsolutePath());
}
9.7.9 打印目录
public static void main(String[] args) {
	File dir = new File(System.getProperty("user.dir"));
	printFile(dir, 0);
}
	
public static void printFile(File file, int deep) {
	for(int i=0;i<deep;i++) {
		System.out.print("-");
	}
	System.out.println(file.getName());
	if(file != null && file.exists() == true) {
		if(file.isDirectory()) {
			for(File f:file.listFiles()) {
				printFile(f, deep+1);
			}
		}
	}
}
9.7.10 获取文件夹大小
public class DirCount {
	
	private long len; // 大小
	
	private String path; // 文件夹
	
	private int dirSize = -1; // 文件夹数量,默认会包含本身
	
	private int fileSize; // 文件数量
	
	private File src; // 源
	
	public DirCount(String path) {
		this.path = path;
		src = new File(path);
		count(this.src);
	}
	
	public long getLen() {
		return len;
	}

	public void setLen(long len) {
		this.len = len;
	}

	public static void main(String[] args) {
		DirCount dir = new DirCount(System.getProperty("user.dir"));
		System.out.println("文件夹大小:" + dir.len);
		System.out.println("文件夹数量:" + dir.dirSize);
		System.out.println("文件数量:" + dir.fileSize);
	}
	
	// 统计大小
	private void count(File src) {
		if(src != null && src.exists()) {
			if(src.isFile()) {
				len += src.length();
				fileSize ++;
			}else {
				for(File s:src.listFiles()) {
					count(s);
				}
				dirSize++;
			}
		}
	}
}
9.7.11 文件编码
String str1 = "猛学两个月a";
		
// 编码:字节数组
byte[] datas = str1.getBytes("utf8"); // 默认使用工程的字符集
System.out.println(datas.length);

// 解码:字符串
str1 = new String(datas, 0, datas.length, "utf8");
System.out.println(str1);
		
乱码原因:
    1. 字节数不够
    2. 字符集不统一
常用字符集:
    US-ASCII	即英文ASCII
    ISO-8859-1	Latin-1 拉丁字符,包含中文、日文
    UTF-8		变长unicode字符(1-3),国际通用
    UTF-16BE	定长unicode字符(2个字节),大端Big-endian表示高字节低地址
    UTF-16LE	定长unicode字符(2个字节),小端little-endian表示低字节低地址
    UTF-16		文件中开头指定大端还是小端表示方式:FE FF表示大端,FF FE表示小端
public class UseFile {
	public static void main(String args[]) throws Exception{
		File dir1 = new File("D:/dir1");
		if(!dir1.exists())
			dir1.mkdir(); 
		File dir2 = new File(dir1,"dir2");
		if(!dir2.exists())
			dir2.mkdirs(); 
		File dir4 = new File(dir1,"dir3/dir4");
		if(!dir4.exists())
			dir4.mkdirs(); 
		File file = new File(dir2,"test.txt");
		if(!file.exists())
			file.createNewFile();
		listDir(dir1);
		deleteDir(dir1); 
	}
	public static void listDir(File dir) {
		File[] lists = dir.listFiles();
		//*********显示当前目录下包含的所有子目录和文件的名字*********
		String info="目录:" + dir.getName() + "(";
		for(int i = 0; i < lists.length; i++)
			info += lists[i].getName() + " ";
		info += ")";
		System.out.println(info); 
		//*********显示当前目录下包含的所有子目录和文件**************
		for(int i = 0; i < lists.length; i++) {
			File f = lists[i];
			if(f.isFile())
				System.out.println("文件:" + f.getName() + " canRead:" + f.canRead()
                  			+ " lastModified:" + new Date(f.lastModified()));
		else  
			listDir(f);  // 如果为目录,就递归调用listDir()方法
		}
	}
	public static void deleteDir(File file) {
		//**********如果file代表文件,就删除该文件**********
		if(file.isFile()) {
			file.delete();
			return;
		}
		//**********如果file代表目录,先删除目录下的所有子目录和文件*********
		File[] lists = file.listFiles();
		for(int i = 0; i < lists.length; i++)
			deleteDir(lists[i]); 		 			
		file.delete();	// 删除当前目录
	}
}

9.8 键盘输入

import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
例:
Scanner scanner = new Scanner(System.in);
System.out.print("请输入名字:");	//字符串
String name = scanner.nextLine();
System.out.print("请输入性别:");
String sex = scanner.nextLine();
System.out.print("请输入年龄:");
int age = scanner.nextInt();	//数字
System.out.println("\n\n"+name+"\t"+sex+"\t"+age);

9.10 CommonsIO

9.10.1 基本操作
/**
 * Commons基本使用
 * 1. 文件大小
 * 2. 目录大小
 * 3. 列出子孙级
 * @author Administrator
 */
public class CIOTest01 {
	public static void main(String[] args) {
		// 文件大小
		long len = FileUtils.sizeOf(new File("src/com/commons/CIOTest01.java"));
		System.out.println(len);
		
		// 目录大小
		len = FileUtils.sizeOf(new File("H:/Java/projects/IO_study04"));
		System.out.println(len);
	
		/* 列出子孙级
		  *  参1:文件 
		  *  参2:过滤器  (EmptyFileFilter.NOT_EMPTY 文件不为空)
		  *  参3:层级 (DirectoryFileFilter.INSTANCE 子孙级)
		 */ 
		Collection<File> files = FileUtils.listFiles(
				new File("H:/Java/projects/IO_study04"), 
				EmptyFileFilter.NOT_EMPTY, 
				DirectoryFileFilter.INSTANCE);
		for(File f:files) {
			System.out.println(f);
		}
		
		System.out.println("----------");
		// 参2:过滤器:  new SuffixFileFilter("java") 查询后缀名为java
		files = FileUtils.listFiles(
				new File("H:/Java/projects/IO_study04"), 
				new SuffixFileFilter("java"), 
				DirectoryFileFilter.INSTANCE);
		for(File f:files) {
			System.out.println(f);
		}
		
		System.out.println("----------");
		// 参2:过滤器:  FileFileterUtils.or() 或
		// 参2:过滤器:  FileFileterUtils.and() 与
		// FileFilterUtils.or(
		// new SuffixFileFilter("java"), 
		// new SuffixFileFilter("class")) 
		// 查询后缀名为java或class
		files = FileUtils.listFiles(
				new File("H:/Java/projects/IO_study04"), 
				FileFilterUtils.or(new SuffixFileFilter("java"), 
				new SuffixFileFilter("class")), 
				DirectoryFileFilter.INSTANCE);
		for(File f:files) {
			System.out.println(f);
		}
	}
}
9.10.2 读取文件
/**
 * 读取文件
 * readFileToString() 读至字符串
 * readFileToByteArray() 读至字节数组
 * readLines() 逐行读取
 * lineIterator 逐行读取
 * @author Administrator
 */
public class CIOTest02 {
	public static void main(String[] args) throws IOException {
		// 读至字符串
		String msg = FileUtils.readFileToString(new File("emp.txt"), "UTF-8");
		System.out.println(msg);
		System.out.println("---------");
		
		// 读至字节数组
		byte[] datas = FileUtils.readFileToByteArray(new File("emp.txt"));
		System.out.println(datas.length);
		System.out.println("---------");
		
		// 逐行读取
		List<String> strs = FileUtils.readLines(new File("emp.txt"), "UTF-8");
		strs.forEach(o -> System.out.println(o));
		System.out.println("---------");
		
		// 逐行读取
		LineIterator it = FileUtils.lineIterator(new File("emp.txt"), "UTF-8");
		while(it.hasNext()) {
			System.out.println(it.nextLine());
		}
	}
}
9.10.3 写出内容
/** 
  *  写出内容 
 * write 以字符串形式写出
 * writeStringToFile 以字符串形式写出
 * writeByteArrayToFile 以字节数组形式写出
 * writeLines 以列表形式写出
 * @author Administrator
 */
public class CIOTest03 {
	public static void main(String[] args) throws IOException {
		// 以字符串形式写出
		FileUtils.write(new File("happy.txt"), "学习是一件很快乐的事!\n","UTF-8");
		
		// 以字符串形式写出
		FileUtils.writeStringToFile(new File("happy.txt"),
                                    "做你不敢做的事情是非常酷的\n","UTF-8",true);
		
		// 以字节数组形式写出
		FileUtils.writeByteArrayToFile(
            new File("happy.txt"),
            "敢于尝试,才会有更多的机会!\n".getBytes("UTF-8"),true);
		
		// 以列表形式写出
		List<String> datas = new ArrayList<>();
		datas.add("上班");
		datas.add("下班");
		datas.add("回家");
		datas.add("睡觉");
		FileUtils.writeLines(new File("happy.txt"), datas,"——",true);
	}
}
9.10.4 拷贝
/**
  *  拷贝
 * copyFile 复制文件
 * copyFileToDirectory 复制文件到目录
 * copyDirectory 复制目录
 * copyDirectoryToDirectory 复制目录到目录
 * copyURLToFile 拷贝URL内容到文件
 * IOUtils.toString 获取URL内容到字符串
 * @author Administrator
 */
public class CIOTest04 {
	public static void main(String[] args) throws IOException {
		// 复制文件
		FileUtils.copyFile(new File("001.jpg"), new File("001_copy.jpg"));
		
		// 复制文件到目录
		FileUtils.copyFileToDirectory(new File("001.jpg"), new File("images"));
		
		// 复制目录 (将images目录下的文件复制到images2目录下)
		FileUtils.copyDirectory(new File("images"), new File("images2"));
		
		// 复制目录到目录 (将images目录变成images2目录的子目录)
		FileUtils.copyDirectoryToDirectory(new File("images"), new File("images2"));
		
		// 拷贝URL内容到文件
		String url = "http://www.shuoshuokong.com/uploads/allimg/150126/1-150126230J0.jpg";
		FileUtils.copyURLToFile(new URL(url), new File("zly.jpg"));
		
		// 获取URL内容到字符串
		String url2 = "http://www.163.com";
		String datas = IOUtils.toString(new URL(url2), "gbk");
		System.out.println(datas);
	}
}

第十章 多线程

线程与进程的主要区别:每个进程都要求操作系统为其分配独立的内存地址空间,而一进程中的所有线程则在同一块地址空间中工作,它们可共享进程的状态和资源。

10.1 线程的创建与启动

10.1.1 创建线程
1、继承Thread类
2、实现Runnable接口(多用)
3、实现Callable接口 (JUC并发包架)
10.1.1 继承Thread
Thread类代表线程类,它的常用方法如下:
1. static Thread currentThread():返回当前正在运行的线程对象的引用。
2. static void yield():暂停当前正在运行的线程对象,并运行其他线程。
3. static void sleep(long millis) throws InterruptedException:在指定的毫秒数内让当前正在运行
	的线程休眠(暂停执行),此操作受系统计时器和调度程序精度和准确性的影响。
4. void start():启动线程,JVM调用该线程的run()方法使其与其他的线程并发运行。
5. void run():如果线程是通过实现Runnable接口的对象构造,则调用该Runnable对象的run()方法;
	否则该方法不执行任何操作并返回。Thread的子类应重写该方法。
6. void interrupt():设置线程的中断标记位,请求线程停止运行。
7. final void setPriority(int newPriority):更改线程的优先级。
8. final int getPriority():返回线程的优先级。
9. final void setName(String name):设置线程的名称为name。
10. final String getName():返回线程的名称。
11. final void join()throws InterruptedException:等待该线程终止。
12. final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
	当正在运行的线程都是守护线程时,JVM退出。该方法必须在启动线程前调用。
13. long getId():返回线程ID(即线程标识符),它是一个正的长整数,在创建线程时生成。
class MyThread extends Thread {    
	 private int a = 0;
	 public void run() {
		 for (int a = 0; a < 10; a++) {
			 System.out.println(currentThread().getName() + ":" + a);
			 try {
				 sleep(100);	// 给其他线程运行的机会
			 } catch (InterruptedException e) {
				 throw new RuntimeException(e);
			 }
		 }
	 }
}
public class TestThread {
	 public static void main(String[] args) {
		 MyThread thread = new MyThread(); 	// 创建用户线程对象
		 thread.start();	// 启动用户线程
		 thread.run(); 		// 主线程调用用户线程对象的run()方法
	 }
}
10.1.2 实现Runnable 接口
public class TestThread1 {
	 public static void main(String args[]) {
		 MyThread1 mt = new MyThread1();
		 Thread t = new Thread(mt);	// 创建用户线程
		 t.start();		// 启动用户线程,  自动调用run()方法
		 for (int a = 0; a < 10; a++) {
			 System.out.println(Thread.currentThread().getName() + ":" + a);
			 try {
				 Thread.sleep(100);
			 } catch (InterruptedException e) {
				 throw new RuntimeException(e);
			 }
		 }
	 }
}
class MyThread1 implements Runnable {// 通过实现Runnable接口来创建线程
	 public void run() {
		 for (int a = 0; a < 10; a++) {
			 System.out.println(Thread.currentThread().getName() + ":" + a);
			 try {
				 Thread.sleep(100);
			 } catch (InterruptedException e) {
				 throw new RuntimeException(e);
			 }
		 }
	 }
}
/**
 * 共享资源 并发(线程安全)
 * @author Administrator
 */
public class Web12306 implements Runnable{
	// 票数
	private int ticketNums = 99;
	
	@Override
	public void run() {
		while(true) {
			if(ticketNums <= 0) {
				break;
			}
			System.out.println(
					Thread.currentThread().getName() 
					+ "拿到第" + ticketNums-- + "张票");
		}
	}
	
	public static void main(String[] args) {
		// 一份资源
		Web12306 web = new Web12306();
		
		// 多个代理
		new Thread(web, "张三").start();
		new Thread(web, "李四").start();
		new Thread(web, "王五").start();
	}
}
/**
 * 模拟龟兔赛跑
 * 各跑各的,但有必须有一个共享资源,将他们联系起来
 * @author Administrator
 */
public class Racer implements Runnable{
	private static String winner; // 胜利者
	
	@Override
	public void run() {
		for(int steps=1; steps <= 100; steps++) {
			System.out.println(Thread.currentThread().getName() + "-->" + steps);
			
			// 结束标志
			if(gameOver(steps)) {
				break;
			}
		}
	}
	
	private boolean gameOver(int steps) {
		if(winner != null) {
			return true;
		}else if(steps >= 100) {
			winner = Thread.currentThread().getName();
			System.out.println(winner + "是胜利者");
			return true;
		}else {
			return false;
		}
	}
	
	public static void main(String[] args) {
		Racer racer = new Racer();
		new Thread(racer, "乌龟").start();
		new Thread(racer, "兔子").start();
	}
}
10.1.3 实现Callable
1、创建目标对象:CDownloader cd = new CDownloader("图片地址", "图片名称");
2、创建执行服务:ExecutorService ser = Executors.newFixedThreadFool(1);
3、提交执行:Futere<Boolean> result1 = ser.submit(cd1);
4、获取结果:boolean r1 = result1.get();
5、关闭服务:ser.shutdownNow();
/**
 * 多线程下载图片
 * 实现Callable接口
 * @author Administrator
 */
public class CDownloader implements Callable<Boolean>{
	
	private String url; // 远程路径
	private String name; // 存储名字
	
	public CDownloader(String url, String name) {
		super();
		this.url = url;
		this.name = name;
	}
	
	@Override
	public Boolean call() throws Exception {
		WebDownloader wd = new WebDownloader();
		wd.download(url, name);
		System.out.println(name);
		return true;
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建目标对象
        CDownloader cd1 = new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20180703/eb87a172b239417cbf0654e1943b1e84.jpeg", "004.jpg");
		CDownloader cd2 = new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20190929/7b68e381dedc4a8f9b607c1744a96463.jpeg", "005.jpg");
		CDownloader cd3 = new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20190408/86fc1fb7ca8e4879ac76059a69397fd6.jpeg", "006.jpg");
		
		// 创建执行服务
		ExecutorService ser = Executors.newFixedThreadPool(3);
		
		// 提交执行
		Future<Boolean> result1 = ser.submit(cd1);
		Future<Boolean> result2 = ser.submit(cd2);
		Future<Boolean> result3 = ser.submit(cd3);
		
		// 获取结果
		boolean r1 = result1.get();
		boolean r2 = result2.get();
		boolean r3 = result3.get();
		
		// 关闭服务
		ser.shutdownNow();
	}
}
10.1.4 静态代理
/**
 * 静态代理
 * 公共接口
 * 1. 真实角色
 * 2. 代理角色
 * @author Administrator
 */
public class StaticProxy {
	public static void main(String[] args) {
		Marry you = new You();
		Marry it = new WeddingCompay(you);
		it.happyMarry();
	}

}

interface Marry{
	void happyMarry();
}

// 真实角色
class You implements Marry{

	@Override
	public void happyMarry() {
		System.out.println("有情人终成眷属");
	}
}

// 代理角色
class WeddingCompay implements Marry{
	// 真实角色
	private Marry ma;
	
	public WeddingCompay(Marry ma) {
		super();
		this.ma = ma;
	}

	@Override
	public void happyMarry() {
		ready();
		this.ma.happyMarry();
		after();
	}
	
	private void ready() {
		System.out.println("布置婚礼现场");
	}
	
	private void after() {
		System.out.println("视频后期剪辑");
	}
}
10.1.5 Lambda
/**
 * Lambda表达式 简化线程(用一次)的使用
 * @author Administrator
 */
public class LambdaThread {
	// 1.静态内部类
//	static class Test implements Runnable{
//		@Override
//		public void run() {
//			for(int i=0;i<20;i++) {
//				System.out.println("听歌");
//			}
//		}
//	}
	
	public static void main(String[] args) {
		
		// 2.局部内部类
//		class Test implements Runnable{
//			@Override
//			public void run() {
//				for(int i=0;i<20;i++) {
//					System.out.println("听歌");
//				}
//			}
//		}
//		new Thread(new Test()).start();
		
		// 3.匿名内部类 必须借助接口或父类
//		new Thread(new Runnable() {
//			@Override
//			public void run() {
//				for(int i=0;i<20;i++) {
//					System.out.println("听歌");
//				}
//			}
//		}).start();
		
		// 5.jdk8简化 lambda
		// lambda 表达式
		new Thread(()-> {
				for(int i=0;i<20;i++) {
					System.out.println("听歌");
				}
			}
		).start();
	}
}
public class lamdbaTest01 {
	// 2.静态内部类
//	static class Like implements ILike{
//		@Override
//		public void lamdba() {
//			System.out.println("我喜欢lambda表达式");
//		}
//	}
	
	public static void main(String[] args) {
		// 3.局部内部类
//		class Like implements ILike {
//			@Override
//			public void lamdba() {
//				System.out.println("我喜欢lambda表达式");
//			}
//		}
//		ILike like = new Like();
//		like.lamdba();
		
		// 4.匿名内部类
//		new ILike() {
//			@Override
//			public void lamdba() {
//				System.out.println("我喜欢lambda表达式");
//			}
//		}.lamdba();
		
		// 5.lambda推导
		ILike like = ()->{
			System.out.println("我喜欢lambda表达式");
		};
		like.lamdba();
	}

}
interface ILike{
	void lamdba();
}

// 1.外部类
//class Like implements ILike{
//	@Override
//	public void lamdba() {
//		System.out.println("我喜欢lambda表达式");
//	}
//}
10.1.6 线程简化
/**
 * 利用lambda表达式简化线程
 * @author Administrator
 */
public class LambdaTest02 {
	public static void main(String[] args) {
		new Thread(()->System.out.println("一边学习")).start();
		new Thread(()->{
			for(int i=0;i<20;i++) {
				System.out.println("一边听歌");
			}
		}).start();
	}
}

10.2 线程的状态

image-20200517223220097

image-20200517223433839

1.新建状态(New):创建一个线程对象(如new Thread(t))时,线程还没开始运行

2.就绪状态(Runnable):其他线程调用了它的start()方法,该线程就进入就绪状态,调度器把时间片分配给它,线程就能运行。

3.阻塞状态(Blocked)

一个线程进入阻塞状态可能有如下原因:
(1)通过调用sleep(milliseconds)使线程进入休眠状态,线程在指定的时间内不会运行。
(2)通过调用wait()方法使线程挂起,直到它得到notify()或notifyAll()消息才会重新进入就绪状态。
(3)线程在等待某个输入/输出完成。
(4)线程试图在某个对象上调用同步控制方法,但对象锁不可用,因为另一个对象已经获取了这个锁。

4.死亡状态(Dead):当线程退出run()方法时就进入死亡状态,正常执行完run()方法退出,但也有可能是遇到异常而退出run()方法。

5、线程方法

image-20200517224106115

public class Text8 {
	public static void main(String[] args) {
		thread1 th=new thread1();
		Thread t=new Thread(th);
		thread2 th2=new thread2();
		t.setDaemon(true);//设置为后台线程,主线程结束后程序结束
		t.start();//启动线程
		th2.start();
		t.resume();//继续
		t.suspend();//暂停
		t.sleep(m);//休眠m毫秒
		t.yield();//让出CPU给更高优先级线程
		t.join();//挂起主线程,等待客户线程执行结束
		t.stop();//结束
	}
}
class thread1 implements Runnable{//线程法一
	public void run() {
		while(true){
			System.out.println("0000");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
class thread2 extends Thread{//线程法二
	public void run(){
		while(true){
			System.out.println("1111");
			try {
				sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
10.2.1 终止线程
/**
 * 终止线程
 * 1.程序正常执行完毕-->次数
 * 2.外部干涉-->加入标识
 * @author Administrator
 */
public class TerminateThread implements Runnable{
	// 加入标识
	private boolean flag = true;
	public static void main(String[] args) {
		TerminateThread tt = new TerminateThread();
		new Thread(tt).start();
		try {
			Thread.sleep(10);
			tt.stop();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		int i = 1;
		while(this.flag) {
			System.out.println("执行" + i++ + "次");
		}
	}
	
	public void stop() {
		this.flag = false;
		System.out.println("终止线程");
	}
}
10.2.2 sleep
1. sleep(时间) 指定当前线程阻塞的毫秒数;
2. sleep 存在异常InterruptedException;
3. sleep 时间达到后线程进入就绪状态;
4. sleep 可以模拟网络延迟、倒计时等;
5. 每个对象都有一个锁,sleep不会释放锁;

public static void test1() throws InterruptedException {
	int num = 10;
	while(num > 0) {
		Thread.sleep(1000);
		System.out.println(num--);
	}
}
// 倒计时 ( 00:06)
public static void test2() throws InterruptedException {
	Date endTime = new Date(1000*10);
	long end = endTime.getTime();
	while(true) {
		System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
		endTime = new Date(endTime.getTime() - 1000);
		Thread.sleep(1000);
		if(endTime.getTime() < 0) {
			break;
		}
	}
}
10.2.3 yield
/**
 * yield礼让线程
 * 暂停线程
 * 回到就绪状态
 * @author Administrator
 */
public class YieldDemo01 {
	public static void main(String[] args) {
		
		new Thread(()->{
			for(int i=0; i<100; i++) {
				System.out.println("lambda..."+i);
			}
		}).start();
		
		for(int i=0; i<100; i++) {
			if(i%20 == 0) {
				Thread.yield(); // 回到就绪状态
			}
			System.out.println("main..."+i);
		}
	}
}
10.2.4 join
join合并线程,等待此线程执行完毕,在执行其他线程,其他线程阻塞。
join(time) 可加毫秒数,time秒后不论插入线程是否执行完毕,此线程进入就绪状态
**
 * join合并线程,插队线程
 * 当前线程进入阻塞状态
 * 等待插入线程执行完毕
 * 当前线程进入就绪状态
 * @author Administrator
 *
 */
public class BlockedJoin01 {
	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(()->{
			for(int i=0; i<100; i++) {
				System.out.println("lambda..."+i);
			}
		});
		t.start();
		
		for(int i=0; i<100; i++) {
			System.out.println("main..."+i);
			if(i == 20) {
				t.join(); // 插队 主线程被阻塞了
			}
		}
	}
}
10.2.6 状态
/**
 * 观察线程的状态
 * NEW 新生状态
 * RUNNABLE 就绪与运行状态
 * TIMED_WAITING/WAITING 阻塞状态
 * TERMINATED 死亡状态
 * @author Administrator
 */
public class AllState {
	public static void main(String[] args) {
		Thread t = new Thread(()->{
			for(int i=0; i<10; i++) {
				if(i == 5) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("内容无所谓" + i);
			}
		});
		System.out.println(t.getState());
		t.start();
		System.out.println(t.getState());
		// 实时监听线程状态
		while(t.getState() != Thread.State.TERMINATED) {
			// 活动的线程数量
			//int num = Thread.activeCount();
			
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(t.getState());
		}
	}
}

10.3 线程调度

线程调度:是指特定机制为多个线程分配CPU的使用权。

有两种调度模型:分时调度和抢占式调度

分时调度:是让所有线程轮流获得CPU,且平均分配每个线程占用CPU的时间片。

抢占式调度:是指优先让运行池中优先级高的线程占用CPU,如果优先级相同,随机选一个。

调整方法:
1. 调整各个线程的优先级。
2. 让处于运行状态的线程调用Thread.sleep()方法。
3. 让处于运行状态的线程调用Thread.yield()方法。
4. 让处于运行状态的线程调用另一个线程的join()方法。
10.3.1 调整线程优先级

所有处于就绪状态的线程根据优先级存放于运行池中。

Thread类的setPriority(int)和getPriority()方法分别用来设置和读取优先级。优先级用整数(取值1~10)表示。

Thread类有以下3个静态常量。
1 MAX_PRIORITY:取值为10,表示最高的优先级。
2 MIN_PRIORITY:取值为1,表示最低的优先级。
3 NORM_PRIORITY:取值为5,表示默认优先级。
public class TestPriority {
	 public static void main(String[] args) {
		 Thread t1 = new Thread(new MyThread1());
		 Thread t2 = new Thread(new MyThread2());
		 t2.setPriority(Thread.MAX_PRIORITY);	// 设置线程对象t2为最高级
		 t1.start();		// 启动线程t1
		 t2.start();
	 }
}
class MyThread1 extends Thread {	// 通过继承Thread类来创建线程类
	 public void run() {
		 //*******获取线程的优先级***********
		 for (int i = 0; i < 10; i++) {
			 System.out.println(currentThread().getName() + ":"+currentThread().getPriority());
		 }
	 }
}
class MyThread2 extends Thread {
	 public void run() {
		 for (int i = 0; i < 10; i++) {
			 System.out.println(currentThread().getName() + ":"+currentThread().getPriority());
		 }
	 }
}
/**
 * 线程优先级 1-10
 * 1. NORM_PRIORITY 5 默认
 * 2. MIN_PRIORITY 1
 * 3. MAX_PRIORITY 10
 * 优先级是概率而不是绝对
 * @author Administrator
 */
public class PriorityTest {
	public static void main(String[] args) {
		MyPriority mp = new MyPriority();
		
		Thread t1 = new Thread(mp, "线程一");
		Thread t2 = new Thread(mp, "线程二");
		Thread t3 = new Thread(mp, "线程三");
		Thread t4 = new Thread(mp, "线程四");
		Thread t5 = new Thread(mp, "线程五");
		Thread t6 = new Thread(mp, "线程六");
		
		// 设置线程优先级在启动前
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.MIN_PRIORITY);
		t3.setPriority(Thread.MAX_PRIORITY);
		t4.setPriority(Thread.MIN_PRIORITY);
		t5.setPriority(Thread.MAX_PRIORITY);
		t6.setPriority(Thread.MIN_PRIORITY);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		t6.start();
	}
}

class MyPriority implements Runnable{
	@Override
	public void run() {
		// 线程礼让
		Thread.yield();
		
		// Thread.currentThread().getName() 获取线程名
		// Thread.currentThread().getPriority() 获取线程优先级
		System.out.println(Thread.currentThread().getName()+
				"-->"+
				Thread.currentThread().getPriority());
	}
}
10.3.2 守护线程
/**
 * 守护线程:是为用户线程服务的
 * JVM停止不用等待守护线程执行完毕
 * 默认:用户线程,JAM等待用户线程执行完毕才会停止
 * @author Administrator
 */
public class DemonTest {
	public static void main(String[] args) {
		Thread you = new Thread(new You());
		Thread god = new Thread(new God());
		
		// 设置守护线程
		god.setDaemon(true);
		
		god.start();
		you.start();
	}
}

class You implements Runnable{
	@Override
	public void run() {
		for(int i=1; i< 365; i++) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("活着的第" + i + "天");
		}
		System.out.println("见上帝去了。。。");
	}
}

class God implements Runnable{
	@Override
	public void run() {
		for(int i=1; i< 365*2; i++) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("上帝守护的第" + i + "天");
		}
	}
}
10.3.3 常用其他方法
isAlive()  判断线程是否还活着
setName()  给线程起一个名字
getName()  获取线程的名字
currentThread()  获取当前正在运行的线程对象,也就是获取自己本身
/**
 * 其他常用方法
 * Thread.currentThread() 当前线程
 * isAlive() 线程是否活着
 * setName() 在线程启动前设置名称
 * getName() 获取线程代理名称
 * @author Administrator
 *
 */
public class InfoTest {
	public static void main(String[] args) throws InterruptedException {
		// 线程是否还活着
		System.out.println(Thread.currentThread().isAlive());
		
		// 设置名称:真实角色
		MyInfo mi = new MyInfo("张三");
		
		Thread t = new Thread(mi);
		t.setName("李四");
		t.start();
		
		Thread.sleep(1000);
		System.out.println(t.isAlive());
	}
}
class MyInfo implements Runnable{
	private String name;
	@Override
	public void run() {
		System.out.println(
				Thread.currentThread().getName() 
				+ "-->" + this.name);
	}
	public MyInfo(String name) {
		super();
		this.name = name;
	}
}
10.3.4 线程让步

当前线程调用yield()方法暂时放弃CPU,给其他线程运行的机会。

public class YieldThread extends Thread {
	 public void run() {
		 for (int i = 1; i <= 10; i++) {
			 System.out.println(currentThread().getName() + " " );
			 yield(); 		// 暂时放弃CPU ,给其他线程运行的机会
		 }
	 }
	 public static void main(String[] args) {
		 YieldThread m1 = new YieldThread();
		 YieldThread m2 = new YieldThread();
		 m1.start();
		 m2.start();
	 }
}
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。
二者的区别如下:
(1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此可能给较低优先级的线程运行机会;而yield()方法只会给相同或更高优先级的线程运行机会。
(2)当线程执行了sleep(long millis)方法后,将转入阻塞状态,参数millis指定睡眠时间;而执行了yield()方法后,线程仍处于就绪态(随时可投入运行)。
10.3.5 合并线程

主线程调用客户线程的join()方法,等到客户线程运行结束主线程才恢复运行。

public class JoinThread {
	 public static void main(String[] args) {
		 MyThread4 mt = new MyThread4();
		 mt.start();
		 try {
			 mt.join();	// 挂起主线程,等待客户线程执行结束
		 } catch (InterruptedException e) {
		 }
		 for (int i = 1; i <= 5; i++) {
			 System.out.println("main Thread ");
		 }
	 }
}
class MyThread4 extends Thread {
	 public void run() {
		 for (int i = 1; i <= 5; i++) {
			 System.out.println(currentThread().getName());
		 }
	 }
}

10.4 后台线程

后台线程是为其他线程提供服务的线程,也称守护线程。

Thread类的setDaemon(true)方法把一个前台线程设置为后台线程,isDaemon()方法判断是否是后台线程。

public class SimpleDaemons implements Runnable {
	 int i = 0;
	 public void run() {
		 try {
			 while (i < 10) {
				 i++;
				 System.out.println(Thread.currentThread());
			 }
		 } catch (Exception e) {
			 System.out.println("sleep() interrupted");
		 }
	 }
	 public static void main(String[] args) throws Exception {
		 Thread daemon = new Thread(new SimpleDaemons());
		 daemon.setDaemon(true);// 将daemon线程设置为后台线程
		 daemon.start();	// 启动后台线程
		 System.out.println("main end");
	 }
}

10.5 线程互斥

定义:当两个或以上的线程进入某些程序段(称为“临界区”)读写同一个共享变量时。如何保证其中一个线程在临界区操作时,不让其他的线程进入临界区。

10.5.1 临界区

临界区:读/写同一 个共享变量的程序段。

最常见的典型情况是:两个或多个线程共享同一个数据 区。

class Share{	
	 private int a;
	 public Share() { 
  		 a = 0;	// 共享变量
  	 }
  	 public Share(int d){
  		 a = d;
  	 } 
 	 //临界区1,对共享变量数据加1 
  	 public void add() {
 		 try{ 
  			 Thread.sleep(10);
 		 }catch(Exception e) {
  			 System.out.println(e.getMessage());
  		 }	
		 a = a +1;
		 System.out.println("Add:"+a);
	 }
	 //临界区2,对共享变量数据减1  
	 public void dec() {
		 if(a>0) {
			 try { 
	  			 Thread.sleep(10);
	 		 }catch(Exception e) {
	  			 System.out.println(e.getMessage());
	  		 }
			 a = a - 1;
			 System.out.println("Dec:" + a);
		 }	
	 }
}
//对共享变量做加1操作的线程
class AddThread extends Thread {  
	 private Share s1;
   	 public AddThread(Share s3) { 	
   		 s1 = s3;
   	 }
   	 public void run() {	
   		 for(int i = 0; i <= 100; i++) {	
			 s1.add();	// 做加1操作
		 }
	 }
}
//对共享变量做减1操作的线程
class DecThread extends Thread {  
	 private Share s2;
   	 public DecThread(Share s4) { 
   		 s2 = s4;
   	 }
   	 public void run() {	
   		 for(int i = 0; i <= 100; i++) {
  			 try{ 
				 Thread.sleep(10);
			 }catch(Exception e) {
				 System.out.println(e.getMessage());
			 }
			 s2.dec();	// 做减1操作
		 }
	 }
}
public class Test {
	 public static void main(String[] args) {
		 Share share = new Share();
  		 new AddThread(share).start();
  		 new DecThread(share).start();
  		 new DecThread(share).start();
	 }
}
10.5.2 对象锁机制

格式一:

synchronized(任何对象){
    临界区
}

格式二:

public synchronized void fun{
    临界区
}
public void add(){	//法一
    synchronized(this){
        a=a+1;
        System.out.println(a);
    }
}
public synchronized void dec(){	//法二(推荐)
    if(a>0){
        a=a-1;
        System.out.println(a);
    }
}
10.5.3 显式Lock对象
private Lock locker = new ReentrantLock();
locker.lock();//加锁
try{
    ……
}finally{
    locker.unlock();//解锁
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

private Lock locker = new ReentrantLock();//创建Lock对象
//临界区1,对共享变量数据加1 
public void add() {
	locker.lock();//加锁
	try{ 
		Thread.sleep(10);
		a = a +1;
		System.out.println("Add:"+a);
	}catch(Exception e) {
		System.out.println(e.getMessage());
	}
	finally{
		locker.unlock();//解锁
	}
}

10.6 线程同步

线程同步:线程之间还需要相互协作来共同完成一些任务,这就是线程同步
并发:同一个对象多个线程同时操作
10.6.1 线程不安全
/**
 * 线程不安全
 * 有相同值或负数
 * @author Administrator
 */
public class UnsafeTest01 {
	public static void main(String[] args) {
		Web12306 web = new Web12306();
		
		new Thread(web, "张三").start();
		new Thread(web, "李四").start();
		new Thread(web, "王五").start();
	}
}

class Web12306 implements Runnable{
	// 票数
	private int ticketNums = 50;
	
	// 票数不为空标识符
	private boolean flag = true;

	@Override
	public void run() {
		while(flag) {
			test1();
		}
	}
		
	public void test1() {
		if(ticketNums < 1) {
			this.flag = false;
			return ;
		}
		// 模拟网络延迟
		try {
			Thread.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() 
				+ "" + ticketNums--);
	}
}
/**
 * 线程不安全:取钱
 * @author Administrator
 */
public class UnSafeTest02 {
	public static void main(String[] args) {
		// 账户
		Account account = new Account(100, "存款");
		
		// 取款
		Drawing you = new Drawing(account, 60, "你");
		Drawing wife = new Drawing(account, 80, "她");
		
		you.start();
		wife.start();
	}
}

// 模拟取款
class Drawing extends Thread{
	Account account; // 取钱账户
	int drawingMoney; // 取的钱数
	int drawTotal; // 口袋的钱
	
	public Drawing(Account account, int drawingMoney, String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}

	@Override
	public void run() {
		if(account.money - drawingMoney < 0) {
			return ;
		}
		// 模拟网络延迟
		try {
			sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		account.money -= drawingMoney;
		drawTotal += drawingMoney;
		System.out.println(account.name + "的账户余额:" + account.money);
		System.out.println(this.getName() + "口袋的钱:" + drawTotal);
	}
}
// 账户
class Account{
	int money; // 金钱
	String name; // 名称
	public Account(int money, String name) {
		super();
		this.money = money;
		this.name = name;
	}
}
10.6.2 线程安全
/**
 * 线程安全 synchronized
 * 不要锁错了对象
 * @author Administrator
 */
public class SafeTest01 {
	public static void main(String[] args) {
		Web1230 web = new Web1230();
		
		new Thread(web, "张三").start();
		new Thread(web, "李四").start();
		new Thread(web, "王五").start();
	}
}

class Web1230 implements Runnable{
	// 票数
	private int ticketNums = 100;
	
	// 票数不为空标识符
	private boolean flag = true;

	@Override
	public void run() {
		while(flag) {
			// 模拟网络延迟
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			test1();
		}
	}
	
	// 线程安全
	public synchronized void test1() {
		if(ticketNums < 1) {
			this.flag = false;
			return ;
		}
		// 模拟网络延迟
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() 
				+ "" + ticketNums--);
	}
}
10.6.3 线程同步
同步块:synchronized (obj){} obj称之为同步监视器
1、obi可以是任何对象,但是推荐使用共享资源作为同步监视器
2、同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,
  或class即 类的模子
同步监视器的执行过程:
 1、第一个线程访问,锁定同步监视器,执行其中代码
 2、第二个线程访问,发现同步监视器被锁定,无法访问
 3、第一个线程访问完毕,解锁同步监视器
 4、第二个线程访问,发现同步监视器未锁,锁定并访问
/**
 * 线程安全:取钱
 * 同步块,目标更明确
 * @author Administrator
 */
public class SafeTest02 {
	public static void main(String[] args) {
		// 账户
		Account2 account = new Account2(100, "存款");
		
		// 取款
		Drawing2 you = new Drawing2(account, 10, "你");
		Drawing2 wife = new Drawing2(account, 20, "她");
		
		you.start();
		wife.start();
	}
}

// 模拟取款
class Drawing2 extends Thread{
	Account2 account; // 取钱账户
	int drawingMoney; // 取的钱数
	int drawTotal; // 口袋的钱
	
	public Drawing2(Account2 account, int drawingMoney, String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}

	@Override
	public void run() {
		test1();
		// 模拟网络延迟
		try {
			sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	// 锁account
	public void test1() {
        // 提升性能
		if(account.money <= 0) {
			return ;
		}
		// 同步块
		synchronized(account) {
			if(account.money - drawingMoney < 0) {
				return ;
			}
			account.money -= drawingMoney;
			drawTotal += drawingMoney;
			System.out.println(account.name + "的账户余额:" + account.money);
			System.out.println(this.getName() + "口袋的钱:" + drawTotal);
		}
	}
}
// 账户
class Account2{
	int money; // 金钱
	String name; // 名称
	public Account2(int money, String name) {
		super();
		this.money = money;
		this.name = name;
	}
}
10.6.4 性能分析
/**
 * 线程安全 synchronized
 * 同步块
 * 性能分析
 * @author Administrator
 */
public class SafeTest04 {
	public static void main(String[] args) {
		Web12304 web = new Web12304();
		
		new Thread(web, "张三").start();
		new Thread(web, "李四").start();
		new Thread(web, "王五").start();
	}
}

class Web12304 implements Runnable{
	// 票数
	private int ticketNums = 1000;
	
	// 票数不为空标识符
	private boolean flag = true;

	@Override
	public void run() {
		long end = System.currentTimeMillis();
		while(flag) {
			// 模拟网络延迟
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			test3();
		}
		long now = System.currentTimeMillis();
		System.out.println(Thread.currentThread().getName()+"所用试间:"+(now-end));
	}
	
	// 同步块
	public void test3() {
		if (ticketNums < 1) {
			this.flag = false;
			return;
		}
		synchronized (this) {
			// 考虑最后一张票
			if (ticketNums < 1) {
				this.flag = false;
				return;
			}
			System.out.println(Thread.currentThread().getName() + "" + ticketNums--);
		}
	}
	
	// 同步块
	public void test2() {
		synchronized (this) {
			if (ticketNums < 1) {
				this.flag = false;
				return;
			}
			System.out.println(Thread.currentThread().getName() + "" + ticketNums--);
		}
	}
	
	// 同步方法
	public synchronized void test1() {
		if(ticketNums < 1) {
			this.flag = false;
			return ;
		}
		System.out.println(Thread.currentThread().getName() 
				+ "" + ticketNums--);
	}
}
10.6.5 影院抢票
/**
 * 快乐影院
 * 选位置抢票
 * @author Administrator
 */
public class HappyCinema2 {
	public static void main(String[] args) {
		// 创建影院
		Cinema2 cinema = new Cinema2(20, "快乐影院");
		
		// 利用数组传输位置编号
		new Thread(new Customer2(cinema, "张无忌", new int[]{1,20})).start();
		new Thread(new Customer2(cinema, "赵敏", new int[]{1,4})).start();
		new Thread(new Customer2(cinema, "周芷若", new int[]{5,6,7})).start();
	}
}

// 顾客
class Customer2 implements Runnable{
	private Cinema2 cinema; // 哪个影院
	private List<Integer> seats = new ArrayList<>(); // 哪几个位置
	private String name;

	public Customer2(Cinema2 cinema, String name, int[] arr) {
		this.cinema = cinema;
		this.name = name;
		// 将数组转化为容器
		for(int i:arr) {
			this.seats.add(i);
		}
	}

	@Override
	public void run() {
		boolean flag;
		synchronized (cinema) {
			flag = cinema.bookTickets(seats);
		}
		if(flag) {
			System.out.println("恭喜" +this.name + ",抢到" + seats + "号位置");
		}else {
			System.out.println(this.name + "购票失败!");
		}
	}
}

// 电影院
class Cinema2{
	List<Integer> available0 = new ArrayList<>(); // 原始位置
	List<Integer> available = new ArrayList<>(); // 剩余位置
	String name; // 名称
	public Cinema2(int nums, String name) {
		super();
		// 创建位置编号
		for(int i=1; i<=nums; i++) {
			this.available0.add(i);
			this.available.add(i);
		}
		this.name = name;
	}
	
	// 购票
	public boolean bookTickets(List<Integer> seats) {
		System.out.println("剩余位置" + available);
		if(!available0.containsAll(seats)) {
			seats.removeAll(available0);
			System.out.print(this.name + "中不存在" + seats + "号位置,");
			return false;
		}
		if(available.containsAll(seats)) {
			available.removeAll(seats); // 移除两容器中相同的元素
			return true;
		}else if(available.size() < seats.size()){
			System.out.print("位置不足,");
			return false;
		}else {
			seats.removeAll(available);
			System.out.print(seats + "号位置已被占用,");
			return false;
		}
	}
}
/**
 * 快乐影院
 * 同步方法
 * 顾客作代理
 * @author Administrator
 */
public class HappyCinema3 {
	public static void main(String[] args) {
		// 创建影院
		Cinema3 cinema = new Cinema3(20, "快乐影院");
		
		// 利用数组传输位置编号
		new Customer3(cinema, "张无忌", new int[]{1,22}).start();
		new Customer3(cinema, "赵敏", new int[]{1,4}).start();
		new Customer3(cinema, "周芷若", new int[]{5,6,7}).start();
	}
}

// 顾客 做代理
class Customer3 extends Thread{
	private List<Integer> seats = new ArrayList<>(); // 哪几个位置

	public Customer3(Runnable r, String name, int[] arr) {
		super(r, name);
		// 将数组转化为容器
		for(int i:arr) {
			this.seats.add(i);
		}
	}

	public List<Integer> getSeats() {
		return seats;
	}
}

// 电影院
class Cinema3 implements Runnable{
	List<Integer> available0 = new ArrayList<>(); // 原始位置
	List<Integer> available = new ArrayList<>(); // 剩余位置
	String name; // 名称
	public Cinema3(int nums, String name) {
		super();
		// 创建位置编号
		for(int i=1; i<=nums; i++) {
			this.available0.add(i);
			this.available.add(i);
		}
		this.name = name;
	}

	@Override
	public synchronized void run() {
		Customer3 customer = (Customer3)Thread.currentThread(); 
		List<Integer> seats = customer.getSeats();
		System.out.println("剩余位置" + available);
		if(!available0.containsAll(seats)) {
			seats.removeAll(available0);
			System.out.println(customer.getName() + "抢票失败," + this.name + "中无" + seats + "号位置!");
			return ;
		}
		if(available.containsAll(seats)) {
			available.removeAll(seats); // 移除两容器中相同的元素
			System.out.println("抢票成功,恭喜" + customer.getName() + "抢到" + seats + "号位置!");
			return ;
		}else if(available.size() < seats.size()){
			System.out.println(customer.getName() + "抢票失败," + "位置不足!");
			return ;
		}else {
			seats.removeAll(available);
			System.out.println(customer.getName() + "抢票失败," + seats + "号位置已被占用!");
			return ;
		}
	}
}
10.6.6 并发容器
/**
 * 线程安全:并发容器
 * @author Administrator
 */
public class SafeTest05 {
	public static void main(String[] args) throws InterruptedException {
		CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
		for(int i=0;i<100; i++) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(list.size());
	}
}
10.6.7 死锁
/**
 * 死锁 过多的同步造成相互不释放资源
 * 从而相互等待,一般发生于同步持有多个对象的锁
 * 相互持有对方需要的锁
 * 避免:不要在同一个代码块中,同时持有多个对象的锁
 * @author Administrator
 */
public class DeadLock {
	public static void main(String[] args) {
		new Makup(0, "小芳").start();
		new Makup(1, "小红").start();
	}
}

// 口红
class Lipstick{
}
// 镜子
class Mirror{
}
//化妆
class Makup extends Thread{
	static Lipstick likstick = new Lipstick(); // 一支口红
	static Mirror mirror = new Mirror(); // 一面镜子
	int choice; // 选择
	String name; // 名字
	public Makup(int choice, String name) {
		super();
		this.choice = choice;
		this.name = name;
	}

	@Override
	public void run() {
		// 化妆
		markup();
	}
	
	// 相互持有对方的对象锁 --> 可能造成死锁
	private void markup(){
		if(choice == 0) {
			synchronized (likstick) {
				System.out.println(this.name + "涂口红");
				
				// 2秒后想拥有镜子
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 套锁 造成死锁
//				synchronized (mirror) {
//					System.out.println(this.name + "照镜子");
//				}
			}
			synchronized (mirror) {
				System.out.println(this.name + "照镜子");
			}
		}else {
			synchronized (mirror) {
				System.out.println(this.name + "照镜子");
				
				// 1秒后想拥有口红
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 套锁 造成死锁
//				synchronized (likstick) {
//					System.out.println(this.name + "涂口红");
//				}
			}
			synchronized (likstick) {
				System.out.println(this.name + "涂口红");
			}
		}
	}
}
10.6.8 线程通信
应用场景:生产者与消费者的问题
解决方式:管程法、信用灯法
线程同步的方法:wati()   notify()	notifyAll()
final void wait()   表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
final void wait(long timeout) 指定等待的毫秒数
private int[] data = new int[10];
private int pos = 0;	// 指向第一个空闲空间,用于放入值
//**********临界区:向共享变量中放入值*************
public synchronized void put(int v) {
	if(pos > 9) {	// 空间已满
    	try{
        	System.out.println(Thread.currentThread().getName() + ":Producer wait");
            this.wait();	// 进入阻塞状态,等待消费者线程取值后唤醒
        } catch(InterruptedException e) { }
    }
    data[pos++] = v		// 放入值
    this.notifyAll();	// 不能忘记:有责任唤醒该临界区对象上可能有的
	// 等待取值的消费者线程,告之已有值可供取走
}
//**********临界区:从共享变量中取值*************
public synchronized int get() {				
    int value; 
    if(pos <= 0) {				// 空间全空,无值可取
    	try {
        	System.out.println(Thread.currentThread().getName() + ":Consumer wait");
            this.wait();			// 进入阻塞状态,等待生产者线程放入值后唤醒
        } catch(InterruptedException e) { 
            e.printStackTrace();
        }
    }
    value = data[--pos]; 	// 取值
    /*不能忘记:有责任唤醒该临界区对象上可能有的等待可用空间的生产者线程,告之已有空闲空间*/
    this.notifyAll();				
    return value;
}
class Producer extends Thread { 			// 生产者线程
private Share s1;	
    public Producer(Share s3)
    { 	s1 = s3;	}
    public void run() {	
        for (int i = 0; i < 100; i++ ) {
            s1.put(i);
            System.out.println("生产者: " + i); 
        }
    }
}
class Consumer extends Thread {			// 消费者线程
    private Share s2;
        public Consumer(Share s4) {
            s2 = s4;
        }
        public void run() {
            int v = 0;
        for (int i = 0; i < 100; i++ ) {
            v = s2.get();
            System.out.println("消费者: " + v); 
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Share share = new Share(); 				// 产生一个含有共享变量的对象
        Producer p = new Producer(share) ; 		// 产生一个生产者线程
        Consumer c = new Consumer(share);		// 产生一个消费者线程
        p.start();
        c.start();
    }
}
/**
 * 协作模型:生产者消费者
 * 方式一:管理流
 * 借助缓冲区
 * @author Administrator
 */
public class CoTest01 {
	public static void main(String[] args) {
		SynContainer container = new SynContainer();
		new Productor(container).start();
		new Consumer(container).start();
	}
}

// 生产者
class Productor extends Thread{
	// 缓存区
	SynContainer container;

	public Productor(SynContainer container) {
		super();
		this.container = container;
	}
	
	@Override
	public void run() {
		// 生产
		for(int i=1; i<100; i++) {
			System.out.println("生产第" + i + "馒头");
			container.push(new Steamedbun(i));
		}
	}
}
// 消费者
class Consumer extends Thread{
	// 缓存区
	SynContainer container;

	public Consumer(SynContainer container) {
		super();
		this.container = container;
	}
	@Override
	public void run() {
		// 消费
		for(int i=0; i<100; i++) {
			System.out.println("消费第" + container.pop().id + "馒头");
		}
	}
}
// 缓冲区
class SynContainer{
	// 馒头数组
	Steamedbun[] buns = new Steamedbun[10];
	// 计数器
	int count = 0;
	
	// 存储 生产
	public synchronized void push(Steamedbun bun) {
		if(count == buns.length) {
			try {
				// 线程阻塞 等待生产者通知消费 解除阻塞
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		buns[count++] = bun;
		this.notifyAll();
	}
	// 获取 消费
	public synchronized Steamedbun pop() {
		if(count == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		Steamedbun bun = buns[--count];
		this.notifyAll();
		return bun;
	}
}
// 馒头
class Steamedbun{
	int id;
	public Steamedbun(int id) {
		super();
		this.id = id;
	}
}
/**
 * 协作模型:生产者消费者
 * 方式二:信号灯法
 * 借助标志位
 * @author Administrator
 */
public class CoTest02 {
	public static void main(String[] args) {
		Tv tv = new Tv();
		new Player(tv).start();
		new Watcher(tv).start();
	}
}

// 生产者 演员
class Player extends Thread{
	Tv tv;
	public Player(Tv tv) {
		super();
		this.tv = tv;
	}

	@Override
	public void run() {
		for(int i=0; i<20; i++) {
			if(i%2==0) {
				this.tv.play("奇葩说");
			}else {
				this.tv.play("《创造101》");
			}
		}
	}
}
// 消费者 观众
class Watcher extends Thread{
	Tv tv;
	public Watcher(Tv tv) {
		super();
		this.tv = tv;
	}

	@Override
	public void run() {
		for(int i=0; i<20; i++) {
			tv.watch();
		}
	}
}
// 同一资源 电视
class Tv{
	String voice;
	// 信号灯
	// T 表示演员表演 观众等待
	// F 表示观众观看 演员等待
	boolean flag = true;
	
	// 表演
	public synchronized void play(String voice) {
		// 演员等待
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("表演了" + voice);
		this.voice = voice;
		// 切换信号灯
		flag = false;
		// 唤醒其他线程
		this.notifyAll();
	}
	
	// 观看
	public synchronized void watch() {
		 // 观众等待
		if(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("听到了" + this.voice);
		// 切换信号灯
		flag = true;
		// 唤醒其他线程
		this.notifyAll();
	}
}

10.7 高级主题

10.7.1 任务定时调度
某一个有规律的时间点干某件事:
    1.每天早上8点,闹钟响起
    2.每年4月1日自己给当年暗恋女神发一封匿名贺卡
    3.想每隔1小时,上传一下自己的学习笔记到云盘
通过Timer和Timetask,我们可以实现定时启动某个线程
    1.java. util. Timer:类似闹钟的功能,本身实现的就是一个线程
    2.java. util. TimerTask: 一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力
/**
 * 任务调度:
 * 1.Timer
 * 2.TimerTask
 * @author Administrator
 */
public class TimerTest01 {
	public static void main(String[] args) {
		Timer timer = new Timer();
		// 执行安排
		
		// 一秒后执行一次
//		timer.schedule(new MyTask(), 1000);
		
		// 一秒后开始执行,每隔0.5秒执行一次
//		timer.schedule(new MyTask(), 1000, 500);
		
		// Calendar 首次执行的时间
		// 1000*10 间隔秒数
		Calendar cal = new GregorianCalendar(2020,4,18,23,32,00);
		timer.schedule(new MyTask(), cal.getTime(),1000*10);
	}
}

// 任务类
class MyTask extends TimerTask{
	@Override
	public void run() {
		System.out.println(new SimpleDateFormat("YY-MM-dd HH:mm:ss").format(new Date()));
	}
}
10.7.2 quartz
Scheduler	调度器,控制所有的调度
Trigger		触发条件,采用DSL模式
JobDetail	需要处理的JOB
Job			执行逻辑

DSL: Domain-specific language领域特定语言,针对一个特定的领域,具有受限表达性的一种计算
机程序语言,即领域专用语言,声明式编程:
    1.Method Chaining 方法链 Fluent Style流畅风格,builder模式构建器
    2.Nested Functions 嵌套函数
    3.Lambda Expressions/Closures
    4.Functional Sequence
/**
 * quartz 学习入门
 * @author Administrator
 */
public class QuartzTest01 {
	public void run() throws Exception {
		// 1. 创建 Scheduler 的工厂
		SchedulerFactory sf = new StdSchedulerFactory();
		// 2.从工厂中获取调度器
		Scheduler sched = sf.getScheduler();

		// 3.创建JobDatail
		JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();

		// 开始运行的时间 下一秒
		Date runTime = evenSecondDateAfterNow();
				
		// 4.触发条件
		Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();

		// 5.注册任务和触发条件
		sched.scheduleJob(job, trigger);

		// 6.启动
		sched.start();

		try {
			// 5秒后停止
			Thread.sleep(5L * 1000L);
		} catch (Exception e) {
		}

		// 线程结束
		sched.shutdown(true);
	}

	public static void main(String[] args) throws Exception {
		QuartzTest01 example = new QuartzTest01();
		example.run();
	}
}

/**
 * 任务
 * @author Administrator
 */
public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
        throws JobExecutionException {
    	System.out.println("------start------");
        System.out.println("Hello World! - " + new Date());
        System.out.println("------end------");
    }
}
/**
 * quartz 框架
 * 时间 次数
 * @author Administrator
 */
public class QuartzTest02 {

	public void run() throws Exception {
		// 1.创建 Scheduler 的工厂
		SchedulerFactory sf = new StdSchedulerFactory();

		// 2.从工厂中获取调度器
		Scheduler sched = sf.getScheduler();

		// 3.创建JobDatail
		JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();

		// 开始时间 15秒后
		Date startTime = DateBuilder.nextGivenSecondDate(null, 5);

		// 4.触发条件 
		// 指定时刻,不重复执行
		SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime)
				.build();

		// 5.注册任务和触发条件
		Date ft = sched.scheduleJob(job, trigger);

		// 3.创建JobDatail-2
		job = newJob(HelloJob.class).withIdentity("job2", "group1").build();

		// 4.触发条件
		trigger = (SimpleTrigger) newTrigger().withIdentity("trigger2", "group1").startAt(startTime).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 3.创建JobDatail-3
		job = newJob(HelloJob.class).withIdentity("job3", "group1").build();

		// 4.触发条件
		// 指定时刻,然后每隔5秒触发一次,共重复触发10次
		trigger = newTrigger().withIdentity("trigger3", "group1").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(10)).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 4.触发条件
		// 指定时刻,然后每隔10秒触发一次,共重复触发2次
		trigger = newTrigger().withIdentity("trigger3", "group2").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(2)).forJob(job).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(trigger);

		// 3.创建JobDatail-4
		job = newJob(HelloJob.class).withIdentity("job4", "group1").build();

		// 4.触发条件
		// 指定时刻,然后每隔10秒触发一次,共重复触发5次
		trigger = newTrigger().withIdentity("trigger4", "group1").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(5)).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 3.创建JobDatail-5
		job = newJob(HelloJob.class).withIdentity("job5", "group1").build();

		// 4.触发条件
		// 2分钟后执行一次
		trigger = (SimpleTrigger) newTrigger().withIdentity("trigger5", "group1")
				.startAt(futureDate(2, IntervalUnit.MINUTE)).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 3.创建JobDatail-6
		job = newJob(HelloJob.class).withIdentity("job6", "group1").build();

		// 4.触发条件
		// 指定时刻,然后每隔30秒触发一次,永不停歇
		trigger = newTrigger().withIdentity("trigger6", "group1").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInSeconds(30).repeatForever()).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 6.启动
		sched.start();

		// 3.创建JobDatail-7
		job = newJob(HelloJob.class).withIdentity("job7", "group1").build();

		// 4.触发条件
		// 指定时刻,然后每隔1分钟触发一次,共重复触发2次
		trigger = newTrigger().withIdentity("trigger7", "group1").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInMinutes(1).withRepeatCount(2)).build();

		// 5.注册任务和触发条件
		ft = sched.scheduleJob(job, trigger);

		// 3.创建JobDatail-8
		job = newJob(HelloJob.class).withIdentity("job8", "group1").storeDurably().build();

		// 添加任务
		sched.addJob(job, true);

		sched.triggerJob(jobKey("job8", "group1"));

		try {
			Thread.sleep(30L * 1000L);
		} catch (Exception e) {
		}

		// 4.触发条件
		// 指定时刻,然后每隔1分钟触发一次,共重复触发3次
		trigger = newTrigger().withIdentity("trigger7", "group1").startAt(startTime)
				.withSchedule(simpleSchedule().withIntervalInMinutes(1).withRepeatCount(3)).build();

		// 5.注册任务和触发条件
		ft = sched.rescheduleJob(trigger.getKey(), trigger);
		
		try {
			Thread.sleep(300L * 1000L);
		} catch (Exception e) {
		}

		sched.shutdown(true);

		SchedulerMetaData metaData = sched.getMetaData();
	}

	public static void main(String[] args) throws Exception {
		QuartzTest02 quartz = new QuartzTest02();
		quartz.run();
	}
}
10.7.3 happenbefore
指令重排:
	执行代码的顺序可能与编写代码不一致,即虚拟机优化代码顺序,则为指令重排happen-before
	即:编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。
数据依赖:
	如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。
/**
 * 指令重排:代码执行顺序与预期不一致
 * 目的:提高性能
 * @author Administrator
 */
public class HappenBefore {
	// 变量1
	private static int n = 0;
	// 变量2
	private static boolean flag = false;
	public static void main(String[] args) throws InterruptedException {
		// 线程1 更改数据
		Thread t1 = new Thread(()->{
			n = 1;
			flag = true;
		});
		
		// 线程2 读取数据
		Thread t2 = new Thread(() -> {
			if(flag) {
				n*=1;
			}
			if(n==0) {
				System.out.println("指令重排:n=" + n);
				// 指令重排:n=0
			}
		});
		t2.start();
		t1.start();
		t1.join();
		t2.join();
	}
}
10.7.3 volatile
volatile:保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,
在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:
    1.线程对变量进行修改之后,要立刻回写到主内存。
    2.线稈对变量读取的时候,更从主内存中读,而不是缓存。
注:
	各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,
不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,
即为了提高执行效率。
volatile是不错的机制,但是volatile不能保证原子性。
/**
 * volatile 用于保证数据的同步
 * 轻量级同步
 * @author Administrator
 */
public class VolatileTest {
	// 只保证num的同步
	private volatile static int num = 0;
	public static void main(String[] args) throws InterruptedException {
		new Thread(()->{
			while(num ==0) {
				
			}
		}).start();
		
		Thread.sleep(2000L);
		num = 1;
	}
}
10.7.4 单例模式
/**
 * 单例模式 : 在懒汉式套路基础上加入并发控制
 * 保证在多线程环境下,对外存在一个对象
 * 1、构造器私有化,避免外部new构造器
 * 2、提供私有的静态属性,存储对象的地址
 * 3、提供公共的静态方法,获取属性
 * @author Administrator
 */
public class DoubleCheckedLocking {
	// 2、提供私有的静态属性
	// 没有volatile其他线程可能访问一个没有初始化的对象
	private static volatile DoubleCheckedLocking instance;
	
	// 1、构造器私有化
	private DoubleCheckedLocking() {
		
	}
	
	// 3、提供公共的静态方法
	private static DoubleCheckedLocking getInstance() {
		// 再次检测
		if(null != instance) { // 已存在对象时,避免不必要的同步
			return instance;
		}
		synchronized (DoubleCheckedLocking.class) {
			if(null == instance) {
				instance = new DoubleCheckedLocking();
				// 1.开辟空间// 2.初始化对象信息// 3.返回对象的地址引用 
			}
			return instance;
		}
	}
	
	private static DoubleCheckedLocking getInstance1(long time) {
	
		if (null == instance) {
			try {
				Thread.sleep(time);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			instance = new DoubleCheckedLocking();
			// 1.开辟空间// 2.初始化对象信息// 3.返回对象的地址引用
		}
		return instance;
	}
	
	public static void main(String[] args) {
		Thread t = new Thread(()->{
			System.out.println(DoubleCheckedLocking.getInstance());
		});
		t.start();
		
		System.out.println(DoubleCheckedLocking.getInstance());
	}
}
10.7.5 Threadlocal
	在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,
因为局部变量只有线程自己能看见,不会影响其他线程。
	ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到
线程安全的目的。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全,常用的方法,
就是get/set/initialVajue方法。
	JDK建议ThreadLocal定义为private static
	ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,
这样一个线程的所有调用到的方法都可以非常方便地访问这些资源。
	Hibernate的Session工具类HibernateUtil通过不同的线程对象设置Bean属性,保证各个线
程Bean对象的独立性。
/**
 * ThreadLocal:每个线程自身的存储 本地、局部区域
 * get/set/initialVajue
 * @author Administrator
 *
 */
public class ThreadLocalTest01 {
//	private static ThreadLocal<Integer> threadlocal = new ThreadLocal<>();
	// 更改初始值 方法一
//	private static ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() {
//		protected Integer initialValue() {
//			return 200;
//		}
//	};
	// 更改初始值 方法二
	private static ThreadLocal<Integer> threadlocal = ThreadLocal.withInitial(()->{
		return 200;
	});
	
	public static void main(String[] args) {
		// 获取值
		System.out.println(Thread.currentThread().getName() + 
				"-->" + threadlocal.get());
		
		// 设置值
		threadlocal.set(99);
		
		new Thread(new MyRun(100)).start();
		new Thread(new MyRun(88)).start();
		
		System.out.println(Thread.currentThread().getName() + 
				"-->" + threadlocal.get());
	}
	
	public static class MyRun implements Runnable{
		private int value;
		
		public MyRun(int value) {
			super();
			this.value = value;
		}

		@Override
		public void run() {
			threadlocal.set(value);
			System.out.println(Thread.currentThread().getName() + 
					"-->" + threadlocal.get());
		}
	}
}
/**
 * InheritableThreadLocal 继承上下文环境的数据
 * 将数据拷贝一份给子线程
 * @author Administrator
 */
public class ThreadLocalTest02 {
	// 初始值设为1
	private static ThreadLocal<Integer> threadlocal = new InheritableThreadLocal<>();
	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + 
				"-->" + threadlocal.get());
		threadlocal.set(2);
		
		// 此线程有main线程开辟
		new Thread(()->{
			System.out.println(Thread.currentThread().getName() + 
					"-->" + threadlocal.get());
		}).start();
	}
}
10.7.6 可重入锁
锁作为并发共享数据保证一致性的工具, 大多数内置锁都是可重入的,也就是说,如果某个线程试图获取
一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同
步代码块时,计数器将会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次企图获得
锁时将会进入死锁状态。可重入锁随处可见。
/**
 * 可重入锁
 * 锁可以延续使用
 * @author Administrator
 */
public class LockTest01 {
	public void test1() {
		// 第一次获得锁
		synchronized (this) {
			while(true) {
				// 第二次获得同样的锁
				synchronized (this) {
					System.out.println("可重入锁");
				}
				try {
					Thread.sleep(1000L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		new LockTest01().test1();
	}
}
/**
 * 不可重入锁:锁不可以延续使用
 * @author Administrator
 */
public class LockTest02 {
	UnLock lock = new UnLock();
	public void a() throws InterruptedException {
		lock.lock();
		b(); // 进入死循环
		lock.unlock();
	}
	public void b() throws InterruptedException {
		lock.lock();
		System.out.println("敲代码啦!");
		lock.unlock();
	}
	
	public static void main(String[] args) throws InterruptedException {
		new LockTest02().a();
	}
}

// 不可重入锁
class UnLock{
	// 是否占用
	private boolean isLocked = false;
	// 使用锁
	public synchronized void lock() throws InterruptedException {
		Thread t = Thread.currentThread();
		while(isLocked) {
			wait();
		}
		isLocked = true;
	}
	// 释放锁
	public synchronized void unlock() {
		isLocked = false;
		notify();
	}	
}
/**
 * 可重入锁,可以延续使用,有计数器
 * @author Administrator
 */
public class LockTest03 {
	Lock lock = new Lock();
	public void a() throws InterruptedException {
		lock.lock();
		System.out.println(lock.getHoldCount());
		b();
		lock.unlock();
		System.out.println(lock.getHoldCount());
	}
	public void b() throws InterruptedException {
		lock.lock();
		System.out.println("敲代码啦!");
		System.out.println(lock.getHoldCount());
		lock.unlock();
	}
	
	public static void main(String[] args) throws InterruptedException {
		LockTest03 lock = new LockTest03();
		lock.a();
		lock.b();
	}
}
//可重入锁
class Lock{
	// 是否占用
	private boolean isLocked = false;
	// 存储线程   锁被谁占用
	Thread lockedBy = null;
	// 锁的计数器
	private int holdCount = 0;
	
	// 使用锁
	public synchronized void lock() throws InterruptedException {
		Thread t = Thread.currentThread();
		while(isLocked && lockedBy != t) {
			wait();
		}
		isLocked = true;
		lockedBy = t;
		holdCount ++;
	}
	
	// 释放锁
	public synchronized void unlock() {
		Thread t = Thread.currentThread();
		if(lockedBy == t) {
			holdCount --;
			if(holdCount == 0) {
				isLocked = false;
				notify();
				lockedBy = null;
			}
		}
	}

	public int getHoldCount() {
		return holdCount;
	}
}
/**
 * ReentrantLock 可重入锁
 * @author Administrator
 */
public class LockTest04 {
	ReentrantLock lock = new ReentrantLock();
	public void a() throws InterruptedException {
		lock.lock();
		System.out.println(lock.getHoldCount());
		b();
		lock.unlock();
		System.out.println(lock.getHoldCount());
	}
	public void b() throws InterruptedException {
		lock.lock();
		System.out.println("敲代码啦!");
		System.out.println(lock.getHoldCount());
		lock.unlock();
		System.out.println(lock.getHoldCount());
	}
	
	public static void main(String[] args) throws InterruptedException {
		LockTest04 test = new LockTest04();
		test.a();
	}
}
10.7.7 锁的分类
锁分为两类: 
1、悲观锁: synchronized是独占锁即悲观锁,会导致其它所有需要锁的线程挂起,
等待持有锁的线程释放锁。
2、乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就
重试,直到成功为止。

Compare and Swap比较并交换:
乐观锁的实现;
    1、有三个值:一个当 前内存值V、旧的预期值A、将更新的值B。先获取到内存当中
    当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返
    回true,否则什么都不做,并返回false;
    2、CAS是一-组原子操作,不会被外部打断;
    3、属于硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。
    4、ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,
    那能说明它的值没有被其他线程修改过了吗?如果在这段期间曾经被改成B,然后又改
    回A,那CAS操 作就会误认为它从来没有被修改过。
/**
 * CAS:比较并交换
 * @author Administrator
 */
public class CAS {
	// 库存 5件
	private static AtomicInteger stock = new AtomicInteger(5);
	public static void main(String[] args) {
		for(int i=0; i<10; i++) {
			new Thread(()->{
				// 模拟网络延迟
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 调用decrementAndGet() 时 库存数量减一
				Integer left = stock.decrementAndGet();
				if(left < 0) {
					System.out.println("手机抢完了。。\n" 
				+ Thread.currentThread().getName() + "手速慢,未抢到手机");
					return ;
				}
				System.out.println(Thread.currentThread().getName() 
						+ "抢到一部手机--->剩余手机" + left + "部");
			}).start();
		}
	}
}

第十一章 图形用户界面

一、Swing简介
Swing 容器
在Swing中有三种可以使用的顶层容器,分别是 JFrame、JDialog 和 JApplet:
1.JFrame:用于框架窗口的类,此窗口带有边框、标题、关闭和最小化窗口的图标。
	带 GUI 的应用程序至少使用一个框架窗口。
2.JDialog:用于对话框的类。
3.JApplet:用于使用 Swing 组件的 Java Applet 类。

常见的中间容器有 JPanel、JScrollPane、JTabbedPane 和 JToolBar:
1.JPanel:表示一个普通面板,是最灵活、最常用的中间容器。
2.JScrollPane:与 JPanel 类似,但它可在大的组件或可扩展组件周围提供滚动条。
3.JTabbedPane:表示选项卡面板,可以包含多个组件,但一次只显示一个组件,用户可在组件之间方便地切换。
4.JToolBar:表示工具栏,按行或列排列一组组件(通常是按钮)。

11.1 窗口

setExtendedState(JFrame.MAXIMIZED_BOTH);//界面显示最大化
setUndecorated(true); // 去掉窗口的装饰,删除标题栏
getRootPane().setWindowDecorationStyle(JRootPane.NONE);//采用指定的窗口装饰风格
getRootPane().setWindowDecorationStyle()方法为窗口指定以下的装饰风格: 

1.NONE                         无装饰(即去掉标题栏)
2.FRAME                        普通窗口风格
3.PLAIN_DIALOG                 简单对话框风格
4.INFORMATION_DIALOG           信息对话框风格
5.ERROR_DIALOG                 错误对话框风格
6.COLOR_CHOOSER_DIALOG     	   拾色器对话框风格
7.FILE_CHOOSER_DIALOG          文件选择对话框风格
8.QUESTION_DIALOG              问题对话框风格
9.WARNING_DIALOG               警告对话框风格

setMinimumSize(new Dimension(500,400));//设置窗口最小的界面
setMaximumSize(new Dimension(500,400));//设置窗口最大的界面
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);//设置关闭按钮事件
setIconImage(Toolkit.getDefaultToolkit().createImage("pic.jpg"));//设置标题栏上左上角的图标 

frame.setExtendedState(JFrame.MAXIMIZED_BOTH);// 界面显示最大化
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);// 设置关闭按钮事件
frame.setMinimumSize(new Dimension(500, 400));// 设置窗口最小的界面
frame.setMaximumSize(new Dimension(500, 400));// 设置窗口最大的界面
11.1.1 Frame窗口
public class Myrame {
	public static void main(String args[]) {	
		Frame fr = new Frame();				
		fr.setTitle("窗口");	// 设定窗体标题
		fr.setSize(400,300);	// 设定窗体的宽度为400,高度为300
		fr.setBackground(Color.green);	// 设定窗体的背景色为绿色
		fr.setLocation(300,500);	// 设定窗体左上角的初始位置为(300,500)
		fr.setResizable(false);		// 设定窗体为不可调整大小
		fr.setVisible(true); 		// 将窗体设为可见
	}
}
11.1.2 JFrame窗口
JFrame 类的常用构造方法:
	JFrame():构造一个初始时不可见的新窗体。
	JFrame(String title):创建一个具有 title 指定标题的不可见新窗体。
JFrame 类中的常用方法:	
		方法名称								概述
	getContentPane()						返回此窗体的 contentPane 对象
	getDefaultCloseOperation()				返回用户在此窗体上单击“关闭”按钮时执行的操作
	setContentPane(Container contentPane)	设置 contentPane 属性
	setDefaultCloseOperation(int operation)	设置用户在此窗体上单击“关闭”按钮时默认执行的操作
	setDefaultLookAndFeelDecorated (boolean
	defaultLookAndFeelDecorated)			设置 JFrame 窗口使用的 Windows 外观(如边框、关闭窗口的 小部件、标题等)
	setIconImage(Image image)				设置要作为此窗口图标显不的图像
	setJMenuBar( JMenuBar menubar)			设置此窗体的菜单栏
	setLayout(LayoutManager manager)		设置 LayoutManager 属性
例1:
	setTitle("Java 第一个 GUI 程序");    //设置显示窗口标题
    setSize(400,200);    //设置窗口显示尺寸
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);    //置窗口是否可以关闭
    JLabel jl=new JLabel("这是使用JFrame类创建的窗口");    //创建一个标签
    Container c=getContentPane();    //获取当前窗口的内容窗格
    c.add(jl);    //将标签组件添加到内容窗格上
    setVisible(true);    //设置窗口是否可见
11.1.3 面板JPanel

JPanel 面板
JPanel 类的构造方法:
JPanel():使用默认的布局管理器创建新面板,默认的布局管理器为 FlowLayout。
JPanel(LayoutManagerLayout layout):创建指定布局管理器的 JPanel 对象。

JPanel类的常用方法:
方法名及返回值类型 说明
Component add(Component comp) 将指定的组件追加到此容器的尾部
void remove(Component comp) 从容器中移除指定的组件
void setFont(Font f) 设置容器的字体
void setLayout(LayoutManager mgr) 设置容器的布局管理器
void setBackground(Color c) 设置组件的背景色

11.2 按钮

JButton b1=new JButton("开始");//按钮

11.3 文本框

//文本框 (JTextField/JTextArea)
JTextField 是一个轻量级组件,它允许编辑单行文本
JTextField(int columns) 构造一个具有指定列数的新的空 TextField。
JTextField(String text) 构造一个用指定文本初始化的新TextField。
JTextField(String text, int columns) 构造一个用指定文本和列初始化的新TextField。
tf.SetText(string);//设置文本域中的文本值
tf.GetText();//获取文本域中的输入文本值
tf.getColumns();//返回文本域的列数
tf.setEditable(true)// 设置文本域是否为只读状态
JTextArea 在使用时通常把它放到 JScrollPane 容器中来使用,以此来实现内容增多时可水平/垂直滚动的效果。
JTextArea ta= new JTextArea(5, 10);//创建一个 5 行 10 列的文本区域
ta.setLineWrap(true);// 设置自动换行,默认为禁止自动换行(false)
ta.setWrapStyleWord(true);//置自动换行行方式,true则将在单词边界(空白)处换行;false则将在字符边界处换行,默认为 false。
ta.setTabSize (4);//设置制表符的大小为8个字符,初始值为4个字符
ta.setBackground (Color.white);//文本区背景
ta.setForeground (Color.red);//字体颜色
ta.setFont(new Font("SansSerif", Font.PLAIN, 14));//字体样式
ta.getLineCount();//获取文本内容
ta.append(String str);//在末尾追加内容
ta.setText(String text)//设置文本内容
ta.getLineCount()//获取内容的行数(以换行符计算,满行自动换下一行不算增加行数)
ta.getLineEndOffset(int line)// 获取指定行(行数从0开始)的行尾(包括换行符)在全文中的偏移量
ta.setCaretColor(Color c)//光标颜色
ta.setSelectionColor(Color c)//选中部分的背景颜色
ta.setSelectedTextColor(Color c)//选中部分文本的颜色
ta.setDisabledTextColor(Color c)//不可用时文本的颜色
ta.setEditable(true)//文本框是否可编辑
ta.addFocusListener(FocusListener listener)// 添加焦点事件监听器
ta.getDocument().addDocumentListener(DocumentListener listener)// 添加文本框内的 文本改变 监听器
ta.addKeyListener(KeyListener listener)// 添加按键监听器
11.3.1 标签(JLabel)
1)  调用无参构造函数
	JTable table = new JTable();
2)  以表头和表数据创建表格.
	Object[][] values = {{"row1-col1", "row1-col2"},{"row2-col1", "row2-col2"}};
	String[] header = {"col1", "col2"};
	JTable table1 = new JTable(cellData, columnNames);
3)  以表头和表数据创建mod表格
	DefaultTableModel model = new DefaultTableModel
	table2 = new JTable(model);
JLabel label1 = new JLabel("默认表格");//标签
JLabel label2 = new JTable (8, 6);//8行6列的表格
tabel2.setRowHeight (30);//设置每行的高度为20
label1.setHorizontalAlignment(SwingConstants.CENTER);//标签内容水平居中
Table.setFont(new Font("微软雅黑",Font.PLAIN,16));//标签字体,大小
panel.add(label1,BorderLayout.NORTH);//面板添加标签,边界布局(北)
11.3.2 单行文本
1.输入框:
    JLabel jl1=new JLabel("账号:");
    JTextField t1=new JTextField("");//单行文本框
    t1.setText("请输入账号");
2.密码框:
    JLabel jl2=new JLabel("密码:");
    JPasswordField t2=new JPasswordField("");//密码框
    t2.setText("请输入密码");
    jl2.setBounds(250,600,40,30);
    t2.setBounds(290,600,110,30);//设置输入框位置与大小
11.3.3 多行文本
JLabel jl3=new JLabel("文本域:");//文本框
JTextArea ta=new JTextArea();//多行输入
ta.setText("人生苦短,\n就学python!\n");
ta.append("这是未来发展的主流方向");//追加数据 
ta.setLineWrap(true);//自动换行

11.4 单选与多选

11.4.1 单选
JRadioButton rb1=new JRadioButton("男");//创建单选框
rb1.setBounds(50,530,80,30);
rb1.setSelected(true);//默认被选中
JRadioButton rb2=new JRadioButton("女");
rb2.setBounds(130,530,80,30);
ButtonGroup bg=new ButtonGroup();//创建按钮组
bg.add(rb1);//将单选按钮添加进按钮组(排他性)
bg.add(rb2);
11.4.2 多选
JCheckBox cb1=new JCheckBox("C语言");//创建多选框
cb1.setBounds(50,500,80,30);
JCheckBox cb2=new JCheckBox("C++");
cb2.setBounds(140,500,80,30);
JCheckBox cb3=new JCheckBox("Python");
cb3.setBounds(230,500,80,30);
JCheckBox cb4=new JCheckBox("JAVA");
cb4.setSelected(true);//默认被选中
cb4.setBounds(320,500,80,30);
System.out.println(cb1.isSelected());//判断是否被选中

11.5 下拉框

String[] str=new String[]{"北京","上海","广东","深圳"};//下拉框条目
JComboBox cs=new JComboBox(str);//下拉框
cs.setBounds(50,560,80,30);

11.6 进度条与滚动条

11.6.1 进度条
JProgressBar pb=new JProgressBar();//进度条
pb.setMaximum(100);//进度条最大值
pb.setValue(50);//当前进度
pb.setStringPainted(true);//显示当前进度
pb.setBounds(100,750,300,30);
11.6.2 滚动条
static TextArea ta = new TextArea(); 	//多行文本框
JScrollPane jp = new JScrollPane(ta, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

JScrollPane scrollPane1 = new JScrollPane();//产生一个带滚动条的面板
frame.getContentPane().add(scrollPane,BorderLayout.CENTER);//窗口添加滚动条,边界布局(中)
//JTextArea text=new JTextArea();//文本框
//JScrollPane scrollPane1 = new JScrollPane(text);//把文本框放在滚动条面板中,可实现水平滚动条
//frame.add(scrollPane);
//scrollPanese.tHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);//设置水平滚动条自动出现
//scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);//设置垂直滚动条自动出现
//js.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);//设置水平总是出现
//js.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置垂直滚动条总是出现
//js.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);//设置水平总是隐藏
//js.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);//设置垂直滚动条总是隐藏

11.7 弹出对话框

11.7.1 基础知识
JOptionPane主要用到四种消息提示框方法: 
	showConfirmDialog():确认对话框 
	showInputDialog():输入对话框 
	showMessageDialog():消息对话框 
	showOptionDialog():选择对话框
	
主要有五种消息类型,类型不同,图标不同: 
	• ERROR_MESSAGE //
	• INFORMATION_MESSAGE //
	• WARNING_MESSAGE //
	• QUESTION_MESSAGE //
	• PLAIN_MESSAGE 	//
参数及其含义: 
	parentComponent 对话框所在的容器 
	message 提示消息 
	title 标题 
	optionType 选择按钮类型 
	messageType 消息类型 
	icon 自定义消息图标 
	initialSelectionValue 默认选项或信息 
	selectionValues 选择选项 
	options 操作选项	
JOptionPane.showConfirmDialog有四种参数设置类型 //确认对话框
JOptionPane.showConfirmDialog(parentComponent, message) 
JOptionPane.showConfirmDialog(parentComponent, message, title, optionType) 
JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType) 
JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon)

JOptionPane.showInputDialog有六种参数设置类型 //输入对话框 
JOptionPane.showInputDialog(message); 
JOptionPane.showInputDialog(parentComponent, message); 
JOptionPane.showInputDialog(message, initialSelectionValue); 
JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue) 
JOptionPane.showInputDialog(parentComponent, message, title, messageType); 
JOptionPane.showInputDialog(parentComponent, message, title, messageType, icon, selectionValues, initialSelectionValue)

JOptionPane.showMessageDialog有三种参数设置 //消息对话框
JOptionPane.showMessageDialog(parentComponent, message); 
JOptionPane.showMessageDialog(parentComponent, message, title, messageType); 
JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon);

JOptionPane.showOptionDialog只有一种参数设置 //选择对话框
JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue)
11.7.2 三种基本模式
1.三种基本模式 
JOptionPane.showComfirmDialog(null,”我的新世界”); 
我的新世界
是 否 取消

JOptionPane.showInputDialog(null,”我的新世界”); 
 我的新世界
 确定 取消
 
JOptionPane.showMessage(null,”我的新世界”); 
我的新世界
确定
11.7.3 消息类型
MessageType 共有五种 
错误: JOptionPane.showMessageDialog(null, “错误”,”提示”,JOptionPane.ERROR_MESSAGE); 
警示: JOptionPane.showMessageDialog(null, “警告”,”提示”,JOptionPane.WARNING_MESSAGE); 
普通信息:JOptionPane.showMessageDialog(null, “普通”,”提示”,JOptionPane.INFORMATION_MESSAGE); 
询问信息:JOptionPane.showMessageDialog(null, “提问信息”,”提示”,JOptionPane.QUESTION_MESSAGE);
不带图标信息:JOptionPane.showMessageDialog(null, “不带图标”,”提示”,JOptionPane.PLAIN_MESSAGE); 
11.7.4 自定义消息图标 icon
ImageIcon icon = new ImageIcon(“image/c.jpg”);//图片的大小需要调整到合适程度 
JOptionPane.showMessageDialog(null, “自定义图标”,”提示”,JOptionPane.ERROR_MESSAGE,icon); 
该消息框的警示信息图标被后面的参数icon所指向的图标覆盖 
11.7.5 可选按钮optionType
4.可选按钮optionType(存在效果相同的参数变量) 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.OK_OPTION); 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.YES_OPTION); 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.YES_NO_OPTION);

JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.NO_OPTION); 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.YES_NO_CANCEL_OPTION); 

JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.CANCEL_OPTION); 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.OK_CANCEL_OPTION); 

JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.CLOSED_OPTION); 
JOptionPane.showConfirmDialog(null, “我的新世界”, “提示”,JOptionPane.DEFAULT_OPTION);
11.7.6 扩展知识
5.在输入对话框设置下拉菜单选择框 
Object[] fruits = {“苹果”,”梨子”,”香蕉”,”西瓜”,”荔枝”}; 
JOptionPane.showInputDialog(null,”你喜欢什么水果”,”标题”,JOptionPane.QUESTION_MESSAGE,null,fruits,fruits[2]); 
最后一个参数是预选项,你希望显示出来的选项。 

6.在选择对话框设置选项 
Object[] fruits = {“苹果”,”梨子”,”香蕉”,”西瓜”,”荔枝”}; 
JOptionPane.showOptionDialog(null, “你喜欢什么水果”, “标题”,JOptionPane.YES_NO_CANCEL_OPTION ,JOptionPane.QUESTION_MESSAGE,null, fruits, fruits[0]); 

7.对消息框传递的消息进行接收 
接收输入框输入的信息 
String str = (String)JOptionPane.showInputDialog(null);

接收并判断点击的按钮是哪个,用int对象op接收对话框返回的值,并用if语句判断 
int op = JOptionPane.showConfirmDialog(null,”新世界”,”提示”,JOptionPane.YES_NO_CANCEL_OPTION); 
if(op==JOptionPane.YES_OPTION){ 
}else if(op==JOptionPane.NO_OPTION){ 
} 
接收选择对话框的消息(必须用数组下标接收) 
Object[] fruits = {“苹果”,”梨子”,”香蕉”,”西瓜”,”荔枝”}; 
int op = JOptionPane.showOptionDialog(null, “你喜欢什么水果”, “标题”,JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE,null, fruits, fruits[0]); 
System.out.print((String)fruits[op]);

接收输入对话框带有下拉列表框的信息(必须用字符串接收) 
Object[] fruits = {“苹果”,”梨子”,”香蕉”,”西瓜”,”荔枝”}; 
String op = (String)JOptionPane.showInputDialog(null,”你喜欢什么水果”,”标题”, 
JOptionPane.QUESTION_MESSAGE,null,fruits,fruits[2]);

11.8 文件选择器

public class Text2 {
	public static void main(String[] args) {
		final JFrame f=new JFrame("文件选择器");
		f.setLayout(new FlowLayout());
		final JFileChooser fc=new JFileChooser();
		fc.setFileFilter(new FileFilter(){
			@Override
			public String getDescription(){
				return ".txt";
			}
			@Override
			public boolean accept(File f){
				return f.getName().toLowerCase().endsWith(".txt");
			}
		});
		
		JButton bopen=new JButton("打开文件");
		JButton bseve=new JButton("保存文件");
		f.add(bopen);
		f.add(bseve);
		
		f.setSize(400,300);
		f.setLocationRelativeTo(null);
		
		bopen.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e){
				int returnVal=fc.showOpenDialog(f);//打开文件
				File file=fc.getSelectedFile();
				if(returnVal==JFileChooser.APPROVE_OPTION){
					JOptionPane.showMessageDialog(f, "计划打开文件:"+file.getAbsolutePath());
				}
			}
		});
		bseve.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e){
				int returnVal=fc.showSaveDialog(f);//保存文件
				File file=fc.getSelectedFile();
				if(returnVal==JFileChooser.APPROVE_OPTION){
					JOptionPane.showMessageDialog(f,"计划保存到文件:"+file.getAbsolutePath());
				}
			}
		});
		
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.setVisible(true);
	}

}

11.9 菜单

11.9.1 菜单类
(1)JMenuBar	//表示一个菜单栏
(2)JMenu	//表示菜单栏上的一个一级菜单
(3)JMenuItem				//表示一级菜单下的一个子菜单项,三者分别表示 普通的子菜单
	JCheckBoxMenuItem		//带复选框的子菜单
	JRadioButtonMenuItem	//带单选按钮的子菜单
JMenu、JCheckBoxMenuItem、JRadioButtonMenuItem 均继承自 JMenuItem

JMenuItem 常用构造方法:

text: 菜单显示的文本	icon: 菜单显示的图标
JMenuItem()
JMenuItem(String text)
JMenuItem(Icon icon)
JMenuItem(String text, Icon icon)

JMenuItem 常用方法:

void setText(String text)// 设置菜单显示的文本
void setIcon(Icon defaultIcon)// 设置菜单显示的图标
void setMnemonic(int mnemonic)// 设置菜单的键盘助记符
void setAccelerator(KeyStroke keyStroke)// 设置修改键,使用键盘快捷键直接触发菜单项的动作
例如按下 ALT+N 键触发菜单项动作:
menuItem.setMnemonic(KeyEvent.VK_N);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.ALT_MASK));

11.9.2 实例

mb=new MenuBar();//创建菜单栏	
m=new Menu("此电脑");//创建文件菜单
s1=new Menu("文档");//创建子菜单
s2=new Menu("图片");
s3=new Menu("音乐");
s4=new Menu("盘符");
closeItem=new MenuItem("退出");
m.add(s1);//将子菜单添加到文件菜单上
m.add(s2);
m.add(s3);
m.add(s4);
m.add(closeItem);//将退出菜单项添加到文件菜单上
sm1=new MenuItem("C盘");//创建子菜单项
sm2=new MenuItem("D盘");
sm3=new MenuItem("E盘");
sm4=new MenuItem("F盘");
s4.add(sm1);//将子菜单项添加到子菜单中
s4.add(sm2);
s4.add(sm3);
s4.add(sm4);
s4.add(closeItem);

11.10 表格

//表格————居中
//默认表头和表格内容偏左显示,手动修改先将自动创建的表格类改为public类型
DefaultTableCellHeaderRenderer hr = new DefaultTableCellHeaderRenderer();//
hr.setHorizontalAlignment(JLabel.CENTER);//
table.getTableHeader().setDefaultRenderer(hr);//设置表头居中显示
DefaultTableCellRenderer cr = new DefaultTableCellRenderer();//
cr.setHorizontalAlignment(JLabel.CENTER);//
table.setDefaultRenderer(Object.class, cr);//设置表数据居中显示
table.getColumn("age").setCellRenderer(render);//设置某列内容居中
(DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER)//设置表头内容居中
DefaultTableCellRenderer render = new DefaultTableCellRenderer();//
render.setHorizontalAlignment(SwingConstants.CENTER);	//
table.getTableHeader().getColumnModel().getColumn(0).setCellRenderer(render);//设置单元格内容居中
//表格————表头
JTableHeader header=table.getTableHeader();//构造表头对象
heaer.setReorderingAllowed(false);//设置列表头不可用户重新拖动排列
header.setBackground(Color.BLUE);//设置表头背景颜色
header.setForeground(Color.RED);//设置表头字体颜色
header.setFont(new Font("黑体",Font.PLAIN,24));//设置表头字体样式、大小
header.setPreferredSize(new Dimension(header.getWidth(),35));//设置表头高度
//表格————数据
table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);//默认列宽
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);手动调整时,后面所有的列跟着调整
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);//此列和后面所有列都会变动
table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);//此列和下一列都会变动
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);其他所有列不调整(添加横向滚动栏的关键代码)
//表格————高/宽
table.setRowHeight(35);//表格行高
table.getColumn(colname[0]).setPreferredWidth(150);  //设置第1列的宽度
table.getColumn(colname[1]).setPreferredWidth(140);  //设置第2列的宽度
table.getColumnModel().getColumn(0).setPreferredWidth(150);//设置第1列的宽度
table.getColumnModel().getColumn(1).setPreferredWidth(140);//设置第2列的宽度
table.getColumn(colname[colname.length - 1]).setMaxWidth(0);// 一
table.getColumn(colname[colname.length - 1]).setMinWidth(0);二
table.getColumn(colname[colname.length - 1]).setPreferredWidth(0);三者结合隐藏最后一列
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);//设置表格的自动调整模式
//表格————选择单元格
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);//设置表格的行选择模式
table.setRowSelectionAllowed(true);//设置是否允许选择表格行
table.setColumnSelectionAllowed(true);//设置是否允许选择表格列
table.setCellSelectionEnabled(true);//设置是否允许同时存在行选择与列选择
table.selectAll();//选中所有行
table.clearSelection();//取消选中
//表格————背景
table.setBackground(new Color(244, 244, 242)); //设置表格背景
table.setForeground(Color.GREEN);   //设置表格颜色
table.setFont(new Font("隶书",Font.PLAIN,14)); //设置表格字体样式、大小
//表格————默认选中行
//table.setRowSelectionInterval(1,3);//设置默认选中行(1,2,3)
//table.addRowSelectionInterval(5, 5);//添加选中行(5)
//表格————显示
ScrollPane2.setViewporView(table);//将table表放到窗口上
//table.setPreferredScrollableViewportSize(new Dimension(500, 70));// 表格的显示尺寸
//表格————获取
table.getRowCount(); //获取表格行数
table.getColumnCount(); //获取表格列数
table.isRowSelected(i); //查看第i+1行的选中状态
table.getSelectRowow(); //查看被选中所的行的首行索引值=行号-1.
table.getSelectedRows(); //获取所有被选中行的索引,返回数组
table.getColumnName(i); //获取第i+1列的列名
table.getValueAt(i,j); //获取第i+1行第j+1列的值
table.moveColumn(i,j); //将第i+1列移动到第j+1列处

//用模型mod来创建表格
DefaultTableModel model=new DefaultTableModel();//创建模型
model.setRowCount(0);//将表格模型中的数据清空
model.setColumnIdentifiers(new Object[] {"学号","姓名"});//设置表头
model.addRow(new Object[]{"001","张三"});//增加行
model.addRow(new Object[]{"001","张三"});//增加行
table.setModel(model);//添加表内容

11.10.2 //表格—居中

//默认表头和表格内容偏左显示,手动修改先将自动创建的表格类改为public类型
DefaultTableCellHeaderRenderer hr = new DefaultTableCellHeaderRenderer();//
hr.setHorizontalAlignment(JLabel.CENTER);//
table.getTableHeader().setDefaultRenderer(hr);//设置表头居中显示
DefaultTableCellRenderer cr = new DefaultTableCellRenderer();//
cr.setHorizontalAlignment(JLabel.CENTER);//
table.setDefaultRenderer(Object.class, cr);//设置表数据居中显示
table.getColumn("age").setCellRenderer(render);//设置某列内容居中
(DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER)//设置表头内容居中
DefaultTableCellRenderer render = new DefaultTableCellRenderer();//
render.setHorizontalAlignment(SwingConstants.CENTER);	//
table.getTableHeader().getColumnModel().getColumn(0).setCellRenderer(render);//设置单元格内容居中
//表格————表头
JTableHeader header=table.getTableHeader();//构造表头对象
heaer.setReorderingAllowed(false);//设置列表头不可用户重新拖动排列
header.setBackground(Color.BLUE);//设置表头背景颜色
header.setForeground(Color.RED);//设置表头字体颜色
header.setFont(new Font("黑体",Font.PLAIN,24));//设置表头字体样式、大小
header.setPreferredSize(new Dimension(header.getWidth(),35));//设置表头高度
//表格————数据
table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);//默认列宽
table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);手动调整时,后面所有的列跟着调整
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);//此列和后面所有列都会变动
table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);//此列和下一列都会变动
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);其他所有列不调整(添加横向滚动栏的关键代码)
//表格————高/宽
table.setRowHeight(35);//表格行高
table.getColumn(colname[0]).setPreferredWidth(150);  //设置第1列的宽度
table.getColumn(colname[1]).setPreferredWidth(140);  //设置第2列的宽度
table.getColumnModel().getColumn(0).setPreferredWidth(150);//设置第1列的宽度
table.getColumnModel().getColumn(1).setPreferredWidth(140);//设置第2列的宽度
table.getColumn(colname[colname.length - 1]).setMaxWidth(0);// 一
table.getColumn(colname[colname.length - 1]).setMinWidth(0);二
table.getColumn(colname[colname.length - 1]).setPreferredWidth(0);三者结合隐藏最后一列
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);//设置表格的自动调整模式
//表格————选择单元格
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);//设置表格的行选择模式
table.setRowSelectionAllowed(true);//设置是否允许选择表格行
table.setColumnSelectionAllowed(true);//设置是否允许选择表格列
table.setCellSelectionEnabled(true);//设置是否允许同时存在行选择与列选择
table.selectAll();//选中所有行
table.clearSelection();//取消选中
//表格————背景
table.setBackground(new Color(244, 244, 242)); //设置表格背景
table.setForeground(Color.GREEN);   //设置表格颜色
table.setFont(new Font("隶书",Font.PLAIN,14)); //设置表格字体样式、大小
//表格————默认选中行
//table.setRowSelectionInterval(1,3);//设置默认选中行(1,2,3)
//table.addRowSelectionInterval(5, 5);//添加选中行(5)
//表格————显示
ScrollPane2.setViewporView(table);//将table表放到窗口上
//table.setPreferredScrollableViewportSize(new Dimension(500, 70));// 表格的显示尺寸
//表格————获取
table.getRowCount(); //获取表格行数
table.getColumnCount(); //获取表格列数
table.isRowSelected(i); //查看第i+1行的选中状态
table.getSelectRowow(); //查看被选中所的行的首行索引值=行号-1.
table.getSelectedRows(); //获取所有被选中行的索引,返回数组
table.getColumnName(i); //获取第i+1列的列名
table.getValueAt(i,j); //获取第i+1行第j+1列的值
table.moveColumn(i,j); //将第i+1列移动到第j+1列处

//用模型mod来创建表格
DefaultTableModel model=new DefaultTableModel();//创建模型
model.setRowCount(0);//将表格模型中的数据清空
model.setColumnIdentifiers(new Object[] {"学号","姓名"});//设置表头
model.addRow(new Object[]{"001","张三"});//增加行
model.addRow(new Object[]{"001","张三"});//增加行
11.10.2 创建表格
String[] columnNames={"A","B","C","D","E","F","G"};//列名数组
Vector<String> columnNameV=new Vector<>();//定义表格列名向量
for(int i=0;i<columnNames.length;i++)
	columnNameV.add(columnNames[i]);//添加列名

Vector<Vector<String>> tableValueV=new Vector<>();	//定义表格数据向量
for(int row=0;row<行数;row++)
	{	Vector<String> rowV=new Vector<String>();	//定义表格行向量
		for(int column=0;column<columnNames.length;column++)
			rowV.add(columnNames[row][column]);	//添加数据
		tableValueV.add(rowV);	//将行添加到数据向量中
	}

DefaultTableModel mod=new DefaultTableModel(values,columnNames);//Model存放表格的数据
table=new JTable(mod);//表格对象table的数据来源是mod对象
table=new JTable(tableValueV,columnNameV);//创建带数据、列名的表格

scrollPane.setViewportView(table);//将table表放到窗口上

11.11 布局

容器				默认布局方式
顶层容器
JFrame				BorderLayout(边界布局)
JDialog				BorderLayout(边界布局)
JApplet				FlowLayout(流式布局)
中间容器
JPanel				FlowLayout(流式布局)
11.11.1 流式布局
FlowLayout(流式布局)
对齐方式:左对齐、居中(默认)、右对齐
流式布局FlowLayout类的常用构造函数和方法:align对齐方式 hgap水平间距 vgap纵向间距
名称												用途
FlowLayout()								构造一个FlowLayout,默认居中对齐,水平和垂直间隙5px
FlowLayout(int align) 						构造一个FlowLayout,指定对齐方式,水平和垂直间隙5px
FlowLayout(int align, int hgap, int vgap)   构造一个FlowLayout,指定对齐方式和水平垂直间距。
align对齐方式5参数:(左 中 右 开始 结束*左)
	0或FlowLayout.lEFT ,控件左对齐
	1或FlowLayout.CENTER ,居中对齐
	2或FlowLayout.RIGHT ,右对齐
	3或FlowLayout.LEADING,容器方向开始边对应
	4或FlowLayout.TRAILING,容器方向结束边对应
	其他整数,则为左对齐
	
final FlowLayout fl=new FlowLayout();//定义流式布局对象
leftButton.addActionListener(new ActionListener(){ // 注册事件监听器
	public void actionPerformed(ActionEvent event){
		fl.setAlignment(FlowLayout.LEFT); // 设置对齐方式(左对齐)
		fl.layoutContainer(frame); // 使Frame重新布局
	}
});

void setAlignment(int align)	Set对齐方式ALIGNMENT
void setHgap(int hgap)			Set水平间距HGAP
void setVgap(int vgap)			Set垂直间距VGAP

设置流式布局后,组件大小无法调整大小
f.add(tf,BorderLayout.SOUTH);
f.add(ta,BorderLayout.NORTH);
f.pack();//自动设置最佳大小
解决方法:
	ta.setPreferredSize(new Dimension(400,300));//大小
	tf.setPreferredSize(new Dimension(400,100));


例:
	1.frame.setLayout(new FlowLayout()); //流式布局
	2.frame.setLayout(new FlowLayout(FlowLayout.LEFT));//流式布局左对齐
	3.frame.setLayout(new FlowLayout(FlowLayout.lEFT,50,5));//流式布局左对齐,水平间距50,垂直间距5
例2:
FlowLayoutDemo window = newFlowLayoutDemo(); //生成对象window
window.setTitle("流式布局"); //窗口标题
window.pack();  //根据组件自适应窗口大小
window.setVisible(true); 
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLocationRelativeTo(null); //让窗体居中显示
11.11.2 边界布局管理器
				上北(NORTH)		
	左西(WEST)中(CENTER)右东(EAST)	
				下南(SOUTH)
				
常见的构建函数和方法:
	BorderLayout(): 构造一个组件之间没有间距(默认间距为0像素)的新边框布局。
	BorderLayout(int hgap, int vgap):hgap为横向间距,vgap为纵向间距
例:
	frame.setLayout(new BorderLayout());//边界布局
	frame.setLayout(new BorderLayout(10,5));// 水平间距10,纵向间距5
	f.add(new Button("north"),BorderLayout.NORTH);上
	f.add(new Button("south"),BorderLayout.SOUTH);;下
	f.add(new Button("east"),BorderLayout.EAST);右
	f.add(new Button("west"),BorderLayout.WEST);左
	f.add(new Button("center"));(默认)
11.11.3 网格布局
GridLayout(网格布局):
	GridLayout是定义在AWT包中的布局管理器。
	GridLayout布局管理器将组件按照网格方式排列,将容器分成规则矩形块,每个组件尽可能占据每块空间。
方法名													说明
GridLayout()									创建具有默认值的网格布局,即每个组件占据一行一列
GridLayout(int rows, int cols)					创建具有指定行数和列数的网格布局
GridLayout(int rows,int cols,int hgap,int vgap)	创建具有指定行数和列数的网格布局

例:	
	frame.setLayout(new GridLayout(3,3,10,10));//三行三列,水平间距10,竖立间距10
网格包布局管理器:GridBagLayout
	是在网格基础上提供复杂的布局,是最灵活、 最复杂的布局管理器
GridBagLayout 不需要组件的尺寸一致,允许组件扩展到多行多列。
每个 GridBagLayout 对象都维护了一组动态的矩形网格单元,每个组件占一个或多个单元,
所占有的网格单元称为组件的显示区域。
1. gridx 和 gridy:用来指定组件左上角在网格中的行和列。
容器中最左边列的 gridx 为 0,最上边行的 gridy 为 0。这两个变量的默认值是 GridBagConstraints.RELATIVE,表示对应的组件将放在前一个组件的右边或下面。
2. gridwidth 和 gridheight:用来指定组件显示区域所占的列数和行数
,以网格单元而不是像素为单位,默认值为 1。
3. fill:指定组件填充网格的方式,
可以是如下值:GridBagConstraints.NONE(默认值)、GridBagConstraints.HORIZONTAL(组件横向充满显示区域,但是不改变组件高度)、
GridBagConstraints.VERTICAL(组件纵向充满显示区域,但是不改变组件宽度)以及 GridBagConstraints.BOTH(组件横向、纵向充满其显示区域)。
4. ipadx 和 ipady
指定组件显示区域的内部填充,即在组件最小尺寸之外需要附加的像素数,默认值为 0。
5. insets
指定组件显示区域的外部填充,即组件与其显示区域边缘之间的空间,默认组件没有外部填充。
6. anchor:指定组件在显示区域中的摆放位置。
可选值有 GridBagConstraints.CENTER(默认值)、GridBagConstraints.NORTH、GridBagConstraints.
NORTHEAST、GridBagConstraints.EAST、GridBagConstraints.SOUTH、GridBagConstraints.SOUTHEAST、GridBagConstraints.WEST、GridBagConstraints.SOUTHWEST 以及 GridBagConstraints.NORTHWEST。
7. weightx 和 weighty:用来指定在容器大小改变时,增加或减少的空间如何在组件间分配,默认值为 0
,即所有的组件将聚拢在容器的中心,多余的空间将放在容器边缘与网格单元之间。weightx 和 weighty 的取值一般在 0.0 与 1.0 之间,数值大表明组件所在的行或者列将获得更多的空间。
例:
 JFrame frame=new JFrame("拨号盘");
        GridBagLayout gbaglayout=new GridBagLayout();    //创建GridBagLayout布局管理器
        GridBagConstraints constraints=new GridBagConstraints();
        frame.setLayout(gbaglayout);    //使用GridBagLayout布局管理器
        constraints.fill=GridBagConstraints.BOTH;    //组件填充显示区域
        constraints.weightx=0.0;    //恢复默认值
        constraints.gridwidth = GridBagConstraints.REMAINDER;    //结束行
        JTextField tf=new JTextField("13612345678");
        gbaglayout.setConstraints(tf, constraints);
        frame.add(tf);
        constraints.weightx=0.5;    // 指定组件的分配区域
        constraints.weighty=0.2;
        constraints.gridwidth=1;
        makeButton("7",frame,gbaglayout,constraints);    //调用方法,添加按钮组件
        makeButton("8",frame,gbaglayout,constraints);
        constraints.gridwidth=GridBagConstraints.REMAINDER;    //结束行
        makeButton("9",frame,gbaglayout,constraints);
        constraints.gridwidth=1;    //重新设置gridwidth的值
11.11.4 盒子布局
两个静态方法:
createHorizontalBox():返回一个 Box 对象,它采用水平 BoxLayout,即 BoxLayout 沿着水平方向放置组件,让组件在容器内从左到右排列。
createVerticalBox():返回一个 Box 对象,它采用垂直 BoxLayout,即 BoxLayout 沿着垂直方向放置组件,让组件在容器内从上到下进行排列
Box类设置组件间隔的静态方法:
	网格包布局											说明
static Component createHorizontalGlue()	创建一个不可见的、可以被水平拉伸和收缩的组件
static Component createVerticalGlue()	创建一个不可见的、可以被垂直拉伸和收缩的组件
static Component createHorizontalStrut(int width)	创建一个不可见的、固定宽度的组件
static Component createVerticalStrut(int height)	创建一个不可见的、固定高度的组件
static Component createRigidArea(Dimension d)	创建一个不可见的、总是具有指定大小的组件
BoxLayout 类只有一个构造方法:BoxLayout(Container c,int axis),参数 Container 是一个容器对象
例:
JFrame frame=new JFrame("Java示例程序");
        Box b1=Box.createHorizontalBox();    //创建横向Box容器
        Box b2=Box.createVerticalBox();    //创建纵向Box容器
        frame.add(b1);    //将外层横向Box添加进窗体
        b1.add(Box.createVerticalStrut(200));    //添加高度为200的垂直框架
        b1.add(new JButton("西"));    //添加按钮1
        b1.add(Box.createHorizontalStrut(140));    //添加长度为140的水平框架 
        b1.add(new JButton("东"));    //添加按钮2
        b1.add(Box.createHorizontalGlue());    //添加水平胶水
        b1.add(b2);    //添加嵌套的纵向Box容器
        //添加宽度为100,高度为20的固定区域  
        b2.add(Box.createRigidArea(new Dimension(100,20))); 
        b2.add(new JButton("北"));    //添加按钮3
        b2.add(Box.createVerticalGlue());    //添加垂直组件
        b2.add(new JButton("南"));    //添加按钮4
        b2.add(Box.createVerticalStrut(40));    //添加长度为40的垂直框架
        //设置窗口的关闭动作、标题、大小位置以及可见性等  
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.setBounds(100,100,400,200); 
        frame.setVisible(true); 
11.11.5 卡片布局
GradLayout将界面看成一系列卡片,在任何时候只有其中一张卡片是可见的,占据整个区域
GradLayout(hgap,vgap)
默认情况下显示第一个被添加的组件,但可通过GradLayout的show(指定容器,卡片名)方法显示

11.12 监听事件

事件类包括:ActionEvent / ItemEvent / MouseEvent / MouseEvent / KeyEvent / WindowEvent
				AWT事件的监听器接口
  事件类		  说明		   		接口类					     方法

ActionEvent		动作事件		ActionListener			actionPerformed(ActionEvent e)

ItemEvent		选项事件		ItemListener			itemStateChanged(ItemEvent e)

MouseEvent		鼠标移动事件	  MouseMotionListener		mouseGragged(MouseEvent e)
														mouseMoved(MouseEvent e
														
														mousePressed(MouseEvent e)
														mouseReleased(MouseEvent e)
														mouseEntered(MouseEvent e)
MouseEvent		鼠标事件		MouseListener			 mouseExited(MouseEvent e)
														mouseClicked(MouseEvent e)

														keyPressed(KeyEvent e)//按下
KeyEvent		键盘事件		KeyListener				 keyReleased(KeyEvent e)//按下与释放
														keyTyped(KeyEvent e)//释放

FocusEvent	    焦点事件		FocusListener			 focusGained(FocusEvent e)												
														focusLost(FocusEvent e)

AdjustmentEvent 移动滚动条		AdjustmentListener		AdjustmentValueChanged(AdjustmentEvent e)

ContainerEvent  容器事件		containerListener		 componentAdded(ContainerEvent e)
														componenRemoved(ContainerEvent e)

														componentMoved(ComponentEvent e)
														componentHidden(ComponentEvent e)
ComponentEvent	组件动作事件	  ComponentListener		    componentResized(ComponentEvent e)
														componentShown(ComponentEvent e)

TextEvent		文本事件		TextListener			 textValueChanged(TextEvent e)

														windowClosing(WindowEvent e)
														windowOpened(WindowEvent e)
														windowIconified(WindowEvent e)
														windowClosed(WindowEvent e)
WindowEvent		窗口事件		WindowListener			 windowDeiconined(WindowEvent e)
														windowActivated(WindowEvent e)
														windowDeactivated(WindowEvent e)
11.12.1 鼠标事件
mouseClicked(MouseEvent e) //鼠标按键在组件上单击(按下并释放)时调用
mouseEntered(MouseEvent e) //鼠标进入到组件上时调用。
mouseExited(MouseEvent e) //鼠标离开组件时调用。
mousePressed(MouseEvent e) //鼠标按键在组件上按下时调用
mouseReleased(MouseEvent e) //鼠标按钮在组件上释放时调用。
例 1:鼠标单、双击与移入事件
button.addMouseListener(new MouseAdapter()//鼠标监听
       {private int count = 1;
       	private int cont=1;
        private int conts=1; 
		private int mouseCount = 1;
		public void mouseEntered(MouseEvent e)//鼠标移入
		{	System.out.println("鼠标移入次数:"+count++);
		    tf1.setText("鼠标移入次数:"+count);
		}
		public void mouseClicked(MouseEvent e)//鼠标单击、双击
		{	if(e.getClickCount()==2){
				conts ++;
				System.out.println("鼠标被双击了,双击次数:"+conts);
				tf2.setText("鼠标双击次数:"+conts);
			}
			else{
				cont++;
				System.out.println("鼠标被单击了,单击次数:"+cont);
				tf3.setText("鼠标单击次数:"+cont);
			}
		  }
});
例 2:
bt.addMouseListener(new MouseMove());// 注册鼠标事件监听器
class MouseMove extends MouseAdapter{
	public void mouseClicked(MouseEvent e) {// 鼠标单击事件
		Frame4.bt.setVisible(false);//按钮消失
	}
	public void mouseExited(MouseEvent e) {	// 鼠标移动事件
		Frame4.bt.setVisible(true);//按钮出现
	}
}
11.12.2 动作事件

例1:修改文本信息

tf.addActionListener(new TFListener1());//添加动作事件
class TFListener1 implements ActionListener {//定义动作事件类
	public void actionPerformed(ActionEvent e) {//构造动作事件方法
		String str = Freme3.tf.getText().trim();// 获取文本框中的内容
		int m = 0; int n = 0; int s = 0;	int i = 0;
		n = Integer.parseInt(str);	// 将字符串类型转换为整型
		Freme3.ta.append(i+"   ");	// 将信息添加到窗体上
	}
}
button.addActionListener(new ActionListener(){//动作监听事件
	public void actionPerformed(ActionEvent e){
		button.setBackground(Color.cyan);
		button.setForeground(Color.red);
		button.setFont(new Font("宋体",Font.PLAIN,24));
		System.out.println("我要开始活动啦!");
	}
});

例2:关闭窗口

方法一:
f.addWindowListener(new WindowAdapter()//窗口监听
{	public void windowClosing(WindowEvent e)
	{	System.out.println("窗体执行关闭!");
		System.exit(0);
	}
});
方法二:
addWindowListener(new closeframe11());
class closeframe11 extends WindowAdapter{
	public void windowClosing(WindowEvent e){
		System.exit(0);
	}
}

例3:

11.12.3 键盘事件
键按下(Pressed)/键释放(Released)/键的按下并释放(Typed)
public int getKeyCode():返回键盘事件中相关键的整数型键码。
public char getKeyChar():返回键盘事件中相关键的字符型键码,例如,对于Shift+A键返回的键码是A。
public static String getKeyText(int keyCode):返回一个描述由参数int keyCode指定的键的字符串,如"HOME"、"F1" 或"A"等。
public String paramString():返回一个标识该事件的参数字符串。
例:
public void keyTyped(KeyEvent e) {	// 键的按下与释放事件
	ta4.append(String.valueOf(e.getKeyChar())+"\n");// 将获取的字符类型编码写入文本区中
}
public void keyPressed(KeyEvent e) {	// 键的按下事件
	ta1.append(String.valueOf(e.getKeyChar())+"\n");		
	ta3.append(e.getKeyText(e.getKeyCode()) + "\n");	// 键的整数类型编码(首选)
}
public void keyReleased(KeyEvent e) {	// 键的释放事件
	ta2.append(String.valueOf(e.getKeyChar())+"\n");
}

11.13 综合实例

public class JFrame4 extends JFrame implements ActionListener{
	static JFrame4 frame;//窗口
	static JLabel lab1,lab2,lab3,lab4,lab5,lab6,lab7;//标签
	static JPanel panel;//面板
	static JTextField tf1,tf2,tf3,tf4;//单行文本框
	static JButton b1,b2,b3,b4,b5;//按钮
	static JTextArea ta,ta2;//多行文本框
	static Font f1,f2;//字体样式
	static JRadioButton rab1,rab2;//单选按钮
	static JComboBox year,month,day;//单选按钮组
	static JCheckBox jc1,jc2,jc3,jc4;//多选按钮
	static JProgressBar jp;//进度条
	
	// 主函数
	public static void main(String[] args) {
		frame=new JFrame4();
		frame.setVisible(true);
	}
	
	// 构造函数
	public JFrame4(){
		//窗口
		setTitle("个人简介");
		setBounds(50,50,840,1000);
		setLayout(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		//字体
		f1=new Font("宋体",Font.PLAIN,24);
		f2=new Font("宋体",Font.PLAIN,20);
		
		//面板
		panel=new JPanel();
		panel.setBounds(100,50,600,850);
		panel.setLayout(null);
		panel.setBackground(Color.white);
		add(panel);
		
		//姓名
		lab1=new JLabel("姓名:");
		lab1.setBounds(50,30,80,30);
		lab1.setFont(f1);
		panel.add(lab1);
		tf1=new JTextField();
		tf1.setBounds(130,30,130,30);
		tf1.setFont(f1);
		panel.add(tf1);
		
		//年龄
		lab2=new JLabel("年龄:");
		lab2.setBounds(300,30,80,30);
		lab2.setFont(f1);
		panel.add(lab2);
		tf2=new JTextField();
		tf2.setBounds(380,30,80,30);
		tf2.setFont(f1);
		panel.add(tf2);
		
		//性别
		lab3=new JLabel("性别:");
		lab3.setBounds(50,80,80,30);
		lab3.setFont(f1);
		panel.add(lab3);
		rab1=new JRadioButton("男");
		rab1.setBounds(150,80,80,30);
		rab1.setBackground(null);
		rab1.setSelected(true);
		rab1.setFont(f1);
		rab2=new JRadioButton("女");
		rab2.setBounds(240,80,80,30);
		rab2.setBackground(null);
		rab2.setFont(f1);
		ButtonGroup bg=new ButtonGroup();
		bg.add(rab1);
		bg.add(rab2);
		panel.add(rab1);
		panel.add(rab2);
		
		//出生日期
		lab4=new JLabel("出生日期:");
		lab4.setBounds(50,130,120,30);
		lab4.setFont(f1);
		panel.add(lab4);
		year=new JComboBox();//下拉选项框
		year.setBounds(180,130,110,30);
		year.setFont(f1);
		for(int i=1995;i<2002;i++){
			year.addItem(""+i+"年");
		}
		panel.add(year);
		month=new JComboBox();//月
		month.setBounds(300,130,110,30);
		month.setFont(f1);
		for(int i=1;i<=12;i++){
			month.addItem(""+i+"月");
		}
		panel.add(month);
		day=new JComboBox();//日
		day.setBounds(420,130,110,30);
		day.setFont(f1);
		for(int i=1;i<=31;i++){
			day.addItem(""+i+"日");
		}
		panel.add(day);
		
		//爱好
		lab5=new JLabel("爱好:");
		lab5.setBounds(50,180,80,30);
		lab5.setFont(f1);
		panel.add(lab5);
		jc1=new JCheckBox("唱歌");
		jc1.setFont(f1);
		jc1.setBounds(140,180,80,30);
		jc1.setBackground(null);
		panel.add(jc1);
		jc2=new JCheckBox("跳舞");
		jc2.setFont(f1);
		jc2.setBounds(240,180,80,30);
		jc2.setBackground(null);
		panel.add(jc2);
		jc3=new JCheckBox("旅游");
		jc3.setFont(f1);
		jc3.setBounds(340,180,80,30);
		jc3.setSelected(true);//默认被选中
		jc3.setBackground(null);
		panel.add(jc3);
		jc4=new JCheckBox("阅读");
		jc4.setFont(f1);
		jc4.setBounds(440,180,80,30);
		jc4.setBackground(null);
		panel.add(jc4);
		
		//个人简介
		lab6=new JLabel("个人简介:");
		lab6.setBounds(50,220,120,30);
		lab6.setFont(f1);
		lab6.setBackground(null);
		panel.add(lab6);
		ta=new JTextArea();//个人简介文本框
		ta.setBounds(50,250,500,200);
		ta.setBackground(Color.LIGHT_GRAY);
		ta.setFont(f1);
		panel.add(ta);
		
		//显示信息窗口
		lab7=new JLabel("个人信息:");
		lab7.setBounds(50,460,120,30);
		lab7.setBackground(null);
		lab7.setFont(f1);
		panel.add(lab7);
		ta2=new JTextArea();
		ta2.setBounds(50,500,500,250);
		ta2.setBackground(Color.cyan);
		ta2.setFont(f1);
		panel.add(ta2);
			
		//进度条
		jp=new JProgressBar();
		jp.setBounds(50,760,500,30);
		jp.setMaximum(100);//最大值
		jp.setValue(50);//当前值
		jp.setStringPainted(true);//显示当前值
		panel.add(jp);
		
		//按钮
		b4=new JButton("提交");
		b4.setBounds(50,800,200,30);
		b4.setFont(f1);
		b4.addActionListener(this);
		panel.add(b4);
		
		b5=new JButton("退出");
		b5.setBounds(350,800,200,30);
		b5.setFont(f1);
		b5.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				System.exit(0);
			}
		});
		panel.add(b5);
	}
	
	public void actionPerformed(ActionEvent e) {
		ArrayList<Indution> list=new ArrayList<Indution>();//泛型
		Indution in=new Indution();
		in.name=tf1.getText().trim();
		in.age=tf2.getText().trim();
		if(rab1.isSelected())
			in.sex="男";
		else in.sex="女";
		in.year=(String) year.getSelectedItem();
		in.month=(String) month.getSelectedItem();
		in.day=(String) day.getSelectedItem();
		String str="";
		if(jc1.isSelected())
			str=str+"唱歌"+"  ";
		if(jc2.isSelected())
			str=str+"跳舞"+"  ";
		if(jc3.isSelected())
			str=str+"旅游"+"  ";
		if(jc4.isSelected())
			str=str+"阅读"+"  ";
		in.love=str;
		in.text=ta.getText().trim();
		ta2.append("姓名:"+in.name+"\n");
		ta2.append("性别:"+in.sex+"\n");
		ta2.append("年龄:"+in.age+"\n");
		ta2.append("出生日期:"+in.year+in.month+in.day+"\n");
		ta2.append("爱好:"+in.love+"\n");
		ta2.append("简介:"+in.text+"\n");
		//保存文件
		String file="H:/JAVA/appendix/个人简介.txt";
		try {
			FileOutputStream fos=new FileOutputStream(file,true);
			byte[] buff=ta2.getText().getBytes();
			fos.write(buff);
			fos.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}

// 个人信息类
class Indution{
	String name,age,sex,year,month,day,love,text;
}

第十二章 网络编程

12.1 基本概念

12.1.1 网络
通讯协议:
	计算机网络中实现通信必须有一些约定 即通信协议,对速率、传输代码、代码结构、传输控制步骤、
	出错控制等制定标准。
通讯接口:
	为了使两个结点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换。
	接口包括两部分:
	1、硬件装置:实现结点之间的信息传送;
	2、软件装置:规定双方进行通信的约定协议。
网络分层:
	TCP/IP是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,
网络接口层(物理+数据链路层)。
	OSI网络通信协议模型,是一一个参考模型,而TCP/IP协议是事实上的标准。
TCP/IP协议参考了0SI模型,但是并没有严格按照0SI规定的七层标准去划分,而只划分了四层,
这样会更简单点,当划分太多层次时,你很难区分某个协议是属于哪个层次的。

image-20200519164717854

image-20200519164810222

数据封装:
	Data Encapsulation是指将协议数据单元(PDU) 封装在一组协议头和协议尾中的过程。
在0SI七层参考模型中,每层主要负责与其它机器上的对等层进行通信。该过程是在协议数据单元
(PDU)中实现的,其中每层的PDU一般由本层的协议头、协议尾和数据封装构成。
	由于用户传输的数据一般都比较大, 有的可以达到MB字节,一次性发送出去十分困难,
于是就需要把数据分成许多片段,再按照一定的次序发送出去。这个过程就需要对数据进行封装

1.应用层:准备数据
2.传输层:接收应用层数据添加上TCP的控制信息(称为TCP头部),这个数据单元称为段(Segment)
	加入控制信息的过程称为封装。由此,将段交给网络层。
3.网络层:接收到段,再添加上IP头部,这个数据单元称为包(Packet) 。然后,将包交给数据链路层。
4.数据链路层:将包再添加上MAC头部和尾部,这个数据单元称为帧(Frame) 。然后,将帧交给物理层。
5.物理层:将接收到的数据转化为比特流,然后在网线中传送。

注:发送方数据处理的方式是从高层到底层,逐层进行数据封装
数据拆封:
	Data dismantling是指将接收到的数据进行拆包,每一-层只把对该层有意义的数据拿走,
或者说每一层只能处理发送方同等层的数据,然后把其余的部分传递给上一~层,这就是对等层通信的概念。

1.物理层:接收到比特流,经过处理后将数据交给数据链路层。
2.数据链路层:将接收到的数据转化为数据帧,再除去MAC未部和尾部,这个除去控制信息的过程称为解封,
	然后将包交给网络层。
3.网络层:接收到包,再除去IP头部,然后将段交给传输层。
4.传输层:接收到段,再除去TCP头部,然后将数据交给应用层。
5.应用层:处理数据

注:接收方数据处理的方式是从底层到高层,逐层进行数据解封装
12.1.2 IP
	用来标识网络中的一个通信实体的地址。通信实体可以是计算机、路由器等。比如互联网的每个服务器
都要有自己的IP地址,而每个局域网的计算机要通信也要配置IP地址。路由器是连接两个或多个网络的网络设备。
IP地址的分类:
	IPV4: 32位地址,以点分十进制表示,如192.168.0.1
	IPV6: 128位(16个字节)写成8个16位的无符号整数,每个整数用四个十六进制位表示,
数之间用日号(:)分开,如: 3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
特殊IP:
	127.0.0.1本机地址
	192.168.0.0--192.168.255.255私有地址,属于非注册地址,专门为组织机构内部使用。
InetAdress
封装计算机的ip地址,没有接口
/**
 * ip 基本操作
 * @author Administrator
 */
public class IPTest01 {
	public static void main(String[] args) throws UnknownHostException {
		// 创建InetAddress对象 
		// getLocalHost() 返回本地主机
		InetAddress addr = InetAddress.getLocalHost(); 
		// getHostAddress()	返回ip地址:192.168.56.1
		System.out.println(addr.getHostAddress());
		// getHostName() 返回计算机名
		System.out.println(addr.getHostName()); 
		
		// 根据域名得到InetAddress对象 
		addr = InetAddress.getByName("www.163.com"); 
		// 返回 163服务器的ip:112.28.224.57
		System.out.println(addr.getHostAddress()); 
		// 返回域名:www.163.com
		System.out.println(addr.getHostName());
		
		// 根据ip得到InetAddress对象 
		addr = InetAddress.getByName("112.28.224.57"); 
		// 返回服务器的ip: 112.28.224.57
		System.out.println(addr.getHostAddress());
		// 输出ip而不是域名
		System.out.println(addr.getHostName());
		// 如果这个IP地址不存在或DNS服务器不允许进行IP地址和域名的映射
		// getHostName方法就直接返回这个IP地址。
	}
}
12.1.3 端口
IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,区分这些不同的程序就要用到端口。
端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。
端口的表示是一个16位的二进制整数, 2个字节,对应十进制的0-65535。
Oracle、MySQL、Tomcat、QQ、msn、迅雷、360等网络程序都有自己的端口;
公认端口0- 1023比 如80端口分配给wWW,21端口分配给FTP
注册端口1024- -49151分 配给用户进程或应用程序
动态/私有端口49152--65535

查看所有端口:netstat -ano
查看指定端口:netstat -aon | findstr "8080"
查看指定进程:tasklist | findstr "4372"
查看具体程序:使用任务管理器查看PID
/**
 * 定义端口
 * @author Administrator
 */
public class PortTest {
	public static void main(String[] args) {
		// 包含ip、端口
		InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",8080); 
		InetSocketAddress socketAddress2 = new InetSocketAddress("localhost",9000); 
		// 获取主机名
		System.out.println(socketAddress.getHostName());
		// 获取ip地址
		System.out.println(socketAddress2.getAddress());
		// 获取端口号
		System.out.println(socketAddress.getPort());
	}
}
12.1.4 URL
在www上,每一信息资源都有统一且唯一的地址,即统资源定位符Uni form Resource Locator
如:http://www. google. com: 80/ index. html,由4部分组成:
	协议、存放资源的主机域名、端口号、资源文件名
URI: Universal Resource Identifier统资源标志符,用来标识抽象或物理资源的一个紧凑字符串。
URL:Universal Resource Locator 统一资源定位符,一种定位资源的主要访问机制的字符串,
一个标准的URL必须包括:protocol、host. port、 path、parameter. anchor 
URN:Universal Resource Name统一资源名称,通过特定命名空间中的唯一名称或ID来标识资源。
/**
 * URL 统一资源定位器 区分资源
 * 1.协议
 * 2.域名、计算机
 * 3.端口:默认80
 * 4.请求资源
 * @author Administrator
 */
public class URLTest01 {
	public static void main(String[] args) throws IOException {
		URL url = new URL("http://wwww.daibu.com:80/index.html?name=java&age=18#a");
		// 获取协议
		System.out.println("协议:" + url.getProtocol()); // http
		// 获取ip/域名
		System.out.println("域名:" + url.getHost()); // wwww.daibu.com
		// 端口
		System.out.println("端口:" + url.getPort()); // 80
		// 请求资源
		System.out.println("请求资源:" + url.getFile()); // /index.html?name=java&age=18
		// 请求资源
		System.out.println("请求资源:" + url.getPath()); // /index.html
		
		// 获取参数
		System.out.println("参数:" + url.getQuery()); // name=java&age=18
		// 锚点
		System.out.println("锚点:" + url.getRef()); // a
	}
}
/**
 * 爬虫原理+模拟浏览器
 * @author Administrator
 */
public class SpiderTest02 {
	public static void main(String[] args) throws IOException {
		// 获取URL
		URL url = new URL("https://www.dianping.com");
		
		// 加上请求报文
		HttpURLConnection conn = (HttpURLConnection)url.openConnection();
		conn.setRequestMethod("GET");
		conn.setRequestProperty("User-Agent",
		"Mozilla/5.0 (Windows NT 10.0; WOW64) 
		AppleWebKit/537.36 (KHTML, like Gecko) 
		Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0");
		
		// 选择流
		BufferedReader br = new BufferedReader(
		new InputStreamReader(conn.getInputStream(), "UTF-8"));
		
		// 获取资源
		String msg = null;
		while((msg = br.readLine()) != null) {
			System.out.println(msg);
		}
		
		// 关闭流
		br.close();
	}
}
12.1.5 传输协议
TCP传输协议: TCP (transfer control protocol)
	一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议
特点:面向对象、点对点的通信、高可靠性、占用系统资源多、效率低

UDP传输协议:UDP (User DatagramProtocol)
	一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
特点:
	非面向连接,传输不可靠,可能丢失
	发送不管对方是否准备好,接收方收到也不确认
	可以广播发送
	非常简单的协议,开销小
套接字Socket:
	我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议,在应用层如何使用传输层的服务呢?
在应用层和传输层之间,则是使用套接字来进行分离。
	套接字就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发
来的数据:而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,是不知道也不需要知
道的,也不会关心它如何传输,这属于网络其它层次的工作。
SOCKET编程:
	基于TCP协议的Socket编程
	通信双发需要建立连接
	连接建立时双方存在主次之分
	例:114查号台
	
	基于UDP协议的Socket编程
	通信双发不需要建立连接
	通信双方完全平等
	例:QQ聊天模式

12.2 UDP编程

需求:完成在线咨询功能
分析:
	使用基于UDP协议的Socket网络编程实现
	不需要利用I0流实现数据的传输
	每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地。
UDP基本概念:
	DatagramSocket:用于发送或接收数据包的套接字
	DatagramPacket:数据包
12.2.1 字符串传输
/**
 * 客户端
 * 1、使用DatagramSocket 指定端口 创建发送端
 * 2、准备数据 转成字节数组
 * 3、封装成DatagramPacket包裹 指定目的地
 * 4、发送包裹 send(DatagramPacket p)*
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpClient {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建发送端
		DatagramSocket client = new DatagramSocket(8888);
		
		// 2、准备数据 转成字节数组
		String data = "精心猛学";
		byte[] datas = data.getBytes("UTF-8");
		
		// 3、封装成DatagramPacket包裹 指定目的地
		InetSocketAddress url = new InetSocketAddress("localhost",9999);
		DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, url);
		
		// 4、发送包裹 send(DatagramPacket p)*
		client.send(packet);
		
		// 5、释放资源
		client.close();
	}
}
/**
 * 接收端
 * 1、使用DatagramSocket 指定端口 创建接收端
 * 2、准备容器 封装成DatagramPacket包裹
 * 3、阻塞式接收包裹receive(DatagramPacket p)
 * 4、分析数据 byte[] getData() 、getLength()
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpServer {

	public static void main(String[] args) throws Exception {
		System.out.println("接收端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建接收端
		DatagramSocket server = new DatagramSocket(9999);
		
		// 2、准备容器 封装成DatagramPacket包裹
		byte[] container = new byte[1024*60];
		DatagramPacket packet = new DatagramPacket(container, 0, container.length);
		
		// 3、阻塞式接收包裹receive(DatagramPacket p)
		server.receive(packet);
		
		// 4、分析数据 byte[] getData() 、getLength()
		byte[] datas = packet.getData();
		int len = packet.getLength();
		System.out.println(new String(datas, 0, len, "UTF-8"));
		
		// 5、释放资源
		server.close();
	}
}
12.2.2 基本类型传输
/**
 * 基本类型:发送端
 * 1、使用DatagramSocket 指定端口 创建发送端
 * 2、将基本类型 转成字节数组
 * 3、封装成DatagramPacket包裹 指定目的地
 * 4、发送包裹 send(DatagramPacket p)*
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpTypeClient {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建发送端
		DatagramSocket client = new DatagramSocket(8888);
		
		// 2、准备数据 转成字节数组
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));

		// 操作数据类型 + 数据
		dos.writeUTF("一把辛酸泪");
		dos.writeInt(18);
		dos.writeBoolean(false);
		dos.writeChar('a');
		dos.flush();
		byte[] datas = baos.toByteArray();
		
		// 3、封装成DatagramPacket包裹 指定目的地
		InetSocketAddress url = new InetSocketAddress("localhost",9999);
		DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, url);
		
		// 4、发送包裹 send(DatagramPacket p)*
		client.send(packet);
		
		// 5、释放资源
		client.close();
	}
}
/**
 *基本类型: 接收端
 * 1、使用DatagramSocket 指定端口 创建接收端
 * 2、准备容器 封装成DatagramPacket包裹
 * 3、阻塞式接收包裹receive(DatagramPacket p)
 * 4、分析数据 将字节数组还原成对应的基本类型
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpTypeServer {

	public static void main(String[] args) throws Exception {
		System.out.println("接收端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建接收端
		DatagramSocket server = new DatagramSocket(9999);
		
		// 2、准备容器 封装成DatagramPacket包裹
		byte[] container = new byte[1024*60];
		DatagramPacket packet = new DatagramPacket(container, 0, container.length);
		
		// 3、阻塞式接收包裹receive(DatagramPacket p)
		server.receive(packet);
		
		// 4、分析数据 
		byte[] datas = packet.getData();
		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(datas));
		String msg = dis.readUTF();
		int age = dis.readInt();
		boolean b = dis.readBoolean();
		char c = dis.readChar();
		System.out.println(msg+age+b+c);
		
		// 5、释放资源
		server.close();
	}
}
12.2.3 文件传输
/**
 * 工具类
 * fileToByteArray() 文件转字节数组
 * ByteArrayToFile() 字节数组转文件
 * @author Administrator
 */
public class IOUtils {
	/**
	  *  文件转字节数组
	 * @param filePath 文件路径
	 * @return
	 */
	public byte[] fileToByteArray(String filePath) {
		// 1. 创建源与目的地
		File src = new File(filePath);

		// 2. 选择流
		InputStream is = null;
		ByteArrayOutputStream baos = null;
		try {
			is = new FileInputStream(src);
			baos = new ByteArrayOutputStream();

			// 3. 操作(读取文件, 写出到字节数组)
			byte[] flush = new byte[1024 * 10];
			int len = -1;
			while ((len = is.read(flush)) != -1) {
				baos.write(flush, 0, len); // 写出到字节数组中
			}
			baos.flush();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 4. 释放资源
			try {
				if(is != null) {
					is.close();	
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * 将字节数组转成文件
	 * @param src 字节数组
	 * @param filePath 文件路径
	 */
	public void byteArrayToFile(byte[] src, String filePath) {
		// 1. 创建源
		File dest = new File(filePath);
		
		// 2. 选择流
		OutputStream os = null;
		InputStream is = null;
		try {
			os = new FileOutputStream(dest, true);
			is = new ByteArrayInputStream(src);
			
			// 3. 操作(读取字节数组,写出文件)
			byte[] flush = new byte[1024*10];
			int len = -1;
			while((len = is.read(flush)) != -1) {
				os.write(flush, 0, len);
			}
			os.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}  catch (IOException e) {
			e.printStackTrace();
		}finally {
			// 4. 释放资源
			try {
				if(os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

/**
 *文件下载: 接收端
 * 1、使用DatagramSocket 指定端口 创建接收端
 * 2、准备容器 封装成DatagramPacket包裹
 * 3、阻塞式接收包裹receive(DatagramPacket p)
 * 4、分析数据 将字节数组还原成文件
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpFileServer {

	public static void main(String[] args) throws Exception {
		System.out.println("接收端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建接收端
		DatagramSocket server = new DatagramSocket(9999);
		
		// 2、准备容器 封装成DatagramPacket包裹
		byte[] container = new byte[1024*60];
		DatagramPacket packet = new DatagramPacket(container, 0, container.length);
		
		// 3、阻塞式接收包裹receive(DatagramPacket p)
		server.receive(packet);
		
		// 4、分析数据 
		byte[] datas = packet.getData();
		new IOUtils().byteArrayToFile(datas, "down/002.jpg");
		
		// 5、释放资源
		server.close();
	}
}
/**
 * 文件上传:发送端
 * 1、使用DatagramSocket 指定端口 创建发送端
 * 2、将文件 转成字节数组
 * 3、封装成DatagramPacket包裹 指定目的地
 * 4、发送包裹 send(DatagramPacket p)*
 * 5、释放资源
 * @author Administrator
 */
public class UdpFileClient {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建发送端
		DatagramSocket client = new DatagramSocket(8888);
		
		// 2、准备数据 转成字节数组
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));

		byte[] datas = new IOUtils().fileToByteArray("up/001.jpg");
		
		// 3、封装成DatagramPacket包裹 指定目的地
		InetSocketAddress url = new InetSocketAddress("localhost",9999);
		DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, url);
		
		// 4、发送包裹 send(DatagramPacket p)*
		client.send(packet);
		
		// 5、释放资源
		client.close();
	}
}
/**
 * 文件上传:发送端   较大文件
 * 1、使用DatagramSocket 指定端口 创建发送端
 * 2、将文件 转成字节数组
 * 3、封装成DatagramPacket包裹 指定目的地
 * 4、发送包裹 send(DatagramPacket p)*
 * 5、释放资源
 * @author Administrator
 */
public class UdpFileClient02 {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建发送端
		DatagramSocket client = new DatagramSocket(8888);
		
		// 3、封装成DatagramPacket包裹 指定目的地
		InetSocketAddress url = new InetSocketAddress("localhost",9999);
		DatagramPacket packet = null;
		
		// 2、准备数据 转成字节数组
		byte[] datas = null;
		
		InputStream is = new BufferedInputStream(new FileInputStream(new File("up/002.jpg")));
		ByteArrayOutputStream os = null;
		
		byte[] flush = new byte[1024*60];
		int len = -1;
		while((len = is.read(flush)) != -1) {
			os = new ByteArrayOutputStream();
			os.write(flush, 0, len);
			os.flush();
			datas = os.toByteArray();
			packet = new DatagramPacket(datas, 0, datas.length, url);
			// 4、发送包裹 send(DatagramPacket p)*
			client.send(packet);
			
			os.close();
		}
		is.close();
		
		// 5、释放资源
		client.close();
	}
}
/**
 *文件下载: 接收端  较大文件
 * 1、使用DatagramSocket 指定端口 创建接收端
 * 2、准备容器 封装成DatagramPacket包裹
 * 3、阻塞式接收包裹receive(DatagramPacket p)
 * 4、分析数据 将字节数组还原成文件
 * 5、释放资源
 * @author Administrator
 *
 */
public class UdpFileServer02 {

	public static void main(String[] args) throws Exception {
		System.out.println("接收端启动中。。。");
		// 1、使用DatagramSocket 指定端口 创建接收端
		DatagramSocket server = new DatagramSocket(9999);
		
		// 2、准备容器 封装成DatagramPacket包裹
		byte[] container = new byte[1024*60];
		DatagramPacket packet = new DatagramPacket(container, 0, container.length);
		
		byte[] datas = null;
		InputStream is = null;
		OutputStream os = new BufferedOutputStream(new FileOutputStream(new File("down/002.jpg"), true));
		
		byte[] flush = new byte[1024*60];
		int len = -1;
		
		while(true) {
			// 3、阻塞式接收包裹receive(DatagramPacket p)
			server.receive(packet);
			
			// 4、分析数据 
			datas = packet.getData();
			int packetSize = packet.getLength();
			is = new BufferedInputStream(new ByteArrayInputStream(datas));
			while ((len = is.read(flush)) != -1) {
				os.write(flush, 0, packetSize);
				os.flush();
			}
			is.close();
			if(packetSize < 1024*60) {
				break;
			}
		}
		
		// 5、释放资源
		server.close();
	}
}
12.2.4 在线聊天
/**
 *	发送线程
 * 多线程多次交流 :发送端
 * 1、使用DatagramSocket 指定端口 创建发送端
 * 2、准备数据 转成字节数组
 * 3、封装成DatagramPacket包裹 指定目的地
 * 4、发送包裹 send(DatagramPacket p)*
 * 5、释放资源
 * @author Administrator
*/
public class TalkClient  implements Runnable{
	private DatagramSocket client;
	private BufferedReader reader;
	private String toIP;
	private int toPort;
	public TalkClient(int port, String toIP, int toPort) {
		this.toIP = toIP;
		this.toPort = toPort;
		try {
			// 1、使用DatagramSocket 指定端口 创建发送端
			client = new DatagramSocket(port);
			reader = new BufferedReader(new InputStreamReader(System.in));
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		while(true) {
			// 2、准备数据 转成字节数组
			String data;
			try {
				data = reader.readLine().trim();
				byte[] datas = data.getBytes("UTF-8");
				
				// 3、封装成DatagramPacket包裹 指定目的地
				DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, 
						new InetSocketAddress(this.toIP,this.toPort));
				
				// 4、发送包裹 send(DatagramPacket p)*
				client.send(packet);
				// 如果输入为"exit" 则结束
				if (data.equals("bye")) {
					break;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
/**
 * 接收线程
 * 多线程多次交流 :接收端
 * 1、使用DatagramSocket 指定端口 创建接收端
 * 2、准备容器 封装成DatagramPacket包裹
 * 3、阻塞式接收包裹receive(DatagramPacket p)
 * 4、分析数据 byte[] getData() 、getLength()
 * 5、释放资源
 * @author Administrator
*/
public class TalkServer implements Runnable{
	// 1、使用DatagramSocket 指定端口 创建接收端
	private DatagramSocket server;
	private String name; // 发送方姓名
	public TalkServer(int port, String name) {
		this.name = name;
		try {
			server = new DatagramSocket(port);
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		while(true) {
			// 2、准备容器 封装成DatagramPacket包裹
			byte[] container = new byte[1024*60];
			DatagramPacket packet = new DatagramPacket(container, 0, container.length);
			
			try {
				// 3、阻塞式接收包裹
				server.receive(packet);
				// 4、分析数据
				byte[] datas = packet.getData();
				int len = packet.getLength();
				String str = new String(datas, 0, len, "UTF-8");
				System.out.println(this.name + ":" + str);
				if(str.equals("bye")) {
					break;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
/**学生
 * @author Administrator
 */
public class TalkStudent {
	public static void main(String[] args) {
		new Thread(new TalkClient(9991, "localhost", 9992)).start(); // 发送
		new Thread(new TalkServer(9994, "老师")).start(); // 接收
	}
}

/**老师
 * @author Administrator
 */
public class TalkTeacher {
	public static void main(String[] args) {
		new Thread(new TalkClient(9993, "localhost", 9994)).start(); // 发送
		new Thread(new TalkServer(9992, "学生")).start(); // 接收
	}
}

12.3 TCP 编程

需求:完成网络登录功能
	用户输入用户名密码,服务器给出登录成功或失败的提示
分析:
	使用基于TCP协议的Socket网络编程实现
	TCP协议基于请求-响应模式
	在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序
	第一次通讯中等待连接的程序被称作服务器端(Server)程序
	利用I0流实现数据的传输
详细步骤(通信原理):
	服务器创建ServerSocket,在指定端口监听并并处理请求
	客户端创建Socket,向服务器发送请求
服务器端:
	创建ServerSocket(int port) 对象
	在Socket上监听客户端的连接请求
	阻塞, 等待连接的建立
	接收并处理请求信息
	将处理结果返回给客户端
	关闭流和Socket对象
客户端:
	创建Socket(tring hostint port)对象
	向服务器发送连接请求
	向服务器发送服务请求
	接收服务结果
	关闭流和Socket对象
12.3.1 服务器与客户端
/**
 * 创建客户端
 * 1. 建立连接:使用Socket创建客服端+服务器地址与端口
 * 2. 操作:输入输出操作
 * 3. 释放资源
 * @author Administrator
 */
public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2. 操作:输入输出操作
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		String data = "helo";
		dos.writeUTF(data);
		dos.flush();
		
		// 3. 释放资源
		dos.close();
		client.close();
	}
}
/**
 * 服务器
 * 1.使用ServerSocket创建服务器 指定端口
 * 2.阻塞式等待连接 accept
 * 3.操作:输入输出流
 * 4.释放资源
 * @author Administrator
 */
public class Server {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		// 2.阻塞式等待连接 accept
		Socket client = server.accept();
		System.out.println("一个客户端连接成功!");
		
		// 3.操作:输入输出流
		DataInputStream dis = new DataInputStream(client.getInputStream());
		String data = dis.readUTF();
		System.out.println(data);
		// 4.释放资源
		dis.close();
		client.close();
		
		server.close();
	}
}
12.3.2 模拟登录一
/**
 * 模拟登录 单向
 * 创建客户端
 * 1. 建立连接:使用Socket创建客服端+服务器地址与端口
 * 2. 操作:输入输出操作
 * 3. 释放资源
 * @author Administrator
 */
public class LoginClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2. 操作:输入输出操作
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入用户名:");
		String name = br.readLine();
		System.out.println("请输入密码:");
		String pwd = br.readLine();
		
		dos.writeUTF("name=" + name + "&pwd=" + pwd);
		dos.flush();
		
		// 3. 释放资源
		dos.close();
		client.close();
	}
}
/**
 * 模拟登录 单向
 * 服务器
 * 1.使用ServerSocket创建服务器 指定端口
 * 2.阻塞式等待连接 accept
 * 3.操作:输入输出流
 * 4.释放资源
 * @author Administrator
 */
public class LoginServer {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		// 2.阻塞式等待连接 accept
		Socket client = server.accept();
		System.out.println("一个客户端连接成功!");
		
		// 3.操作:输入输出流
		DataInputStream dis = new DataInputStream(client.getInputStream());
		String data = dis.readUTF();
		String[] datas = data.split("&");
		for(String s:datas) {
			String[] ss = s.split("=");
			System.out.println(ss[0]+":"+ss[1]);
		}
		// 4.释放资源
		dis.close();
		client.close();
		
		server.close();
	}
}
12.3.3 模拟登录二
/**
 * 模拟登录 双向
 * 创建客户端
 * 1. 建立连接:使用Socket创建客服端+服务器地址与端口
 * 2. 操作:输入输出操作
 * 3. 释放资源
 * @author Administrator
 */
public class LoginClient02 {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2. 操作:输入输出操作
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入用户名:");
		String name = br.readLine();
		System.out.println("请输入密码:");
		String pwd = br.readLine();
		
		dos.writeUTF("name=" + name + "&pwd=" + pwd);
		dos.flush();
		
		// 等待回应
		DataInputStream dis = new DataInputStream(client.getInputStream());
		String result = dis.readUTF();
		System.out.println(result);
		
		// 3. 释放资源
		dis.close();
		dos.close();
		client.close();
	}
}
/**
 * 模拟登录 双向
 * 服务器
 * 1.使用ServerSocket创建服务器 指定端口
 * 2.阻塞式等待连接 accept
 * 3.操作:输入输出流
 * 4.释放资源
 * @author Administrator
 */
public class LoginServer02 {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		// 2.阻塞式等待连接 accept
		Socket client = server.accept();
		System.out.println("一个客户端连接成功!");
		
		// 3.操作:输入输出流
		DataInputStream dis = new DataInputStream(client.getInputStream());
		String data = dis.readUTF();
		String[] datas = data.split("&");
		String name = "";
		String pwd = "";
		for(String s:datas) {
			String[] ss = s.split("=");
			if(ss[0].equals("name")) {
				name = ss[1];
			}
			if(ss[0].equals("pwd")) {
				pwd = ss[1];
			}
			System.out.println(ss[0]+":"+ss[1]);
		}
		
		// 回应
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		
		// 验证成功
		if(name.equals("admin") && pwd.equals("123456")) {
			dos.writeUTF("登录成功!");
		}else { // 失败
			dos.writeUTF("登录失败!");
		}
		dos.flush();
		
		// 4.释放资源
		dos.close();
		dis.close();
		client.close();
		
		server.close();
	}
}
12.3.4 文件传输
/**
 * 文件上传
 * 创建客户端
 * 1. 建立连接:使用Socket创建客服端+服务器地址与端口
 * 2. 操作:输入输出操作
 * 3. 释放资源
 * @author Administrator
 */
public class FileClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2. 操作:拷贝
		InputStream is = new BufferedInputStream(new FileInputStream("up/001.jpg"));
		OutputStream os = new BufferedOutputStream(client.getOutputStream());
		byte[] flush = new byte[1024];
		int len = -1;
		while((len = is.read(flush)) != -1) {
			os.write(flush, 0, len);
		}
		os.flush();
		// 3. 释放资源
		os.close();
		is.close();
		client.close();
	}
}
/**
 * 文件存储
 * 服务器
 * 1.使用ServerSocket创建服务器 指定端口
 * 2.阻塞式等待连接 accept
 * 3.操作:输入输出流
 * 4.释放资源
 * @author Administrator
 */
public class FileServer {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		// 2.阻塞式等待连接 accept
		Socket client = server.accept();
		System.out.println("一个客户端连接成功!");
		
		// 3.操作:文件存储
		InputStream is = new BufferedInputStream(client.getInputStream());
		OutputStream os = new BufferedOutputStream(
            new FileOutputStream("down/001_copy.jpg", true));
		byte[] flush = new byte[1024];
		int len = -1;
		while((len = is.read(flush)) != -1) {
			os.write(flush, 0, len);
		}
		os.flush();
		// 4.释放资源
		os.close();
		is.close();
		client.close();
		server.close();
	}
}
12.3.5 多用户访问
/**
 * 模拟登录 多个客户端
 * 创建客户端
 * 1. 建立连接:使用Socket创建客服端+服务器地址与端口
 * 2. 操作:输入输出操作
 * 3. 释放资源
 * @author Administrator
 */
public class LoginClient03 {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2. 操作:先请求后相应
		new Send(client).send();
		new Receive(client).receive();
		
		// 3. 释放资源
		client.close();
	}
	
	static class Send{
		private DataOutputStream dos;
		private Socket client;
		private BufferedReader br;
		private String msg;
		
		// 初始化
		private String init() {
			String name = "";
			String pwd = "";
			try {
				// 准备数据
				System.out.println("请输入姓名:");
				name = br.readLine().trim();
				System.out.println("请输入密码:");
				pwd = br.readLine().trim();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if(br != null) {
					br.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return "name=" + name + "&pwd=" + pwd;
		}
		
		public Send(Socket client){
			this.client = client;
			try {
				dos = new DataOutputStream(client.getOutputStream());
				br = new BufferedReader(new InputStreamReader(System.in));
			} catch (IOException e) {
				e.printStackTrace();
			}
			this.msg = init();
		}
		// 发送
		public void send() {
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	static class Receive{
		private DataInputStream dis;
		private Socket client;
		
		public Receive(Socket client) {
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		// 接收
		public void receive() {
			try {
				String result = dis.readUTF();
				System.out.println(result);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
/**
 * 模拟登录 多个客户端请求
 * 服务器
 * 1.使用ServerSocket创建服务器 指定端口
 * 2.阻塞式等待连接 accept
 * 3.操作:输入输出流
 * 4.释放资源
 * @author Administrator
 */
public class LoginServer03 {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		boolean isRunning = true;
		while(isRunning) {
			// 2.阻塞式等待连接 accept
			Socket client = server.accept();
			System.out.println("一个客户端连接成功!");
			new Thread(new Channel(client)).start();
		}
		
		server.close();
	}
	
	static class Channel implements Runnable{
		private Socket client;
		private DataInputStream dis;
		private DataOutputStream dos;
		public Channel(Socket client) {
			this.client = client;
			try {
				// 输入输出流
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
			} catch (IOException e) {
				e.printStackTrace();
				release();
			}
		}

		// 接收数据
		private String receive() {
			String data = "";
			try {
				data = dis.readUTF();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return data;
		}
		
		// 发送数据
		private void send(String msg) {
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		// 释放资源
		private void release() {
			try {
				if(dos != null) {
					dos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if(dis != null) {
					dis.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if(client != null) {
					client.close();
				}
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
		
		@Override
		public void run() {
			String name = "";
			String pwd = "";
			String[] datas = receive().split("&");
			for(String s:datas) {
				String[] ss = s.split("=");
				if(ss[0].equals("name")) {
					name = ss[1];
				}
				if(ss[0].equals("pwd")) {
					pwd = ss[1];
				}
				System.out.println(ss[0]+":"+ss[1]);
			}
			
			// 验证成功
			if(name.equals("admin") && pwd.equals("123456")) {
				send("登录成功!");
			}else { // 失败
				send("登录失败!");
			}
			release();
		}
	}
}
12.3.6 聊天一
/**
 * 在线聊天室:客户端
 * 目标:实现一个客户可以重复收发信息
 * @author Administrator
 */
public class MulitClient {
	public static void main(String[] args) throws IOException{
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2.发送消息
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		DataInputStream dis = new DataInputStream(client.getInputStream());
		boolean isRunning = true;
		while(isRunning) {
			String msg = br.readLine().trim();
			dos.writeUTF(msg);
			dos.flush();
			
			// 3.接收消息
			String result = dis.readUTF();
			System.out.println(result);
		}
		
		// 3. 释放资源
		dis.close();
		br.close();
		dos.close();
		client.close();
	}
}
/**
 * 在线聊天室:服务端
 * 目标:实现一个客户可以正常收发信息
 * @author Administrator
 */
public class MulitChat {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		// 2.阻塞式等待连接 accept
		Socket client = server.accept();
		System.out.println("一个客户端连接成功!");
		
		// 3.接收消息
		DataInputStream dis = new DataInputStream(client.getInputStream());
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		boolean isRunning = true;
		while(isRunning) {
			String data = dis.readUTF();
			
			// 4.返回消息
			dos.writeUTF(data);
			dos.flush();
		}
		
		//释放资源
		dos.close();
		dis.close();
		server.close();
	}
}
12.3.7 聊天二
/**
 * 在线聊天室:客户端
 * 目标:使用多线程实现多个客户正常收发信息
 * 问题:代码不好维护、客户端没有读写分离
 * @author Administrator
 */
public class TMulitClient01 {
	public static void main(String[] args) throws IOException{
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2.发送消息
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		DataInputStream dis = new DataInputStream(client.getInputStream());
		boolean isRunning = true;
		while(isRunning) {
			String msg = br.readLine().trim();
			dos.writeUTF(msg);
			dos.flush();
			
			// 3.接收消息
			String result = dis.readUTF();
			System.out.println(result);
		}
		
		// 3. 释放资源
		dis.close();
		dos.close();
		br.close();
		client.close();
	}
}
/**
 * 在线聊天室:服务端
 * 目标:使用多线程实现多个客户正常收发信息
 * 问题:代码不好维护、客户端没有读写分离
 * @author Administrator
 */
public class TMulitChat01 {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		while(true) {
			// 2.阻塞式等待连接 accept
			Socket client = server.accept();
			System.out.println("一个客户端连接成功!");
			
			new Thread(()->{
				// 3.接收消息
				DataInputStream dis = null;
				DataOutputStream dos = null;
				try {
					dis = new DataInputStream(client.getInputStream());
					dos = new DataOutputStream(client.getOutputStream());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
				boolean isRunning = true;
				while(isRunning) {
					String data;
					try {
						data = dis.readUTF();
						// 4.返回消息
						dos.writeUTF(data);
						dos.flush();
					} catch (IOException e) {
						e.printStackTrace();
						isRunning = false;
					}
				}
				//释放资源
				try {
					if(dos != null) {
						dos.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					if(dis != null) {
						dis.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					if(client != null) {
						client.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}).start();
		}
	}
}
12.3.8 封装
/**
 * 工具类
 * 释放资源
 * @author Administrator
 */
class SxtUtils {
	public static void close(Closeable...targets) {
		for(Closeable target:targets) {
			try {
				if(target != null) {
					target.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
/**
 * 在线聊天室:客户端
 * 目标:使用多线程实现多个客户正常收发信息
 * @author Administrator
 */
public class TMulitClient02 {
	public static void main(String[] args) throws IOException{
		System.out.println("————客户端————");
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2.客户端发送消息
		new Thread(new Send(client)).start();
		
		// 3.客户端接收消息
		new Thread(new Receive(client)).start();
	}
}
/**
 * 使用多线程封装发送端
 * 1、从控制台获取消息
 * 2、发送信息
 * 3、释放资源
 * @author Administrator
 */
public class Send implements Runnable{
	private Socket client;
	private BufferedReader br;
	private DataOutputStream dos;
	private boolean isRunning;
	
	public Send(Socket client) {
		this.client = client;
		br = new BufferedReader(new InputStreamReader(System.in));
		try {
			dos = new DataOutputStream(client.getOutputStream());
			this.isRunning = true;
		} catch (IOException e) {
			release();
		}
	}
	
	@Override
	public void run() {
		while(isRunning) {
			String msg = getStrFromConsole();
			if(!msg.equals("")) {
				send(msg);
			}
		}
	}
	
	// 发送信息
	private void send(String datas) {
		try {
			dos.writeUTF(datas);
			dos.flush();
		} catch (IOException e) {
			release();
		}
	}
	
	/**
	 * 从控制台获取消息
	 * @return
	 */
	private String getStrFromConsole() {
		try {
			return br.readLine().trim();
		} catch (IOException e) {
			release();
		}
		return "";
	}
	
	// 释放资源
	private void release() {
		isRunning = false;
		SxtUtils.close(dos, br, client);
	}
}
/**
 * 使用多线程封装接收端
 * 1、接收消息
 * 2、释放资源
 * @author Administrator
 */
public class Receive implements Runnable{
	private Socket client;
	private DataInputStream dis;
	private boolean isRunning;
	
	public Receive(Socket client) {
		this.client = client;
		try {
			dis = new DataInputStream(client.getInputStream());
			this.isRunning = true;
		} catch (IOException e) {
			release();
		}
	}
	
	@Override
	public void run() {
		while(isRunning) {
			String msg = receive();
			if(!msg.equals("")) {
				System.out.println(msg);
			}
		}
	}
	
	// 接收消息
	private String receive() {
		String msg = "";
		try {
			msg = dis.readUTF();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return msg;
	}
	
	// 释放资源
	private void release() {
		isRunning = false;
		SxtUtils.close(dis, client);
	}
}
/**
 * 在线聊天室:服务端
 * 目标:使用多线程实现多个客户正常收发信息
 * 封装
 * @author Administrator
 */
public class TMulitChat02 {
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		while(true) {
			// 2.阻塞式等待连接 accept
			Socket client = server.accept();
			System.out.println("一个客户端连接成功!");
			
			new Thread(new Channel(client)).start();
		}
	}
	
	// 一个客户代表一个channel
	static class Channel implements Runnable{
		private Socket client;
		private DataInputStream dis;
		private DataOutputStream dos;
		private boolean isRunning;
		
		public Channel(Socket client) {
			this.client = client;
			try {
				this.dis = new DataInputStream(client.getInputStream());
				this.dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
			} catch (IOException e) {
				release();
			}
		}
		
		// 接收消息
		private String receive() {
			String msg = "";
			try {
				 msg = dis.readUTF();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return msg;
		}
		// 发送信息
		private void send(String datas) {
			try {
				dos.writeUTF(datas);
				dos.flush();
			} catch (IOException e) {
				release();
			}
		}
		// 释放资源
		private void release() {
			this.isRunning = false;
			SxtUtils.close(dis,dos,client);
		}

		@Override
		public void run() {
			while(isRunning) {
				String msg = receive();
				if(!msg.equals("")) {
					send(msg);
				}
			}
		}
	}
}
12.3.9 群聊
/**
 * 在线聊天室:客户端
 * 目标:加入容器实现群聊
 * @author Administrator
 */
class Client {
	public static void main(String[] args) throws IOException{
		System.out.println("————客户端————");
		// 身份
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入用户名:");
		String name = br.readLine().trim();
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2.客户端发送消息
		new Thread(new Send(client,name)).start();
		
		// 3.客户端接收消息
		new Thread(new Receive(client)).start();
	}
}
/**
 * 在线聊天室:服务器
 * 目标:加入容器实现群聊 
 * @author Administrator
 */
public class Chat {
	private static CopyOnWriteArrayList<Channel>  all = new CopyOnWriteArrayList<>();
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		while(true) {
			// 2.阻塞式等待连接 accept
			Socket client = server.accept();
			System.out.println("一个客户端建立连接!");
			Channel c = new Channel(client);
			all.add(c); // 管理所有的成员
			new Thread(c).start();
		}
	}
	
	// 一个客户代表一个channel
	static class Channel implements Runnable{
		private Socket client;
		private DataInputStream dis;
		private DataOutputStream dos;
		private boolean isRunning;
		private String name;
		
		public Channel(Socket client) {
			this.client = client;
			try {
				this.dis = new DataInputStream(client.getInputStream());
				this.dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
				// 获取名称
				this.name = receive();
				// 系统反馈信息
				this.send("欢迎进入帝都聊天室!");
				sendOthers("欢迎"+ this.name + "进入聊天室!", true);
			} catch (IOException e) {
				release();
			}
		}
		
		// 接收消息
		private String receive() {
			String msg = "";
			try {
				 msg = dis.readUTF();
			} catch (IOException e) {
				release();
			}
			return msg;
		}
		// 发送信息
		private void send(String msg) {
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				release();
			}
		}
		
		/**
		 * 群聊:获取自己的消息,发给其他人
		 * @param msg 信息
		 *  * @param msg 是否为系统消息
		 */
		private void sendOthers(String msg, boolean isSys) {
			for(Channel other:all) {
				if(other == this) { // 自己
					continue;
				}
				if(isSys == true) {
					other.send(msg);
				}else {
					other.send(this.name + ":" + msg);
				}
			}
		}
				
		// 释放资源
		private void release() {
			this.isRunning = false;
			SxtUtils.close(dis,dos,client);
			// 退出
			all.remove(this);
			sendOthers(this.name + "已离开!",true);
		}

		@Override
		public void run() {
			while(isRunning) {
				String msg = receive();
				if(!msg.equals("")) {
					//send(msg); // 发给自己
					sendOthers(msg,false);
				}
			}
		}
	}
}
12.3.10 私聊
/**
 * 在线聊天室:客户端
 * 目标:加入容器实现群聊
 * @author Administrator
 */
class Client02 {
	public static void main(String[] args) throws IOException{
		System.out.println("————客户端————");
		// 身份
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入用户名:");
		String name = br.readLine().trim();
		// 1. 建立连接:使用Socket创建客服端+服务器地址与端口
		Socket client = new Socket("localhost", 8888);
		
		// 2.客户端发送消息
		new Thread(new Send(client,name)).start();
		
		// 3.客户端接收消息
		new Thread(new Receive(client)).start();
	}
}
/**
 * 在线聊天室:服务器
 * 目标:私聊
 * @author Administrator
 */
public class Chat02 {
	private static CopyOnWriteArrayList<Channel>  all = new CopyOnWriteArrayList<>();
	public static void main(String[] args) throws IOException {
		System.out.println("————服务器————");
		// 1.使用ServerSocket创建服务器 指定端口
		ServerSocket server = new ServerSocket(8888);
		
		while(true) {
			// 2.阻塞式等待连接 accept
			Socket client = server.accept();
			System.out.println("一个客户端建立连接!");
			Channel c = new Channel(client);
			all.add(c); // 管理所有的成员
			new Thread(c).start();
		}
	}
	
	// 一个客户代表一个channel
	static class Channel implements Runnable{
		private Socket client;
		private DataInputStream dis;
		private DataOutputStream dos;
		private boolean isRunning;
		private String name;
		
		public Channel(Socket client) {
			this.client = client;
			try {
				this.dis = new DataInputStream(client.getInputStream());
				this.dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
				// 获取名称
				this.name = receive();
				// 系统反馈信息
				this.send("欢迎进入帝都聊天室!");
				sendOthers("欢迎"+ this.name + "进入聊天室!", true);
			} catch (IOException e) {
				release();
			}
		}
		
		// 接收消息
		private String receive() {
			String msg = "";
			try {
				 msg = dis.readUTF();
			} catch (IOException e) {
				release();
			}
			return msg;
		}
		// 发送信息
		private void send(String msg) {
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				release();
			}
		}
		
		/**
		 * 群聊:获取自己的消息,发给其他人
		 * 私聊:约定数据格式:@xxx:内容
		 * @param msg 信息
		 *  * @param msg 是否为系统消息
		 */
		private void sendOthers(String msg, boolean isSys) {
			boolean isPrivate = msg.startsWith("@");
			if(isPrivate) { // 私聊
				int idx = msg.indexOf(":");
				// 获取目标和数据
				String targetname = msg.substring(1,idx);
				msg = msg.substring(idx+1);
				for(Channel other:all) {
					if(targetname.equals(other.name)) {
						other.send(this.name + "私聊你:" + msg);
					}
				}
			}else {
				for(Channel other:all) {
					if(other == this) { // 自己
						continue;
					}
					if(isSys == true) {
						other.send(msg);
					}else {
						other.send(this.name + ":" + msg);
					}
				}
			}
		}
				
		// 释放资源
		private void release() {
			this.isRunning = false;
			SxtUtils.close(dis,dos,client);
			// 退出
			all.remove(this);
			sendOthers(this.name + "已离开!",true);
		}

		@Override
		public void run() {
			while(isRunning) {
				String msg = receive();
				if(!msg.equals("")) {
					//send(msg); // 发给自己
					sendOthers(msg,false);
				}
			}
		}
	}
}

第十三章 手写服务器

13.1 反射

	反射Reflection:把java类中的各种结构(方法、属性、构造器、类名)映射成个个的Java对象。
利用反射技术可以对一个类进行解剖,反射是框架设计的灵魂。
/**
 * 反射
 * 1、获取Class对象
 * 三中方式
 * 1、对象.getClass()
 * 2、Iphone.class
 * 3、Class.forName("com.server.basic.Iphone")
 * 创建对象:
 * 1、(Iphone)clz.newInstance();
 * 2、(Iphone)clz.getConstructor().newInstance(); 推荐
 * @author Administrator
 *
 */
public class ReflectTest {
	public static void main(String[] args) throws 
	ClassNotFoundException, InstantiationException, 
	IllegalAccessException, IllegalArgumentException, 
	InvocationTargetException, NoSuchMethodException, SecurityException {
		// 三中方式
		Iphone iphone = new Iphone();
		// 1、对象.getClass()
		Class clz = new Iphone().getClass();
		
		// 2、类.class()
		clz = Iphone.class;
		
		// 3 包名与类名
		Class.forName("com.server.basic.Iphone");
		
		// 创建对象 (不推荐)
		Iphone iphone2 = (Iphone)clz.newInstance();
		System.out.println(iphone2);
		
		// 创建对象(通过构造器,推荐)
		Iphone iphone3 = (Iphone)clz.getConstructor().newInstance();
		System.out.println(iphone3);
	}
}
class Iphone{
	public Iphone(){
	}
}

13.2 XML解析

	XML: Extensible Markup Language,可扩展标记语言,作为数据的一种存储格式或用于存储软件的参数,
程序解析此配置文件,就可以到达不修改代码就能更改程序的目的。
13.2.1 解析流程
/**
 * 熟悉SAX解析流程
 * 1、获取解析工厂
 * 2、从解析工厂获取解析器
 * 3、加载Document注册处理器
 * 4、编写处理器
 * 5、解析
 * @author Administrator
 */
public class XmlTest01 {
	public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException{
		// SAX解析
		// 1、获取解析工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		
		// 2、从解析工厂获取解析器
		SAXParser parse =factory.newSAXParser();
		
		// 3、加载Document注册处理器
		
		// 4、编写处理器
		PHandler handler=new PHandler();
		
		//5、解析
		parse.parse(Thread.currentThread()
				.getContextClassLoader().getResourceAsStream("com/server/basic/p.xml"),
				handler);
	}
}

/**
 * SAX解析流程
 * 解析文档开始->persons解析开始->person解析开始->name解析开始
 * ->name解析结束->person解析结束->persons解析结束->解析文档结束
 * @author Administrator
 *
 */
class PHandler extends DefaultHandler{
	@Override
	public void startDocument() throws SAXException {
		System.out.println("———————解析文档开始———————");
	}
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		System.out.println(qName + "-->解析开始");
	}
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		String contents = new String(ch,start,length).trim();
		if(contents.length()>0) {
			System.out.println("内容为-->" + contents);
		}else {
			System.out.println("内容为-->" + "空");
		}
	}
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		System.out.println(qName + "-->解析结束");
	}
	@Override
	public void endDocument() throws SAXException {
		System.out.println("———————解析文档结束———————");
	}
}
13.2.2 获取值
/**
 * 熟悉SAX解析流程
 * 1、获取解析工厂
 * 2、从解析工厂获取解析器
 * 3、加载Document注册处理器
 * 4、编写处理器
 * 5、解析
 * @author Administrator
 */
public class XmlTest02 {
	public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException{
		// SAX解析
		// 1、获取解析工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		
		// 2、从解析工厂获取解析器
		SAXParser parse =factory.newSAXParser();
		
		// 3、加载Document注册处理器
		
		// 4、编写处理器
		PersonHandler handler=new PersonHandler();
		
		//5、解析
		parse.parse(Thread.currentThread()
				.getContextClassLoader().getResourceAsStream("com/server/basic/p.xml"),
				handler);
		
		// 6、获取数据
		List<Person> persons = handler.getPersons();
		for(Person p:persons) {
			System.out.println("姓名:" + p.getName() + "\t年龄:" + p.getAge());
		}
	}
}

/**
 * SAX解析流程
 * 解析文档开始->persons解析开始->person解析开始->name解析开始
 * ->name解析结束->person解析结束->persons解析结束->解析文档结束
 * @author Administrator
 *
 */
class PersonHandler extends DefaultHandler{
	private List<Person> persons;
	private Person person;
	private String tag; // 存储操作的标签
	public List<Person> getPersons() {
		return persons;
	}
	@Override
	public void startDocument() throws SAXException {
		persons = new ArrayList<Person>();
	}
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if(qName != null) {
			tag = qName;
			if(qName.equals("person")) {
				person = new Person();
			}
		}
	}
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		String contents = new String(ch,start,length).trim();
		if(contents.length() > 0 && tag != null) { // 处理内容为空的问题
			if(tag.equals("name")) {
				person.setName(contents);
			}else if(tag.equals("age")) {
				person.setAge(Integer.valueOf(contents));
			}
		}
	}
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if(qName != null) {
			if(qName.equals("person")) {
				persons.add(person);
			}
		}
		tag = null; // tag 丢弃了
	}
	@Override
	public void endDocument() throws SAXException {
	}
}
/**
 * 熟悉SAX解析流程
 * 1、获取解析工厂
 * 2、从解析工厂获取解析器
 * 3、加载Document注册处理器
 * 4、编写处理器
 * 5、解析
 * @author Administrator
 */
public class XmlTest03 {
	public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException{
		// SAX解析
		// 1、获取解析工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		
		// 2、从解析工厂获取解析器
		SAXParser parse =factory.newSAXParser();
		
		// 3、加载Document注册处理器
		
		// 4、编写处理器
		WebHandler handler=new WebHandler();
		
		//5、解析
		parse.parse(Thread.currentThread()
				.getContextClassLoader().getResourceAsStream("com/server/basic/servlet/web.xml"),
				handler);
		
		// 6、获取数据
		List<Entity> entitys = handler.getEntitys();
		List<Mapping> mappings = handler.getMappings();
		for(Entity e:entitys) {
			System.out.println("servlet-name:" + e.getName() + "\tservlet-class:" + e.getClz());
		}
		for(Mapping m:mappings) {
			System.out.println("servlet-name:" + m.getName() + "\tservlet-class:" +m.getPatterns());
		}
	}
}

/**
 * SAX解析流程
 * 解析文档开始->persons解析开始->person解析开始->name解析开始
 * ->name解析结束->person解析结束->persons解析结束->解析文档结束
 * @author Administrator
 */
class WebHandler extends DefaultHandler{
	private List<Entity> entitys;
	private List<Mapping> mappings;
	private Entity entity;
	private Mapping mapping;
	private String tag; // 存储操作的标签
	private boolean isMapping = false;
	public List<Entity> getEntitys() {
		return entitys;
	}
	public List<Mapping> getMappings() {
		return mappings;
	}
	@Override
	public void startDocument() throws SAXException {
		entitys = new ArrayList<Entity>();
		mappings = new ArrayList<Mapping>();
	}
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if(qName != null) {
			tag = qName;
			if(qName.equals("servlet")) {
				entity = new Entity();
				isMapping = false;
			}else if(qName.equals("servlet-mapping")){
				mapping = new Mapping();
				isMapping = true;
			}
		}
	}
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		String contents = new String(ch,start,length).trim();
		if(contents.length() > 0 && tag != null) { // 处理内容为空的问题
			if(isMapping) { // 操作servlet-mapping
				if(tag.equals("servlet-name")) {
					mapping.setName(contents);
				}else if(tag.equals("url-pattern")) {
					mapping.addPattern(contents);
				}
			}else { // 操作servlet
				if(tag.equals("servlet-name")) {
					entity.setName(contents);
				}else if(tag.equals("servlet-class")) {
					entity.setClz(contents);
				}
			}
		}
	}
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if(qName != null) {
			if(qName.equals("servlet")) {
				entitys.add(entity);
			}else if(qName.equals("servlet-name")) {
				if(mapping != null) {
					mappings.add(mapping);
				}
			}
		}
		tag = null; // tag 丢弃了
	}
	@Override
	public void endDocument() throws SAXException {
	}
}

13.3 反射与xml解析

13.3.1 xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<servlet>
		<servlet-name>login</servlet-name>
		<servlet-class>com.server.basic.servlet.LoginServlet
		</servlet-class>
	</servlet>
	<servlet>
		<servlet-name>reg</servlet-name>
		<servlet-class>com.server.basic.servlet.RegisterServlet
		</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>login</servlet-name>
		<url-pattern>/login</url-pattern>
		<url-pattern>/g</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>reg</servlet-name>
		<url-pattern>/reg</url-pattern>
	</servlet-mapping>
</web-app>
13.3.2 数据类一
/**用于存储xml中对应数据
 * <servlet>
		<servlet-name>login</servlet-name>
		<servlet-class>com.sxt.server.basic.servlet.LoginServlet
		</servlet-class>
	</servlet>
 * @author Administrator
 */
public class Entity {
	private String name;
	private String clz;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getClz() {
		return clz;
	}
	public void setClz(String clz) {
		this.clz = clz;
	}
	@Override
	public String toString() {
		return "Entity [name=" + name + ", clz=" + clz + "]";
	}
	public Entity(String name, String clz) {
		super();
		this.name = name;
		this.clz = clz;
	}
	public Entity() {
		super();
	}
}
13.3.3 数据类二
/**用于存储xml中对应数据
 * <servlet-mapping>
		<servlet-name>login</servlet-name>
		<url-pattern>/login</url-pattern>
		<url-pattern>/g</url-pattern>
	</servlet-mapping>
 * @author Administrator
 */
public class Mapping {
	private String name;
	private Set<String> patterns;
	public Mapping() {
		patterns = new HashSet<String>();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Set<String> getPatterns() {
		return patterns;
	}
	public void setPatterns(Set<String> patterns) {
		this.patterns = patterns;
	}
	public Mapping(String name, Set<String> patterns) {
		super();
		this.name = name;
		this.patterns = patterns;
	}
	@Override
	public String toString() {
		return "Mapping [name=" + name + ", patterns=" + patterns + "]";
	}
	public void addPattern(String pattern) {
		this.patterns.add(pattern);
	}
}
13.3.4 处理数据类
/**
 * 工具类 处理xml中的数据
 * 处理 Entity数据与Mapping数据
 * 通过Mapping的pattern值找到Entity中对应class的值
 * @author Administrator
 */
public class WebContext {
	private List<Entity> entitys;
	private List<Mapping> mappings;
	
	// key-->servlet-name  value-->servlet-class
	private Map<String, String> entityMap = new HashMap<String, String>();
	
	//  key-->url-pattern  value-->servlet-name
	private Map<String, String> mappingMap = new HashMap<String, String>();
	
	public WebContext(List<Entity> entitys, List<Mapping> mappings) {
		super();
		this.entitys = entitys;
		this.mappings = mappings;
		
		// 将entity 的 List 转成了对应的 map
		for(Entity e:entitys) {
			entityMap.put(e.getName(), e.getClz());
		}
		
		// 将mapping 的 List 转成了对应的 map
		for(Mapping m:mappings) {
			for(String p:m.getPatterns()) {
				mappingMap.put(p, m.getName());
			}
		}
	}
	
	/**
	 * 通过URL的路径找到了对应的class
	 * @param pattern
	 * @return 
	 */
	public String getClz(String pattern) {
		 String name = mappingMap.get(pattern);
		return entityMap.get(name);
	}
}
13.3.5 SAX解析
/**
 * 熟悉SAX解析流程
 * 1、获取解析工厂
 * 2、从解析工厂获取解析器
 * 3、加载Document注册处理器
 * 4、编写处理器
 * 5、解析
 * @author Administrator
 */
public class XmlTest03 {
	public static void main(String[] args) throws Exception{
		// SAX解析
		// 1、获取解析工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		
		// 2、从解析工厂获取解析器
		SAXParser parse =factory.newSAXParser();
		
		// 3、加载Document注册处理器
		
		// 4、编写处理器
		WebHandler handler=new WebHandler();
		
		//5、解析
		parse.parse(Thread.currentThread()
				.getContextClassLoader().getResourceAsStream("com/server/basic/servlet/web.xml"),
				handler);
		
		// 6、获取数据
		WebContext context = new WebContext(handler.getEntitys(), handler.getMappings());
		
		// 假设你输入了 /login
		String className = context.getClz("/reg"); // 返回类的路径
		
		// 反射
		Class clz = Class.forName(className);
		// 实例化
		Servlet servlet = (Servlet)clz.getConstructor().newInstance();
		
		System.out.println(servlet);
		servlet.service();
	}
}

/**
 * SAX解析流程
 * 解析文档开始->persons解析开始->person解析开始->name解析开始
 * ->name解析结束->person解析结束->persons解析结束->解析文档结束
 * @author Administrator
 */
class WebHandler extends DefaultHandler{
	private List<Entity> entitys;
	private List<Mapping> mappings;
	private Entity entity;
	private Mapping mapping;
	private String tag; // 存储操作的标签
	private boolean isMapping = false;
	public List<Entity> getEntitys() {
		return entitys;
	}
	public List<Mapping> getMappings() {
		return mappings;
	}
	@Override
	public void startDocument() throws SAXException {
		entitys = new ArrayList<Entity>();
		mappings = new ArrayList<Mapping>();
	}
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if(qName != null) {
			tag = qName;
			if(qName.equals("servlet")) {
				entity = new Entity();
				isMapping = false;
			}else if(qName.equals("servlet-mapping")){
				mapping = new Mapping();
				isMapping = true;
			}
		}
	}
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		String contents = new String(ch,start,length).trim();
		if(contents.length() > 0 && tag != null) { // 处理内容为空的问题
			if(isMapping) { // 操作servlet-mapping
				if(tag.equals("servlet-name")) {
					mapping.setName(contents);
				}else if(tag.equals("url-pattern")) {
					mapping.addPattern(contents);
				}
			}else { // 操作servlet
				if(tag.equals("servlet-name")) {
					entity.setName(contents);
				}else if(tag.equals("servlet-class")) {
					entity.setClz(contents);
				}
			}
		}
	}
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if(qName != null) {
			if(qName.equals("servlet")) {
				entitys.add(entity);
			}else if(qName.equals("servlet-name")) {
				if(mapping != null) {
					mappings.add(mapping);
				}
			}
		}
		tag = null; // tag 丢弃了
	}
	@Override
	public void endDocument() throws SAXException {
		
	}
}
13.3.6 接口与实现类
// 接口
public interface Servlet {
	void service();
}

// 实现接口一
public class LoginServlet implements Servlet {
	@Override
	public void service() {
		System.out.println("LoginServlet");
	}
}

// 实现接口二
public class RegisterServlet implements Servlet {
	@Override
	public void service() {
		System.out.println("RegisterServlet");
	}
}

13.4 HTTP协议

	超文本传输协议(HTTP, Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络协议,
所有的wWW文件都必须遵守这个标准。
请求协议:
	1、请求行:方法(GET/POST)、URI、 协议版本
	2、请求头: (Request Header)
	3、请求正文:
响应协议:
	1、状态行:协议/版本 状态码 状态描述
	2、响应头(Response Header)
	3、响应正文:
13.4.1 http请求协议
GET /index.html?name=tesF&upwd=123456 HTTP/1.1
Accept: text/html,application/xhtmlxml,*/*
Accept-Language: zh-CN
User-Agent: Moilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost
Connection: Keep-Alive

POST /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
User-Agent: Moilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost
Connection: Keep-Alive

name=test&pwd=123456
13.4.2 http响应协议
典型的响应协议:
	1、状态行:HTTP/1.0 200 OK
	2、响应头:
        Date:Mon,31Dec209904:25:57GMT
        Server:shsxt Server/0.0.1;charset=GBK
        Content-typetext/html
        Content-length:39725426
	3、请求正文(注意与请求头之间有个空行)
	
	X000xx
	
状态码:
-1xx   指示信息一表示请求已接收,继续处理。
-2xx   成功-表示请求已经被成功接收、理解、接受。如:200工OK客户端请求成功
-3xx   重定向要完成请求必须进行更进一步的操作。
-4xx   客户端错误-请求有语法错误或请求无法实现。如404,Not Found请求的资源不存在,例:输入了错误的URL
-5xx   服务器端错误服务器未能实现合法的请求。
13.4.3 请求与响应
/**
 * 目标:使用ServerSocket建立与浏览器的连接,获取请求协议
 * 发送响应内容
 * @author Administrator
 */
public class Server01 {
	private ServerSocket serverSocket;
	public static void main(String[] args) {
		Server01 server = new Server01();
		server.start();
	}
	
	// 启动服务
	public void start() {
		try {
			// 使用ServerSocket创建服务器 指定端口
			serverSocket = new ServerSocket(8888);
			receive();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("服务器启动失败!");
		}
	}
	
	// 接受连接处理
	public void receive() {
		// 阻塞式等待连接 accept
		try {
			Socket client = serverSocket.accept();
			System.out.println("一个客户端建立了连接........");
			// 获取请求协议
			InputStream is = client.getInputStream();
			byte[] datas = new byte[1024*100];
			int len = is.read(datas);
			String requestInfo = new String(datas,0,len);
			System.out.println(requestInfo);
			
			StringBuilder content =new StringBuilder();
			content.append("<html>");
			content.append("<head>");
			content.append("<title>");
			content.append("服务器响应成功");
			content.append("</title>");
			content.append("</head>");
			content.append("<body>");
			content.append("shsxt server终于回来了。。。。");
			content.append("</body>");
			content.append("</html>");
			int size = content.toString().getBytes().length; //必须获取字节长度
			StringBuilder responseInfo =new StringBuilder();
			String blank =" ";
			String CRLF = "\r\n";
			//返回
			//1、响应行: HTTP/1.1 200 OK
			responseInfo.append("HTTP/1.1").append(blank);
			responseInfo.append(200).append(blank);
			responseInfo.append("OK").append(CRLF);
			//2、响应头(最后一行存在空行):
			/*
			 Date:Mon,31Dec209904:25:57GMT
			Server:shsxt Server/0.0.1;charset=GBK
			Content-type:text/html
			Content-length:39725426
			 */
			responseInfo.append("Date:").append(new Date()).append(CRLF);
			responseInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
			responseInfo.append("Content-type:text/html").append(CRLF);
			responseInfo.append("Content-length:").append(size).append(CRLF);
			responseInfo.append(CRLF);
			//3、正文
			responseInfo.append(content.toString());
			
			//写出到客户端			
			BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
			bw.write(responseInfo.toString());
			bw.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("客户端错误!");
		}
	}
	
	// 结束服务
	public void stop() {
		
	}
}
13.4.4 封装响应
/**
 * 1、响应信息
 * 2、动态加载内容
 * 3、发送响应请求
 * @author Administrator
 */
class Response {
	private BufferedWriter bw;
	// 正文
	private StringBuilder content;
	// 协议头(状态行与请求头 回车)信息
	private StringBuilder headInfo;
	private int len; //正文的字节数
	
	private final String BLANK =" ";
	private final  String CRLF = "\r\n";
	
	private Response() {
		this.content = new StringBuilder();
		this.headInfo = new StringBuilder();
		len = 0;
	}
	public Response(Socket client) {
		this();
		try {
			bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
			headInfo = null;
		}
	}
	public Response(OutputStream os) {
		this();
		bw =new BufferedWriter(new OutputStreamWriter(os));
	}
	
	//动态添加内容
	public Response print(String info) {
		content.append(info).append(CRLF);
		len += (info+CRLF).getBytes().length;
		return this;
	}
	public	Response println(String info) {
		content.append(info).append(CRLF);
		len+=(info+CRLF).getBytes().length;
		return this;
	}
	
	// 推送响应信息
	public void pushToBrowser(int code) throws IOException {
		if(null ==headInfo) {
			code = 505;
		}
		createHeadInfo(code);
		bw.append(headInfo);
		bw.append(content);
		bw.flush();
	}
	
	// 构造头信息
	private void createHeadInfo(int code) {
		// 1、响应行: HTTP/1.1 200 OK
		headInfo.append("HTTP/1.1").append(BLANK);
		headInfo.append(code).append(BLANK);
		switch (code) {
		case 200:
			headInfo.append("OK").append(CRLF);
			break;
		case 404:
			headInfo.append("NOT FOUND").append(CRLF);
			break;
		case 505:
			headInfo.append("SERVER ERROR").append(CRLF);
			break;
		}
		// 2、响应头(最后一行存在空行)
		headInfo.append("Date:").append(new Date()).append(CRLF);
		headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
		headInfo.append("Content-type:text/html").append(CRLF);
		headInfo.append("Content-length:").append(len).append(CRLF);
		headInfo.append(CRLF);
	}
}
/**
 * 目标:封装响应信息
 * 1、内容可以动态添加
 * 2、关注状态码
 * @author Administrator
 */
public class Server02 {
	private ServerSocket serverSocket;
	public static void main(String[] args) {
		Server01 server = new Server01();
		server.start();
	}
	
	// 启动服务
	public void start() {
		try {
			// 使用ServerSocket创建服务器 指定端口
			serverSocket = new ServerSocket(8888);
			receive();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("服务器启动失败!");
		}
	}
	
	// 接受连接处理
	public void receive() {
		// 阻塞式等待连接 accept
		try {
			Socket client = serverSocket.accept();
			System.out.println("一个客户端建立了连接........");
			// 获取请求协议
			InputStream is = client.getInputStream();
			byte[] datas = new byte[1024*100];
			int len = is.read(datas);
			String requestInfo = new String(datas,0,len);
			System.out.println(requestInfo);
			
			Response response = new Response(client);
			// 关注了内容
			response.print("<html>");
			response.print("<head>");
			response.print("<title>");
			response.print("服务器响应成功");
			response.print("</title>");
			response.print("</head>");
			response.print("<body>");
			response.print("shsxt server终于回来了。。。。");
			response.print("</body>");
			response.print("</html>");
			// 关注了状态码
			response.pushToBrowser(200);
			
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("客户端错误!");
		}
	}
	
	// 结束服务
	public void stop() {
		
	}
}
13.4.5 封装request
/**
 * 封装请求协议
 * 获取 method uri 以及请求参数
 * @author Administrator
 */
class Request {
	private InputStream is;
	private String requestInfo; // 协议信息
	private String method; // 请求方式
	private String url; // 请求url
	private String queryStr; // 请求参数
	private final  String crlf = "\r\n";
	public Request(InputStream is) {	
		byte[] datas = new byte[1024*1024];
		int len;
		try {
			len = is.read(datas);
			this.requestInfo = new String(datas,0,len);			
		} catch (IOException e) {
			e.printStackTrace();
			return ;
		}
		//分解字符串
		parseRequestInfo();
	}
	public Request(Socket client) throws IOException {
		this(client.getInputStream());
	}
	
	// 分解字符串
	private void parseRequestInfo() {
		System.out.println(requestInfo);
		System.out.println("------分解-------");
		// 1、请求方式
		int index_i = requestInfo.indexOf("/");
		this.method = requestInfo.substring(0, index_i).trim().toLowerCase();
		
		// 2、url
		int index_j = requestInfo.indexOf("HTTP");
		this.url = this.requestInfo.substring(index_i, index_j).trim();
		// 3、参数
		int index_m = url.indexOf("?");	
		if(index_m >= 0) { //存在请求参数
			String[] urlArray = this.url.split("\\?");
			this.url =urlArray[0];
			queryStr =urlArray[1];
		}
		System.out.println(this.url);
		
		if(method.equals("post")) {
			String qStr =this.requestInfo.substring(this.requestInfo.lastIndexOf(crlf)).trim();
			System.out.println(qStr);
			if(null==queryStr) {
				queryStr =qStr;
			}else { 
				queryStr +="&"+qStr;
			}
		}
		queryStr = null==queryStr?"":queryStr;
		System.out.println("请求方式:" + method);
		System.out.println("URL:" + url);
		System.out.println("参数:" + queryStr);
	}
}
13.4.6 封装参数
/**
 * 封装请求协议
 * 获取 method uri 以及请求参数
 * 封装请求参数为Map
 * @author Administrator
 */
class Request2 {
	private InputStream is;
	private String requestInfo; // 协议信息
	private String method; // 请求方式
	private String url; // 请求url
	private String queryStr; // 请求参数
	private final  String crlf = "\r\n";
	// 存储数据
	private Map<String,List<String>> parameterMap;
	
	public Request2(InputStream is) {	
		byte[] datas = new byte[1024*1024];
		int len;
		parameterMap = new HashMap<String, List<String>>();
		try {
			len = is.read(datas);
			this.requestInfo = new String(datas,0,len);			
		} catch (IOException e) {
			e.printStackTrace();
			return ;
		}
		//分解字符串
		parseRequestInfo();
	}
	public Request2(Socket client) throws IOException {
		this(client.getInputStream());
	}
	
	// 分解字符串
	private void parseRequestInfo() {
		System.out.println("------分解-------");
		// 1、请求方式
		int index_i = requestInfo.indexOf("/");
		this.method = requestInfo.substring(0, index_i).trim().toLowerCase();
		
		// 2、url
		int index_j = requestInfo.indexOf("HTTP");
		this.url = this.requestInfo.substring(index_i, index_j).trim();
		// 3、参数
		int index_m = url.indexOf("?");	
		if(index_m >= 0) { //存在请求参数
			String[] urlArray = this.url.split("\\?");
			this.url =urlArray[0];
			queryStr =urlArray[1];
		}
		System.out.println(this.url);
		
		if(method.equals("post")) {
			String qStr =this.requestInfo.substring(this.requestInfo.lastIndexOf(crlf)).trim();
			System.out.println(qStr);
			if(null==queryStr) {
				queryStr =qStr;
			}else { 
				queryStr +="&"+qStr;
			}
		}
		queryStr = null== queryStr ? "" : queryStr;
		System.out.println("请求方式:" + method);
		System.out.println("URL:" + url);
		System.out.println("参数:" + queryStr);
		
		// 转成Map
		convertMap();
	}
	
	// 处理请求参数为Map
	private void convertMap() {
		// 分割字符串&
		String[] keyValues = this.queryStr.split("&");
		
		// 遍历
		for(String query : keyValues) {
			// 再分割=
			String[] kv = query.split("=");
			kv = Arrays.copyOf(kv, 2); // 拷贝,使其保持两个长度
			
			// 获取key和value
			String key = kv[0];
			String value = kv[1] == null ? null : decode(kv[1],"UTF-8");
			
			// 存储到Map中
			if(!parameterMap.containsKey(key)) {
				parameterMap.put(key, new ArrayList<>());
			}
			parameterMap.get(key).add(value);
		}
	}
	
	/**
	 * 处理中文
	 * @return
	 */
	private String decode(String value,String enc) {
		try {
			return java.net.URLDecoder.decode(value, enc);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 通过name获取对应的多个值
	 * @param key
	 * @return
	 */
	public String[] getParameterValues(String key) {
		List<String> list = parameterMap.get(key);
		if(list == null || list.size() < 1) {
			return null;
		}
		return list.toArray(new String[0]); // 将容器转成数组
	}
	/**
	 * 通过name获取对应的一个值
	 * @param key
	 * @return
	 */
	public String getParameterValue(String key) {
		String[] values = getParameterValues(key);
		return values == null ? null : values[0];
	}
	public String getMethod() {
		return method;
	}
	public String getUrl() {
		return url;
	}
	public String getQueryStr() {
		return queryStr;
	}
	public Map<String, List<String>> getParameterMap() {
		return parameterMap;
	}
}

13.5. Server

13.5.1 Request
/**
 * 封装请求协议
 * 获取 method uri 以及请求参数
 * 封装请求参数为Map
 * @author Administrator
 */
class Request {
	private InputStream is;
	private String requestInfo; // 协议信息
	private String method; // 请求方式
	private String url; // 请求url
	private String queryStr; // 请求参数
	private final  String crlf = "\r\n";
	// 存储数据
	private Map<String,List<String>> parameterMap;
	
	public Request(InputStream is) {	
		byte[] datas = new byte[1024*1024];
		int len;
		parameterMap = new HashMap<String, List<String>>();
		try {
			len = is.read(datas);
			this.requestInfo = new String(datas,0,len);			
		} catch (IOException e) {
			e.printStackTrace();
			return ;
		}
		//分解字符串
		parseRequestInfo();
	}
	public Request(Socket client) throws IOException {
		this(client.getInputStream());
	}
	
	// 分解字符串
	private void parseRequestInfo() {
		System.out.println("------分解-------");
		// 1、请求方式
		int index_i = requestInfo.indexOf("/");
		this.method = requestInfo.substring(0, index_i).trim().toLowerCase();
		
		// 2、url
		int index_j = requestInfo.indexOf("HTTP");
		this.url = this.requestInfo.substring(index_i, index_j).trim();
		// 3、参数
		int index_m = url.indexOf("?");	
		if(index_m >= 0) { //存在请求参数
			String[] urlArray = this.url.split("\\?");
			this.url =urlArray[0];
			queryStr =urlArray[1];
		}
		System.out.println(this.url);
		
		if(method.equals("post")) {
			String qStr =this.requestInfo.substring(this.requestInfo.lastIndexOf(crlf)).trim();
			System.out.println(qStr);
			if(null==queryStr) {
				queryStr =qStr;
			}else { 
				queryStr +="&"+qStr;
			}
		}
		queryStr = null== queryStr ? "" : queryStr;
		System.out.println("请求方式:" + method);
		System.out.println("URL:" + url);
		System.out.println("参数:" + queryStr);
		
		// 转成Map
		convertMap();
	}
	
	// 处理请求参数为Map
	private void convertMap() {
		// 分割字符串&
		String[] keyValues = this.queryStr.split("&");
		
		// 遍历
		for(String query : keyValues) {
			// 再分割=
			String[] kv = query.split("=");
			kv = Arrays.copyOf(kv, 2); // 拷贝,使其保持两个长度
			
			// 获取key和value
			String key = kv[0];
			String value = kv[1] == null ? null : decode(kv[1],"gbk");
			
			// 存储到Map中
			if(!parameterMap.containsKey(key)) {
				parameterMap.put(key, new ArrayList<>());
			}
			parameterMap.get(key).add(value);
		}
	}
	
	/**
	 * 处理中文
	 * @return
	 */
	private String decode(String value,String enc) {
		try {
			return java.net.URLDecoder.decode(value, enc);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 通过name获取对应的多个值
	 * @param key
	 * @return
	 */
	public String[] getParameterValues(String key) {
		List<String> list = parameterMap.get(key);
		if(list == null || list.size() < 1) {
			return null;
		}
		return list.toArray(new String[0]); // 将容器转成数组
	}
	/**
	 * 通过name获取对应的一个值
	 * @param key
	 * @return
	 */
	public String getParameterValue(String key) {
		String[] values = getParameterValues(key);
		return values == null ? null : values[0];
	}
	public String getMethod() {
		return method;
	}
	public String getUrl() {
		return url;
	}
	public String getQueryStr() {
		return queryStr;
	}
	public Map<String, List<String>> getParameterMap() {
		return parameterMap;
	}
}
13.5.2 Response
/**
 * 1、响应信息
 * 2、动态加载内容
 * 3、发送响应请求
 * @author Administrator
 */
class Response {
	private BufferedWriter bw;
	// 正文
	private StringBuilder content;
	// 协议头(状态行与请求头 回车)信息
	private StringBuilder headInfo;
	private int len; //正文的字节数
	
	private final String BLANK =" ";
	private final  String CRLF = "\r\n";
	
	private Response() {
		this.content = new StringBuilder();
		this.headInfo = new StringBuilder();
		len = 0;
	}
	public Response(Socket client) {
		this();
		try {
			bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
			headInfo = null;
		}
	}
	public Response(OutputStream os) {
		this();
		bw =new BufferedWriter(new OutputStreamWriter(os));
	}
	
	//动态添加内容
	public Response print(String info) {
		content.append(info).append(CRLF);
		len += (info+CRLF).getBytes().length;
		return this;
	}
	public	Response println(String info) {
		content.append(info).append(CRLF);
		len+=(info+CRLF).getBytes().length;
		return this;
	}
	
	// 推送响应信息
	public void pushToBrowser(int code) throws IOException {
		if(null ==headInfo) {
			code = 505;
		}
		createHeadInfo(code);
		bw.append(headInfo);
		bw.append(content);
		bw.flush();
	}
	
	// 构造头信息
	private void createHeadInfo(int code) {
		// 1、响应行: HTTP/1.1 200 OK
		headInfo.append("HTTP/1.1").append(BLANK);
		headInfo.append(code).append(BLANK);
		switch (code) {
		case 200:
			headInfo.append("OK").append(CRLF);
			break;
		case 404:
			headInfo.append("NOT FOUND").append(CRLF);
			break;
		case 505:
			headInfo.append("SERVER ERROR").append(CRLF);
			break;
		}
		// 2、响应头(最后一行存在空行)
		headInfo.append("Date:").append(new Date()).append(CRLF);
		headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
		headInfo.append("Content-type:text/html").append(CRLF);
		headInfo.append("Content-length:").append(len).append(CRLF);
		headInfo.append(CRLF);
	}
}
13.5.3 Sevlet
/**
 * 服务器小脚本接口
 * @author Administrator
 */
public interface Servlet {
	void service(Request request,Response response);
}
13.5.4 业务层
/**
 * 业务层:登录
 * @author Administrator
 */
public class LoginServlet implements Servlet {
	@Override
	public void service(Request request,Response response) {
		response.print("登录成功!");
	}
}

/**
 * 业务层:注册
 * @author Administrator
 */
public class RegisterServlet implements Servlet {
	@Override
	public void service(Request request,Response response) {
		response.print("恭喜您,注册成功!");
	}
}
13.5.5 Server
/**
 * 服务器
 * 目标: 加入了Servlet解耦了业务代码
 * @author 
 */
public class Server04 {
	private ServerSocket serverSocket ;
	public static void main(String[] args) {
		Server04 server = new Server04();
		server.start();
	}

	// 启动服务
	public void start() {
		try {
			serverSocket = new ServerSocket(8888);
			receive();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("服务器启动失败....");
		}
	}

	// 接受连接处理
	public void receive() {
		try {
			Socket client = serverSocket.accept();
			System.out.println("一个客户端建立了连接....");

			// 获取请求协议
			Request request = new Request(client);

			// 获取响应协议
			Response response = new Response(client);
			
			// 实现servlet接口
			Servlet servlet = null;
			if(request.getUrl().equals("/login")) {
				servlet = new LoginServlet();
			}else if(request.getUrl().equals("/reg")) {
				servlet = new RegisterServlet();
			}else {
				servlet = new RegisterServlet();
			}
			servlet.service(request, response);
			
			// 关注了状态码
			response.pushToBrowser(200);
			
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("客户端错误");
		}
	}

	// 停止服务
	public void stop() {
	}
}
13.5.6 多线程
/**
 * 多线程处理请求与响应
 * @author Administrator
 */
class Dispatcher implements Runnable{
	private Socket client;
	private Response response;
	private Request request;
	public Dispatcher(Socket client){
		this.client = client;
		try {
			// 获取请求协议
			request = new Request(client);
			// 获取响应协议
			response = new Response(client);
		} catch (IOException e) {
			release();
		}
	}

	@Override
	public void run() {
		try {
			// 实现servlet接口
			Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
			if (servlet != null) {
				servlet.service(request, response);
				// 关注了状态码
				response.pushToBrowser(200);
			} else {
				// 错误
				response.pushToBrowser(404);
			}
		} catch (IOException e) {
			release();
		}
		release();
	}
	
	// 释放资源
	private void release() {
		try {
			client.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}
/**
 * 服务器
 * 目标: 多线程处理分发器
 * @author 
 */
public class Server06 {
	private ServerSocket serverSocket ;
	private boolean isRunning;
	public static void main(String[] args) {
		Server06 server = new Server06();
		server.start();
	}

	// 启动服务
	public void start() {
		try {
			serverSocket = new ServerSocket(8888);
			isRunning = true;
			receive();
		} catch (IOException e) {
			System.out.println("服务器启动失败....");
			stop();
		}
	}

	// 接受连接处理
	public void receive() {
		while (isRunning) {
			try {
				Socket client = serverSocket.accept();
				System.out.println("一个客户端建立了连接....");

				// 多线程处理
				new Thread(new Dispatcher(client)).start();
			} catch (IOException e) {
				System.out.println("客户端错误");
				stop();
			}
		}
	}

	// 停止服务
	public void stop() {
		isRunning = false;
		try {
			this.serverSocket.close();
			System.out.println("服务器已停止");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

13.6 手写服务器

13.6.1 Server
/**
 * 服务器
 * @author
 */
public class Server {
	private ServerSocket serverSocket;
	private boolean isRunning; 
	public static void main(String[] args) {
		Server server = new Server();
		server.start();
	}
	
	// 启动服务器
	public void start() {
		try {
			// 1.使用ServerSocket创建服务器 指定端口
			serverSocket = new ServerSocket(8888);
			isRunning = true;
			
			receive(); // 连接处理
		} catch (IOException e) {
			System.out.println("启动服务器失败");
			stop();
		}
	}
	
	// 接受连接处理
	public void receive(){
		while(isRunning) {
			Socket client = null;
			try {
				// 2. 阻塞式等待连接
				client = serverSocket.accept();
				System.out.println("一个客户端连接成功!");
				
				// 3.请求与响应
				// 交由多线程处理
				new Thread(new Dispatcher(client)).start();
			} catch (IOException e) {
				System.out.println("连接失败!");
				try {
					client.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
		
		// 关闭服务器
		stop();
	}
	
	// 关闭服务器
	public void stop() {
		isRunning = false;
		try {
			this.serverSocket.close();
			System.out.println("服务器已停止");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
13.6.2 Dispatcher
/**
 * 分配器:
 * @author Administrator
 */
public class Dispatcher implements Runnable{
	private Socket client;
	private Response response;
	private Request request;
	
	public Dispatcher(Socket client){
		this.client = client;
		try {
			// 获取请求协议
			request = new Request(client);
			
			// 获取响应协议
			response = new Response(client);
			
		} catch (IOException e) {
			release();
		}
	}
	@Override
	public void run() {
		try {
			// 实现servlet接口
			Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
			// 跳转至首页
			if (request.getUrl() == null || request.equals("")) {

				InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("index.html");
				byte[] flush = new byte[1024 * 1024];
				int len = -1;
				while ((len = is.read(flush)) != -1) {
					response.print(new String(flush, 0, len));
				}
				response.send(200);
				is.close();
				return;
			}
			
			if (servlet != null) { // 路径正确
				servlet.service(request, response);
				// 关注了状态码
				response.send(200);
			} else { // 路径错误
				InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("error.html");
				byte[] flush = new byte[1024 * 1024];
				int len = -1;
				while ((len = is.read(flush)) != -1) {
					response.print(new String(flush, 0, len));
				}
				is.close();
				response.send(200);
			}
		} catch (IOException e) {
			response.print("<h1>网站维护中。。。</h1>");
			response.send(505);
		}
		release();
	}
	
	// 释放资源
	private void release() {
		try {
			client.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}
13.6.3 Request
/**
 * 封装请求协议
 * 获取 method uri 以及请求参数
 * 封装请求参数为Map
 * @author Administrator
 */
public class Request {
	private InputStream is;
	private String requestInfo; // 协议信息
	private String method; // 请求方式
	private String url; // 请求url
	private String queryStr; // 请求参数
	private final  String crlf = "\r\n";
	
	// 存储数据
	private Map<String,List<String>> parameterMap;
	
	public Request(Socket client) throws IOException {
		this(client.getInputStream());
	}
	public Request(InputStream is) {
		this.is = is;
		int len = -1;
		byte[] flush = new byte[1024*1024];
		parameterMap = new HashMap<String, List<String>>();
		try {
			len = is.read(flush);
			this.requestInfo = new String(flush,0,len);			
		} catch (IOException e) {
			e.printStackTrace();
			return ;
		}
		
		//分解字符串
		parseRequestInfo();
	}
	
	// 分解字符串
	private void parseRequestInfo() {
		System.out.println("-------分解--------");
		// 1、请求方式
		int index_i = requestInfo.indexOf("/");
		this.method = requestInfo.substring(0, index_i).trim().toLowerCase();
		
		// 2、url
		int index_j = requestInfo.indexOf("HTTP");
		this.url = requestInfo.substring(index_i, index_j).trim();
		
		// 3、参数
		int index_m = url.indexOf("?");
		if(index_m >= 0) { //存在请求参数
			String[] urlArray = url.split("\\?");
			this.url = urlArray[0].trim();
			this.queryStr = urlArray[1].trim();
		}
		System.out.println(this.url);
		
		if(this.method.equals("post")) {
			int index_n = this.requestInfo.lastIndexOf(crlf);
			String qStr = this.requestInfo.substring(index_n).trim();
			System.out.println(qStr);
			if(this.queryStr == null) {
				this.queryStr = qStr;
			}else {
				this.queryStr += "&" + qStr;
			}
		}
		
		queryStr = null== queryStr ? "" : queryStr;
		url = url.trim().equals("/") ? null : url; // url为空时也有一个"/"
		
	}
	public String getUrl() {
		return url;
	}
}
13.6.4 Response
/**
 * 1、响应信息
 * 2、动态加载内容
 * 3、发送响应请求
 * @author Administrator
 */
public class Response {
	private BufferedWriter bw;
	private StringBuilder content; // 正文
	private StringBuilder headInfo; // 协议信息
	private int len; // 正文的字节数

	private final String BLANK = " ";
	private final String CRLF = "\r\n";
	
	private Response() {
		this.content = new StringBuilder();
		this.headInfo = new StringBuilder();
		len = 0;
	}
	public Response(Socket client) {
		this();
		try {
			bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
			headInfo = null;
		}
	}
	public Response(OutputStream os) {
		this();
		bw =new BufferedWriter(new OutputStreamWriter(os));
	}
	
	//动态添加内容
	public Response print(String info) {
		content.append(info).append(CRLF);
		len += (info + CRLF).getBytes().length;
		return this;
	}
	
	// 推送响应信息
	public void send(int code) {
		try {
			createHeadInfo(code); // 响应协议
			bw.append(headInfo); // 输出协议
			bw.append(content); // 输出内容
			bw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// 构造头信息
	private void createHeadInfo(int code) {
		// 1、响应行: HTTP/1.1 200 OK
		headInfo.append("HTTP/1.1").append(BLANK);
		headInfo.append(code).append(BLANK);
		switch (code) {
		case 200:
			headInfo.append("OK").append(CRLF);
			break;
		case 404:
			headInfo.append("NOT FOUND").append(CRLF);
			break;
		case 505:
			headInfo.append("SERVER ERROR").append(CRLF);
			break;
		}
		// 2、响应头(最后一行存在空行)
		headInfo.append("Date:").append(new Date()).append(CRLF);
		headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
		headInfo.append("Content-type:text/html").append(CRLF);
		headInfo.append("Content-length:").append(len).append(CRLF);
		headInfo.append(CRLF);
	}
}
13.6.5 xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<servlet>
		<servlet-name>login</servlet-name>
		<servlet-class>com.user.LoginServlet
		</servlet-class>
	</servlet>
	<servlet>
		<servlet-name>register</servlet-name>
		<servlet-class>com.user.RegisterServlet
		</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>login</servlet-name>
		<url-pattern>/login</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>register</servlet-name>
		<url-pattern>/register</url-pattern>
		<url-pattern>/reg</url-pattern>
	</servlet-mapping>
</web-app>
13.6.6 WebApp
/**
 * 解析xml
 * 1、获取解析工厂
 * 2、从解析工厂获取解析器
 * 3、加载Document注册处理器
 * 4、编写处理器
 * 5、解析
 * @author Administrator
 */
public class WebApp {
	private static WebContext webContext; // 用于处理数据
	
	//静态代码块 只加载一 
	static{
		try {
			// SAX解析
			// 1、获取解析工厂
			SAXParserFactory factory = SAXParserFactory.newInstance();

			// 2、从解析工厂获取解析器
			SAXParser parse = factory.newSAXParser();

			// 3、加载Document注册处理器

			// 4、编写处理器
			WebHandler handler = new WebHandler();

			// 5、解析
			parse.parse(Thread.currentThread()
					.getContextClassLoader()
					.getResourceAsStream("web.xml"), handler);

			// 6、获取数据
			webContext = new WebContext(handler.getEntitys(), handler.getMappings());
			
		} catch (Exception e) {
			System.out.println("xml解析错误!");
		}
	}
	
	/**
	 * 通过url获取配置文件的servlet
	 * @param url
	 * @return
	 */
	public static Servlet getServletFromUrl(String url) {
		// 假设你输入了 /login
		String className = webContext.getClass(url); // 返回类的路径
		
		if(className == null || className.equals("")) {
			return null;
		}
		
		// 反射
		Class clz;
		try {
			clz = Class.forName(className);
			// 实例化
			Servlet servlet = (Servlet) clz.getConstructor().newInstance();
			return servlet;
		} catch (Exception e) {
			System.out.println("反射错误!");
		}
		return null;
	}
}
13.6.7 WebHandler
/**
 * xml处理器
 * 解析文档开始->servlet解析开始->servlet-name解析开始->内容解析
 * ->servlet-name解析结束->servlet解析结束->解析文档结束
 * @author Administrator
 */
public class WebHandler extends DefaultHandler{
	private List<Entity> entitys; // 存储 servlet-name、servlet-class
	private List<Mapping> mappings; // 存储servlet-name、url-pattern
	private Entity entity;
	private Mapping mapping;
	private String tag; // 存储操作的标签
	private boolean isMapping = false; // 是否为servlet-mapping标签
	public List<Entity> getEntitys() {
		return entitys;
	}
	public List<Mapping> getMappings() {
		return mappings;
	}
	
	// 开始解析文档
	@Override
	public void startDocument() throws SAXException {
		entitys = new ArrayList<Entity>();
		mappings = new ArrayList<Mapping>();
	}
	
	// 开始解析servlet
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if(qName != null) { // 标签是否存在
			tag = qName; 
			// 根据不同标签 创建不同数据存储对象
			if(qName.equals("servlet")) {
				entity = new Entity();
				isMapping = false;
			}else if(qName.equals("servlet-mapping")) {
				mapping = new Mapping();
				isMapping = true;
			}
		}
	}
	
	// 内容解析
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		if(tag !=  null) {
			String content = new String(ch, start, length).trim(); // 获取内容
			if(content != null && !content.equals("")) { // 处理内容为空问题
				if(isMapping) { // 操作servlet-mapping
					if(tag.equals("servlet-name")) {
						mapping.setName(content);
					}else if(tag.equals("url-pattern")) {
						mapping.addPattern(content);
					}
				}else { // 操作servlet
					if(tag.equals("servlet-name")) {
						entity.setName(content);
					}else if(tag.equals("servlet-class")) {
						entity.setClz(content);
					}
				}
			}
		}
	}
	
	// // 结束解析servlet
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if(qName != null) { // 标签是否存在
			// 根据不同标签 将不同数据存储对象添加到容器中
			if(qName.equals("servlet")) {
				entitys.add(entity);
			}else if(qName.equals("servlet-mapping")) {
				if(mapping != null) {
					mappings.add(mapping);
				}
			}
		}
		tag = null; // tag 丢弃了
	}
	
	// 结束解析文档
	@Override
	public void endDocument() throws SAXException {
	}
}
13.6.8 WebContext
/**
 * 处理xml数据
 * 整合Mapping数据与Entity数据
 * @author Administrator
 */
public class WebContext {
	private List<Mapping> mappings;
	private List<Entity> entitys;
	
	// 用Map存储Mapping与Entity数据
	private Map<String, String> emtityMap;
	private Map<String, String> mappingMap;
	
	public WebContext(List<Entity> entitys, List<Mapping> mappings) {
		super();
		this.mappings = mappings;
		this.entitys = entitys;
		this.emtityMap = new HashMap<String, String>();
		this.mappingMap = new HashMap<String, String>();
		
		// 将Entity转化成Map
		for(Entity entity : entitys) {
			this.emtityMap.put(entity.getName(), entity.getClz());
		}
		// 将Mapping转化成Map
		// 为方便查找,将mapping的pattern值做为key,name做为value
		for(Mapping mapping : mappings) {
			for(String value : mapping.getPattern()) {
				mappingMap.put(value, mapping.getName());
			}
		}
	}
	
	/**
	 * 通过URL的路径找到对应的class
	 * @param pattern
	 * @return
	 */
	public String getClass(String pattern) {
		// pattern 为空或不存在
		if(pattern == null || pattern.equals("")) {
			return null;
		}
		String name = mappingMap.get(pattern);
		
		// pattern 对应的值不存在
		if(name == null) { 
			return "";
		}
		return emtityMap.get(name);
	}

	@Override
	public String toString() {
		return "WebContext [emtityMap=" + emtityMap + ", mappingMap=" + mappingMap + "]";
	}
}
13.6.9 Servlet
/**
 * 服务器小脚本接口
 * @author Administrator
 */
public interface Servlet {
	void service(Request request, Response response);
}
13.6.10 LoginServlet
public class LoginServlet implements Servlet {
	@Override
	public void service(Request request, Response response) {
		response.print("登录成功!");
	}
}

第十四章

14.1 反射

14.1.1 反射机制
指的是可以于运行时加载、探知、使用编译期间完全末知的类。
程序在运行状态中,可以动态加载一个只有名称的类 ,对于任意一个已加载的类,
都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个 方法和属性;
14.1.2 获取类对象
获取Class类对象:
	1、getClass()
	2、Class.forName() (最常用)
	3、.class
// 对象时表示封装一些数据,一个类会创建一个class
// 整个结构的信息会被放到对应的class对象中
// 这个class对象就像一面镜子一样,通过这镜子我可以看到对应类的全部信息
			
// 获取对象的方法
// 方法一
Class clz = String.class;
			
// 方法二
Class clz2 = Class.forName(path);
			
// 方法三
Class clz3 = path.getClass();
14.1.3 获取类信息
// 获取类的对象
Class clz = Class.forName(path);
			
// 获取类的名字
System.out.println(clz.getName()); // com.text.bean.User
System.out.println(clz.getSimpleName()); // User
			
// 获取属性信息
Field[] fields = clz.getFields(); // 只获取public属性
Field[] fields2 = clz.getDeclaredFields(); // 获取所有属性
Field f = clz.getDeclaredField("name"); // private java.lang.String com.text.bean.User.name
System.out.println(fields.length); // 0
System.out.println(fields2.length); // 3
for(Field temp: fields2) {
	System.out.println("属性:" + temp);
}
			
// 获取方法信息
Method[] methods = clz.getDeclaredMethods();
Method m = clz.getDeclaredMethod("getId", null); // 参数类型,防重载
System.out.println(m); // public int com.text.bean.User.getId()
for(Method temp: methods) {
	System.out.println("方法:" + temp);
}
			
// 获取构造器信息
Constructor[] Constructors = clz.getDeclaredConstructors(); //所有构造器
// 无参构造器 public com.text.bean.User()
Constructor c = clz.getDeclaredConstructor(null);
// 有参构造器 public com.text.bean.User(int,java.lang.String,int)
Constructor c2 = clz.getDeclaredConstructor(int.class, String.class, int.class);
System.out.println(c);
System.out.println(c2);
for(Constructor temp: Constructors) {
	System.out.println("构造器:" + temp);
}
14.1.4 动态操作
// 获取类的对象
Class<User> clz = (Class<User>)Class.forName(path);
			
// 通过反射API调用构造方法,构造对象
// 方法一
User u = clz.newInstance(); // 其实是调用了User的无参构造方法
System.out.println(u); // User [id=0, name=null, age=0]

// 方法二
Constructor<User> c = clz.getDeclaredConstructor(int.class,String.class,int.class);
User u2 = c.newInstance(101,"admin",19); // 传参
System.out.println(u2); // User [id=101, name=admin, age=19]
System.out.println(u2.getName()); // admin

// 通过反射API调用普通方法
User u3 = clz.newInstance();
Method method = clz.getDeclaredMethod("setName", String.class); // 获取方法
method.invoke(u3, "mysql"); // u3.setName("mysql");
System.out.println(u3.getName()); // mysql

// 通过反射API操作属性
User u4 = clz.newInstance();
Field f = clz.getDeclaredField("name"); // 获取属性
f.setAccessible(true); // 这个属性不需要做安全检查了,可以直接访问
f.set(u4, "python"); // 通过反射直接写属性
System.out.println(u4.getName()); // 私有属性在别的类中无法调用(直接调用)
System.out.println(f.get(u4)); // f.get(u4)<=>u4.getName() (通过反射调用方法)
14.1.5 反射机制性能
setAccessible
	启用和禁用访问安全检查的开关值为true则指示反射的对象在使用时应该取消Java语言访问检查。
	值为false则指示反射的对象应该实施Java语言访问检查。并不是为true就能访问为false就不能访问。
	禁止安全检查 。可以提高反射的运行速度。
可以考虑使用: cglib/javaassist字节码操作
14.1.5 反射操作泛型
Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去
强制类型转换的麻烦。但是, 一旦编译完成,所有的和泛型有关的类型全部擦除。
为了通过反射操作这些类型以迎合实际开发的需要. Java就新增了ParameterizedType.
GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class
类中的类型但是又和原始类型齐名的类型。

ParameterizedType:表示种参数化的类型,比如Collection<String>
GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable:是各种类型变量的公共父接口
WildcardType:代表-种通配符类型表达式,比如?, ? extends Number, ? super Integer
[wildcard是一个单词:就是“通配符”]
/**
 * 通过反射获取泛型信息
 * @author
 */
public class Demo04 {
	public void test01(Map<String,User> map,List<User> list){
		System.out.println("Demo04.test01()");
	}
	public Map<Integer,User> test02(){
		System.out.println("Demo04.test02()");
		return null;
	}
	public static void main(String[] args) {
		try {
			//获得指定方法参数泛型信息
			Method m = Demo04.class.getMethod("test01", Map.class,List.class);
			Type[] t = m.getGenericParameterTypes();
			for (Type paramType : t) {
				System.out.println("#"+paramType);
				if(paramType instanceof ParameterizedType){
					Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
					for (Type genericType : genericTypes) {
						System.out.println("泛型类型:"+genericType);
					}
				}
			}
			
			//获得指定方法返回值泛型信息
			Method m2 = Demo04.class.getMethod("test02", null);
			Type returnType = m2.getGenericReturnType();
			if(returnType instanceof ParameterizedType){
				Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
				for (Type genericType : genericTypes) {
					System.out.println("返回值,泛型类型:"+genericType);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
14.1.6 动态编译
动态编译的应用场景:
	可以做一个浏览器端编写java代码 ,上传服务器编译和运行的在线评测系统。
	服务器动态加载某些类文件进行编译
动态编译的两种做法:
	通过Runtime调用javac ,启动新的进程去操作
	Runtime run = Runtime.getRuntime0;
	rProcess process = run.exec("javac .cp. d:/myiava/ HelloWorld.java");
	通过JavaCompiler动态编译

通过JavaComplier动态编译

public static int compileFile(String sourceFile) {
    // 动态编译
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    int result = compiler.run(null, null, null, sourceFile);
    System.out.println(result == 0 ? "编译成功" : "编译失败");
    return result;
}
第一个参数:为java编译器提供参数
第二个参数:得到Java编译器的输出信息
第三个参数:接收编译器的错误信息
第四个参数:可变参数(是一个String数组)能传入一个或多个Java源文件
返回值: 0表示编译成功,非0表示编译失败
String path = "H:/Java/projects/test/HelloWrold.java";
// 动态编译
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
int result = complier.run(null, null, null, path);
System.out.println(result == 0 ? "编译成功" : "编译失败");

// 方法二
Runtime run = Runtime.getRuntime();
Process process = run.exec("javac .cp. H:/Java/projects/test	HelloWrold.java");
InputStream is = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
System.out.println(reader.readLine());

// 通过反射运行程序
URL[] urls = new URL[] {new URL("file:/"+"C:/myJava")};
URLClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("HelloWorld");
//调用加载类的main方法
Method m = c.getMethod("main",String[].class);
m.invoke(null, (Object)new String[]{});
//由于可变参数是JDK5.0之后才有。
//m.invoke(null, (Object)new String[]{});会编译成:m.invoke(null,"aa","bb"),就发生了参数个数不匹配的问题。
//因此,必须要加上(Object)转型,避免这个问题。
//public static void main(String[] args)
14.1.6 脚本引擎执行js代码
脚本弓|擎介绍:
	使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的。
	Java脚本API是连通Java平台和脚本语言的桥梁。
	可以把一些复杂异变的业务逻辑交给脚本语善处理,这又大大提高了开发效率。
获得脚本弓|擎对象:
	//获得脚本引擎对象
	ScriptEngineManager sem = new ScriptEngineManager(); 
	ScriptEngine engine = sem. getEngineByName("javascript )
获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最核心的接口。
注意是:接口。Java可以使用各种不同的实现,从而通用的调用js、groovy. python等脚本。
Js使用了: Rhino
Rhino是一种使用Java语言编写的JavaScript的开源实现,原先由Moilla开发。现在被集成进入JDK 6.0
通过脚本引擎的运行上下文在脚本和Java平台间交换数据。
通过Java应用程序调用脚本函数。
/**
 * Rhino
 * 测试脚本引擎执行javascript代码[
 * @author Administrator
 */
public class Demo01 {
	public static void main(String[] args) throws Exception {
		// 获取脚本引擎对象
		ScriptEngineManager sem = new ScriptEngineManager();
		ScriptEngine engine = sem.getEngineByName("javascript");
		
		// 定义变量 存储到引擎上下文中
		engine.put("msg", "you are a  good man!"); // msg = 'you are a  good man!'
		String str = "var user = {name:'张三',age:23,schools:['清华大学','北京大学']};";
		str += "print(user.name);" ;
		
		// 执行代码
		engine.eval(str); // 张三
		engine.eval("msg = '改变了';");
		System.out.println(engine.get("msg")); // 改变了
		System.out.println("-----------------------");
		
		// 定义函数
		engine.eval("function add(a,b){var sum = a + b ; return sum;}");
		// 取得调用接口
		Invocable jsInvake = (Invocable)engine;
		// 执行脚本中定义的方法
		Object result = jsInvake.invokeFunction("add", new Object[] {3,5});
		System.out.println(result); // 8.0
		
		// 导入其他Java包,使用其他包中的Java类
		String jsCode = "var list = java.util.Arrays.asList([\"清华大学\",\"北京大学\"]);";
		engine.eval(jsCode);
		
		List<String> list2 = (List<String>)engine.get("list");
		for (String temp : list2) {
			System.out.println(temp);
		}
		
		//执行一个js文件(我们将a.js至于项目的src下即可)
		URL url = Demo01.class.getClassLoader().getResource("a.js");
		FileReader fr = new FileReader(url.getPath());
		engine.eval(fr); // 执行文件:7
		fr.close();
	}
}

14.2 JAVA字节码

JAVA动态性的两种常见实现方式:
	字节码操作
	反射
运行时操作字节码可以让我们实现如下功能:
	动态生成新的类
	动态改变某个类的结构(添加/删除/修改新的属性/方法)
优势:
	比反射开销小,性能高。
	JAVAasist性能高于反射,低于ASM
14.2.1 字节码操作类库
BCEL:
	-Byte Code Engineering Library (BCEL) ,这是Apache Software Foundation的Jakarta项目的一
部分。BCEL是Java classworking广泛使用的一种框架,它可以让您深入JVM汇编语进行类操作的细节。BCEL与
Javassist 有不同的处理字节码方法,BCEL在实际的JVM指令屈次上进行操作(BCEL拥有丰富的JVM摘令级支持)
而Javassist所强调的是源代码级别的工作。
ASM:
	是一个开源的分析.编辑和创建Java字节码的关库。性能较ASM差。跟cglib差不多:但是使用简单。
很多开源框架都在使用它。很多开源框架都在使用它。
BCEL
	Byte Code Engineering Library (BCEL) ,这是Apache Software Foundation的Jakarta项目的一
部分。BCEL是Java classworking广泛使用的一种框架,它可以让您深入JVM汇编语进行类操作
的细节。BCEL与Javassist 有不同的处理字节码方法,BCEL在实际的JVM指令屈次上进行操作(BCEL
拥有丰富的JVM摘令级支持)而Javassist所强调的是源代码级别的工作。
ASM
	是一个轻量级java字节码操作框架,直接涉及到JVM底层的操作和指令
CGLIB(Code Generation Library)
	是一个强大的,高性能。高质量的Code生成类库。基于ASM实现。
Javassist
	是一个开源的分析.编辑和创建Java字节码的关库。性能较ASM差。跟cglib差不多:但是使用简单。
很多开源框架都在使用它。
javassist的最外层的API和JAVA的反射包中的API颇为类似。
它主要由CtClass, CtMethod, ,以及CtField几个类组成。用以执行和
JDK反射API中java.lang.Class, java.lang.reflect.Method,
java.lang.reflect.Method .Field相同的操作。
14.2.2 javassist创建类
// 测试使用javassist生成一个新的类
ClassPool pool = ClassPool.getDefault();
CtClass cc= pool.makeClass("com.test.bean.Emp"); // 创建类

// 创建属性
CtField f1 = CtField.make("private int empno;", cc);
CtField f2 = CtField.make("private String name;", cc);
cc.addField(f1);
cc.addField(f2);

// 创建方法
CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
CtMethod m2= CtMethod.make("public void setEmpno(int empno){this.empno=empno;}", cc);
cc.addMethod(m1);
cc.addMethod(m2);

// 添加构造器
CtConstructor constructor = new CtConstructor(
        new CtClass[] {CtClass.intType,pool.get("java.lang.String")}, cc);
// 构造器的方法体
constructor.setBody("{this.empno = empno;this.name = name;}");

// 将上面构造好的类写入到C:/myJava中
cc.writeFile("C:/myJava");
System.out.println("类创建成功!");
14.2.3 javassist详解

image-20200523172303630

14.2.4 javassist操作类
/**
 * 测试javassist的API
 * @author Administrator
 */
public class Demo02 {
	
	// 处理类的基本用法
	public static void test01() throws Exception {
		// 获取类
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get("com.test.Emp");
		
		// 转化为字节码
		byte[] bytes = cc.toBytecode();
		System.out.println(Arrays.toString(bytes));
		
		System.out.println(cc.getName()); // 类名 com.test.Emp
		System.out.println(cc.getSimpleName()); // Emp
		System.out.println(cc.getSuperclass()); // 获取父类
		System.out.println(cc.getInterfaces()); // 获取接口
	}
	
	// 处理方法的基本用法
	public static void test02() throws Exception {
		// 获取类
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get("com.test.Emp");
		
		// 定义一个新方法
		//CtMethod m = CtMethod.make("public int add(int a, int b){return a+b;}", cc);
		
		// 定义一个新方法
		CtMethod m2 = new CtMethod(CtClass.intType,"add",
				new CtClass[] {CtClass.intType,CtClass.intType}, cc);
		m2.setModifiers(Modifier.PUBLIC);
		m2.setBody("{System.out.println(\"通过javassist构造类\");return $1+$2;}");
		
		// 将方法加入到类中
		cc.addMethod(m2);
		
		// 通过反射调用新生的方法
		Class clz = cc.toClass();
		// 通过调用Emp的无参构造器,创建新对象
		Object obj = clz.newInstance();
		// 获取方法
		Method method = clz.getDeclaredMethod("add", int.class,int.class);
		Object result = method.invoke(obj, 200, 300);
		System.out.println(result);
	}
	
	// 修改已有方法的信息,修改内容
	public static void test03() throws Exception {
		// 获取类
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get("com.test.Emp");
		
		// 获取方法
		CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[] {CtClass.intType});
		// 向方法中添加内容
		// 前面添加
		cm.insertBefore("System.out.println($0);System.out.println(\"在前面添加内容\");");
		// 某行添加
		cm.insertAt(31, "System.out.println(\"在某行加内容\");");
		// 后面添加
		cm.insertAfter("System.out.println(\"在后面添加内容\");");
		
		// 通过反射调用新增的方法
		Class clz = cc.toClass();
		// 通过调用Emp的无参构造器,创建新对象
		Object obj = clz.newInstance();
		// 获取方法
		Method method = clz.getDeclaredMethod("sayHello", int.class);
		method.invoke(obj,1001);
	}
	
	// 属性的操作
	public static void test04() throws Exception {
		// 获取类
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get("com.test.Emp");
		
		// 创建新的属性
		//CtField f1 = CtField.make("private int empno", cc);
		
		// 加入新属性
		CtField f2 = new CtField(CtClass.intType, "age", cc);
		f2.setModifiers(Modifier.PRIVATE);
		cc.addField(f2);
		
		// 获取指定的属性
		//cc.getDeclaredField("name");
		
		// 为属性添加方法
		cc.addMethod(CtNewMethod.getter("getAge", f2));
		cc.addMethod(CtNewMethod.getter("setAge", f2));
		
		// 通过反射创建类
		Class clz = cc.toClass();
		Field[] fields2 = clz.getDeclaredFields(); // 获取所有属性
		for(Field temp: fields2) {
			System.out.println("属性:" + temp);
		}
	}
	
	// 构造器的操作
	public static void test05() throws Exception {
		// 获取类
		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get("com.test.Emp");
		
		// 获取所有构造器
		CtConstructor[] cs = cc.getConstructors();
		for(CtConstructor c : cs) {
			System.out.println(c.getLongName()); // com.test.Emp()
		}
	}
	
	// 获取注解
	public static void test06() throws Exception {
		// 获取类
		CtClass cc = ClassPool.getDefault().get("com.test.Emp");
		// 获取所有的注解
		Object[] all = cc.getAnnotations();
		// 将注解强制转存Author
		Author a = (Author) all[0];
		String name = a.name();
		int year = a.year();
		System.out.println("name: " + name + ", year: " + year);
	}
	
	public static void main(String[] args) throws Exception {
		//test01();
		//test02();
		//test03();
		//test04();
		//test05();
		test06();
	}
}
14.2.5 javassist的局限
	JDK5.0新语法不支持(包括泛型、枚举) ,不支持注解惨改,但可以通过底层的avassist类来解决,
具体参考: javassistbytecode .annotation|
	不支持数组的初始化,如StringD("1",*2"} ,除非贝有数组的容量为1
	不支持内部类和匿名类
	不支持continue和btreak表达式。
	对于继承关系,有些不支持。例如:
		class AD 
		class B extends A 0
		dass C extendsB0

14.3 JVM

链接将Java类的二进制代码合并到JVM的运行状态之中的过程
	验证:确保加载的类信息符合JVM规范。没有安全方面的问题。
	准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段 ,这些内存都将在方法区中进行分配
	解析:虚拟机常量池内的符号引用替换为直接引用的过程
初始化:
	初始化阶段是执行类构造器<clinit> ()方法的过程。类构造器< clinit> ()方法是由编译器自动收集
	类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
	当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
	虚拟机会保证一个类的<clinit>(方法在多线程环境中被正确加锁和同步。
	当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化。

image-20200523203759106

14.3.1 类加载过程
类的主动引用(一定会发生类的初始化)
    new一个类的对象
    调用类的静态成员(除了final常量)和静态方法
    使用java.lang.reflect包的方法对类进行反射调用
    当虚拟机启动,java Hello ,则一定会初始化Hello类。说白了就是先启动main方法所在的类
    当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
类的被动引|用(不会发生类的初始化) 
    当访问一个静态域时 ,只有真正声明这个域的类才会被初始化
    通过子类引用父类的静态变量。不会导致子关初始化
    通过数组定义类引用,不会触发此类的初始化
    引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

第十五章 GOF23

15.1 概念

创建型模式:
	单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
结构型模式:
	适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:
	模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、
	解释器模式、状态模式、策略模式、职责链模式、访问者模式。

15.2 单例模式

核心作用:
	保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景:
	1. Windows的Task Manager (任务管理器)就是很典型的单例模式
	2. windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护
	着仅有的一个实例。
	3. 项目中,读取配置文件的类,一般也只有一个对象。 没有必要每次使用配置文件数据,每次new一个对象去读取。
	4. 网站的计数器,一般也是采用单例模式实现 ,否则难以同步。
	5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一 处于打开状态,
	因为只能有一个实例去操作否则内容不好追加。
	6. 数据库连接池的设计一般也是采用单例模式 ,因为数据库连接是一种数据库资源。
	7. 操作系统的文件系统,也是大的单例模式实现的具体例子, 一个操作系统只能有一个文件系统。
	8. Application也是单例的典型应用( Servlet编程中会涉及到)
	9. 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
	10. 在servlet编程中,每个Servlet也是单例
	11. 在spring MVC框架/struts1框架中,控制器对象也是单例
单例模式的优点:
	1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、
	产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决;
	2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,
	负责所有数据表的映射处理
常见的五种单例模式实现方式:
	主要:
		1. 饿汉式(线程安全,调用效率高。但是,不能延时加载。)
		2. 懒汉式(线程安全,调用效率不高。但是,可以延时加载。)
	其他:
		1. 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
		2. 静态内部类式(线程安全,调用效率高。但是,可以延时加载)
		3. 枚举单例(线程安全,调用效率高,不能延时加载)
15.2.1 饿汉式
/**
 * 1、测试饿汉式单例模式 
 * 2、构造器私有化
 * 3、创建静态对象
 * 4、公开对象的调用方法
 * @author Administrator
 */
public class SingletonDemo01 {
	// 2、创建静态对象,类初始化时立即加载这个对象,天然的是线程安全的!
	private static SingletonDemo01 instance = new SingletonDemo01();
	
	// 1、构造器私有化
	private SingletonDemo01() {
	}
	
	// 3、方法没有同步,调用效率高
	public static SingletonDemo01 getInstance() {
		return instance;
	}
}
	饿汉式单例模式代码中, static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。
虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
	问题:如果只是加载本类,而不是要调用getInstance() ,甚至永远没有调用,则会造成资源浪费!
15.2.2 懒汉式
/**
 * 1、测试懒汉式单例模式 
 * 2、构造器私有化
 * 3、类初始化时不创建对象
 * 4、方法同步
 * @author Administrator
 */
public class SingletonDemo02 {
	// 2、类初始化时不初始化这个对象,真正用时在创建。
	private static SingletonDemo02 instance;
	
	// 1、构造器私有化
	private SingletonDemo02() {
	}
	
	// 3、方法同步,调用效率低
	public static synchronized SingletonDemo02 getInstance() {
		if(instance == null) {
			instance = new SingletonDemo02();
		}
		return instance;
	}
}
要点:
	延迟加载,懒加载,真正用的时候才加载!
问题:
	资源利用率高了。但是,每次调用getInstaNce()方法都要同步,并发效率低。
16.2.3 双重检测锁
/**
 * 1、双重检查锁实现单例模式
 * 2、构造器私有化
 * 3、类初始化时不创建对象
 * 4、在if内部实现双重检查锁
 * @author Administrator
 */
public class SingletonDemo03 {
	// 2、类初始化时不初始化这个对象,真正用时在创建。
	private static SingletonDemo03 instance = null;
	
	// 1、构造器私有化
	private SingletonDemo03() {
	}
	
	// 3、在if内部实现双重检查锁
	public static SingletonDemo03 getInstance() {
		if (instance == null) {
			SingletonDemo03 sc;
			synchronized (SingletonDemo03.class) {
				sc = instance;
				if (sc == null) {
					synchronized (SingletonDemo03.class) {
						if (sc == null) {
							sc = new SingletonDemo03();
						}
					}
					instance = sc;
				}
			}
		}
		return instance;
	}
}
原理:	
	这个模式将同步内容放到if内部,提高了执行的效率不必每次获取对象时都进行同步,
只有第一次才同步创建了以后就没必要了。
问题:
	由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
16.2.4 静态内部类
/**
 * 1、静态内部类实现单例模式
 * 2、构造器私有化
 * 3、创建静态内部类
 * 4、创建对象,static final 类型
 * 5、提供外部获取对象的方法
 * @author Administrator
 */
public class SingletonDemo04 {
	
	// 2、创建静态内部类
	private static class SingletonClassInstance {
		// 3、创建对象,static final 类型
		private static final SingletonDemo04 instance = new SingletonDemo04();
	}
	
	// 1、构造器私有化
	private SingletonDemo04() {
	}
	
	// 4、提供外部获取对象的方法
	public static SingletonDemo04 getInstance() {
		return SingletonClassInstance.instance;
	}
}
静态内部类实现方式(也是一种懒加载方式)
优点:线程安全,调用效率高,并且实现了延时加载!
要点:
	1. 外部类没有static属性,则不会像饿汉式那样立即加载对象。
	2. 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。instance是static final
	类型,保证了内存中只有这样一个实例存在 ,而且只能被赋值一次,从而保证了线程安全性.
	3. 兼备了并发高效调用和延迟加载的优势!
15.2.5 枚举
/**
 * 测试枚举式实现单例模式(没有延时加载)
 * @author Administrator
 */
public class Client {
	public static void main(String[] args) {
		SingletonDemo05 s1 = SingletonDemo05.INCTANCE;
		System.out.println(s1);
	}
}
优点:
	实现简单
	枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点:
	无延迟加载
15.2.6 破解单例模式
反射可以破解上面几种(枚举除外)实现方式! (可以在构造方法中手动抛出异常控制)
反序列化可以破解.上面几种实现方式!
可以通过定义readResolve0防止获得不同对象。
反序列化时,如果对象所在类定义了readResolve(). (实际是一 种回调) ,
定义返回哪个对象。
/**
 * 测试反射和反序列化破解单例模式
 * @author Administrator
 */
public class Client02 {
	public static void main(String[] args) throws Exception {
		SingletonDemo06 s1 = SingletonDemo06.getInstance();
		SingletonDemo06 s2 = SingletonDemo06.getInstance();
		
		System.out.println(s1);
		System.out.println(s2); // s1 == s2
		
		// 反射
//		Class<SingletonDemo06> clz = (Class<SingletonDemo06>) Class.forName("com.singleton.SingletonDemo06");
//		
//		// 获取构造器
//		Constructor<SingletonDemo06> c = clz.getDeclaredConstructor(null);
//		// 跳过权限检查
//		c.setAccessible(true);
//		
//		SingletonDemo06 s3 = c.newInstance();
//		System.out.println(s3); // s1 != s3
		
		// 通过反序列化的方式构造多个对象
		// 序列化
		FileOutputStream fos = new FileOutputStream("a.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(s1);
		oos.close();
		fos.close();
		 
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
		SingletonDemo06 s3 =  (SingletonDemo06) ois.readObject();
		System.out.println(s3);
	}
}
15.2.7 单例模式效率
CountDown Latch:
	同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
	countDown()当前线程调此方法,则计数减一(建议放在 finally里执行)
	await() ,调用此方法会 一直阻塞当前线程,直到计时器的值为0
/**
 * 测试多线程5种单例模式的效率
 * @author 
 */
public class Client03 {
	public static void main(String[] args) throws Exception {
		
		long start = System.currentTimeMillis();
		int threadNum = 10;
		// 计数器
		final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
		
		for(int i=0; i<threadNum; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int i = 0; i < 100000; i++) {
						Object o = SingletonDemo01.getInstance();
					}
					countDownLatch.countDown();
				}
			}).start();
		}
		// 主线程阻塞,直到计数器变为0,继续执行
		countDownLatch.await();
		long end = System.currentTimeMillis();
		System.out.println(end-start);
	}
}

15.3 工厂模式

工厂模式:
	实现了创建者和调用者的分离。
详细分类:
	简单工厂模式
	工厂方法模式
	抽象工厂模式
面向对象设计的基本原则:
	OCP(开闭原则, Open-Closed Principle) :一个软件的实体应当对扩展开放,对修改关闭。
	DIP(依赖倒转原则, Dependence Inversion Principle ) : 要针对接口编程,不要针对实现编程。
	LoD(迪米特法则,Law of Demeter ) :只与你直接的朋友通信.而避免和陌生人通信。

15.3.1

第十六章 JDBC

mysql-connector-java-5.1.25-bin.jar

16.1 jdbc概念

	JDBC(Java Database Connection)为java开发者使用数据库提供了统一的编程接口
它由一组java类和接口组成。是java程序与数据库系统通信的标准API。JDBC API使得开发人员
可以使用纯java的方式来连接数据库,并执行操作。
sun公司由于不知道各个主流商用数据库的程序代码,因此无法自己写代码连接各个数据库,因此,
供一套api,凡是数据库想与Java进行连接的,数据库厂商自sun公司决定,自己提供一套api 
凡是数据库想与Java进行连接的,数据库厂商自己必须实现JDBC这套接口。而数据库厂商的
JDBC实现,我们就叫他此数据库的数据库驱动。

16.2 jdbc 常用接口

Driver接口:
	Driver接口由数据库厂家提供,对于java开发者而言,只需要使用Driver接口就可以了。
	在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。不同的数据库有不同的装载方法
	驱动:就是各个数据库厂商实现的Sun公司提出的JDBC接口。即对Connection等接口的实现类的jar文件
	装载MySq|驱动:
		Class.forName("com.mysql.jdbc.Driver");
	装载Oracle驱动:
		Class.forName("oracle.jdbc.driver.OracleDriver");
DriverManager接口:
	DriverManager是JDBC的管理层 ,作用于用户和驱动程序之间。
	DriverManager跟踪可用的驱动程序,并在数据库和相应的驱动程序之间建立连接。
Connection接口:
	Connection与特定数据库的连接(会话) ,在连接上下文中执行SQL语句并返回结果。
	DriverManager的getConnection()方法建立在JDBC URL中定义的数据库Connection连接上
	连接MYSQL数据库:
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/jdbc","root","20200110");
	连接ORACLE数据库:
		Connection con =DriverManager.getConnection"idbcoracle:thin:
		@hostportdatabse","user""password");
Statement接口:
 用于执行静态SQL语句并返回它所生成结果的对象。
 三种Statement类:
 	Statement ::(存在sql注入风险)
 		由createStatement创建 ,用于发送简单的SQL语句。(不带参数的)
 	PreparedStatement:
 		继承自Statement接口 ,由prepareStatement创建,用于发送含有一个或多个输入参数的sq|
 		语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入。我们
 		一般都用PreparedStatement.
 	CallableStatement:
 		继承自PreparedStatement. 由方法prePareCall创建 ,用于调用存储过程。
 		一常用的Statement方法:
 		execute() :运行语句,返回是否有结果集。
 	常用的Statement方法:
 		execute() :运行语句,返回是否有结果集。
 		executeQuery() :运行select语句,返回ResultSet结果集。
 		executeUpdate():运行insert/update/delete操作,返回更新的行数。

16.3 连接数据库

16.3.1 建立连接
/**
 * 测试数据库连接
 * @author Administrator
 */
public class Demo01 {
	public static void main(String[] args) {
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			// 建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!
			// 这是Connection对象管理的一个要点!)
			// 真正开发中,为了提高效率,都会使用连接池来管理连接对象!
			Connection conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			System.out.println(conn);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
16.3.2 执行语句
/**
 * 1、测试连接数据库
 * 2、执行sql语句
 * 3、sql注入问题
 * @author Administrator
 */
public class Demo02 {
	public static void main(String[] args) {
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			Connection conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			// 3、获取连接对象
			Statement stmt = conn.createStatement();
			
			// 4、执行sql语句
			String sql = "insert into user(name,pwd,create_time) "
					+ "values ('王五','123456',now());";
			stmt.execute(sql);
			
			// 测试sql注入
//			String id = "6 or 1=1"; // 恶意sql代码
//			String sql = "delete from user where id=" + id;
//			stmt.execute(sql);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
16.3.3 PreparedStatement
/**
 * 1、测试连接数据库
 * 2、PreparedStatement 基本用法
 * 2、执行sql语句
 * @author Administrator
 */
public class Demo03 {
	public static void main(String[] args) {
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			Connection conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			// 3、准备sql语句 可用占位符
			String sql = "insert into user (name,pwd,create_time)
                values (?,?,now())";
			
			// 4、获取连接对象
			PreparedStatement ps = conn.prepareStatement(sql);
			
			// 5、传入参数
//			ps.setString(1, "赵六"); // 索引从1开始
//			ps.setString(2, "123456");
			
			// 使用setObject方法处理参数,避免判断类型
			ps.setObject(1, "赵六");
			ps.setObject(2, "123456");
			
			// 6、执行语句
			ps.execute();
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
16.3.4 ResultSet
/**
 * 1、测试连接数据库
 * 2、PreparedStatement 基本用法
 * 3、执行sql语句
 * 4、ResultSet 返回结果
 * @author Administrator
 */
public class Demo04 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			// 3、准备sql语句 可用占位符
			String sql = "select * from user where id<?";
			
			// 4、获取连接对象
			ps = conn.prepareStatement(sql);
			
			// 5、传入参数
			// 使用setObject方法处理参数,避免判断类型
			ps.setObject(1, 6);
			
			// 6、执行语句,返回结果集
			rs = ps.executeQuery();
			
			// 获取查询值
			while(rs.next()) {
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String pwd = rs.getString("pwd");
				Date date = rs.getDate("create_time");
				System.out.println("id="+id+"\tname="+name+
                                   "\tpwd="+pwd+"\tdate="+date);
			}
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			// 关闭连接
			try {
				if(rs != null) {
					rs.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(ps != null) {
					ps.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(conn != null) {
					conn.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

16.4 jdbc详细操作

灵活指定SQL语句中的变量:PreparedStatement
对存储过程进行调用:CallableStatement
运用事务处理:Transaction
批处理:
	Batch
	对于大量的批处理.建议使用Statement ,因为PreparedStatement的预编译空间有限,
	当数据量特别大时,会发生异常。
16.4.1 批处理
/**
 * 1、测试批处理的基本用法
 * @author Administrator
 */
public class Demo05 {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			// 3、设置手动提交事务
			conn.setAutoCommit(false); 
			
			// 开始时间
			long start = System.currentTimeMillis();
			
			// 4、获取连接对象
			stmt = conn.createStatement();
			
			// 5、插入20000条数据
			for(int i=0; i<20000; i++) {
				stmt.addBatch("insert into user (name,pwd,create_time) "
						+ "values ('list_"+i+"','666666',now())");
			}
			
			// 6、执行语句
			stmt.executeBatch();
			
			// 7、提交事务
			conn.commit();
			
			// 结束时间
			long end = System.currentTimeMillis();
			System.out.println("插入20000条数据耗时:" + (end-start) + "毫秒");
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			// 关闭连接
			try {
				if(rs != null) {
					rs.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(stmt != null) {
					stmt.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(conn != null) {
					conn.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}
16.4.2 事务
事务基本概念:
	要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。
	事务开始于:
		连接到数据库上,并执行一条DML语句insert、update或delete
		前一个事务结束后,又输入了另外一条DML语句
	事务结束于:
		执行COMMIT或ROLLBACK语句
		执行条DDL语句,例如CREATE TABLE语句;在这种情况下,会自动执行COMMIT语句。
		执行条DCL语句,例如GRANT语句;在这种情况下,会自动执行COMMIT语句。
		断开与数据库的连接。
		执行了一条DML语句,该语句却失败了;在这种情况中,会为这个无效的
		DML语句执行ROLLBACK语句。
事务的四大特点(ACID):
	atomicity (原子性)
		表示一个事务内的所有操作是一个整体,要么全部成功,要么全失败;
	consistency (一致性)
		表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;
	isolation (隔离性)
		事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,
		要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
	durability (持久性)
		持久性事务完成之后,它对于系统的影响是永久性的。
事务隔离级别从低到高:
	读取未提交( Read Uncommitted)
	读取已提交(Read Committed)
	可重复读( Repeatable Read)
	序列化( serializable)
/**
 * 1、测试事务
 * @author Administrator
 */
public class Demo06 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps1 = null;
		PreparedStatement ps2 = null;
		ResultSet rs = null;
		try {
			// 1、加载驱动类
			Class.forName("com.mysql.jdbc.Driver");
			
			// 2、建立连接
			conn = DriverManager.getConnection(
					"jdbc:mysql://localhost:3306/jdbc","root","20200110");
			
			// 3、设置jdbc手动提交事务(默认自动提交)
			conn.setAutoCommit(false);
			
			// 4、准备sql语句 可用占位符
			String sql1 = "insert into user (name,pwd,create_time) values (?,?,now())";
			// 5、获取连接对象
			ps1 = conn.prepareStatement(sql1);
			// 6、传入参数
			ps1.setObject(1, "王五");
			ps1.setObject(2, "123456");
			// 7、执行语句
			ps1.execute();
			
			System.out.println("插入了一个用户!");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			// 4、准备sql语句 可用占位符
			String sql2 = "insert into user (name,pwd) values (?,?,now())";
			// 5、获取连接对象
			ps2 = conn.prepareStatement(sql2);
			// 6、传入参数
			ps2.setObject(1, "赵六");
			ps2.setObject(2, "123456");
			// 7、执行语句
			ps2.execute();
			
			// 提交事务
			conn.commit();
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			try {
				conn.rollback(); // 回滚
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			// 关闭连接
			try {
				if(rs != null) {
					rs.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(ps2 != null) {
					ps2.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(ps1 != null) {
					ps1.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if(conn != null) {
					conn.close();	
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

16.5 时间类型

java.util.Date
	子类: java.sql.Date	表示年月日
	子类: java.sql.Time	表示时分秒
	子类: java.sql.Timestamp	表示年月日时分秒
日期比较处理:
	插入随机日期
	取出指定日期范围的记录
Date date = new Date(System.currentTimeMillis());
Timestamp last = new Timestamp(System.currentTimeMillis());

// 获取某段时间内的数据
String sql = "select * from job where last_time > ? and last_time < ?";

16.6 CLOB

CLOB (Character Large Object)
	用于存储大量的文本数据
	大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的方式来处理的。
	而非一般的字段,一次即可读出数据。
Mysql中相关类型:
	TINYTEXT最大长度为255(2^[8]-1)字符的TEXT列。
	TEXT[(M)]最大长度为65535(2^[16]-1)字符的TEXT列。
	MEDIUMTEXT最大长度为16777215(2^[24]-1)字符的TEXT列。
	LONGTEXT最大长度为4294967295或4GB(2^[32]-1)字符的TEXT列。
// 传入一个流
// 通过流的方式向数据库中传数据 以txt类型存储
ps.setClob(2, new FileReader(new File("a.txt")));
Clob c = rs.getClob("myinfo");
Reader r = c.getCharacterStream(); // 获取流
int temp = 0;
while((temp = r.read()) != -1) {
    System.out.print((char)temp);
}
// 存储图片
// 数据库中以blob类型存储
ps.setBlob(1,new FileInputStream("H:/Web/js_projects/image/1.jpg"));
os = new FileOutputStream("002.jpg", true);
			
while(rs.next()) {
Blob b = rs.getBlob("status");
InputStream r = b.getBinaryStream();

int temp = 0;
byte[] flus = new byte[1024*10];
while((temp = r.read(flus)) != -1) {
    os.write(flus,0,temp);
	}
}

16.7 JDBC封装

/**
 * 数据库工具类
 * @author Administrator
 */
public class JDBCUtil {
	// 可以帮助读取和处理资源文件中的信息
	static Properties pros = null;
	
	static { // 加载JDBCUtil类的时候调用
		pros = new Properties();
		try {
			pros.load(Thread.currentThread().getContextClassLoader()
                      .getResourceAsStream("db.properties"));
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public static Connection getMysqlConn() {
		try {
			// 1、加载驱动类
			Class.forName(pros.getProperty("mysqlDriver"));
			// 2、建立连接
			return DriverManager.getConnection(
					pros.getProperty("mysqlURL"),
					pros.getProperty("mysqlUser"),
					pros.getProperty("mysqlPwd"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	public static void close(Connection conn) {
		// 关闭连接
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(ResultSet rs,Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
/**
 * 1、封装JDBC
 * @author Administrator
 */
public class Demo11 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtil.getMysqlConn();
			
			String sql = "select * from user";
			ps = conn.prepareStatement(sql);
			
//			ps.setString(1, "c++");
//			ps.setString(2, "123456");
//			ps.executeUpdate();
			
			rs = ps.executeQuery();
			while(rs.next()) {
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String pwd = rs.getString("pwd");
				Date date = rs.getDate("create_time");
				
				System.out.println("id=" + id + "\tname=" + 
				name + "\tpwd=" + pwd + "\tdate=" + date);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.close(rs, ps, conn);
		}
	}
}
// jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
user=root
password=20200110

16.8 ORM

ORM基本思想
	ORM(Object Rlelationship Mapping)的基本思想表结构跟类对应;
	表中字段和类的属性对应;表中记录和对象对应;
	让javabean的属性名和类型尽量和数据库保持一致!
	一条记录对应一个对象。 将这些查询到的对象放到容器中(List,Set,Map)
将表中的一条记录封装到Object数组中
将表中的一条记录封装到map中
将表中一条记录封装 到javabean对象中
16.8.1 Object数组
/**
 * 测试使用Object[]来封装一条记录
 * @author Administrator
 */
public class Demo01 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		Object[] objs = null;
		List<Object[]> list = new ArrayList<Object[]>();
		try {
			// 1、建立连接
			conn = JDBCUtil.getMysqlConn();
			
			// 2、获取连接对象
			String sql = "select * from emp";
			ps = conn.prepareStatement(sql);
			
			// 3、执行SQL语句
			rs = ps.executeQuery();
			
			// 4、获取结果
			while(rs.next()) {
				objs = new Object[6];
				for(int i=0; i<objs.length; i++) {
					objs[i] = rs.getObject(i+1);
				}
				list.add(objs);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 5、关闭连接
			JDBCUtil.close(rs, ps, conn);
		}
		for(Object[] obj : list ) {
			System.out.println(Arrays.toString(obj));
		}
	}
}
16.8.2 Map容器
/**
 * 测试使用Map、List来封装一条记录
 * @author Administrator
 */
public class Demo02 {
	public static void main(String[] args) {
//		test01();
		test02();
	}
	
	// 利用Map嵌套 存储数据
	public static void test02() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		Map<String, Object> map = null;
		Map<Integer ,Map> maps = new HashMap<>();
		try {
			// 1、建立连接
			conn = JDBCUtil.getMysqlConn();

			// 2、获取连接对象
			String sql = "select * from emp";
			ps = conn.prepareStatement(sql);

			// 3、执行SQL语句
			rs = ps.executeQuery();

			// 4、获取结果
			while (rs.next()) {
				map = new HashMap<String, Object>();
				map.put("id", rs.getObject(1));
				map.put("dept_id", rs.getObject(2));
				map.put("name", rs.getObject(3));
				map.put("age", rs.getObject(4));
				map.put("salary", rs.getObject(5));
				map.put("birthday", rs.getObject(6));

				maps.put((int)map.get("id"), map);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 5、关闭连接
			JDBCUtil.close(rs, ps, conn);
		}
		for (int i=0; i<maps.size();i++) {
			map = maps.get(i+1);
			System.out.println("id=" + map.get("id") + "\tdept_id=" + 
					map.get("dept_id") + "\tname=" + map.get("name") + "\tage=" + 
					map.get("age") + "\tsalary=" + map.get("salary") + 
					"\tbirthday=" + map.get("birthday"));
		}
	}
	
	// 利用Map、List 存储数据
	public static void test01() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		Map<String,Object> map = null;
		List<Map> list = new ArrayList<>();
		try {
			// 1、建立连接
			conn = JDBCUtil.getMysqlConn();
			
			// 2、获取连接对象
			String sql = "select * from emp";
			ps = conn.prepareStatement(sql);
			
			// 3、执行SQL语句
			rs = ps.executeQuery();
			
			// 4、获取结果
			while(rs.next()) {
				map = new HashMap<String,Object>();
				map.put("id", rs.getObject(1));
				map.put("dept_id", rs.getObject(2));
				map.put("name", rs.getObject(3));
				map.put("age", rs.getObject(4));
				map.put("salary", rs.getObject(5));
				map.put("birthday", rs.getObject(6));
				
				list.add(map);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 5、关闭连接
			JDBCUtil.close(rs, ps, conn);
		}
		for(Map m : list) {
			System.out.println("id=" + m.get("id") + "\tdept_id=" + 
		m.get("dept_id") + "\tname=" + m.get("name") + "\tage=" + 
					m.get("age") + "\tsalary=" + m.get("salary") +
					 "\tbirthday=" + m.get("birthday"));
		}
	}
}
16.8.3 Javabean
/**
 * 测试使用javabean来封装一条记录
 * List<javabean>封装多条记录
 * @author Administrator
 */
public class Demo03 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		Emp emp = null;
		List<Emp> list = new ArrayList<>();
		try {
			// 1、建立连接
			conn = JDBCUtil.getMysqlConn();
			
			// 2、获取连接对象
			String sql = "select * from emp";
			ps = conn.prepareStatement(sql);
			
			// 3、执行SQL语句
			rs = ps.executeQuery();
			
			// 4、获取结果
			while(rs.next()) {
				emp = new Emp();
				emp.setId(rs.getInt("id"));
				emp.setDept_id(rs.getInt("dept_id"));
				emp.setName(rs.getString("name"));
				emp.setId(rs.getInt("age"));
				emp.setSalary(rs.getDouble("salary"));
				emp.setBirthday(rs.getDate("birthday"));
				
				list.add(emp);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 5、关闭连接
			JDBCUtil.close(rs, ps, conn);
		}
		for(Emp e : list) {
			System.out.println("id=" + e.getId() + "\tdept_id=" + 
		e.getDept_id() + "\tname=" + e.getName() + "\tage=" + 
					e.getAge() + "\tsalary=" + e.getSalary() +
					 "\tbirthday=" + e.getBirthday());
		}
	}
}

第十七章 数据库连接池

17.1 连接池

commons-dbcp-1.4.jar
commons-pool-1.5.6.jar
/**
 * 数据库连接池
 * 1.向池子里放10个连接
 * 2.来个程序获取一个连接
 * 3.用完归还连接
 * 4.扩容
 */
public class MyDataSource implements DataSource {
	
	List<Connection> list = new ArrayList<Connection>();
	
	public MyDataSource() {
		for(int i=0; i<10; i++) {
			Connection conn = JDBCUtil.getConn();
			list.add(conn);
		}
	}
	
	// 该连接池对外公布的获取连接的方法
	@Override
	public Connection getConnection() throws SQLException {
		// 来拿连接时,先判断连接池是否有连接
		if(list.size() == 0) {
			for(int i=0; i<5; i++) {
				Connection conn = JDBCUtil.getConn();
				list.add(conn);
			}
		}
		Connection conn = list.remove(0);
		return conn;
	}
	
	// 用完之后,记得要还
	public void addBack(Connection conn) {
		list.add(conn);
	}

	@Override
	public PrintWriter getLogWriter() throws SQLException {
		return null;
	}

	@Override
	public int getLoginTimeout() throws SQLException {
		return 0;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return null;
	}

	@Override
	public void setLogWriter(PrintWriter arg0) throws SQLException {
		
	}

	@Override
	public void setLoginTimeout(int arg0) throws SQLException {
		
	}

	@Override
	public boolean isWrapperFor(Class<?> arg0) throws SQLException {
		return false;
	}

	@Override
	public <T> T unwrap(Class<T> arg0) throws SQLException {
		return null;
	}

	@Override
	public Connection getConnection(String arg0, String arg1) throws SQLException {
		return null;
	}

}
MyDataSource ds = new MyDataSource();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    conn = ds.getConnection();

    String sql = "select * from user";
    ps = conn.prepareStatement(sql);

    rs = ps.executeQuery();

    while(rs.next()) {
        System.out.println(rs.getString("name"));
    }

} catch (SQLException e) {
    e.printStackTrace();
} finally {
    try {
        if(ps != null) {
            ps.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    ds.addBack(conn);
}

17.2 重写close方法

public class ConnectionWrap implements Connection{
	private Connection conn = null;
	private List<Connection> list = null;
	
	public ConnectionWrap(Connection conn, List<Connection> list) {
		super();
		this.conn = conn;
		this.list = list;
	}

	@Override
	public void close() throws SQLException {
		list.add(conn);
	}
	
	@Override
	public PreparedStatement prepareStatement(String arg0) throws SQLException {
		return conn.prepareStatement(arg0);
	}
// 该连接池对外公布的获取连接的方法
@Override
public Connection getConnection() throws SQLException {
// 来拿连接时,先判断连接池是否有连接
if(list.size() == 0) {
    for(int i=0; i<5; i++) {
        Connection conn = JDBCUtil.getConn();
        list.add(conn);
    }
}
Connection conn = list.remove(0);

// 装饰一下
Connection connw = new ConnectionWrap(conn, list);

return connw;
}

17.3 DBCP

public static void test01() {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        // 1.构建数据源对象
        BasicDataSource dataSource = new BasicDataSource();

        // 设置数据库信息
        //jdbc:mysql://localhost/bank 主协议:子协议 ://本地/数据库
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost/user");
        dataSource.setUsername("root");
        dataSource.setPassword("20200110");

        // 2.获取连接对象
        conn = dataSource.getConnection();

        // 3.数据库操作
        String sql = "insert into user (name,pwd,create_time) values(?, ?, now())";
        ps = conn.prepareStatement(sql);
        ps.setObject(1, "test01");
        ps.setObject(2, "123");

        // 保存
        ps.executeUpdate();

    } catch (SQLException e) {
    } finally {
        try {
            if(ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        JDBCUtil.close(conn);
    }
}

配置文件

public static void test02() {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        // 1.构建数据源对象
        BasicDataSourceFactory factory = new BasicDataSourceFactory();
        Properties properties = new Properties();
        InputStream is = new FileInputStream("src//dbcpconfig.properties");
        properties.load(is);
        DataSource dataSource = factory.createDataSource(properties);

        // 2.获取连接对象
        conn = dataSource.getConnection();

        // 3.数据库操作
        String sql = "insert into user (name,pwd,create_time) values(?, ?, now())";
        ps = conn.prepareStatement(sql);
        ps.setObject(1, "test01");
        ps.setObject(2, "123");

        // 保存
        ps.executeUpdate();

    } catch (Exception e) {
    } finally {
        try {
            if(ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        JDBCUtil.close(conn);
    }
}

dbcpconfig.properties

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
username=root
password=20200110

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

17.4 C3P0

c3p0-0.9.1.2.jar
public void test01(){
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        // 1. 创建datasource
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // 2. 加载驱动
        dataSource.setDriverClass("com.mysql.jdbc.Driver");

        // 3. 设置数据库信息
        dataSource.setJdbcUrl("jdbc:mysql://localhost/jdbc");
        dataSource.setUser("root");
        dataSource.setPassword("20200110");

        // 4. 得到连接对象
        conn = dataSource.getConnection();

        String sql = "insert into account values(null , ? , ?)";
        ps = conn.prepareStatement(sql);
        ps.setString(1, "admi234n");
        ps.setInt(2, 103200);

        // 5, 执行语句
        ps.executeUpdate();

    } catch (Exception e) {
    } finally {
        try {
            if(ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        JDBCUtil.close(conn);
    }
}
public void test02(){
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        // 1. 创建datasource
        // 默认加载xml文件
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // 4. 得到连接对象
        conn = dataSource.getConnection();

        String sql = "insert into account values(null , ? , ?)";
        ps = conn.prepareStatement(sql);
        ps.setString(1, "admi234n");
        ps.setInt(2, 103200);

        // 5, 执行语句
        ps.executeUpdate();

    } catch (Exception e) {
    } finally {
        try {
            if(ps != null) {
                ps.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        JDBCUtil.close(conn);
    }
}

配置文件

// 文件名必须为:c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
    <property name="user">root</property>
    <property name="password">20200110</property>
    
    <property name="initialPoolSize">10</property>
    <property name="maxIdleTime">30</property>
    <property name="maxPoolSize">100</property>
    <property name="minPoolSize">10</property>
    <property name="maxStatements">200</property>
  </default-config>
</c3p0-config>

17.5 DBUtils

引入 commons-dbutils-1.4.jar
17.5.1 工具类
public class DBUtils {
	private static ComboPooledDataSource dataSource = null;
	
	static {
		dataSource = new ComboPooledDataSource();
	}
	
	/**
	 * 与数据库建立连接
	 * @return
	 */
	public static Connection getConn() {
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static void close(ResultSet rs,Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(Connection conn) {
		// 关闭连接
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
17.5.2 连接数据库
public class DBUtilsDemo01 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 建立连接
			conn = DBUtils.getConn();

			// 准备sql
			String sql = "select * from user";
			
			// 获取连接对象
			ps = conn.prepareStatement(sql);
			
			// 执行sql语句,返回结果集
			rs = ps.executeQuery();
			
			while(rs.next()) {
				System.out.println(rs.getString("name"));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			DBUtils.close(rs, ps, conn);
		}
	}
}
17.5.3 增删该查
// 增加
public static void test01() {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "insert into user (name,pwd,create_time) values (?,?,now())";
        // 执行sql语句
        queryRunner.update(sql, "java", "123");

    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 修改
public static void test02(String name, String pwd, int id) {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "update user set name=?,pwd=? where status = 1 and id = ?";
        // 执行sql语句
        queryRunner.update(sql,name,pwd,id);

    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 删除
public static void test03(int id) {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "update user set status=0 where status = 1 and id = ?";
        // 执行sql语句
        queryRunner.update(sql, id);

    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 查询
public static void test04() {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "select * from user where status=1 and id=?";
        // 执行sql语句
        String name = queryRunner.query(sql,new ResultSetHandler<String>(){
            public String handle(ResultSet rs) throws SQLException {
                while(rs.next()) {
                    String name = rs.getString("name");
                    return name;
                }
                return null;
            }
        }, 1);

        System.out.println(name);

    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 查询单条数据
public static void test04() {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "select * from user where status=1 and id=?";

        // 当查询数据只有一条时用BeanHandler
        // User中的属性必须与数据库中的字段一样
        User user = queryRunner.query(sql,new BeanHandler<User>(User.class),1);
        System.out.println(user);

    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 查询多条数据
public static void test04() {
    try {
        // 获取数据库连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

        // dbutils 只是帮我们简化了CRUD 的代码
        // 但是连接的创建以及获取工作。 不在他的考虑范围
        QueryRunner queryRunner = new QueryRunner(dataSource);

        // 准备sql
        String sql = "select * from user where status=1";

        // 查询多条数据
        List<User> users = queryRunner.query(sql,
        new BeanListHandler<User>(User.class));

        for(User u:users) {
            System.out.println(u);
        }

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

第十八章 MVC

18.1 元数据

Meata data 
描述数据的数据 String sql , 描述这份sql字符串的数据叫做元数据
数据库元数据  DatabaseMetaData
参数元数据  ParameterMetaData
结果集元数据  ResultSetMetaData
18.1.1 JDBCUtil
/**
 * 数据库连接工具类
 */
public class JDBCUtil {
	private static ComboPooledDataSource dataSource = null;
	
	static {
		// 创建连接池
		dataSource = new ComboPooledDataSource();
	}
	
	// 获取数据库连接
	public static Connection getConn() {
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	// 关闭连接
	public static void close(ResultSet rs,Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(Statement ps,Connection conn) {
		// 关闭连接
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(Connection conn) {
		// 关闭连接
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void close(ResultSet rs) {
		// 关闭连接
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
18.1.2 增改删
/**
 * 通用的增删该功能
 * @param sql 传入的sql语句
 * @param args 可变参数,有几个传几个
 */
public static void update(String sql, Object ... args) {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        // 获取连接
        conn = JDBCUtil.getConn();

        // 预处理
        ps = conn.prepareStatement(sql);

        // 元数据
        // 以问号个数为标准
        ParameterMetaData metaData = ps.getParameterMetaData();
        int count = metaData.getParameterCount();
        for(int i=0; i<count; i++) {
            ps.setObject(i+1, args[i]);
        }

        // 添加参数
        // 以传入参数个数为标准
//			for(int i=0; i<args.length; i++) {
//				ps.setObject(i+1, args[i]);
//			}

        // 执行操作
        ps.executeUpdate();

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtil.close(ps, conn);
    }
}
18.1.3 查
public static ResultSet query(String sql, Object ... args) {
    try {
        // 获取连接
        conn = JDBCUtil.getConn();

        // 预处理
        ps = conn.prepareStatement(sql);

        // 元数据
        // 以问号个数为标准
        ParameterMetaData metaData = ps.getParameterMetaData();
        int count = metaData.getParameterCount();
        for(int i=0; i<count; i++) {
            ps.setObject(i+1, args[i]);
        }

        // 执行操作
        rs = ps.executeQuery();

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
//			JDBCUtil.close(ps, conn);
    }
    return rs;
}
private static Connection conn = null;
	private static PreparedStatement ps = null;
	private static ResultSet rs = null;
	public static void main(String[] args) {
		// 增
//		update("insert into user (name,pwd,create_time) values (?,?,now())", "王五", "1234");
		
		// 改
//		update("update user set pwd=? where status = 1 and id = ?", "123456", 3);
		
		// 删
//		update("update user set status=0 where status = 1 and id = ?", 3);
		ResultSet rs = null;
		try {
			// 查询单个
//			rs = query("select * from user where status=1 and id=?", 2);
			
			// 查询多个
			rs = query("select * from user where status=1");
			while(rs.next()) {
				System.out.println(rs.getString("name"));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(rs, ps, conn);
		}
	}
18.1.4 封装结果集
public T query(String sql,ResultSetHandler<T> handler, Object ... args) {
    try {
        // 获取连接
        conn = JDBCUtil.getConn();

        // 预处理
        ps = conn.prepareStatement(sql);

        // 元数据
        // 以问号个数为标准
        ParameterMetaData metaData = ps.getParameterMetaData();
        int count = metaData.getParameterCount();
        for(int i=0; i<count; i++) {
            ps.setObject(i+1, args[i]);
        }

        // 执行操作
        rs = ps.executeQuery();
        T t = (T)handler.handle(rs);

        return t;
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtil.close(ps, conn);
    }
    return null;
}
public interface ResultSetHandler<T> {
	
	/**
	 * 定义了数据封装的规则
	 * @param rs
	 */
	T handle(ResultSet rs);

}
User user = new CommonUtil<User>().query("select * from user where status=1 and id=?"
,new ResultSetHandler<User>() {
    User user = null;
    @Override
    public User handle(ResultSet rs) {
        try {
            while(rs.next()) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setPwd(rs.getString("pwd"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return user;
    }

},1);

System.out.println(user);

18.2 JSP的开发模式

image-20200531101410380

18.2.1 三层架构&MVC练习

image-20200531101825421

18.2.2 MVC优点
1. Dao只针对单一的逻辑,数据操作层面
2. service 是业务的层面

18.3 学生系统

18.3.1 JDBCUtil
/**
 * 数据库连接工具
 * @author Administrator
 */
public class JDBCUtil {
	
	static ComboPooledDataSource dataSource = null;
	static{
		// 创建数据库连接池
		dataSource = new ComboPooledDataSource();
	}
	
	/**
	 * 获取数据库连接池
	 * @return
	 */
	public static DataSource getDataSource() {
		return dataSource;
	}
	
	/**
	 * 获取连接对象
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConn() throws SQLException{
		return dataSource.getConnection();
	}
	
	/**
	 * 释放资源
	 * @param conn
	 * @param st
	 * @param rs
	 */
	public static void release(Connection conn , Statement st , ResultSet rs){
		closeRs(rs);
		closeSt(st);
		closeConn(conn);
	}
	public static void release(Connection conn , Statement st){
		closeSt(st);
		closeConn(conn);
	}
	
	private static void closeRs(ResultSet rs){
		try {
			if(rs != null){
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			rs = null;
		}
	}
	
	private static void closeSt(Statement st){
		try {
			if(st != null){
				st.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			st = null;
		}
	}
	
	private static void closeConn(Connection conn){
		try {
			if(conn != null){
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			conn = null;
		}
	}
}
18.3.2 DaoImpl
/**
 * 针对StudentDao接口的实现
 * @author Administrator
 */
public class StudentDaoImpl implements StudentDao {

	/**
	 * 查询所有学生信息
	 * @throws SQLException 
	 */
	@Override
	public List<Student> findAll() throws SQLException {
		
		// DBUtils封装了对JDBC的操作,简化了JDBC操作
		QueryRunner runner = new QueryRunner(JDBCUtil.getDataSource());
		
		// 准备sql
		String sql = "select * from stu";
		
		// 执行sql语句,返回结果集
		List<Student> list = runner.query(sql, new BeanListHandler<Student>(Student.class));
		
		return list;
	}
}
posted @ 2020-07-04 06:57  你还是曾经那个少年  阅读(165)  评论(0)    收藏  举报