Java
Java
Overview
第一个程序
范例:HelloWorld
public class HelloWorld{
public static void main(String[] args) {
System.out.println("Hello World");
}
}
范例:Java版本
Microsoft Windows [版本 10.0.18362.1256]
(c) 2019 Microsoft Corporation。保留所有权利。
C:\Java\jdk1.8.0_162\bin>java -version
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)
Fundamentals
注释
- 单行注释://
- 多行注释:/…/
- 文档注释:/**…*/
数据类型
No. | 数据类型 | 大小/位 | 可表示的数据范围 | 默认值 |
---|---|---|---|---|
1 | byte (字节) | 8 | -128 ~ 127 | 0 |
2 | short (短整型) | 16 | -32768 ~ 32767 | 0 |
3 | int (整型) | 32 | -2147483648 ~ 2147483647 | 0 |
4 | long (长整型) | 64 | - 9223372036854775808 ~ 9223372036854775807 | 0 |
5 | float (单精度) | 32 | -3.4E38 (-3.4x10^38) ~ 3.4E38 (3.4x10^38) | 0.0 |
6 | double (双精度) | 64 | -1.7E308 (-1.7x10^308) ~ 1.7E308 (1.7x10^308) | 0.0 |
7 | char (字符) | 16 | 0 ~ 255 | '\u0000' |
8 | boolean (布尔) | - | true或false | false |
数据类型使用准则:
- 整数一般使用
int
- 小数一般使用
double
- 数据传输一般使用
byte
- 日期时间或文件长度使用
long
范例:正确的变量定义
public class TestDemo{
public static void main(String[] args){
int x=10;
System.out.println(x);
}
}
范例:两个int型常量进行除法运算
public class TestDemo{
public static void main(String[] args){
System.out.println(9/2);
}
}
输出:4
整型运算,结果还是整型。
范例:将其中一个 int
型转换为其他类型,可以得出正确结果
public class TestDemo{
public static void main(String[] args){
System.out.println(9/(double)2);
}
}
范例:任何数据类型遇到 String
变量的 “+”
操作都会自动变为 String
类型
public class TestDemo{
public static void main(String[] args){
double x=10.2;
int y=20;
String result="result:"+x+y;
System.out.println(result);
}
}
输出:result:10.220
需要使用括号改变运行顺序,即可得到正确结果。
运算符
范例:三目运算符
public class TestDemo{
public static void main(String[] args){
int x=10;
int y=20;
int result=x>y?x:y;
System.out.println(result);
}
}
程序结构
范例:if
语句
public class TestDemo{
public static void main(String[] args){
int age=16;
if(age<18) {
System.out.println("还未成年");
}
}
}
范例:if…else
语句
public class TestDemo{
public static void main(String[] args){
int age=26;
if(age<18) {
System.out.println("还未成年");
}else {
System.out.println("成年了");
}
}
}
范例:if…else if …else
语句
public class TestDemo{
public static void main(String[] args){
int age=30;
if(age<18) {
System.out.println("还未成年");
}else if(age>18&&age<40){
System.out.println("青年");
}else {
System.out.println("老年");
}
}
}
范例:使用 switch
public class TestDemo{
public static void main(String[] args){
int ch=0;
switch(ch) {
case 0:
System.out.println(0);
break;
case 1:
System.out.println(1);
break;
case 2:
System.out.println(2);
break;
default:
System.out.println(100);
break;
}
}
}
范例:使用 do…while
循环实现 1~100 累加
public class TestDemo{
public static void main(String[] args){
int x=1;
int sum=0;
do {
sum+=x;
x++;
}while(x<=100);
System.out.println(sum);
}
}
范例:使用 while
循环实现 1~100 累加
public class TestDemo{
public static void main(String[] args){
int x=1;
int sum=0;
while(x<=100){
sum+=x;
x++;
};
System.out.println(sum);
}
}
范例:使用 for
循环实现 1~100 累加
public class TestDemo{
public static void main(String[] args){
int sum=0;
for(int x=1; x<=100; x++) {
sum+=x;
}
System.out.println(sum);
}
}
范例:使用 break
语句
public class TestDemo{
public static void main(String[] args){
for(int x=0; x<5; x++) {
if(x==2) {
break;
}
System.out.println(x);
}
}
}
范例:使用 continue
语句
public class TestDemo{
public static void main(String[] args){
for(int x=0; x<5; x++) {
if(x==2) {
continue;
}
System.out.println(x);
}
}
}
方法
范例:定义一个无参无返回值方法
public class TestDemo{
public static void main(String[] args){
printinfo();
printinfo();
}
private static void printinfo() {
System.out.println("***********");
System.out.println("Hello World");
System.out.println("***********");
}
}
范例:定义一个有参无返回值方法
public class TestDemo{
public static void main(String[] args){
printinfo(3);
printinfo(5);
}
private static void printinfo(int line) {
for(int x=0; x<line; x++) {
for(int y=0; y<line-x; y++) {
System.out.println(" ");
}
for(int y=0; y<=x; y++) {
System.out.println("*");
}
System.out.println();
}
}
}
范例:定义一个有参有返回值的方法
public class TestDemo{
public static void main(String[] args){
if(isType(3)) {
System.out.println("偶数");
}else {
System.out.println("奇数");
}
}
public static boolean isType(int num) {
return num % 2 == 0;
}
}
范例:使用 return
结束方法调用
public class TestDemo{
public static void main(String[] args){
fun(10);
fun(30);
}
public static void fun(int num) {
if(num == 10) {
return; //结束方法调用
}
System.out.println(num);
}
}
范例:方法重载
public class TestDemo{
public static void main(String[] args){
System.out.println("两个整数相加: " + add(10,20));
System.out.println("三个整数相加: " + add(10,20,30));
System.out.println("两个小数相加: " + add(10.2,20.2));
}
public static int add(int x,int y) {
return x + y;
}
public static int add(int x,int y,int z) {
return x + y + z;
}
public static double add(double x,double y) {
return x + y;
}
}
方法重载时只看方法名称、参数类型和个数,无需关注方法的返回值类型。
范例:递归操作
public class TestDemo{
public static void main(String[] args){
System.out.println(add(100));
}
public static int add(int num) {
if(num == 1) {
return 1;
}
return num + add(num-1);
}
}
Object Oriented
类与对象
范例:对象使用前必须首先进行实例化操作,否则出现 NullPointerException
class Person{
String name;
int age;
public void tell() {
System.out.println("姓名:" + name + ",年龄: " + age);
}
}
public class TestDemo{
public static void main(String[] args){
Person per = null; //声明对象,却没有实例化对象
per.name="张三";
per.age=30;
per.tell();
}
}
范例:对象引用传递
class Person{
String name;
int age;
public void tell() {
System.out.println("姓名:" + name + ",年龄: " + age);
}
}
public class TestDemo{
public static void main(String[] args){
Person per1 = new Person();
per1.name="张三";
per1.age=30;
Person per2 = per1; //对象引用传递
per2.name="李四";
per1.tell();
}
}
封装
范例:封装属性,增加 getter
和 setter
方法
class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>=0 && age<=200) {
this.age = age;
}
}
public void tell() {
System.out.println("姓名:" + name + ",年龄: " + age);
}
}
public class TestDemo{
public static void main(String[] args){
Person per1 = new Person();
per1.setName("张三");
per1.setAge(-30);
per1.tell();
}
}
输出:姓名:张三,年龄: 0
构造方法
范例:构造方法重载
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>=0 && age<=200) {
this.age = age;
}
}
public void tell() {
System.out.println("姓名:" + name + ",年龄: " + age);
}
}
public class TestDemo{
public static void main(String[] args){
Person per = new Person("张三");
per.tell();
}
}
输出:姓名:张三,年龄: 0
匿名对象
范例:定义匿名对象
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>=0 && age<=200) {
this.age = age;
}
}
public void tell() {
System.out.println("姓名:" + name + ",年龄: " + age);
}
}
public class TestDemo{
public static void main(String[] args){
new Person("张三",20).tell(); //匿名对象.方法()
}
}
简单 Java 类
范例:实现雇员的简单 Java 类
class Emp{
private int empno;
private String ename;
private String job;
private double sal;
private double comm;
public Emp() {
super();
}
public Emp(int empno, String ename, String job, double sal, double comm) {
super();
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public double getComm() {
return comm;
}
public void setComm(double comm) {
this.comm = comm;
}
@Override
public String toString() {
return "Emp [empno=" + empno + ", ename=" + ename + ", job=" + job + ", sal=" + sal + ", comm=" + comm + "]";
}
}
public class TestDemo{
public static void main(String[] args){
Emp emp = new Emp(7369, "张三", "IT", 3600, 0.2);
System.out.println(emp);
}
}
输出:Emp [empno=7369, ename=张三, job=IT, sal=3600.0, comm=0.2]
数组
范例:定义并使用一个数组,使用动态初始化赋值。
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[3];
data[0] = 10;
data[1] = 20;
data[2] = 30;
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
}
}
范例:数组的引用传递
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[3];
data[0] = 10;
data[1] = 20;
data[2] = 30;
int[] temp = data;
temp[0] = 100;
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
输出:100,20,30,
引用数据类型,包括数组,在使用前需要为其开辟空间,否则会出现 NullPointerException
。
范例:数组的静态初始化
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {1,2,3,4,5};
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
范例:使用方法接收数组
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {1,2,3,4,5};
print(data);
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
范例:判断一个数据是否在指定的数组中
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {1,2,3,4,5};
int searchData = 3;
if(isExists(data,searchData)) {
System.out.println("找到了");
}else {
System.out.println("没有找到");
}
}
private static boolean isExists(int[] data, int searchData) {
for (int i = 0; i < data.length; i++) {
if(data[i] == searchData) {
return true; //查找到了结束循环
}
}
return false;
}
}
范例:在方法里修改数组内容
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {1,2,3,4,5};
inc(data);
print(data);
}
private static void inc(int[] data) {
for (int i = 0; i < data.length; i++) {
data[i] *= 2;
}
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.println(data[i] + ",");
}
}
}
范例:数组排序操作
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {5,4,3,2,1};
sort(data);
print(data);
}
private static void sort(int[] data) {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data.length - 1; j++) {
if(data[j] > data[j + 1]) {
int t = data[j];
data[j] = data[j + 1];
data[j + 1] = t;
}
}
}
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.println(data[i] + ",");
}
}
}
范例:使用内置方法进行数组排序
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {2,5,6,1,7,8,4};
java.util.Arrays.sort(data);
print(data);
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
范例:通过方法返回数组
public class TestDemo{
public static void main(String[] args) {
int[] data = init();
print(data);
}
private static int[] init() {
int[] temp = new int[] {1,2,3,4,5};
return temp;
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.println(data[i] + ",");
}
}
}
范例:获取数据的最大,最小,总和和平均值
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {2,5,6,1,7,8,4};
int[] st = stat(data);
System.out.println("最大值:" + st[0]);
System.out.println("最小值:" + st[1]);
System.out.println("总和值:" + st[2]);
System.out.println("平均值:" + st[3]);
}
private static int[] stat(int[] data) {
int[] result = new int[4];
result[0] = data[0];
result[1] = data[0];
for (int i = 0; i < data.length; i++) {
if(result[0] < data[i]) {
result[0] = data[i];
}
if(result[1] > data[i]) {
result[1] = data[i];
}
result[2] += data[i];
}
result[3] = result[2] / data.length;
return result;
}
}
范例:数组拷贝
public class TestDemo{
public static void main(String[] args) {
int[] dataA = new int[] {2,5,6,1,7,8,4};
int[] dataB = new int[] {4,7,5,9,6,3,5};
System.arraycopy(dataB, 2, dataA, 3, 2);
print(dataA);
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
范例:定义二维数组
public class TestDemo{
public static void main(String[] args) {
int[][] data = new int[][] {{1,2,3},{4,5},{6,7,8,9}};
print(data);
}
public static void print(int[][] data) {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j] + " ");
}
System.out.println();
}
}
}
范例:对象数组的动态初始化
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Person[] per = new Person[3];
per[0] = new Person("张三",20);
per[1] = new Person("李四",21);
per[2] = new Person("王五",22);
for (int i = 0; i < per.length; i++) {
System.out.println(per[i]);
}
}
}
输出:
Person [name=张三, age=20]
Person [name=李四, age=21]
Person [name=王五, age=22]
范例:对象数组的静态初始化
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Person[] per = new Person[] {new Person("张三",20),new Person("李四",21),new Person("王五",22)};
for (int i = 0; i < per.length; i++) {
System.out.println(per[i]);
}
}
}
范例:一维数组转置
public class TestDemo{
public static void main(String[] args) {
int[] data = new int[] {5,4,3,2,1};
reverse(data);
print(data);
}
private static void reverse(int[] data) {
int head = 0;
int tail = data.length - 1;
int center = data.length / 2;
for (int i = 0; i < center; i++) {
int t = data[head];
data[head] = data[tail];
data[tail] = t;
head++;
tail--;
}
}
private static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
范例:二维数组转置
public class TestDemo{
public static void main(String[] args) {
int[][] data = new int[][] {{1,2,3},{4,5,6},{7,8,9}};
reverse(data);
print(data);
}
private static void reverse(int[][] data) {
for (int i = 0; i < data.length; i++) {
for (int j = i; j < data[i].length; j++) {
int t = data[i][j];
data[i][j] = data[j][i];
data[j][i] = t;
}
}
}
private static void print(int[][] data) {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j] + " ");
}
System.out.println();
}
}
}
范例:数组去除 0
public class TestDemo{
public static void main(String[] args) {
int[] oldArr = new int[] {1,3,0,5,0,0,6,9,0};
print(oldArr);
System.out.println();
int[] newArr = new int[count(oldArr)];
copy(oldArr,newArr);
print(newArr);
}
private static void copy(int[] oldArr, int[] newArr) {
int index = 0;
for (int i = 0; i < oldArr.length; i++) {
if(oldArr[i] != 0) {
newArr[index] = oldArr[i];
index++;
}
}
}
private static int count(int[] oldArr) {
int index = 0;
for (int i = 0; i < oldArr.length; i++) {
if(oldArr[i] != 0) {
index++;
}
}
return index;
}
private static void print(int[] temp) {
for (int i = 0; i < temp.length; i++) {
System.out.print(temp[i] + ",");
}
}
}
范例:数组合并后排序
public class TestDemo{
public static void main(String[] args) {
int[] dataA = new int[] {0,1,2,3,4};
int[] dataB = new int[] {9,8,7,6,5};
int[] dataC = copy(dataA,dataB);
java.util.Arrays.sort(dataC);
print(dataC);
}
private static int[] copy(int[] dataA, int[] dataB) {
int[] newArr = new int[dataA.length + dataB.length];
System.arraycopy(dataA, 0, newArr, 0, dataA.length);
System.arraycopy(dataB, 0, newArr, dataA.length, dataB.length);
return newArr;
}
private static void print(int[] temp) {
for (int i = 0; i < temp.length; i++) {
System.out.print(temp[i] + ",");
}
}
}
String 类
范例:使用 equals()
方法进行字符串比较
public class TestDemo{
public static void main(String[] args) {
String str1 = "Hello"; //直接赋值实例化,可以入池,采用这种方式实例化
String str2 = new String("Hello"); //构造方法实例化,不会入池,不采用
String str3 = str2;
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str2.equals(str3));
}
}
结果:
true
true
true
范例:字符串与字符串常量比较
public class TestDemo{
public static void main(String[] args) {
String str = null;
if("Hello".equals(str)) {
System.out.println("条件满足");
}else {
System.out.println("条件不满足");
}
}
}
范例:字符串内容声明后不可改变,避免对字符串进行大量重复累加,否则会产生大量垃圾,应尽量避免以下代码
public class TestDemo{
public static void main(String[] args) {
String str = "";
for (int i = 0; i < 1000; i++) {
str += i;
}
System.out.println(str);
}
}
范例:使用字符数组转换小写字符串为大写字符串
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
char[] data = str.toCharArray();
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
data[i] -= 32;
}
System.out.println();
System.out.println("全部字符数组变为字符串:" + new String(data));
System.out.println("部分字符数组变为字符串:" + new String(data,0,5));
}
}
输出:
h,e,l,l,o,w,o,r,l,d,
全部字符数组变为字符串:HELLOWORLD
部分字符数组变为字符串:HELLO
范例:判断一个字符串是否全是数字
public class TestDemo{
public static void main(String[] args) {
String str = "12a34";
if(isNumber(str)) {
System.out.println("字符串全是数字");
}else {
System.out.println("字符串不全是数字");
}
}
private static boolean isNumber(String str) {
char[] data = str.toCharArray();
for (int i = 0; i < data.length; i++) {
if(data[i] < '0' || data[i] > '9') {
return false;
}
}
return true;
}
}
范例:使用字节数组将小写字母转为大写字母
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
byte[] data = str.getBytes();
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
data[i] -= 32;
}
System.out.println();
System.out.println("全部字节数组变为字符串:" + new String(data));
System.out.println("部分字节数组变为字符串:" + new String(data,0,5));
}
}
输出:
104,101,108,108,111,119,111,114,108,100,
全部字节数组变为字符串:HELLOWORLD
部分字节数组变为字符串:HELLO
范例:比较字符串大小
public class TestDemo{
public static void main(String[] args) {
String str1 = "Hellowold";
String str2 = "HELLOWORLD";
System.out.println(str1.compareTo(str2));
System.out.println(str2.compareTo(str1));
System.out.println("Hello".compareTo("Hello"));
}
}
输出:
32:大于0,表示大于
-32:小于0,表示小于
0:等于0,表示相等
范例:判断字符串的开头和结尾
public class TestDemo{
public static void main(String[] args) {
String str = "**@@hello##";
System.out.println(str.startsWith("*")); //判断开头
System.out.println(str.startsWith("@@",2)); //从指定位置开始判断开头
System.out.println(str.endsWith("##"));
}
}
输出:
true
true
true
范例:查找字符串是否存在
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
System.out.println(str.contains("hello"));
System.out.println(str.contains("xx"));
}
}
输出:
true
false
范例:查找字符串是否存在,存在返回位置,不存在返回 -1
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
System.out.println(str.indexOf("hello"));
System.out.println(str.indexOf("xx"));
System.out.println(str.indexOf("l",5));
System.out.println(str.lastIndexOf("l"));
}
}
输出:
0
-1
8
8
范例:字符串替换
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
System.out.println(str.replace("l", "_"));
System.out.println(str.replaceFirst("l", "_"));
}
}
输出:
he__owor_d
he_loworld
范例:字符串截取
public class TestDemo{
public static void main(String[] args) {
String str = "helloworld";
System.out.println(str.substring(5)); //从指定位置截取到结尾
System.out.println(str.substring(0, 5)); //截取部分字符串
}
}
输出:
world
hello
范例:字符串拆分,完全拆分
public class TestDemo{
public static void main(String[] args) {
String str = "hello world !!!";
String[] result = str.split(" ");
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
}
输出:
hello
world
!!!
范例:字符串拆分,指定个数
public class TestDemo{
public static void main(String[] args) {
String str = "hello world !!!";
String[] result = str.split(" ",2);
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
}
输出:
hello
world !!!
范例:拆分IP地址
public class TestDemo{
public static void main(String[] args) {
String str = "192.168.1.1";
String[] result = str.split("\\.");
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
}
范例:字符串常用操作
public class TestDemo{
public static void main(String[] args) {
String str = " hello ";
System.out.println(str.isEmpty());
System.out.println(str.length());
System.out.println(str.trim().length());
System.out.println(str.trim().concat("World").toUpperCase());
System.out.println(str.trim().concat("World").toLowerCase());
}
}
输出:
false
9
5
HELLOWORLD
helloworld
范例:开头首字母大写
public class TestDemo{
public static void main(String[] args) {
String str = "hello";
System.out.println(initcap(str));
}
private static String initcap(String str) {
return str.substring(0, 1).toUpperCase().concat(str.substring(1));
}
}
this 关键字
范例:使用 this
调用本类构造
class Person{
private String name;
private int age;
public Person() { //至少要保留一个构造方法没有调用其他构造
System.out.println("**一个新的Person类对象被实例化**");
}
public Person(String name) {
this(); //调用构造方法的操作一定要放在构造方法的首行
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
//setter,getter略
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Person per = new Person("张三",20);
System.out.println(per);
}
}
输出:
**一个新的Person类对象被实例化**
Person [name=张三, age=20]
如果一个类中有多个构造方法,并且之间使用 this()
互相调用,至少要保留一个构造方法没有 this()
作为出口,而这个出口一定会去调用父类构造。
范例:调用本类构造
class Emp{
private int empno;
private String ename;
private double salary;
private String dept;
public Emp() {
super();
}
public Emp(int empno) {
this(empno,"Tom",2000,"IT");
}
public Emp(int empno, String ename) {
this(empno,ename,1000,"IT");
}
public Emp(int empno, String ename, double salary, String dept) {
super();
this.empno = empno;
this.ename = ename;
this.salary = salary;
this.dept = dept;
}
@Override
public String toString() {
return "Emp [empno=" + empno + ", ename=" + ename + ", salary=" + salary + ", dept=" + dept + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Emp emp = new Emp(100);
System.out.println(emp);
}
}
输出:Emp [empno=100, ename=Tom, salary=2000.0, dept=IT]
范例:验证 this
为当前对象
class Demo{
public void print() {
System.out.println("当前对象:" + this);
}
}
public class TestDemo{
public static void main(String[] args) {
Demo demo1 = new Demo();
Demo demo2 = new Demo();
System.out.println(demo1);
demo1.print();
System.out.println(demo2);
demo2.print();
}
}
输出:
Demo@70dea4e
当前对象:Demo@70dea4e
Demo@5c647e05
当前对象:Demo@5c647e05
范例:引用传递 1
class Demo{
private int data = 10;
public Demo() {
super();
}
public Demo(int data) {
super();
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
@Override
public String toString() {
return "Demo [data=" + data + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Demo demo = new Demo(100);
fun(demo); //Demo temp = demo;
System.out.println(demo.getData());
}
private static void fun(Demo temp) {
temp.setData(30);
}
}
输出:30
String
的内容一旦声明则不可改变,改变的是内存地址的指向。
范例:引用传递 2
public class TestDemo{
public static void main(String[] args) {
String str = "Hello";
fun(str);
System.out.println(str);
}
public static void fun(String temp) {
temp = "World";
}
}
输出:Hello
范例:引用传递 3
class Demo{
private String data;
public Demo() {
super();
}
public Demo(String data) {
super();
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
public class TestDemo{
public static void main(String[] args) {
Demo demo = new Demo("Hello");
fun(demo);
System.out.println(demo.getData());
}
private static void fun(Demo temp) {
temp.setData("World");
}
}
输出:World
对象比较
范例:定义比较方法
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean compare(Person per) {
if(per == null) {
return false;
}
if(this == per) {
return true;
}
if(this.name.equals(per.name) && this.age == per.age) {
return true;
}
return false;
}
}
public class TestDemo{
public static void main(String[] args) {
Person per1 = new Person("张三",20);
Person per2 = new Person("张三",20);
if(per1.compare(per2)) {
System.out.println("是同一个对象");
}else {
System.out.println("不是同一个对象");
}
}
}
输出:是同一个对象
static 关键字
属性前面加上 static
关键字,则将该属性定义为公共属性,位于全局数据区,所有对象都可以使用,所有对象的属性值都相同。
范例:使用 static
定义属性
class Person{
private String name;
private int age;
static String country = "北京";
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getInfo() {
return "name:" + name + ",age:" + age + ",country:" + country;
}
}
public class TestDemo{
public static void main(String[] args) {
Person per1 = new Person("张三",20);
Person per2 = new Person("李四",21);
Person per3 = new Person("王五",22);
per1.country = "南京";
System.out.println(per1.getInfo());
System.out.println(per2.getInfo());
System.out.println(per3.getInfo());
}
}
输出:
name:张三,age:20,country:南京
name:李四,age:21,country:南京
name:王五,age:22,country:南京
Java 中主要存在四块内存空间:
- 栈内存空间:保存所有的对象名称。
- 堆内存空间:保存对象的属性。
- 全局数据区:保存
static
类型属性。 - 全局代码区:保存方法定义。
范例:使用类名称直接调用 static
属性
class Person{
private String name;
private int age;
static String country = "北京";
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getInfo() {
return "name:" + name + ",age:" + age + ",country:" + country;
}
}
public class TestDemo{
public static void main(String[] args) {
Person.country = "南京";
System.out.println(Person.country);
}
}
使用 static
定义的方法可以在没有实例化对象产生的情况下由类名称直接进行调用。
范例:使用 static
定义的方法
class Person{
private String name;
private int age;
private static String country = "北京";
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public static void setCountry(String c) {
country = c;
}
public static String getCountry() {
return country;
}
public String getInfo() {
return "name:" + name + ",age:" + age + ",country:" + country;
}
}
public class TestDemo{
public static void main(String[] args) {
Person.setCountry("南京");
System.out.println(Person.getCountry());
Person per = new Person("张三",20);
System.out.println(per.getInfo());
}
}
输出:
南京
name:张三,age:20,country:南京
注意:static
定义的方法不能直接调用非 static
定义的属性和方法。非 static
定义的方法可以直接调用 static
的属性和方法。
范例:统计一个类产生的实例化对象个数
class Person{
private static int count = 0;
public Person() {
System.out.println("对象个数:" + ++count);
}
}
public class TestDemo{
public static void main(String[] args) {
new Person();new Person();new Person();
}
}
输出:
对象个数:1
对象个数:2
对象个数:3
范例:为类的属性自动命名
class Book{
private static int count = 0;
private String title;
public Book() {
this("NoName-" + count++);
}
public Book(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
}
public class TestDemo{
public static void main(String[] args) {
System.out.println(new Book().getTitle());
System.out.println(new Book("Java Develop").getTitle());
System.out.println(new Book().getTitle());
}
}
输出:
NoName-0
Java Develop
NoName-1
代码块
范例:普通代码块,定义在方法中的代码块,很少使用。
public class TestDemo{
public static void main(String[] args) {
{
int x = 10;
System.out.println(x);
}
int x = 100;
System.out.println(x);
}
}
输出:
10
100
范例:构造块,定义在类中的代码块。
class Person{
public Person() { //定义构造方法
System.out.println("构造方法。");
}
{ //定义构造块
System.out.println("构造块。");
}
}
public class TestDemo{
public static void main(String[] args) {
new Person();
new Person();
new Person();
}
}
输出:
构造块。
构造方法。
构造块。
构造方法。
构造块。
构造方法。
构造块优先于构造方法执行,每当有一个新的实例化对象产生时,就会重复执行构造块的程序。
静态块,定义在类中,在构造块上使用 static
关键字进行定义。分为非主类中定义的静态块和主类中定义的静态块。
范例:非主类中定义的静态块
class Person{
public Person() { //定义构造方法
System.out.println("构造方法。");
}
{ //定义构造块
System.out.println("构造块。");
}
static { //定义静态块
System.out.println("静态块。");
}
}
public class TestDemo{
public static void main(String[] args) {
new Person();
new Person();
new Person();
}
}
输出:
静态块。
构造块。
构造方法。
构造块。
构造方法。
构造块。
构造方法。
静态块优先于构造块执行,而且不管有多少个实例化对象产生,静态块只会执行一次,主要作用是为类中的 static
属性初始化。
范例:主类中定义的静态块
public class TestDemo{
static {
System.out.println("静态块。");
}
public static void main(String[] args) {
System.out.println("主方法。");
}
}
输出:
静态块。
主方法。
在主类中定义的静态块的执行要优先于主方法执行。
范例:登录程序
class LoginValidate{ //登录验证
private String usename;
private String password;
public LoginValidate() {
super();
}
public LoginValidate(String usename, String password) {
super();
this.usename = usename;
this.password = password;
}
public boolean isValidate() {
if("stone".equals(this.usename) && "123456".equals(this.password)) {
return true;
}
return false;
}
}
class Operate{
private String[] data;
public Operate() {
super();
}
public Operate(String[] data) {
super();
this.data = data;
this.exit();
}
private void exit() {
if(this.data.length != 2) {
System.out.println("输入的参数错误!");
System.exit(1);
}
}
public String getInfo() {
if(new LoginValidate(this.data[0],this.data[1]).isValidate()) {
return "用户登录成功";
}else {
return "用户登录失败";
}
}
}
public class LoginDemo {
public static void main(String[] args) {
System.out.println(new Operate(args).getInfo());
}
}
内部类
内部类是指在一个类的内部定义了其他类。内部类可以方便地访问外部类的私有属性,外部类可以方便地访问内部类的私有属性。
范例:认识内部类
class Outer{
private String msg = "hello world";
class Inner{
public void print() {
System.out.println(Outer.this.msg);
}
}
public void fun() {
Inner in = new Inner();
in.print();
}
}
public class TestDemo{
public static void main(String[] args) {
Outer out = new Outer();
out.fun();
}
}
输出:hello world
范例:将内部类拆分为两个独立的类
class Outer{
private String msg = "hello world";
public void fun() {
Inner in = new Inner(this);
in.print();
}
public String getMsg() {
return this.msg;
}
}
class Inner{
private Outer outer = null;
public Inner(Outer outer) {
this.outer = outer;
}
public void print() {
System.out.println(this.outer.getMsg());
}
}
public class TestDemo{
public static void main(String[] args) {
Outer out = new Outer();
out.fun();
}
}
输出:hello world
范例:外部类访问内部类的私有属性
class Outer{
private String msg = "hello world";
class Inner{
private String info = "hello china";
public void print() {
System.out.println(Outer.this.msg);
}
}
public void fun() {
Inner in = new Inner();
in.print();
System.out.println(in.info);
}
}
public class TestDemo{
public static void main(String[] args) {
Outer out = new Outer();
out.fun();
}
}
输出:
hello world
hello china
范例:在不同的类中实例化内部类对象
class Outer{
private String msg = "hello world";
class Inner{
private String info = "hello china";
public void print() {
System.out.println(Outer.this.msg);
}
}
}
public class TestDemo{
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner(); //实例化内部类对象
in.print();
}
}
输出:hello world
范例:使用private关键字定义私有内部类,不能被其他类使用。
class Outer{
private String msg = "hello world";
private class Inner{
private String info = "hello china";
public void print() {
System.out.println(Outer.this.msg);
}
}
}
范例:static定义的内部类,只能访问外部类中static类型的操作。
class Outer{
private static String msg = "hello world";
static class Inner{
public void print() {
System.out.println(Outer.msg);
}
}
}
public class TestDemo{
public static void main(String[] args) {
Outer.Inner in = new Outer.Inner(); //实例化static内部类
in.print();
}
}
输出:hello world
范例:在方法中定义内部类
class Outer{
private String msg = "hello world";
public void fun(){
class Inner{
public void print() {
System.out.println(Outer.this.msg);
}
}
Inner in = new Inner();
in.print();
}
}
public class TestDemo{
public static void main(String[] args) {
new Outer().fun();
}
}
输出:hello world
此时内部类如果要访问方法的参数或者方法中定义变量时,这些参数或者变量前一定要增加一个final关键字,否则无法调用。
范例:在方法的参数或者变量前加上final
class Outer{
private String msg = "hello world";
public void fun(final int x){
final String info = "hello china";
class Inner{
public void print() {
System.out.println(Outer.this.msg);
System.out.println(x);
System.out.println(info);
}
}
Inner in = new Inner();
in.print();
}
}
public class TestDemo{
public static void main(String[] args) {
new Outer().fun(30);
}
}
输出:
hello world
30
hello china
引用与类抽象的实际作用
范例:完成简单Java类的编写并设置关联关系
class Member{
private int mid;
private String name;
private Car car;
public Member() {
super();
}
public Member(int mid, String name) {
super();
this.mid = mid;
this.name = name;
}
public int getMid() {
return mid;
}
public void setMid(int mid) {
this.mid = mid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Member [mid=" + mid + ", name=" + name + "]";
}
}
class Car{
private String title;
private String color;
private Member member;
public Car() {
super();
}
public Car(String title, String color) {
super();
this.title = title;
this.color = color;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Member getMember() {
return member;
}
public void setMember(Member member) {
this.member = member;
}
@Override
public String toString() {
return "Car [title=" + title + ", color=" + color + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Member mem = new Member(1, "张三");
Car car = new Car("BMW", "红色");
mem.setCar(car);
car.setMember(mem);
System.out.println("【取得member的信息】:" + mem.toString());
System.out.println("【取得member对应的car的信息】:" + mem.getCar().toString());
System.out.println("【取得car对应的member的信息】:" + car.getMember().toString());
}
}
输出:
【取得member的信息】:Member [mid=1, name=张三]
【取得member对应的car的信息】:Car [title=BMW, color=红色]
【取得car对应的member的信息】:Member [mid=1, name=张三]
范例:深入引用关系
class Member{
private int mid;
private String name;
private Car car;
private Member child;
public Member() {
super();
}
public Member(int mid, String name) {
super();
this.mid = mid;
this.name = name;
}
public int getMid() {
return mid;
}
public void setMid(int mid) {
this.mid = mid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public Member getChild() {
return child;
}
public void setChild(Member child) {
this.child = child;
}
@Override
public String toString() {
return "Member [mid=" + mid + ", name=" + name + "]";
}
}
class Car{
private String title;
private String color;
private Member member;
public Car() {
super();
}
public Car(String title, String color) {
super();
this.title = title;
this.color = color;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Member getMember() {
return member;
}
public void setMember(Member member) {
this.member = member;
}
@Override
public String toString() {
return "Car [title=" + title + ", color=" + color + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Member mem = new Member(1, "张三");
Member chd = new Member(100, "张小三");
Car car = new Car("BMW", "红色");
Car c = new Car("蹦蹦车", "黑色");
mem.setCar(car);
mem.setChild(chd);
chd.setCar(c);
car.setMember(mem);
c.setMember(chd);
System.out.println("【取得member的child的车信息】:" + mem.getChild().getCar().toString());
}
}
输出:【取得member的child的车信息】:Car [title=蹦蹦车, color=黑色]
范例:操作人员与部门的映射
class Emp{
private int empno;
private String ename;
private String job;
private double sal;
private double comm;
private Emp mgr;
private Dept dept;
public Emp() {
super();
}
public Emp(int empno, String ename, String job, double sal, double comm) {
super();
this.empno = empno;
this.ename = ename;
this.job = job;
this.sal = sal;
this.comm = comm;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public double getComm() {
return comm;
}
public void setComm(double comm) {
this.comm = comm;
}
public Emp getMgr() {
return mgr;
}
public void setMgr(Emp mgr) {
this.mgr = mgr;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp [empno=" + empno + ", ename=" + ename + ", job=" + job + ", sal=" + sal + ", comm=" + comm + "]";
}
}
class Dept{
private int deptno;
private String dname;
private String loc;
private Emp[] emps;
public Dept() {
super();
}
public Dept(int deptno, String dname, String loc) {
super();
this.deptno = deptno;
this.dname = dname;
this.loc = loc;
}
public int getDeptno() {
return deptno;
}
public void setDeptno(int deptno) {
this.deptno = deptno;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public Emp[] getEmps() {
return emps;
}
public void setEmps(Emp[] emps) {
this.emps = emps;
}
@Override
public String toString() {
return "Dept [deptno=" + deptno + ", dname=" + dname + ", loc=" + loc + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
//1.配置关系
Dept dept = new Dept(10, "IT", "beijing");
Emp empa = new Emp(100, "King", "manager", 10000, 0);
Emp empb = new Emp(101, "abel", "programmer", 8000, 0);
Emp empc = new Emp(102, "Tom", "dba", 10000, 0);
empa.setMgr(empb);
empb.setMgr(empc);
empa.setDept(dept);
empb.setDept(dept);
empc.setDept(dept);
dept.setEmps(new Emp[]{empa,empb,empc});
//2.取得关系
System.out.println(dept.toString());
for (int i = 0; i < dept.getEmps().length; i++) {
System.out.println(dept.getEmps()[i].toString());
if(dept.getEmps()[i].getMgr() != null) {
System.out.println("\t" + dept.getEmps()[i].getMgr().toString());
}
System.out.println("---------------------");
}
}
}
输出:
Dept [deptno=10, dname=IT, loc=beijing]
Emp [empno=100, ename=King, job=manager, sal=10000.0, comm=0.0]
Emp [empno=101, ename=abel, job=programmer, sal=8000.0, comm=0.0]
---------------------
Emp [empno=101, ename=abel, job=programmer, sal=8000.0, comm=0.0]
Emp [empno=102, ename=Tom, job=dba, sal=10000.0, comm=0.0]
---------------------
Emp [empno=102, ename=Tom, job=dba, sal=10000.0, comm=0.0]
---------------------
单向链表
范例:扩大一个对象数组的范围
public class TestDemo{
public static void main(String[] args) {
String[] str = {"hello","world","stone"};
String[] newStr = new String[6];
System.arraycopy(str, 0, newStr, 0, str.length);
str = newStr;
str[3] = "beijing";
str[4] = "nanjing";
for (int i = 0; i < str.length; i++) {
System.out.print(str[i] + ",");
}
}
}
输出:hello,world,stone,beijing,nanjing,null,
范例:链表的核心组成类-NODE,使用while循环输出全部节点
class Node{
private String data;
private Node next;
public Node() {
super();
}
public Node(String data) {
super();
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
public class TestDemo{
public static void main(String[] args) {
Node n1 = new Node("火车头");
Node n2 = new Node("车厢A");
Node n3 = new Node("车厢B");
n1.setNext(n2);
n2.setNext(n3);
Node currentNode = n1;
while(currentNode != null) {
System.out.println(currentNode.getData());
currentNode = currentNode.getNext();
}
}
}
输出:
火车头
车厢A
车厢B
范例:使用递归输出全部节点
class Node{
private String data;
private Node next;
public Node() {
super();
}
public Node(String data) {
super();
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
public class TestDemo{
public static void main(String[] args) {
Node n1 = new Node("火车头");
Node n2 = new Node("车厢A");
Node n3 = new Node("车厢B");
n1.setNext(n2);
n2.setNext(n3);
print(n1);
}
private static void print(Node node) {
System.out.println(node.getData());
if(node.getNext() != null) {
print(node.getNext());
}
}
}
输出:
火车头
车厢A
车厢B
范例:实现链表基础操作类
class Node{
private String data;
private Node next;
public Node() {
super();
}
public Node(String data) {
super();
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public void addNode(Node newNode) {
if(this.next == null) {
this.next = newNode;
}else {
this.next.addNode(newNode);
}
}
public void printNode() {
System.out.println(this.data);
if(this.next != null) {
this.next.printNode();
}
}
}
class Link{ //处理节点关系
private Node root;
public Link() {
super();
}
public Link(Node root) {
super();
this.root = root;
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
public void add(String data) {
if(data == null) {
return;
}
Node newNode = new Node(data);
if(root == null) {
this.root = newNode;
}else {
this.root.addNode(newNode);
}
}
public void print() {
if(this.root != null) {
this.root.printNode();
}
}
}
public class TestDemo{
public static void main(String[] args) {
Link all = new Link();
all.add("hello");
all.add("world");
all.add("stone");
all.print();
}
}
输出:
hello
world
stone
范例:链表的方法
class Link{
private class Node{ //处理节点关系
private String data;
private Node next;
public Node() {
super();
}
public Node(String data) {
super();
this.data = data;
}
public void addNode(Node newNode) { //增加节点
if(this.next == null) {
this.next = newNode;
}else {
this.next.addNode(newNode);
}
}
public boolean containsNode(String data) { //查找数据
if(data.equals(this.data)) {
return true;
}else {
if(this.next != null) {
return this.next.containsNode(data);
}else {
return false;
}
}
}
public void removeNode(Node previous, String data) { //删除数据
if(data.equals(this.data)) {
previous.next = this.next;
}else {
this.next.removeNode(this, data);
}
}
public void toArrayNode() {
Link.this.retData[Link.this.foot ++] = this.data;
if(this.next != null) {
this.next.toArrayNode();
}
}
public String getNode(int index) {
if(Link.this.foot ++ == index) {
return this.data;
}else {
return this.next.getNode(index);
}
}
}
private Node root;
private int count; //统计元素个数
private int foot = 0; //返回数据的脚标
private String[] retData; //返回数组
private boolean changeFlag = true; //链表修改标记
public boolean add(String data) { //增加数据
if(data == null) {
return false;
}
Node newNode = new Node(data); //将数据封装为节点
if(this.root != null) {
this.root = newNode;
}else {
this.root.addNode(newNode);
}
this.count ++; //元素个数增加
this.changeFlag = true;
return true;
}
public boolean addAll(String[] data) { //增加多个数据
for (int i = 0; i < data.length; i++) {
if(!this.add(data[i])) {
return false;
}
}
this.changeFlag = true;
return true;
}
public int size() { //取得元素个数
return this.count;
}
public boolean isEmpty() { //判断是否为空链表
return this.count == 0;
}
public boolean contains(String data) { //查找数据
if(this.root == null || data == null) {
return false;
}
return this.root.containsNode(data);
}
public void remove(String data) { //删除数据
if(!this.contains(data)) {
return;
}
if(data.equals(this.root.data)) {
this.root = this.root.next;
}else {
this.root.next.removeNode(this.root,data);
}
this.changeFlag = true;
this.count --;
}
public String[] toArray() { //获取链表全部数据
if(this.count == 0) {
return null;
}
if(this.changeFlag == true) {
this.foot = 0;
this.retData = new String[this.count];
this.root.toArrayNode();
this.changeFlag = false;
}
return this.retData;
}
public String get(int index) { //取得指定索引数据
if(index > this.count) {
return null;
}
this.foot = 0;
return this.root.getNode(index);
}
public void clear() {
this.root = null;
this.count = 0;
}
}
public class TestDemo{
public static void main(String[] args) {
}
}
范例:通过链表保存多个部门
class Link{
private class Node{ //处理节点关系
private Dept data;
private Node next;
public Node(Dept data) {
super();
this.data = data;
}
public void addNode(Node newNode) { //增加节点
if(this.next == null) {
this.next = newNode;
}else {
this.next.addNode(newNode);
}
}
public boolean containsNode(Dept data) { //查找数据
if(data.compare(this.data)) {
return true;
}else {
if(this.next != null) {
return this.next.containsNode(data);
}else {
return false;
}
}
}
public void removeNode(Node previous, Dept data) { //删除数据
if(data.compare(this.data)) {
previous.next = this.next;
}else {
this.next.removeNode(this, data);
}
}
public void toArrayNode() {
Link.this.retData[Link.this.foot ++] = this.data;
if(this.next != null) {
this.next.toArrayNode();
}
}
public Dept getNode(int index) {
if(Link.this.foot ++ == index) {
return this.data;
}else {
return this.next.getNode(index);
}
}
}
private Node root;
private int count; //统计元素个数
private int foot = 0; //返回数据的脚标
private Dept[] retData; //返回数组
private boolean changeFlag = true; //链表修改标记
public boolean add(Dept data) { //增加数据
if(data == null) {
return false;
}
Node newNode = new Node(data); //将数据封装为节点
if(this.root == null) {
this.root = newNode;
}else {
this.root.addNode(newNode);
}
this.count ++; //元素个数增加
this.changeFlag = true;
return true;
}
public boolean addAll(Dept[] data) { //增加多个数据
for (int i = 0; i < data.length; i++) {
if(!this.add(data[i])) {
return false;
}
}
this.changeFlag = true;
return true;
}
public int size() { //取得元素个数
return this.count;
}
public boolean isEmpty() { //判断是否为空链表
return this.count == 0;
}
public boolean contains(Dept data) { //查找数据
if(this.root == null || data == null) {
return false;
}
return this.root.containsNode(data);
}
public void remove(Dept data) { //删除数据
if(!this.contains(data)) {
return;
}
if(data.compare(this.root.data)) {
this.root = this.root.next;
}else {
this.root.next.removeNode(this.root,data);
}
this.changeFlag = true;
this.count --;
}
public Dept[] toArray() { //获取链表全部数据
if(this.count == 0) {
return null;
}
if(this.changeFlag == true) {
this.foot = 0;
this.retData = new Dept[this.count];
this.root.toArrayNode();
this.changeFlag = false;
}
return this.retData;
}
public Dept get(int index) { //取得指定索引数据
if(index > this.count) {
return null;
}
this.foot = 0;
return this.root.getNode(index);
}
public void clear() {
this.root = null;
this.count = 0;
}
}
class Dept{
private int deptno;
private String dname;
private String loc;
public Dept() {
super();
}
public Dept(int deptno, String dname, String loc) {
super();
this.deptno = deptno;
this.dname = dname;
this.loc = loc;
}
@Override
public String toString() {
return "Dept [deptno=" + deptno + ", dname=" + dname + ", loc=" + loc + "]";
}
public boolean compare(Dept dept) {
if(this == dept) {
return true;
}
if(dept == null) {
return false;
}
if(this.deptno == dept.deptno && this.dname.equals(dept.dname) && this.loc.equals(dept.loc)) {
return true;
}
return false;
}
}
public class TestDemo{
public static void main(String[] args) {
Link all = new Link();
all.add(new Dept(10,"IT","beijing"));
all.add(new Dept(20,"Sales","shanghai"));
all.add(new Dept(30,"HR","shenzhen"));
System.out.println(all.contains(new Dept(10,"IT","beijing")));
all.remove(new Dept(10,"IT","beijing"));
Dept[] result = all.toArray();
for (int i = 0; i < result.length; i++) {
System.out.println(result[i].toString());
}
}
}
输出:
true
Dept [deptno=20, dname=Sales, loc=shanghai]
Dept [deptno=30, dname=HR, loc=shenzhen]
继承
范例:观察继承的实现
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
class Student extends Person{
}
public class TestDemo{
public static void main(String[] args) {
Student stu = new Student();
stu.setName("张三");
stu.setAge(20);
System.out.println(stu.toString());
}
}
输出:Person [name=张三, age=20]
范例:在子类中扩充父类功能
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
class Student extends Person{
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class TestDemo{
public static void main(String[] args) {
Student stu = new Student();
stu.setName("张三");
stu.setAge(20);
stu.setSchool("NJST");
System.out.println(stu.getName() + "," + stu.getAge() + "," + stu.getSchool());
}
}
输出:张三,20,NJST
继承的限制:
- 一个子类只能继承一个父类,存在单继承局限。
- 在一个子类继承时,实际上会继承父类中的所有操作(属性、方法),但是需要注意的是,对于所有的非私有操作属于显式继承(可以直接使用对象操作),而所有的私有操作属于隐式继承。
- 在继承关系中,如果要实例化子类对象,会默认先调用父类构造,为父类中的属性初始化,之后再调用子类构造,为子类中的属性初始化,即默认情况下,子类会找到父类中的无参构造方法。
范例:子类对象实例化前调用父类构造
class A{
public A() {
System.out.println("------父类无参构造------");
}
}
class B extends A{
public B() {
System.out.println("******子类无参构造******");
}
}
public class TestDemo{
public static void main(String[] args) {
B b = new B();
}
}
输出:
------父类无参构造------
******子类无参构造******
默认情况下,子类调用的是父类的无参构造,如果父类没有无参构造,子类必须通过super()调用指定参数的构造方法。且super()调用父类构造时一定要放在构造方法首行。this()和super()调用构造方法时不能同时出现。
范例:子类调用指定的构造方法
class A{
public A(String msg) {
System.out.println("------父类有参构造------");
System.out.println(msg);
}
}
class B extends A{
public B() {
super("hello world");
System.out.println("******子类无参构造******");
}
}
public class TestDemo{
public static void main(String[] args) {
B b = new B();
}
}
输出:
------父类有参构造------
hello world
******子类无参构造******
覆写
当子类定义了和父类在方法名称、返回值类型、参数类型及个数完全相同的方法,称为方法的覆写。被子类覆写的方法不能拥有比父类更严格的访问控制权限。Java会根据实例化的子类不同,调用不同子类所覆写过的方法。
范例:实现方法覆写
class A{
public void print() {
System.out.println("hello world");
}
}
class B extends A{
@Override
public void print() {
System.out.println("hello stone");
}
}
public class TestDemo{
public static void main(String[] args) {
B b = new B();
b.print();
}
}
输出:hello stone
当一个子类要调用父类被子类覆写过的方法,要在方法前加上super。操作范围:
- this.方法():先从本类查找,如果没有找到,调用父类方法。
- super.方法():直接调用父类方法。
范例:访问父类方法
class A{
public void print() {
System.out.println("hello world");
}
}
class B extends A{
@Override
public void print() {
super.print();
System.out.println("hello stone");
}
}
public class TestDemo{
public static void main(String[] args) {
B b = new B();
b.print();
}
}
输出:
hello world
hello stone
范例:定义数组父类,排序子类,反转子类
class Array{
private int[] data;
private int foot = 0;
public Array() {
super();
}
public Array(int len) {
if(len > 0) {
this.data = new int[len];
}else {
this.data = new int[1];
}
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
public boolean add(int num) {
if(this.foot < this.data.length) {
this.data[this.foot++] = num;
return true;
}
return false;
}
public void increment(int num) {
int[] newArr = new int[this.data.length + num];
System.arraycopy(this.data, 0, newArr, 0, this.data.length);
this.data = newArr;
}
}
class SortArrar extends Array{ //定义排序类
public SortArrar(int len) {
super(len);
}
@Override
public int[] getData() {
java.util.Arrays.sort(super.getData());
return super.getData();
}
}
class ReverseArray extends Array{
public ReverseArray(int len) {
super(len);
}
@Override
public int[] getData() {
int head = 0;
int tail = super.getData().length - 1;
int center = super.getData().length / 2;
for (int i = 0; i < center; i++) {
int temp = super.getData()[head];
super.getData()[head] = super.getData()[tail];
super.getData()[tail] = temp;
head++;
tail--;
}
return super.getData();
}
}
public class TestDemo{
public static void main(String[] args) {
}
}
final关键字
使用final定义的类不能有子类,即无法被其他类所继承。
使用final定义的方法不能被子类覆写。
使用final定义的变量就是常量,常量在定义时必须设置默认值,并且无法修改。如果使用public static定义常量,这个常量就称为全局常量。定义final常量时每个单词的字母都要大写。
单例设计模式
范例:单例设计模式,构造方法私有化,使用static方法取得本类的实例化对象
class Singleton{
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
public void print() {
System.out.println("hello world");
}
}
public class TestDemo{
public static void main(String[] args) {
Singleton inst = Singleton.getInstance();
inst.print();
}
}
输出:hello world
多例设计模式
范例:多例设计模式
class Sex{
private static final Sex MALE = new Sex("男");
private static final Sex FEMALE = new Sex("女");
private String title;
private Sex() {}
private Sex(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public static Sex getMale() {
return MALE;
}
public static Sex getFemale() {
return FEMALE;
}
public static Sex getInstance(String msg) {
switch (msg) {
case "male":
return MALE;
case "female":
return FEMALE;
default:
return null;
}
}
}
public class TestDemo{
public static void main(String[] args) {
Sex male = Sex.getInstance("male");
System.out.println(male.getTitle());
}
}
输出:男
多态
方法的多态性:重载与覆写
- 重载:同一个方法名称,根据不同的参数类型和个数完成不同的功能。
- 覆写:同一个方法名称,根据操作的子类不同,所完成的功能也不同。
对象的多态性:父子类对象的转换
- 向上转型:子类对象变为父类对象,父类 父类对象 = 子类实例,自动。
- 向下转型:父类对象变为子类对象,子类 子类对象 = (子类)父类实例,强制。
对象多态性和方法覆写是紧密联系在一起的。对于对象向上转型操作而言,覆写后调用的一定是被覆写过的方法。 当对象发生向下转型关系前,一定要首先发生对象的向上转型关系。在实际开发中,使用最多的也是向上转型。
范例:对象向上转型
class A{
public void print() {
System.out.println("父类方法");
}
}
class B extends A{
@Override
public void print() {
System.out.println("子类方法");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new B(); //向上转型
a.print(); //调用被覆写的方法
}
}
输出:子类方法
范例:对象向下转型
class A{
public void print() {
System.out.println("父类方法");
}
}
class B extends A{
@Override
public void print() {
System.out.println("子类方法");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new B(); //向上转型
B b = (B) a; //向下转型
a.print(); //调用被覆写的方法
b.print();
}
}
输出:
子类方法
子类方法
范例:判断对象是否是某个类的实例
class A{
public void print() {
System.out.println("父类方法");
}
}
class B extends A{
@Override
public void print() {
System.out.println("子类方法");
}
public void getB() {
System.out.println("B,getB()");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new B();
System.out.println(a instanceof A); //判断对象是否是A类实例
System.out.println(a instanceof B); //判断对象是否是B类实例
if(a instanceof B) { //如果是B类实例,则执行向下转型
B b = (B)a;
b.getB();
}
}
}
输出:
true
true
B,getB()
注意:子类不要扩充方法。
为了日后的操作方便,在编写代码时,尽量不要去执行向下转型操作。子类尽量不要扩充新的方法名称(父类没有的方法名称),应该依据父类定义的操作完善方法。
范例:利用向上转型,定义一个方法,接收任意子类对象
class A{
public void print() {
System.out.println("父类方法");
}
}
class B extends A{
@Override
public void print() {
System.out.println("子类B方法");
}
}
class C extends A{
@Override
public void print() {
System.out.println("子类C方法");
}
}
public class TestDemo{
public static void main(String[] args) {
fun(new B());
fun(new C());
}
private static void fun(A a) { //接收A类子类实例
a.print();
}
}
输出:
子类B方法
子类C方法
抽象类
抽象类最大的特点是包含了抽象方法,抽象方法是只声明而未实现(没有方法体)的方法。抽象方法定义时要使用abstract关键字完成,并且抽象方法一定要在抽象类中,抽象类要使用abstract关键字声明。
抽象类的使用原则如下:
- 抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类。
- 子类(如果不是抽象类)必须覆写抽象类中的全表抽象方法。
- 抽象类可以使用子类的向上转型方式,通过子类来进行实例化操作。
- 类的名字如果为名词,一般使用抽象类。
范例:使用抽象类
abstract class A{
private String info = "hello world";
public void print() {
System.out.println(info);
}
public abstract void get();
}
class Impl extends A{
@Override
public void get() {
System.out.println("hello stone");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new Impl();
a.print();
a.get();
}
}
输出:
hello world
hello stone
范例:一个类只有在执行了构造方法后,才可以为类中的属性初始化,而在属性没有初始化前,类中的所有属性都是其对应数据类型的默认值。
abstract class Demo{
public Demo() { //构造方法
this.print();
}
public abstract void print(); //抽象方法
}
class DemoImpl extends Demo{
private int x = 100;
public DemoImpl(int x) { //子类构造方法
super(); //子类对象实例化前会首先调用父类构造
this.x = x;
}
@Override
public void print() {
System.out.println("x=" + x);
}
}
public class TestDemo{
public static void main(String[] args) {
new DemoImpl(30);
}
}
输出:x=0
范例:模板设计模式
abstract class Action{
public static final int EAT = 1;
public static final int SLEEP = 3;
public static final int WORK = 5;
public static final int RUN = 7;
public void order(int flag) {
switch (flag) {
case EAT:
this.eat();
break;
case SLEEP:
this.sleep();
break;
case WORK:
this.work();
break;
case RUN:
this.run();
break;
case EAT + SLEEP + RUN:
this.eat();
this.sleep();
this.run();
break;
case EAT + WORK:
this.eat();
this.work();
break;
case EAT + SLEEP + WORK + RUN:
this.eat();
this.sleep();
this.work();
this.run();
break;
}
}
public abstract void eat();
public abstract void sleep();
public abstract void work();
public abstract void run();
}
class Dog extends Action{
@Override
public void eat() {
System.out.println("Dog eat");
}
@Override
public void sleep() {
System.out.println("Dog sleep");
}
@Override
public void work() {
}
@Override
public void run() {
System.out.println("Dog run");
}
}
class Person extends Action{
@Override
public void eat() {
System.out.println("Person eat");
}
@Override
public void sleep() {
System.out.println("Person sleep");
}
@Override
public void work() {
System.out.println("Person work");
}
@Override
public void run() {
System.out.println("Person run");
}
}
public class TestDemo{
public static void main(String[] args) {
Action act1 = new Dog();
act1.order(Action.EAT + Action.RUN + Action.SLEEP);
}
}
输出:
Dog eat
Dog sleep
Dog run
接口
如果一个类定义时全部由抽象方法和全局常量组成,那么这种类就称为接口。接口使用interface关键字定义。
接口使用原则如下:
- 每一个接口必须定义子类,子类使用implements关键字实现接口。
- 接口的子类(如果不是抽象类)必须覆写接口中所定义的全部抽象方法。
- 利用接口的子类,采用对象的向上转型方式,进行接口对象的实例化操作。
- 类的名字如果为形容词,一般使用接口。
接口的访问权限只有一种:public。
一个抽象类可以实现多个接口,一个接口却不能继承抽象类,一个接口却可以同时继承多个接口。
范例:子类实现接口
interface A{
public static final String INFO = "hello world";
public abstract void print();
}
interface B{
public abstract void get();
}
class X implements A,B{ //同时实现2个接口
@Override
public void get() { //方法覆写
System.out.println(INFO);
}
@Override
public void print() { //方法覆写
System.out.println("hello stone");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new X();
B b = new X();
a.print();
b.get();
}
}
输出:
hello stone
hello world
范例:子类继承抽象类同时实现接口
interface A{
public static final String INFO = "hello world";
public abstract void print();
}
interface B{
public abstract void get();
}
abstract class C{
public abstract void fun();
}
class X extends C implements A,B{ //同时继承抽象类和接口
@Override
public void get() { //方法覆写
System.out.println(INFO);
}
@Override
public void print() { //方法覆写
System.out.println("hello stone");
}
@Override
public void fun() {
System.out.println("hello china");
}
}
public class TestDemo{
public static void main(String[] args) {
A a = new X();
B b = new X();
C c = new X();
a.print();
b.get();
c.fun();
}
}
输出:
hello stone
hello world
hello china
范例:接口多继承
interface A{
public static final String INFO = "hello world";
public abstract void print();
}
interface B{
public abstract void get();
}
interface C extends A,B{
public abstract void printc();
}
class X implements C{
@Override
public void print() {}
@Override
public void get() {}
@Override
public void printc() {}
}
public class TestDemo{
public static void main(String[] args) {
}
}
接口3大主要功能:
- 制定操作标准。
- 表示一种能力。
- 将服务器端的远程方法视图暴露给客户端:分布式开发。
范例:使用接口定义标准
interface USB{ //操作标准
public abstract void install();
public abstract void work();
}
class Phone implements USB{ //实现USB接口
@Override
public void install() {
System.out.println("安装手机驱动程序");
}
@Override
public void work() {
System.out.println("手机与电脑进行工作");
}
}
class Print implements USB{ //实现USB接口
@Override
public void install() {
System.out.println("安装打印机驱动程序");
}
@Override
public void work() {
System.out.println("打印机开始打印");
}
}
class Computer{
public void plugin(USB usb) { //接收USB接口实例
usb.install(); //调用接口方法
usb.work(); //调用接口方法
}
}
public class TestDemo{
public static void main(String[] args) {
Computer c = new Computer();
c.plugin(new Phone());
c.plugin(new Print());
}
}
输出:
安装手机驱动程序
手机与电脑进行工作
安装打印机驱动程序
打印机开始打印
范例:工厂设计模式-问题引出
interface Fruit{
public abstract void eat();
}
class Apple implements Fruit{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
class Orange implements Fruit{
@Override
public void eat() {
System.out.println("吃橘子");
}
}
public class TestDemo{
public static void main(String[] args) {
Fruit f = new Apple();
f.eat();
}
}
输出:吃苹果
主方法中,一个接口和一个子类紧密耦合在一起,不方便维护,缺乏可移植性。
范例:工厂设计模式
interface Fruit{
public abstract void eat();
}
class Apple implements Fruit{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
class Orange implements Fruit{
@Override
public void eat() {
System.out.println("吃橘子");
}
}
class Factory{
public static Fruit getInstance(String className) {
if("apple".equals(className)) {
return new Apple();
}
if("orange".equals(className)) {
return new Orange();
}
return null;
}
}
public class TestDemo{
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
输出:吃苹果
范例:代理设计模式(静态代理)
interface Network{
public abstract void browse();
}
class Real implements Network{ //真实的上网操作
@Override
public void browse() {
System.out.println("上网浏览信息");
}
}
class Proxy implements Network{ //代理上网
private Network network;
public Proxy(Network network) {
super();
this.network = network;
}
private void check() {
System.out.println("检查用户是否合法");
}
@Override
public void browse() {
this.check();
this.network.browse(); //调用真实上网操作
}
}
public class TestDemo{
public static void main(String[] args) {
Network net = new Proxy(new Real()); //实例化代理,同时传入代理真实操作
net.browse();
}
}
输出:
检查用户是否合法
上网浏览信息
范例:代理设计模式(静态代理)
接口:
package cn.stone.staticproxy;
public interface Account {
public abstract void queryAccount();
public abstract void updateAccount();
}
实现类:
package cn.stone.staticproxy;
public class AccountImpl implements Account {
@Override
public void queryAccount() {
System.out.println("查看账户的方法");
}
@Override
public void updateAccount() {
System.out.println("修改账户的方法");
}
}
代理类:
package cn.stone.staticproxy;
public class AccountProxy implements Account{
private AccountImpl accountImpl;
public AccountProxy(AccountImpl accountImpl) {
super();
this.accountImpl = accountImpl;
}
@Override
public void queryAccount() {
System.out.println("事务处理前");
accountImpl.queryAccount();
System.out.println("事务处理后");
}
@Override
public void updateAccount() {
System.out.println("事务处理前");
accountImpl.updateAccount();
System.out.println("事务处理后");
}
}
测试类:
package cn.stone.staticproxy;
public class AccountTest {
public static void main(String[] args) {
AccountImpl accountImpl = new AccountImpl();
AccountProxy accountProxy = new AccountProxy(accountImpl);
accountProxy.updateAccount();
accountProxy.queryAccount();
}
}
输出:
事物处理前
修改账户的方法
事物处理后
事物处理前
查看账户的方法
事物处理后
范例:代理设计模式(JDK动态代理)
接口:
package cn.stone.jdkdynamicproxy;
public interface BookFacade {
public abstract void addBook();
}
实现类:
package cn.stone.jdkdynamicproxy;
public class BookFacadeImpl implements BookFacade {
@Override
public void addBook() {
System.out.println("增加图书的方法");
}
}
代理类:
package cn.stone.jdkdynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) { //绑定委托对象并返回一个代理类
this.target = target; //取得代理类
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("事务开始");
Object result = method.invoke(target, args); //执行方法
System.out.println("事务结束");
return result;
}
}
测试类:
package cn.stone.jdkdynamicproxy;
public class TestProxy {
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy();
BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
bookProxy.addBook();
}
}
输出:
事物开始
增加图书的方法
事物结束
注意:JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用CGLIB动态代理了。
CGLIB动态代理:
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
范例:代理设计模式(CGLIB动态代理)
类:
package cn.stone.cglibdynamicproxy;
public class BookFacade {
public void addBook() {
System.out.println("增加图书的方法");
}
}
代理类:
package cn.stone.cglibdynamicproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this); //回调方法
return enhancer.create(); //创建代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("事务开始");
proxy.invokeSuper(obj, args);
System.out.println("事务结束");
return null;
}
}
测试类:
package cn.stone.cglibdynamicproxy;
public class TestCglibProxy {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
BookFacade bookFacade = (BookFacade) cglibProxy.getInstance(new BookFacade());
bookFacade.addBook();
}
}
输出:
事物开始
增加图书的方法
事物结束
匿名内部类
范例:定义匿名内部类
interface Message{
public abstract void print();
}
class Demo{
public static void get(Message msg) {
msg.print();
}
}
public class TestDemo{
public static void main(String[] args) {
Demo.get(new Message() {
@Override
public void print() {
System.out.println("hello world");
}
});
}
}
输出:hello world
Object类
如果定义一个没有继承任何父类的类,则该类默认继承Object类,即Object类可以接收所有类的实例化对象。
范例:使用Object类接收任意对象的引用
class Person{
}
public class TestDemo{
public static void main(String[] args) {
Object obj = new Person();
Person per = (Person) obj;
}
}
对于任意一个简单Java类而言,理论上应该覆盖Object类的3个方法:
- 取得对象信息:public String toString();toString()方法默认输出对象地址。
- 对象比较:public boolean equals(Object obj);
- 取得哈希码:public int hashCode();
范例:直接输出对象
class Person{
}
public class TestDemo{
public static void main(String[] args) {
Person per = new Person();
System.out.println(per);
System.out.println(per.toString());
}
}
输出:
Person@70dea4e
Person@70dea4e
对象直接输出默认调用了Object类的toString()方法,toString()默认输出对象地址。子类可以根据自己的需要进行方法的覆写。
范例:覆写Object类的toString()方法
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo{
public static void main(String[] args) {
Person per = new Person("张三",20);
System.out.println(per.toString());
}
}
输出:Person [name=张三, age=20]
在Object类中,默认的equals()比较的是两个对象的内存地址。子类可以根据自己的需要进行方法的覆写。
范例:覆写Object类的equals()方法,实现对象比较操作
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj == null) {
return false;
}
if(!(obj instanceof Person)) {
return false;
}
Person per = (Person) obj;
if(this.name.equals(per.name) && this.age == per.age) {
return true;
}
return false;
}
}
public class TestDemo{
public static void main(String[] args) {
Person per1 = new Person("张三",20);
Person per2 = new Person("张三",20);
System.out.println(per1.equals(per2));
}
}
输出:true
范例:使用Object类接收数组
public class TestDemo{
public static void main(String[] args) {
Object obj = new int[] {1,2,3};
if(obj instanceof int[]) {
int[] data = (int[]) obj;
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + ",");
}
}
}
}
输出:1,2,3,
范例:从接口定义而言,他是不能继承一个父类的,但是由于接口依然属于引用类型,所以即使没有继承类,也可以使用Object接收
interface Message{
}
class MessageImpl implements Message{
@Override
public String toString() {
return "New Message: Hello World";
}
}
public class TestDemo{
public static void main(String[] args) {
Message msg = new MessageImpl(); //向上转型
Object obj = msg; //使用Object接收接口对象,向上转型
Message tmp = (Message) obj; //向下转型
System.out.println(tmp);
}
}
输出:New Message: Hello World
包装类
范例:基本数据类型的包装
class Int{
private int num;
public Int() {
super();
}
public Int(int num) { //传入基本数据类型
super();
this.num = num;
}
public int getNum() { //取得包装的数据
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public class TestDemo{
public static void main(String[] args) {
Int temp = new Int(10); //把基本数据类型变为类
int result = temp.getNum();
System.out.println(result);
}
}
输出:10
8种包装类:
- 数值型(Number子类):Byte,Short,Integer,Long,Float,Double
- 对象型(Object子类):Boolean,Character
基本数据类型与包装类的转换:
- 装箱操作:将基本数据类型变为包装类,称为装箱。
- 拆箱操作:将包装类变为基本数据类型,称为拆箱。
范例:以int和Integer为例实现装箱和拆箱操作
public class TestDemo{
public static void main(String[] args) {
Integer var = new Integer(15); //装箱
int result = var.intValue(); //拆箱
System.out.println(result);
}
}
输出:15
范例:以int和Integer为例实现自动装箱和拆箱操作
public class TestDemo{
public static void main(String[] args) {
Integer var = 15; //装箱
int result = var; //拆箱
System.out.println(++var * result); //直接计算
}
}
输出:240
范例:使用Object接收int类型
public class TestDemo{
public static void main(String[] args) {
Object obj = 15; //int->自动装箱->Object
int result = (Integer) obj; //Object->包装类->自动拆箱
System.out.println(result * result);
}
}
输出:225
范例:通过直接赋值方式实例化的包装类对象可以自动入池
public class TestDemo{
public static void main(String[] args) {
Integer x = 10;
Integer y = 10;
System.out.println(x == y);
}
}
输出:true
范例:将字符串变为int类型,double类型和boolean类型数据
public class TestDemo{
public static void main(String[] args) {
String str1 = "16"; //字符串由数字组成
int result1 = Integer.parseInt(str1); //String->int
System.out.println(result1);
String str2 = "16.00";
double result2 = Double.parseDouble(str2);
System.out.println(result2);
String str3 = "true";
boolean result3 = Boolean.parseBoolean(str3);
System.out.println(result3);
}
}
输出:
16
16.0
true
任何基本类型数据遇到String后都变为String型数据。
范例:将int型数据变为String型数据
public class TestDemo{
public static void main(String[] args) {
int num = 100;
String str = num + ""; //int->String
System.out.println(str.length());
}
}
输出:3
范例:利用String类方法valueOf()将int类型变为String类型
public class TestDemo{
public static void main(String[] args) {
int num = 100;
String str = String.valueOf(num); //int->String
System.out.println(str.length());
}
}
输出:3
Exception
Java异常的体系结构是一个树形结构,其中根节点是 Throwable
类,所有异常类都是它的子类。这个体系结构主要分为两大类:Error
和 Exception
。
Error
Error
类表示系统级错误或资源耗尽的情况,这些错误通常是由 Java 虚拟机(JVM)抛出的,并且应用程序通常无法处理它们。例如,OutOfMemoryError
(内存溢出错误)和StackOverflowError
(栈溢出错误)都是 Error
类的子类。由于这些错误通常是由 JVM 管理的资源引起的,因此应用程序不应该尝试去捕获或处理它们。
Exception
Exception
类表示由程序本身引起的问题,这些问题可以被捕获并处理。Exception
类又可以分为两种类型:
- 检查型异常(Checked Exception)
检查型异常是在编译时期就需要被捕获或声明抛出的异常。这些异常通常是由于外部因素(如文件读写、网络操作等)引起的,因此程序必须显式地处理它们。常见的检查型异常有 IOException
(输入输出异常)、SQLException
(数据库操作异常)等。
- 非检查型异常(Unchecked Exception)
非检查型异常,也被称为运行时异常(RuntimeException),是在运行时可能发生的异常,它们不需要在编译时期被捕获或声明抛出。这些异常通常是由程序逻辑错误引起的。常见的非检查型异常有NullPointerException
(空指针异常)、ArrayIndexOutOfBoundsException
(数组越界异常)、ClassCastException
(类型转换异常)等。
总的来说,Java 的异常体系结构是一个层次结构,其中 Throwable
是顶层类,Error
和 Exception
是它的两个主要子类。Exception
类又进一步细分为检查型异常和非检查型异常。这种体系结构使得 Java 能够更好地处理各种异常情况,提高程序的健壮性和可维护性。
Java 异常的默认处理流程是在程序执行过程中,一旦出现异常且没有被显式捕获和处理,就会按照特定的步骤进行处理。以下是Java异常的默认处理流程:
- 异常产生:当程序在“编译”或者“执行”的过程中遇到问题时,比如语法错误(注意,语法错误并不属于异常体系)、空指针引用、数组越界等,就会抛出异常。
- 自动创建异常对象:Java 虚拟机会在出现异常的代码处自动创建一个异常对象。例如,如果出现除数为零的情况,会自动创建一个
ArithmeticException
异常对象。 - 异常抛出:异常会从出现异常的代码点开始,被逐级抛出给调用者,最终由调用者抛出给JVM虚拟机。
- 虚拟机处理:当JVM虚拟机接收到异常对象后,它首先会在控制台直接输出异常栈信息数据。这些信息有助于开发者定位问题发生的具体位置和原因。
- 程序终止:最后,JVM虚拟机会直接从当前执行的异常点终止程序。这意味着,一旦异常没有被捕获和处理,程序就会立即结束,后续的代码将没有机会执行。
为了避免程序因未处理的异常而意外终止,开发者应该使用 try-catch
语句块来捕获并处理可能出现的异常,或者使用 throws
关键字声明可能会抛出的异常,以便调用者能够妥善处理。同时,开发者还可以利用 finally
块来执行一些必要的清理工作,如释放资源等,以确保程序的健壮性和稳定性。
try {
// 可能会抛出异常的代码
// ...
} catch (ExceptionType e) {
// 处理异常
// ...
} finally {
// 无论是否发生异常都会执行的代码
// 如关闭文件、释放资源等
// ...
}
处理异常
范例:产生异常的程序
public class TestDemo{
public static void main(String[] args) {
System.out.println("1、除法开始计算");
int result = 10 / 0;
System.out.println("2、除法计算结果" + result);
System.out.println("3、除法计算结束");
}
}
输出:
1、除法开始计算
Exception in thread "main" java.lang.ArithmeticException: / by zero
at TestDemo.main(TestDemo.java:4)
范例:处理异常
public class TestDemo{
public static void main(String[] args) {
System.out.println("1、除法开始计算");
try {
int result = 10 / 0;
System.out.println("2、除法计算结果" + result); //之前语句有异常,此语句不再执行
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("不管是否出现异常都要执行");
}
System.out.println("3、除法计算结束");
}
}
输出:
1、除法开始计算
java.lang.ArithmeticException: / by zero
at TestDemo.main(TestDemo.java:5)
不管是否出现异常都要执行
3、除法计算结束
异常处理流程:
- 没有异常处理程序,则交给 JVM 处理,输出异常信息,结束程序。
- 有异常处理程序,没有捕获异常,则执行完
finally
语句块后,则交给 JVM 处理,输出异常信息,结束程序。 - 有异常处理程序,有捕获异常,执行
catch
语句块进行异常处理,再执行完finally
语句块后,继续执行其他语句。
捕获范围小的异常必须放在捕获范围大的异常前面。
throws 关键字
Throws 关键字主要是在方法上使用,表示此方法中不进行异常处理,而交给被调用处处理。
范例:调用 throws
声明的方法
class MyMath{
public int div(int x,int y) throws Exception{ //此方法不处理异常
return x / y;
}
}
public class TestDemo{
public static void main(String[] args) {
try {
System.out.println(new MyMath().div(10, 0));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
java.lang.ArithmeticException: / by zero
at MyMath.div(TestDemo.java:3)
at TestDemo.main(TestDemo.java:9)
调用 throws
声明的方法时,一定要使用异常处理操作进行异常处理。也可以在主方法上继续使用 throws
进行异常抛出。相当于交给 JVM 进行异常处理。
范例:在主方法上抛出异常
class MyMath{
public int div(int x,int y) throws Exception{ //此方法不处理异常
return x / y;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception {
System.out.println(new MyMath().div(10, 0));
}
}
输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at MyMath.div(TestDemo.java:3)
at TestDemo.main(TestDemo.java:8)
注意:
子类重写父类方法时,不能抛出父类没有的异常,或者比父类更大的异常,通常在子类中使用
try...catch
处理异常。
throw 关键字
之前的异常类对象都是由 JVM 自动进行实例化操作的,用户也可以使用 throw
关键字手工抛出一个异常类的实例化对象。
范例:使用 throw
手工抛出一个异常
public class TestDemo{
public static void main(String[] args){
try {
throw new Exception("手工抛出一个异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
java.lang.Exception: 手工抛出一个异常
at TestDemo.main(TestDemo.java:4)
以上程序手工抛出一个异常类的实例化对象,此时的程序必须使用 try…catch
语句进行处理或者在方法上增加 throws
声明。
- 抛出的异常对象如果是编译时异常,必需使用
throws
声明 - 抛出的异常对象如果是运行时异常,则不需要使用
throws
声明
异常处理的标准格式
范例:定义一个 div()
方法,(1)进行除法操作前,输出一行提示信息;(2)除法操作执行完毕后,输出一行提示信息;(3)如果中间产生了异常,交给被调用处来进行处理
class MyMath{
public int div(int x,int y) throws Exception{ //出现异常交给被调用处处理
System.out.println("======计算开始======");
int result = 0;
try {
result = x / y;
} catch (Exception e) {
throw e; //向上抛出异常
}finally {
System.out.println("======计算结束======"); //在return前执行
}
return result;
}
}
public class TestDemo{
public static void main(String[] args){
try {
System.out.println(new MyMath().div(10, 0));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
======计算开始======
======计算结束======
java.lang.ArithmeticException: / by zero
at MyMath.div(TestDemo.java:6)
at TestDemo.main(TestDemo.java:18)
RuntimeException
对于 RuntimeException
异常类型可以有选择性地进行处理,如果没有处理,将交给JVM默认进行处理。
Java编译器要求方法必须声明抛出可能发生的非运行时异常,但并不要求必须声明抛出未被捕获的运行时异常。即 Exception
定义了必须处理的异常,而 RuntimeException
定义的异常可以选择性地进行处理。
常见的 RuntimeException
有 NumberFormatException
,ClassCastException
,NullPointerException,ArithmeticException
,ArrayIndexOutOfBoundsException
。
范例:将字符串变为Int型
public class TestDemo{
public static void main(String[] args){
String str = "123";
int num = Integer.parseInt(str);
System.out.println(num);
}
}
输出:123
Integer类parseInt()方法的定义:
public static int parseInt(String s) throws NumberFormatException
NumberFormatException 的继承结构:
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.IllegalArgumentException
java.lang.NumberFormatException
自定义异常类
在 Java 中,自定义异常的步骤通常包括以下两部分:
- 编写一个类继承
Exception
或RuntimeException
:- 自定义异常类需要继承自
Exception
或其子类,或者直接继承RuntimeException
。如果自定义的异常需要在编译时被检查,即需要调用者显式地捕获和处理,则应该继承自Exception
或其子类。如果自定义的异常希望在运行时被抛出,即不需要调用者显式地捕获和处理,则可以继承自RuntimeException
。
- 自定义异常类需要继承自
- 提供构造方法:
- 在自定义异常类中,需要至少提供一个构造方法。通常,我们会提供两个构造方法:一个无参数的构造方法,和一个带有描述信息(通常是字符串)的构造方法。这样,在抛出异常时,我们可以选择是否提供异常的具体描述信息。
范例:自定义异常
class MyException extends Exception{
public MyException(String msg) {
super(msg);
}
}
public class TestDemo{
public static void main(String[] args) throws MyException{
throw new MyException("自己的异常类");
}
}
输出:
Exception in thread "main" MyException: 自己的异常类
at TestDemo.main(TestDemo.java:8)
Package
定义及使用
包实际上就是文件夹。
范例:定义类Hello.java
package cn.stone.demo;
public class Hello {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
输出:Hello Java
范例:包的导入
package cn.stone.util;
public class Message {
public String getInfo() {
return "Hello World";
}
}
package cn.stone.test;
import cn.stone.util.*;
public class Test {
public static void main(String[] args) {
Message msg = new Message();
System.out.println(msg.getInfo());
}
}
输出:Hello World
范例:导入不同包的同名类
package cn.stone.info;
public class Message {
public String getInfo() {
return "Hello People";
}
}
package cn.stone.test;
public class Test {
public static void main(String[] args) {
cn.stone.util.Message msg1 = new cn.stone.util.Message();
System.out.println(msg1.getInfo());
cn.stone.info.Message msg2 = new cn.stone.info.Message();
System.out.println(msg2.getInfo());
}
}
输出:
Hello World
Hello People
访问权限
- Private只能在一个类中访问
- Default只能在一个包中访问
- Protected在不同包子类
- Public所有包都可以
范例:观察protect权限
package cn.aa;
public class A {
protected String info = "Hello World";
}
package cn.bb;
import cn.aa.A;
public class B extends A{
public void print() {
System.out.println(super.info); //可以访问protected属性
}
}
package cn.test;
import cn.bb.*;
public class Test {
public static void main(String[] args) {
new B().print();
}
}
输出:Hello World
命名规范
- 类名称:每一个单词的首字母大写
- 变量名称:第一个单词的首字母小写,之后每个单词的首字母大写
- 方法名称:第一个单词的首字母小写,之后每个单词的首字母大写
- 常量名称:所有字母大写
- 包名称:所有字母小写
New Features
可变参数
在 Java 中,可变参数(Varargs,也称为变长参数)是一种在方法声明中接受可变数量参数的特性。它允许调用方法时传入任意数量的参数,而这些参数会被当作数组来处理。要使用可变参数,需要在参数类型后面加上三个点(...
)。
注意:
- 一个方法只能有一个可变参数,并且它必须是该方法的最后一个参数。
- 可变参数可以看作是数组,在方法体内可以作为数组来使用。
- 在方法调用时,如果只有一个参数,并且这个参数是数组,并且该数组类型与可变参数的类型兼容,那么可以不需要显式地使用数组名作为参数(即不需要使用
arrayName.clone()
或类似的方法)。Java 会自动处理这种情况。 - 可变参数可以与普通参数一起使用,但是可变参数必须位于参数列表的最后。
范例:使用可变参数
public class TestDemo{
public static void main(String[] args) throws Exception{
System.out.println(add(new int[] {1,2,3}));
System.out.println(add(1,2,3));
System.out.println(add());
}
public static int add(int ... data) {
int sum = 0;
for (int i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
}
输出:
6
6
0
foreach 输出
范例:使用 foreach 输出
public class TestDemo{
public static void main(String[] args) throws Exception{
int[] data = new int[] {1,2,3,4,5};
int sum = 0;
for (int i : data) {
sum += i;
}
System.out.println(sum);
}
}
输出:15
静态导入
如果一个类中的全部方法都是static型的,则可以进行静态导入,将这个类中全部方法导入进来,就可以直接使用了。
范例:使用静态导入
package cn.stone.util;
public class MyMath {
public static int add(int x,int y) {
return x + y;
}
public static int sub(int x,int y) {
return x -y;
}
public static int mul(int x,int y) {
return x * y;
}
public static int div(int x,int y) {
return x / y;
}
}
package cn.stone.demo;
import static cn.stone.util.MyMath.*; //静态导入
public class TestDemo{
public static void main(String[] args) throws Exception{
System.out.println("加法操作:" + add(10,20)); //直接调用方法
System.out.println("减法操作:" + sub(20,10)); //直接调用方法
}
}
输出:
加法操作:30
减法操作:10
泛型
Jav a泛型(Generics)是 Java 编程语言的一个重要特性,它在 JDK 5 版本中引入,主要用于增强代码的类型安全性和可读性。泛型允许在定义类、接口或方法时使用参数化类型,即类型参数作为变量。这使得程序员可以编写更加灵活和可重用的代码。
具体来说,泛型定义是指在定义类、接口或方法时,可以使用类型参数(如 T(Type)、E(Element)、K(Key)、V(Value) 等)来表示一种未知的类型。然后,在创建类的实例或调用方法时,可以指定这个类型参数的具体类型,以统一数据类型。通过使用泛型,可以在编译时检查类型的一致性,避免在运行时出现类型转换异常。
泛型的应用场景主要包括以下几个方面:
- 集合类和数据结构:泛型最常见的用途是在集合类(如 ArrayList、LinkedList、HashMap 等)和数据结构中使用。使用泛型可以创建存储特定类型的元素的集合,并在编译时捕获类型错误。
- 自定义数据结构:使用泛型可以创建自定义的数据结构,以适应不同类型的数据。这有助于编写通用的、可重用的代码,而不必为不同类型的数据编写不同的实现。
- 泛型方法:除了泛型类,Java 还支持泛型方法。泛型方法允许在方法级别使用泛型,这对于那些只需要在特定方法中使用泛型的情况非常有用。
- 接口和抽象类:泛型也可以在接口和抽象类中使用,以创建通用接口和抽象类。这些接口和类可以被不同类型的实现或子类使用。
- 异常处理:泛型可以在异常处理中使用,以创建通用的异常类,以便处理不同类型的异常情况。
泛型确定类型的时机 :
泛型类:创建类时指定泛型,创建对象的时确定具体的类型
泛型方法:
非静态方法:根据类的泛型进行匹配
静态方法:必须申明独立泛型,在调用方法,传入实际参数的时候,确认到具体的类型
泛型接口:
实现类实现接口的时候确定到具体的类型
实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配,等创建对象的时候再确定
泛型通配符:
?
: 任意类型? extends E
: 可以传入的是 E,或者是 E 的子类? super E
: 可以传入的是 E,或者是 E 的父类
注意:泛型只支持引用数据类型
范例:泛型的引出
class Point{
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Point point = new Point();
point.setX(10.2);
point.setY(20.3);
double x = (double) point.getX();
double y = (double) point.getY();
System.out.println(x + "," + y);
}
}
输出:10.2,20.3
public class TestDemo{
public static void main(String[] args) throws Exception{
Point point = new Point();
point.setX("东经100度");
point.setY("北纬20度");
String x = (String) point.getX();
String y = (String) point.getY();
System.out.println(x + "," + y);
}
}
输出:东经100度,北纬20度
public class TestDemo{
public static void main(String[] args) throws Exception{
Point point = new Point();
point.setX(10.2);
point.setY("北纬20度");
String x = (String) point.getX();
String y = (String) point.getY();
System.out.println(x + "," + y);
}
}
输出:
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
at TestDemo.main(TestDemo.java:22)
范例:实现泛型
class Point<T>{
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Point<String> point = new Point<String>();
point.setX("东经100度");
point.setY("北纬20度");
String x = (String) point.getX();
String y = (String) point.getY();
System.out.println(x + "," + y);
}
}
输出:东经100度,北纬20度
方法参数类型的泛型使用通配符?,可以接收任意的泛型类型设置,且方法内部不能修改此泛型类型,只能够输出。
范例:使用通配符接收泛型
class Message<T>{
private T info;
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message<Integer> msg = new Message<Integer>();
msg.setInfo(10);
print(msg);
}
private static void print(Message<?> msg) { //通配符?接收任意泛型
//msg.setInfo("20"); //语法错误,无法修改
System.out.println(msg.getInfo());
}
}
输出:10
在通配符?上又衍生出了两个字符号:
- 设置泛型的上限:? extends 类,如? extends Number,表示只能是Number或者Number的子类Integer等。
- 设置泛型的下限:? super 类,如? super String,表示只能是String或者String的父类(Object)。
范例:设置泛型上限
class Message<T extends Number>{ //上限,只能是Number或者其子类
private T info;
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message<Integer> msg = new Message<Integer>(); //Integer是Number的子类
msg.setInfo(10);
print(msg);
}
private static void print(Message<?> msg) {
System.out.println(msg.getInfo());
}
}
输出:10
范例:设置泛型下限
class Message<T>{
private T info;
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message<String> msg = new Message<String>();
msg.setInfo("Hello World");
print(msg);
}
private static void print(Message<? super String> msg) { //设置下限
System.out.println(msg.getInfo());
}
}
输出:Hello World
范例:泛型接口方式1,在子类上继续定义泛型,同时此泛型在接口上继续使用
interface Message<T>{ //泛型接口
public abstract String echo(T msg);
}
class MessageImpl<T> implements Message<T>{ //子类继续设置泛型
@Override
public String echo(T msg) { //方法上使用泛型
return "ECHO:" + msg;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message<String> msg = new MessageImpl<String>();
System.out.println(msg.echo("Hello World"));
}
}
输出:ECHO:Hello World
范例:泛型接口方式2,在子类上设置具体类型
interface Message<T>{ //泛型接口
public abstract String echo(T msg);
}
class MessageImpl implements Message<String>{ //直接设置好具体的泛型类型
@Override
public String echo(String msg) {
return "ECHO:" + msg;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message<String> msg = new MessageImpl();
System.out.println(msg.echo("Hello World"));
}
}
输出:ECHO:Hello World
泛型除了可以定义在类上,也可以在方法上定义,而在方法上定义泛型时,这个方法不一定非要在泛型类中定义。
范例:泛型方法
public class TestDemo{
public static void main(String[] args) throws Exception{
Integer[] result = get(1,2,3); //调用泛型方法
for (int temp : result) {
System.out.print(temp + ",");
}
}
public static <T> T[] get(T ... args) { //T的类型由方法调用的时候来决定
return args;
}
}
输出:1,2,3,
可以发现,在定义泛型方法时,还要首先定义出泛型名称<T>
,否则这个泛型标记将无法使用。
枚举
使用枚举简化多例设计模式。枚举中的每一个定义的对象,都必须写在枚举类的首行。
如果在枚举类中定义了有参构造方法,则声明每一个枚举对象时都必须明确地调用此构造方法。
范例:定义枚举
enum Color{ //定义枚举类
RED, GREEN, BLUE; //实例化对象
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Color c = Color.RED;
System.out.println(c);
}
}
输出:RED
范例:使用enum父类Enum的方法
enum Color{ //定义枚举类
RED, GREEN, BLUE; //实例化对象
}
public class TestDemo{
public static void main(String[] args) throws Exception{
for(Color c : Color.values()) { //利用value方法取得枚举中的全部对象
System.out.println(c.ordinal() + "," + c.name()); //取得枚举的序号和名称
}
}
}
输出:
0,RED
1,GREEN
2,BLUE
范例:在switch中利用枚举判断
enum Color{ //定义枚举类
RED, GREEN, BLUE; //实例化对象
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Color c = Color.RED;
switch(c) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
范例:在枚举中定义其他结构
enum Color{ //定义枚举类
RED("红色"), GREEN("绿色"), BLUE("蓝色"); //枚举对象,写在首行
private String title;
private Color(String title) { //构造方法,不能是public
this.title = title;
}
@Override
public String toString() {
return this.title;
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Color c = Color.BLUE;
System.out.println(c);
}
}
输出:蓝色
范例:让枚举实现接口
interface Message{
public abstract String getColor();
}
enum Color implements Message{ //实现接口
RED("红色"), GREEN("绿色"), BLUE("蓝色"); //枚举对象,写在首行
private String title;
private Color(String title) { //构造方法,不能是public
this.title = title;
}
@Override
public String toString() {
return this.title;
}
@Override
public String getColor() { //覆写方法
return this.toString();
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Message msg = Color.RED;
System.out.println(msg.getColor());
}
}
输出:红色
范例:在枚举中定义抽象方法,需要每一个枚举对象分别实现抽象方法
enum Color{
RED("红色") {
@Override
public String getColor() {
return this.toString();
}
},
GREEN("绿色") {
@Override
public String getColor() {
return this.toString();
}
},
BLUE("蓝色") {
@Override
public String getColor() {
return this.toString();
}
}; //枚举对象,写在首行
private String title;
private Color(String title) { //构造方法,不能是public
this.title = title;
}
@Override
public String toString() {
return this.title;
}
public abstract String getColor();
}
public class TestDemo{
public static void main(String[] args) throws Exception{
System.out.println(Color.RED.getColor());
}
}
输出:红色
范例:枚举的应用
enum Sex{
MALE("男"),FEMALE("女"); //定义枚举对象
private String title;
private Sex(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
class Person{
private String name;
private int age;
private Sex sex; //定义枚举类型
public Person(String name, int age, Sex sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
public class TestDemo{
public static void main(String[] args) throws Exception{
Person per = new Person("张三",20,Sex.MALE);
System.out.println(per);
}
}
输出:Person [name=张三, age=20, sex=MALE]
注解
Java SE中存在三种注解(Annotation):@Override、@Deprecated和@SupressWarnings
Multithreading
创建多线程
范例:使用 Thread
类实现多线程(存在单继承问题),一般不采用
public class ThreadDemo1 {
/*
开启线程第一种方式: 继承Thread类
1. 编写一个类继承Thread
2. 重写run方法
3. 将线程任务代码写在run方法中
4. 创建线程对象
5. 调用start方法开启线程
细节: 调用start方法开启线程, 会自动的调用run方法执行.
注意: 只有调用了start方法, 才是开启了新的线程
*/
public static void main(String[] args) {
// 4. 创建线程对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
// 5. 调用start方法开启线程
mt1.start();
mt2.start();
}
}
// 1. 编写一个类继承Thread
class MyThread extends Thread {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码写在run方法中
for (int i = 1; i <= 200; i++) {
System.out.println("线程任务执行了" + i);
}
}
}
范例:通过 Runnable
接口实现多线程,用于线程任务无返回值的情况
public class ThreadDemo3 {
/*
开启线程的第二种方式: 实现Runnable接口
1. 编写一个类实现Runnable接口
2. 重写run方法
3. 将线程任务代码写在run方法中
4. 创建线程任务资源
5. 创建线程对象, 将资源传入
6. 使用线程对象调用start方法, 开启线程
*/
public static void main(String[] args) {
// 4. 创建线程任务资源
MyRunnable mr = new MyRunnable();
// 5. 创建线程对象, 将资源传入
Thread t = new Thread(mr);
// 6. 使用线程对象调用start方法, 开启线程
t.start();
for (int i = 1; i <= 2000; i++) {
System.out.println("main线程执行了");
}
}
}
// 1. 编写一个类实现Runnable接口
class MyRunnable implements Runnable {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码写在run方法中
for (int i = 1; i <= 200; i++) {
System.out.println("线程任务执行了" + i);
}
}
}
范例:通过 Callable
接口实现多线程,用于线程任务无返回值的情况
public class ThreadDemo4 {
/*
开启线程的第三种方式: 实现Callable接口
1. 编写一个类实现Callable接口
2. 重写call方法
3. 将线程任务代码写在call方法中
4. 创建线程任务资源对象
5. 创建线程任务对象, 封装线程资源
6. 创建线程对象, 传入线程任务
7. 使用线程对象调用start开启线程
*/
public static void main(String[] args) throws Exception {
// 创建线程任务资源对象
MyCallable mc = new MyCallable();
// 创建线程任务对象, 封装线程资源
FutureTask<Integer> task1 = new FutureTask<>(mc);
FutureTask<Integer> task2 = new FutureTask<>(mc);
// 创建线程对象, 传入线程任务
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
// 使用线程对象调用start开启线程
t1.start();
t2.start();
Integer result1 = task1.get();
Integer result2 = task2.get();
System.out.println("task1获取到的结果为:" + result1);
System.out.println("task2获取到的结果为:" + result2);
}
}
// 1. 编写一个类实现Callable接口,其中泛型类型为call方法的返回值类型
class MyCallable implements Callable<Integer> {
// 2. 重写call方法
@Override
public Integer call() throws Exception {
// 3. 将线程任务代码写在call方法中
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
System.out.println("sum=" + sum);
}
return sum;
}
}
线程方法
Thread
类的常用方法:
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
范例:使用 Thread
类实现多线程时设置和获取线程名称
public class ThreadNameDemo1 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) {
MyThread mt1 = new MyThread("A: ");
MyThread mt2 = new MyThread("B: ");
// mt1.setName("A: ");
// mt2.setName("B: ");
mt1.start();
mt2.start();
}
}
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(super.getName() + "线程任务执行了" + i);
}
}
}
范例:通过 Runnable
接口实现多线程时设置和获取线程名称
public class ThreadNameDemo2 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr, "A: ");
t.start();
for (int i = 1; i <= 2000; i++) {
System.out.println(Thread.currentThread().getName() + "---" + "线程执行了");
}
}
}
class MyRunnable extends Object implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "线程任务执行了" + i);
}
}
}
范例:通过 Callable
接口实现多线程时设置和获取线程名称
public class ThreadNameDemo3 {
/*
线程设置名字和获取名字
Thread类的方法:
public String getName() : 获取线程名字
public void setName() : 设置线程名字
public static Thread currentThread() : 获取当前线程的对象
*/
public static void main(String[] args) throws Exception {
MyCallable mc = new MyCallable();
FutureTask<Integer> task1 = new FutureTask<>(mc);
FutureTask<Integer> task2 = new FutureTask<>(mc);
Thread t1 = new Thread(task1, "线程A: ");
Thread t2 = new Thread(task2, "线程B: ");
t1.start();
t2.start();
Integer result1 = task1.get();
Integer result2 = task2.get();
System.out.println(t1.getName() + "获取到的结果为:" + result1);
System.out.println(t2.getName() + "获取到的结果为:" + result2);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "sum=" + sum);
}
return sum;
}
}
范例:线程休眠
public class ThreadMethodDemo1 {
/*
休眠线程的方法
public static void sleep(long time) : 让线程休眠指定的时间,单位为毫秒
*/
public static void main(String[] args) throws InterruptedException {
for (int i = 5; i >= 1; i--) {
System.out.println("倒计时" + i + "秒");
Thread.sleep(1000);
}
}
}
范例:设置线程优先级,范围为 1 到 10,默认为 5
public class ThreadMethodDemo2 {
/*
线程优先级的方法:
public setPriority(int newPriority) : 设置线程优先级
public final int getPriority() : 获取线程优先级
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}, "线程A: ");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}, "线程B: ");
t1.setPriority(1);
t2.setPriority(10);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.start();
t2.start();
}
}
范例:设置守护线程
public class ThreadMethodDemo3 {
/*
public final void setDaemon(boolean on) : 设置为守护线程
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 20; i++){
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}, "线程A: ");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 200; i++){
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}, "线程B: ");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
线程安全
当多个线程操作同一份共享数据时就会出现线程安全问题。
范例:发现线程的安全问题
public class ThreadTest1 {
/*
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 多条线程共享操作同一份资源
*/
public static void main(String[] args) {
TicketTask task = new TicketTask();
Thread t1 = new Thread(task, "窗口1");
Thread t2 = new Thread(task, "窗口2");
Thread t3 = new Thread(task, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketTask implements Runnable {
private int tickets = 2000;
@Override
public void run() {
while (true) {
if (tickets == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
}
}
}
此时出现了不同步的操作问题。要解决此问题,必须使用同步。所谓的同步是指多个操作在同一时间段内只能有一个线程运行,其他线程要等待此线程完成后才可以继续执行。
方式1:同步代码块。使用 synchronized
关键字定义的代码块就称为同步代码块。在进行同步操作时必须设置同一个要同步对象,而这个对象可以设置为当前对象 this
。
范例:使用同步代码块解决线程安全问题
public class ThreadTest1 {
/*
同步代码块:
synchronized(锁对象) {
多条语句操作共享数据的代码
}
*/
public static void main(String[] args) {
TicketTask task = new TicketTask();
Thread t1 = new Thread(task, "窗口1");
Thread t2 = new Thread(task, "窗口2");
Thread t3 = new Thread(task, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketTask implements Runnable {
private int tickets = 2000;
@Override
public void run() {
while (true) {
synchronized (this) {
if (tickets == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
}
}
}
}
方式二:同步方法,在方法的返回值类型前面加入 synchronized
关键字。
范例:利用同步方法解决线程安全问题
public class ThreadTest4 {
/*
同步方法: 在方法的返回值类型前面加入 synchronized 关键字
public synchronized void method() {
}
同步方法的锁对象:
1. 非静态的方法 : this
2. 静态的方法 : 类的字节码对象
*/
public static void main(String[] args) {
TicketTask4 task = new TicketTask4();
Thread t1 = new Thread(task, "窗口1");
Thread t2 = new Thread(task, "窗口2");
t1.start();
t2.start();
}
}
class TicketTask4 implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (method()) {
break;
}
}
}
private synchronized boolean method() {
if (tickets == 0) {
return true;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
return false;
}
}
方法三:使用互斥锁
范例:使用互斥锁解决线程安全问题
public class ThreadTest6 {
/*
互斥锁 ReentrantLock()
*/
public static void main(String[] args) {
TicketTask6 task = new TicketTask6();
Thread t1 = new Thread(task, "窗口1");
Thread t2 = new Thread(task, "窗口2");
t1.start();
t2.start();
}
}
class TicketTask6 implements Runnable {
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "号票");
tickets--;
} finally {
lock.unlock();
}
}
}
}
死锁是由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。同步嵌套 就会产生死锁。
范例:死锁
public class Deadlock {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(() -> {
while (true) {
synchronized (objA) {
// 线程一
System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上A锁");
synchronized (objB) {
System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上B锁");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (objB) {
// 线程二
System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上B锁");
synchronized (objA) {
System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上A锁");
}
}
}
}).start();
}
}
线程通信
通过线程通信可以确保线程能够按照预定的顺序执行,并且能够安全地访问共享资源,以使多条线程更好的进行协同工作。
成员方法 | 说明 |
---|---|
void wait(); | 使当前线程等待 ,在等待的时候会释放锁对象 |
void notify(); | 随机唤醒单个等待的线程 |
void notifyAll(); | 唤醒所有等待的线程 |
注意:这些方法需要使用锁对象调用。
范例:两条线程通信
public class CorrespondenceDemo1 {
/*
两条线程通信
*/
public static void main(String[] args) {
Printer1 p = new Printer1();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Printer1.class) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Printer1.class) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
class Printer1 {
int flag = 1;
public void print1() throws InterruptedException {
if(flag != 1){
Printer1.class.wait();
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;
Printer1.class.notify();
}
public void print2() throws InterruptedException {
if(flag != 2){
Printer1.class.wait();
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 1;
Printer1.class.notify();
}
}
范例:三条线程通信
public class CorrespondenceDemo2 {
/*
三条线程通信
问题: sleep方法和wait方法的区别?
回答:
sleep方法是线程休眠, 时间到了自动醒来, sleep方法在休眠的时候, 不会释放锁.
wait方法是线程等待, 需要由其它线程进行notify唤醒, wait方法在等待期间, 会释放锁.
*/
public static void main(String[] args) {
Printer2 p = new Printer2();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Printer2.class) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Printer2.class) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (Printer2.class) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
class Printer2 {
int flag = 1;
public void print1() throws InterruptedException {
while (flag != 1) {
// 线程1等待
Printer2.class.wait();
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;
Printer2.class.notifyAll();
}
public void print2() throws InterruptedException {
while (flag != 2) {
// 线程2等待
Printer2.class.wait();
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 3;
Printer2.class.notifyAll();
}
public void print3() throws InterruptedException {
while (flag != 3) {
Printer2.class.wait();
}
System.out.print("传");
System.out.print("智");
System.out.print("大");
System.out.print("学");
System.out.println();
flag = 1;
Printer2.class.notifyAll();
}
}
前面的线程通信效率太低,可以使用 ReentrantLock
实现同步,并获取 Condition
对象,使用以下方法进行等待和唤醒以提高效率:
成员方法 | 说明 |
---|---|
void await(); | 指定线程等待 |
void signal(); | 指定唤醒单个等待的线程 |
public class CorrespondenceDemo3 {
/*
三条线程通信 - 优化
*/
public static void main(String[] args) {
Printer3 p = new Printer3();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
class Printer3 {
ReentrantLock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
int flag = 1;
public void print1() throws InterruptedException {
lock.lock();
if (flag != 1) {
// 线程1等待, c1绑定线程1
c1.await();
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;
c2.signal();
lock.unlock();
}
public void print2() throws InterruptedException {
lock.lock();
if (flag != 2) {
// 线程2等待, c2绑定线程2
c2.await();
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 3;
c3.signal();
lock.unlock();
}
public void print3() throws InterruptedException {
lock.lock();
if (flag != 3) {
c3.await();
}
System.out.print("传");
System.out.print("智");
System.out.print("大");
System.out.print("学");
System.out.println();
flag = 1;
c1.signal();
lock.unlock();
}
}
范例:生产者和消费者问题的基本实现。为了解耦生产者和消费者的关系,通常会采用共享的数据区域 (缓冲区),就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为,消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为。
public class WareHouse {
public static boolean mark = false;
public static ReentrantLock lock = new ReentrantLock();
public static Condition producer = lock.newCondition();
public static Condition consumer = lock.newCondition();
}
public class Producer implements Runnable {
@Override
public void run() {
while (true) {
WareHouse.lock.lock();
if(WareHouse.mark){
// true : 说明有包子, 线程进入等待状态
try {
WareHouse.producer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// false : 没有包子, 生产包子, 改变Mark的状态, 唤醒消费者线程
System.out.println("生产者线程生产包子...");
WareHouse.mark = true;
WareHouse.consumer.signal();
}
WareHouse.lock.unlock();
}
}
}
public class Consumer implements Runnable {
@Override
public void run() {
while (true) {
WareHouse.lock.lock();
if(WareHouse.mark){
// true : 说明有包子, 开吃, 改变mark的状态, 唤醒生产者线程
System.out.println("消费者线程吃包子...");
WareHouse.mark = false;
WareHouse.producer.signal();
} else {
// false : 没有包子, 消费者线程等待
try {
WareHouse.consumer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
WareHouse.lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
}
生命周期
线程对象在不同的时期有不同的状态,线程状态被定义在了 java.lang.Thread.State
枚举类 :
状态 | 具体含义 |
---|---|
NEW(新建) | 创建线程对象 |
RUNNABLE(就绪) | start 方法被调用,但是还没有抢到 CPU 执行权 |
BLOCKED(阻塞) | 线程开始运行,但是没有获取到锁对象 |
WAITING(等待) | wait 方法 |
TIMED_WAITING(计时等待) | sleep 方法 |
TERMINATED(结束状态) | 代码全部运行完毕 |
线程池
Java 中的线程池是一种用于管理线程的技术,它允许应用程序重用固定数量的线程,而不是为每个任务创建一个新的线程。这有助于减少线程创建和销毁的开销,并控制并发线程的数量。
线程资源必需通过线程池提供,不允许在应用中自行显式创建线程。
线程池不允许使用 Executors
去创建,而是通过 ThreadPoolExecutor
的方式,这样的处理方式可以更加明确线程池的运行规则,避免资源耗尽的风险。
public class ThreadPoolDemo2 {
/*
自定义线程池对象
参数5: 任务队列
1) 有界队列 new ArrayBlockingQueue<>(10)
2) 无界队列 new LinkedBlockingDeque<>()
*/
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for(int i = 1; i <= 16; i++){
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了任务");
}
});
}
}
}
ThreadPoolExecutor
参数说明:
- 核心线程数
- 最大线程数
- 空闲线程等待新任务的最长时间
- 时间单位
- 阻塞队列(用于存放待执行的任务)
- 线程工厂(用于创建新线程)
- 拒绝策略(当线程池无法处理新任务时使用的策略)
策略选项 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出 RejectedExecutionException 异常 (默认,推荐) |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法, 绕过线程池直接执行 |
Class Libraries
StringBuffer
String类不适用于频繁修改的字符串操作上,这种情况下可以使用StringBuffer类。String类使用“+”进行数据的连接操作,StringBuffer类使用append()方法进行数据的连接操作。
范例:使用StringBuffer操作,StringBuffer的内容可以改变
public class TestDemo {
public static void main(String[] args) throws Exception {
StringBuffer buf = new StringBuffer();
buf.append("hello ").append("world ");
fun(buf);
System.out.println(buf);
}
private static void fun(StringBuffer buf) {
buf.append("Hello ").append("stone");
}
}
输出:hello world Hello stone
面对字符串的操作,大部分情况下使用String类,小部分情况下考虑StringBuffer类。
范例:使用StringBuffer类的构造方法将String变为StringBuffer
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "hello";
StringBuffer buf = new StringBuffer(str);
System.out.println(buf);
}
}
输出:hello
范例:使用StringBuffer类的append()方法将String变为StringBuffer
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "hello";
StringBuffer buf = new StringBuffer();
buf.append(str);
System.out.println(buf);
}
}
输出:hello
范例:利用StringBuffer的tostring()方法将StringBuffer变为String
public class TestDemo {
public static void main(String[] args) throws Exception {
StringBuffer buf = new StringBuffer();
buf.append("Hello World");
String str = buf.toString();
System.out.println(str);
}
}
输出:Hello World
范例:字符串反转操作,public StringBuffer reverse()
替换指定范围的数据,public StringBuffer replace(int start,int end,String str)
在指定位置插入数据,public StringBuffer insert(int offset,数据类型 变量)
public class TestDemo {
public static void main(String[] args) throws Exception {
StringBuffer buf1 = new StringBuffer();
buf1.append("Hello World");
System.out.println(buf1.reverse());
StringBuffer buf2 = new StringBuffer();
buf2.append("Hello World");
System.out.println(buf2.replace(6, 12, "stone"));
StringBuffer buf3 = new StringBuffer();
buf3.append("World").insert(0, "Hello");
System.out.println(buf3);
}
}
输出:
dlroW olleH
Hello stone
HelloWorld
Runtime
范例:操作Runtime类
public class TestDemo {
public static void main(String[] args) throws Exception {
Runtime run = Runtime.getRuntime();
System.out.println("1.MAX_MEMORY:" + run.maxMemory());
System.out.println("1.TOTAL_MEMORY:" + run.totalMemory());
System.out.println("1.FREE_MEMORY:" + run.freeMemory());
String str = "";
for (int i = 0; i < 30000; i++) { //产生垃圾
str += i;
}
System.out.println("2.MAX_MEMORY:" + run.maxMemory());
System.out.println("2.TOTAL_MEMORY:" + run.totalMemory());
System.out.println("2.FREE_MEMORY:" + run.freeMemory());
run.gc();
System.out.println("3.MAX_MEMORY:" + run.maxMemory());
System.out.println("3.TOTAL_MEMORY:" + run.totalMemory());
System.out.println("3.FREE_MEMORY:" + run.freeMemory());
}
}
输出:
1.MAX_MEMORY:1780482048
1.TOTAL_MEMORY:120586240
1.FREE_MEMORY:118698720
2.MAX_MEMORY:1780482048
2.TOTAL_MEMORY:750256128
2.FREE_MEMORY:588603176
3.MAX_MEMORY:1780482048
3.TOTAL_MEMORY:750256128
3.FREE_MEMORY:736094624
System
范例:使用System类的currentTimeMillis()来统计一个操作所花费的时间
public class TestDemo {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 30000; i++) {
str += i;
}
long end = System.currentTimeMillis();
System.out.println("花费的时间:" + (end - start) + "ms");
}
}
输出:花费的时间:1499ms
可以使用Object类的finalize()方法执行对象回收前的收尾工作:protected void finalize() throws Throwable
范例:覆写finalize()方法
class Person{
public Person() {
System.out.println("Person类的实例化对象产生。");
}
@Override
protected void finalize() throws Throwable {
System.out.println("Person对象被回收。");
throw new Exception("不影响程序。"); //抛异常,程序不会中断
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person();
per = null;
System.gc();
System.out.println("Hello World");
}
}
输出:
Person类的实例化对象产生。
Hello World
Person对象被回收。
日期操作类
日期时间类
范例:取得当前的日期时间
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
System.out.println(date);
}
}
输出:Wed Aug 01 16:10:01 CST 2018
范例:将Date类型数据变为long类型数据
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
long num = date.getTime();
System.out.println(num);
}
}
输出:1533111106396
范例:将long类型数据变为日期型数据
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date(System.currentTimeMillis());
System.out.println(date);
}
}
输出:Wed Aug 01 16:14:14 CST 2018
日期格式化操作类
范例:将日期变为字符串,格式化显示
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String str = sdf.format(date);
System.out.println(str);
}
}
输出:2018-08-01 16:24:44.965
范例:将字符串格式化为日期
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "2018-08-01 16:25:25.555";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date = sdf.parse(str);
System.out.println(date);
}
}
输出:Wed Aug 01 16:25:25 CST 2018
新日期时间类
JDK 8 增加了新的日期时间 API,与之前的 API 对比如下:
JDK 8 之前的日期时间 API | JDK 8 新增的日期时间 API |
---|---|
设计欠妥,使用不方便 | 设计更合理,功能丰富,使用更方便 |
都是可变对象,修改后会丢失最开始的时间信息 | 都是不可变对象,修改后会返回新的时间对象,不会丢失最开始的时间 |
线程不安全 | 线程安全 |
只能精确到毫秒 | 能精确到毫秒,纳秒 |
JDK 8 的日期时间 API 有:
- 日历类:
LocalDate
:年、月、日LocalTime
:时、分、秒LocalDateTime
:年、月、日、时、分、秒
- 日期格式化类:
DateTimeFormatter
:时间的格式化和解析
- 时间类:
Instant
:时间戳/时间线ZoneId
:时区ZonedDateTime
:带时区的时间
- 工具类:
Period
:时间间隔(年、月、日)Duration
:时间间隔(时、分、秒)ChronoUnit
:时间间隔(所有单位)
日历类
日历类有:
LocalDate
:年、月、日LocalTime
:时、分、秒LocalDateTime
:年、月、日、时、分、秒
使用静态方法 now()
获取系统当前时间对应的对象,例如:
LocalDate nowDate = LocalDate.now();
LocalTime nowTime = LocalTime.now();
LocalDateTime nowDateTime = LocalDateTime.now();
使用静态方法 of()
获取指定时间的对象,例如:
LocalDate date = LocalDate.of(2024,04,24);
LocalTime time = LocalTime.of(22,42,30);
LocalDateTime dateTime = LocalDateTime.of(2024,04,24,22,42,30);
LocalDateTime
对象可以使用 toLocalDate()
转换为 LocalDate
对象。
LocalDateTime
对象可以使用 toLocalTime()
转换为 LocalTime
对象。
LocalDateTime nowDateTime = LocalDateTime.now();
LocalDate nowDate = nowDateTime.toLocalDate();
LocalTime nowTime = nowDateTime.toLocalTime();
可以使用 getYear()
,getMonthValue()
,getDayOfMonth()
,getHour()
,getMinute()
,和 getSecond()
方法来分别获取年、月、日、时、分和秒。注意,getMonthValue()
返回的是月份的数字表示(1到12),而 getMonth()
返回的是Month
枚举类型。
LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println(nowDateTime.getYear() + "年");
System.out.println(nowDateTime.getMonth() + "月");
System.out.println(nowDateTime.getMonthValue() + "月");
System.out.println(nowDateTime.getDayOfMonth() + "日");
System.out.println(nowDateTime.getHour() + "时");
System.out.println(nowDateTime.getMinute() + "分");
System.out.println(nowDateTime.getSecond() + "秒");
System.out.println(nowDateTime.getNano() + "纳秒");
System.out.println("dayOfYear:" + nowDateTime.getDayOfYear());
System.out.println("星期" + nowDateTime.getDayOfWeek());
System.out.println("星期" + nowDateTime.getDayOfWeek().getValue());
修改日期和时间的方法:
方法名 | 说明 |
---|---|
withYear(年), withMonth(月), withDayOfMonth(日), withHour(时), withMinute(分), withSecond(秒), withNano(纳秒) | 修改时间,返回新的时间对象 |
plusYears(年), plusMonths(月), plusDays(日), plusWeeks(周), plusHours(时), plusMinutes(分), plusSeconds(秒), plusNanos(纳秒) | 增加时间,返回新的时间对象 |
minusYears(年), minusMonths(月), minusDays(日), minusWeeks(周), minusHours(时), minusMinutes(分), minusSeconds(秒), minusNanos(纳秒) | 减少时间,返回新的时间对象 |
equals, isBefore, isAfter | 比较 2 个时间对象 |
范例:修改日期和时间
public class UpdateTimeDemo {
public static void main(String[] args) {
LocalDateTime nowTime = LocalDateTime.now();
// 当前时间
System.out.println(nowTime);
// minus : 减去
// minusYears(年), minusMonths(月), minusDays(日), minusWeeks(周), minusHours(时), minusMinutes(分), minusSeconds(秒), minusNanos(纳秒)
System.out.println("减一小时:" + nowTime.minusHours(1));
System.out.println("减一分钟:" +nowTime.minusMinutes(1));
System.out.println("减一秒钟:" +nowTime.minusSeconds(1));
System.out.println("减一纳秒:" +nowTime.minusNanos(1));
System.out.println("对比时间, 确定方法返回的都是新的实例 >>>>>> " +nowTime);
System.out.println("----------------");
// plus : 加
// plusYears(年), plusMonths(月), plusDays(日), plusWeeks(周), plusHours(时), plusMinutes(分), plusSeconds(秒), plusNanos(纳秒)
System.out.println("加一小时:" + nowTime.plusHours(1));
System.out.println("加一分钟:" + nowTime.plusMinutes(1));
System.out.println("加一秒钟:" + nowTime.plusSeconds(1));
System.out.println("加一纳秒:" + nowTime.plusNanos(1));
System.out.println("---------------");
// with : 这里体现出的是,设置效果
System.out.println("修改的效果:");
//withYear(年), withMonth(月), withDayOfMonth(日), withHour(时), withMinute(分), withSecond(秒), withNano(纳秒)
System.out.println(nowTime.withYear(2008));
System.out.println(nowTime.withMonth(8));
System.out.println(nowTime.withDayOfMonth(8));
System.out.println(nowTime.withHour(8));
System.out.println(nowTime.withMinute(8));
System.out.println(nowTime.withSecond(8));
System.out.println(nowTime.withNano(8));
System.out.println("---------------");
LocalDate myDate = LocalDate.of(2008, 8, 8);
LocalDate nowDate = LocalDate.now();
//2008-08-08是否在nowDate之前?
System.out.println(myDate + "是否在" + nowDate + "之前? " + myDate.isBefore(nowDate));
//2008-08-08是否在nowDate之后?
System.out.println(myDate + "是否在" + nowDate + "之后? " + myDate.isAfter(nowDate));
System.out.println("---------------------------");
// 判断两个时间是否相同
System.out.println(myDate.equals(nowDate));
}
}
日期格式化类
日期格式化类有:
DateTimeFormatter
:时间的格式化和解析
范例:日期格式化
public class DateTimeFormatterDemo {
/*
用于时间的格式化和解析:
1. 对象的获取 :
static DateTimeFormatter ofPattern(格式) : 获取格式对象
2. 格式化 :
String format(时间对象) : 按照指定方式格式化
3. 解析 :
LocalDateTime.parse("解析字符串", 格式化对象);
LocalDate.parse("解析字符串", 格式化对象);
LocalTime.parse("解析字符串", 格式化对象);
*/
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println("格式化之前:" + now);
// 获取格式化对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年M月d日");
// 格式化
String result = formatter.format(now);
System.out.println("格式化之后:" + result);
// 解析
String time = "2008年8月8日";
LocalDate parse = LocalDate.parse(time,formatter);
System.out.println(parse);
}
}
时间类
时间类有:
Instant
:时间戳/时间线ZoneId
:时区ZonedDateTime
:带时区的时间
范例:获取当前时间
public class InstantDemo1 {
/*
Instant类 : 用来表示时间的对象,类似于之前学的Date
*/
public static void main(String[] args) {
Instant now = Instant.now();
System.out.println("当前的时间戳是:" + now);
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime);
}
}
范例:获取时区信息
public class ZoneIdDemo {
/*
ZoneId类 : 时区类
常见方法 :
1. static Set<String> getAvailableZoneIds() : 获取Java中支持的所有时区
2. static ZoneId systemDefault() : 获取系统默认时区
3. static ZoneId of(String zoneId) : 获取一个指定时区
*/
public static void main(String[] args) {
// 获取Java中支持的所有时区
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);
System.out.println(set.size());
System.out.println("-----------------------------------");
// 获取系统默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
System.out.println("-----------------------------------");
// 获取一个指定时区
ZoneId of = ZoneId.of("Africa/Nairobi");
System.out.println(of);
ZonedDateTime zonedDateTime = LocalDateTime.now().atZone(of);
System.out.println(zonedDateTime);
}
}
范例:带时区的时间对象的操作
public class ZoneDateTimeDemo {
/*
ZoneDataTime 带时区的时间对象 :
static ZonedDateTime now() : 获取当前时间的ZonedDateTime对象
static ZonedDateTime ofXxxx(...) : 获取指定时间的ZonedDateTime对象
ZonedDateTime withXxx(时间) : 修改时间系列的方法
ZonedDateTime minusXxx(时间) : 减少时间系列的方法
ZonedDateTime plusXxx(时间) : 增加时间系列的方法
*/
public static void main(String[] args) {
// 获取当前时间的ZonedDateTime对象
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
System.out.println("--------------------------");
// 获取指定时间的ZonedDateTime对象
ZonedDateTime of = ZonedDateTime.of
(2008, 8, 8, 8, 8, 8, 8,
ZoneId.systemDefault());
System.out.println(of);
System.out.println("--------------------------");
// 修改时间系列的方法
System.out.println(now.withYear(2008));
System.out.println(now.withMonth(8));
System.out.println(now.withDayOfMonth(8));
System.out.println("--------------------------");
// 减少时间系列的方法
System.out.println(now.minusYears(1));
System.out.println(now.minusMonths(1));
System.out.println(now.minusDays(1));
System.out.println("--------------------------");
// 增加时间系列的方法
System.out.println(now.plusYears(1));
System.out.println(now.plusMonths(1));
System.out.println(now.plusDays(1));
}
}
工具类
工具类有:
Period
:时间间隔(年、月、日)Duration
:时间间隔(时、分、秒)ChronoUnit
:时间间隔(所有单位)
范例:计算年月日时间间隔
/**
* Period 计算日期间隔 (年月日)
*/
public class PeriodDemo {
public static void main(String[] args) {
// 此刻年月日
LocalDate today = LocalDate.now();
System.out.println(today);
// 昨天年月日
LocalDate otherDate = LocalDate.of(2024, 4, 24);
System.out.println(otherDate);
//Period对象表示时间的间隔对象
Period period = Period.between(today, otherDate); // 第二个参数减第一个参数
System.out.println(period.getYears()); // 间隔多少年
System.out.println(period.getMonths()); // 间隔的月份
System.out.println(period.getDays()); // 间隔的天数
System.out.println(period.toTotalMonths()); // 总月份
}
}
范例:计算时分秒时间间隔
/**
* Duration 计算日期间隔 (时分秒)
*/
public class DurationDemo {
public static void main(String[] args) {
// 此刻日期时间对象
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 昨天的日期时间对象
LocalDateTime otherDate = LocalDateTime.of(2024, 4, 24, 0, 0, 0);
System.out.println(otherDate);
Duration duration = Duration.between(otherDate, today); // 第二个参数减第一个参数
System.out.println(duration.toDays()); // 两个时间差的天数
System.out.println(duration.toHours()); // 两个时间差的小时数
System.out.println(duration.toMinutes()); // 两个时间差的分钟数
System.out.println(duration.toMillis()); // 两个时间差的毫秒数
System.out.println(duration.toNanos()); // 两个时间差的纳秒数
}
}
范例:计算时间间隔
/**
* ChronoUnit 可用于在单个时间单位内测量一段时间,这个工具类是最全的了,可以用于比较所有的时间单位
*/
public class ChronoUnitDemo {
public static void main(String[] args) {
// 本地日期时间对象:此刻的
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 生日时间
LocalDateTime birthDate = LocalDateTime.of(2023, 2, 4,
0, 0, 0);
System.out.println(birthDate);
System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
}
}
随机数
范例:产生10个0-100之间的正整数
import java.util.Random;
public class TestDemo {
public static void main(String[] args) throws Exception {
Random rand = new Random();
for (int i = 0; i < 10; i++) {
System.out.print(rand.nextInt(101) + ",");
}
}
}
输出:67,90,11,23,85,12,45,33,76,61,
数学公式类
范例:观察四舍五入操作
public class TestDemo {
public static void main(String[] args) throws Exception {
System.out.println(Math.round(15.5));
System.out.println(Math.round(15.51));
System.out.println(Math.round(15.6));
System.out.println(Math.round(-15.5));
System.out.println(Math.round(-15.51));
System.out.println(Math.round(-15.6));
}
}
输出:
16
16
16
-15
-16
-16
大数字操作类
大整数操作类
范例:进行BigInteger的演示
import java.math.BigInteger;
public class TestDemo {
public static void main(String[] args) throws Exception {
BigInteger bigA = new BigInteger("11111111111111111111");
BigInteger bigB = new BigInteger("22222222222222222222");
System.out.println(bigB.add(bigA));
System.out.println(bigB.subtract(bigA));
System.out.println(bigB.multiply(bigA));
System.out.println(bigB.divide(bigA));
System.out.println(bigB.divideAndRemainder(bigA)[0] + "," + bigB.divideAndRemainder(bigA)[1]);
}
}
输出:
33333333333333333333
11111111111111111111
246913580246913580241975308641975308642
2
2,0
大小数操作类
范例:使用BigDecimal完成准确的四舍五入操作
import java.math.BigDecimal;
class MyMath{
public static double round(double num,int scale) {
BigDecimal big = new BigDecimal(num);
BigDecimal result = big.divide(new BigDecimal(1), scale, BigDecimal.ROUND_HALF_UP);
return result.doubleValue();
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
System.out.println(MyMath.round(15.5, 0));
System.out.println(MyMath.round(-15.5, 0));
}
}
输出:
16.0
-16.0
数组操作类
范例:Arrays类的操作方法
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) throws Exception {
int[] data1 = new int[] {1,2,3};
int[] data2 = new int[] {1,2,3};
System.out.println(Arrays.equals(data1, data2));
Arrays.fill(data1, 3);
System.out.println(Arrays.toString(data1));
}
}
输出:
true
[3, 3, 3]
比较器
常用比较器:Comparable
如果要为对象指定比较规则,那么对象所在的类必须实现Comparable接口。
范例:实现比较器
import java.util.Arrays;
class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
if(this.age > o.age) {
return 1;
}else if (this.age < o.age) {
return -1;
}else {
return 0;
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person[] per = new Person[] {new Person("张三",20), new Person("李四",19), new Person("王五",21)};
Arrays.sort(per);
System.out.println(Arrays.toString(per));
}
}
输出:[Person [name=李四, age=19], Person [name=张三, age=20], Person [name=王五, age=21]]
范例:使用Comparator接口对已经定义好的类进行排序。
import java.util.Arrays;
import java.util.Comparator;
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
class PersonComparator implements Comparator<Person>{ //定义比较规则
@Override
public int compare(Person o1, Person o2) {
if(o1.getAge() > o2.getAge()) {
return 1;
}else if (o1.getAge() < o2.getAge()) {
return -1;
}else {
return 0;
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person[] per = new Person[] {new Person("张三",20),new Person("李四",19),new Person("王五",21)};
Arrays.sort(per, new PersonComparator());
System.out.println(Arrays.toString(per));
}
}
输出:[Person [name=李四, age=19], Person [name=张三, age=20], Person [name=王五, age=21]]
对象克隆
克隆方法:protected Object clone() throws CloneNotSupportedException
要克隆对象的类必须实现cloneable接口,这个接口没有方法,属于标识接口,只表示一种能力。
范例:实现对象克隆操作
class Person implements Cloneable{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per1 = new Person("张三", 20);
Person per2 = (Person) per1.clone();
per2.setName("王五");
System.out.println(per1);
System.out.println(per2);
}
}
输出:
Person [name=张三, age=20]
Person [name=王五, age=20]
克隆后的对象各占有各自的堆内存空间,互不影响。
正则表达式
范例:使用正则表达式验证字符串是否全部由数字组成
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "123";
if(str.matches("\\d+")) {
System.out.println("全部是数字");
}else {
System.out.println("不全部是数字");
}
}
}
输出:全部是数字
范例:将字符串中的字母消除
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "123abc456def";
String regex = "[a-zA-Z]+";
System.out.println(str.replaceAll(regex, ""));
System.out.println(str.replaceFirst(regex, ""));
}
}
输出:
123456
123456def
范例:字符串按照数字拆分
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "a1bb2cccc3dddd4";
String regex = "\\d";
String[] result = str.split(regex);
for (String string : result) {
System.out.print(string + ",");
}
}
}
输出:a,bb,cccc,dddd,
范例:验证一个字符串是否是整型,如果是将其变为int型数据,而后执行乘法操作
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "12";
String regex = "\\d+";
if (str.matches(regex)) {
int data = Integer.parseInt(str);
System.out.println(data * data);
} else {
System.out.println("字符串不是数字所组成");
}
}
}
输出:144
范例:验证一个字符串是否是小数,如果是将其变为double型数据,而后执行乘法操作
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "12.1";
String regex = "\\d+(\\.\\d+)?";
if (str.matches(regex)) {
double data = Double.parseDouble(str);
System.out.println(data * data);
} else {
System.out.println("字符串不是数字所组成");
}
}
}
输出:146.41
范例:输入一个字符串,按照“年-月-日 时:分:秒”形式,如果正确,将其变为Date类型
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "2018-08-02 22:25:00.100";
String regex = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}";
if (str.matches(regex)) {
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(str);
System.out.println(date);
} else {
System.out.println("字符串不是数字所组成");
}
}
}
输出:Thu Aug 02 22:25:00 CST 2018
范例:一个用户名只能由字母、数字、下划线组成,长度只能是6~15位
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "123abc_";
String regex = "\\w{6,15}";
if (str.matches(regex)) {
System.out.println("用户名合法");
} else {
System.out.println("用户名非法");
}
}
}
输出:用户名合法
范例:验证电话号码
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "010-12345678";
String regex = "((\\d{3,4}|\\(\\d{3,4}\\))-?)?\\d{7,8}";
if (str.matches(regex)) {
System.out.println("电话号码输入合法");
} else {
System.out.println("电话号码输入非法");
}
}
}
输出:电话号码输入合法
范例:验证Email,用户名由字母、数字、下划线和点组成,不能以数字和点开头。
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "stone@163.com";
String regex = "[a-zA-Z_][a-zA-Z_0-9\\.]*@[a-zA-Z_0-9\\.]+\\.(com|cn|net)";
if (str.matches(regex)) {
System.out.println("Email输入合法");
} else {
System.out.println("Email输入非法");
}
}
}
输出:Email输入合法
范例:判断字符串组成:姓名:年龄:成绩|姓名:年龄:成绩|姓名:年龄:成绩|…,姓名只能是字母,年龄只能是数字,成绩可能有小数
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "SMITH:19:90.1|ALLEN:18:90.1|kING:20:95.2";
String regex = "([a-zA-Z]+:\\d{1,3}:\\d{1,3}(\\.\\d{1,2})?\\|)+"
+ "([a-zA-Z]+:\\d{1,3}:\\d{1,3}(\\.\\d{1,2})?)?";
if (str.matches(regex)) {
System.out.println("输入合法");
} else {
System.out.println("输入非法");
}
}
}
输出:输入合法
范例:对上面的数据按成绩和年龄排序
import java.util.Arrays;
class Student implements Comparable<Student>{
private String name;
private int age;
private double score;
public Student() {
super();
}
public Student(String name, int age, double score) {
super();
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
@Override
public int compareTo(Student o) {
if(this.score > o.score) {
return -1;
}else if (this.score < o.score) {
return 1;
}else {
if(this.age > o.age) {
return 1;
}else if (this.age < o.age) {
return -1;
}else {
return 0;
}
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "SMITH:19:90.1|ALLEN:18:90.1|kING:20:95.2";
String regex = "([a-zA-Z]+:\\d{1,3}:\\d{1,3}(\\.\\d{1,2})?\\|)+"
+ "([a-zA-Z]+:\\d{1,3}:\\d{1,3}(\\.\\d{1,2})?)?";
if (str.matches(regex)) {
String[] result = str.split("\\|");
Student[] stu = new Student[result.length];
for (int i = 0; i < result.length; i++) {
String[] temp = result[i].split(":");
stu[i] = new Student(temp[0],Integer.parseInt(temp[1]),Double.parseDouble(temp[2]));
}
Arrays.sort(stu);
System.out.println(Arrays.toString(stu));
} else {
System.out.println("输入非法");
}
}
}
输出:[Student [name=kING, age=20, score=95.2], Student [name=ALLEN, age=18, score=90.1], Student [name=SMITH, age=19, score=90.1]]
反射机制
范例:认识反射
package cn.stone.demo;
class Person{
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person(); //正向操作,实例化对象
System.out.println(per.getClass().getName()); //反向操作,找到类
}
}
输出:cn.stone.demo.Person
范例:取得Class类实例化对象方式一,通过Object类的getClass()方法(基本不用)
package cn.stone.demo;
class Person{
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Person per = new Person(); //正向操作,实例化对象
Class<?> cls = per.getClass();
System.out.println(cls.getName());
}
}
输出:cn.stone.demo.Person
范例:取得Class类实例化对象方式二,使用“类.class”取得
package cn.stone.demo;
class Person{
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class;
System.out.println(cls.getName());
}
}
输出:cn.stone.demo.Person
范例:取得Class类实例化对象方式三,使用Class类内部定义的一个static方法forName()(主要使用)
package cn.stone.demo;
class Person{
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person");
System.out.println(cls.getName());
}
}
输出:cn.stone.demo.Person
范例:通过反射实例化对象
package cn.stone.demo;
class Person{
@Override
public String toString() {
return "Person Class Instance";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类对象
Object obj = cls.newInstance(); //实例化对象,和使用关键字new一样
Person per = (Person) obj; //向下转型
System.out.println(per); //调用toString()方法
}
}
输出:Person Class Instance
之前的工厂设计模式有一个最大的问题:接口的子类增加了,工厂类肯定需要修改,造成这个问题的原因是new,使用反射的方式修改工厂类。
范例:通过反射机制实现工厂设计模式
接口:
package cn.stone.factory;
public interface Fruit {
public abstract void eat();
}
实现类:
package cn.stone.factory;
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("eat apple");
}
}
工厂类:
package cn.stone.factory;
public class FruitFactory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
测试类:
package cn.stone.factory;
public class FruitDemo {
public static void main(String[] args) {
Fruit f = FruitFactory.getInstance("cn.stone.factory.Apple");
f.eat();
}
}
输出:eat apple
增加实现类:
package cn.stone.factory;
public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("eat orange");
}
}
修改测试类:
package cn.stone.factory;
public class FruitDemo {
public static void main(String[] args) {
Fruit f = FruitFactory.getInstance("cn.stone.factory.Orange");
f.eat();
}
}
输出:eat orange
范例:取得一个类的全部构造
package cn.stone.demo;
import java.lang.reflect.Constructor;
class Person{
public Person(){}
public Person(String name) {}
public Person(String name,int age) {}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
Constructor<?>[] cons = cls.getConstructors(); //取得全部构造
for (int i = 0; i < cons.length; i++) {
System.out.println(cons[i]);
}
}
}
输出:
public cn.stone.demo.Person()
public cn.stone.demo.Person(java.lang.String)
public cn.stone.demo.Person(java.lang.String,int)
如果一个类中没有无参构造,则在反射过程中使用cls.newInstance()时会抛出InstantiationException异常,故要么为类加上无参构造,要么先取得类的构造,然后使用Constructor类的newInstance()方法实例化对象。
范例:通过反射调用指定参数的构造实例化对象
package cn.stone.demo;
import java.lang.reflect.Constructor;
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
//取得指定参数类型的构造方法,传递两个参数类型的Class对象
Constructor<?> cons = cls.getConstructor(String.class,int.class);
Object obj = cons.newInstance("张三",20); //为构造方法传递参数
System.out.println(obj);
}
}
输出:Person [name=张三, age=20]
范例:取得类的全部普通方法
package cn.stone.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
Method[] met = cls.getMethods();
for (int i = 0; i < met.length; i++) {
System.out.println(met[i]);
}
}
}
输出:
public java.lang.String cn.stone.demo.Person.getName()
public void cn.stone.demo.Person.setName(java.lang.String)
public int cn.stone.demo.Person.getAge()
public void cn.stone.demo.Person.setAge(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
范例:利用反射调用Person类的setName(),getName()方法
package cn.stone.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class Person{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
Object obj = cls.newInstance(); //实例化对象
String attribute = "name"; //调用属性名称
//分别取得Person类的setName()方法和getName()方法的Method对象
Method setMet = cls.getMethod("set" + initcap(attribute),String.class);
Method getMet = cls.getMethod("get" + initcap(attribute));
setMet.invoke(obj, "张三"); //等价于Person对象.setName("张三")
System.out.println(getMet.invoke(obj)); //等价于Person对象.getName()
}
private static String initcap(String str) { //首字母大写方法
return str.substring(0, 1).toUpperCase().concat(str.substring(1));
}
}
输出:张三
范例:取得类的全部属性
package cn.stone.demo;
import java.lang.reflect.Field;
class Person{
private String name;
private int age;
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
Field[] f = cls.getDeclaredFields();
for (int i = 0; i < f.length; i++) {
System.out.println(f[i]);
}
}
}
输出:
private java.lang.String cn.stone.demo.Person.name
private int cn.stone.demo.Person.age
如果要调用Field类的set()和get()方法,必须先使用setAccessible()方法,让属性对外可见(解除封装)。
范例:利用反射操作类中属性
package cn.stone.demo;
import java.lang.reflect.Field;
class Person{
private String name;
private int age;
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.stone.demo.Person"); //取得Class类实例化对象
Object obj = cls.newInstance(); //对象实例化
Field nameField = cls.getDeclaredField("name"); //找到name属性
Field ageField = cls.getDeclaredField("age"); //找到age属性
nameField.setAccessible(true); //解除封装
ageField.setAccessible(true); //解除封装
nameField.set(obj, "张三");
ageField.set(obj, 20);
System.out.println(nameField.get(obj) + "," + ageField.get(obj));
}
}
输出:张三,20
不建议使用上面的操作进行属性的设置,所有的属性应该通过setter()和getter()方法设置和取得。
File
Java 中的 File
类是一个用于文件和目录路径名的抽象表示形式的类。它提供了许多用于文件和目录操作的方法,但并不直接涉及文件内容的读取或写入。File
类主要用于文件系统的操作,如创建、删除、重命名文件和目录,检查文件或目录是否存在,以及获取文件或目录的路径信息等。
以下是一些 File
类的主要特性和方法:
主要特性:
- 提供了对文件和目录路径名的抽象表示。
- 提供了对文件和目录创建、删除、重命名等操作的支持。
- 提供了对文件和目录属性的访问,如长度、读写权限等。
- 提供了列出目录内容的方法。
主要方法:
- 构造函数:
File(String pathname)
,File(String parent, String child)
,File(File parent, String child)
等,用于创建File
对象。 - 路径名操作:
getAbsolutePath()
,getPath()
,getName()
,getParent()
,getParentFile()
等,用于获取文件的绝对路径、相对路径、文件名、父目录路径或父目录的File
对象。 - 文件或目录检查:
exists()
,isDirectory()
,isFile()
,isHidden()
,canRead()
,canWrite()
,canExecute()
等,用于检查文件或目录是否存在、是否为目录、是否为隐藏文件、是否具有读/写/执行权限等。 - 文件或目录创建:
createNewFile()
,mkdir()
,mkdirs()
等,用于创建新文件或目录。 - 文件或目录删除:
delete()
,用于删除文件或目录。 - 文件或目录重命名:
renameTo(File dest)
,用于将文件或目录重命名为指定名称。 - 文件长度:
length()
,用于获取文件的大小(以字节为单位)。 - 目录内容列表:
listFiles()
,list()
,listFiles(FileFilter filter)
,listFiles(FilenameFilter filter)
等,用于列出目录中的文件和子目录。
范例:创建 File 对象关联文件或文件夹
public class FileDemo1 {
/*
File类介绍 : 文件或文件夹对象
构造方法 :
1. public File (String pathname) : 根据传入的字符串路径封装 File 对象
2. public File (String parent, String child) : 根据传入的父级路径和子级路径来封装 File 对象
3. public File (File parent, String child) : 根据传入的父级路径(File类型)和子级路径来封装 File 对象
*/
public static void main(String[] args) throws IOException {
File f1 = new File("D:\\A.txt");
f1.createNewFile();
File f2 = new File("D:\\test");
System.out.println(f2.exists());
File f3 = new File("D:\\", "test");
System.out.println(f3.exists());
File f4 = new File(new File("D:\\"), "test");
System.out.println(f4.exists());
}
}
范例:绝对路径和相对路径
public class FileDemo2 {
/*
路径的写法 :
1. 绝对路径: 从盘符根目录开始,一直到某个具体的文件或文件夹
2. 相对路径: 相对于当前项目
*/
public static void main(String[] args) throws IOException {
File f = new File("src/cn/iamdt/day12\\A.txt");
f.createNewFile();
}
}
范例:File 类常用方法
public class FileMethodDemo3 {
/*
File 类常见方法 :
1. 判断相关
public boolean isDirectory() : 判断是否是文件夹
public boolean isFile() : 判断是否是文件
public boolean exists() : 判断是否存在
2. 获取相关
public long length() : 返回文件的大小(字节数量)
* 文件对象操作, 返回正确的字节个数
* 文件夹对象操作, 返回的是错误的字节个数
public String getAbsolutePath() : 返回文件的绝对路径
public String getPath() : 返回定义文件时使用的路径
public String getName() : 返回文件的名称,带后缀
public long lastModified() : 返回文件的最后修改时间(时间毫秒值)
*/
public static void main(String[] args) {
File f1 = new File("D:\\A.txt");
System.out.println(f1.isDirectory()); // false
System.out.println(f1.isFile()); // true
System.out.println(f1.exists()); // true
System.out.println("----------------------");
File f2 = new File("D:\\test");
System.out.println(f1.length());
System.out.println(f2.length());
System.out.println("----------------------");
File f3 = new File("A.txt");
System.out.println(f3.getAbsolutePath());
System.out.println("----------------------");
System.out.println(f1.getName());
System.out.println(f2.getName());
System.out.println(f3.getName());
System.out.println("----------------------");
long time = f1.lastModified();
System.out.println(new Date(time));
}
}
范例:文件的创建和删除
public class FileMethodDemo4 {
/*
File类的创建方法和删除方法 :
public boolean createNewFile() :创建文件
public boolean mkdir() : 创建单级文件夹
public boolean mkdirs() : 创建多级文件夹
public boolean delete() : 删除文件或文件夹
- delete 方法删除文件夹, 只能删除空的文件夹.
*/
public static void main(String[] args) throws IOException {
File f1 = new File("src\\com\\itheima\\day12\\B.txt");
System.out.println(f1.createNewFile());
File f2 = new File("src\\com\\itheima\\day12\\aaa");
System.out.println(f2.mkdirs());
File f3 = new File("src\\com\\itheima\\day12\\C.txt");
System.out.println(f3.mkdirs());
System.out.println(f1.delete());
System.out.println(f2.delete());
}
}
范例:文件的遍历
public class FileMethodDemo5 {
/*
File类的遍历方法 :
public File[] listFiles() 获取当前目录下所有的 "一级文件对象" 返回 File 数组,有可能会返回 Null,需要先做非空判断
*/
public static void main(String[] args) {
File f = new File("D:\\test");
File[] files = f.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
范例:递归获取指定文件
public class FileTest2 {
/*
需求:找出指定文件夹下所有的 .java 文件
*/
public static void main(String[] args) {
File dir = new File("D:\\test");
printJavaFiles(dir);
}
public static void printJavaFiles(File dir) {
for (File file : dir.listFiles()) {
if (file.isFile()) {
if (file.getName().endsWith(".java")) {
System.out.println(file);
}
} else {
if (file.listFiles() != null)
printJavaFiles(file);
}
}
}
private static void method(File dir) {
// 获取当前文件夹下所有的文件对象
File[] files = dir.listFiles();
// 遍历files
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".java")) {
System.out.println(file);
}
}
}
}
范例:删除目录及目录下的文件
public class FileTest3 {
/*
需求:设计一个方法,删除文件夹 (delete() 只能删除空文件夹)
*/
public static void main(String[] args) {
deleteDir(new File("E:\\test2222"));
}
public static void deleteDir(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
file.delete();
} else {
if (file.listFiles() != null) {
deleteDir(file);
}
}
}
dir.delete();
}
}
范例:统计文件夹大小
public class FileTest4 {
/*
需求:键盘录入一个文件夹路径,统计文件夹的大小
*/
public static void main(String[] args) {
File dir = new File("D:\\test");
long dirLength = getDirLength(dir);
System.out.println(dirLength);
}
public static long getDirLength(File dir) {
long sum = 0;
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
sum += file.length();
} else {
if (file.listFiles() != null) {
sum += getDirLength(file);
}
}
}
return sum;
}
}
范例:统计文件夹下的文件
public class FileTest5 {
/*
需求:键盘录入一个文件夹路径,统计文件夹中每种文件的个数并打印(考虑子文件夹)
打印格式如下:
txt:3个
doc:4个
jpg:6个
*/
static HashMap<String, Integer> hm = new HashMap<>();
static int count;
public static void main(String[] args) {
File dir = new File("D:\\test");
getCount(dir);
hm.forEach((type, count) -> System.out.println(type + ":" + count + "个"));
System.out.println("无后缀名文件:" + count + "个");
}
public static void getCount(File dir) {
// 获取文件对象
File[] files = dir.listFiles();
// 遍历文件对象
for (File file : files) {
// 判断文件对象是否为文件
if (file.isFile()) {
// 执行计数器代码
// 判断文件是否有后缀
if (!file.getName().contains(".")) {
count++;
} else {
// 文件有后缀,对后缀进行切割操作
String[] split = file.getName().split("\\.");
String type = split[split.length - 1];
// 判断文件类型是否存在于HashMap中
if (hm.containsKey(type)) {
// 当文件类型存在时执行代码逻辑
hm.put(type, hm.get(type) + 1);
} else {
// 当文件类型不存在时执行代码逻辑
hm.put(type, 1);
}
}
} else {
// 确保有权限调用listFiles方法
if (file.listFiles() != null) {
// 递归调用代码,统计子文件夹
getCount(file);
}
}
}
}
}
范例:修改文件分隔符
package cn.stone.demo;
import java.io.File;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo.txt");
if(file.exists()) {
file.delete();
}else {
file.createNewFile();
}
}
}
范例:创建带目录的文件
package cn.stone.demo;
import java.io.File;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "test" + File.separator + "demo.txt");
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (file.exists()) {
file.delete();
} else {
file.createNewFile();
}
}
}
范例:取得文件信息
package cn.stone.demo;
import java.io.File;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "software" + File.separator + "rhel-server-7.4-x86_64-dvd.iso");
if(file.exists()) {
System.out.println("文件名称:" + file.getName());
System.out.println(file.getName() + (file.isDirectory()?"是目录":"不是目录"));
System.out.println(file.getName() + (file.isFile()?"是文件":"不是文件"));
System.out.println(file.getName() + (file.isHidden()?"是隐藏文件":"不是隐藏文件"));
System.out.println("最后一次更改时间" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified())));
System.out.println("文件大小" + new BigDecimal(file.length() / (double) 1024 / 1024).divide(new BigDecimal(1),2,BigDecimal.ROUND_HALF_UP).doubleValue() + "M。");
}
}
}
输出:
文件名称:rhel-server-7.4-x86_64-dvd.iso
rhel-server-7.4-x86_64-dvd.iso不是目录
rhel-server-7.4-x86_64-dvd.iso是文件
rhel-server-7.4-x86_64-dvd.iso不是隐藏文件
最后一次更改时间2018-03-21 21:04:34
文件大小3871.0M。
范例:为一个文件重命名
package cn.stone.demo;
import java.io.File;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo.txt");
if(file.exists()) {
File newFile = new File("D:" + File.separator + "code" + File.separator + "demo1.txt");
file.renameTo(newFile);
}else {
file.createNewFile();
}
}
}
范例:列出指定的目录内容
package cn.stone.demo;
import java.io.File;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code");
if(file.exists()) {
File[] result = file.listFiles();
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
}
}
输出:
D:\code\.metadata
D:\code\.recommenders
D:\code\demo1.txt
D:\code\HelloWorld
范例:递归列出指定目录下的全部内容
package cn.stone.demo;
import java.io.File;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code");
print(file);
}
private static void print(File file) {
if(file.isDirectory()) {
File[] result = file.listFiles();
if(result != null) {
for (int i = 0; i < result.length; i++) {
print(result[i]);
}
}
}
System.out.println(file);
}
}
IO Stream
Java 的 IO 流体系结构是非常庞大和复杂的,但基本上可以划分为四个主要的类别:基于字节的流(InputStream
和 OutputStream
)、基于字符的流(Reader
和 Writer
)、缓冲流(BufferedInputStream
, BufferedOutputStream
, BufferedReader
, BufferedWriter
等)以及特定用途的流(如 FileInputStream
, FileOutputStream
, FileReader
, FileWriter
等)。
以下是 Java IO 流体系结构的一个简化概述:
- 基于字节的流,不是纯文本文件都使用字节流
InputStream
:所有字节输入流的超类。FileInputStream
:用于从文件读取字节。BufferedInputStream
:为其他输入流添加缓冲功能。ObjectInputStream
:用于从输入流中反序列化对象。
OutputStream
:所有字节输出流的超类。FileOutputStream
:用于将数据写入文件。BufferedOutputStream
:为其他输出流添加缓冲功能。ObjectOutputStream
:用于将对象序列化到输出流。
- 基于字符的流,用于读写纯文本文件
Reader
:所有字符输入流的超类。FileReader
:用于从文件读取字符。BufferedReader
:为其他字符输入流添加缓冲功能,并提供了按行读取的便利方法。InputStreamReader
:将字节流转换为字符流,通常与指定的字符集一起使用。
Writer
:所有字符输出流的超类。FileWriter
:用于将数据写入文件。BufferedWriter
:为其他字符输出流添加缓冲功能。OutputStreamWriter
:将字符流转换为字节流,通常与指定的字符集一起使用。PrintWriter
:一个方便打印格式化字符串和对象的输出流。
- 缓冲流
缓冲流主要用于提高 IO 操作的效率,通过减少与底层资源(如文件或网络连接)的交互次数来实现。例如,BufferedInputStream
和 BufferedOutputStream
是基于字节的缓冲流,而 BufferedReader
和 BufferedWriter
是基于字符的缓冲流。
- 特定用途的流
Java IO 还提供了许多特定用途的流,如 DataInputStream
和 DataOutputStream
(用于读取和写入基本数据类型),PrintStream
(通常与 System.out
相关联,用于打印格式化输出),SequenceInputStream
(用于将多个输入流合并为一个逻辑流)等。
- 序列化与反序列化
Java IO 还提供了对象序列化和反序列化的功能,通过 ObjectInputStream
和 ObjectOutputStream
实现。这些流允许你将对象转换为字节序列(序列化),然后可以将这些字节序列写入持久性存储(如文件)或通过网络发送到另一台机器。在接收端,可以使用 ObjectInputStream
将这些字节序列还原为原始对象(反序列化)。
- 字符集编码
当处理字符流时,Java IO 还需要考虑字符集编码的问题。不同的字符集使用不同的编码方式来表示字符,因此在使用 InputStreamReader
和 OutputStreamWriter
时,通常需要指定一个字符集(如 UTF-8 或 ISO-8859-1)。这确保了字符在字节流和字符流之间的正确转换。
对资源操作的步骤:
如果要操作的是文件,首先通过 File 类对象找到要操作的文件路径
通过字节流或字符流的子类为字节流或字符流的对象实例化(向上转型)
执行读/写操作
关闭资源
FileOutputStream
FileOutputStream
是 Java 的一个内置类,它用于将数据写入到文件。这个类属于 java.io
包,并且它是字节流的一种,意味着它使用字节作为数据的传输单位。
范例:使用 FileOutputStream
向文件输出数据
package cn.stone.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo1.txt");//第1步,定义文件路径
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OutputStream output = new FileOutputStream(file); //第2步:通过子类实例化父类
String data = "Hello World"; //要输出的数据
output.write(data.getBytes()); //第3步:将数据变为字节数组输出
output.close(); //第4步:关闭资源
}
}
输出:Hello World
范例:追加数据
package cn.stone.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo1.txt");//第1步,定义文件路径
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
OutputStream output = new FileOutputStream(file,true); //第2步:通过子类实例化父类
String data = "Hello World\r\n"; //要输出的数据
output.write(data.getBytes()); //第3步:将数据变为字节数组输出
output.close(); //第4步:关闭资源
}
}
输出:
Hello World
Hello World
范例:使用 FileOutputStream
public class FileOutputStreamDemo1 {
/*
字节流写出数据
构造方法:
1. public FileOutputStream(String name) : 输出流关联文件,文件路径以字符串形式给出
2. public FileOutputStream(File file) : 输出流关联文件,文件路径以File对象形式给出
成员方法:
public void write(int i) : 写出一个字节
public void write(byte[] bys) : 写出一个字节数组
细节:
输出流关联文件,文件如果不存在:会自动创建出来
文件如果存在:会清空现有的内容然后再进行写入操作
*/
public static void main(String[] args) throws IOException {
// 创建字节流管道
FileOutputStream fos = new FileOutputStream("E:\\A.txt");
// FileOutputStream 通过重载方法传入true可将模式切换为追加,即不清空原有内容的情况下追加写出
// FileOutputStream fos = new FileOutputStream("E:\\A.txt",true);
// 创建字节数组
byte[] bys = {97, 98, 99};
// 写出一个字节
fos.write(97);
fos.write(98);
fos.write(99);
// 写出一个字节数组
fos.write(bys);
// 通过.getBytes()方法获取文本字节
fos.write("你好Java!".getBytes());
// 写出一个字节数组指定长度
// off 开始索引 len 索引个数
fos.write(bys,1,2);
}
}
范例:JDK7 版本以前关闭流
public class FileOutputStreamDemo3 {
/*
IO 流的异常处理方式:JDK7 版本以前
*/
public static void main(String[] args) {
FileOutputStream fos = null;
try{
// System.out.println(10/0); // ArithmeticException错误,导致后面代码均不能执行到
fos = new FileOutputStream("E:\\B.txt");
fos.write("abc".getBytes());
} catch (IOException e){
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
范例:JDK7 版本开始关闭流
public class FileOutputStreamDemo4 {
/*
IO 流的异常处理方式:JDK7 版本开始
*/
public static void main(String[] args) throws Exception {
// try() 括号中创建流对象,完成调用后可自动调用 close() 方法
try(FileOutputStream fos = new FileOutputStream("E:\\B.txt");){
fos.write("abc".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
try(Demo d = new Demo()){
} catch (Exception e){
}
}
}
// 需要实现 AutoCloseable 接口后才可使用try()
class Demo implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("此重写方法会在 try() 结束后自动调用...");
}
}
FileInputStream
FileInputStream
是 Java 标准库(java.io
包)中的一个类,用于从文件中读取原始字节数据。它继承自 InputStream
抽象类,并提供了读取文件内容的功能。
主要功能:
- 从文件中读取字节数据:通过创建一个
FileInputStream
对象,并将文件的路径或File
对象传递给它,您可以使用read()
方法逐个字节地读取文件中的内容。 - 读取二进制文件:
FileInputStream
可以用于读取任何类型的文件,包括文本文件和二进制文件。对于二进制文件,您可以使用read(byte[])
方法读取一定长度的字节数据。 - 处理大文件:
FileInputStream
可以有效地处理大文件,因为它只从文件中读取一小块数据到内存中,而不是将整个文件加载到内存中。
构造方法:
FileInputStream(File file)
: 通过File
对象创建一个FileInputStream
。FileInputStream(String name)
: 通过文件名(路径)创建一个FileInputStream
。
常用方法:
int read()
: 从输入流中读取一个数据字节。如果因为已经到达文件末尾而没有可用的字节,则返回 -1。int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组b
中。返回读取的字节数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。int read(byte[] b, int off, int len)
: 从输入流中读取最多len
个字节的数据,并将它们存储到字节数组b
中,从偏移量off
开始。返回实际读取的字节数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。long skip(long n)
: 跳过并丢弃此输入流中的n
个字节的数据。返回实际跳过的字节数。void close()
: 关闭此文件输入流并释放与此流相关联的任何系统资源。
范例:读取单个字节,创建对象的时候关联文件,如果文件不存在就会抛出异常
public class FileInputStreamDemo1 {
/*
字节流读取数据
public int read() : 读取单个字节
*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\A.txt");
// 这样子太慢了,效率不咋滴,这跟去超市似得,一个一个买鸡蛋回家,效率肯定低啊
// 所以准备一个菜篮子还是有必要的
int i;
while ((i = fis.read()) != -1) {
System.out.println((char) i);
}
fis.close();
}
}
范例:拷贝文件
public class CopyTest1 {
/*
将 D:\\1.png 拷贝到 D:\\image\\1.png 根目录下
分析:
1.创建输入流对象读取文件
2.创建输出流对象关联数据目的
3.读写操作
4.关闭流释放资源
*/
public static void main(String[] args) throws IOException {
// 1.创建输入流对象读取文件
FileInputStream fis = new FileInputStream("D:\\1.png");
// 2.创建输出流对象关联数据目的
FileOutputStream fos = new FileOutputStream("D:\\image\\1.png");
// 3.创建输出流对象关联数据目的
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 4.关闭流释放资源
fis.close();
fos.close();
}
}
范例:读取多个字节
public class FileInputStreamDemo2 {
/*
字节流读取数据:
public int read(byte[] bys : 读取一个字节数组)
- 将读取到的字节,存入到数组容器,返回读取到的有效字节个数
*/
public static void main(String[] args) throws IOException {
// 准备容器,接收读取出来的
byte[] bys = new byte[2];
// 创建字节读取流对象
FileInputStream fis = new FileInputStream("D:\\A. txt");
// 读取到的个数
int len;
// 循环读取,读取出来的个数不等于-1,就是读取到文件了
while((len = fis.read(bys)) != -1){
// String对象的构造方法,读取一个字节数组,从0开始读取到指定位置
String s = new String(bys,0,len);
System.out.print(s);
}
// 关闭字节流
fis.close();
}
}
范例:图片加密和解密
public class ImageTest {
/*
图片文件加密解密
加密思路: 改变原始文件中的字节,就无法打开了
字节^2
解密思路: 将文件中的字节还原成原始字节既可
字节^2
*/
public static void main(String[] args) throws IOException {
// 1. 创建字节输入流对象,关联要加密的图片
FileInputStream fis = new FileInputStream("D:\\1.png");
// 2. 创建一个容器,用来存储读取到的字节
ArrayList<Integer> list = new ArrayList<>();
// 3. 循环读取文件中的字符,并存入集合
int i;
while ((i = fis.read()) != -1) {
list.add(i);
}
// 4. 关闭输入流对象
fis.close();
// 5. 创建输出流对象,关联图片文件
FileOutputStream fos = new FileOutputStream("D:\\1.png");
// 6. 遍历集合,从集合中取出字节,并写出
for (Integer myByte : list) {
fos.write(myByte ^ 2);
}
// 7. 关闭输出流对象
fos.close();
}
}
范例:拷贝文件夹及子文件夹
public class CopyDirTest {
/*
拷贝一个文件夹, 考虑子文件夹
将 D:\\test文件夹, 拷贝到 E:\\
*/
public static void main(String[] args) throws IOException {
// 源文件所在路径
File src = new File("D:\\test");
// 要拷贝到的目的地
File dest = new File("E:\\");
if (src.equals(dest)) {
System.out.println("目标文件夹是源文件的子文件夹");
} else {
copyDir(src, dest);
}
}
public static void copyDir(File src, File dest) throws IOException {
// 封装 File 对象,根据传入的父级路径跟子级路径封装,dest是父级路径,src.getName是子级路径,
File newDir = new File(dest, src.getName());
// 创建文件夹
newDir.mkdirs();
// 从数据源中获取数据(File 对象)
File[] files = src.listFiles();
// 遍历数组,获取每一个文件或文件夹对象
for (File file : files) {
// 判断当前对象是否是文件
if (file.isFile()) {
// 是的话直接拷贝
// file 表示当前文件
FileInputStream fis = new FileInputStream(file);
// newDir 表示父级路径,file 是当前文件,获取他的名字,作为子级路径
FileOutputStream fos = new FileOutputStream(new File(newDir, file.getName()));
int len;
byte[] bys = new byte[1024];
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fis.close();
fos.close();
} else {
// 如果是文件夹,递归调用方法
copyDir(file, newDir);
}
}
}
}
字节缓冲流
字节缓冲流在源代码中内置了字节数组,可以提高读写效率。
NO. | 方法或常量 | 类型 | 描述 |
---|---|---|---|
1 | BufferedInputStream(InputStream in) | 构造 | 对传入的字节输入流进行包装 |
2 | BufferedOutputStream(OutputStream out) | 构造 | 对传入的字节输出流进行包装 |
注意:缓冲流不具备读写功能,只是对普通流对象进行包装,真正和文件建立关联的还是普通的流对象。
范例:使用字节缓冲流拷贝数据
public class BufferedStreamDemo1 {
/*
字节缓冲输入流 : public BufferedInputStream(InputStream in)
字节缓冲输出流 : public BufferedOutputStream(OutputStream out)
提高效率,为啥?
因为默认创建一个数组
字节缓冲输入过程
在创建对象的时候,会调用构造方法,这个时候就在初始化了,会初始化一个8192大小的一个数组,然后读取,一次性读取8192个字节,将内置数组装满
字节缓冲输出过程
把8192大小数组装满后才会一次性全部写出去,然后准备接新的,不是直接写到文件,而是先写到内置数组当中。
要是数据没有8192个,关闭了流,数据会写出去,因为在调close方法的时候,会检查缓冲区里面有没有数据,有数据刷出去,再关闭。
*/
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:\\1.png");
FileOutputStream fos = new FileOutputStream("D:\\image\\1.png");
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
fis.close();
fos.close();
}
private static void method() throws IOException {
// 1.创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\1.png"));
// 2.创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\image\\1.png"));
// 3.读写操作,从字节缓冲输入流的长度 8192 的数组中一个一个字节搬运到字节缓冲输出流的长度 8192 的数组中
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
// 4.关流
bis.close();
bos.close();
}
}
范例:文件拷贝效率比较
public class CopyTest2 {
/*
D:\00-课程介绍.mp4 146MB
拷贝的效率测试:
1. 普通流单个字节拷贝 361719毫秒
2. 普通流 + 自定义数组拷贝 146毫秒 建议选择这种方式
3. 缓冲流单个字节拷贝 2055毫秒
4. 缓冲流 + 自定义数组拷贝 148毫秒
*/
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copyFile2();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void copyFile1() throws IOException {
FileInputStream fis = new FileInputStream("D:\\00-课程介绍.mp4");
FileOutputStream fos = new FileOutputStream("E:\\copy1.mp4");
int i;
while ((i = fis.read()) != -1) {
fos.write(i);
}
fis.close();
fos.close();
}
public static void copyFile2() throws IOException {
FileInputStream fis = new FileInputStream("D:\\00-课程介绍.mp4");
FileOutputStream fos = new FileOutputStream("E:\\copy2.mp4");
byte[] bys = new byte[8192];
int len;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fis.close();
fos.close();
}
public static void copyFile3() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\00-课程介绍.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\copy3.mp4"));
int i;
while ((i = bis.read()) != -1) {
bos.write(i);
}
bis.close();
bos.close();
}
public static void copyFile4() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\00-课程介绍.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\copy4.mp4"));
int len;
byte[] bys = new byte[1024];
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bis.close();
bos.close();
}
}
FileWriter
FileWriter
是 Java 标准库(java.io
包)中的一个类,用于将字符数据写入文件。它提供了一种便捷的方式来处理基于字符的文件 I/O 操作。下面是对 FileWriter
的简要介绍:
构造函数:
FileWriter
有几个构造函数,但最常见的是这两个:
FileWriter(String fileName)
: 使用指定的文件名创建一个新的FileWriter
,如果该文件已存在,则它将被覆盖。FileWriter(File file)
: 使用File
对象创建一个新的FileWriter
,该File
对象表示要写入的文件。
主要方法:
void write(int c)
: 写入单个字符。void write(char[] cbuf)
: 写入字符数组。void write(char[] cbuf, int off, int len)
: 写入字符数组的一部分,从off
开始,长度为len
。void write(String str)
: 写入字符串。void write(String str, int off, int len)
: 写入字符串的一部分,从off
开始,长度为len
。void flush()
: 刷新流,将任何缓冲的输出字节都写入目标。void close()
: 关闭流,释放与之关联的系统资源。
范例:使用 FileWriter
将数据写入到文件
public class FileWriterDemo1 {
/*
FileWriter字符输出流写出数据 :
构造方法:
FileWriter(String fileName): 字符输出流关联文件,路径以字符串形式给出
FileWriter(String fileName, boolean append): 参数2: 追加写入的开关
FileWriter(File file): 字符输出流关联文件,路径以File对象形式给出
FileWriter(File file, boolean append): 参数2: 追加写入的开关
成员方法:
public void write(int c) 写出单个字符
public void write(char[] cbuf) 写出一个字符数组
public void write(char[] cbuf, int off, int len) 写出字符数组的一部分
public void write(String str) 写出字符串
public void write(String str, int off, int len) 写出字符串的一部分
*/
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("D:\\C.txt");
char[] chs = {'a','b','c'};
fw.write('a');
fw.write(chs);
fw.write(chs, 0, 2);
fw.write("你好你好~");
fw.write("哈哈哈哈哈", 0, 2);
fw.close();
}
}
public class FileWriterDemo2 {
/*
注意事项:字符输出流写出数据,需要调用flush或close方法,数据才会写出,
flush() : 输出数据,刷出后可以继续写出
close() : 关闭流释放资源,顺便刷出数据,关闭后不可以继续写出
他也有内置数组,大小为1024个大小,在写操作的时候,写不是直接写出去,而是先会写到内置数组当中,不调用上面这两个方法,数据就出不去
*/
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("D:\\D.txt");
fw.write("人活一世,草木一秋");
}
}
FileReader
使用 FileReader
读取纯文本文件,解决中文乱码问题。
No. | 方法或常量 | 类型 | 描述 |
---|---|---|---|
1 | FileReader(String fileName) | 构造 | 字符输入流关联文件,路径以字符串形式给出 |
2 | FileReader(File file) | 构造 | 字符输入流关联文件,路径以 File 对象形式给出 |
3 | public int read() | 普通 | 读取单个字符 |
4 | public int read(char[] cbuf) | 普通 | 读取一个字符数组,返回读取到的有效字符个数 |
范例:读取文本文件
public class FileReaderDemo1 {
/*
FileReader:用于读取纯文本文件,解决中文乱码问题
构造方法:
1.public FileReader(String fileName) 字符输入流关联文件,路径以字符串形式给出2
2.public FIleReader(File file) 字符输入流关联文件,路径以File对象形式给出
成员方法:
public int read() : 读取单个字符
public int read(char[] cbuf) : 读取一个字符数组,返回读取到的有效字符个数
*/
public static void main(String[] args) throws IOException {
// 创建字符流输入流对象
FileReader fr = new FileReader("D:\\A.txt");
// 创建数组容器
char[] chs = new char[1024];
// 返回个数
int len;
// 只要读取东西了,返回值就不会是-1,如果是那就说明,读取到末尾了
while ((len = fr.read(chs)) != -1) {
// 利用String的构造方法,将数组容器里的值,转换成String
String s = new String(chs, 0, len);
System.out.println(s);
}
fr.close();
}
private static void method() throws IOException {
FileReader fr = new FileReader("D:\\A.txt");
int i;
while ((i = fr.read()) != -1) {
System.out.print((char) i);
}
fr.close();
}
}
范例:字符集与字符编码
public class StringDemo {
/*
平台默认字符编码 Unicode - UTF-8的形式
重点记忆 : 中文字符,通常都是由负数的字节进行组成的,
特殊情况,可能会出现正数,但是就算有正数,第一个字节肯定是负数
注意事项 : 今后如果出现乱码问题: 大概率是因为编解码方式不一致所导致的
编码:字符转字节
public byte[] getBytes() : 使用平台默认字符编码方式,对字符串编码
public byte[] getBytes(String charsetName) : 指定字符编码方式,对字符串编码
解码 : 字节转字符
public String(byte[] bytes) : 使用平台默认字符编码方式,对字符串解码
public String(byte[] bytes, String charsetName) : 指定字符解码方式,对字符串解码
*/
public static void main(String[] args) throws IOException {
// 编码就是把字符串转成字节
String s = "你好,你好";
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
byte[] gbks = s.getBytes("gbk");
System.out.println(Arrays.toString(gbks));
System.out.println("-----------------------------------------");
byte[] utf8Bytes = {-28, -67, -96, -27, -91, -67, 44, -28, -67, -96, -27, -91, -67};
byte[] gbkBytes = {-60, -29, -70, -61, 44, -60, -29, -70, -61};
// 解码就是把自己转换成能看懂的字符串
String s1 = new String(gbkBytes, "GBK");
System.out.println(s1);
}
}
范例:统计字符出现次数
public class CharacterTest {
/*
统计文件每一个字符出现的次数,随后展示在控制台
效果:
A(1)B(2)C(3)
*/
public static void main(String[] args) throws IOException {
// 1. 准备 map 集合,用于统计每一种字符出现的次数
HashMap<Character, Integer> hm = new HashMap<>();
// 2. 创建字符输入流,读取纯文本文件
FileReader fr = new FileReader("D:\\info.txt");
// 3. 读取字符
int i;
while ((i = fr.read()) != -1) {
char c = (char) i;
// 4. 统计这个字符出现的次数
if (!hm.containsKey(c)) {
hm.put(c, 1);
} else {
hm.put(c, hm.get(c) + 1);
}
}
// 5. 关闭输入流
fr.close();
// 6. 准备StringBuilder用与拼接操作
StringBuilder sb = new StringBuilder();
hm.forEach(new BiConsumer<Character, Integer>() {
@Override
public void accept(Character key, Integer value) {
sb.append(key).append("(").append(value).append(")");
}
});
System.out.println(sb);
}
}
字节流和字符流的区别:
字节流在进行IO操作时,直接针对的是操作的数据终端(如文件),而字符流操作时不是直接针对于终端,而是针对于缓存区(理解为内容)的操作,而后由缓存区操作终端(如文件),这属于间接操作,按照这样的方式,如果在使用字节流时不关闭最后的输出流操作,也可以将所有的内容进行输出,而字符输出流时如果不关闭,则意味着缓冲区中的内容不会被输出,当然,这个时候可以由用户自己去调用flush()方法进行强制性的手工清空。
范例:强制清空缓冲区后使用Writer输出
package cn.stone.demo;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo1.txt");//第1步,定义文件路径
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
Writer out = new FileWriter(file);
String data = "Hello World";
out.write(data);
out.flush();
}
}
字节流和字符流的主要区别:
- 字节流没有使用到缓冲区,而字符流使用了。
- 处理各种数据都可以通过字节流完成,而在处理中文时使用字符流会更好。
范例:实现文件复制
package cn.stone.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Copy {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println("执行程序命令错误!");
System.exit(1);
}
long start = System.currentTimeMillis();
File inFile = new File(args[0]); //要复制的源文件
if(!inFile.exists()) {
System.out.println("源文件不存在!");
System.exit(1);
}
File outFile = new File(args[1]);
InputStream input = new FileInputStream(inFile);
OutputStream output = new FileOutputStream(outFile);
int temp = 0;
byte[] data = new byte[1024];
while ((temp = input.read(data)) != -1) {
output.write(data, 0, temp);
}
long end = System.currentTimeMillis();
System.out.println("拷贝完成,所花费的时间:" + (end - start));
input.close();
output.close();
}
}
输出:拷贝完成,所花费的时间:1
字符缓冲流
字符缓冲流在源代码中内置了字符数组,可以提高读写效率。
NO. | 方法或常量 | 类型 | 描述 |
---|---|---|---|
1 | BufferedReader(Reader reader) | 构造 | 对传入的字符输入流进行包装 |
2 | BufferedWriter(Writer writer) | 构造 | 对传入的字符输出流进行包装 |
3 | public String readLine() | 普通 | 读取一行字符串,读取到末尾返回 Null |
4 | public void newLine() | 普通 | 写出换行符(具有跨平台性) |
注意:缓冲流不具备读写功能,只是对普通流对象进行包装,真正和文件建立关联的还是普通的流对象。
范例:使用字符缓冲流拷贝数据
public class BufferedStreamDemo1 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\b.txt"));
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
}
br.close();
bw.close();
}
private static void method1() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\b.txt"));
int i;
// 默认还是使用长度为 8092 的数组作为缓冲读取和缓冲写出,这里读取和写出之间还是一个一个字符处理
while ((i = br.read()) != -1) {
bw.write(i);
}
br.close();
bw.close();
}
}
范例:一次读取一行数据
public class BufferedStreamDemo2 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter(("D:\\b.txt")));
String line;
// readLine() 不会读取换行符,使用 newLine() 加上换行符
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
}
private static void method() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
范例:对文件内容进行排序
public class BufferedStreamTest1 {
public static void main(String[] args) throws IOException {
// 创建 TreeSet 集合用于排序
TreeSet<String> ts = new TreeSet<>();
BufferedReader br = new BufferedReader(new FileReader("D:\\出师表.txt"));
String line;
while ((line = br.readLine()) != null) {
ts.add(line);
}
br.close();
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\出师表.txt"));
for (String content : ts) {
bw.write(content);
bw.newLine();
}
bw.close();
}
}
转换流
Java 中的转换流主要用于在字节流(InputStream
和 OutputStream
)与字符流(Reader
和 Writer
)之间进行转换。
作用:
- 按照指定的字符编码读写操作。
- 将字节流转换为字符流进行操作。
Java 提供了两个主要的转换流类:
- InputStreamReader:这个类将字节输入流转换为字符输入流。它的构造函数接受一个
InputStream
对象和一个可选的字符集名称。如果不指定字符集,则使用默认字符集。InputStreamReader
通常用于将字节流中的数据转换为字符流,以便进行更高效的文本处理。 - OutputStreamWriter:这个类将字符输出流转换为字节输出流。它的构造函数接受一个
Writer
对象和一个可选的字符集名称。如果不指定字符集,则使用默认字符集。OutputStreamWriter
通常用于将字符流中的数据转换为字节流,以便将其写入文件或通过网络发送。
两个类的定义结构和构造方法如下:
类名称 | OutputStreamWriter | InputStreamReader |
---|---|---|
继承结构 | public class OutputStreamWriter extends Writer | public class InputStreamReader extends Reader |
构造方法 | OutputStreamWriter(OutputStream out, String charsetName) | InputStreamReader(InputStream in, String charsetName) |
范例:按照指定的字符编码读写文件
public class ChangeStreamDemo {
/*
转换流按照指定的字符编码读写
构造方法:
InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter(OutputStream out, String charsetName)
不指定编码默认使用 UTF8 编码读写
*/
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\a.txt", true), "GBK");
osw.write("哈哈");
osw.close();
}
private static void method() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\a.txt"), "GBK");
int i;
while ((i = isr.read()) != -1) {
System.out.println((char)i);
}
isr.close();
}
}
内存操作流
字节内存操作流:内存输入流(ByteArrayInputStream),内存输出流(ByteArrayOutputStream);
字符内存操作流:内存输入流(CharArrayReader),内存输出流(CharArrayWriter)。
实际开发中以字节内存操作流为主,这两个类的继承结构和构造方法的定义:
类名称 | ByteArrayInputStream | ByteArrayOutputStream |
---|---|---|
继承结构 | java.lang.Object java.io.InputStream java.io.ByteArrayInputStream | java.lang.Object java.io.OutputStream java.io.ByteArrayOutputStream |
构造方法 | public ByteArrayInputStream(byte[] buf) | public ByteArrayOutputStream() |
文件操作流和内存操作流对比:
- 文件操作流形式
- FileOutputStream:程序 ==》OutputStream ==》文件;
- FileInputStream:程序《== InputStream《== 文件。
- 内存操作流形式
- ByteArrayInputStream:程序 ==》InputStream ==》内存;
- ByteArrayOutputStream:程序《== OutputStream《== 内存。
范例:使用内存操作流完成一个字符串大小写字母的转换操作
package cn.stone.demo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
String str = "Hello World."; //有非字母
InputStream input = new ByteArrayInputStream(str.getBytes()); //将数据输出到内存
OutputStream output = new ByteArrayOutputStream(); //从内存读取数据
int temp = 0;
while ((temp = input.read()) != -1) {
output.write(Character.toUpperCase(temp));
}
String newStr = output.toString();
output.close();
input.close();
System.out.println(newStr);
}
}
输出:HELLO WORLD.
字符编码
项目开发中必须采用UTF-8编码。
范例:查看当前环境的所有属性
package cn.stone.demo;
public class TestDemo {
public static void main(String[] args) throws Exception {
System.getProperties().list(System.out);
}
}
范例:手工生成乱码
package cn.stone.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "code" + File.separator + "demo1.txt");
OutputStream output = new FileOutputStream(file);
output.write("世界,你好".getBytes("ISO8859-1")); //强制转码
output.close();
}
}
输出:?????
打印流
现在要输出字符串,使用 Writer 可以直接输出,而使用 OutputStream 还需要将字符串变为字节数组;如果现在想输出数字(int 型或 double 型),还需要将这些数据先变为字符串,之后再变为字节数组输出,所以用户直接调用 OutputStream 或 Writer 输出时本身并不方便。为了更好地为用户提供输出的支持,可以想办法将 OutputStream 或 Writer 加强,Java 提供了两个类:字节打印类(PrintSteam)和字符打印类(PrintWriter)。
PrintStream 类和 PrintWriter 类的继承结构和构造方法:
类名称 | PrintStream | PrintWriter |
---|---|---|
继承结构 | java.lang.Object java.io.OutputStream java.io.FilterOutputStream java.io.PrintStream | java.lang.Object java.io.Writer java.io.PrintWriter |
构造方法 | public PrintStream(OutputStream out) | public PrintWriter(Writer out) |
PrintStream 类常用方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public PrintStream(File file) | 构造 | 通过一个 File 对象实例化 PrintStream 类 |
2 | public PrintStream(OutputStream out) | 构造 | 接收 OutputStream 对象,实例化 PrintStream 类 |
3 | public PrintStream(String filepath) | 构造 | 接收字符串作为文件路径 |
4 | public PrintStream(String filepath, String charsetName) | 构造 | 接收字符串作为文件路径,可以指定字符集 |
5 | public PrintStream printf(String format,Object … args) | 普通 | 根据本地环境格式化输出,JDK1.5 后增加 |
6 | public void print(数据类型 b) | 普通 | 输出任意数据 |
7 | public void println(数据类型 b) | 普通 | 输出任意数据后换行 |
范例:使用字节打印流
public class PrintStreamDemo1 {
/*
字节打印流:
构造方法
public PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
public PrintStream(String fileName, Charset charset) 指定字符编码
public PrintStream(OutputStreamout, boolean autoFlush) 自动刷新
public PrintStream(OutputStream out, boolean autoFlush, String encoding) 指定字符编码且自动刷新
成员方法:
public void write(int b) 常规方法:规则跟之前一样,将指定的字节写出,不建议使用
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format, Object... args) 特有方法:带有占位符的打印语句,不换行
*/
public static void main(String[] args) throws IOException {
// 创建打印流对象
PrintStream ps = new PrintStream(new FileOutputStream("D:\\a.txt"), true, "UTF-8");
// 使用打印流打印
ps.println(97);
ps.print(true);
ps.println();
ps.printf("%s %s", "Hello", "World");
// 关闭流对象
ps.close();
}
}
范例:使用字符打印流
public class PrintStreamDemo3 {
/*
字符打印流:
构造方法
public PrintWriter(OutputStream/Writer/File/String) 关联字节输出流/文件/文件路径
public PrintWriter(String fileName, Charset charset) 指定字符编码
public PrintWriter(Write, boolean autoFlush) 自动刷新,字符流可以使用,只对 println 有效
public PrintWriter(Write out, boolean autoFlush, String encoding) 指定字符编码且自动刷新
成员方法:
public void write(int b) 常规方法:规则跟之前一样,将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format, Object... args) 特有方法:带有占位符的打印语句,不换行
*/
public static void main(String[] args) throws IOException {
// 创建字符打印流
PrintWriter pw = new PrintWriter(new FileOutputStream("D:\\a.txt"));
// 打印数据
pw.println("今天天气也很好");
pw.print("是吗?");
pw.printf("%s %s", "Hello", "World");
// 关闭流对象
pw.close();
}
}
System
System类的3个IO常量:
NO. | 常量 | 类型 | 描述 |
---|---|---|---|
1 | public static final PrintStream err | 常量 | 错误输出 |
2 | public static final PrintStream out | 常量 | 系统输出 |
3 | public static final InputStream in | 常量 | 系统输入 |
范例:由键盘输入数据
package cn.stone.demo;
import java.io.InputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
InputStream input = System.in;
byte[] data = new byte[1024];
System.out.println("请输入数据:");
int len = input.read(data); //等待用户输入,程序进入阻塞状态
System.out.println("输入的内容是:" + new String(data,0,len));
}
}
输出:
请输入数据:
www.stonecoding.net
输入的内容是:www.stonecoding.n
请输入数据:
中华人民共和国
输入的内容是:中华人民共和国
以上程序由于InputStream类读取时需要开辟空间(此时为1024),所以如果输入的数据超过设置的长度,就只会接收满足指定长度的数据,那么最好的解决方法是不设置长度,输入一个读取一个,一直到用户不输入为止。
范例:由键盘输入数据
package cn.stone.demo;
import java.io.InputStream;
public class TestDemo {
public static void main(String[] args) throws Exception {
InputStream input = System.in;
StringBuffer buf = new StringBuffer();
System.out.print("请输入数据:");
int temp = 0;
while ((temp = input.read()) != -1) { //用户可以一直输入下去
if (temp == '\n') {
break;
}
buf.append((char) temp);
}
System.out.println("输入的内容是:" + buf);
}
}
输出:
请输入数据:www.stonecoding.net
输入的内容是:www.stonecoding.net
请输入数据:中华人民共和国
输入的内容是:ä¸å人æ°å ±åå½
要解决上述乱码问题,应该先将用户输入的全部内容保存到一块指定的内存空间,而后一次性地将数据全部读取出来。而要想完成这一功能需要通过缓冲区读取完成,同时考虑到要读取中文的方式,应该使用字符缓冲区-BufferedReader类完成。
Scanner
JDK1.5之后提供的类,可以完成输入数据操作以及对输入数据进行验证,在java.util包下。
常用方法如下:
No. | 方法或常量 | 类型 | 描述 |
---|---|---|---|
1 | public Scanner(InputStream source) | 构造 | 从指定的字节输入流中接收内容 |
2 | public boolean hasNext(Pattern pattern) | 普通 | 判断输入的数据是否符合指定的正则标准 |
3 | public boolean hasNext() | 普通 | 判断有输入内容 |
4 | public boolean hasNextXxx() | 普通 | 判断输入的是否为指定的数据类型 |
5 | public String next() | 普通 | 接收内容 |
6 | public String next(Pattern pattern) | 普通 | 接收内容,进行正则验证 |
7 | public int nextXxx() | 普通 | 接收指定的输入类型 |
8 | public Scanner useDelimiter(String pattern) | 普通 | 设置读取的分隔符 |
在使用Scanner类接收数据方法(执行nextXxx())前一定要首先使用hasNextXxx()判断是否有指定格式的数据出现。
范例:通过Scanner进行数据的输入
package cn.stone.demo;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in); //实例化Scanner
scan.useDelimiter("\n"); //将换行作为分隔符
System.out.print("请输入数据:");
if (scan.hasNext()) { //有内容输入
String str = scan.next(); //接收数据
System.out.println("输入数据为:" + str);
}
}
}
输出:
请输入数据:北京
输入数据为:北京
范例:使用Scanner判断输入数据是否是double
package cn.stone.demo;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in); //实例化Scanner
scan.useDelimiter("\n"); //将换行作为分隔符
System.out.print("请输入数据:");
if (scan.hasNextDouble()) { //有内容输入
double data = scan.nextDouble(); //接收数据
System.out.println("输入数据为:" + data);
}else {
System.out.println("输入的数据不是数字");
}
}
}
输出:
请输入数据:12
输入数据为:12.0
范例:利用正则验证
package cn.stone.demo;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(System.in); //实例化Scanner
System.out.print("请输入你的生日:");
if (scan.hasNext("\\d{4}-\\d{2}-\\d{2}")) { //有符合格式的内容
String str = scan.next("\\d{4}-\\d{2}-\\d{2}");
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
System.out.println(date);
}else {
System.out.println("输入的数据不是数字");
}
}
}
输出:
请输入你的生日:1983-11-22
Tue Nov 22 00:00:00 CST 1983
由程序向文件输出内容,使用PrintStream完成。
在程序中读取文件内容,使用Scanner完成。
范例:使用Scanner读取文件
package cn.stone.demo;
import java.io.File;
import java.io.FileInputStream;
import java.util.Scanner;
public class TestDemo {
public static void main(String[] args) throws Exception {
Scanner scan = new Scanner(new FileInputStream(new File("D:" + File.separator + "code" + File.separator + "demo1.txt")));
scan.useDelimiter("\n");
while (scan.hasNext()) {
System.out.println(scan.next());
}
scan.close();
}
}
输出:姓名:张三,年龄:20,成绩:89.26
在日常程序开发中,如果要通过程序输出数据,则建议使用PrintStream打印流,如果存在中文,则建议使用PrintWriter,而对于程序输入数据,则建议使用Scanner类完成。
序列化
概念
所谓的对象序列化指的是可以将在内存中保存的对象数据(主要指的是一个对象里面所包含数据内容)进行二进制数据传输的一种操作,要想完成这样的二进制操作,对象所在的类就必须实现java.io.Serializable接口。这个接口和Cloneable接口一样,都属于一种标识接口,表示一种能力。
范例:定义可以被序列化的类
public class Student implements Serializable {
// 为类添加版本号,避免修改类后重新序列化出现的 local class incompatible 错误
private static final long serialVersionUID = 781129628827291954L;
// transient : 瞬态关键字,不会把当前属性序列化到本地文件
private transient String address;
private String name;
private int age;
public Student() {
}
public Student(String name, int age, String address) {
this.address = address;
this.name = name;
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{ address = " + address + ", name = " + name + ", age = " + age + "}";
}
}
实现序列化和反序列化
使用 ObjectOutputStream 和 ObjectInputStream 进行对象的序列化和反序列化操作,这两个类的继承结构、构造方法和操作方法定义如下:
类名称 | ObjectOutputStream | ObjectInputStream |
---|---|---|
继承结构 | java.lang.Object java.io.OutputStream java.io.ObjectOutputStream | java.lang.Object java.io.InputStream java.io.ObjectInputStream |
构造方法 | public ObjectOutputStream(OutputStream out) throws IOException | public ObjectInputStream(InputStream in) throws IOException |
序列化对象/反序列化对象方法 | public final void writeObject(Object obj) throws IOException | public final Object readObject() throws IOException,ClassNotFoundException |
范例:实现序列化操作
public class ObjectStreamDemo1 {
/*
需求
利用序列化流/对象操作输出流,把每一个对象写到本地文件中
构造方法
public ObjectOutStream(ObjectStream out) : 把基本流变成高级流
成员方法
public final void writeObject(Object obj) : 把对象序列化(写出)到文件中
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建学生对象
Student stu = new Student("张三", 23,"北京");
// 创建序列化流对象(对象操作输出流)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\C.txt"));
// 写出数据
oos.writeObject(stu);
// 释放资源
oos.close();
}
}
范例:执行反序列化操作
public class ObjectStreamDemo2 {
/*
需求
利用反序列化流/对象操作输入流,把文件中的对象读取到程序中
构造方法
public ObjectInputStream(InputStream out) 把基本流变成高级流
成员方法
public Object readObject() 读取本地文件中的对象,读取到程序中
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建反序列化流对象
ObjectInputStream ios = new ObjectInputStream(new FileInputStream("D:\\c.txt"));
// 读取
Object stu = ios.readObject();
// 打印
System.out.println(stu);
// 关流
ios.close();
}
}
transient
默认情况下,当一个类的对象被序列化时,这个类中的所有属性都被保存下来,如果某些属性不希望被保存,那么可以使用 transient 进行声明。
Network
完成网络开发,需要java.net包中的两个类:
- ServerSocket类:是一个封装支持TCP协议的操作类,主要工作在服务器端,用于接收客户端请求。ServerSocket类常用方法有:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public ServerSocket(int port) throws IOException | 构造 | 开辟一个指定的端口监听,一般使用5000以上 |
2 | public Socket accept() throws IOException | 普通 | 服务器端接收客户端请求,通过Socket返回 |
3 | public void close() throws IOException | 普通 | 关闭服务器端 |
- Socket类:是一个封装了TCP协议的操作类,每一个Socket对象都表示一个客户端。Socket类常用的方法有:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Socket(String hots,int port) throws UnknowHostException,IOException | 构造 | 指定要连接的主机(IP地址)和端口 |
2 | public OutputStream getOutputStream() throws IOException | 普通 | 取得指定客户端的输出对象,使用时肯定使用PrintStream装饰操作 |
3 | public InputStream getInputStream() | 普通 | 从指定的客户端读取数据,使用Scanner操作 |
范例:完成一个服务器端程序代码
package cn.stone.demo;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class HelloServer {
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9999); //在9999端口监听
System.out.println("服务开始启动......");
Socket client = server.accept(); //接收客户端连接,进入阻塞状态
PrintStream out = new PrintStream(client.getOutputStream());
out.println("Hello World"); //向客户端输出
out.close();
client.close();
server.close();
System.out.println("服务器已关闭......");
}
}
输出:
服务开始启动......
客户端输入:
Microsoft Telnet> open localhost 9999
客户端输出:
Hello World
遗失对主机的连接。
按任意键继续...
服务器端输出:
服务器已关闭......
范例:编写一个客户端
package cn.stone.demo;
import java.net.Socket;
import java.util.Scanner;
public class HelloClient {
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost",9999); //连接服务器及端口
Scanner scan = new Scanner(client.getInputStream()); //取得客户端输入流
scan.useDelimiter("\n");
if (scan.hasNext()) {
System.out.println("服务器的回应数据:" + scan.next());
}
scan.close();
client.close();
}
}
输出:服务器的回应数据:Hello World
本程序利用Socket类连接到指定的服务器,而后通过Scanner读取服务器发送来的数据(服务器发送来的数据,对于客户端就是输入数据)
范例:网络开发经典模型-ECHO程序-基本模型
package cn.stone.demo;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class HelloServer {
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9999); //在9999端口监听
boolean flag = true;
System.out.println("服务器开始运行......");
Socket client = server.accept(); //接收客户端连接,进入阻塞状态
Scanner scan = new Scanner(client.getInputStream());
PrintStream out = new PrintStream(client.getOutputStream());
while (flag) {
if (scan.hasNext()) {
String str = scan.next();
if ("byebye".equalsIgnoreCase(str.trim())) {
out.println("Bye Bye");
flag = false;
}
out.println("ECHO:" + str.trim());
}
}
System.out.println("服务器停止运行......");
server.close();
}
}
范例:网络开发经典模型-ECHO程序-使用匿名内部类增加多线程机制-服务端程序
package cn.stone.demo;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class HelloServer {
public static void main(String[] args) throws Exception{
ServerSocket server = new ServerSocket(9999); //在9999端口监听
boolean flag = true;
System.out.println("服务器开始运行......");
while (flag) {
final Socket client = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
boolean runFlag = true;
try {
Scanner scan = new Scanner(client.getInputStream());
PrintStream out = new PrintStream(client.getOutputStream());
while (runFlag) {
if (scan.hasNext()) {
String str = scan.next();
if ("ByeBye".equalsIgnoreCase(str.trim())) {
out.println("Bye Bye");
runFlag = false;
}
out.println("Echo: " + str.trim());
}
}
} catch (IOException e) {
e.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
System.out.println("服务器停止运行......");
server.close();
}
}
范例:网络开发经典模型-ECHO程序-客户端程序
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class HelloClient {
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost",9999); //连接服务器及端口
PrintStream out = new PrintStream(client.getOutputStream());
Scanner scanin = new Scanner(System.in);
scanin.useDelimiter("\n"); //将换行作为分隔符
System.out.print("请输入数据:");
if (scanin.hasNext()) { //有内容输入
String str = scanin.next(); //接收数据
System.out.println("客户端输入数据为:" + str);
out.println(str);
}
Scanner scan = new Scanner(client.getInputStream()); //取得客户端输入流
scan.useDelimiter("\n");
if (scan.hasNext()) {
System.out.println("服务器的回应数据:" + scan.next());
}
scan.close();
client.close();
}
}
输出:
请输入数据:你好
客户端输入数据为:你好
服务器的回应数据:Echo: 你好
Collections Framework
JCF(Java collections framework)类集是一种动态的对象数组,属于各个数据结构的实现类,整个类集的主要组成是一些核心的操作接口:Collection、List、Set、Map、Iterator和Enumeration。
Java的集合框架(Collections Framework)中,单列集合(Single-column Collections)和双列集合(Double-column Collections,通常也被称为映射或 Map)是两种主要的分类。
- 单列集合(Single-column Collections):
- 单列集合一次只能存储一个元素。
- 它主要包括
Collection
接口及其子接口List
和Set
。List
:是有序的集合,允许存储重复的元素,并且可以通过索引来访问元素。常用的实现类有ArrayList
、LinkedList
和Vector
。Set
:不包含重复元素的集合,且元素是无序的,没有索引。常用的实现类有HashSet
、LinkedHashSet
和TreeSet
。
- 在单列集合中,元素是孤立存在的,向集合中存储元素是以单个元素的方式进行的。
- 双列集合(Double-column Collections 或 Map):
- 双列集合基于键(Key)和值(Value)的结构来存储数据。每个元素都是一个键值对。
- 在双列集合中,键是唯一的,不允许重复,但值可以重复,并且允许为
null
。 - 键和值之间是一一对应的,即每个键只能对应一个值。
- 常用的实现类有
HashMap
、LinkedHashMap
、TreeMap
、Hashtable
和ConcurrentHashMap
。 - 在双列集合中,元素是以成对的键-值形式存在的,就像夫妻一样。可以通过键来找到对应的值。
Collection
Collection 是单值保存的最大父接口,所谓的单值保存指的是每一次操作只会保存一个对象,就好像之前的链表程序一样,每一次只保存一个对象,在 Collection 接口中定义的常用方法:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public boolean add(E e) | 普通 | 数据增加 |
2 | public void clear() | 普通 | 清除数据 |
3 | public boolean contains(Object o) | 普通 | 查找数据是否存在 |
4 | public boolean isEmpty() | 普通 | 判断是否为空集合 |
5 | public Iterator<E> iterator() | 普通 | 为 Iterator 接口实例化 |
6 | public boolean remove(Object o) | 普通 | 删除数据 |
7 | public int size() | 普通 | 取得集合的个数 |
8 | public Object[] toArray() | 普通 | 将集合变为对象数组 |
从开发来讲,很少会直接使用 Collection,一般使用 Collection 的两个接口:List 和 Set。优先考虑使用 List。
使用场景:
- 如果集合中的元素可重复,首选基于数组的
ArrayList
。 - 如果集合中的元素不重复,首选集合哈希表的
HashSet
。 - 如果集合中的元素可重复,且当前的增删操作明显多于查询操作,使用基于链表的
LinkedList
。 - 如果集合中的元素不重复,且需要保证存取属性,使用基于哈希表和双向链表的
LinkedHashSet
。 - 如果集合中的元素是有序,使用基于红黑树的
TreeSet
。
List
List 是 Collection 的一个最为常用的子接口,是有序的集合,允许存储重复的元素,并且可以通过索引来访问元素。定义如下:
public interface List<E> extends Collection<E>
List 接口常用方法:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public void add(int index, E element) | 普通 | 在指定的索引位置添加元素 |
2 | public E remove(int index) | 普通 | 根据索引删除集合中的元素 |
3 | public E get(int index) | 普通 | 取得指定索引位置上的数据 |
4 | public E set(int index,E element) | 普通 | 修改指定索引位置上的数据 |
5 | public ListIterator<E> listIterator() | 普通 | 为 ListIterator 接口实例化 |
List 集合的遍历方式 :
- 迭代器遍历
- 增强 for 循环
- foreach 方法
- 普通 for 循环
- ListIterator (List 集合特有的迭代器)
范例:List 的增删改查
package cn.iamdt.collection.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo1 {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
// 存取有序,可以存储重复的元素
list1.add("张三");
list1.add("李四");
list1.add("王五");
list1.add("张三");
System.out.println(list1);
// 删除集合中指定索引位置的元素
list1.remove(3);
System.out.println(list1);
// 在指定的索引位置添加元素
list1.add(0, "赵六");
System.out.println(list1);
// 修改指定的索引位置的元素
list1.set(0, "赵6");
System.out.println(list1);
// 返回指定索引处的元素
String s = list1.get(1);
System.out.println(s);
System.out.println("----------------------------------------");
// 数字数组的数字会被误认为是索引
List<Integer> list2 = new ArrayList<>();
// 此处存在自动装箱
list2.add(111);
list2.add(222);
list2.add(333);
System.out.println(list2);
// list2.remove(222); 222 此时被识别成元素索引,发生【IndexOutOfBoundsException】错误,此时需要手动装箱
list2.remove(Integer.valueOf(222));
System.out.println(list2);
}
}
List 接口有两个常用子类:ArrayList 和 Vector。
ArrayList
Java ArrayList
是一个常用的动态数组实现,它属于 Java 集合框架(Java Collections Framework)的一部分。ArrayList
类提供了动态数组的功能,可以容纳任意数量的元素,并且会随着元素的添加而自动增长。实际工作中常用。
基本特性:
动态数组:
ArrayList
可以动态地增长和缩小,以适应存储的元素数量。有序:
ArrayList
中的元素是按照插入顺序来维护的。允许重复:与
Set
不同,ArrayList
允许存储重复的元素。非线程安全:
ArrayList
不是线程安全的,如果需要在多线程环境中使用,需要进行额外的同步处理。
主要方法:
add(E e)
:向列表的末尾添加一个元素。add(int index, E element)
:在列表的指定位置插入一个元素。remove(int index)
:删除列表中指定位置的元素。remove(Object o)
:删除列表中首次出现的指定元素(如果存在)。get(int index)
:返回列表中指定位置的元素。set(int index, E element)
:用指定元素替换列表中指定位置的元素。size()
:返回列表中的元素数量。isEmpty()
:检查列表是否为空。contains(Object o)
:检查列表中是否包含指定元素。iterator()
:返回一个用于遍历列表的迭代器。clear()
:移除列表中的所有元素。
ArrayList
内部使用了一个动态增长的数组来存储元素。当添加元素而当前数组容量不足时,ArrayList
会创建一个新的更大的数组,并将原有数组中的元素复制到新数组中。默认情况下,新的数组容量是原容量的 1.5 倍(Java 8 及以后版本),但也可以通过构造函数指定初始容量和扩容因子。
ArrayList 是 List 子接口使用最多的一个子类,定义如下:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,Serializable
范例:使用 ArrayList 进行 List 接口的功能验证
package cn.stone.demo;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
public static void main(String[] args) throws Exception {
// 创建 ArrayList 时,默认容量为 0,直至添加第一个元素时容量扩容为 10,存满时会扩容 1.5 倍
List<String> all = new ArrayList<String>(); //实例化 List 接口
all.add("Hello"); //增加内容
all.add("Hello"); //内容重复
all.add("World"); //增加内容
for (int i = 0; i < all.size(); i++) { //循环输出集合内容
String str = all.get(i); //get() 方法只有 List 接口有
System.out.print(str + ",");
}
}
}
输出:Hello,Hello,World,
List 可以保存重复的数据,而且数据保存的顺序就是存入数据的顺序。
范例:动物园动物的添加,删除以及检索
package cn.stone.demo;
import java.util.ArrayList;
import java.util.List;
interface Animal{
public abstract String getName();
public abstract int getAge();
}
class Zoo{
private List<Animal> animals = new ArrayList<Animal>();
public void add(Animal ani) {
this.animals.add(ani);
}
public void delete(Animal ani) {
this.animals.remove(ani);
}
public List<Animal> search(String keyWord) {
List<Animal> result = new ArrayList<Animal>();
Object[] obj = this.animals.toArray();
for (int i = 0; i < obj.length; i++) {
Animal ani = (Animal) obj[i];
if (ani.getName().contains(keyWord)) {
result.add(ani);
}
}
return result;
}
}
class Dog implements Animal{
private String name;
private int age;
public Dog() {
super();
}
public Dog(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if(obj == null) {
return false;
}
if (!(obj instanceof Dog)) {
return false;
}
Dog dog = (Dog) obj;
if (this.name.equals(dog.name) && this.age == dog.age) {
return true;
}
return false;
}
}
class Tiger implements Animal{
private String name;
private int age;
public Tiger() {
super();
}
public Tiger(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Tiger [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if(!(obj instanceof Tiger)) {
return false;
}
Tiger tiger = (Tiger) obj;
if (this.name.equals(tiger.name) && this.age == tiger.age) {
return true;
}
return false;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Zoo zoo = new Zoo();
zoo.add(new Dog("花狗",1));
zoo.add(new Dog("黄狗",1));
zoo.add(new Dog("黑狗",1));
zoo.add(new Dog("斑点狗",1));
zoo.add(new Tiger("斑点虎",2));
zoo.add(new Tiger("黑虎",2));
zoo.add(new Tiger("花虎",2));
zoo.delete(new Dog("斑点狗",1));
List<Animal> result = zoo.search("斑点");
Object[] obj = result.toArray();
for (int i = 0; i < obj.length; i++) {
System.out.println(obj[i]);
}
}
}
输出:Tiger [name=斑点虎, age=2]
如果要想使用 List 接口中的 contains() 或者 remove() 等方法,则类中一定要覆写 equals() 方法,否则无法正确完成功能。
LinkedList
LinkedList
是 Java 集合框架(Java Collections Framework)中的一个重要类,它实现了 List
接口,同时它也是双向链表(Doubly-Linked List)的实现。与 ArrayList
不同,LinkedList
不需要预先分配连续的内存空间来存储元素,这使得它在某些情况下具有更高的灵活性。
以下是 LinkedList
的一些主要特点和功能:
- 双向链表:
LinkedList
中的每个元素(称为节点)都包含对前一个元素和后一个元素的引用。这使得在链表的任何位置插入或删除元素都非常高效,时间复杂度为 O(1)(除了需要在特定位置插入或删除元素的情况,此时需要遍历到该位置,时间复杂度为 O(n))。 - 高效插入和删除:在
LinkedList
的头部或尾部插入或删除元素是非常快的,因为不需要移动其他元素。 - 不是线程安全的:
LinkedList
不是线程安全的,如果多个线程同时访问和修改一个LinkedList
实例,可能会导致数据不一致。如果需要线程安全,可以使用Collections.synchronizedList()
方法来包装LinkedList
,或者使用CopyOnWriteArrayList
或Vector
(但Vector
在大多数场景下不如ArrayList
高效)。 - 提供迭代器:
LinkedList
提供了ListIterator
,它可以向前或向后遍历链表,同时支持元素的添加和删除。 - 栈和队列操作:由于
LinkedList
的双向链表特性,它可以被用作栈(Stack)或队列(Queue)。LinkedList
类提供了push()
,pop()
,peek()
,offer()
,poll()
,peekFirst()
,peekLast()
等方法来支持这些操作。 - 元素有序且可重复:与
Set
接口的实现类(如HashSet
)不同,LinkedList
中的元素是有序的(插入顺序),并且元素可以重复。
LinkedList
特有方法:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public void addFirst(E e) | 普通 | 头部添加 |
2 | public void addLast(E e) | 普通 | 尾部添加 |
3 | public E getFirst() | 普通 | 获取第一个 |
4 | public E getLast() | 普通 | 获取最后一个 |
5 | public E removeFirst() | 普通 | 删除第一个 |
6 | public E removeLast() | 普通 | 删除最后一个 |
范例:LinkedList 使用
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
// 查找元素时会根据索引的位置来决定从头还是从尾遍历
String s = list.get(1);
System.out.println(s);
}
private static void method2() {
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
System.out.println(list.getFirst()); // out : 张三
System.out.println(list.getLast()); // out : 赵六
list.removeFirst();
list.removeLast();
System.out.println(list); // out : [李四, 王五]
}
private static void method1() {
LinkedList<String> list = new LinkedList<>();
list.addFirst("张三");
list.addFirst("李四");
list.addFirst("王五");
list.addLast("赵六");
System.out.println(list); // out : [王五, 李四, 张三, 赵六]
}
}
Vector
Vector类是在JDK1.0时就推出的一个实现动态数组的操作类。实际上现在依然有许多的类还是在使用Vector。
范例:通过Vector为List接口实例化
package cn.stone.demo;
import java.util.List;
import java.util.Vector;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> all = new Vector<String>(); //实例化List接口
all.add("Hello");
all.add("Hello");
all.add("World");
for (int i = 0; i < all.size(); i++) {
String str = all.get(i);
System.out.print(str + ",");
}
}
}
输出:Hello,Hello,World,
ArrayList和Vector的区别:
No. | 区别 | ArrayList | Vector |
---|---|---|---|
1 | 推出时间 | JDK1.2 | JDK1.0 |
2 | 性能 | 采用异步处理方式,性能更高 | 采用同步处理方式,性能相对较低 |
3 | 安全性 | 非线程安全 | 线程安全 |
4 | 输出 | Iterator、ListIterator、foreach | Iterator,ListIterator、foreach、Enumeration |
就实际开发而言,开发异步程序比较常见,所以首选的肯定是ArrayList子类。
Set
Set也是一个Collection常用的子接口,不允许重复数据,定义如下:
public interface Set<E> extends Collection<E>
在Set子接口中常用的两个子类为HashSet和TreeSet。
TreeSet
TreeSet 是 Java 集合框架(Java Collections Framework)中的一个重要类,它实现了 Set 接口,并提供了基于红黑树(Red-Black Tree)的排序功能。TreeSet 中的元素以升序排列,每个元素只会出现一次,因此它保证了元素的唯一性和有序性。
主要特性:
- 有序性:TreeSet 中的元素按照升序排列。默认情况下,元素根据其自然顺序(Natural Order,即实现了 Comparable 接口的对象)进行排序。如果需要自定义排序规则,可以在创建 TreeSet 时传入一个 Comparator 对象。
- 唯一性:TreeSet 不允许插入重复元素。如果试图插入一个已存在的元素,TreeSet 会忽略这次插入操作。
- 动态大小:TreeSet 可以动态地增长和缩小,以适应存储的元素数量。
- 快速查找:由于 TreeSet 是基于红黑树实现的,因此它具有快速的查找、插入和删除性能,时间复杂度通常为 O(log n)。
- 非线程安全:和大多数 Java 集合类一样,TreeSet 也是非线程安全的。如果需要在多线程环境中使用,需要额外的同步措施。
TreeSet 通常用于需要存储唯一且有序的元素的场景。例如,可以使用 TreeSet 来存储一个用户 ID 集合,其中每个用户 ID 都是唯一的,并且你希望这些 ID 以升序排列。此外,TreeSet 还常用于需要快速查找、插入和删除操作的场景,如排序算法、索引构建等。
范例:使用TreeSet保存数据
package cn.stone.demo;
import java.util.Set;
import java.util.TreeSet;
public class TestDemo {
public static void main(String[] args) throws Exception {
Set<String> all = new TreeSet<String>();
all.add("C");
all.add("B");
all.add("B");
all.add("A");
System.out.println(all);
}
}
输出:[A, B, C]
没有重复数据并且已经排序。
如果想正确排序自定义对象的大小,那么对象所在的类必须实现Comparable接口,设置比较规则。但是在这种情况下有一点必须注意:一旦使用了Comparable后,类中的所有属性必须写进排序规则。
当调用 add
方法向 TreeSet 添加元素时,内部会自动调用 compareTo
方法,根据这个方法的返回值来决定节点怎么走。
范例:自然排序,用于为自定义的类指定排序规则
public class Student implements Comparable<Student> {
// this.xxx - o.xxx 正序
// o.xxx - this.xxx 降序
@Override
public int compareTo(Student o) {
// 根据年龄做主要排序条件
int ageResult = this.age - o.age;
// 根据姓名做次要排序条件
int nameResult = ageResult == 0 ? o.name.compareTo(this.name) : ageResult;
// 判断姓名是否相同
int result = nameResult == 0 ? 1 : nameResult;
return result;
}
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
public class TreeSetDemo2 {
/*
TreeSet集合存储 Student 学生对象
*/
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
ts.add(new Student("王五", 25));
ts.add(new Student("张三", 23));
ts.add(new Student("李四", 24));
ts.add(new Student("赵六", 26));
ts.add(new Student("赵七", 26));
ts.add(new Student("赵七", 26));
System.out.println(ts);
}
}
范例:比较器排序,用于覆盖默认排序规则
public class TreeSetDemo3 {
/*
如果同时具备比较器和自然排序,会优先按照比较器的规则,进行排序操作
*/
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int ageResult = o1.getAge() - o2.getAge();
return ageResult == 0 ? o1.getName().compareTo(o2.getName()) : ageResult;
}
});
ts.add(new Student("张三", 23));
ts.add(new Student("李四", 24));
ts.add(new Student("王五", 25));
System.out.println(ts);
}
}
public class TreeSetDemo4 {
public static void main(String[] args) {
// 覆盖默认排序,其中 String 默认按照字典顺序排序,Integer 和 Double 默认升序排序
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
ts.add("a");
ts.add("aa");
ts.add("aaaa");
ts.add("aaa");
System.out.println(ts);
}
}
范例:自定义类排序
package cn.stone.demo;
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
if (this.age > o.age) {
return 1;
}else if (this.age < o.age) {
return -1;
}else {
return this.name.compareTo(o.name);
}
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三",20));
all.add(new Person("张三",20));
all.add(new Person("李四",20));
all.add(new Person("王五",19));
all.add(new Person("赵六",21));
System.out.println(all);
}
}
输出:[Person [name=王五, age=19], Person [name=张三, age=20], Person [name=李四, age=20], Person [name=赵六, age=21]]
虽然TreeSet依靠Comparable进行重复元素判断,但是HashSet子类却无法依靠Comparable接口判断重复元素。从真正意义上来讲,判断重复元素依靠的不是Comparable(只有排序的时候才依靠Comparable),所有的重复元素的判断依赖于Object类的两个方法:
- hash码:public int hashCode();
- 对象比较:public Boolean equals(Object obj);
在进行对象比较的过程中,首先会使用hashCode()与已保存在集合中的对象的hashCode()进行比较,如果相同,再使用equals()方法进行属性的依次判断,如果全部相同,则为相同元素。
范例:消除重复元素
package cn.stone.demo;
import java.util.HashSet;
import java.util.Set;
class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Set<Person> all = new HashSet<Person>();
all.add(new Person("张三",20));
all.add(new Person("张三",20));
all.add(new Person("李四",20));
all.add(new Person("王五",19));
all.add(new Person("赵六",21));
System.out.println(all);
}
}
输出:[Person [name=赵六, age=21], Person [name=李四, age=20], Person [name=王五, age=19], Person [name=张三, age=20]]
HashSet
HashSet
是 Java 集合框架(Java Collections Framework)中的一部分,它实现了 Set
接口。HashSet
提供了一个不包含重复元素的集合。它是基于哈希表的实现,这意味着它使用哈希码(Hash Code)来存储和检索元素。JDK 8 的哈希表底层由数组、链表和红黑树组成。实际工作中常用。
以下是关于 HashSet
的一些重要特点和用法:
- 不重复元素:
HashSet
不允许存储重复的元素。如果尝试添加已存在的元素,该操作会被忽略,集合的大小不会增加。 - 无序性:
HashSet
不保证元素的顺序(插入顺序或任何特定的顺序)。 - 基于哈希表:
HashSet
使用哈希表来存储元素,这意味着查找、添加和删除操作的时间复杂度通常是 O(1)(平均情况下),但在最坏的情况下可能是 O(n),这取决于哈希冲突(Hash Collisions)的数量。 - 可变性:
HashSet
是可变的,意味着你可以添加或删除元素。 - 线程不安全:
HashSet
不是线程安全的。如果需要在多线程环境中使用它,可以考虑使用Collections.synchronizedSet()
方法或者CopyOnWriteArraySet
类。 - 迭代:可以使用迭代器(
Iterator
)或增强的 for 循环来遍历HashSet
中的元素。
范例:HashSet 集合的基本使用
package cn.stone.demo;
import java.util.HashSet;
import java.util.Set;
public class TestDemo {
public static void main(String[] args) throws Exception {
Set<String> all = new HashSet<String>();
all.add("Hello");
all.add("Hello");
all.add("World");
System.out.println(all);
}
}
输出:[Hello, World]
集合中重复的数据并没有被保存,并且保存的数据也是无序的。
对于自定义对象的 HashSet 集合,在增加对象的过程中,首先会使用对象的 hashCode()
与已保存在集合中的对象的 hashCode()
进行比较,如果相同,再使用对象 equals()
方法进行比较,如果全部相同,则为相同元素,不会被添加。如果对象的 hashCode()
方法固定返回相同的值,且 equals()
方法比较结果为不通,则数据都会挂在一个索引下面。
创建 HashSet 集合,内部默认会存在一个长度为 16 的数组。调用集合的添加方法时,会使用对象的 hashCode()
方法计算出应存入的索引位置。
当数组中的元素个数达到了 16*0.75(加载因子)=12 时,扩容原数组 2 倍大小。当链表挂载的元素超过了 8(域值)个,并且数组长度没有超过 64 时,扩容原数组 2 倍大小。扩容后会重新计算对象的 Hash 值并挂载到对应的位置,以减少单个索引下挂载的元素数量。
当链表挂载的元素超过了 8(域值)个,并且数组长度达到超过 64 时,会将链表转为红黑树。
范例:自定义对象的 HashSet 集合的使用
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
System.out.println("equals方法执行了...");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
public class HashSetDemo2 {
/*
HashSet集合存储自定义对象
*/
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<Student>();
hs.add(new Student("张三", 23));
hs.add(new Student("李四", 24));
hs.add(new Student("王五", 25));
hs.add(new Student("王五", 25));
System.out.println(hs);
}
}
LinkedHashSet
LinkedHashSet
是 Java 集合框架(Java Collections Framework)中的一个类,它实现了 Set
接口,并且内部使用了链表(Linked List)和哈希表(Hash Table)来维护元素的插入顺序。LinkedHashSet
提供了所有 Set
接口的特性,如元素唯一性,同时还保持了元素的插入顺序。
主要特性:
- 元素唯一性:与
HashSet
类似,LinkedHashSet
也不允许有重复的元素。当尝试插入一个已经存在的元素时,LinkedHashSet
会忽略这次插入操作。 - 保持插入顺序:
LinkedHashSet
使用链表来维护元素的插入顺序。当遍历LinkedHashSet
时,会按照元素的插入顺序来看到它们。 - 基于哈希表:虽然
LinkedHashSet
使用了链表来维护顺序,但它仍然基于哈希表来存储元素。这意味着查找、添加和删除操作的平均时间复杂度都是 O(1)。 - 迭代顺序:由于
LinkedHashSet
维护了元素的插入顺序,因此迭代(遍历)LinkedHashSet
的结果总是按照元素的插入顺序。
注意:
- 由于
LinkedHashSet
维护了元素的插入顺序,因此它在使用时可能会比HashSet
消耗更多的内存和稍微慢一些(尤其是在迭代时)。但在大多数情况下,这种性能差异是可以接受的。 - 如果只关心元素的唯一性,而不关心元素的插入顺序,那么使用
HashSet
可能是一个更好的选择。
import java.util.LinkedHashSet;
public class LinkedHashSetExample {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
// 添加元素
set.add("A");
set.add("B");
set.add("C");
set.add("D");
// 尝试添加重复元素,这将被忽略
set.add("A");
// 遍历集合
for (String s : set) {
System.out.print(s + " "); // 输出:A B C D
}
}
}
Output
Iterator
Iterator 是专门的迭代输出接口,所谓的迭代输出就是对元素逐个进行判断,判断其是否有内容,如果有内容则把内容取走。此接口在使用时也需要指定泛型,当然在此处指定的泛型类型最好与集合中的泛型类型一致。常用的方法如下:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean hasNext() | 普通 | 判断是否有下一个值 |
2 | public E next() | 普通 | 取出当前元素 |
3 | public void remove() | 普通 | 移除当前元素 |
使用 Collection 接口的 iterator() 方法取得 Iterator 接口的实例化对象。
范例:使用 Iterator 输出集合数据
package cn.stone.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("Hello");
all.add("World");
Iterator<String> iter = all.iterator(); //为Iterator实例化
while (iter.hasNext()) {
String str = iter.next();
System.out.print(str + ",");
}
}
}
输出:Hello,Hello,World,
此时程序利用了 Iterator 接口进行了输出,而对于 Collection 的所有子接口,都会存在 iterator() 方法,即 Collection 接口的所有子接口都支持 Iterator 接口输出。
在日后进行项目开发时,只要是遇到集合对象的输出问题,一定要使用 Iterator 接口完成输出。
注意:
在循环中,next() 方法最好只调用一次。
ListIterator
Iterator 可以完成由前向后的单向输出操作,如果希望完成由前向后和由后向前输出的话,那么就可以利用 ListIterator 接口完成,此接口是 Iterator 的子接口,在 ListIterator 接口中主要使用以下两个扩充方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean hasPrevious() | 普通 | 判断是否有前一个元素 |
2 | public E previous() | 普通 | 取出前一个元素 |
如果要取得 ListIterator 接口的实例化对象,只能依靠List接口。使用List接口如下方法:
public ListIterator<E> listIterator()
范例:执行双向迭代
package cn.stone.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("Hello");
all.add("World");
ListIterator<String> iter = all.listIterator();
System.out.println("由前向后输出:");
while (iter.hasNext()) {
String str = iter.next();
System.out.print(str + ",");
}
System.out.println();
System.out.println("由后向前输出:");
while (iter.hasPrevious()) {
String str = iter.previous();
System.out.print(str + ",");
}
}
}
输出:
由前向后输出:
Hello,Hello,World,
由后向前输出:
World,Hello,Hello,
对于由后向前的输出操作,在进行前一定要首先发生由前向后的输出。由于此输出接口只有 Lis t可以使用,所以在开发中几乎不会出现。
范例:并发修改异常
public class ListDemo3 {
/*
并发修改异常 : ConcurrentModificationException
场景:使用[迭代器]遍历集合的过程中,调用了[集合对象]的修改,删除方法,就会出现此异常
解决方案: 迭代器的遍历过程中,不允许使用集合对象的修改或删除,那就使用迭代器自己的添加或删除方法
删除方法 : 普通的迭代器有
添加方法 : 普通的迭代器没有,需要使用List集合特有的迭代器
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("眼瞅着你不是真正的开心");
list.add("温油");
list.add("离开俺们这旮表面");
list.add("伤心的人别扭秧歌");
list.add("私奔到东北");
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("温油")) {
it.remove();
it.add("哈哈");
}
}
System.out.println(list);
}
}
Enumeration
在E numeration 接口中只定义了两个方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Boolean hasMoreElements() | 普通 | 判断是否有下一个值 |
2 | public E nextElement() | 普通 | 取出当前元素 |
如果要取得 Enumeration 的实例化对象,只能够依靠 Vector 类完成,在 Vector 子类中定义了方法:
public Enumeration<E> elements()
范例:使用 Enumeration 进行输出
package cn.stone.demo;
import java.util.Enumeration;
import java.util.Vector;
public class TestDemo {
public static void main(String[] args) throws Exception {
Vector<String> all = new Vector<String>();
all.add("Hello");
all.add("Hello");
all.add("World");
Enumeration<String> enu = all.elements();
while (enu.hasMoreElements()) {
String str = enu.nextElement();
System.out.print(str + ",");
}
}
}
输出:Hello,Hello,World,
增强 for 循环
使用增强 for 循环对集合类完成输出,其底层就是迭代器。
范例:使用增强 for 循环输出集合
package cn.stone.demo;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("Hello");
all.add("World");
for (String string : all) {
System.out.print(string + ",");
}
}
}
输出:Hello,Hello,World,
对于 JDK 8,还可以使用集合的 forEach()
方法进行遍历,其是对增强 for 循环的封装。
all.forEach(str -> System.out.println(str));
Stream
Java Stream API 是 Java 8 引入的一个新特性,它允许我们以声明性方式处理数据集合(如 List、Set 等)。与传统的集合操作(如 forEach 循环)相比,Stream API 提供了一种更加高效、简洁和易读的方式来处理数据。
特点:
- 函数式编程风格:Stream API 采用了函数式编程的思想,使得代码更加简洁、易于理解和维护。
- 惰性求值:Stream API 的操作是惰性的,即只有在需要结果时才会真正执行计算。这允许我们构建复杂的查询逻辑,而无需立即处理整个数据集。
- 内部迭代:与传统的外部迭代(如 forEach 循环)不同,Stream API 使用内部迭代来遍历数据。这意味着我们不需要显式地编写循环代码,而是可以通过链式调用各种操作来定义数据处理流程。
- 并行处理:Stream API 支持并行处理,可以自动将数据划分为多个部分并在多个线程上并行执行操作。这有助于充分利用多核处理器资源,提高数据处理性能。
Stream API 提供了一系列基本操作,包括:
- map:将流中的每个元素映射到另一个值,并返回一个新的流。
- filter:过滤流中的元素,只保留满足指定条件的元素。
- reduce:将流中的元素组合成一个单一的结果。
- collect:将流中的元素收集到一个结果容器(如 List、Set 或 Map)中。
- sorted:对流中的元素进行排序。
- limit 和 skip:限制流的元素数量或跳过流中的前几个元素。
- anyMatch、allMatch 和 noneMatch:检查流中的元素是否满足某个条件。
范例:使用 Stream API 简化集合操作
public class StreamDemo {
/*
需求:按照下面的要求完成集合的创建和遍历
1. 创建一个集合,储存多个字符串元素
2. 把集合中所有以“张”开头的元素储存到一个新的集合
3. 把“张”开头的集合中长度为3的元素存储到一个新的集合
4. 遍历上一步得到的集合中的元素输出
*/
public static void main(String[] args) {
// 1. 创建一个集合,储存多个字符串元素
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张良");
list.add("王二麻子");
list.add("谢广坤");
list.add("张三丰");
list.add("张翠山");
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
}
private static void method(List<String> list) {
// 2. 把集合中所有以“张”开头的元素储存到一个新的集合
ArrayList<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) {
list1.add(s);
}
}
// 3. 把“张”开头的集合中长度为3的元素存储到一个新的集合
ArrayList<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.length() == 3) {
list2.add(s);
}
}
// 4. 遍历上一步得到的集合中的元素输出
list2.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
}
范例:获取 Stream 流对象
public class StreamDemo1 {
/*
获取 Stream 流对象演示
- 将数据放在流水线的传送带上
1. 集合获取 Stream 流对象 (使用 Collection 接口中的默认方法)
default Stream<E> stream()
* Map 集合获取 Stream 流对象, 需要间接获取
- map.entrySet().stream()
2. 数组获取 Stream 流对象 (使用 Arrays 数组工具类中的静态方法)
static <T> Stream<T> stream (T[] array)
3. 零散的数据获取 Stream 流对象 (使用 Stream 类中的静态方法)
static <T> Stream<T> of(T... values)
*/
public static void main(String[] args) {
Stream.of(11, 22, 33, 44, 55, 66).forEach(s -> System.out.println(s));
Stream.of("张三", "李四", "王五").forEach(s -> System.out.println(s));
}
private static void method2() {
int[] arr1 = {11, 22, 33};
double[] arr2 = {11.1, 22.2, 33.3};
Arrays.stream(arr1).forEach(s -> System.out.println(s));
Arrays.stream(arr2).forEach(s -> System.out.println(s));
}
private static void method1() {
List<String> list = new ArrayList<String>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
list.stream().forEach(s -> System.out.println(s));
Set<String> set = new HashSet<String>();
set.add("张三丰");
set.add("张无忌");
set.add("张翠山");
set.add("王二麻子");
set.add("张良");
set.add("谢广坤");
set.stream().forEach(s -> System.out.println(s));
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三丰", 100);
map.put("张无忌", 35);
map.put("张翠山", 55);
map.put("王二麻子", 22);
map.put("张良", 30);
map.put("谢广坤", 55);
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
entrySet.stream().forEach(s -> System.out.println(s));
}
}
范例:使用 Stream 流中间方法操作数据
public class StreamDemo2 {
/*
Stream 流的中间操作方法
- 操作后返回 Stream 对象, 可以继续操作
Stream<T> filter(Predicate<? super T> predicate) 用于对流中的数据进行过滤
Stream<T> limit(long maxSize) 获取前几个元素
Stream<T> skip(long n) 跳过前几个元素
Stream<T> distinct() 去除流中重复的元素依赖 (hashCode 和 equals方法)
static <T> Stream<T> concat(Stream a, Stream b) 合并a和b两个流为一个流
注意事项: 流对象已经被消费过(使用过), 就不允许再次消费了.
*/
public static void main(String[] args) {
// 需求: 将集合中以 【张】 开头的数据,过滤出来并打印在控制台
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
System.out.println("---------------------------");
// 需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(s -> System.out.println(s));
System.out.println("---------------------------");
// 需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(s -> System.out.println(s));
System.out.println("---------------------------");
// 需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(s -> System.out.println(s));
System.out.println("---------------------------");
// 需求4:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
// 需求5:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
// 需求6:合并需求4和需求5得到的流,并把结果在控制台输出
Stream<String> s3 = Stream.concat(s1, s2);
System.out.println("---------------------------");
// 需求7:合并需求4和需求5得到的流,并把结果在控制台输出,要求字符串元素不能重复
s3.distinct().forEach(s -> System.out.println(s));
System.out.println("---------------------------");
}
}
范例:使用 Stream 流终结方法
public class StreamDemo3 {
/*
Stream 流的终结操作方法
- 流水线中的最后一道工序
public void forEach (Consumer action) 对此流的每个元素执行遍历操作
public long count () 返回此流中的元素数
*/
public static void main(String[] args) {
long count = Stream.of(11, 22, 33, 44, 55, 66).filter(s -> s % 2 == 0).count();
System.out.println(count);
}
}
范例:将 Stream 流处理后的数据收集到集合
public class StreamDemo4 {
/*
Stream流的收集操作
public R collect (Collector c) : 将流中的数据收集到集合
Collectors
public static <T> Collector toList()
public static <T> Collector toSet()
public static Collector toMap(Function keyMapper , Function valueMapper)
*/
public static void main(String[] args) {
List<Integer> list1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(s -> s % 2 == 0).collect(Collectors.toList());
System.out.println(list1);
Set<Integer> set1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10).filter(s -> s % 2 == 0).collect(Collectors.toSet());
System.out.println(set1);
}
}
public class StreamDemo5 {
/*
创建一个 ArrayList 集合,并添加以下字符串
"张三,23"
"李四,24"
"王五,25"
保留年龄大于等于 24 岁的人,并将结果收集到 Map 集合中,姓名为键,年龄为值
*/
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张三,23");
list.add("李四,24");
list.add("王五,25");
Map<String, Integer> map = list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return Integer.parseInt(s.split(",")[1]) >= 24;
}
}).collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.split(",")[0];
}
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s.split(",")[1]);
}
}));
System.out.println(map);
}
}
public class StreamTest {
/*
现在有两个 ArrayList 集合,分别存储6名男演员和6名女演员,要求完成如下的操作:
1. 男演员只要名字为3个字的前两人
2. 女演员只要姓林的,并且不要第一个
3. 把过滤后的男演员姓名和女演员姓名合并到一起
4. 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
5. 演员类 Actor, 里面有一个成员变量, 一个带参构造方法,以及成员变量对应的get/set方法
*/
public static void main(String[] args) {
ArrayList<String> manList = new ArrayList<String>();
manList.add("周润发");
manList.add("成龙");
manList.add("刘德华");
manList.add("吴京");
manList.add("周星驰");
manList.add("李连杰");
ArrayList<String> womanList = new ArrayList<String>();
womanList.add("林心如");
womanList.add("张曼玉");
womanList.add("林青霞");
womanList.add("柳岩");
womanList.add("林志玲");
womanList.add("王祖贤");
Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(2);
Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
Stream<String> stream = Stream.concat(manStream, womanStream);
stream.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
Actor a = new Actor(name);
System.out.println(a);
}
});
}
}
class Actor {
private String name;
public Actor() {
}
public Actor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Actor{name = " + name + "}";
}
}
Map
Java 中的 Map
接口是一个非常重要的集合接口,它用于存储键值对(Key-Value Pair)的数据。在 Map
中,每个键(Key)都是唯一的,并且与某个值(Value)相关联。
常用实现类:
HashMap
:基于哈希表的实现,提供了快速的插入和查找操作。它不保证映射的顺序,特别是它不保证该顺序恒久不变。TreeMap
:基于红黑树的实现,能够对键进行自然排序或根据提供的Comparator
进行排序。LinkedHashMap
:这是HashMap
的一个子类,它维护了一个双向链表来记录插入顺序或访问顺序。ConcurrentHashMap
:适用于并发环境的哈希表实现,它提供了比HashMap
和Hashtable
更好的并发性能。Hashtable
:是Map
接口的一个早期实现,它在多线程环境中是同步的,但通常性能较低,现在已不常用。
Map 接口的主要操作方法如下:
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public V put(K key,V value) | 普通 | 向集合中保存数据,如果键相同则覆盖 |
2 | public V remove(Object key) | 普通 | 根据键删除键值对元素 |
3 | public void clear() | 普通 | 移除所有的键值对元素 |
4 | public boolean isEmpty() | 普通 | 判断集合是否为空 |
5 | public int size() | 普通 | 集合的长度,也就是集合中键值对的个数 |
6 | public boolean containsKey(Object key) | 普通 | 判断集合是否包含指定的键 |
7 | public boolean containsValue(Object value) | 普通 | 判断集合是否包含指定的值 |
8 | public V get(Object key) | 普通 | 通过指定的 Key 取得对应的 Value |
9 | public Set<K> ketSet() | 普通 | 将 Map 中的所有 Key 以 Set 集合的方法返回 |
10 | public Set<Map.Entry<K,V>> entrySet() | 普通 | 将 Map 集合变为 Set 集合 |
public class MapDemo1 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
// 添加元素
map.put("张三", "北京");
map.put("李四", "北京");
map.put("王五", "上海");
map.put("王五", "深圳");
System.out.println(map);
// 删除元素
map.remove("王五");
System.out.println(map);
// 判断集合是否为空
System.out.println(map.isEmpty());
// 集合的长度
System.out.println(map.size());
// 判断集合中是否包含指定键
System.out.println(map.containsKey("张三")); // out: true
// 判断集合中是否包含指定值
System.out.println(map.containsValue("深圳")); // out: false
// 清空元素
map.clear();
System.out.println(map);
}
}
双列集合底层的数据结构, 都是针对于键有效, 跟值没有关系:
- HashMap : 键为哈希表结构,唯一 (自定义对象需要重写
hashCode
和equals
方法) - TreeMap : 键为红黑树结构,有序 (实现
Comparable
接口, 重写compareTo
方法) - LinkedHashMap : 键为哈希表和双向链表结构,唯一, 且可以保证存取顺序
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person{name = " + name + ", age = " + age + "}";
}
}
public class MapDemo2 {
public static void main(String[] args) {
showLinkedHashMap();
}
private static void showTreeMap() {
TreeMap<Person, String> tm = new TreeMap<>();
tm.put(new Person("李四", 24), "上海");
tm.put(new Person("张三", 23), "北京");
tm.put(new Person("王五", 25), "成都");
tm.put(new Person("赵六", 25), "成都");
System.out.println(tm);
}
private static void showHashMap() {
HashMap<Person, String> hm = new HashMap<>();
hm.put(new Person("李四", 24), "上海");
hm.put(new Person("张三", 23), "北京");
hm.put(new Person("王五", 25), "成都");
hm.put(new Person("王五", 25), "成都");
System.out.println(hm);
}
private static void showLinkedHashMap() {
LinkedHashMap<Person, String> hm = new LinkedHashMap<>();
hm.put(new Person("李四", 24), "上海");
hm.put(new Person("张三", 23), "北京");
hm.put(new Person("王五", 25), "成都");
hm.put(new Person("王五", 25), "成都");
System.out.println(hm);
}
}
HashMap
Java 中的 HashMap
是一个基于哈希表的 Map
接口的实现。它允许你存储键值对(key-value pairs)的集合,并且可以根据键快速检索值。HashMap
提供了高效的插入和查找操作,并且不保证映射的顺序,特别是它不保证该顺序恒久不变。
特点:
- 基于哈希表:
HashMap
使用哈希表数据结构来存储键值对。这意味着它使用键的哈希码(hashCode)来计算键的存储位置。 - 不保证顺序:
HashMap
不保证映射的顺序,特别是它不保证该顺序恒久不变。当迭代HashMap
时,可能会发现元素的顺序与它们被插入的顺序不同,并且在不同的 Java 实现或不同版本的 Java 中也可能有所不同。 - 允许使用 null 键和值:
HashMap
允许使用null
作为键和值(至多一个null
键,但可以有多个null
值,因为不同的键可以映射到相同的值)。 - 非同步:
HashMap
不是线程安全的。如果多个线程同时修改HashMap
,那么它必须在外部进行同步。 - 高效:
HashMap
提供了非常高效的插入和查找操作,因为哈希表允许在常数时间内(平均情况下)进行这些操作。 - 容量和负载因子:
HashMap
有一个初始容量(默认为 16)和一个负载因子(默认为 0.75)。当HashMap
中的元素数量超过容量和负载因子的乘积时,HashMap
会自动进行扩容,以容纳更多的元素。扩容是一个相对昂贵的操作,因为它需要重新哈希所有元素。
HashMap 是 Map 接口中使用最多的一个子类,定义如下:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
范例:验证 Map 的方法
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// 创建一个 HashMap 实例
Map<String, Integer> hashMap = new HashMap<>();
// 添加键值对
hashMap.put("Apple", 1);
hashMap.put("Banana", 2);
hashMap.put("Cherry", 3);
// 检索特定键的值
Integer value = hashMap.get("Banana");
System.out.println("Value for 'Banana': " + value);
// 检查键是否存在
boolean containsKey = hashMap.containsKey("Cherry");
System.out.println("Does the HashMap contain 'Cherry'? " + containsKey);
// 遍历 HashMap 中的键值对
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
Map 集合中的 Key 不允许重复。
Map 集合和 Collection 集合在保存数据后的操作上的不同点如下:
- Collection 接口设置完内容的目的是为了输出。
- Map 接口设置完内容的目的是为了查找。
范例:取得全部的 Key,全部的 Key 通过 Set 集合返回,再遍历 Set 集合,获取对应的 Value
package cn.stone.demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class TestDemo {
public static void main(String[] args) throws Exception {
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(3, "张三");
map.put(null, "无名氏");
map.put(3, "李四");
map.put(1, "王五");
map.put(0, "赵六");
Set<Integer> set = map.keySet();
Iterator<Integer> iter = set.iterator();
while (iter.hasNext()) {
Integer key = iter.next();
System.out.println(key + "--->" + map.get(key));
}
}
}
输出:
null--->无名氏
0--->赵六
1--->王五
3--->李四
TreeMap
Java 中的 TreeMap 是一个有序的 Key-Value 集合,它基于红黑树(Red-Black Tree)的数据结构实现。TreeMap 是 Java 集合框架(Collections Framework)的一部分,并且实现了 NavigableMap 接口,这意味着它支持一系列的导航方法,比如返回有序的 Key 集合。
特点:
- 有序性:TreeMap 通过红黑树数据结构来维护键值对的顺序,因此它能够保证键值对按照键的自然顺序或自定义顺序排列。
- 元素唯一:TreeMap 中的键是唯一的,相同的键只能存储一个元素。如果在 TreeMap 中插入一个已经存在的键,则新的值会覆盖原有的值。
- 映射性:TreeMap 是一种映射表数据结构,可以用键来查找对应的值,同时也支持键值对的遍历操作。
- 排序:TreeMap 的键可以自然排序或者通过实现 Comparable 接口或 Comparator 接口来自定义排序规则。默认情况下,TreeMap 按照键的升序进行排序。
- 增删改查性能良好:由于 TreeMap 底层采用红黑树实现,因此它具有较好的增删改查性能。
使用场景:
- 存储键值对且需要排序:当需要按照某种顺序(例如升序或降序)存储键值对时,可以使用 TreeMap。因为 TreeMap 内部的红黑树会自动按照键进行排序,所以可以快速地获取某个范围的键值对,也可以进行快速的键查找和插入操作。
- 在已排序的 Map 上执行基于范围的查找:使用 TreeMap 可以快速地获得某个范围的键值对,比如在已经排好序的 Map 中查找某个范围内的键值对。这对于需要频繁进行基于范围的查询时非常有用。
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
// 创建一个 TreeMap 实例
Map<String, Integer> treeMap = new TreeMap<>();
// 添加键值对
treeMap.put("Zebra", 1);
treeMap.put("Apple", 2);
treeMap.put("Cat", 3);
treeMap.put("Dog", 4);
// 打印 TreeMap 中的所有键值对
System.out.println("TreeMap: " + treeMap);
// 检索特定键的值
Integer value = treeMap.get("Apple");
System.out.println("Value for 'Apple': " + value);
// 检查键是否存在
boolean containsKey = treeMap.containsKey("Cat");
System.out.println("Does the TreeMap contain 'Cat'? " + containsKey);
// 遍历 TreeMap 中的键值对
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 自定义排序(假设按照字符串长度排序)
// 首先需要创建一个自定义的 Comparator
Map<String, Integer> lengthSortedTreeMap = new TreeMap<>((k1, k2) -> Integer.compare(k1.length(), k2.length()));
// 添加键值对
lengthSortedTreeMap.put("LongWord", 5);
lengthSortedTreeMap.put("Short", 6);
lengthSortedTreeMap.put("Medium", 7);
// 打印按长度排序的 TreeMap
System.out.println("TreeMap sorted by length: " + lengthSortedTreeMap);
}
}
Hashtable
Java 中的 Hashtable
是一个基于哈希表的Map接口的实现,用于存储键值对(Key-Value Pairs)。与 HashMap
类似,Hashtable
也使用哈希函数将键映射到存储桶的索引位置,以实现快速的数据检索。
范例:使用Hashtable为接口实例化
package cn.stone.demo;
import java.util.Hashtable;
import java.util.Map;
public class TestDemo {
public static void main(String[] args) throws Exception {
Map<Integer,String> map = new Hashtable<Integer,String>();
map.put(3, "张三");
map.put(3, "李四");
map.put(1, "王五");
map.put(0, "赵六");
System.out.println(map.get(3));
System.out.println(map.get(10));
}
}
输出:
李四
null
注意:Hashtable 不能设置为 Null,否则运行时就会出现 “NullPointerException”。、
HashMap 和 Hashtable 的区别:
No. | 区别 | HashMap | Hashtable |
---|---|---|---|
1 | 推出时间 | JDK1.2 | JDK1.0 |
2 | 性能 | 采用异步处理方式,性能更高 | 采用同步处理方式,性能相对较低 |
3 | 安全性 | 非线程安全 | 线程安全 |
4 | 设置 Null | 允许将 Key 或 Value 设置为 Null | 不允许出现 Null,否则出现空指针异常 |
在开发中,优先考虑的是 HashMap 子类。
Output
集合的输出都使用 Iterator 完成,而 Map 接口中并没有提供像 Collection 接口那样的 iterator()
方法,如果想真正实现 Map 接口通过 Iterator 输出,需要使用 Map.Entry 接口:
public static interface Map.Entry<K,V>
这是一个在 Map 接口中使用 static
定义的一个内部接口,这个内部接口有以下两个常用方法:
- 取得当前的 Key:public K getKey();
- 取得当前的 Value:public V getValue();
在 Map 集合和 Collection 集合中保存的最大区别是 Collection 直接保存的是要操作的对象,而 Map 集合是将保存的 Key 和 Value 变成了一个 Map.Entry 对象,通过这个对象包装了 Key 和 Value,所以根据这一特征,就可以给出 Map 使用 Iterator 输出的操作步骤:
- 使用 Map 接口中的
entrySet()
方法,将 Map 集合变为 Set 集合; - 取得了 Set 接口实例后就可以利用
iterator()
方法取得 Iterator 的实例化对象; - 使用 Iterator 迭代找到每一个 Map.Entry 对象,并进行 Key 和 Value 的分离。
范例:使用 Iterator 输出 Map 集合
package cn.stone.demo;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class TestDemo {
public static void main(String[] args) throws Exception {
Map<Integer,String> map = new Hashtable<Integer,String>();
map.put(3, "张三");
map.put(3, "李四"); //重复数据,value会被新内容覆盖
map.put(1, "王五");
map.put(0, "赵六");
Set<Entry<Integer, String>> set = map.entrySet();
Iterator<Entry<Integer, String>> iter = set.iterator();
while (iter.hasNext()) {
Entry<Integer, String> ent = iter.next();
System.out.println(ent.getKey() + "-->" + ent.getValue());
}
}
}
输出:
3-->李四
1-->王五
0-->赵六
本程序首先将 Map 集合变为了 Set 集合,而此时的 Set 集合中保存的是多个 Map.Entry 接口对象,当取得了 Set 集合后就可以利用 iterator()
方法为 Iterator 接口实例化并进行迭代输出,通过每一个 Map.Entry 对象就可以取得所封装的 Key 及 Value。
还可以使用增强 for 循环遍历取得 Key 及 Value:
private static void method2(Map<Integer,String> map) {
// 1. 获取到所有的键值对对象
Set<Map.Entry<String, String>> entrySet = map.entrySet();
// 2. 遍历 set 集合获取到每一个键值对对象
for (Map.Entry<String, String> entry : entrySet) {
// 3. 通过键值对对象,获取键和值
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
还可以使用 forEach()
方法遍历取得 Key 及 Value:
map.forEach((key, value) -> System.out.println(key + "---" + value));
范例:请统计字符串中每一个字符出现的次数
public class MapTest1 {
/*
需求:字符串 ababc
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出格式:
a(2)b(2)c(1)
*/
public static void main(String[] args) {
String info = "ababc";
// 1. 准备map集合,用于统计字符的次数
TreeMap<Character, Integer> tm = new TreeMap<>();
// 2. 拆分字符串为数组
char[] charArray = info.toCharArray();
// 3. 循环拿出每一个字符
for (char c : charArray) {
// 4. 判断字符是否存在于TreeMap
if (!tm.containsKey(c)) {
// 如果不存在则新建一个键值对
tm.put(c, 1);
} else {
// 如果存在则执行+1操作
tm.put(c, tm.get(c) + 1);
}
}
// 5. 按照格式输出结果
StringBuilder sb = new StringBuilder();
tm.forEach((key, value) -> sb.append(key).append("(").append(value).append(")"));
System.out.println(sb);
}
}
范例:集合嵌套
public class MapTest2 {
/*
需求:
定义一个Map集合,键用表示省份名称,值表示市,但是市会有多个
添加完成后,遍历结果:
格式如下:
江苏省=南京市,扬州市,苏州市,无锡市,常州市
*/
public static void main(String[] args) {
HashMap<String, List<String>> hm = new HashMap<>();
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "南京市", "扬州市", "苏州市", "无锡市", "常州市");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "武汉市", "孝感市", "十堰市", "宜宾市", "鄂州市");
ArrayList<String> list3 = new ArrayList<>();
Collections.addAll(list3, "成都市", "绵阳市", "自贡市", "攀枝花市", "泸州市");
hm.put("江苏省", list1);
hm.put("湖北省", list2);
hm.put("四川省", list3);
hm.forEach(new BiConsumer<String, List<String>>() {
@Override
public void accept(String key, List<String> value) {
System.out.print(key + "=");
for (int i = 0; i < value.size() - 1; i++) {
System.out.print(value.get(i) + ",");
}
System.out.println(value.get(value.size() - 1));
}
});
}
}
在 Collection 接口中存在一个增加一组集合的方法:
public boolean addAll(Collection<? extends E> c);
可以将 List 集合加到 Set 集合中去。
范例:集合转换
package cn.stone.demo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("Hello");
list.add("World");
Set<String> set = new HashSet<String>();
set.addAll(list); //将 List 集合加到 Set 中
Map<UUID,String> map = new HashMap<UUID,String>();
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
map.put(UUID.randomUUID(), iter.next()); //数据保存到 Map 集合
}
Iterator<Entry<UUID, String>> iterMap = map.entrySet().iterator();
while (iterMap.hasNext()) {
Entry<UUID, String> ent = iterMap.next();
System.out.println(ent.getKey() + "-->" + ent.getValue());
}
}
}
输出:
7e9e12b9-1515-4386-b304-2bd566ff327c-->World
ff76240d-20d4-4b96-99a6-4a5a2107ece5-->Hello
Key
之前的Map集合中都是使用了系统类作为Map的key,也可以使用自定义的类作为key,因为key属于查找操作,所以要想找到符合的key,那么作为key所在的类就必须覆写Object类的两个方法:hashCode()和equals()。
范例:自定义类作为key。
package cn.stone.demo;
import java.util.HashMap;
import java.util.Map;
class Person{
private String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Map<Person,String> map = new HashMap<Person,String>();
map.put(new Person("张三"), new String("zs")); //匿名对象设置
System.out.println(map.get(new Person("张三"))); //匿名对象查找
}
}
输出:zs
在实际开发中,作为key的类型几乎都是String类。
Stack
栈是采用先进后出的数据存储方式,每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出。在Java中使用Stack类进行栈的操作,Stack类是Vector的子类,Stack类的定义如下:
public class Stack<E> extends Vector<E>
使用时不使用Vector类定义的方法,而使用Stack类自己的方法:
- 入栈操作:public E push(E item);
- 出栈操作:public E pop()
范例:观察栈的基本操作
package cn.stone.demo;
import java.util.Stack;
public class TestDemo {
public static void main(String[] args) throws Exception {
Stack<String> all = new Stack<String>();
all.add("A");
all.add("B");
all.add("C");
System.out.println(all.pop());
System.out.println(all.pop());
System.out.println(all.pop());
System.out.println(all.pop()); //没数据了,出现EmptyStackException
}
}
输出:
C
B
A
Exception in thread "main" java.util.EmptyStackException
at java.util.Stack.peek(Unknown Source)
at java.util.Stack.pop(Unknown Source)
at cn.stone.demo.TestDemo.main(TestDemo.java:12)
出栈的顺序和入栈的顺序刚好相反,如果栈中已经没有数据,则会产生空栈异常。
Collections
Collections 是一个集合工具类,并没有实现 Collection 接口,但是在这个类中,有许多的操作方法,可以方便地进行集合的操作。
常用静态方法:
Collections.addAll(list, "A","B","C")
:批量添加元素到集合。Collections.binarySearch(list, key)
:使用二分查找元素。Collections.sort(list)
:对集合中的内容进行排序。Collections.reverse(list)
:对集合中的内容进行反转。Collections.shuffle(list)
:对集合中的元素进行随机排序。Collections.swap(List list, int i, int j)
:在集合中交换索引为i和j的元素。Collections.fill(List list, Object o)
:用指定的元素替换中的所有元素。Collections.copy(List dest, List src)
:复制集合内容。Collections.min(list)
和Collections.max(list)
:分别返回集合中的最小和最大元素。
范例:使用 Collections 操作
package cn.stone.demo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestDemo {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
Collections.addAll(all, "A","B","C");
System.out.println(all);
Collections.reverse(all);
System.out.println(all);
}
}
输出:
[A, B, C]
[C, B, A]
Properties
属性一般针对于字符串数据,并且所有的字符串数据都会按照 “key=value” 的形式保存,属性操作类主要是针对于属性文件完成。Properties 类本身是 Hashtable 的子类:
public class Properties extends Hashtable<Object,Object>
在使用方法上操作的并不是有 Map 接口定义的方法,而是使用 Properties 自己的方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Properties() | 构造 | 构造一个空的属性类 |
2 | public Properties(Properties defaults) | 构造 | 构造一个指定属性内容的属性类 |
3 | public String getProperty(String key) | 常量 | 根据属性的key取得属性的value,如果没有key则返回null |
4 | public String getProperty(String key,String defaultValue) | 常量 | 根据属性的key取得属性的vallue,如果没有key则返回defaultValue |
5 | public Object setProperty(String key,String value) | 常量 | 设置属性 |
6 | public Set<String> stringPropertyNames() | 普通 | 返回一组不可修改的键 |
7 | public void load(InputStream inStream) | 常量 | 从输入字节流中取得全部的属性内容 |
8 | public void load(Reader reader) | 普通 | 从输入字符流中读取属性列表 |
9 | public void store(OutputStream out,String comments) | 常量 | 将属性内容通过输出字节流输出,同时声明属性的注释 |
10 | public void store(Writer writer, String comments) | 普通 | 将属性内容通过输出字符流输出,同时声明属性的注释 |
所有设置属性的 key 和 value 的类型都为 String。
范例:将属性保存在文件
public class PropertiesTest1 {
public static void main(String[] args) throws IOException {
// 创建集合对象,这个类并不是泛型类,所以没有泛型
Properties prop = new Properties();
// 添加数据
// 虽然是没有泛型,可以添加任意的数据类型,不过一般添加数据的时候不会添加任意类型的数据
// 只会添加字符串类型的数据
prop.put("aaa", "111");
prop.put("bbb", "222");
prop.put("ccc", "333");
// 字节输出流
FileOutputStream fos = new FileOutputStream(new File("D:\\Properties.properties"));
prop.store(fos, "test");
fos.close();
}
private static void method(Properties prop) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Properties.properties"));
Set<Map.Entry<Object, Object>> entries = prop.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
Object key = entry.getKey();
Object value = entry.getValue();
bw.write(key + "=" + value);
bw.newLine();
}
bw.close();
}
}
范例:通过属性文件读取内容
public class PropertiesTest2 {
public static void main(String[] args) throws IOException {
// 创建集合对象
Properties prop = new Properties();
// 创建字节输入流
FileInputStream fis = new FileInputStream("D:\\Properties.properties");
// 读取
prop.load(fis);
// 关流
fis.close();
System.out.println(prop);
}
}
JDBC
JDBC核心组成部分:DriverManager类、Connection接口、Statement接口、PreparedStatement接口和ResultSet接口。代码的操作流程:
- 加载数据库驱动程序。
- 连接数据库。
- 使用语句进行数据库操作。
- 关闭数据库连接。
Connect to DB
- Oracle 10g驱动程序:$ORACLE_HOME/jdbc/lib/classes12.jar
- Oracle 11g驱动程序:$ORACLE_HOME/jdbc/lib/ojdbc6_g.jar
如果向数据库插入中文字符,则需要在再应用服务器和数据库服务器将环境变量NLS_LANG设置为AMERICAN_AMERICA.AL32UTF8。
对于jar包的配置需要分两种情况:
- 情况一:如果现在使用命令行方式开发(不用开发工具),则这个jar包要在CLASSPATH属性中进行配置。
- 情况二:如果现在使用Eclipse进行开发,此时配置CLASSPATH不会起作用,必须在项目属性中配置指定库文件的开发包。配置步骤:项目点右键 ==> 属性 ==> Java Bulid Path ==> Add External Jars。
如果要进行连接,那么首先需要知道如下的几个连接信息:
- 数据库的驱动程序路径(配置的*.jar):oracle.jdbc.driver.OracleDriver;
- 数据库的连接地址:jdbc:oracle:thin:@主机名称:端口号:数据库的实例名称;
- 数据库的用户名:scott;
- 数据库的密码:tiger。
实现JDBC的每一步操作:
第一步:加载数据库驱动程序。
数据库驱动程序给出的是“包.类”,只要看见写出了完整的类名称都应该想到反射,所以驱动程序的加载时通过Class类完成的:Class.forName(驱动程序)。
第二步:通过DriverManager类根据指定的属性内容连接数据库。
java.sql.DriverManager是一个负责取得数据库连接接口(Connection)对象的操作类,在这个类中定义了如下一个可以取得连接的方法:
public static Connection getConnection(String url,String user,String password) throws SQLException
第三步:关闭数据库连接。在Connection接口中定义了close()方法:
public void close() throws SQLException
范例:连接数据库
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Connection conn = null; //每一个Connection对象表示一个数据库连接
Class.forName(DBDRIVER); //加载驱动程序
conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD);
System.out.println(conn);
conn.close();
}
}
输出:oracle.jdbc.driver.T4CConnection@59f95c5d
在JDBC操作中,DriverManager就是一工厂类,专门负责取得Connection接口的实例化对象,而通过Class.forName()可以加载指定子类的名称(包.类),所以JDBC属于工厂设计模式应用。
Statement
Statement接口常用方法如下:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public int executeUpdate(String sql) throws SQLException | 普通 | 执行数据库更新的SQL语句,例如INSERT、UPDATE、DELETE等语句,返回更新的记录数 |
2 | public ResultSet executeQuery(String sql) throws SQLException | 普通 | 执行数据库查询操作,返回一个结果集对象 |
3 | public void close() throws SQLException | 普通 | 关闭操作 |
Statement是一个接口,如果要想取得Statement接口对象,必须依靠Connection接口中提供的createStatement()方法实例化。
public Statement createStatement() throws SQLException
范例:数据库创建脚本
create sequence myseq;
create table member(
mid number,
name varchar2(20) not null,
age number(3),
birthday date,
note clob,
constraint pk_mid primary key(mid),
constraint ck_age check(age between 0 and 200));
使用Statement接口进行数据表的增删改操作:
范例:执行数据的增加操作
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
Statement stmt = conn.createStatement();
String sql = "insert into member(mid,name,age,birthday,note) values(myseq.nextval,'张三',20,sysdate,'Java 培训')";
int len = stmt.executeUpdate(sql);
System.out.println("插入行数" + len);
stmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:插入行数1
范例:执行数据修改操作
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
Statement stmt = conn.createStatement();
String sql = "update member set name='李四',age=19,note='林石科技' where mid=1";
int len = stmt.executeUpdate(sql);
System.out.println("修改行数" + len);
stmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:修改行数1
范例:执行删除操作
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
Statement stmt = conn.createStatement();
String sql = "delete from member where mid=2";
int len = stmt.executeUpdate(sql);
System.out.println("删除行数" + len);
stmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:删除行数1
使用Statement接口进行查询:
当所有的记录返回到ResultSet时,所有的内容都是按照数据类型存放的,所以用户只需要按照数据类型一行行的取出数据即可,ResultSet接口方法如下:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public boolean next() throws SQLException | 普通 | 移动指针并判断是否有数据 |
2 | public 数据类型 getXxx(列的标记) throws SQLException | 普通 | 取得指定类型的数据 |
3 | public void close() throws SQLException | 普通 | 关闭结果集 |
范例:使用ResultSet取出数据
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
Statement stmt = conn.createStatement();
String sql = "select mid,name,age,birthday,note from member";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int mid = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birthday = rs.getDate(4);
String note = rs.getString(5);
System.out.println(mid + "," + name + "," + age + "," + birthday + "," + note);
}
rs.close(); //关闭结果集
stmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:
1,李四,19,2018-08-08,林石科技
3,张三,20,2018-08-08,Java 培训
PreparedStatement
PreparedStatement是Statement的子接口,属于预处理操作,与直接使用Statement不同的是,PreparedStatement在操作时是先在数据表中准备好了一条SQL语句,但是此SQL语句的具体内容暂时不设置,而是之后再进行设置。由于PreparedStatement对象已经编译过,所以其执行速度要高于Statement对象。因此,对于需要多次执行的SQL语句经常使用PreparedStatement对象操作,以提高效率。PreparedStatement的基本操作方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public int executeUpdate() throws SQLException | 普通 | 执行设置的预处理SQL语句 |
2 | public ResultSet executeQuery() throws SQLException | 普通 | 执行数据库查询操作,返回ResultSet |
3 | public void set数据类型(int parameterIndex,数据类型 x) throws SQLException | 普通 | 指定要设置的索引编号,并设置数据 |
4 | public void setDate(int parameterIndex,Date x) throws SQLException | 普通 | 指定要设置的索引编号,并设置java.sql.Date类型的日期内容 |
要取得PreparedStatement接口的实例化对象,使用Connection的如下方法:
public PreparedStatement prepareStatement(String sql) throws SQLException
注意:在PreparedStatement接口中定义的setDate()方法可以设置日期内容,但是此方法使用时,后面的Date类型变量是java.sql.Date,而不是java.util.Date,而java.sql.Date是java.util.Date的子类。所以如果要想将一个java.util.Date类型的内容变为java.sql.Date类型的内容应该使用如下的语句形式:
java.util.Date temp = new Date(); //java.util.Date
java.sql.Date bir = new java.sql.Date(temp.getTime()); //java.sql.Date
在java.util.Date类中存在一个getTime()方法,可以将一个日期时间变为long型数据,而java.sql.Date类的构造方法中可以将一个long型变为java.sql.Date类对象。
范例:通过PreparedStatement执行增加操作
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
String name = "Mr'Smith";
Date birthday = new Date();
int age = 30;
String note = "www.bigexecl.net";
String sql = "insert into member(mid,name,age,birthday,note) values(myseq.nextval,?,?,?,?)";
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
pstmt.setString(1, name); //设置索引数据
pstmt.setInt(2, age);
pstmt.setDate(3, new java.sql.Date(birthday.getTime()));
pstmt.setString(4, note);
int len = pstmt.executeUpdate();
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
在日后的开发中,全部都使用PreparedStatement,而不要使用Statement接口操作。
范例:查询全部数据
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
String sql = "select mid,name,age,birthday,note from member";
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int mid = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birthday = rs.getDate(4);
String note = rs.getString(5);
System.out.println(mid + "," + name + "," + age + "," + birthday + "," + note);
}
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:
1,李四,19,2018-08-08,林石科技
22,Mr'Smith,30,2018-08-08,www.stonecoding.net
3,张三,20,2018-08-08,Java 培训
21,Mr'Smith,30,2018-08-08,www.stonecoding.net
范例:根据id查询数据
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
String sql = "select mid,name,age,birthday,note from member where mid=?";
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
pstmt.setInt(1, 3); //在执行之前设置内容
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
int mid = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birthday = rs.getDate(4);
String note = rs.getString(5);
System.out.println(mid + "," + name + "," + age + "," + birthday + "," + note);
}else {
System.out.println("没有查询结果!");
}
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:3,张三,20,2018-08-08,Java 培训
范例:设置模糊查询
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
String column = "name";
String keyWord = "三";
String sql = "select mid,name,age,birthday,note from member where " + column + " like ?";
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
pstmt.setString(1, "%" + keyWord + "%"); //在执行之前设置内容
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int mid = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birthday = rs.getDate(4);
String note = rs.getString(5);
System.out.println(mid + "," + name + "," + age + "," + birthday + "," + note);
}
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:3,张三,20,2018-08-08,Java 培训
范例:分页显示,Oracle使用ROWNUM完成
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
String column = "name"; //模糊查询列
String keyWord = ""; //没有设置关键字,表示查询全部
int currentPage = 1; //当前所在页
int lineSize = 3; //每页显示的长度
String sql = "select * from (select mid,name,age,birthday,note,rownum rn from member where " + column + " like ? and rownum<=?) temp where temp.rn>?";
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
pstmt.setString(1, "%" + keyWord + "%"); //在执行之前设置内容
pstmt.setInt(2, currentPage * lineSize);
pstmt.setInt(3, (currentPage - 1) * lineSize);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int mid = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
Date birthday = rs.getDate(4);
String note = rs.getString(5);
System.out.println(mid + "," + name + "," + age + "," + birthday + "," + note);
}
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:
1,李四,19,2018-08-08,林石科技
22,Mr'Smith,30,2018-08-08,www.stonecoding.net
3,张三,20,2018-08-08,Java 培训
范例:统计查询,统计数据量,统计函数使用COUNT()完成
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
String column = "name"; //模糊查询列
String keyWord = ""; //没有设置关键字,表示查询全部
String sql = "select count(mid) from member where " + column + " like ?";
Class.forName(DBDRIVER); //加载驱动程序
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD); //每一个Connection对象表示一个数据库连接
PreparedStatement pstmt = conn.prepareStatement(sql); //执行SQL
pstmt.setString(1, "%" + keyWord + "%"); //在执行之前设置内容
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
int count = rs.getInt(1);
System.out.println(count);
}
pstmt.close(); //关闭操作,如果不关闭操作,关闭连接也是一样的
conn.close(); //关闭连接
}
}
输出:4
Batch and Transaction
批处理是指数据库可以一次性的执行多条SQL语句,在JDBC2.0之后,对于Statement和PreparedStatement接口都有了一些新的方法。
- Statement接口增加了两个方法:
- 增加一个执行的SQL:public void addBatch(String sql) throws SQLException,没有执行。
- 一次性执行多条SQL:public int[] executeBatch() throws SQLException。
- PreparedStatement接口增加了一个方法:
- 增加执行的SQL:public void addBatch() throws SQLException。
范例:使用Statement执行一次批处理
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD);
Statement stmt = conn.createStatement();
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三A')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三B')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三C')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三D')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三E')");
int[] data = stmt.executeBatch();
System.out.println(Arrays.toString(data));
conn.close();
}
}
输出:[1, 1, 1, 1, 1]
在JDBC中的所有事务处理都是自动提交的,这就意味着只要执行了SQL语句,那么都会自动的提交数据库的事务。如果上面其中一条SQL执行失败,那么之后的SQL都不会执行了,如果要解决这个问题,就必须手工处理事务,所有的事务处理操作命令都在Connection接口中定义了,有如下3个支持事务操作的方法:
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public void setAutoCommit(boolean autoCommit) throws SQLException | 普通 | 设置事务是否自动提交 |
2 | public void commit() throws SQLException | 普通 | 提交事物 |
3 | public void rollback() throws SQLException | 普通 | 回滚事物 |
范例:手工控制事务
package cn.stone.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;
public class TestDemo {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD);
conn.setAutoCommit(false); //取消自动提交
Statement stmt = conn.createStatement();
try {
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三A')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三B')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三C')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三D')");
stmt.addBatch("insert into member(mid,name) values(myseq.nextval,'张三E')");
int[] data = stmt.executeBatch(); //执行批处理
System.out.println(Arrays.toString(data));
conn.commit(); //不出错提交
} catch (Exception e) {
e.printStackTrace();
conn.rollback(); //回滚事务
}
conn.close(); //关闭连接
}
}
输出:[1, 1, 1, 1, 1]
DAO
后台代码一定要有两个组成接口:
- 数据层(数据访问层,Data Access Object):指的是执行数据的具体操作。现在的开发中,大多数都是针对于数据库的开发,所以在数据层中的主要任务是负责完成数据的CRUD,在Java中,如果要想进行数据的CRUD实现,肯定使用java.sql.PreparedStatement接口完成;
- 业务层(业务对象,Business Object,BO,又或者将其 称为Service,服务层),业务层的主要目的是根据业务需求进行数据层的操作,一个业务层要包含多个数据层的操作。
以scott.emp表为例,实现如下功能:
- 【业务层】增加一个新雇员信息。
- 【数据层】要根据增加的雇员编号查看此雇员是否存在;
- 【数据层】如果雇员不存在则执行插入操作,如果存在则不插入。
- 【业务层】修改一个雇员的信息。
- 【数据层】直接传入新的数据即可,如果没有修改返回的更新行数是0。
- 【业务层】删除一个雇员的信息。
- 【数据层】直接传入要删除的雇员编号即可,如果没有此雇员信息返回的是0。
- 【业务层】根据编号查询一个雇员的信息。
- 【数据层】返回一个雇员的完整信息。
- 【业务层】取得全部雇员的信息,要求可以实现模糊查询和分页查询,查询结果除了返回数据之外,还要求取得模糊或全部查询时所返回的全部数据量。
- 【数据层】模糊或查询全部满足条件的雇员数据;
- 【数据层】使用COUNT()进行满足条件的数据统计。
Prepare
VO
在程序分层后实际上都是指面向对象的分层,而不同层之间(这些层除了数据层要操作SQL之外,其他层操作的数据都应该是对象)是需要进行数据传递的,那么就应该有一个负责传输的数据对象,而这个对象需要和一条数据进行完整映射,所以此对象的类应该为简单Java类,而这种类在DAO设计模式中就称为Value Object(简称VO)。
简单Java类的开发原则:
- 类名称要和表名称保持一致;
- 为了日后类的操作方便,所有的简单Java类必须实现java.io.Serializable接口;
- 类中不允许出现任何的基本数据类型,只能使用包装类;
- 类中的所有属性都必须封装,必须都编写setter、getter;
- 类中一定要提供有无参构造方法。
范例:定义cn.stone.oracle.vo.Emp类
package cn.stone.oracle.vo;
import java.io.Serializable;
import java.util.Date;
@SuppressWarnings("serial")
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Date hiredate;
private Double sal;
private Double comm;
public Emp() {
super();
}
public Emp(Integer empno, String ename, String job, Date hiredate, Double sal, Double comm) {
super();
this.empno = empno;
this.ename = ename;
this.job = job;
this.hiredate = hiredate;
this.sal = sal;
this.comm = comm;
}
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
public Double getSal() {
return sal;
}
public void setSal(Double sal) {
this.sal = sal;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
}
DatabaseConnection
既然现在要完成数据层的开发,那么就一定需要数据库的连接与关闭操作,可是如果将数据库的连接和关闭写在每一个数据层中则代码过于重复,而且也不方便维护。为方便起见,现在定义一个DatabaseCollection类,这个类专门负责取得和关闭数据库连接。
范例:定义cn.stone.oracle.dbc.DatabaseConnection类
package cn.stone.oracle.dbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 本类的主要功能是负责数据库的连接和关闭
* @author stone
*
*/
public class DatabaseConnection {
public static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
public static final String DBURL = "jdbc:oracle:thin:@192.168.8.137:1521:stone";
public static final String DBUSER = "scott";
public static final String PASSWORD = "tiger";
private Connection conn = null; //保存连接对象
/**
* 构造方法的主要目的是进行数据库连接,只要在程序中实例化了DatabaseCollection对象
* 那么就表示要进行数据库的连接操作了,所以在构造方法中连接数据库
* 在本构造方法中,如果出现了异常,将直接输出异常信息,因为如果数据库连接没有了,根本就无法操作
*/
public DatabaseConnection() {
try {
Class.forName(DBDRIVER);
this.conn = DriverManager.getConnection(DBURL, DBUSER, PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 取得一个数据库连接对象,这个对象在构造方法中取得
* @return Connection对象
*/
public Connection getConnection() {
return this.conn;
}
/**
* 关闭连接,不管是否连接上,执行此操作都不会出错
*/
public void close() {
if(this.conn != null) { //取得了连接
try {
this.conn.close(); //关闭连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
DAO
IEmpDAO
不同层之间的操作依靠的是接口,所以数据层的开发首先要定义出来的就是标准。既然是标准就需要定义一个接口,现在很明显针对的是emp表,所以这个接口的名称就应该为表名称DAO,及EmpDAO。但是接口和类的命名要求是一致的,所以为了从名称上区分出是接口还是类,建议在接口名称前增加一个字母“I”,表示Interface的含义,即emp这张实体表的操作标准的接口名称为IEmpDAO,而且这个接口应该保存在cn.stone.oracle.dao包中。
那么对于这个接口的开发主要是针对于数据的两种操作(更新、查询),所以从开发标准上对于命名也有着严格的要求,而且必须遵守。基本标准如下:
- 更新操作:以“doXXX()”的方式命名,如doCreate()、doUpdate()、doDelete();
- 查询操作:查询操作分为两类:
- 数据查询:以“findXxx()”或“findByXxx()”为主,如findAll()、findById(),findByJob();
- 统计查询:以“getXxx()”或“getByXxx()”为主,如getAllCount()、getByJobCount()。
范例:编写IEmpDAO接口的操作标准
package cn.stone.oracle.dao;
import java.util.List;
import cn.stone.oracle.vo.Emp;
public interface IEmpDAO {
/**
* 执行数据的增加操作
* @param vo 包含所要增加的数据的VO对象
* @return 如果增加数据成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doCreate(Emp vo) throws Exception;
/**
* 执行数据的更新操作
* @param vo 包含了新数据的VO对象
* @return 如果修改成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doUpdate(Emp vo) throws Exception;
/**
* 删除一个雇员的信息
* @param id 要删除的雇员编号
* @return 如果删除成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doDelete(int id) throws Exception;
/**
* 根据雇员编号查询一个雇员的完整信息
* @param id 要查询的雇员编号
* @return 如果没有指定雇员的编号,返回值为null,如果有指定雇员编号,则将雇员信息包装到Emp实例化对象中返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public Emp findById(int id) throws Exception;
/**
* 查询全部的雇员信息
* @return 多个雇员信息使用List返回,如果List集合的size()长度为0,则表示没有数据返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public List<Emp> findAll() throws Exception;
/**
* 分页显示所有雇员的信息,同时可以完成模糊查询
* @param column 要模糊查询的字段名称
* @param keyWord 要模糊查询的数据,如果为空字符串(isEmpty()判断为true,表示空字符串),则表示查询全部
* @param currentPage 读取所在的页
* @param lineSize 每页显示的记录条数
* @return 多个雇员信息使用List返回,如果List集合的size()长度为0,则表示没有数据返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public List<Emp> findAll(String column,String keyWord,int currentPage,int lineSize) throws Exception;
/**
* 统计模糊查询的数据结果,使用COUNT()函数进行统计
* @param column 要模糊查询的字段名称
* @param keyWord 要模糊查询的数据,如果为空字符串(isEmpty()判断为true,表示空字符串),则表示查询全部
* @return 会根据数据量的多少返回数据的长度,如果没有数据返回0
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public int getAllCount(String column,String keyWord) throws Exception;
}
范例:也可以定义一个公共的IDAO接口,再定义IEmpDAO接口
package cn.stone.oracle.dao;
import java.util.List;
/**
* 公共的DAO操作接口
* @author stone
* @param <K> 要操作的数据表的主键类型
* @param <V> 要操作的VO类型
*/
public interface IDAO<K,V> {
/**
* 执行数据的增加操作
* @param vo 包含所要增加的数据的VO对象
* @return 如果增加数据成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doCreate(V vo) throws Exception;
/**
* 执行数据的更新操作
* @param vo 包含了新数据的VO对象
* @return 如果修改成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doUpdate(V vo) throws Exception;
/**
* 删除一条记录
* @param id 要删除的主键值
* @return 如果删除成功返回true,否则返回false
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public boolean doDelete(K id) throws Exception;
/**
* 根据主键查询一条记录的完整信息
* @param id 要查询的主键值
* @return 如果没有指定主键值,返回值为null,如果有指定主键值,则将记录包装到V实例化对象中返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public V findById(K id) throws Exception;
/**
* 查询全部的记录
* @return 多条记录使用List返回,如果List集合的size()长度为0,则表示没有数据返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public List<V> findAll() throws Exception;
/**
* 分页显示所有记录,同时可以完成模糊查询
* @param column 要模糊查询的字段名称
* @param keyWord 要模糊查询的数据,如果为空字符串(isEmpty()判断为true,表示空字符串),则表示查询全部
* @param currentPage 读取所在的页
* @param lineSize 每页显示的记录条数
* @return 多条记录使用List返回,如果List集合的size()长度为0,则表示没有数据返回
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public List<V> findAll(String column,String keyWord,int currentPage,int lineSize) throws Exception;
/**
* 统计模糊查询的数据结果,使用COUNT()函数进行统计
* @param column 要模糊查询的字段名称
* @param keyWord 要模糊查询的数据,如果为空字符串(isEmpty()判断为true,表示空字符串),则表示查询全部
* @return 会根据数据量的多少返回数据的长度,如果没有数据返回0
* @throws Exception 操作中出现了异常,返回给被调用处执行处理
*/
public int getAllCount(String column,String keyWord) throws Exception;
}
package cn.stone.oracle.dao;
public interface IEmpDAO extends IDAO<Integer, Emp>{
}
EmpDAOImpl
既然在接口中已经定义了数据层的操作标准,那么对于实现类只需要遵循数据层的CRUD操作即可,但是对于DAO接口的实现类需要有明确的定义,要求将其定义在cn.stone.oracle.dao.impl包中。
由于一个业务要进行多个数据层操作,所以数据库连接与关闭交给业务层最合适,而数据层只需要有一个Connection对象就可以操作了。
范例:实现IEmpDAO接口
package cn.stone.oracle.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import cn.stone.oracle.dao.IEmpDAO;
import cn.stone.oracle.vo.Emp;
public class EmpDAOImpl implements IEmpDAO {
private Connection conn;
private PreparedStatement pstmt;
public EmpDAOImpl() {
super();
}
public EmpDAOImpl(Connection conn) {
super();
this.conn = conn;
}
@Override
public boolean doCreate(Emp vo) throws Exception {
String sql = "insert into emp(empno,ename,job,hiredate,sal,comm) values(?,?,?,?,?,?)";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setInt(1, vo.getEmpno());
this.pstmt.setString(2, vo.getEname());
this.pstmt.setString(3, vo.getJob());
this.pstmt.setDate(4, new java.sql.Date(vo.getHiredate().getTime()));
this.pstmt.setDouble(5, vo.getSal());
this.pstmt.setDouble(6, vo.getComm());
if(this.pstmt.executeUpdate() > 0) {
return true;
}
return false;
}
@Override
public boolean doUpdate(Emp vo) throws Exception {
String sql = "update emp set ename=?,job=?,hiredate=?,sal=?,comm=? where empno=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, vo.getEname());
this.pstmt.setString(2, vo.getJob());
this.pstmt.setDate(3, new java.sql.Date(vo.getHiredate().getTime()));
this.pstmt.setDouble(4, vo.getSal());
this.pstmt.setDouble(5, vo.getComm());
this.pstmt.setInt(6, vo.getEmpno());
if(this.pstmt.executeUpdate() > 0) {
return true;
}
return false;
}
@Override
public boolean doDelete(int id) throws Exception {
String sql = "delete from emp where empno=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setInt(1, id);
if (this.pstmt.executeUpdate() > 0) {
return true;
}
return false;
}
@Override
public Emp findById(int id) throws Exception {
Emp emp = null;
String sql = "select empno,ename,job,hiredate,sal,comm from emp where empno=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setInt(1, id);
ResultSet rs = this.pstmt.executeQuery();
if (rs.next()) {
emp = new Emp();
emp.setEmpno(rs.getInt(1));
emp.setEname(rs.getString(2));
emp.setJob(rs.getString(3));
emp.setHiredate(rs.getDate(4));
emp.setSal(rs.getDouble(5));
emp.setComm(rs.getDouble(6));
}
return emp;
}
@Override
public List<Emp> findAll() throws Exception {
List<Emp> all = new ArrayList<Emp>();
String sql = "select empno,ename,job,hiredate,sal,comm from emp";
this.pstmt = this.conn.prepareStatement(sql);
ResultSet rs = this.pstmt.executeQuery();
while (rs.next()) {
Emp emp = new Emp();
emp.setEmpno(rs.getInt(1));
emp.setEname(rs.getString(2));
emp.setJob(rs.getString(3));
emp.setHiredate(rs.getDate(4));
emp.setSal(rs.getDouble(5));
emp.setComm(rs.getDouble(6));
all.add(emp);
}
return all;
}
@Override
public List<Emp> findAll(String column, String keyWord, int currentPage, int lineSize) throws Exception {
List<Emp> all = new ArrayList<Emp>();
String sql = "select * from (select empno,ename,job,hiredate,sal,comm,rownum rn from emp where " + column + " like ? and rownum<=?) temp where temp.rn>?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, keyWord + "%");
this.pstmt.setInt(2, currentPage * lineSize);
this.pstmt.setInt(3, (currentPage - 1) * lineSize);
ResultSet rs = this.pstmt.executeQuery();
while (rs.next()) {
Emp emp = new Emp();
emp.setEmpno(rs.getInt(1));
emp.setEname(rs.getString(2));
emp.setJob(rs.getString(3));
emp.setHiredate(rs.getDate(4));
emp.setSal(rs.getDouble(5));
emp.setComm(rs.getDouble(6));
all.add(emp);
}
return all;
}
@Override
public int getAllCount(String column, String keyWord) throws Exception {
String sql = "select count(empno) from emp where " + column + " like ?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, keyWord + "%");
ResultSet rs = this.pstmt.executeQuery();
if (rs.next()) {
return rs.getInt(1);
}
return 0;
}
}
DAOFactory
由于不同层之间只能依靠接口取得对象,所以就一定需要定义工厂操作类,工厂类定义在cn.stone.oracle.factory包中,名称为DAOFactory
范例:定义工厂类
package cn.stone.oracle.factory;
import java.sql.Connection;
import cn.stone.oracle.dao.IEmpDAO;
import cn.stone.oracle.dao.impl.EmpDAOImpl;
public class DAOFactory {
public static IEmpDAO getIEmpDAOInstance(Connection conn) {
return new EmpDAOImpl(conn);
}
}
Service
业务层的主要功能是调用数据层操作,而与数据层之间的数据传输都是利用简单Java类完成的。
IEmpService
业务层以后也是需要留给其他层进行调用的,所以业务层定义时也需要首先定义出操作标准,而这个标准也依然使用接口完成。对于业务层,接口命名要求为表名称+Service,如IEmpService,表示操作Emp表的业务。
范例:定义IEmpService接口
package cn.stone.oracle.service;
import java.util.Map;
import cn.stone.oracle.vo.Emp;
public interface IEmpService {
/**
* 调用数据库的增加操作,操作流程如下:<br>
* <li>首先使用IEmpDAO接口中的findById()方法,根据要增加的id查看指定的雇员信息是否存在
* <li>如果要增加的雇员信息不存在,则执行IEmpDAO接口的doCreate()方法,并将结果返回
* @param vo 包装数据的对象
* @return 如果增加成功,返回true,如果雇员编号存在或者是增加失败,返回false
* @throws Exception 有异常交给被调用处处理
*/
public boolean insert(Emp vo) throws Exception;
/**
* 执行数据的更新操作,操作时直接调用IEmpDAO接口的doUpdate()方法,并将更新结果返回
* @param vo 包装数据的对象
* @return 如果增加成功,返回true,如果数据不存在或增加失败,返回false
* @throws Exception 有异常交给被调用处处理
*/
public boolean update(Emp vo) throws Exception;
/**
* 执行数据的删除操作,删除操作时调用IEmpDAO接口的doDelete()方法
* @param id 要删除雇员的id
* @return 如果删除成功,返回true,如果数据不存在或删除失败,则返回false
* @throws Exception 有异常交给被调用处处理
*/
public boolean delete(int id) throws Exception;
/**
* 根据雇员的编号取得全部信息
* @param id 雇员编号
* @return 如果雇员存在则将数据包装为Emp对象返回,如果数据不存在则返回null
* @throws Exception 有异常交给被调用处处理
*/
public Emp get(int id) throws Exception;
/**
* 查询全部或者是模糊查询全部数据,查询的同时可以返回满足此查询的数据量,在调用时执行以下操作:<br>
* <li>查询全部的雇员信息:使用IEmpDAO接口的findAll()方法操作
* <li>查询满足条件的雇员数量:使用IEmpDAO接口的getAllCount()方法操作
* @param column 模糊查询的字段
* @param keyWord 模糊查询的关键字
* @param currentPage 当前所在页
* @param lineSize 每页显示的记录条数
* @return 由于在进行数据返回时,此方法要返回两类数据:List<Emp>、int,使用Map返回:<br>
* <li>返回值1:key=allEmps,value=findAll();
* <li>返回值2:key=empCount,value=getAllCount();
* @throws Exception 有异常交给被调用处处理
*/
public Map<String,Object> list(String column,String keyWord,int currentPage,int lineSize) throws Exception;
}
EmpServiceImpl
如果要实现业务层的标准,必须有一个原则:一个业务层的方法操作要调用多个数据层,同时每个业务要处理数据库的打开和关闭。
范例:定义实现类
package cn.stone.oracle.service.impl;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import cn.stone.oracle.dao.IEmpDAO;
import cn.stone.oracle.dbc.DatabaseConnection;
import cn.stone.oracle.factory.DAOFactory;
import cn.stone.oracle.service.IEmpService;
import cn.stone.oracle.vo.Emp;
public class EmpServiceImpl implements IEmpService {
private DatabaseConnection dbc = new DatabaseConnection();
@Override
public boolean insert(Emp vo) throws Exception {
try {
Connection conn = this.dbc.getConnection(); //取得连接
IEmpDAO dao = DAOFactory.getIEmpDAOInstance(conn); //取得DAO接口对象
if(dao.findById(vo.getEmpno()) == null) { //没有要查询的雇员信息
return dao.doCreate(vo); //返回DAO的结果
}
return false; //数据存在,直接返回false
} catch (Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public boolean update(Emp vo) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doUpdate(vo);
} catch (Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public boolean delete(int id) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doDelete(id);
} catch (Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public Emp get(int id) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findById(id);
} catch (Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public Map<String, Object> list(String column, String keyWord, int currentPage, int lineSize) throws Exception {
try {
Map<String,Object> map = new HashMap<String,Object>();
map.put("allEmps", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findAll(column, keyWord, currentPage, lineSize));
map.put("empCount", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).getAllCount(column, keyWord));
return map;
} catch (Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
}
ServiceFactory
如果要取得IEmpService接口对象,一定也需要使用工厂类,避免耦合问题。
范例:定义工厂类
package cn.stone.oracle.factory;
import cn.stone.oracle.service.IEmpService;
import cn.stone.oracle.service.impl.EmpServiceImpl;
public class ServiceFactory {
public static IEmpService getIEmpServiceInstance() {
return new EmpServiceImpl();
}
}
Test
使用JUNIT建立TestCase进行测试,首先应该选择的是服务层接口。
范例:编写测试程序类
package cn.stone.oracle.test;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import cn.stone.oracle.factory.ServiceFactory;
import cn.stone.oracle.vo.Emp;
import junit.framework.TestCase;
class IEmpServiceTest {
@Test
void testInsert() {
Emp vo = new Emp();
vo.setEmpno(9988);
vo.setEname("张三");
vo.setJob("清洁工");
vo.setHiredate(new Date());
vo.setSal(3000.00);
vo.setComm(200.00);
try {
TestCase.assertTrue(ServiceFactory.getIEmpServiceInstance().insert(vo));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void testUpdate() {
Emp vo = new Emp();
vo.setEmpno(9988);
vo.setEname("张三");
vo.setJob("清洁工");
vo.setHiredate(new Date());
vo.setSal(1000.00);
vo.setComm(600.00);
try {
TestCase.assertTrue(ServiceFactory.getIEmpServiceInstance().update(vo));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void testDelete() {
try {
TestCase.assertTrue(ServiceFactory.getIEmpServiceInstance().delete(9988));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void testGet() {
try {
Emp vo = ServiceFactory.getIEmpServiceInstance().get(7369);
TestCase.assertNotNull(vo);
System.out.println(vo.getEname());
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void testList() {
try {
Map<String,Object> map = ServiceFactory.getIEmpServiceInstance().list("ename", "", 1, 5);
TestCase.assertNotNull(map);
System.out.println("总记录数:" + map.get("empCount"));
@SuppressWarnings("unchecked")
List<Emp> all = (List<Emp>) map.get("allEmps");
Iterator<Emp> iter = all.iterator();
while (iter.hasNext()) {
Emp emp = iter.next();
System.out.println(emp.getEname() + "," + emp.getJob());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
JDBC Advance
通过Driver接口获取数据库连接
属性文件jdbc.properties:
user=hr
password=hr
driverClass=oracle.jdbc.driver.OracleDriver
jdbcUrl=jdbc:oracle:thin:@192.168.8.137:1521/stone
获取数据库连接:
package com.stone.jdbc;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.Test;
public class JDBCTest {
/**
* 编写一个通用的方法,在不修改源程序的情况下,可以获取任何数据库的连接
* 解决方法:把数据库驱动Driver实现类的全类名,url,user,password放入一个配置文件中
* 通过修改配置文件的方式实现和具体数据库的解耦
*/
public Connection getConnection() throws Exception {
String driverClass = null;
String jdbcUrl = null;
String user = null;
String password = null;
//读取类路径下的jdbc.properties文件
InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(in);
driverClass = properties.getProperty("driverClass");
jdbcUrl = properties.getProperty("jdbcUrl");
user = properties.getProperty("user");
password = properties.getProperty("password");
//通过反射创建Driver对象
Driver driver = (Driver) Class.forName(driverClass).newInstance();
Properties info = new Properties();
info.put("user", user);
info.put("password", password);
//通过Driver的connect方法获取数据库连接
Connection connection = driver.connect(jdbcUrl, info);
return connection;
}
@Test
public void testGetConnection() throws Exception {
System.out.println(getConnection());
}
}
通过DriverManager获取数据库连接
public Connection getConnection2() throws Exception {
//1.读取类路径下的jdbc.properties文件
Properties properties = new Properties();
InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//2.加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
Class.forName(driverClass);
//3.通过DriverManager的getConnection方法获取数据库连接
return DriverManager.getConnection(jdbcUrl, user, password);
}
@Test
public void testGetConnection2() throws Exception {
System.out.println(getConnection2());
}
/**
* DriverManager是驱动的管理类
* 1)。可以通过重载的getConnection()方法获取数据库连接,较为方便
* 2)。可以同时管理多个驱动程序:若注册了多个数据库连接,则调用getConnection()
* 方法时传入的参数不同,即返回不同的数据库连接
* @throws Exception
*/
@Test
public void testDriverManager() throws Exception {
//1.准备连接数据库的4个字符串
String driverClass = null;
String jdbcUrl = null;
String user = null;
String password = null;
//读取类路径下的jdbc.properties文件
Properties properties = new Properties();
InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
driverClass = properties.getProperty("driverClass");
jdbcUrl = properties.getProperty("jdbcUrl");
user = properties.getProperty("user");
password = properties.getProperty("password");
//2.加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
Class.forName(driverClass);
//3.通过DriverManager的getConnection方法获取数据库连接
Connection connection = DriverManager.getConnection(jdbcUrl, user, password);
System.out.println(connection);
}
通过Statement执行更新操作
创建工具类:
package com.stone.jdbc;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 操作JDBC的工具类,其中封装了一些工具方法 Version 1
*
*/
public class JDBCTools {
/**
* 关闭Statement和Connection
* @param statement
* @param conn
*/
public static void release(Statement statement, Connection conn) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 1.获取连接的方法。 通过读取配置文件从数据库服务器获取一个连接
*
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
// 1.读取类路径下的jdbc.properties文件
Properties properties = new Properties();
InputStream in = JDBCTools.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
// 2.加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
Class.forName(driverClass);
// 3.通过DriverManager的getConnection方法获取数据库连接
return DriverManager.getConnection(jdbcUrl, user, password);
}
}
执行增删改操作:
/**
* 通用的更新的方法:包括insert,update,delete
* 版本1
*/
public void update(String sql) {
Connection conn = null;
Statement statement = null;
try {
conn = JDBCTools.getConnection();
statement = conn.createStatement();
statement.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(statement, conn);
}
}
/**
* 通过JDBC向指定的数据表中插入一条记录
* 1.Statement:用于执行SQL语句的对象
* 1)。通过Connection的createStatement()方法来获取
* 2)。通过executeUpdate(sql)可以执行SQL语句
* 3)。传入的SQL可以是insert,update或delete,但不能是select
*
* 2.Connection,Statement都是应用程序和数据库服务器的连接资源,使用后一定要关闭
* 需要在finally中关闭Connection和Statement对象。
*
* 3.关闭的顺序是:先关闭后获取的,即先关闭Statement后再关闭Connection
* @throws Exception
*/
@Test
public void testStatement() throws Exception {
Connection conn = null;
Statement statement = null;
try {
//1.获取数据库连接
conn = getConnection2();
//3.准备插入的SQL语句
String sql = null;
//sql = "insert into customers(name,email,birth) values('bbb','bbb@stone.com','2019-01-01')";
//sql = "delete from customers where id=3";
sql="update customers set name='ccc' where id=2";
//4.执行插入
//1)。获取操作SQL语句的Statement对象:调用Connection的createStatement()方法来获取
statement = conn.createStatement();
//2).调用Statement对象的executeUpdate(sql) 执行SQL语句进行插入
statement.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//5.关闭Statement对象
if (statement != null) {
statement.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//2.关闭连接
if (conn != null) {
conn.close();
}
}
}
}
public Connection getConnection2() throws Exception {
//1.读取类路径下的jdbc.properties文件
Properties properties = new Properties();
InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
//2.加载数据库驱动程序(对应的Driver实现类中有注册驱动的静态代码块)
Class.forName(driverClass);
//3.通过DriverManager的getConnection方法获取数据库连接
return DriverManager.getConnection(jdbcUrl, user, password);
}
通过ResultSet执行查询操作
/**
* ResultSet:结果集。封装了使用JDBC进行查询的结果
* 1.调用Statement对象的executeQuery(sql)可以得到结果集
* 2.ResultSet返回的实际是就是一张数据表。有一个指针指向数据表的第一行的前面
* 可以调用next()方法检测下一行是否有效,若有效该方法返回true,且指针下移。
* 相当于Iterator对象的hasNext()和next()方法的结合体
* 3.当指针定位到一行时,可以通过调用getXxx(index)或getXxx(column)获取
* 每一列的值,例如:getInt(1),getString("name")
* 4.ResultSet当前也需要进行关闭
*/
@Test
public void testResultSet() {
//获取id=4的customers数据表的记录,并打印
Connection conn = null;
Statement statement = null;
ResultSet rs = null;
try {
//1.获取Connection
conn = JDBCTools.getConnection();
//2.获取Statement
statement = conn.createStatement();
//3.准备SQL
String sql = "select id,name,email,birth from customers";
//4.执行查询,得到ResultSet
rs = statement.executeQuery(sql);
//5.处理ResultSet
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString("name");
String email = rs.getString(3);
Date birth = rs.getDate(4);
System.out.println(id);
System.out.println(name);
System.out.println(email);
System.out.println(birth);
}
//6.关闭数据库资源
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(rs, statement, conn);
}
}
以面向对象方式编写JDBC程序
创建Java bean:
package com.stone.jdbc;
public class Student {
private int flowId;
private int type;
private String idCard;
private String examCard;
private String studentName;
private String location;
private int grade;
public Student() {
super();
}
public Student(int flowId, int type, String idCard, String examCard, String studentName, String location,
int grade) {
super();
this.flowId = flowId;
this.type = type;
this.idCard = idCard;
this.examCard = examCard;
this.studentName = studentName;
this.location = location;
this.grade = grade;
}
//getter,setter方法省略
@Override
public String toString() {
return "Student [flowId=" + flowId + ", type=" + type + ", idCard=" + idCard + ", examCard=" + examCard
+ ", studentName=" + studentName + ", location=" + location + ", grade=" + grade + "]";
}
}
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import org.junit.Test;
public class JDBCTest1 {
private Connection connection;
@Test
public void testGet2Student() {
//1.得到查询的类型
int searchType = getSearchTypeFromConsole();
//2.具体查询学生信息
Student student = searchStudent(searchType);
//3.打印学生信息
printStudent(student);
}
/**
* 打印学生信息:若学生存在则打印具体信息,若不存在,打印插入此人
* @param student
*/
private void printStudent(Student student) {
if (student != null) {
System.out.println(student);
} else {
System.out.println("查无此人");
}
}
/**
* 具体查询学生信息,返回一个student对象,若不存在,则返回null
* @param searchType :1 或者 2
* @return
*/
private Student searchStudent(int searchType) {
String sql = "select flowid,type,idcard,examcard,studentname,location,grade from examstudent where ";
Scanner scanner = new Scanner(System.in);
//1.根据输入的searchType,提示用户输入信息。
//若searchType为1,提示:请输入身份证号,若为2,提示输入准考证号
//2.根据searchType确定SQL
if (searchType == 1) {
System.out.println("请输入身份证号");
String idCard = scanner.next();
sql = sql + "idcard='" + idCard + "'";
} else {
System.out.println("请输入准考证号");
String examCard = scanner.next();
sql = sql + "examCard='" + examCard + "'";
}
//3.执行查询
Student student = getStudent(sql);
//4.若存在查询结果,把查询结果封装为Student对象
return student;
}
/**
* 根据传入的SQL返回Student对象
* @param sql
* @return
*/
private Student getStudent(String sql) {
Student student = null;
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
student = new Student(resultSet.getInt(1),
resultSet.getInt(2),
resultSet.getString(3),
resultSet.getString(4),
resultSet.getString(5),
resultSet.getString(6),
resultSet.getInt(7));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, statement, connection);
}
return student;
}
/**
* 从控制台读入一个整数,确定要查询的类型
* @return:1.用身份证查询。 2.用准考证号查询。 其他无效,并提示用户重新输入
*/
private int getSearchTypeFromConsole() {
System.out.print("请输入查询类型:1.用身份证查询。 2.用准考证号查询");
Scanner scanner = new Scanner(System.in);
int type = scanner.nextInt();
if (type != 1 && type != 2) {
System.out.println("输入有误请重新输入!");
throw new RuntimeException();
}
return type;
}
@Test
public void testaddNewStudent() {
Student student = getStudentFromConsole();
this.addNewStudent(student);
}
/**
* 从控制台输入学生的信息
*
* @return
*/
private Student getStudentFromConsole() {
Scanner scanner = new Scanner(System.in);
Student student = new Student();
System.out.print("FlowId:");
student.setFlowId(scanner.nextInt());
System.out.print("Type:");
student.setType(scanner.nextInt());
System.out.print("IdCard:");
student.setIdCard(scanner.next());
System.out.print("ExamCard:");
student.setExamCard(scanner.next());
System.out.print("StudentName:");
student.setStudentName(scanner.next());
System.out.print("Location:");
student.setLocation(scanner.next());
System.out.print("Grade:");
student.setGrade(scanner.nextInt());
return student;
}
public void addNewStudent(Student student) {
// 1.准备一条SQL语句
String sql = "insert into examstudent values("
+ student.getFlowId() + ","
+ student.getType() + ",'"
+ student.getIdCard() +"','"
+ student.getExamCard() + "','"
+ student.getStudentName() + "','"
+ student.getLocation() + "',"
+ student.getGrade() +")";
System.out.println(sql);
//2.调用JDBCTools类的update(sql)方法执行插入操作
JDBCTools.update(sql);
}
}
PreparedStatement
/**
* 执行SQL语句,使用PreparedStatement
* @param sql
* @param args:填写SQL占位符的可变参数
*/
public static void update(String sql, Object ... args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, ps, conn);
}
}
public void addNewStudent2(Student student) {
String sql = "insert into examstudent(flowid,type,idcard,examcard,studentname,location,grade) values(?,?,?,?,?,?,?)";
JDBCTools.update(sql, student.getFlowId(),student.getType(),
student.getIdCard(),student.getExamCard(),student.getStudentName(),
student.getLocation(),student.getGrade());
}
/**
* PreparedStatement:是Statement的子接口,可以传入带占位符的SQL语句,
* 并且提供了补充占位符变量的方法
* 可以有效防止SQL注入
* 提高性能
*/
@Test
public void testPreparedStatement() {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCTools.getConnection();
String sql = "insert into customers(name,email,birth) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "ddd");
ps.setString(2, "ddd@stone.com");
ps.setDate(3, new java.sql.Date(new Date().getTime()));
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, ps, conn);
}
}
JDBC元数据
Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC可以访问一个事先并不了解的数据库。 获取这些信息的方法都是在DatabaseMetaData类的对象上实现的,而DataBaseMetaData对象是在Connection对象上获得的。 DatabaseMetaData 类中提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息:
- getURL():返回一个String类对象,代表数据库的URL。
- getUserName():返回连接当前数据库管理系统的用户名。
- isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
- getDatabaseProductName():返回数据库的产品名称。
- getDatabaseProductVersion():返回数据库的版本号。
- getDriverName():返回驱动驱动程序的名称。
- getDriverVersion():返回驱动程序的版本号。
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象:
- getColumnName(int column):获取指定列的名称
- getColumnCount():返回当前 ResultSet 对象中的列数。
- getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
- getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
- isNullable(int column):指示指定列中的值是否可以为 null。
- isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import org.junit.Test;
public class MetaDataTest {
/**
* ResultSetMetaData:描述结果集的元数据
* 可以得到结果集中的基本信息:结果集中有哪些列,列名,列的别名等。
*/
@Test
public void testResultSetMetaData() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
//得到列的个数
int columnCount = metaData.getColumnCount();
System.out.println(columnCount);
for (int i = 0; i < columnCount; i++) {
//得到列名
String columnName = metaData.getColumnName(i + 1);
//得到列的别名
String columnLabel = metaData.getColumnLabel(i + 1);
System.out.println(columnName + " , " + columnLabel);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
/**
* DatabaseMetaData 是描述数据库的元数据对象
*/
@Test
public void testDatabaseMetaData() {
Connection connection = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
//可以得到数据库本身的一些基本信息
System.out.println(metaData.getDatabaseProductVersion());
System.out.println(metaData.getUserName());
resultSet = metaData.getCatalogs();
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, null, connection);
}
}
}
输出:
4
id , id
name , name
email , email
birth , birth
5.7.22-log
stone@192.168.8.137
information_schema
hivedb
jflow3
mappingtran
mappingtransfer
mybatis
mysql
performance_schema
sys
创建表:
CREATE TABLE `examstudent2` (
`flow_id` int(11) NOT NULL,
`type` int(11) DEFAULT NULL,
`id_card` varchar(18) DEFAULT NULL,
`exam_card` varchar(18) DEFAULT NULL,
`student_name` varchar(20) DEFAULT NULL,
`location` varchar(20) DEFAULT NULL,
`grade` int(11) DEFAULT NULL,
PRIMARY KEY (`flow_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/**
* ResultSetMetaData:是描述ResultSet的元数据对象,即从中可以获取到结果集中有多少列,列名是什么
* 通过调用ResultSet的getMetaData()方法获取ResultSetMetaData对象。 ResultSetMetaData好用的方法:
* >int getColumnCount():SQL语句中包含哪些列
* >String getColumnLabel(int column):获取指定的列的别名,其中索引从1开始
*/
@Test
public void testResultSetMetaData() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
String sql = "select flow_id flowId,type,id_card idCard,exam_card examCard,student_name studentName,location,grade from examstudent2 where flow_id=?";
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);
resultSet = preparedStatement.executeQuery();
Map<String, Object> values = new HashMap<String, Object>();
// 1.得到ResultSetMetaData对象
ResultSetMetaData metaData = resultSet.getMetaData();
while (resultSet.next()) {
// 2.获取每一列的列名
for (int i = 0; i < metaData.getColumnCount(); i++) {
String columnLabel = metaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(columnLabel);
values.put(columnLabel, columnValue);
}
}
System.out.println(values);
Class clazz = Student.class;
Object object = clazz.newInstance();
for(Map.Entry<String, Object> entry: values.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
System.out.println(fieldName + " : " + fieldValue);
ReflectionUtils.setFieldValue(object, fieldName, fieldValue);
}
System.out.println(object);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
反射工具类:
package com.stone.utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 反射的 Utils 函数集合
* 提供访问私有变量, 获取泛型类型 Class, 提取集合中元素属性等 Utils 函数
* @author Administrator
*
*/
public class ReflectionUtils {
/**
* 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
* 如: public EmployeeDao extends BaseDao<Employee, String>
* @param clazz
* @param index
* @return
*/
@SuppressWarnings("unchecked")
public static Class getSuperClassGenricType(Class clazz, int index){
Type genType = clazz.getGenericSuperclass();
if(!(genType instanceof ParameterizedType)){
return Object.class;
}
Type [] params = ((ParameterizedType)genType).getActualTypeArguments();
if(index >= params.length || index < 0){
return Object.class;
}
if(!(params[index] instanceof Class)){
return Object.class;
}
return (Class) params[index];
}
/**
* 通过反射, 获得 Class 定义中声明的父类的泛型参数类型
* 如: public EmployeeDao extends BaseDao<Employee, String>
* @param <T>
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public static<T> Class<T> getSuperGenericType(Class clazz){
return getSuperClassGenricType(clazz, 0);
}
/**
* 循环向上转型, 获取对象的 DeclaredMethod
* @param object
* @param methodName
* @param parameterTypes
* @return
*/
public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes){
for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
try {
//superClass.getMethod(methodName, parameterTypes);
return superClass.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
//Method 不在当前类定义, 继续向上转型
}
//..
}
return null;
}
/**
* 使 filed 变为可访问
* @param field
*/
public static void makeAccessible(Field field){
if(!Modifier.isPublic(field.getModifiers())){
field.setAccessible(true);
}
}
/**
* 循环向上转型, 获取对象的 DeclaredField
* @param object
* @param filedName
* @return
*/
public static Field getDeclaredField(Object object, String filedName){
for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
try {
return superClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
//Field 不在当前类定义, 继续向上转型
}
}
return null;
}
/**
* 直接调用对象方法, 而忽略修饰符(private, protected)
* @param object
* @param methodName
* @param parameterTypes
* @param parameters
* @return
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static Object invokeMethod(Object object, String methodName, Class<?> [] parameterTypes,
Object [] parameters) throws InvocationTargetException{
Method method = getDeclaredMethod(object, methodName, parameterTypes);
if(method == null){
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]");
}
method.setAccessible(true);
try {
return method.invoke(object, parameters);
} catch(IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
return null;
}
/**
* 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
* @param object
* @param fieldName
* @param value
*/
public static void setFieldValue(Object object, String fieldName, Object value){
Field field = getDeclaredField(object, fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
}
/**
* 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
* @param object
* @param fieldName
* @return
*/
public static Object getFieldValue(Object object, String fieldName){
Field field = getDeclaredField(object, fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
return result;
}
}
利用反射及JDBC元数据编写通用查询方法
@Test
public void testGet() {
String sql = "select id,name,email,birth from customers where id=?";
Customers customers = get(Customers.class, sql, 4);
System.out.println(customers);
sql = "select flow_id flowId,type,id_card idCard,exam_card examCard,student_name studentName,location,grade from examstudent2 where flow_id=?";
Student student = get(Student.class, sql, 1);
System.out.println(student);
}
/**
* 编写通用的查询方法,具体步骤:
* 1.先利用 SQL 进行查询,得到结果集
* 2.利用反射创建实体类的对象:创建 Student 对象
* 3.获取结果集的列的别名:idCard、studentName
* 4.再获取结果集的每一列的值, 结合 3 得到一个Map,键:列的别名,值:列的值:{flowId:5, type:6, idCard: xxx ……}
* 5.再利用反射为 2 的对应的属性赋值:属性即为Map 的键,值即为 Map 的值
* @param clazz
* @param sql
* @param args
* @return
*/
public <T> T get(Class<T> clazz, String sql, Object... args) {
T entity = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.得到ResultSet对象
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//2.得到ResultSetMetaData对象
ResultSetMetaData metaData = resultSet.getMetaData();
//3.创建一个Map<String, Object>对象,键:SQL查询的列的别名,值:列的值
Map<String, Object> values = new HashMap<>();
//4.处理结果集,利用 ResultSetMetaData 填充3对应的Map对象
if (resultSet.next()) {
for (int i = 0; i < metaData.getColumnCount(); i++) {
String columnLabel = metaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(columnLabel);
values.put(columnLabel, columnValue);
}
}
//5.若Map不为空集,利用反射创建clazz对应的对象
if (values.size() > 0) {
entity = clazz.newInstance();
//6.遍历Map对象,利用反射为Clazz对象的对应属性赋值
for(Map.Entry<String, Object> entry : values.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
ReflectionUtils.setFieldValue(entity, fieldName, fieldValue);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
return entity;
}
public static void setFieldValue(Object object, String fieldName, Object value){
Field field = getDeclaredField(object, fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
System.out.println("�������׳����쳣");
}
}
使用BeanUtils操作JavaBean
添加commons-beanutils-1.9.3.jar,commons-logging-1.2.jar包。
package com.stone.jdbc;
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
public class BeanUtilsTest {
@Test
public void testGetProperty() throws Exception {
Object object = new Student();
System.out.println(object);
BeanUtils.setProperty(object, "idCard", "11111");
System.out.println(object);
String property = BeanUtils.getProperty(object, "idCard");
System.out.println(property);
}
/**
* 在JavaEE中,Java类的属性通过getter,setter来定义:
* 对getter(或setter)方法去除get(或set)后,第一个字母小写即为Java类的属性。
* 通过工具包beanutils来操作Java类的属性。
* @throws Exception
*/
@Test
public void testSetProperty() throws Exception {
Object object = new Student();
System.out.println(object);
BeanUtils.setProperty(object, "idCard", "11111");
System.out.println(object);
}
}
DAO设计模式
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
public class DAO2 {
// insert,update,delete操作都可以包含在其中
public void update(String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, ps, conn);
}
}
// 查询一条记录,返回对应的对象
public <T> T get(Class<T> clazz, String sql, Object... args) {
List<T> result = getForList(clazz, sql, args);
if (result.size() > 0) {
return result.get(0);
}
return null;
}
// 查询多条记录,返回对应的对象集合
public <T> List<T> getForList(Class<T> clazz, String sql, Object... args) {
List<T> list = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.得到结果集
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
//2.处理结果集,得到Map的List,其中一个Map对象,就是一条记录。Map的key为resultSet中列的别名,Map的value为列的值
List<Map<String, Object>> values = handleResultSetToMapList(resultSet);
//3.把Map的List转为clazz对应的List,其中Map的key即为clazz对应对象的propertyName,Map的value即为clazz对应对象的propertyValue
list = transferMapListToBeanList(clazz, values);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
return list;
}
public <T> List<T> transferMapListToBeanList(Class<T> clazz, List<Map<String, Object>> values)
throws Exception {
List<T> result = new ArrayList<>();
T bean = null;
if (values.size() > 0) {
for (Map<String, Object> m : values) {
for (Map.Entry<String, Object> entry : m.entrySet()) {
String propertyName = entry.getKey();
Object propertyValue = entry.getValue();
bean = clazz.newInstance();
BeanUtils.setProperty(bean, propertyName, propertyValue);
}
result.add(bean);
}
}
return result;
}
/**
* 处理结果集,得到Map的一个List,其中一个Map对象对应一条记录
* @param resultSet
* @return
* @throws Exception
* @throws SQLException
*/
public List<Map<String, Object>> handleResultSetToMapList(ResultSet resultSet) throws Exception, SQLException {
//3.创建一个List<Map<String, Object>>对象,一个Map对象对应一条记录,
//键:SQL查询的列的别名,值:列的值
List<Map<String, Object>> values = new ArrayList<>();
List<String> columnLabels = getColumnLabels(resultSet);
Map<String, Object> map = null;
//4.处理结果集,利用 ResultSetMetaData 填充3对应的Map对象
while(resultSet.next()) {
map = new HashMap<>();
for (String columnLabel : columnLabels) {
Object columnValue = resultSet.getObject(columnLabel);
map.put(columnLabel, columnValue);
}
values.add(map);
}
return values;
}
/**
* 获取结果集的ColumnLabel的list
* @param resultSet
* @return
* @throws Exception
*/
private List<String> getColumnLabels(ResultSet resultSet) throws Exception{
List<String> labels = new ArrayList<>();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 0; i < columnCount; i++) {
labels.add(metaData.getColumnLabel(i + 1));
}
return labels;
}
// 返回某条记录的某一个字段的值或一个统计的值(一共有多少条记录等)
public <E> E getForValue(String sql, Object... args) {
//1.得到结果集:应该只有一行,且只有一列
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
return (E) resultSet.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
return null;
}
}
JDBC获取自动插入的主键值
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import org.junit.Test;
public class JDBCTest2 {
@Test
public void testGetKeyValue() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "insert into customers(name,email,birth) values(?,?,?)";
//preparedStatement = connection.prepareStatement(sql);
//使用重载的prepareStatement(sql,flag)来生成preparedStatement对象
preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, "jerry");
preparedStatement.setString(2, "jerry@stone.com");
preparedStatement.setDate(3, new Date(new java.util.Date().getTime()));
preparedStatement.executeUpdate();
//通过preparedStatement的getGeneratedKeys()获取包含了新生成的主键的ResultSet对象
resultSet = preparedStatement.getGeneratedKeys();
if (resultSet.next()) {
System.out.println(resultSet.getObject(1));
}
//在ResultSet中只有1列GENERATED_KEY,用于存放新生成的主键值
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 0; i < metaData.getColumnCount(); i++) {
System.out.println(metaData.getColumnName(i + 1));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
}
处理BLOB
Oracle LOB:
- LOB,即Large Objects(大对象),是用来存储大量的二进制和文本数据的一种数据类型(一个LOB字段可存储可多达4GB的数据)。
- LOB 分为两种类型:内部LOB和外部LOB。
- 内部LOB将数据以字节流的形式存储在数据库的内部。因而,内部LOB的许多操作都可以参与事务,也可以像处理普通数据一样对其进行备份和恢复操作。Oracle支持三种类型的内部LOB:
- BLOB(二进制数据)
- CLOB(单字节字符数据)
- NCLOB(多字节字符数据)
- CLOB和NCLOB类型适用于存储超长的文本数据,BLOB字段适用于存储大量的二进制数据,如图像、视频、音频,文件等。
- 目前只支持一种外部LOB类型,即BFILE类型。在数据库内,该类型仅存储数据在操作系统中的位置信息,而数据的实体以外部文件的形式存在于操作系统的文件系统中。因而,该类型所表示的数据是只读的,不参与事务。该类型可帮助用户管理大量的由外部程序访问的文件。
MySQL BLOB :
- MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
- MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
- 实际使用中根据需要存入的数据大小定义不同的BLOB类型。需要注意的是:如果存储的文件过大,数据库的性能会下降。
使用JDBC来写入BLOB型数据到Oracle中 :
- Oracle的BLOB字段比LONG字段的性能要好,可以用来保存如图片之类的二进制数据。
- Oracle的BLOB字段由两部分组成:数据(值)和指向数据的指针(定位器)。尽管值与表自身一起存储,但是一个BLOB列并不包含值,仅有它的定位指针。为了使用大对象,程序必须声明定位器类型的本地变量。
- 当Oracle内部LOB被创建时,定位器被存放在列中,值被存放在LOB段中,LOB段是在数据库内部表的一部分。
- 因为BLOB自身有一个cursor,当写入BLOB字段必须使用指针(定位器)对BLOB进行操作,因而在写入BLOB之前,必须获得指针(定位器)才能进行写入
- 如何获得BLOB的指针(定位器) :需要先插入一个empty的BLOB,这将创建一个BLOB的指针,然后再把这个empty的BLOB的指针查询出来,这样通过两步操作,就获得了BLOB的指针,可以真正的写入BLOB数据了。
步骤:
- 插入空BLOB insert into javatest(name,content) values(?,empty_blob());
- 获得BLOB的cursor select content from javatest where name= ? for update; 注意: 须加for update,锁定该行,直至该行被修改完毕,保证不产生并发冲突。
- 利用 io,和获取到的cursor往数据库写数据流
MySQL:
/**
* 读取BLOB数据
* 1.使用getBlob()方法读取到BLOB对象
* 2.调用BLOB的getBinaryStream()方法得到输入流,再使用IO操作即可。
*/
@Test
public void testReadBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "select id,name,email,picture from customers2 where id=11";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
Blob picture = resultSet.getBlob(4);
InputStream in = picture.getBinaryStream();
OutputStream out = new FileOutputStream("2.jpg");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
/**
* 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据是无法使用字符串拼接的
* 调用setBlob(int index, InputStream inputStream)方法
*/
@Test
public void testInsertBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "insert into customers2(name,email,birth,picture) values(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "jerry");
preparedStatement.setString(2, "jerry@stone.com");
preparedStatement.setDate(3, new Date(new java.util.Date().getTime()));
InputStream in = new FileInputStream("1.jpg");
preparedStatement.setBlob(4, in);
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
Oracle:
/**
* 读取BLOB数据
* 1.使用getBlob()方法读取到BLOB对象
* 2.调用BLOB的getBinaryStream()方法得到输入流,再使用IO操作即可。
*/
@Test
public void testOracleReadBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "select name,email,birth,picture from customers2";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
Blob picture = resultSet.getBlob(4);
InputStream in = picture.getBinaryStream();
OutputStream out = new FileOutputStream("2.jpg");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
/**
* 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据是无法使用字符串拼接的
* 调用setBlob(int index, InputStream inputStream)方法
*/
@Test
public void testOracleInsertBlob() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
String sql = "insert into customers2(name,email,birth,picture) values(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "jerry");
preparedStatement.setString(2, "jerry@stone.com");
preparedStatement.setDate(3, new Date(new java.util.Date().getTime()));
InputStream in = new FileInputStream("1.jpg");
preparedStatement.setBlob(4, in);
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(resultSet, preparedStatement, connection);
}
}
数据库事物
- 在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
- 事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来,如果回退(ROLLBACK),数据库管理系统将放弃所作的所有修改而回到开始事务时的状态。
事务的ACID(acid)属性
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
JDBC 事物处理
- 事务:指构成单个逻辑工作单元的操作集合
- 事务处理:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),要么整个事务回滚(rollback)到最初状态
- 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
- 为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
- 若此时 Connection 没有被关闭, 则需要恢复其自动提交状态
数据库的隔离级别
- 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
- 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
- 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
- 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
- 一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
- 数据库提供的 4 种事务隔离级别
- Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED
- MySQL支持 4 中事务隔离级别. MySQL默认的事务隔离级别为: REPEATABLE READ
在 MySql 中设置隔离级别
- 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
- 查看当前的隔离级别: SELECT @@tx_isolation;
- 设置当前 mySQL 连接的隔离级别:
- set transaction isolation level read committed;
- 设置数据库系统的全局的隔离级别:
- set global transaction isolation level read committed;
创建表:
CREATE TABLE `mybatis`.`user` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NULL,
`password` VARCHAR(45) NULL,
`balance` INT NULL,
PRIMARY KEY (`id`));
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
public class TestTransaction {
/**
* 关于事物:
* 1.如果多个操作,每个操作使用的是自己的单独的连接,则无法保证事物
* 2.具体步骤:
* 1)。事物操作开始时,开始事物:取消Connection的默认提交行为:connection.setAutoCommit(false)
* 2)。如果事物的操作都成功,则提交事物:connection.commit();
* 3)。如果出现异常,则回滚事物:connection.rollback();
*/
@Test
public void test() {
Connection connection = null;
try {
connection = JDBCTools.getConnection();
//开始事物:默认取消提交
connection.setAutoCommit(false);
String sql = "update user set balance=balance-500 where id=1";
update(connection, sql);
sql = "update user set balance=balance+500 where id=2";
update(connection, sql);
//提交事物
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
JDBCTools.release(null, null, connection);
}
}
public void update(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, ps, null);
}
}
}
JDBC批量处理
- 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
- JDBC的批量处理语句包括下面两个方法:
- addBatch(String):添加需要批量处理的SQL语句或是参数;
- executeBatch();执行批量处理语句;
- 通常我们会遇到两种批量执行SQL语句的情况:
- 多条SQL语句的批量处理;
- 一个SQL语句的批量传参;
在Oracle数据库中创建表:
create table customers(
id number(7) primary key,
name varchar2(30),
birth date);
package com.stone.jdbc;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.junit.Test;
public class TestBatch {
/**
* 向Oracle的customers数据表中插入10w条记录
* 测试如何插入,用时最短
* 3.使用Batch,插入10w条记录可以在1秒内完成
*/
@Test
public void testBatch() {
Connection connection = null;
PreparedStatement preparedStatement = null;
String sql = null;
try {
connection = JDBCTools.getConnection();
sql = "insert into customers values(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
Date date = new Date(new java.util.Date().getTime());
long begin = System.currentTimeMillis();
JDBCTools.beginTx(connection);
for (int i = 0; i < 100000; i++) {
preparedStatement.setInt(1, i + 1);
preparedStatement.setString(2, "name_" + i);
preparedStatement.setDate(3, date);
//preparedStatement.executeUpdate();
//积攒SQL,当积攒到一定程度就统一执行一次,并清空先前积攒
preparedStatement.addBatch();
if ((i + 1) % 3000 == 0) {
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
}
//若总条数不是批量数值的整数倍,则还需要在执行一次
if (100000 % 300 != 0) {
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
JDBCTools.commit(connection);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - begin));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, preparedStatement, connection);
}
}
/**
* 向Oracle的customers数据表中插入10w条记录
* 测试如何插入,用时最短
* 2.使用PreparedStatement
*/
@Test
public void testBatchWithPreparedStatement() {
Connection connection = null;
PreparedStatement preparedStatement = null;
String sql = null;
try {
connection = JDBCTools.getConnection();
sql = "insert into customers values(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
Date date = new Date(new java.util.Date().getTime());
long begin = System.currentTimeMillis();
JDBCTools.beginTx(connection);
for (int i = 0; i < 100000; i++) {
preparedStatement.setInt(1, i + 1);
preparedStatement.setString(2, "name_" + i);
preparedStatement.setDate(3, date);
preparedStatement.executeUpdate();
}
JDBCTools.commit(connection);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - begin));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, preparedStatement, connection);
}
}
/**
* 向Oracle的customers数据表中插入10w条记录
* 测试如何插入,用时最短
* 1.使用Statement
*/
@Test
public void testBatchWithStatement() {
Connection connection = null;
Statement statement = null;
String sql = null;
try {
connection = JDBCTools.getConnection();
statement = connection.createStatement();
long begin = System.currentTimeMillis();
JDBCTools.beginTx(connection);
for (int i = 0; i < 100000; i++) {
sql = "insert into customers values(" + (i + 1) +",'name_" + i + "','11-JAN-19')";
statement.executeUpdate(sql);
}
JDBCTools.commit(connection);
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - begin));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, statement, connection);
}
}
}
DBCP连接池
JDBC数据库连接池的必要性
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接。
- 进行sql操作
- 断开数据库连接。
- 这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池(connection pool)
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
两种开源的数据库连接池
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 数据库连接池
- C3P0 数据库连接池
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
DBCP 数据源
- DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool. 如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
- Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
DBCP 数据源使用范例
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
创建dbcp.properties配置文件:
usernamestone
password=stone
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.8.137:3306/mybatis
initialSize=10
maxTotal=20
minIdle=5
maxWaitMillis=5000
package com.stone.jdbc;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.Test;
public class TestDBCP {
/**
* 1.加载dbcp的properties配置文件,配置文件中的键需要来自于BasicDataSource的属性
* 2.调用BasicDataSourceFactory.createDataSource()方法创建DataSource实例
* 3.从DataSource中获取数据库连接
* @throws Exception
*/
@Test
public void testDBCPWithDataSourceFactory() throws Exception {
Properties properties = new Properties();
InputStream inputStream = TestDBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(inputStream);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());
BasicDataSource basicDataSource = (BasicDataSource) dataSource;
System.out.println(basicDataSource.getMaxTotal());
}
/**
* 使用DBCP连接池
* 1.加入jar包:commons-dbcp2-2.5.0.jar,commons-pool2-2.6.0.jar
* 2.创建数据库连接池
* @throws Exception
*/
@Test
public void test() throws Exception {
//BasicDataSource dataSource = null;
//1.创建DBCP数据源实例
//dataSource = new BasicDataSource();
final BasicDataSource dataSource = new BasicDataSource();
//2.为数据源实例指定必须的属性
dataSource.setUsername("stone");
dataSource.setPassword("stone");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://192.168.8.137:3306/mybatis");
//3.指定数据源实例可选属性
//1)。指定数据库连接池中初始化连接数
dataSource.setInitialSize(5);
//2).指定最大的连接数:同一时刻可以同时向数据库申请的连接数
dataSource.setMaxTotal(5);
//3).指定最小空闲连接数:在数据库连接池中保存的最少的空闲连接的数量
dataSource.setMinIdle(5);
//4).等待数据库连接池分配连接的最长时间,单位为毫秒,超出时间将抛出异常
dataSource.setMaxWaitMillis(1000 * 5);
//4.从数据源中获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection.getClass());
connection = dataSource.getConnection();
System.out.println(connection.getClass());
connection = dataSource.getConnection();
System.out.println(connection.getClass());
connection = dataSource.getConnection();
System.out.println(connection.getClass());
Connection connection2 = dataSource.getConnection();
System.out.println(connection2.getClass());
new Thread() {
public void run() {
Connection conn;
try {
conn = dataSource.getConnection();
System.out.println(conn.getClass());
} catch (SQLException e) {
e.printStackTrace();
}
};
}.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
connection2.close();
}
}
C3P0连接池
创建配置文件c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="mysql">
<!-- 指定连接数据源的基本属性 -->
<property name="user">stone</property>
<property name="password">stone</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://192.168.8.137:3306/mybatis</property>
<!-- 若数据库中连接数不足时,一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中最大的数据库连接数 -->
<property name="maxPoolSize">100</property>
<!-- C3P0数据库连接池可以维护的Statement的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接可以同时使用的Statement对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
package com.stone.jdbc;
import javax.sql.DataSource;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class TestC3P0 {
/**
* 创建c3p0-config.xml配置文件
* @throws Exception
*/
@Test
public void testC3P0WithConfigFile() throws Exception {
DataSource dataSource = new ComboPooledDataSource("mysql");
System.out.println(dataSource.getConnection());
ComboPooledDataSource comboPooledDataSource = (ComboPooledDataSource) dataSource;
System.out.println(comboPooledDataSource.getMaxStatements());
}
/**
* C3P0连接池
* 1.导入jar包:c3p0-0.9.5.2.jar,mchange-commons-java-0.2.11.jar
* @throws Exception
*/
@Test
public void test() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://192.168.8.137:3306/mybatis");
cpds.setUser("stone");
cpds.setPassword("stone");
System.out.println(cpds.getConnection());
}
}
使用DBUtils进行更新操作
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
API介绍:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
工具类
- org.apache.commons.dbutils.DbUtils
DbUtils类
- DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
- public static void close(…) throws java.sql.SQLException:DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
- public static void closeQuietly(…):这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLException。
- public static void commitAndCloseQuietly(Connection conn):用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
- public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
QueryRunner类
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
QueryRunner类提供了两个构造方法:
- 默认的构造方法
- 需要一个 javax.sql.DataSource 来作参数的构造方法。
QueryRunner类的主要方法
- public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
- public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
- public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 执行一个不需要置换参数的查询操作。
- public int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
- public int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作。
ResultSetHandler接口
- 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
- ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
ResultSetHandler 接口的实现类
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
/**
* 测试QueryRunner类的update方法
* 该方法可用于insert,delete和update,类似于前面编写的Dao方法。
*/
@Test
public void testQueryRunnerUpdate() {
//1.创建QueryRunner的实现类
QueryRunner queryRunner = new QueryRunner();
//2.使用其update方法
String sql = "delete from customers where id in (?,?)";
Connection conn = null;
try {
conn = JDBCTools.getConnection();
queryRunner.update(conn, sql, 10,11);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
使用DBUtils进行查询操作
class MyResultSetHandler implements ResultSetHandler{
@Override
public Object handle(ResultSet rs) throws SQLException {
List<Customers> customers = new ArrayList<>();
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
String email = rs.getString(3);
Date birth = rs.getDate(4);
Customers customer = new Customers(id, name, email, birth);
customers.add(customer);
}
return customers;
}
}
/**
* queryRunner的query方法的返回值取决于其ResultSetHandler参数的handle方法的返回值
*/
@Test
public void testQuery() {
// 1.创建QueryRunner的实现类
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers";
Object obj = queryRunner.query(conn, sql, new MyResultSetHandler());
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
/**
* ScalarHandler:把结果集转为一个数值(可以是任意基本数据类型)返回
*/
@Test
public void testScalarHandler() {
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select name from customers where id=?";
Object name = queryRunner.query(conn, sql, new ScalarHandler(),2);
System.out.println(name);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
/**
* MapListHandler:将结果集转为一个Map的List
* Map对应查询的一条记录:键:SQL查询的列名(不是列的别名),值:列的值
* 而MapListHandler:返回多条记录对应的Map集合
*/
@Test
public void testMapListHandler() {
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers";
List<Map<String, Object>> customers = queryRunner.query(conn, sql, new MapListHandler());
System.out.println(customers);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
/**
* MapHandler:返回SQL对应的第一条记录对应的Map对象
* 键:SQL查询的列名(不是列的别名),值:列的值
*/
@Test
public void testMapHandler() {
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers";
Map<String, Object> customers = queryRunner.query(conn, sql, new MapHandler());
System.out.println(customers);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
/**
* BeanListHandler:把结果集转为List,该List不为null,但可能为空集合(size()方法返回0)
* 若SQL语句的确能够查询到记录,List中存放创建BeanListHandler传入的class对象对应的对象
*/
@Test
public void testBeanListHandler() {
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers";
@SuppressWarnings("unchecked")
List<Customers> customers = (List<Customers>) queryRunner.query(conn, sql, new BeanListHandler(Customers.class));
System.out.println(customers);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
/**
* BeanHandler:把结果集的第一条记录转为创建BeanHandler对象时传入的class参数对应的对象
*/
@Test
public void testBeanHandler() {
QueryRunner queryRunner = new QueryRunner();
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name,email,birth from customers where id=?";
@SuppressWarnings("unchecked")
Customers customer = (Customers) queryRunner.query(conn, sql, new BeanHandler(Customers.class), 2);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
使用DBUtils编写通用的DAO
package com.stone.jdbc;
import java.sql.Connection;
import java.util.List;
/**
* 访问数据的DAO接口
* 定义访问数据表的各种方法
* @param T:DAO处理实体类的类型
*/
public interface DBUtilsDAO<T> {
/**
* INSERT,UPDATE,DELETE
* @param connection:数据库连接
* @param sql:SQL语句
* @param args:填充占位符的可变参数
*/
void update(Connection connection, String sql, Object ... args);
/**
* 返回一个T的对象
* @param connection
* @param sql
* @param args
* @return
* @throws Exception
*/
T get(Connection connection, String sql, Object ... args) throws Exception;
/**
* 返回T的一个集合
* @param connection
* @param sql
* @param args
* @return
*/
List<T> getForList(Connection connection, String sql, Object ... args);
/**
* 返回具体的一个值,例如总记录数
* @param connection
* @param sql
* @param args
* @return
*/
<E> E getForValue(Connection connection, String sql, Object ... args);
/**
* 批量处理的方法
* @param connection
* @param sql
* @param args:填充占位符的Object[]类型的可变参数
*/
void batch(Connection connection, String sql, Object[] ... args);
}
package com.stone.jdbc;
import java.sql.Connection;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
/**
* 使用QueryRunner提供其具体的实现
* @param <T>:子类需要传入的泛型类型
*/
public class DBUtilsDAOImpl<T> implements DBUtilsDAO<T> {
private QueryRunner queryRunner = null;
private Class<T> type;
public DBUtilsDAOImpl() {
queryRunner = new QueryRunner();
type = ReflectionUtils.getSuperGenericType(getClass());
}
@Override
public void update(Connection connection, String sql, Object... args) {
// TODO Auto-generated method stub
}
@Override
public T get(Connection connection, String sql, Object... args) throws Exception {
return queryRunner.query(connection, sql, new BeanHandler<>(type), args);
}
@Override
public List<T> getForList(Connection connection, String sql, Object... args) {
return null;
}
@Override
public <E> E getForValue(Connection connection, String sql, Object... args) {
// TODO Auto-generated method stub
return null;
}
@Override
public void batch(Connection connection, String sql, Object[]... args) {
// TODO Auto-generated method stub
}
}
package com.stone.jdbc;
public class CustomersDao extends DBUtilsDAOImpl<Customers> {
}
package com.stone.jdbc;
import static org.junit.Assert.fail;
import java.sql.Connection;
import org.junit.Test;
public class CustomersDaoTest {
CustomersDao customersDao = new CustomersDao();
@Test
public void testUpdate() {
fail("Not yet implemented");
}
@Test
public void testGet() {
Connection conn = null;
try {
conn = JDBCTools.getConnection();
String sql = "select id,name customerName,email,birth from customers where id=?";
Customers customer = (Customers) customersDao.get(conn, sql, 2);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, null, conn);
}
}
@Test
public void testGetForList() {
}
@Test
public void testGetForValue() {
fail("Not yet implemented");
}
@Test
public void testBatch() {
fail("Not yet implemented");
}
}
调用函数及存储过程
package com.stone.jdbc;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;
import org.junit.Test;
public class TestCallableStatement {
/**
* 如何使用JDBC调用存储在数据库中的函数或存储过程
*/
@Test
public void test() {
Connection conn = null;
CallableStatement callableStatement = null;
try {
conn = JDBCTools.getConnection();
//1.通过Connection对象的prepareCall()方法创建一个CallableStatement对象的实例,
//在使用Connection对象的prepareCall()方法时,需要传入一个String类型的字符串,该字符串用于指明如何调用存储过程
String sql = "{?= call sum_salary(?,?)}";
callableStatement = conn.prepareCall(sql);
//2.通过CallableStatement对象的registerOutParameter方法注册OUT参数
callableStatement.registerOutParameter(1, Types.NUMERIC);
callableStatement.registerOutParameter(3, Types.NUMERIC);
//3.通过CallableStatement对象的setXxx()方法设定IN或IN OUT参数
//若想将参数默认值设为null,可以使用setNull()方法
callableStatement.setInt(2, 80);
//4.通过CallableStatement对象的execute()方法执行存储过程
callableStatement.execute();
//5.如果所调用的是带返回参数的存储过程,还需要通过CallableStatement对象的getXxx()方法获取其返回值
double sumSalary = callableStatement.getDouble(1);
long empCount = callableStatement.getLong(3);
System.out.println(sumSalary);
System.out.println(empCount);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(null, callableStatement, conn);
}
}
}
JUC
在Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了用于多线程上下文中的Collection 实现等。
volatile
内存可见性:
- 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
- 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
- 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:
- 对于多线程,不是一种互斥关系
- 不能保证变量状态的“原子性操作”
问题引出:
package com.stone.juc;
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
if (td.isFlag()) {
System.out.println("=========");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + isFlag());
}
}
输出:flag=true
此时结果输出只有“flag=true”,表示“new Thread(td).start();”这个语句执行了,但是后面的while循环这个main线程却没有执行。这就引出了内存可见性。
内存可见性:当多个线程操作共享数据时,彼此不可见。这是由于多个线程有自己的缓存区域,对自身缓存的修改对其他线程不可见。
以前是加锁来解决这个问题,但是效率低。
while (true) {
synchronized (td) {
if (td.isFlag()) {
System.out.println("=========");
break;
}
}
}
可以使用volatile关键字修饰共享变量来解决,相当于直接修改主存中数据。当多个线程进行操作共享数据时,可以保证内存中的数据可见。
private volatile boolean flag = false;
volatile相较于synchronized是一种较为轻量级的同步策略。
CAS
CAS 算法
- CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
- CAS 是一种无锁的非阻塞算法的实现。
- CAS 包含了3 个操作数:
- 需要读写的内存值V
- 进行比较的值A
- 拟写入的新值B
- 当且仅当V 的值等于A 时,CAS 通过原子方式用新值B 来更新V 的值,否则不会执行任何操作。
原子变量
- 类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。
- 类AtomicBoolean、AtomicInteger、AtomicLong 和AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
- AtomicIntegerArray、AtomicLongArray 和AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
- 核心方法:boolean compareAndSet(expectedValue, updateValue)
- java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
- AtomicBoolean 、AtomicInteger 、AtomicLong 、AtomicReference
- AtomicIntegerArray 、AtomicLongArray
- AtomicMarkableReference
- AtomicReferenceArray
- AtomicStampedReference
问题引出:
package com.stone.juc;
/**
* 1.i++的原子性问题:i++的操作实际上分为三个步骤“读-改-写”
* int i = 10;
* i = i++; //10
*
* int temp = i;
* i = i + 1;
* i = temp;
*
* 2.原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用了原子变量
*
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNumber = 0;
public int getSerialNumber() {
return serialNumber++;
}
public void setSerialNumber(int serialNumber) {
this.serialNumber = serialNumber;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
}
输出:
Thread-3:0
Thread-0:2
Thread-7:3
Thread-4:4
Thread-1:1
Thread-2:0
Thread-5:5
Thread-8:6
Thread-9:8
Thread-6:7
可以看到输出结果有重复的数据,这是由于多个线程对主存的操作导致,可以使用原子变量来解决:
package com.stone.juc;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 1.i++的原子性问题:i++的操作实际上分为三个步骤“读-改-写”
* int i = 10;
* i = i++; //10
*
* int temp = i;
* i = i + 1;
* i = temp;
*
* 2.原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用了原子变量
* 1). volatile保证内存可见性
* 2). CAS(Compare-And-Swap)算法保证数据的原子性,CAS算法是硬件对于并发操作共享数据的支持
* CAS包含了三个操作数:内存值:V,预估值:A,更行值:B,当且仅当V==A时,V=B,否则,将不做任何操作
*
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private AtomicInteger serialNumber = new AtomicInteger();
public int getSerialNumber() {
return serialNumber.getAndIncrement();
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
}
package com.stone.juc;
/**
* 模拟CAS算法
*/
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random()*100));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap {
private int value;
// 获取内存值
public synchronized int get() {
return value;
}
// 比较
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
this.value = newValue;
}
return oldValue;
}
// 设置
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
ConcurrentHashMap
- Java 5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
- ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于HashMap 与Hashtable 之间。内部采用“锁分段”机制替代Hashtable 的独占锁。进而提高性能。
- 此包还提供了用于多线程上下文中的Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和CopyOnWriteArraySet。当期望多线程访问一个给定collection 时,ConcurrentHashMap 通常优于同步的HashMap,ConcurrentSkipListMap 通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的ArrayList。
package com.stone.juc;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* CopyOnWriteArrayList/CopyOnWriteArraySet:写入并复制
* 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常大。并发迭代操作多时可以选择
*/
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable{
//private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("aa");
list.add("bb");
list.add("cc");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
list.add("aa");
}
}
}
CountDownLatch
- Java 5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
- CountDownLatch (闭锁)一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
- 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
- 等待直到某个操作所有参与者都准备就绪再继续执行。
package com.stone.juc;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch:闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行 计算多线程程序的执行时间
*/
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗费时间:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
super();
this.latch = latch;
}
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 10000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
Callable
- Java 5.0 在java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口
- Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。
- Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁。
创建执行线程的方式有4种:
- 无返回值:
- 继承thread类
- 实现runnable接口
- 有返回值:
- callable接口
- 线程池
package com.stone.juc1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建执行线程的方式3:实现Callable接口,相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
* 执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果,FutureTask是Future接口的实现类
*/
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
try {
Integer sum = result.get(); //获取线程运算后的结果,FutureTask也可用于闭锁,等待线程运行完成后再获取结果
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
Lock
- 在Java 5.0 之前,协调共享对象的访问时可以使用的机制只有synchronized 和volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
- ReentrantLock 实现了Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
使用Lock解决安全问题:
package com.stone.juc1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 用于解决多线程安全问题的方式:
* 1.同步代码块(synchronized隐式锁)
* 2.同步方法(synchronized隐式锁)
* 3.同步锁Lock:是一个显示锁,需要通过lock()方法上锁,必须通过unlock()方法释放锁
*/
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"1号窗口").start();
new Thread(ticket,"2号窗口").start();
new Thread(ticket,"3号窗口").start();
}
}
class Ticket implements Runnable{
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock(); //上锁
try {
if (tick > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成售票,余票为: " + --tick);
}
} finally {
lock.unlock(); //释放锁
}
}
}
}
生产者消费者问题-使用Synchronized方式:
package com.stone.juc1;
/**
* 生产者消费者
*
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();
}
}
// 店员
class Clerk {
private int product = 0;
// 进货
public synchronized void get() {
while (product >= 1) {
System.out.println("产品已满!");
try {
this.wait(); // 为了避免虚假唤醒问题,应该总是使用在循环中
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
// 卖货
public synchronized void sale() {
while (product <= 0) {
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
// 生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
// 消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
Condition
- Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个Lock 可能与多个Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的Object 版本中的不同。
- Condition 对象中,与wait、notify 和notifyAll 方法对应的分别是await、signal 和signalAll。
- Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。
生产者消费者问题-使用Lock同步锁方式:
package com.stone.juc1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 生产者消费者
*
*/
public class TestProductorAndConsumerForLock {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();
}
}
// 店员
class Clerk {
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 进货
public void get() {
lock.lock();
try {
while (product >= 1) {
System.out.println("产品已满!");
try {
condition.await(); // 为了避免虚假唤醒问题,应该总是使用在循环中
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
condition.signalAll();
} finally {
lock.unlock();
}
}
// 卖货
public void sale() {
lock.lock();
try {
while (product <= 0) {
System.out.println("缺货!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
// 生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
// 消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
编写一个程序,开启3 个线程,这三个线程的ID 分别为A、B、C,每个线程将自己的ID 在屏幕上打印10 遍,要求输出的结果必须按顺序显示。
如:ABCABCABC…… 依次递归
package com.stone.juc1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo alternateDemo = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternateDemo.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternateDemo.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternateDemo.loopC(i);
System.out.println("-----------------");
}
}
}, "C").start();
}
}
class AlternateDemo {
private int number = 1; // 当前正在执行线程的标记
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalLoop) {
lock.lock();
try {
if (number != 1) {
condition1.await();
}
for (int i = 0; i < 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
lock.lock();
try {
if (number != 2) {
condition2.await();
}
for (int i = 0; i < 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
lock.lock();
try {
if (number != 3) {
condition3.await();
}
for (int i = 0; i < 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
ReadWriteLock
- ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer,读取锁可以由多个reader 线程同时保持。写入锁是独占的。。
- ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
package com.stone.juc1;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 写写/读写需要“互斥”
* 读读不需要互斥
*/
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteDemo rw = new ReadWriteDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random() * 101));
}
},"write:").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
}).start();
}
}
}
class ReadWriteDemo{
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
//读
public void get() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " : " + number);
} finally {
lock.readLock().unlock();
}
}
//写
public void set(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
lock.writeLock().unlock();
}
}
}
线程八锁
- 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
- 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
- 加个普通方法后发现和同步锁无关
- 换成两个对象后,不是同一把锁了,情况立刻变化。
- 都换成静态同步方法后,情况又变化
- 所有的非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
- 所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
package com.stone.juc1;
/**
* 1.两个普通同步方法,两个线程,标准打印,打印 【one,tow】
* 2.新增Thread.sleep()给getOne(),打印【one,tow】
* 3.新增普通方法getThree(),打印【three,one,tow】
* 4.两个普通同步方法,两个Number对象,打印【tow,one】
* 5.修改getOne()为静态同步方法,打印【tow,one】
* 6.修改两个方法均为静态同步方法,一个Number对象,打印【one,tow】
* 7.一个是静态同步方法,一个非静态同步方法,两个Number对象,打印【tow,one】
* 8.两个静态同步方法,两个Number对象,打印【one,tow】
*
* 线程八锁的关键:
* 1.非静态方法的锁默认为this,静态方法的锁为对应的Class实例
* 2.在某一个时刻内,只能有一个线程持有锁,不论几个方法
*
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//number.getTwo();
number2.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("three");
}
}
ThreadPool
- 第四种获取线程的方法:线程池,一个ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用Executors 工厂方法配置。
- 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。
- 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子(hook)。但是,强烈建议程序员使用较为方便的Executors 工厂方法:
- Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
- Executors.newFixedThreadPool(int)(固定大小线程池)
- Executors.newSingleThreadExecutor()(单个后台线程)
- 它们均为大多数使用场景预定义了设置。
package com.stone.juc1;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 线程池:提供了一个线程队列,队列中保存所有等待状态的线程,避免了创建于销毁额外开销,提高了响应的速度
* 线程池的体系结构:
* java.util.concurrent.Executor:负责线程的使用与调度的根接口
* |--ExecutorService:子接口:线程池的主要接口
* |--ThreadPoolExecutor:线程池实现类
* |--ScheduledExecutorService:子接口:负责线程的调度
* |--ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor,实现了ScheduledExecutorService
*
* 工具类:Executors
* ExecutorService newCachedThreadPool():缓存线程池,线程的数量不固定,可以根据需求自动的更改数量
* ExecutorService newFixedThreadPool(int nThreads):创建固定大小的线程池
* ExecutorService newSingleThreadExecutor():创建单个线程的线程池,线程池中只有一个线程
*
* ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建固定大小的线程,可以延迟或定时执行任务
*/
public class TestThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
list.add(future);
}
pool.shutdown();
for (Future<Integer> future : list) {
System.out.println(future.get());
}
// ThreadPoolDemo tpd = new ThreadPoolDemo();
//
// //2.为线程池中的线程分配任务
// for (int i = 0; i < 10; i++) {
// pool.submit(tpd);
// }
//
// //3.关闭线程池
// pool.shutdown();
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
package com.stone.juc1;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TestScheduledThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
ScheduledFuture<Integer> future = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int number = new Random().nextInt(100);
System.out.println(Thread.currentThread().getName() + " : " + number);
return number;
}
}, 1, TimeUnit.SECONDS);
System.out.println(future.get());
}
pool.shutdown();
}
}
ForkJoinPool
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总。
Fork/Join 框架与线程池的区别
- 采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
- 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
package com.stone.juc1;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class TestForkJoinPool {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 100L);
Long sum = pool.invoke(task);
System.out.println(sum);
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long>{
/**
*
*/
private static final long serialVersionUID = 5550409637088062743L;
private long start;
private long end;
private static final long THRESHOLD = 10L;
public ForkJoinSumCalculate(long start, long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else {
long middle = (start + end) / 2;
ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
left.fork(); //进行拆分,同时压入线程队列
ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
NIO
Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
Java NIO 与IO 的主要区别:
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(NonBlocking IO) |
(无) | 选择器(Selectors) |
通道和缓冲区
Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO 系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
简而言之,Channel负责传输,Buffer负责存储
Buffer
缓冲区(Buffer):一个用于特定基本数据类型的容器。由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类。
Java NIO中的Buffer主要用于与NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
Buffer就像一个数组,可以保存多个相同类型的数据。根据数据类型不同(boolean除外) ,有以下Buffer常用子类:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述Buffer类他们都采用相似的方法进行管理数据,只是各自管理的数据类型不同而已。都是通过如下方法获取一个Buffer 对象:
static XxxBuffer allocate(int capacity) : 创建一个容量为capacity的XxxBuffer对象
缓冲区的基本属性
Buffer中的重要概念:
- 容量(capacity) :表示Buffer最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
- 限制(limit):第一个不应该读取或写入的数据的索引,即位于limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
- 位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制。
- 标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。
- 标记、位置、限制、容量遵守以下不变式:0<=mark<=position<=limit<=capacity
Buffer 的常用方法:
方法 | 描述 |
---|---|
Buffer clear() | 清空缓冲区并返回对缓冲区的引用 |
Buffer flip() | 将缓冲区的界限设置为当前位置,并将当前位置充值为0 |
int capacity() | 返回Buffer的capacity大小 |
boolean hasRemaining() | 判断缓冲区中是否还有元素 |
int limit() | 返回Buffer的界限(limit) 的位置 |
Buffer limit(int n) | 将设置缓冲区界限为n, 并返回一个具有新limit的缓冲区对象 |
Buffer mark() | 对缓冲区设置标记 |
int position() | 返回缓冲区的当前位置position |
Buffer position(int n) | 将设置缓冲区的当前位置为n , 并返回修改后的Buffer对象 |
int remaining() | 返回position和limit之间的元素个数 |
Buffer reset() | 将位置position转到以前设置的mark所在的位置 |
Buffer rewind() | 将位置设为为0,取消设置的mark |
缓冲区的数据操作
Buffer所有子类提供了两个用于数据操作的方法:get()与put()方法
获取Buffer 中的数据
get() :读取单个字节
get(byte[] dst):批量读取多个字节到dst中
get(int index):读取指定索引位置的字节(不会移动position)
放入数据到Buffer 中
- put(byte b):将给定单个字节写入缓冲区的当前位置
- put(byte[] src):将src中的字节写入缓冲区的当前位置
- put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动position)
@Test
public void test1() {
String str = "abcde";
//1.分配一个指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("--------------allocate()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//2.使用put()方法存入数据到缓冲区
byteBuffer.put(str.getBytes());
System.out.println("--------------put()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//3.切换读取数据模式
byteBuffer.flip();
System.out.println("--------------flip()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//4.使用get()读取缓冲区中的数据
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst);
System.out.println(new String(dst,0,dst.length));
System.out.println("--------------get()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//5.rewind():可重复读
byteBuffer.rewind();
System.out.println("--------------rewind()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
//6.clear():清空缓冲区.但是缓冲区中的数据依然存在,处于“被遗忘”状态
byteBuffer.clear();
System.out.println("--------------clear()--------------");
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
System.out.println((char)byteBuffer.get());
}
输出:
--------------allocate()--------------
0
1024
1024
--------------put()--------------
5
1024
1024
--------------flip()--------------
0
5
1024
abcde
--------------get()--------------
5
5
1024
--------------rewind()--------------
0
5
1024
--------------clear()--------------
0
1024
1024
a
@Test
public void test2() {
String str = "abcde";
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(str.getBytes());
byteBuffer.flip();
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst, 0, 2);
System.out.println(new String(dst, 0, 2));
System.out.println(byteBuffer.position());
//mark():标记
byteBuffer.mark();
byteBuffer.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));
System.out.println(byteBuffer.position());
//reset():恢复到mark的位置
byteBuffer.reset();
System.out.println(byteBuffer.position());
//判断缓冲区中是否还有剩余的数据
if (byteBuffer.hasRemaining()) {
//获取缓冲区中可以操作的数据量
System.out.println(byteBuffer.remaining());
}
}
输出:
ab
2
cd
4
2
3
直接与非直接缓冲区:
- 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java虚拟机会尽最大努力直接在此缓冲区上执行本机I/O操作。也就是说,在每次调用基础操作系统的一个本机I/O操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
- 直接字节缓冲区可以通过调用此类的allocateDirect()工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
- 直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。Java平台的实现有助于通过JNI从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
- 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
@Test
public void test3() {
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
System.out.println(buf.isDirect());;
}
输出:true
Channel
通道(Channel):由java.nio.channels包定义的。Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer 进行交互。
Java为Channel接口提供的最主要实现类如下:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:通过UDP 读写网络中的数据通道。
- SocketChannel:通过TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的TCP 连接,对每一个新进来的连接都会创建一个SocketChannel。
获取通道的一种方式是对支持通道的对象调用getChannel()方法。支持通道的类如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
获取通道的其他方式是使用Files类的静态方法newByteChannel()获取字节通道。或者通过通道的静态方法open()打开并返回指定通道。
package com.stone.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
/**
* 1.通道(Channel):用于源节点与目标节点的连接,在JAVA NIO中负责缓冲区中数据的传输。Channel本身不存储数据,因此需要配置缓冲区进行传输
* 2.通道的主要实现类
* java.nio.channels.Channel 接口:
* |--FileChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
* 3.获取通过
* 1)。Java针对支持通道的类提供了getChannel()方法
* 本地IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
* 2)。在Jdk 1.7中的NIO 2 针对各个通道提供了静态方法open()
* 3)。在Jdk 1.7中的NIO 2 的Files工具类的newByteChannel()
*
* 4.通道之间的数据传输
* transferFrom()
* transferTo()
*
*/
public class TestChannel {
//通道之间的数据传输(直接缓冲区)
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);
//inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
outChannel.close();
inChannel.close();
}
//2.使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
outChannel.close();
inChannel.close();
}
//1.利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
//获取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//将通道中的数据存入缓冲区中
while (inChannel.read(buf) != -1) {
buf.flip(); //切换成读取数据的模式
//将缓冲区中的数据写入通道中
outChannel.write(buf);
buf.clear(); //清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Scatter and Gather
分散读取(Scattering Reads)是指从Channel中读取的数据“分散”到多个Buffer中。
注意:按照缓冲区的顺序,从Channel中读取的数据依次将Buffer填满。
聚集写入(Gathering Writes)是指将多个Buffer中的数据“聚集”到Channel。
注意:按照缓冲区的顺序,写入position和limit之间的数据到Channel 。
@Test
public void test4() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
//1.获取通道
FileChannel channel1 = raf1.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3.分散读取
ByteBuffer[] bufs = {buf1,buf2};
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println("*****************************");
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
//4.聚集写入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
}
FileChannel的常用方法:
方法 | 描述 |
---|---|
int read(ByteBuffer dst) | 从Channel 中读取数据到ByteBuffer |
long read(ByteBuffer[] dsts) | 将Channel 中的数据“分散”到ByteBuffer[] |
int write(ByteBuffer src) | 将ByteBuffer 中的数据写入到Channel |
long write(ByteBuffer[] srcs) | 将ByteBuffer[] 中的数据“聚集”到Channel |
long position() | 返回此通道的文件位置 |
FileChannel position(long p) | 设置此通道的文件位置 |
long size() | 返回此通道的文件的当前大小 |
FileChannel truncate(long s) | 将此通道的文件截取为给定大小 |
void force(boolean metaData) | 强制将所有对此通道的文件更新写入到存储设备中 |
Charset
编码:字符串 ==> 字节数组
解码:字节数组 ==> 字符串
//字符集
@Test
public void test6() throws IOException {
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder ce = cs1.newEncoder();
//获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("中华人民共和国");
cBuf.flip();
//编码
ByteBuffer bBuf = ce.encode(cBuf);
for (int i = 0; i < 14; i++) {
System.out.println(bBuf.get());
}
//解码
bBuf.flip();
CharBuffer cBuf2 = cd.decode(bBuf);
System.out.println(cBuf2.toString());
System.out.println("---------------------------------------------");
Charset cs2 = Charset.forName("UTF-8");
bBuf.flip();
CharBuffer cBuf3 = cs2.decode(bBuf);
System.out.println(cBuf3.toString());
}
阻塞与非阻塞
传统的IO流都是阻塞式的。也就是说,当一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
Java NIO是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
阻塞式:
package com.stone.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
/**
* 1.使用NIO完成网络通信的三个核心:
* 1)通道(Channel):负责连接
* java.nio.channels.Channel 接口:
* |--SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
* 2)缓冲区(Buffer):负责数据的存取
* 3)选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
*
*/
public class TestBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地文件并发送到服务端
while (inChannel.read(buf) != -1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
//4.关闭通道
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("4.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3.获取客户端连接的通道
SocketChannel sChannel = ssChannel.accept();
//4.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//5.接收客户端数据,并保存到本地
while (sChannel.read(buf) != -1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
//6.关闭通道
sChannel.close();
outChannel.close();
ssChannel.close();
}
}
package com.stone.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
/**
* 1.使用NIO完成网络通信的三个核心:
* 1)通道(Channel):负责连接
* java.nio.channels.Channel 接口:
* |--SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
* 2)缓冲区(Buffer):负责数据的存取
* 3)选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
*
*/
public class TestBlockingNIO2 {
//客户端
@Test
public void client() throws IOException {
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024);
while (inChannel.read(buf) != -1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
//表示客户端结束输入
sChannel.shutdownOutput();
//接收服务端的反馈
int len = 0;
while ((len = sChannel.read(buf)) != -1) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException {
ServerSocketChannel ssChannel = ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("4.jpg"), StandardOpenOption.WRITE, StandardOpenOption.WRITE);
ssChannel.bind(new InetSocketAddress(9898));
SocketChannel sChannel = ssChannel.accept();
ByteBuffer buf = ByteBuffer.allocate(1024);
while (sChannel.read(buf) != -1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
//发送反馈给客户端
buf.put("服务端接收数据成功".getBytes());
buf.flip();
sChannel.write(buf);
sChannel.close();
outChannel.close();
ssChannel.close();
}
}
非阻塞式:
package com.stone.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
public class TestNonBlockingNIO {
//客户端
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换成非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4.发送数据给服务端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String str = scanner.next();
buf.put((new Date().toString() + "\n" + str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//5.关闭通道
scanner.close();
sChannel.close();
}
@Test
public void server() throws IOException {
//1。获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换成非阻塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上,并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询获取选择器上已经“准备就绪”的事件
while (selector.select() > 0) {
//7.获取当前选择器中所有注册的"选择键(已就绪的监听事件)"
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
//8.获取准备"就绪"的事件
SelectionKey sk = it.next();
//9.判断具体是什么事件准备就绪
if (sk.isAcceptable()) {
//10.若"接收就绪",获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//11.切换非阻塞模式
sChannel.configureBlocking(false);
//12.将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if (sk.isReadable()) {
//13.获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//14.读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = sChannel.read(buf)) > 0) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
//15.取消选择键SelectionKey
it.remove();
}
}
}
}
选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO 的核心。
方法 | 描述 |
---|---|
Set<SelectionKey> keys() | 所有的SelectionKey集合。代表注册在该Selector上的Channel |
Set<SelectionKey> selectedKeys() | 被选择的SelectionKey集合。返回此Selector的已选择键集 |
Selector open() | 创建一个selector |
int select() | 监控所有注册的Channel,当它们中间有需要处理的IO操作时,该方法返回,并将对应得的SelectionKey加入被选择的SelectionKey集合中,该方法返回这些Channel的数量。 |
int select(long timeout) | 可以设置超时时长的select()操作 |
int selectNow() | 执行一个立即返回的select()操作,该方法不会阻塞线程 |
Selector wakeup() | 使一个还未返回的select() 方法立即返回 |
void close() | 关闭该选择器 |
选择器(Selector)的应用
- 创建Selector :通过调用Selector.open()方法创建一个Selector。
- 向选择器注册通道:SelectableChannel.register(Selector sel, int ops)
- 当调用register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops 指定。
- 可以监听的事件类型(可使用SelectionKey的四个常量表示):
- 读: SelectionKey.OP_READ (1)
- 写: SelectionKey.OP_WRITE (4)
- 连接: SelectionKey.OP_CONNECT(8)
- 接收: SelectionKey.OP_ACCEPT (16)
- 若注册时不止监听一个事件,则可以使用“位或”操作符连接。
SelectionKey:表示SelectableChannel和Selector之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。
方法 | 描述 |
---|---|
int interestOps() | 获取感兴趣事件集合 |
int readyOps() | 获取通道已经准备就绪的操作的集合 |
SelectableChannel channel() | 获取注册通道 |
Selector selector() | 返回选择器 |
boolean isReadable() | 检测Channal 中读事件是否就绪 |
boolean isWritable() | 检测Channal 中写事件是否就绪 |
boolean isConnectable() | 检测Channel 中连接是否就绪 |
boolean isAcceptable() | 检测Channel 中接收是否就绪 |
SocketChannel:
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。
操作步骤:
- 打开SocketChannel
- 读写数据
- 关闭SocketChannel
ServerSocketChannel
Java NIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
DatagramChannel
Java NIO中的DatagramChannel是一个能收发UDP包的通道。
操作步骤:
- 打开DatagramChannel
- 接收/发送数据
package com.stone.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
public class TestNonBlockingNIO2 {
@Test
public void send() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
String str = scan.next();
buf.put((new Date().toString() +":\n" + str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
buf.clear();
}
scan.close();
dc.close();
}
@Test
public void receive() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
if (sk.isReadable()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
Pipe
Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
package com.stone.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.Pipe.SinkChannel;
import java.nio.channels.Pipe.SourceChannel;
import org.junit.Test;
public class TestPipe {
@Test
public void test1() throws IOException {
//1.获取管道
Pipe pipe = Pipe.open();
//2.将缓冲区中的数据写入管道
ByteBuffer buf = ByteBuffer.allocate(1024);
SinkChannel sinkChannel = pipe.sink();
buf.put("通过单向管道发送数据".getBytes());
buf.flip();
sinkChannel.write(buf);
//3.读取缓冲区中数据
SourceChannel sourceChannel = pipe.source();
buf.flip();
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(), 0, len));
sourceChannel.close();
sinkChannel.close();
}
}