自身感觉学的体系日渐庞大,虽然可以渐渐的熟悉项目开发,但是自身知识消化理解还不是很好。遂准备重新复习基础。
基础 变量 变量:在内存里开辟一个合适的空间来储存数据
特性: 1.)存储数据 2.)允许被改变 3.)系统来分配,在内存里开辟一个内存空间, 先声明,再赋值,使用
数据类型
数据过大溢出
随机数
random 产生的是[0,1)的小数。 如果想要产生[0–50)之间的数字: random() * 50 如果想要产生[50–100]之间的整数: (int)Math.random() * 50 + 50
取余(rem)取整 取模(Mod)
取余 %:
10 % 3 = 1(10 除以 3)—-求余数 2 % 5 = 2 (没有可以整除 5 那部分的) rem(3,2) = 1 rem(-3,-2) = -1 rem(3,-2) = 1 rem(-3,2) = -1
取模运算:Math.floorMod()
mod 结果的符号与除数 b 相同 mod(3,2)=1 mod(-3,-2)=-1 mod(3,-2)=-1 mod(-3,2)=1
取整
5 / 2 = 2 (2.5) 2 / 5 = 0 (0.4)
注意: 当除数与被除数的符号相同时,rem 和 mod 的结果是完全相同的; 当除数与被除数的符号不相同时,结果不同;
自加自减运算 不过先加减还是先运算再加减,值都是变化了
加加 减减 运算后 值都是会改变的; 只是 a++是值改变但是对本身的式子不会产生影响(因为已经运算完了,须传递给后面的式子) 而 ++a 是 先 自加 然后再进行本身式子的运算,所以会立即产生影响
三元运算符
判断条件 ? 表达式 1 : 表达式 2
Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package xnxy.blue.ternary.operator;public class TernaryOperator { public static void main (String[] args) { double num = 3.51 ; int num1 = (int )num; double num2 = num - num1; int result = (int ) (num2 >= 0.5 ? num1+1 : num1); System.out.println("num1=" +num1+",num2=" +num2); System.out.println(result); } }
异或
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package cn.blue.logicaloperator;public class LogicalOperatorSecondDemo { public static void main (String[] args) { System.out.println(true | true ); System.out.println(true | false ); System.out.println(true || (1 / 0 == 0 )); System.out.println("--------------" ); System.out.println(!false ); System.out.println(!true ); System.out.println(!!true ); System.out.println("--------------" ); System.out.println(false ^ false ); System.out.println(true ^ true ); System.out.println(false ^ true ); } }
两个变量间值的交换
键盘扫描器 Scanner 1 2 3 4 5 Scanner input = new Scanner (System.in)int num1 = input.nextint; char c1 = input.next().charAt(0 );String str = input.next();
switch 语句 1 2 3 4 5 6 7 8 9 switch (整数选择因子或者字符串或者枚举) { case 整数值 1 : 语句; break ; case 整数值 2 : 语句; break ; case 整数值 3 : 语句; break ; case 整数值 4 : 语句; break ; case 整数值 5 : 语句; break ; default :语句;92 }
switch 语句接受的数据类型
switch 语句中的表达式的数据类型,是有要求的 JDK1.0 - 1.4 数据类型接受 byte short int char JDK1.5 数据类型接受 byte short int char enum(枚举) JDK1.7 数据类型接受 byte short int char enum(枚举), String
循环语句
循环语句 for for 循环语句是最常用的循环语句,一般用在循环次数已知的情况下。for 循环语句的语法格式如下:
1 2 3 4 5 for (初始化表达式; 循环条件; 操作表达式){ 执行语句 ……… }
接下来分别用 ① 表示初始化表达式、② 表示循环条件、③ 表示操作表达式、④ 表示循环体,通过序号来具体分析 for 循环的执行流程。具体如下: for(① ; ② ; ③){ ④ }
跳转语句(break、continue)
break 语句
在 switch 条件语句和循环语句中都可以使用 break 语句。当它出现在 switch 条件语句中时,作用是终止某个 case 并跳出 switch 结构。当它出现在循环语句中,作用是跳出循环语句,执行后面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 public class BreakDemo { public static void main (String[] args) { int x = 1 ; while (x <= 4 ) { System.out.println("x = " + x); if (x == 3 ) { break ; } x++; } } }
使用标记跳出循环
当 break 语句出现在嵌套循环中的内层循环时,它只能跳出内层循环,如果想使用 break 语句跳出外层循环则需要对外层循环添加标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class BreakDemo02 { public static void main (String[] args) { int i, j; Loop: for (i = 1 ; i <= 9 ; i++) { for (j = 1 ; j <= i; j++) { if (i > 4 ) { break Loop; } System.out.print("*" ); } System.out.print("\n" ); } } }
continue 语句
continue 语句用在循环语句中,它的作用是终止本次循环,执行下一次循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ContinueDemo { public static void main (String[] args) { int sum = 0 ; for (int i = 1 ; i <= 100 ; i++) { if (i % 2 == 0 ) { continue ; } sum += i; } System.out.println("sum = " + sum); } }
九九乘法口诀
数组
变量空间:是在栈里面开辟的并且地址是由系统分配的 数组的开辟:是在堆里面开辟一块连续的内存空间,并且这个内存空间的大小是可以由程序控制的(在栈里面分配)
数组的特性:
空间连续(知道第一个地址,就知道第二个第三个……)
长度固定(局限性)
类型单一(开辟的整型的数组,就只能存放整型的,不过 byte,short 也可 以)因为可以自动转换
数组的定义:
静态初始化:
数据类型 [ ] 数组名 = {值列表}; * 数据类型 [ ] 数组名 = new 数据类型[ ]{值列表};
动态初始化:
数据类型 [ ] 数组名 = new 数据类型[ 数组长度] - 数组赋值:数组名[索引下标 ] = 值; - 属性名.length
二维数组:
静态: 数据类型 数组名 [][] = {值列表 1},{值列表 2},{值列表 3}…. 记得大花括号都括起来(语法问题报错) 动态: 数据类型 数组名 [][] = new 数据类型[ 数组长度 ][ 数组长度]
排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int [] num = {28 ,25 ,5 ,8 ,13 } ;for (int i = 0 ;i < num.length - 1 ;i++) { for (int j = 0 ;j < num.length - 1 - i;j++) { if (num[j] > num[j + 1 ]) { int temp = num[j + 1 ]; num[j + 1 ] = num[j]; num[j] = temp; } } } for (int i = 0 ;i < num.length;i++) { System.out.println(num[i] + " " ); }
1 2 3 4 5 6 7 8 int [] num2 = new int [原来数组 num.length];for (int index = num2.length - 1 ;index >= 0 ;index--) { num2[num2.length - index - 1 ] = num2[index]; } for (int index = 0 ;index < num2.length;index++) {System.out.print(num2[index] + " " ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) { int [] num = {28 ,25 ,3 ,21 ,14 }; for (int i = 0 ;i < num.length - 1 ;i++) { for (int j = i + 1 ;j < num.length;j++) { if (num[i] > num[j]) { int temp = num[i]; num[i] = num[j]; num[j] = temp; } } } for (int i = 0 ;i < num.length;i++) { System.out.print(num[i] + " " ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.summarize;import java.util.Scanner;public class test1 {public static void main (String[] args) { int [] num1 = {3 ,10 ,15 ,23 ,0 }; Scanner input = new Scanner (System.in); System.out.println("请输入要插入的值:" ); int n = input.nextInt(); int index = num1.length - 1 ; for (int m = 0 ;m < num1.length;m++) { if (num1[m] > n) { index = m; break ; } } for (int m = num1.length - 1 ;m > index;m--) { num1[m] = num1[m - 1 ]; } num1[index] = n; System.out.println("插入后的结果:" ); for (int m = 0 ;m < num1.length;m++) { System.out.print(num1[m] + " " ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package cn.blue.homework;import java.util.Scanner;public class test { public static void main (String[] args) { Scanner input = new Scanner (System.in); int [] num = new int [5 ]; for (int i = 0 ;i < 5 ;i++) { System.out.println("请输入数组的第" + i + "个数:" ); num[i] = input.nextInt(); } System.out.println(); System.out.print("输出的数组是:" ); for (int i = 0 ;i < 5 ;i++) { System.out.print(num[i] + " " ); } for (int i = 0 ;i < num.length - 1 ;i++) { System.out.println("" ); for (int j = 0 ;j < num.length - i - 1 ;j++) { if (num[j] > num[j + 1 ]) { int temp = num[j + 1 ]; num[j + 1 ] = num[j]; num[j] = temp; } } } for (int i = 0 ;i < num.length;i++) { System.out.println(num[i] + " " ); } System.out.print("请输入你要查找的数字:" ); int number = input.nextInt(); int low = 0 ; int high = num.length - 1 ; int index = binarySearch(number,4 ); 报错 } static int binaryArray (int num[],int number) { int low = 0 ; int high = num.length - 1 ; while (low <= high) { int middle = (high + low) / 2 ; if (number == num[middle]) { return middle; }else if (number < num[middle]) { high = middle; }else { low = middle; } } return - 1 ; } }
方法
特点:方法的单一性,一个方法实现一个功能
方法的执行流程
方法重载设计
类和对象
类:
抽象的,具有共同行为特征的一组对象的集合(对象的模板) 在一组对象里抽取共同的行为特征
对象
实际存在的,看得见摸得到,并且不随主观意识的改变而改变(类的实例)
属性
对象具有的各种特征 每个对象的每个属性都拥有特征值
方法
对象的行为
构造方法
作用: 创建对象并且初始化对象 对象出生的方法 特点: 方法名与类名一致 无返回值,但 void(无返回值类型,故构造方法里没有 void) 不写,虚拟机会默认生成一个无参数的构造方法 如果有显示的构造方法就不会生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class Customer { public String name; public int age; public int weight; public void setName (String name) { this .name = name; } public String getName () { return name; } public void setAge (int age) { this .age = age; } public int getAge () { return age; } public void setWeight (int Weight) { this .weight = weight; } public int getWeight () { return weight; } public Customer (String name,int age,int weight) { this .name = name; this .age = age; this .weight = weight; } }
new 对象:Customer zhanghao = new Customer(“张浩”,20,60);
一个对象的内存图
关键字
this 关键字
使用:当局部变量与成员变量名重名,如果要调用成员变量,此时在变量前加 this 关键字 意义:代表着当前操作的对象 原理图:
static 关键字
使用: 当希望某个成员变量属于整个类而不是某个对象,就加static eg:public static int count; //学生人数的累加 意义: static 修饰的成员,此成员属于某个类 总结: 1.)用 static 修饰的成员,不能用 this 关键字 2.)实例方法(非静态的方法)可以直接使用静态成员 3.)静态方法不能直接调用实例对象(非静态的成员) 4.)静态成员:类名.实例对象 不能直接调用,可以创建新对象 5.)static 不能修饰构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class StudentDemo { public int age; public static int count; public StudentDemo () { count++; } public void print () { System.out.println("学生人数:" +count); } } public class StudentCount { public static void main (String[] args) { StudentDemo student1 = new StudentDemo (); student1 .print(); StudentDemo student2 = new StudentDemo (); student2 .print(); } }
final 关键字 继承关系最大弊端是破环封装:子类能访问父类的实现细节,而且可以通过方法覆盖的形式修改实现细节 含义:最终的,不可改变的 ,它可以修饰非抽象类,非抽象方法和变量。注意:构造方法不能使用 final 修饰,因为本身不能被继承,肯定是最终的。
final修饰的变量称为常量,这些变量只能赋值一次。
1 2 final int i = 20 ;i = 30 ;
引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改。
1 2 3 4 5 final Person p = new Person ();Person p2 = new Person ();p = p2; p.name = "小明" ;
修饰成员变量,需要在创建对象前赋值,否则报错。(当没有显式赋值时,多个构造方法的均需要为其赋值。)
1 2 3 4 5 6 7 8 9 10 11 class Demo { final int m = 100 ; final int n; public Demo () { n = 2016 ; } }
super 关键字 当前对象的父类对象 因为通过无参构造器,我们就可以点出方法
{ } 代码块
执行时机:类加载之后,对象创建之前(对象创建一次就执行一次)
执行过程:
1.) 加载类
2.) 创建对象
3.) 构造方法执行(初始化的作用)
静态代码块: 执行时机:类加载时(只执行了一次) static { System.out.print(“”); }
封装
概念:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问 前提:私有的 private 目的:解决安全性的问题 步骤:
1. 把属性设置为 私有 ---->设置为 private
2. 创建共有的setter/getter方法 ---->便于属性的读写
3. 在setter/getter方法中加入属性控制语句 ---->对属性的合法性进行判断
继承 extends 语法:子类 extends 父类 特点: 1.)从内存角度讲:继承父类所有成员(成员变量,成员方法) 2.)继承单向性: 子类可以调用父类成员 父类不能调用子类成员 3.)应用角度讲:private 私有的,虽然继承过来了,但是我只是没有访问权限 4.)继承单一性: 只能继承一个父类,不能继承多个 一个父类可以有多个子类 5.)继承传递性: 父类可以传递给子类,子类再传递给子类的子类(儿子也继承爷爷的) 6.)构造方法不能被继承的 继承中的构造方法:子类对象产生的时候,会优先创建父类对象 解决:代码重用性的问题
继承图
重写(方法覆盖) 使用:当父类的方法无法满足子类的需求,重写父类的方法 特点:
方法名一致,参数一致,返回类型一致。
访问权限大于或者等于父类方法的访问权限
方法体不相同
使用注解@Override
实现方式:
继承父类
实现接口
方法重写和方法重载的区别
修饰符的访问权限 修饰符 | 同类 | 同包 | 子类 | 其他包类 | 备注 :-: | :-: | :-: | :-: | :-: | :-: | :-: public | true | true | true | true | 访问权限最高 protected | true | true | true | false | default | true | true | false | false | 不写就是默认的 private | true | false | false | false | 访问权限最低,只能在本类中访问
抽象类 abstract 使用:当某个类不希望被实例化,将此类设为抽象类(可以 new,但不能被实例化)
修饰类:class 前面 + abstract 修饰方法:访问修饰符 + abstract + [返回值类型]
eg:public abstract void eat();
抽象方法: 1.)抽象方法没有方法体{ } 2.)访问权限不能是 private 3.)抽象方法实现靠子类继承然后重写实现 4.)子类必须实现父类的抽象方法,如果不实现此类必然也要定义为抽象类(只要有一个抽象的方法,就算不使用,也要定义为抽象类)
原因: 1.)抽象类里面可以有实例方法也可以有抽象方法 2.)实例类里面不能有抽象方法,一旦某个类有抽象方法此类必须定义为抽象类
多态 特点:
1.引用转型:父类引用指向了子类对象
2.方法覆盖:子类覆盖父类的方法
实现多态的两种形式:
使用父类作为方法形参实现多态: public void play(Pet p){} 使用父类作为方法返回值实现多态: public Pet getPet(int type){}
多态理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Person { public void say () { System.out.println("Person.say" ); } } public class Man extends Person { public void say () { System.out.println("Man.say" ); } } public class Women extend Person{ public void say () { System.out.println("Women.say" ); } }
Person person = new Man();//向上转型(把某个对象的引用视为对其基类型的引用),向上转型会缩小接口范围
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { Test.operate(new Man ()); Test.operate(new Women ()); } public static void operate (Person person) { person.say(); } }
接口 实现接口:
[访问权限] interface 接口名称{ //规范 }
注意:
一般来说会使用 I 开头 + 名称
成员变量:
默认的是公共的静态常量 public final static:public final static int age = 20;
静止的属于类,不能被重写
成员方法:
公共的抽象方法 public abstract:public abstract void show();
jdk1.7 后的: public abstract void show();
接口的方法,实现此接口的类必须实现接口的所有方法;
实现:
类名 implements 接口列表(Ilock ,ITv)
接口可以多重实现
接口不能被实例化
接口类与抽象类的区别:
接口的特点和接的继承:
匿名对象 匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。
1 2 3 4 5 6 7 8 9 10 public class Person { public void eat () { System.out.println(); } } Person p = new Person ();new Person ();
匿名对象的特点: 创建匿名对象直接使用,没有变量名。 new Person().eat() //eat方法被一个没有名字的Person对象调用了。
类与类之间的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Employee { private String name; private Employee () { } public void work () { System.out.println(name + "员工工作!" ); } class Phone { private String name; public void show (Employee el) { System.out.println(el.name); System.out.println(this .name); } } }
1 2 3 4 5 6 7 8 9 10 11 12 static class Phone { public String name; public void show (Employee el) { System.out.println(el.name); System.out.println(this .name); } } ========================= Employee.Phone pr = new Employee .Phone(); pr.name = "李四" ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Test { public void height () { Door door = new Door (){ @Override public void open () { } @Override public void close () { } }; Student student = new Student () { @Override public void Study () { System.out.println("学生在学习" ); } }; }
异常 异常:依靠程序能够解决的轻微问题 错误:依靠程序不能解决的严重问题
Exception:所有异常的父类 RuntimeException:运行时异常, 不要求程序必须做出处理 checked异常:程序必须处理的异常 ArithmeticException:算术异常 InputMismatchException:输入类型不匹配 ArrayIndexOutOfBoundsException :数组下标越界异常 NullPointerException:空指针异常 ClassNotFoundException:类无法加载异常 IllegalArgumentException:方法接收到非法参数 ClassCastException:对象强制类型转换出错 NumberFormatException:数字格式转换异常,如把”abc”转换成数字
Throwable 类是 Java 语言中所有错误或异常的超类。
try: 可能会引发异常的代码块
功能:捕获异常
里面引发异常的位置后面的代码不会执行
catch: 对应异常出现的解决方案
可以有多个catch
排序要从子到父
有异常就要捕获,不管有return或System.exit(0);
finally
不论引发异常与否,就算有return,这串代码都会被执行;
finally唯一不执行的情况,程序直接退出了 :System.exit(0);
字符串 String对象 特性:不可变性 长度不可变 字符串的分类:
可变的字符串:StringBuilder / StringBuffer 当对象创建完毕后,该对象的内容可以发生改变,当内容发生改变的时候,对象保持不变
字符串的本质(底层是什么):
底层其实是char[],char表示一个字符,数组表示同一种类型的多个数据如何理解char[]: String str = “ABCDEFG”, //定义一个字符串对象 等价于 char[] cs = new char[]{‘A’,’B’,’C’,’D’,’E’,’F’,’G’};
>注:这里不考虑常量池本身含有需要被创建的对象,如果有的话,理应少一个创建的对象 str2 字符串 + 常量 会在堆里面创建对象 常量 + 常量 就直接在常量池 创建”张三李四”String str3 = “张三”+”李四”; 创建三个对象:张三 李四 张三李四 String str1 = “张三”; String str2 =str1+”李四”; 创建四个对象:张三 李四 张三李四 堆里面一个对象空间 String str4 = new String(“张三”); 创建两个:张三 堆里面 JVM对于字符串引用,由于在字符串的”+”连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即(str1 + “李四”)无法被编译器优化,只有在程序运行期来动态分配使用StringBuilder连接后的新String对象赋给str2。
String的拼接: 因为String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都是创建一个StringBuilder对象 ,并调用append()方法 ,最后调用toString()创建新String对象 ,以包含修改后的字符串内容。
StringBuffer StringBuffer:常用于频繁的修改,线程安全的,加锁(synchronized) 字符串反转(reverse):
1 2 3 4 5 6 7 8 9 public class StringBufferDemo04 { public static void main (String[] args) { StringBuffer buf = new StringBuffer () ; buf.append("World!!" ) ; buf.insert(0 ,"Hello " ) ; String str = buf.reverse().toString() ; System.out.println(str) ; } }
打印内容:!!dlroW olleH 替换字符串指定内容
1 2 3 4 5 6 7 8 public class StringBufferDemo05 { public static void main (String[] args) { StringBuffer buf = new StringBuffer () ; buf.append("Hello " ).append("World!!" ) ; buf.replace(6 , 11 , "偶my耶" ) ; System.out.println("内容替换之后的结果:" + buf) ; } }
打印内容:Hello 偶my耶!!
StringBuilder StringBuilder:线程不安全,效率最高,多用它拼接字符串
包装类
Byte short int long float double char boolean void 原始型 Byte Short Integer Long Float Double Character Boolean Void 包装类
原始型 | Byte | short | int | long | float | double | char | boolean | void :-: | :-: | :-: | :-: | :-:| :-: | :-:| :-: | :-:| :-: | :-: 包装类 | Byte | Short | Integer | Long | float | Double | Char | Boolean | Void
日期
日期格式转String SimpleDateFormat1 2 3 Date date= new Date (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" );String str=sdf.format(date);
String转日期1 2 SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); Date date = sdf.parse(str);
集合框架类 Collection Collections:提供了对集合进行排序、遍历等多种算法实现,是一个类
List 接口 List:有序的,可重复的
ArrayList
底层是数组实现的,数组没变,初始容量为10,(空间不够)创建新数组,复制原来的数组的值 ,增加新的值进去,变得是数组引用指向(指向了新的数组)
特性:
空间连续
有序的
数据可重复(只能存 引用型)
对中间节点,查询快,但是做增删操作,效率低下。
优点:单链表,查询效率快
2. LinkedList >底层是双向链表 适合存储大量数据, - 优点:查询慢,但中间节点增删快,
Vector
类似于ArrayList ,但较之于它,Vector是线程安全的
Set 接口
HashSet : 底层是Hash表
其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。 无序集合、不能重复 相比set接口,HashSet 多了一个clone()方法。
特点:
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素可以是 null,但只能放入一个 null
一般操作 HashSet 还是调用 Collection 的 add / remove 等方法进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class HashSetTest { public static void main (String[] args) { Set<String> hashSet = new HashSet <String>(); hashSet.add("1" ); hashSet.add("2" ); hashSet.add("3" ); hashSet.add("4" ); hashSet.add("5" ); hashSet.remove("1" ); System.out.println("是否包含1元素:" + hashSet.contains("2" )); Iterator<String> it = hashSet.iterator(); while (it.hasNext()){ System.out.print(it.next() + " " ); } } }
当向 HashSet 结合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在 HashSet 中存储位置。根据这种方式可以看出,HashSet 的数据存取其实是通过哈希算法实现的,因为通过哈希算法可以极大的提高数据的读取速度。通过阅读 JDK 源码,我们知道 HashSet 是通过 HashMap 实现的,只不过是HashSet 的 value 上的值都是 null 而已 。
简单的说,HashSet 集合判断两个元素相等的标准是两个对象通过 equals() 方法比较相等,并且两个对象的hashCode() 方法返回值相等。
注意,如果要把一个对象放入 HashSet 中,重写该对象对应类的 equals() 方法,也应该重写其 hashCode() 方法 。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算hashCode的值。
LinkedHashSet LinkedHashSet 在迭代访问 Set 中的全部元素时,性能比 HashSet 好,但是插入时性能稍微逊色于HashSet (因为 HashSet 直接采用哈希算法,而 LinkedHashSet 还需要维护链表结构)。
TreeSet SortedSet 接口的唯一实现类 ,TreeSet 可以确保集合元素处于排序状态 ,这也是 TreeSet最大的特征之一。
底层是最优二叉树
Map集合
Map中的元素是两个对象,一个对象作为键,一个对象作为值。键不可以重复,但是值可以重复。
Map存储元素使用put方法,Collection使用add方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Map学习体系: ---| Map 接口 将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 ---| HashMap 采用哈希表实现,所以无序 ---| TreeMap 可以对健进行排序 ---|Hashtable: 底层是哈希表数据结构,线程是同步的,不可以存入null键,null值。 效率较低,被HashMap 替代。 ---|HashMap: 底层是哈希表数据结构,线程是不同步的,可以存入null键,null值。 要保证键的唯一性,需要覆盖hashCode方法,和equals方法。 ---| LinkedHashMap: 该子类基于哈希表又融入了链表。可以Map集合进行增删提高效率。 ---|TreeMap: 底层是二叉树数据结构。可以对map集合中的键进行排序。需要使用Comparable或者Comparator 进行比较排序。return 0,来判断键的唯一性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 因为在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确使用Map必须保证:作为key的对象必须正确覆写equals()方法。 我们经常使用String作为key,因为String已经正确覆写了equals()方法。但如果我们放入的key是一个自己写的类,就必须保证正确覆写了equals()方法。 我们再思考一下HashMap为什么能通过key直接计算出value存储的索引。相同的key对象(使用equals()判断时返回true )必须要计算出相同的索引,否则,相同的key每次取出的value就不一定对。 通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int 整数。HashMap正是通过这个方法直接定位key对应的value的索引,继而直接返回value。 因此,正确使用Map必须保证: 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true ; 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范: 如果两个对象相等,则两个对象的hashCode()必须相等; 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。 即对应两个实例a和b: 如果a和b相等,那么a.equals(b)一定为true ,则a.hashCode()必须等于b.hashCode(); 如果a和b不相等,那么a.equals(b)一定为false ,则a.hashCode()和b.hashCode()尽量不要相等。 上述第一条规范是正确性,必须保证实现,否则HashMap不能正常工作。 而第二条如果尽量满足,则可以保证查询效率,因为不同的对象,如果返回相同的hashCode(),会造成Map内部存储冲突,使存取的效率下降。
编写hashCode和equals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package cn.blue;import java.util.List;import java.util.Objects;public class Equals { public static void main (String[] args) { List<Person> list = List.of( new Person ("Xiao" , "Ming" , 18 ), new Person ("Xiao" , "Hong" , 25 ), new Person ("Bob" , "Smith" , 20 ) ); boolean exist = list.contains(new Person ("Bob" , "Smith" , 20 )); System.out.println(exist ? "测试成功!" : "测试失败!" ); } static class Person { String firstName; String lastName; int age; public Person (String firstName, String lastName, int age) { this .firstName = firstName; this .lastName = lastName; this .age = age; } @Override public boolean equals (Object o) { if (o instanceof Person) { Person p = (Person) o; return Objects.equals(this .lastName, p.lastName) && Objects.equals(this .firstName, p.firstName) && this .age == p.age; } return false ; } @Override public int hashCode () { return Objects.hash(firstName, lastName, age); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package cn.blue;import java.util.HashMap;import java.util.Iterator;import java.util.Map;public class TraversalMap { public static void main (String[] args) { Map<String, Integer> map = new HashMap <>(); map.put("apple" , 123 ); map.put("pear" , 456 ); map.put("banana" , 789 ); map.values().stream().forEach(System.out::println); map.keySet().stream().forEach(System.out::println); map.forEach((k, v) -> { System.out.println(k + "," + v); }); for (String key : map.keySet()) { Integer value = map.get(key); System.out.println(key + " = " + value); } for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); int value = entry.getValue(); System.out.println(key + " " + value); } System.out.println("================" ); Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Integer> entry = it.next(); String key = entry.getKey(); int value = entry.getValue(); System.out.println(key + " " + value); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package cn.blue;import java.util.HashMap;import java.util.Map;public class MapDemo { public static void main (String[] args) { Student s = new Student ("Xiao Ming" , 99 ); Map<String, Student> map = new HashMap <>(); map.put("Xiao Ming" , s); Student target = map.get("Xiao Ming" ); System.out.println(target.toString()); System.out.println(target.score); Student another = map.get("Bob" ); System.out.println(another); } static class Student { public String name; public int score; public Student (String name, int score) { this .name = name; this .score = score; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", score=" + score + '}' ; } } }
Map 进阶学习
使用Iterator 倒序遍历集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package cn.blue;import java.util.*;public class Main { public static void main (String[] args) { ReverseList<String> reverseList = new ReverseList <>(); reverseList.add("Apple" ); reverseList.add("Orange" ); reverseList.add("Pear" ); for (String s : reverseList) { System.out.println(s); } } } class ReverseList <T> implements Iterable <T> { private List<T> list = new ArrayList <>(); public void add (T t) { list.add(t); } @Override public Iterator<T> iterator () { return new ReverseIterator (list.size()); } class ReverseIterator implements Iterator <T> { int index; ReverseIterator(int index) { this .index = index; } @Override public boolean hasNext () { return index > 0 ; } @Override public T next () { index--; return ReverseList.this .list.get(index); } } }
小结
1 2 3 4 5 Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有: 对任何集合都采用同一种访问模型; 调用者对集合内部结构一无所知; 集合类返回的Iterator对象知道如何迭代。
IO 流 File 类
遍历文件和目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package cn.blue;import java.io.File;import java.io.FilenameFilter;import java.io.IOException;public class FileDemo { public static void main (String[] args) throws IOException { File f = new File ("C:\\Windows" ); File[] fs1 = f.listFiles(); printFiles(fs1); File[] fs2 = f.listFiles(new FilenameFilter () { @Override public boolean accept (File dir, String name) { return name.endsWith(".exe" ); } }); printFiles(fs2); } static void printFiles (File[] files) { System.out.println("==========" ); if (files != null ) { for (File f : files) { System.out.println(f); } } System.out.println("==========" ); } }
按层次打印目录下的所有子目录和文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package cn.blue;import java.io.File;import java.io.IOException;public class FileDe2 { public static void main (String[] args) throws IOException { File currentDir = new File ("." ); System.out.println(currentDir.getAbsolutePath()); listDir(currentDir.getCanonicalFile(), 0 ); } static void listDir (File dir, int level) { File[] fs = dir.listFiles(); if (fs != null ) { for (File f : fs) { for (int i = 0 ; i < level; i++) { System.out.print(" " ); } if (f.isDirectory()) { System.out.println(f.getName()); try { listDir(f.getCanonicalFile(), level + 1 ); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println(f.getName()); } } } } }
File 类
InputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package cn.blue;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;class InputStreamDemo { public static void main (String[] args) { InputStream input = null ; try { input = new FileInputStream ("readme.txt" ); while (true ) { int n = input.read(); if (n == -1 ) { break ; } System.out.println(n); } } catch (IOException e) { e.printStackTrace(); } finally { try { assert input != null ; input.close(); } catch (IOException e) { e.printStackTrace(); } } } }
用try … finally来编写上述代码会感觉比较复杂,更好的写法是利用Java 7引入的新的try(resource) 的语法,只需要编写try语句,让编译器自动为我们关闭资源。推荐的写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package cn.blue;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;class InputStreamDemo2 { public static void main (String[] args) throws IOException { try (InputStream input = new FileInputStream ("readme.txt" )) { int n; while ((n = input.read()) != -1 ) { System.out.println(n); } } } }
实际上,编译器并不会特别地为InputStream加上自动关闭。编译器只看try(resource = …)中的对象是否实现了java.lang.AutoCloseable接口,如果实现了,就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口,因此,都可以用在try(resource)中。
缓冲 ,一次性读取多个字节到缓冲区
int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填充数1 2 3 4 5 6 7 8 9 10 11 try (InputStream input = new FileInputStream ("readme.txt" )) { byte [] buffer = new byte [1000 ]; int n; while ((n = input.read(buffer)) != -1 ) { String str = new String (buffer); System.err.println(str); } }
阻塞 在调用InputStream的read()方法读取数据时,我们说read()方法是阻塞(Blocking)的。它的意思是,对于下面的代码:
1 2 3 int n;n = input.read(); int m = n;
执行到第二行代码时,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间。
OutputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package cn.blue;import java.io.*;import java.nio.Buffer;import java.nio.charset.StandardCharsets;public class OutputStreamDemo { public static void main (String[] args) throws IOException { try (OutputStream output = new FileOutputStream ("readme.txt" )) { output.write("Hello " .getBytes(StandardCharsets.UTF_8)); output.write("World" .getBytes(StandardCharsets.UTF_8)); try (InputStream input = new FileInputStream ("readme.txt" )) { byte [] buffer = new byte [100 ]; int n; while ((n = input.read(buffer)) != -1 ) { String s = new String (buffer); System.err.println(s); } } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package cn.blue;import java.io.*;public class BufferedDemo { public static void main (String[] args) throws IOException { BufferedReader br = null ; BufferedWriter bw = null ; InputStreamReader ir = null ; OutputStreamWriter osw = null ; String str = null ; try (InputStream fis = new FileInputStream (new File ("readme.txt" ))) { OutputStream fos = new FileOutputStream (new File ("out.txt" )); ir = new InputStreamReader (fis); osw = new OutputStreamWriter (fos); br = new BufferedReader (ir); bw = new BufferedWriter (osw); while ((str = br.readLine()) != null ) { System.out.println(str); if (bw != null ) { bw.write(str); bw.newLine(); bw.flush(); } } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cn.blue;import java.io.*;import java.nio.charset.StandardCharsets;import java.util.Arrays;import java.util.zip.ZipEntry;public class FileReaderDemo { public static void main (String[] args) throws IOException { File fi = new File ("src/read.txt" ); if (!fi.exists()) { fi.createNewFile(); } try (Reader reader = new FileReader ("readme.txt" , StandardCharsets.UTF_8)) { char [] buffer = new char [1000 ]; int n; while ((n = reader.read(buffer)) != -1 ) { System.out.println("read " + n + " chars." ); System.out.println((new String (buffer))); } PrintStream ps = new PrintStream ("readme.txt" ); System.setOut(ps); } StringWriter buffer = new StringWriter (); try (PrintWriter pw = new PrintWriter (buffer)) { pw.println("Hello" ); pw.println(12345 ); pw.println(true ); } } }
IO 流
日期与时间 Date和Calendar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package cn.blue;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;public class TimeDemo { public static void main (String[] args) { System.out.println(System.currentTimeMillis()); Date date = new Date (); System.out.println(date.getYear() + 1900 ); System.out.println(date.getMonth() + 1 ); System.out.println(date.getDate()); System.out.println(date.toString()); System.out.println(date.toGMTString()); System.out.println(date.toLocaleString()); var sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); var sdf1 = new SimpleDateFormat ("E MMM dd, yyyy" ); Calendar c = Calendar.getInstance(); int y = c.get(Calendar.YEAR); int m = 1 + c.get(Calendar.MONTH); int d = c.get(Calendar.DAY_OF_MONTH); int w = c.get(Calendar.DAY_OF_WEEK); int hh = c.get(Calendar.HOUR_OF_DAY); int mm = c.get(Calendar.MINUTE); int ss = c.get(Calendar.SECOND); int ms = c.get(Calendar.MILLISECOND); System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms); Calendar ca = Calendar.getInstance(); ca.clear(); ca.set(Calendar.YEAR, 2019 ); ca.set(Calendar.MONTH, 8 ); ca.set(Calendar.DATE, 2 ); ca.set(Calendar.HOUR_OF_DAY, 21 ); ca.set(Calendar.MINUTE, 22 ); ca.set(Calendar.SECOND, 23 ); System.out.println(new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ).format(ca.getTime())); } }
LocalDateTime
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package cn.blue;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import java.util.Calendar;import java.util.Date;public class LocalDateTimeDemo { public static void main (String[] args) { LocalDate d = LocalDate.now(); LocalTime t = LocalTime.now(); LocalDateTime dt = LocalDateTime.now(); System.out.println(d); System.out.println(t); System.out.println(dt); LocalDateTime ldt = LocalDateTime.now(); LocalDate d1 = ldt.toLocalDate(); LocalTime t1 = ldt.toLocalTime(); LocalDate d2 = LocalDate.of(2019 , 11 , 30 ); LocalTime t2 = LocalTime.of(15 , 16 , 17 ); LocalDateTime dt2 = LocalDateTime.of(2019 , 11 , 30 , 15 , 16 , 17 ); LocalDateTime dt3 = LocalDateTime.of(d2, t2); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss" ); System.out.println(dtf.format(LocalDateTime.now())); LocalDateTime dt4 = LocalDateTime.parse("2019/11/30 15:16:17" , dtf); System.out.println(dt4); LocalDateTime localDateTime = LocalDateTime.of(2019 , 10 , 26 , 20 , 30 , 59 ); System.out.println(localDateTime); LocalDateTime dt5 = localDateTime.plusDays(5 ).minusHours(3 ); System.out.println(dt5); LocalDateTime dt6 = dt5.minusMonths(1 ); System.out.println(dt6); } }
DateTimeFormatter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package cn.blue;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.ZonedDateTime;import java.time.format.DateTimeFormatter;import java.util.Locale;public class DateTimeFormatterDemo { public static void main (String[] args) { ZonedDateTime zdt = ZonedDateTime.now(); var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ" ); System.out.println(formatter.format(zdt)); var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm" , Locale.CHINA); System.out.println(zhFormatter.format(zdt)); var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm" , Locale.US); System.out.println(usFormatter.format(zdt)); } }
数据库 | 对应Java类(旧) | 对应Java类(新) :-: | :-: | :-: | :-: DATETIME | java.util.Date | LocalDateTime DATE | java.sql.Date | LocalDate TIME | java.sql.Time | LocalTime TIMESTAMP | java.sql.Timestamp | LocalDateTime
单元测试 编写junit测试 测试代码如下:(下面以最新版本JUnit 5为例),idea可以看着junit插件,就不需要再额外配置jar管理,或者使用maven项目,自动导入。
1 2 3 4 5 6 7 8 9 public class Factorial { public static long fact (long n) { long r = 1 ; for (long i = 1 ; i <= n; i++) { r = r * i; } return r; } }
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package cn.blue;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.assertEquals;public class DemoTest { @BeforeAll static void beforeAll () { System.out.println("init once" ); } @AfterAll static void afterAll () { System.out.println("ending once" ); } @BeforeEach void before () { System.out.println("init" ); } @AfterEach void after () { System.out.println("ending" ); } @Test void add () { System.out.println((1 + 2 )); } @Test void testFact () { assertEquals(1 , Factorial.fact(1 )); assertEquals(2 , Factorial.fact(2 )); assertEquals(6 , Factorial.fact(3 )); assertEquals(3628800 , Factorial.fact(10 )); assertEquals(2432902008176640000L , Factorial.fact(20 )); } }
输出结果: init once init ending init 3 ending ending once
1 2 3 4 5 6 7 8 核心测试方法testFact()加上了@Test 注解,这是JUnit要求的,它会把带有@Test 的方法识别为测试方法。在测试方法内部,我们用assertEquals(1 , Factorial.fact(1 ))表示,期望Factorial.fact(1 )返回1 。assertEquals(expected, actual)是最常用的测试方法,它在Assertion类中定义。Assertion还定义了其他断言方法,例如: assertTrue(): 期待结果为true assertFalse () : 期待结果为false assertNotNull () : 期待结果为非null assertArrayEquals () : 期待结果为数组并与期望数组每个元素的值均相等... 运行单元测试非常简单。选中Factorial.java文件,点击Run - Run As - JUnit Test,Eclipse会自动运行这个JUnit测试,并显示结果:
使用Fixture
不必在每个测试方法中都写上初始化代码,而是通过@BeforeEach来初始化,通过@AfterEach来清理资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package cn.blue;import org.junit.jupiter.api.AfterEach;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class CalculatorTest { Calculator calculator; @BeforeEach public void setUp () { this .calculator = new Calculator (); } @AfterEach public void tearDown () { this .calculator = null ; } @Test void testAdd () { assertEquals(100 , this .calculator.add(100 )); } @Test void testSub () { assertEquals(-100 , this .calculator.sub(100 )); } }
异常测试
1 2 3 4 5 6 7 8 9 10 11 12 public class Factorial { public static long fact (long n) { if (n < 0 ) { throw new IllegalArgumentException (); } long r = 1 ; for (long i = 1 ; i <= n; i++) { r = r * i; } return r; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package cn.blue;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.function.Executable;import static org.junit.jupiter.api.Assertions.*;class FactorialTest { @Test void testNegative () { assertThrows(IllegalArgumentException.class, new Executable () { @Override public void execute () throws Throwable { Factorial.fact(-1 ); } }); } @Test void testNegativeSimple () { assertThrows(IllegalArgumentException.class, () -> { Factorial.fact(-1 ); }); } }
条件测试 在运行测试的时候,有些时候,我们需要排出某些@Test方法,不要让它运行,这时,我们就可以给它标记一个 @Disabled :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package cn.blue;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.condition.EnabledOnOs;import org.junit.jupiter.api.condition.OS;import static org.junit.jupiter.api.Assertions.*;class ConfigTest { Config config; @Test @EnabledOnOs(OS.WINDOWS) void testWindows () { assertEquals("C:\\test.ini" , config.getConfigFile("test.ini" )); } @Test @EnabledOnOs({ OS.LINUX, OS.MAC }) void testLinuxAndMac () { assertEquals("/usr/local/test.cfg" , config.getConfigFile("test.cfg" )); } }
参数化测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package cn.blue;import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.Arguments;import org.junit.jupiter.params.provider.MethodSource;import java.util.List;import static org.junit.jupiter.api.Assertions.*;class StringUtilsTest { @ParameterizedTest @MethodSource void testCapitalize (String input, String result) { assertEquals(result, StringUtils.capitalize(input)); } static List<Arguments> testCapitalize () { return List.of( Arguments.arguments("abc" , "Abc" ), Arguments.arguments("APPLE" , "Apple" ), Arguments.arguments("gooD" , "Good" )); } }
正则表达式 分组匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package cn.blue.pattern;import java.util.regex.Matcher;import java.util.regex.Pattern;public class Exercise { public static void main (String[] args) { String regex = "([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)" ; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher("08:45:34" ); if (matcher.matches()) { String s1 = matcher.group(1 ); String s2 = matcher.group(2 ); String s3 = matcher.group(3 ); } else { System.out.println("失败" ); } } }
非贪婪匹配
1 2 3 这是因为正则表达式默认使用贪婪匹配:任何一个规则,它总是尽可能多地向后匹配,因此,\d+总是会把后面的0 包含进来。 要让\d+尽量少匹配,让0 *尽量多匹配,我们就必须让\d+使用非贪婪匹配。在规则\d+后面加个?即可表示非贪婪匹配。我们改写正则表达式如下:
1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) { Pattern pattern = Pattern.compile("(\\d+?)(0*)" ); Matcher matcher = pattern.matcher("1230000" ); if (matcher.matches()) { System.out.println("group1=" + matcher.group(1 )); System.out.println("group2=" + matcher.group(2 )); } } }
1 2 3 因此,给定一个匹配规则,加上?后就变成了非贪婪匹配。 我们再来看这个正则表达式(\d??)(9*),注意\d?表示匹配0个或1个数字,后面第二个?表示非贪婪匹配,因此,给定字符串"9999",匹配到的两个子串分别是""和"9999",因为对于\d?来说,可以匹配1个9,也可以匹配0个9,但是因为后面的?表示非贪婪匹配,它就会尽可能少的匹配,结果是匹配了0个9。
搜索和替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 模板引擎是指,定义一个字符串作为模板: Hello, ${name}! You are learning ${lang}! 其中,以${key}表示的是变量,也就是将要被替换的内容 当传入一个Map<String, String>给模板后,需要把对应的key替换为Map的value。 例如,传入Map为: { "name" : "Bob" , "lang" : "Java" } 然后,${name}被替换为Map对应的值"Bob”,${lang}被替换为Map对应的值" Java",最终输出的结果为: Hello, Bob! You are learning Java! 请编写一个简单的模板引擎,利用正则表达式实现这个功能。
我们获取到Matcher对象后,不需要调用matches()方法(因为匹配整个串肯定返回false),而是反复调用find()方法 ,在整个串中搜索能匹配上规则的子串,并打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package cn.blue.pattern;import java.util.HashMap;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;public class TemplateDemo { public static void main (String[] args) { String s = "Hello, ${name}! You are learning ${lang}!" ; Template template = new Template (s); Map<String, String> map = new HashMap <>(); map.put("name" , "Bob" ); map.put("lang" , "Java" ); System.out.println(template.render(map)); } } class Template { private final String template; private final Pattern pattern = Pattern.compile("\\$\\{(\\w+)}" ); public Template (String template) { this .template = template; } public String render (Map<String, String> data) { Matcher m = pattern.matcher(template); StringBuffer sb = new StringBuffer (); while (m.find()) { String key = template.substring(m.start() + 2 , m.end() - 1 ); m.appendReplacement(sb, data.get(key)); } m.appendTail(sb); return sb.toString(); } }
输出:Hello, Bob! You are learning Java!
多线程 Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程等。
因此,对于大多数Java程序来说,我们说多任务,实际上是说如何使用多线程实现多任务。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。
Java多线程编程的特点又在于:
多线程模型是Java程序最基本的并发模型; 后续读写网络、数据库、Web开发等都依赖Java多线程模型。
创建新线程 方法一:从Thread派生一个自定义类,然后覆写run()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Main { public static void main (String[] args) { Thread t = new MyThread (); t.start(); } } class MyThread extends Thread { @Override public void run () { System.out.println("start new thread!" ); } }
执行上述代码,注意到start()方法会在内部自动调用实例的run()方法。
方法二:创建Thread实例时,传入一个Runnable实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Main { public static void main (String[] args) { Thread t = new Thread (new MyRunnable ()); t.start(); } } class MyRunnable implements Runnable { @Override public void run () { System.out.println("start new thread!" ); } }
特殊方式:Java8引入的lambda语法进一步简写为:
1 2 3 4 5 6 7 8 public class Main { public static void main (String[] args) { Thread t = new Thread (() -> { System.out.println("start new thread!" ); }); t.start(); } }
使用线程执行的打印语句,和直接在main()方法执行有区别吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Main { public static void main (String[] args) { System.out.println("main start..." ); Thread t = new Thread () { public void run () { System.out.println("thread run..." ); System.out.println("thread end." ); } }; t.start(); System.out.println("main end..." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 我们用蓝色表示主线程,也就是main线程,main线程执行的代码有4 行,首先打印main start,然后创建Thread对象,紧接着调用start()启动新线程。当start()方法被调用时,JVM就创建了一个新线程,我们通过实例变量t来表示这个新线程对象,并开始执行。 接着,main线程继续执行打印main end语句,而t线程在main线程执行的同时会并发执行,打印thread run和thread end语句。 当run()方法结束时,新线程就结束了。而main()方法结束时,主线程也结束了。 我们再来看线程的执行顺序: 1. main线程肯定是先打印main start,再打印main end;2. t线程肯定是先打印thread run,再打印thread end。但是,除了可以肯定,main start会先打印外,main end打印在thread run之前、thread end之后或者之间,都无法确定。因为从t线程开始运行以后,两个线程就开始同时运行了,并且由操作系统调度,程序本身无法确定线程的调度顺序。 要模拟并发执行的效果,我们可以在线程中调用Thread.sleep(),强迫当前线程暂停一段时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { System.out.println("main start..." ); Thread t = new Thread () { public void run () { System.out.println("thread run..." ); try { Thread.sleep(10 ); } catch (InterruptedException e) {} System.out.println("thread end." ); } }; t.start(); try { Thread.sleep(20 ); } catch (InterruptedException e) {} System.out.println("main end..." ); } }
要特别注意:直接调用Thread实例的run()方法是无效的:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Main { public static void main (String[] args) { Thread t = new MyThread (); t.run(); } } class MyThread extends Thread { @Override public void run () { System.out.println("hello" ); } }
直接调用 run() 方法,相当于调用了一个普通的Java方法,当前线程并没有任何改变,也不会启动新线程。上述代码实际上是在main()方法内部又调用了run()方法,打印hello语句是在main线程中执行的,没有任何新线程被创建。
必须调用Thread实例的start()方法才能启动新线程,如果我们查看Thread类的源代码,会看到start()方法内部调用了一个private native void start0() 方法,native 修饰符表示这个方法是由JVM虚拟机内部的C代码实现的,不是由Java代码实现的。
可以对线程设定优先级,设定优先级的方法是:
Thread.setPriority(int n) // 1~10, 默认值5
优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
1 2 3 4 5 6 7 8 9 Java用Thread对象表示一个线程,通过调用start()启动一个新线程; 一个线程对象只能调用一次start()方法; 线程的执行代码写在run()方法中; 线程调度由操作系统决定,程序本身无法决定调度顺序; Thread.sleep()可以把当前线程暂停一段时间。
线程的状态
线程终止的原因有:
线程正常终止:run()方法执行到return 语句返回;
线程意外终止:run()方法因为未捕获的异常导致线程终止;
对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。
一个线程还可以等待另一个线程直到其运行结束。例如,main线程在启动t线程后,可以通过t.join() 等待t线程结束后再继续运行:
1 2 3 4 5 6 7 8 9 10 11 public class Main { public static void main (String[] args) throws InterruptedException { Thread t = new Thread (() -> { System.out.println("hello" ); }); System.out.println("start" ); t.start(); t.join(); System.out.println("end" ); } }
输出
start hello end
当main线程对线程对象t调用join()方法时,主线程将等待变量t表示的线程运行结束 ,即join就是指等待该线程结束,然后才继续往下执行自身线程。所以,上述代码打印顺序可以肯定是main线程先打印start,t线程再打印hello,main线程最后再打印end 。
如果t线程已经结束,对实例t调用join()会立刻返回。此外,join(long)的重载方法也可以指定一个等待时间,超过等待时间后就不再继续等待。
1 2 3 4 5 6 7 8 9 Java线程对象Thread的状态包括: New、Runnable、Blocked、Waiting、Timed Waiting和Terminated; 通过对另一个线程对象调用join()方法可以等待其执行结束; 可以指定等待时间,超过等待时间线程仍然没有结束就不再等待; 对已经运行结束的线程调用join()方法会立刻返回。
yield()方法与join()方法 yield()方法是这样描述的:暂停当前正在执行的线程对象,并执行其他线程。在多线程的情况下,由CPU决定执行哪一个线程,而yield()方法就是暂停当前的线程,让给其他线程(包括它自己)执行,具体由谁执行由CPU决定。通俗来说是可以达到让步目的,但是最终谁被再次调用执行还是要看cpu调度的。注意,yield是静态方法
join()方法是指等待调用join()方法的线程执行结束,程序才会继续执行下去,这个方法适用于:一个执行程序必须等待另一个线程的执行结果才能够继续运行的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) { JoinRunnable runnable1 = new JoinRunnable (); Thread thread1 = new Thread (runnable1, "线程1" ); System.out.println("主线程开始执行!" ); thread1.start(); try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程执行结束!" ); }
1 2 3 4 5 6 7 8 9 执行结果: 主线程开始执行! 线程1 开始执行! 线程1 执行了[1 ]次 线程1 执行了[2 ]次 线程1 执行了[3 ]次 线程1 执行了[4 ]次 线程1 执行了[5 ]次 主线程执行结束!
从执行结果可以看出,加入join()方法,主线程启动了子线程之后,在等待子线程执行完毕才继续执行下面的操作。
网络编程 网络模型
由于计算机网络从底层的传输到高层的软件设计十分复杂,要合理地设计计算机网络模型,必须采用分层模型,每一层负责处理自己的操作。OSI(Open System Interconnect)网络模型是ISO组织定义的一个计算机互联的标准模型,注意它只是一个定义,目的是为了简化网络各层的操作,提供标准接口便于实现和维护。这个模型从上到下依次是:
应用层,提供应用程序之间的通信;
表示层:处理数据格式,加解密等等;
会话层:负责建立和维护会话;
传输层:负责提供端到端的可靠传输;
网络层:负责根据目标地址选择路由来传输数据;
链路层和物理层负责把数据进行分片并且真正通过物理网络传输,例如,无线网、光纤等。
常用协议
IP协议是一个分组交换,它不保证可靠传输。而TCP协议是传输控制协议,它是面向连接的协议,支持可靠传输和双向通信。TCP协议是建立在IP协议之上的,简单地说,IP协议只负责发数据包,不保证顺序和正确性,而TCP协议负责控制数据包传输,它在传输数据之前需要先建立连接,建立连接后才能传输数据,传输完后还需要断开连接。TCP协议之所以能保证数据的可靠传输,是通过接收确认、超时重传这些机制实现的。并且,TCP协议允许双向通信,即通信双方可以同时发送和接收数据。
TCP协议 也是应用最广泛的协议,许多高级协议都是建立在TCP协议之上的,例如HTTP、SMTP等。
UDP协议 (User Datagram Protocol)是一种数据报文协议,它是无连接协议,不保证可靠传输。因为UDP协议在通信前不需要建立连接,因此它的传输效率比TCP高,而且UDP协议比TCP协议要简单得多。
选择UDP协议时,传输的数据通常是能容忍丢失的,例如,一些语音视频通信的应用会选择UDP协议。
TCP编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package cn.blue.socket;import java.io.*;import java.net.ServerSocket;import java.net.Socket;import java.nio.charset.StandardCharsets;public class Server { public static void main (String[] args) throws IOException { ServerSocket ss = new ServerSocket (6666 ); System.out.println("server is running..." ); for (; ; ) { Socket sock = ss.accept(); System.out.println("connected from " + sock.getRemoteSocketAddress()); Thread t = new Handler (sock); t.start(); } } } class Handler extends Thread { Socket sock; public Handler (Socket sock) { this .sock = sock; } @Override public void run () { try (InputStream input = this .sock.getInputStream(); OutputStream output = this .sock.getOutputStream()) { handle(input, output); } catch (Exception e) { try { this .sock.close(); } catch (IOException ioe) { } System.out.println("client disconnected." ); } } private void handle (InputStream input, OutputStream output) throws IOException { var writer = new BufferedWriter (new OutputStreamWriter (output, StandardCharsets.UTF_8)); var reader = new BufferedReader (new InputStreamReader (input, StandardCharsets.UTF_8)); writer.write("hello\n" ); writer.flush(); for (; ; ) { String s = reader.readLine(); if (s.equals("bye" )) { writer.write("bye\n" ); writer.flush(); break ; } writer.write("ok: " + s + "\n" ); writer.flush(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package cn.blue.socket;import java.io.*;import java.net.Socket;import java.nio.charset.StandardCharsets;import java.util.Scanner;public class Client { public static void main (String[] args) throws IOException { Socket sock = new Socket ("localhost" , 6666 ); try (InputStream input = sock.getInputStream()) { try (OutputStream output = sock.getOutputStream()) { handle(input, output); } } sock.close(); System.out.println("disconnected." ); } private static void handle (InputStream input, OutputStream output) throws IOException { var writer = new BufferedWriter (new OutputStreamWriter (output, StandardCharsets.UTF_8)); var reader = new BufferedReader (new InputStreamReader (input, StandardCharsets.UTF_8)); Scanner scanner = new Scanner (System.in); System.out.println("[server] " + reader.readLine()); for (;;) { System.out.print(">>> " ); String s = scanner.nextLine(); writer.write(s); writer.newLine(); writer.flush(); String resp = reader.readLine(); System.out.println("<<< " + resp); if ("bye" .equals(resp)) { break ; } } } }
多发互聊
服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package cn.blue.chart;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketException;public class Test { public static void main (String[] args) { ServerSocket ss = null ; Socket s = null ; BufferedReader br = null ; PrintWriter out = null ; BufferedReader brInput = null ; try { ss = new ServerSocket (9001 ); System.out.println("服务器正在监听9001端口" ); s = ss.accept(); br = new BufferedReader (new InputStreamReader (s.getInputStream())); out = new PrintWriter (s.getOutputStream(), true ); brInput = new BufferedReader (new InputStreamReader (System.in)); String ip = s.getInetAddress().getHostAddress(); out.println("你好啊" ); while (true ) { String msgFromClient = br.readLine(); System.out.println(ip + "说:" + msgFromClient); System.out.print("您说:" ); String msgToClient = brInput.readLine(); out.println(msgToClient); } } catch (SocketException e) { System.out.println("对方已经下线或网络断开" ); } catch (IOException e) { e.printStackTrace(); } finally { if (brInput != null ) { try { brInput.close(); } catch (IOException e) { e.printStackTrace(); } } if (br != null ) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null ) { out.close(); } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } if (ss != null ) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package cn.blue.chart;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.InetAddress;import java.net.Socket;import java.net.UnknownHostException;public class Client { public static void main (String[] args) { Socket s = null ; BufferedReader br = null ; PrintWriter out = null ; BufferedReader brInput = null ; try { s = new Socket (InetAddress.getByName("127.0.0.1" ),9001 ); br = new BufferedReader (new InputStreamReader (s.getInputStream())); out = new PrintWriter (s.getOutputStream(),true ); brInput = new BufferedReader (new InputStreamReader (System.in)); while (true ) { String msgFromServer = br.readLine(); System.out.println("服务器说:" +msgFromServer); System.out.print("您说:" ); String msgToServer = brInput.readLine(); out.println(msgToServer); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if (brInput != null ) { try { brInput.close(); } catch (IOException e) { e.printStackTrace(); } } if (br != null ) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null ) { out.close(); } if (s != null ) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
发送邮件
1.引入相关依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > javax.mail</groupId > <artifactId > javax.mail-api</artifactId > <version > 1.6.2</version > </dependency > <dependency > <groupId > com.sun.mail</groupId > <artifactId > javax.mail</artifactId > <version > 1.6.2</version > </dependency >
2.通过JavaMail API连接到SMTP服务器上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 String smtp = "smtp.qq.com" ;String username = "youremail@qq.com" ;String password = "lhmaqecoacwcbjci" ;Properties props = new Properties ();props.put("mail.smtp.host" , smtp); props.put("mail.smtp.port" , "465" ); props.put("mail.smtp.auth" , "true" ); props.setProperty("mail.debug" , "true" ); props.put("mail.smtp.starttls.enable" , "true" ); props.put("mail.smtp.timeout " , "10000" ); props.setProperty("mail.smtp.socketFactory.port" , "465" ); props.setProperty("mail.smtp.socketFactory.fallback" , "false" ); props.setProperty("mail.smtp.socketFactory.class" , "javax.net.ssl.SSLSocketFactory" ); Session session = Session.getInstance(props, new Authenticator () { @Override public PasswordAuthentication getPasswordAuthentication () { return new PasswordAuthentication (username, password); } }); session.setDebug(true );
3.发送邮件
1 2 3 4 5 6 7 8 9 10 11 12 13 MimeMessage message = new MimeMessage (session);message.setFrom(new InternetAddress ("youremail@qq.com" )); message.setRecipient(Message.RecipientType.TO, new InternetAddress ("toemail@qq.com" )); message.setSubject("发送内嵌图片的HTML邮件" , "UTF-8" ); String body = "<h1>Hello 小朋友❤...</h1> <img src=\"cid:img01\" /><p>Hi <a href='https://coderblue.cn'>这是我的博客</a></p>" ;message.setText(body, "UTF-8" , "html" );
附件和内嵌图片公用一个 multipart
1 2 3 4 5 Multipart multipart = new MimeMultipart ();BodyPart textpart = new MimeBodyPart ();textpart.setContent(body, "text/html;charset=utf-8" ); multipart.addBodyPart(textpart);
4.携带附件发送
1 2 3 4 5 6 7 8 9 BodyPart imagepart = new MimeBodyPart ();imagepart.setFileName("图片.jpg" ); InputStream input = new FileInputStream ("D:\\Blog\\public\\images\\ayer.png" );imagepart.setDataHandler(new DataHandler (new ByteArrayDataSource (input, "application/octet-stream" ))); multipart.addBodyPart(imagepart);
5.HTML内嵌图片
1 2 3 4 5 6 7 8 9 BodyPart imagepart1 = new MimeBodyPart ();InputStream input1 = new FileInputStream ("D:\\Blog\\public\\images\\blue.jpg" );imagepart1.setDataHandler(new DataHandler (new ByteArrayDataSource (input1, "image/jpeg" ))); imagepart1.setHeader("Content-ID" , "img01" ); imagepart1.setFileName("blue.jpg" ); multipart.addBodyPart(imagepart1);
6.将附件和图片都添加进去
1 2 message.setContent(multipart);
7.发送邮件
1 2 Transport.send(message);
HTTP编程
HTTP Header,服务器依靠某些特定的Header来识别客户端请求,例如:
Host:表示请求的域名,因为一台服务器上可能有多个网站,因此有必要依靠Host来识别用于请求;
User-Agent:表示客户端自身标识信息,不同的浏览器有不同的标识,服务器依靠User-Agent判断客户端类型;
Accept:表示客户端能处理的HTTP响应格式,*/*表示任意格式,text/*表示任意文本,image/png表示PNG格式的图片;
Accept-Language:表示客户端接收的语言,多种语言按优先级排序,服务器依靠该字段给用户返回特定语言的网页版本。
如果是GET请求,那么该HTTP请求只有HTTP Header,没有HTTP Body。如果是POST请求,那么该HTTP请求带有Body,以一个空行分隔。一个典型的带Body的HTTP请求如下:
1 2 3 4 5 6 POST /login HTTP/1.1 Host: www.example.com Content-Type: application/x-www-form-urlencoded Content-Length: 30 username=hello&password=123456
TCP的三次握手与四次挥手理解及面试题
字段
含义
URG
紧急指针是否有效。为1,表示某一位需要被优先处理
ACK
确认号是否有效,一般置为1。
PSH
提示接收端应用程序立即从TCP缓冲区把数据读走。
RST
对方要求重新建立连接,复位。
SYN
请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1
FIN
希望断开连接。
三次握手过程理解
第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
TCP的三次握手与四次挥手理解及面试题
使用Jackson解析dom 定义好JavaBean
1 2 3 4 5 6 7 8 public class Book { public long id; public String name; public String author; public String isbn; public List<String> tags; public String pubDate; }
一个名叫Jackson的开源的第三方库可以轻松做到XML到JavaBean的转换
1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.10 .1 </version> </dependency> <dependency> <groupId>org.codehaus.woodstox</groupId> <artifactId>woodstox-core-asl</artifactId> <version>4.4 .1 </version> </dependency>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 InputStream input = Main.class.getResourceAsStream("/book.xml" );JacksonXmlModule module = new JacksonXmlModule ();XmlMapper mapper = new XmlMapper (module );Book book = null ;try { book = mapper.readValue(input, Book.class); System.out.println(book.id); System.out.println(book.name); System.out.println(book.author); System.out.println(book.isbn); System.out.println(book.tags); System.out.println(book.pubDate); } catch (IOException e) { e.printStackTrace(); }
输出
1 2 3 4 5 6 1 Java核心技术 Cay S. Horstmann 1234567 [Java, Network] null
JDBC编程 JDBC 查询 注意到这里添加依赖的scope是runtime,因为编译Java程序并不需要MySQL的这个jar包 ,只有在运行期才需要使用。如果把runtime改成compile,虽然也能正常编译,但是在IDE里写程序的时候,会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆,所以坚决不要设置为compile。
这里使用,mysql8.0
1 2 3 4 5 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String JDBC_URL = "jdbc:mysql://localhost:3306/easypoi?serverTimezone=GMT%2b8" ;String JDBC_USER = "root" ;String JDBC_PASSWORD = "密码" ;try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (PreparedStatement ps = conn.prepareStatement("select * from userinfo" )) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { int id = rs.getInt(1 ); String name = rs.getString(2 ); String sex = rs.getString(3 ); Date birthday = rs.getDate(4 ); System.out.println(id + " " + name + " " + sex + " " + birthday); } } } }
SQL 注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 使用Statement拼字符串非常容易引发SQL 注入的问题,这是因为SQL 参数往往是从方法参数传入的。 我们来看一个例子:假设用户登录的验证方法如下: User login(String name, String pass) { ... stmt.executeQuery("SELECT * FROM user WHERE login='" + name + "' AND pass='" + pass + "'"); ... } 其中,参数name和pass通常都是Web页面输入后由程序接收到的。 如果用户的输入是程序期待的值,就可以拼出正确的SQL 。例如:name = "bob",pass = "1234": SELECT * FROM user WHERE login= 'bob' AND pass= '1234' 但是,如果用户的输入是一个精心构造的字符串,就可以拼出意想不到的SQL ,这个SQL 也是正确的,但它查询的条件不是程序设计的意图。例如:name = "bob' OR pass=", pass = " OR pass='": SELECT * FROM user WHERE login= 'bob' OR pass= ' AND pass=' OR pass= '' 这个SQL 语句执行的时候,根本不用判断口令是否正确,这样一来,登录就形同虚设。 要避免SQL 注入攻击,一个办法是针对所有字符串参数进行转义,还有一个办法就是使用PreparedStatement。 PreparedStatement ps = conn.prepareStatement(sql );
JDBC更新 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 String JDBC_URL = "jdbc:mysql://localhost:3306/easypoi?serverTimezone=GMT%2b8" ;String JDBC_USER = "root" ;String JDBC_PASSWORD = "密码" ;try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO userinfo (name, sex, birthday) VALUES (?,?,?)" , Statement.RETURN_GENERATED_KEYS)) { ps.setObject(1 , "Bob" ); ps.setObject(2 , "1" ); ps.setObject(3 , "2020-05-03" ); int n = ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { long id = rs.getLong(1 ); } } } try (PreparedStatement ps = conn.prepareStatement("UPDATE userinfo SET name=? WHERE id=?" )) { ps.setObject(1 , "Jim" ); ps.setObject(2 , 3 ); int n = ps.executeUpdate(); } try (PreparedStatement ps = conn.prepareStatement("DELETE FROM userinfo WHERE id=?" )) { ps.setObject(1 , 4 ); int n = ps.executeUpdate(); } try (PreparedStatement ps = conn.prepareStatement("select * from userinfo" )) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { int id = rs.getInt(1 ); String name = rs.getString(2 ); String sex = rs.getString(3 ); Date birthday = rs.getDate(4 ); System.out.println(id + " " + name + " " + sex + " " + birthday); } } } }
JDBC事务 数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:
Atomicity:原子性
Consistency:一致性
Isolation:隔离性
Durability:持久性
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
Isolation Level
脏读(Dirty Read)
不可重复读(Non Repeatable Read)
幻读(Phantom Read)
Read Uncommitted
Yes
Yes
Yes
Read Committed
-
Yes
Yes
Repeatable Read
-
-
Yes
Serializable
-
-
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Connection conn = openConnection();try { conn.setAutoCommit(false ); insert(); update(); delete(); conn.commit(); } catch (SQLException e) { conn.rollback(); } finally { conn.setAutoCommit(true ); conn.close(); }
Web开发 Web基础 编写HTTP Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 package cn.blue.web;import java.io.*;import java.net.ServerSocket;import java.net.Socket;import java.nio.charset.StandardCharsets;import java.util.Map;public class Server { public static void main (String[] args) throws IOException { ServerSocket ss = new ServerSocket (8080 ); System.out.println("server is running..." ); for (; ; ) { Socket sock = ss.accept(); System.out.println("connected from " + sock.getRemoteSocketAddress()); Thread t = new Handler (sock); t.start(); } } } class Handler extends Thread { Socket sock; public Handler (Socket sock) { this .sock = sock; } @Override public void run () { try (InputStream input = this .sock.getInputStream()) { try (OutputStream output = this .sock.getOutputStream()) { handle(input, output); } } catch (Exception e) { try { this .sock.close(); } catch (IOException ioe) { } System.out.println("client disconnected." ); } } private void handle (InputStream input, OutputStream output) throws IOException { System.out.println("Process new http request..." ); var reader = new BufferedReader (new InputStreamReader (input, StandardCharsets.UTF_8)); var writer = new BufferedWriter (new OutputStreamWriter (output, StandardCharsets.UTF_8)); int requestFlag = -1 ; String first = reader.readLine(); if (first.startsWith("GET / HTTP/1." )) { requestFlag = 1 ; } if (first.startsWith("GET /favicon.ico HTTP/1." )) { requestFlag = 2 ; } for (; ; ) { String header = reader.readLine(); if (header.isEmpty()) { break ; } System.out.println(header); } System.out.println(requestFlag > 0 ? "Response OK" : "Response Error" ); switch (requestFlag) { case -1 : writer.write("404 Not Found\r\n" ); writer.write("Content-Length: 0\r\n" ); writer.write("\r\n" ); writer.flush(); break ; case 1 : String data = "<html><body><h1>Hello, world!</h1></body></html>" ; int length = data.getBytes(StandardCharsets.UTF_8).length; writer.write("HTTP/1.0 200 OK\r\n" ); writer.write("Connection: close\r\n" ); writer.write("Content-Type: text/html\r\n" ); writer.write("Content-Length: " + length + "\r\n" ); writer.write("\r\n" ); writer.write(data); writer.flush(); break ; case 2 : byte [] b = Server.class.getResourceAsStream("/favicon.png" ).readAllBytes(); writer.write("HTTP/1.0 200 OK\r\n" ); writer.write("Connection: close\r\n" ); writer.write("Content-Type: image/x-icon\r\n" ); writer.write("Content-Length: " + b.length + "\r\n" ); writer.write("\r\n" ); writer.flush(); output.write(b); output.flush(); break ; default : } } }
Spring开发 Ioc原理
IoC又称为依赖注入,它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现 ,例如:
bean标签中的class一定要写成全类名的形式,因为spring是根据类的全类名进行反射获取类的。
1 2 3 4 5 6 7 8 9 <beans > <bean id ="dataSource" class ="cn.blue.HikariDataSource" /> <bean id ="bookService" class ="BookService" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="userService" class ="cn.blue.UserService" > <property name ="dataSource" ref ="dataSource" /> </bean > </beans >
上述XML配置文件指示IoC容器创建3个JavaBean组件,并把id为dataSource的组件通过属性dataSource(即调用setDataSource()方法)注入到另外两个组件中。
在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。
构造方法注入
1 2 3 4 5 6 7 public class BookService { private DataSource dataSource; public BookService (DataSource dataSource) { this .dataSource = dataSource; } }
属性注入 属性注入是通过Car类的set方法进行注入的,因此一定要对类的属性定义set方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Car { private String brand; private float price; public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public float getPrice () { return price; } public void setPrice (float price) { this .price = price; } @Override public String toString () { return "Car [brand=" + brand + ", price=" + price + "]" ; } }
Spring的IoC容器同时支持属性注入和构造方法注入,并允许混合使用。
注入对象类型属性service-dao
Spring的生命周期 加载bean,实例化bean,调用生命周期的第一个方法:init-method 再就是DI–>注入操作–>再destory-method
bean的作用域–scope
SpringMVC 常用配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1.导包 spring-webmvc 2.创建配置文件 /WEB-INF/{servletName}-servlet.xml:文件名-->dispatcher-servlet.xml 3.配置SpringMvc: <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc ="http://www.springframework.org/schema/mvc" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd " >1.开启扫描 : <context:component-scan base-package ="cn.jasonone.controller" > </context:component-scan > 2.配置视图解析器: <bean class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="prefix" value ="/WEB-INF/views/" > </property > <property name ="suffix" value =".jsp" > </property > </bean > 注: 视图访问 prefix+viewName+suffix 4.修改web.xml: <servlet > <servlet-name > ds</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > ds</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping >
存入Session域