文章目录
九.File、IO流
File
- File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹).
IO流
- 用于读写数据的(可以读写文件,或网络中的数据…)
1.File
1.1创建对象
创建File类的对象
注意
- File对象既可以代表文件、也可以代表文件夹.
- File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的.
绝对路径、相对路径
- 绝对路径:从盘符开始
- 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件.
示例:
Test1
package com.itheima.d1_file;
import java.io.File;
//目标:掌握File创建对象,代表具体文件的方案.
public class Test1 {
public static void main(String[] args) {
//1、创建一个FiLe对象,指代某个具体的文件,三种写法
//File f1=new File("D:/迅雷下载/abc.docx");
//File f1=new File("D:\\迅雷下载\\abc.docx");
File f1=new File("D:"+File.separator+"迅雷下载"+File.separator+"abc.docx");
System.out.println(f1.length());//文件大小(单位字节)
//文件夹本身的大小
File f2=new File("D:/迅雷下载");
System.out.println(f2.length());
//注意:File对象可以指代一个不存在的文件路径
File f3 =new File("D:/resource/aaaa.txt");
System.out.println(f3.length());//0
System.out.println(f3.exists());//false
//我现在要定位的文件是在模块中,应该怎么定位呢?
//绝对路径:带盘符的
File f4=new File("D:\\Code\\javasepro\\file-io-app\\src\\HH.txt");
//相对路径(重点):不带盘符,默认是直接去工程下寻找文件的.
File f5=new File("src/HH.txt");
System.out.println(f5.length());
}
}
1.2常用方法1:判断文件类型、获取文件信息
File提供的判断文件类型、获取文件信息功能
示例:
Test2
package com.itheima.d1_file;
import org.w3c.dom.ls.LSOutput;
import java.io.File;
import java.text.SimpleDateFormat;
//目标:掌握File提供的判断文件类型、获取文件信息功能
public class Test2 {
public static void main(String[] args) {
//1,创建文件对象,指代某个文件
File f1 = new File("c");
//2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true
System.out.println(f1.exists());
//3、public boolean isFile():判断当前文件对象指代的是否是文件,是文件返回true,反之.
System.out.println(f1.isFile());
//4、public boolean isDirectory():判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之.
System.out.println(f1.isDirectory());
//5.public String getName():获取文件的名称(包含后缀)
System.out.println(f1.getName());
//6.public long length():获取文件的大小,返回字节个数
System.out.println(f1.length());
//7.pubL1 c long lastModified():获取文件的最后修改时间.
long time =f1.lastModified();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(sdf.format(time));
//8.public String getPath():获取创建文件对象时,使用的路径
File f2=new File("D:/迅雷下载/abc.docx");
File f3=new File("src/HH.txt");
System.out.println(f2.getPath());//D:\迅雷下载\abc.docx
System.out.println(f3.getPath());//src\HH.txt
//9.public String getAbsolutePath():获取绝对路径
System.out.println(f2.getAbsolutePath());//D:\迅雷下载\abc.docx
System.out.println(f3.getAbsolutePath());//D:\Code\javasepro\file-io-app\src\HH.txt
}
}
1.3常用方法2:创建文件、删除文件
File类创建文件的功能
File类删除文件的功能
注意:delete方法默认只能删除**文件和空文件夹,**删除后的文件不会进入回收站.
示例:
Test3
package com.itheima.d1_file;
import java.io.File;
import java.io.IOException;
//目标:掌握File创建和删除文件相关的方法
public class Test3 {
public static void main(String[] args) throws IOException {
//1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之,
File f1 =new File("D:/迅雷下载/itheima.txt");
System.out.println(f1.createNewFile());
//2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
File f2=new File("D:/迅雷下载/abv");
System.out.println(f2.mkdir());
//3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
File f3=new File("D:/迅雷下载/dsasd/af/ads/ds");
System.out.println(f3.mkdirs());
//4、public boolean delete():删除文件,或者空文件夹,注意:不能删除非空文件夹.
System.out.println(f1.delete());
System.out.println(f2.delete());
//非空文件夹
File f4=new File("D:/迅雷下载/dsasd");
System.out.println(f4.delete());
}
}
1.4常用方法3:遍历文件夹
File类提供的遍历文件夹的功能
使用listFiles方法时的注意事项:
- 当主调是文件,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
- 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
示例
Test4
package com.itheima.d1_file;
import java.io.File;
//目标:掌握FiLe提供的遍历文件夹的方法,
public class Test4 {
public static void main(String[] args) {
//1、public String[] List():获取当前目录下所有的一级文件名称"到一个字符申数组中去返回.
File f1=new File("D:\\迅雷下载");
String[]names= f1.list();
for (String name : names) {
System.out.println(name);
}
//2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
File[] files=f1.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath());
}
//主调是文件
File f=new File("D:\\迅雷下载\\abc.docx");
File[] files1=f.listFiles();
System.out.println(files1);//null
}
}
2.前置知识:方法递归
2.1认识递归的形式
什么是方法递归?
- 递归是一中算法,在程序设计语言中广泛应用.
- 从形式上说:方法调用自身的形式称为方法递归(recursion).
递归的形式 - 直接递归:方法自己调用自己.
- 间接递归:方法调用其他方法,其他方法又回调方法自己.
使用方法递归时需要注意的问题:
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误.
示例:
Test1
package com.itheima.d2_recursion;
//目标:认识一下递归的形式.
public class Test1{
public static void main(String[]args){
test1();
}
//直接方法递归
public static void test1() {
System.out.println("----test1---");
test1();//直接方法递归
}
//间接方法递归
public static void test2(){
System.out.println("---test2---");
test3();
}
public static void test3(){
test2();//间接递归
}
}
2.2应用、执行流程、算法思想
案例
终止后逐层返回
递归结束
递归算法三要素:
示例:
Test2
package com.itheima.d2_recursion;
//目标:掌握通归的应用,执行流程和算法思想
public class Test2 {
public static void main(String[] args) {
System.out.println("5的阶乘是:" + f(5));
}
public static int f(int n) {
//终结点
if (n == 1) {
return 1;
} else{
return f(n - 1) * n;
}
}
}
2.3其他应用:文件搜索
案例:文件搜索
**需求:**从D:盘中,搜索"Apifox.exe"这个文件,找到后直接输出其位置.
分析:
1.先找出D:盘下的所有一级文件对象
2. 遍历全部一级文件对象,判断是否是文件
3. 如果是文件,判断是否是自己想要的
4. 如果是文件夹,需要继续进入到该文件夹,重复上述过程
拓展:删除文件夹
**需求:**删除非空文件夹
分析:
- File默认不可以删除非空文件夹
- 我们需要遍历文件夹,先删除里面的内容,再删除自己.
示例:
Test3
package com.itheima.d2_recursion;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
//目标:掌握文件搜索的实现
public class Test3 {
public static void main(String[] args) throws IOException {
searchFile(new File("D:/"),"Apifox.exe");
//找到了:D:\download\Apifox\Apifox.exe
}
/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir,String fileName) throws IOException {
//1.把非法的情况都拦截住
if(dir==null||!dir.exists()||dir.isFile()){
return;//代表无法搜索
}
//2.dir不是null,存在,一定是目录对象
//获取当前目录下的全部一级文件对象
File[] files=dir.listFiles();
//3.判断当前目录下是否存在一级文件对象,以及是否可以拿到文件对象
if(files!=null&&files.length>=0){
//4.遍历全部一级文件对象
for (File f : files) {
//5、判断文件是否是文件,还是文件夹
if(f.isFile()){
//是文件,判断这个文件名是否是我们所要找的
if(f.getName().equals(fileName)){
System.out.println("找到了:"+f.getAbsolutePath());
//启动
Runtime runtime=Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
}
}else {
//是文件夹,继续重夏这个过程(递归),深度优先
searchFile(f,fileName);
}
}
}
}
}
Test4
package com.itheima.d2_recursion;
import java.io.File;
import java.nio.file.Files;
//目标:删除非空文件夹.独立功能独立成方法.
public class Test4 {
public static void main(String[] args) {
File dir = new File("D:\\迅雷下载\\dsasd");
delectDir(dir);
}
public static void delectDir(File dir){
if(dir==null||!dir.exists()){
return;
}
if(dir.isFile()){
dir.delete();
return;
}
//1.dir存在且是文件夹,拿里面的一级文件对象
File[] files =dir.listFiles();
if(files==null){
return;//没权限
}
//2、这是一个有内容的文件夹,干掉里面的内容,再干掉自己.
for (File file : files) {
if(file.isFile()){
file.delete();
}else {
delectDir(file);
}
}
dir.delete();
}
}
3.前置知识:字符集
3.1常见字符集介绍
标准ASCII字符集
- ASCII(American Standard Code for Information Interchange):美国信息交换标准代码,包括了英文、符号等.
- 标准ASCI使用1个字节存储一个字符,首位是0,总共可表示128个字符,对美国佬来说完全够用.
GBK(汉字内码扩展规范,国标)
- 汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储.
- 注意:GBK兼容了ASCII字符集.
- GBK规定:汉字的第一个字节的第一位须是1
Unicode字符集(统一码,也叫万国码)
- Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集.
UTF-8
- 是Uicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节
- 英文字符、数字等只占1个字节(兼容标准ASC川编码),汉字字符占用3个字节.
注意:技术人员在开发时都应该使用UTF-8编码!
本节要点
- ASCI字符集:只有英文、数字、符号等,占1个字节.
- GBK字符集:汉字占2个字节,英文、数字占1个字节.
- UTF-8字符集:汉字占3个字节,英文、数字占1个字节.
- 注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
- 注意2:英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码.
3.2字符集的编码、解码操作
Java代码完成对字符的编码
Java代码完成对字符的解码
示例:
Test
package com.itheima.d3_charset;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
//目标:掌握如何使用Java代码完成对字符的编码和解码
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
//1.编码
String data="a我b";
byte[] bytes=data.getBytes();//平台默认,此处为UTF-8
System.out.println(Arrays.toString(bytes));
//按照指定字符集进行编码
byte[] bytes1=data.getBytes("GBK");
System.out.println(Arrays.toString(bytes1));
//2.解码
String s1=new String(bytes);
System.out.println(s1);//a我b
String s2=new String(bytes1);
System.out.println(s2);//a��b
String s3=new String(bytes1,"GBK");
System.out.println(s3);//a我b
}
}
4.IO流
4.1认识IO流
IO流概述
IO流应用场景
怎么学IO流
1、先搞清楚IO流的分类、体系.
2、再挨个学习每个IO流的作用、用法.
IO流分类
总结流的四大类:
- 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流.
- 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流.
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流
IO流的体系
直接继承的才有箭头
4.2 IO流-字节流
4.2.1文件字节输入流:每次读取一个字节
FilelnputStream(文件字节输入流)
- 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去.
注意事项
-使用FilelnputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码.
示例:
FileInputStreamTest1
package com.itheima.d4_byte_stream;
import java.io.*;
//目标:掌握文件字节输入流,每次读取一个字节.
public class FileInputStreamTest1 {
public static void main(String[] args) throws IOException {
//1.创建文件字节输入流管道,与源文件接通.
//InputStream is = new FileInputStream(new File("src/01.txt"));
//简化写法:推荐使用
InputStream is = new FileInputStream("src/01.txt");
//2、开始读取文件的字节数据.
//public int read():每次读取1个字节返回,如果没有数据了,返回-1.
/* int b1 =is.read();
System.out.println((char)b1);//a
int b2=is.read();
System.out.println((char)b2);//b
int b3=is.read();
System.out.println(b3);//-1
*/
//3、使用循环改造上述代码
int b;//用于记住读取的字节.
while((b=is.read())!=-1){
System.out.print((char)b);//ab
}
//读取数据的性能很差!访问硬盘次数太多
//读取汉字输出会乱码!!无法避免的!!
//流使用后必须关闭
is.close();
}
}
4.2.2文件字节输入流:每次读取多个字节
注意事项
- 使用FilelnputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码.
示例:
FileInputStreamTest2
package com.itheima.d4_byte_stream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
//目标:掌握使用FileInputStream每次读取多个字节.
public class FileInputStreamTest2 {
public static void main(String[] args) throws IOException {
//1、创建一个字节输入流对象代表字节输入流管道与源文件接通.
InputStream is =new FileInputStream("src/02.txt");
//2、开始读取文件中的字节数据:每次读取多个字节
//public int read(byte b[])throws IOException
//每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1
/* byte[]buffer =new byte[3];
int len =is.read(buffer);
String rs=new String(buffer);
System.out.println(rs);//abc
System.out.println("当次读取的字节数量:"+len);//当次读取的字节数量:3
//buffer [66c],第二次读不满3个,c留在里面
*//* int len2 =is.read(buffer);
String rs2 =new String(buffer);
System.out.println(rs2);//66c
System.out.println("当次读取的字节数量:"+len2);//当次读取的字节数量:2
*//*
int len2 =is.read(buffer);
//注意:读取多少,例出多少.
String rs2 =new String(buffer,0,len2);
System.out.println(rs2);//66
System.out.println("当次读取的字节数量:"+len2);//当次读取的字节数量:2
int len3 = is.read(buffer);
System.out.println(len3);//-1
*/
//3.使用循环改造
byte[]buffer= new byte[3];
int len;//记住每次读取了多少个字节.
while ((len =is.read(buffer))!=-1) {
//(注意:读取多少,例出多少.
String rs = new String(buffer, 0, len);
System.out.println(rs);
}
//性能得到明显提升
//读取汉字还是会出现乱码
is.close();//关闭流
}
}
4.2.3文件字节输入流:一次读取完全部字节
- 方式一:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节.
- 方式二:Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回.
示例:
FileInputStreamTest3
package com.itheima.d4_byte_stream;
import java.io.*;
//目标:使用文件字节输入流一次读取完文件的全部字节.
public class FileInputStreamTest3 {
public static void main(String[] args) throws IOException {
//1、一次性读取完文件的全部字节到一个字节数组中去.
//创建一个字节输入流管道与源文件接通
InputStream is =new FileInputStream("src/03.txt");
/*
//2、准备一个字节数组,大小与文件的大小正好一样大,
File f = new File("src/03.txt");
long size = f.length();
byte[]buffer = new byte[(int)size];//byte最大int,再大装不下了
int len =is.read(buffer);
System.out.println(new String(buffer));
System.out.println(size);
System.out.println(len);
*/
byte[]buffer =is.readAllBytes();
System.out.println(new String(buffer));
}
}
4.2.4文件字节输出流:写字节出去
FileOutputStream(文件字节输出流)
- 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去.
示例:
FileOutputStreamTest4
package com.itheima.d4_byte_stream;
import java.io.FileOutputStream;
import java.io.OutputStream;
//目标:掌握文件字节输出流FileOutputstream的使用.
public class FileOutputStreamTest4 {
public static void main(String[] args) throws Exception {
//1、创建一个字节输出流管道与目标文件接通,没文件则创建文件
//OutputStream os =new FileOutputStream("src/04out.txt");//覆盖
OutputStream os =new FileOutputStream("src/04out.txt",true);//追加
//2、开始写字节数据出去了
os.write(97);//97就是一个字节,代表a
os.write('b');//'b'也是一个字节
//os.write('磊');//[ooo]默认只能写出去一个字节
byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);
os.write(bytes,0,15);
//换行符
os.write("\r\n".getBytes());
os.close();//关闭流
}
}
4.2.5案例:文件复制
文件复制
字节流非常适合故一t切文件的复制操作
- 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
示例:
CopyTest5
package com.itheima.d4_byte_stream;
import java.io.*;
//目标:使用字节流完成对文件的夏制操作.
public class CopyTest5 {
public static void main(String[] args) throws Exception {
//需求:复制照片.
//1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:\\360downloads\\test5.png");
//2、创建一个字节输出流管道与目标文件接通.
OutputStream os = new FileOutputStream("D:\\迅雷下载\\test5.png");
//3、创建一个字节数组,负责转移字节数据.
byte[] buffer = new byte[1024];//1KB.
//4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
int len;//记住每次读取了多少个字节.
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}os.close();
is.close();
System.out.println("复制完成!!");
}
}
4.3 释放资源的方式
try-catch-finally
- finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止.
- 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法).
JDK7开始提供了更简单的资源释放方案:try-with-resource
MyConnection
package com.itheima.d5_resource;
public class MyConnection implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("释放了与某个硬件资源的链接");
}
}
Test1
package com.itheima.d5_resource;
//目标:认识try-catch-finally
public class Test1 {
public static void main(String[] args) {
try {
System.out.println(10 / 0);//异常也得执行finally
//return;return也得在finally之后执行
//唯一例外:System.exit(0)虚拟机挂了,finally也没用
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("===finally执行了一次===");
}
System.out.println(chu(10,2));//111
}
public static int chu(int a, int b) {
try {
return a / b;
} catch (Exception e) {
e.printStackTrace();
return -1;//代表的是出现异常
} finally {
//千万不要在Finally中返回数据!
return 111;
}
}
}
Test2
package com.itheima.d5_resource;
import java.io.*;
//目标:掌握释放资源的方式.
public class Test2 {
public static void main(String[] args) {
InputStream is=null;
OutputStream os=null;
try {
//1.创建一个字节输入流管道与源文件接通
is = new FileInputStream("src\\03.txt");
//2、创建一个字节输出流管道与目标文件接通.
os = new FileOutputStream("src\\03copy.txt");
//3、创建一个字节数组,负责转移字节数据.
byte[] buffer = new byte[1024];//1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
int len;//记住每次读取了多少个字节.
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
}catch (IOException e) {
e.printStackTrace();
}finally{
//释放资源的操作
try {
if(os!=null)os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(os!=null)is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Test3
package com.itheima.d5_resource;
import java.io.*;
//try-with-resource
public class Test3 {
public static void main(String[] args) {
try(
//1.创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("src\\03.txt");
//2、创建一个字节输出流管道与目标文件接通.
OutputStream os = new FileOutputStream("src\\03copy.txt");
//注意:这里只能放置资源对象.(流对象)
//什么是资源呢?资源都是会实现AutoCloseable接口,资源都会有一个close方法,并且资源放到这里后
//用完之后,会被自动调用其close方法完成资源的释放操作
MyConnection conn=new MyConnection();
) {
//3、创建一个字节数组,负责转移字节数据.
byte[] buffer = new byte[1024];//1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
int len;//记住每次读取了多少个字节.
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
}catch (Exception e) {
e.printStackTrace();
}
}
}
4.4IO流-字符流
4.4.1文件字符输入流-读字符数据进来
FileReader(文件字符输入流)
- 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去.
示例:
FileReaderTest1
package com.itheima.d1_char_stream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
//目标:学握文件字符输入流每次读取一个字符.
public class FileReaderTest1 {
public static void main(String[] args) {
//一次读一个字符
try(
//1、创建一个文件字符输入流管道与源文件接通
Reader fr =new FileReader("src\\01.txt");
) {
//2.读取文本文件内容
int c;//记住每次读取的字符编号
while((c=fr.read())!=-1){
System.out.print((char) c);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("---------------------");
//一次读多个字符
try(
Reader fr =new FileReader("src\\01.txt");
) {
char[] buffer =new char[3];
int len;
while((len=fr.read(buffer))!=-1){
System.out.print(new String(buffer, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.4.2文件字符输出流-写字符数据出去
FileWriter(文件字符输出流)
- 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去.
字符输出流使用时的注意事项 - 字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
字节流、字符流的使用场景小结: - 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出.
- 字符流适合做文本文件的操作(读,写).
示例:
FileReaderTest2
package com.itheima.d1_char_stream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
//目标:掌握文件字符输出流:写字符数据出去
public class FileWriterTest2 {
public static void main(String[] args) {
//0、创建一个文件字符输出流管道与目标文件接通.
try (
Writer fw =new FileWriter("src/02out.txt",true);//追加
){
//1、public void write(intc):写一个字符出去
fw.write('a');
fw.write(97);
fw.write('我');//写一个字符出去
fw.write("\r\n");
//2、public void write(String c)写一个字符串出去
fw.write("我爱你中国abc");
fw.write("\r\n");
//3、public void write(String c,int pos,int len):写字符串的一部分出去
fw.write("我爱你中国abc",0,5);
fw.write("\r\n");
//4、public void write(char[]buffer):写一个字符数组出去
char[]buffer={'黑','马','a','b','c'};
fw.write(buffer);
fw.write("\r\n");
//5、public void write(char[]buffer,int pos,int len):写字符数组的一部分出去
fw.write(buffer,1,2);//马a
fw.write("\r\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
FileReaderTest3
package com.itheima.d1_char_stream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
//目标:搞清楚字符输出流使用时的注意事项.
public class FileWriterTest3 {
public static void main(String[] args) throws Exception {
Writer fw =new FileWriter("src/03out.txt");
//写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');
fw.write('d');
fw.write("\r\n");
fw.write("我爱你中国");
fw.write("\r\n");
fw.write("我爱你中国");
//文件被创建,但是空,因为只写到缓冲区,缓冲区装满会自动刷新
/* fw.flush();//刷新流
fw.write("张三");
fw.flush();
*/
fw.close();//关闭流,关闭流包含刷新操作!
}
}
4.5 IO流-缓冲流
4.5.1字节缓冲流
字节缓冲流的作用
- 提高字节流读写数据的性能
- 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池.
示例
BufferedInputStreamTest1
package com.itheima.d2_buffered_stream;
import java.io.*;
//目标:掌握字节缓冲流的作用.
public class BufferedInputStreamTest1 {
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("src/01.txt");
//1、定义1个字节缓冲输入流包装原始的字节输入流
InputStream bis = new BufferedInputStream(is,8192*2);//可自定义缓冲池大小
OutputStream os = new FileOutputStream("src/01_bak.txt");
//2、定义1个字节缓冲输出流包装原始的字节输出流
OutputStream bos = new BufferedOutputStream(os);
) {
byte[] buffer =new byte[1024];
int len;
while ((len =bis.read(buffer))!=-1){
bos.write(buffer,0, len);
}
System.out.println("复制完成!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.5.2字符缓冲流
BufferedReader(字符缓冲输入流)
- 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能.
BufferedWriter(字符缓冲输出流) - 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能.
示例
BufferedReaderTest2
package com.itheima.d2_buffered_stream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
//目标:掌握字符缓冲输入流的用法.
public class BufferedReaderTest2 {
public static void main(String[] args) {
try (
Reader fr = new FileReader("src\\04.txt");
//创建一个字符缓冲输入流包装原始的子符输入流
BufferedReader br = new BufferedReader(fr);
) {
/* char[] buffer =new char[3];
int len;
while ((len = br.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
*/
String line;//记住每次读取的一行数据
while ((line=br.readLine())!=null){
System.out.println(line);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
BufferedWriterTest3
package com.itheima.d2_buffered_stream;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
//目标:掌握字符缓冲输出流的用法.
public class BufferedWriterTest3 {
public static void main(String[] args) {
try (
Writer fw =new FileWriter("src/05out.txt",true);//追加
//创建一个字符缓冲输出流管道包装原始的字符输出流
BufferedWriter bw =new BufferedWriter(fw);
){
bw.write('a');
bw.write(97);
bw.write('磊');
bw.newLine();
bw.write("我爱你中国abc");
bw.newLine();
}catch (Exception e){
e.printStackTrace();
}
}
}
4.5.3案例
示例
Demo5
package com.itheima.d2_buffered_stream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo5 {
public static void main(String[] args) {
try (
2、创建缓冲字符输入流管道与源文件接通
BufferedReader br =new BufferedReader(new FileReader("src/csb.txt"));
BufferedWriter bw =new BufferedWriter(new FileWriter("src/newcsb.txt"));
){
//目标:恢复出师表的顺序到新文件中.
//1、定义一个ArrayList集合存储每段内容
List<String> data =new ArrayList<>();
//3、按照行读取每段,存入到集合中去
String line;
while ((line =br.readLine())!=null) {
data.add(line);
}
//4、L1st集合中的每段文章进行排序.
Collections.sort(data);
System.out.println(data);
//5、遍历list集合的每段内容,依次写出去到新文件中.
for (String ln :data){
bw.write(ln);
bw.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.5.3原始流、缓冲流的性能分析[重点]
示例
TimeTest5
package com.itheima.d2_buffered_stream;
import java.io.*;
public class TimeTest5 {
//复制的视频路径
private static final String SRC_FILE="D:\\迅雷下载\\abc.docx";
//复制到娜个目的地
private static final String DEST_FILE ="D:\\迅雷下载\\";
public static void main(String[] args) {
copy01();//低级字节流一个一个字节复制耗时:13s
copy02();//低级字节流使用字节数组复制耗时:0.016s
copy03();//缓冲流一个一个字节复制耗时:0.029s
copy04();//缓冲流使用字节数组复制耗时:0.004s
}
private static void copy01() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
OutputStream os = new FileOutputStream(DEST_FILE + "1.avi");
) {
int b;
while ((b = is.read()) != -1) {
os.write(b);
}
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("低级字节流一个一个字节复制耗时:" + (endTime - startTime) / 1000 + "s");
}
private static void copy02() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
OutputStream os = new FileOutputStream(DEST_FILE + "2.avi");
) {
byte[] buffer = new byte[1024];//1KB
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("低级字节流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
private static void copy03() {
long startTime = System.currentTimeMillis();
try (
InputStream is = new FileInputStream(SRC_FILE);
BufferedInputStream bis=new BufferedInputStream(is);
OutputStream os = new FileOutputStream(DEST_FILE + "3.avi");
BufferedOutputStream bos=new BufferedOutputStream(os);
) {
int b;
while ((b= bis.read()) != -1) {
bos.write(b);
}
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
private static void copy04() {
Long startTime = System.currentTimeMillis();
try ( InputStream is = new FileInputStream(SRC_FILE);
BufferedInputStream bis=new BufferedInputStream(is);
OutputStream os = new FileOutputStream(DEST_FILE + "4.avi");
BufferedOutputStream bos=new BufferedOutputStream(os);
) {
byte[] buffer = new byte[1024];//1KB
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
}
}
4.6 IO流-转换流
4.6.1引出问题:不同编码读取时会乱码
不同编码读取出现乱码的问题
- 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
- 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
4.6.2字符输入转换流
InputStreamReader(字符输入转换流)
- 解决不同编码时,字符流读取文本内容乱码的问题.
- 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了.
示例
Test1
package com.itheima.d3_transform_stream;
import java.io.*;
//目标:掌握字符输入转换流的作用
public class Test1 {
public static void main(String[] args) {
try ( //1.得到文件的原始字节流(GBK)
InputStream is=new FileInputStream("src/06.txt");
//2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
Reader isr = new InputStreamReader(is,"GBK");
//3、把字符输入流包装成缓冲字符输入流
BufferedReader br =new BufferedReader(isr);
){
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.6.3字符输出转换流
OutputStreamWriter字符输出转换流
- 作用:可以控制写出去的字符使用什么字符集编码.
- 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了.
示例
Test2
package com.itheima.d3_transform_stream;
import java.io.*;
//目标:掌握字符输出转换流的使用.
public class Test2{
public static void main(String[]args){
//指定写出去的字符编码.
try(
//1、创建一个文件字节输出流
OutputStream os =new FileOutputStream("src/07out.txt");
//2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流.
Writer osw =new OutputStreamWriter(os,"GBK");
//3、把字符输出流包装成缓冲字符输出流
BufferedWriter bw =new BufferedWriter(osw);
) {
bw.write("我是中国人abc");
bw.write("我爱你中国123");
}catch (Exception e){
e.printStackTrace();
}
}
}
4.7 IO流-打印流
PrintStream/PrintWriter(打印流)
- 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去.
PrintStream提供的打印数据的方案
PrintWriter提供的打印数据的方案
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法.
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去.
打印流的一种应用:输出语句的重定向
- 可以把输出语句的打印位置改到某个文件中去.
示例:
Test1
package com.itheima.d4_printstream;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.nio.charset.Charset;
//目标:掌握打印流:PrintStream的用法,
public class Test1 {
public static void main(String[] args) {
try (
//1、创建一个打印流管道
PrintStream ps =
new PrintStream("src/08.txt", Charset.forName("UTF-8"));
){
ps.println(97);
ps.println('a');
ps.println("我爱你中国abc");
ps.println(true);
ps.println(99.5);
ps.write(97);//'a' 写数据和打印数据的区别
} catch (Exception e) {
e.printStackTrace();
}
}
}
Test2
package com.itheima.d4_printstream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
//目标:掌握打印流:PrintWrite的用法,
public class Test2 {
public static void main(String[] args) {
try (
//1、创建一个打印流管道,
PrintWriter ps =
new PrintWriter("src/09.txt", Charset.forName("UTF-8"));
//不能直接追加,得换成低级流
//PrintWriter ps = new PrintWriter(new FileOutputStream("src/09.txt", true));
){
ps.println(97);
ps.println('a');
ps.println("我爱你中国abc");
ps.println(true);
ps.println(99.5);
ps.write(97);//'a' PrintWrite和PrintStream写上面有区别,其他方面几乎没区别
} catch (Exception e) {
e.printStackTrace();
}
}
}
Test3
package com.itheima.d4_printstream;
import java.io.FileNotFoundException;
import java.io.PrintStream;
//目标:了解下输出语句的重定向.
public class Test3 {
public static void main(String[] args) {
System.out.println("老攘伏枥");
System.out.println("志在千里");
try(PrintStream ps= new PrintStream("src/09.txt");) {
//把系统默认的打印流对象改成自己设置的打印流
System.setOut(ps);
System.out.println("烈士暮年");
System.out.println("壮心不已");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.8IO流-数据流
DataOutputStream(数据输出流)
- 允许把数据和其类型一并写出去.
DatalnputStream(数据输入流) - 用于读取数据输出流写出去的数据.
示例
Test1
package com.itheima.d5_data_stream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
//目标:数据输出流.
public class Test1 {
public static void main(String[] args) {
try (
//1、创建一个数据输出流包装低级的字节输出流
DataOutputStream dos =
new DataOutputStream(new FileOutputStream("src/10out.txt"));
) {
dos.writeInt(97);
dos.writeDouble(99.5);
dos.writeBoolean(true);
dos.writeUTF ( "黑马程序员666!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Test2
package com.itheima.d5_data_stream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//目标:使用数据输入流读取特定类型的数据.
public class Test2 {
public static void main(String[] args) {
try ( DataInputStream dis=
new DataInputStream(new FileInputStream("src/10out.txt"));
){
int i=dis.readInt();
System.out.println(i);
double d =dis.readDouble();
System.out.println(d);
boolean b =dis.readBoolean();
System.out.println(b);
String rs =dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.9IO流-序列化流
ObjectOutputStream(对象字节输出流)
- 可以把Java对象进行序列化:把Java对象存入到文件中去.
注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)
ObjectInputStream(对象字节输入流)
- 可以把]ava对象进行反序列化:把存储在文件中的]ava对象读入到内存中来.
如果要一次序列化多个对象,怎么办?
- 用一个ArrayList:集合存储多个对象,然后直接对集合进行序列化即可
注意:ArrayList集合已经实现了序列化接口!
示例
Test1
package com.itheima.d6_object_stream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
//目标:掌握对象字节输出流的使用:序列化对象.
public class Test1 {
public static void main(String[] args) {
try (
//2、创建一个对象字节输出流包装原始的字节输出流.
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("src/11out.txt"));
){
//1.创建一个Java对象
User u=new User("admin","张三",32,"123abc");
//3、序列化对象到文件中去
oos.writeObject(u);
System.out.println("序列化对象成功!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Test2
package com.itheima.d6_object_stream;
import java.io.*;
//目标:掌握对象字节输入流的使用:反序列化对象.
public class Test2 {
public static void main(String[] args) {
try (
//2、创建一个对象字节输入流管道,包装低级的字节输入流与源文件接通
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("src/11out.txt"));
){
//1.创建一个Java对象
User u=(User)ois.readObject();
System.out.println(u);
//User{loginName='admin', userName='张三', age=32, passWord='123abc'}
} catch (Exception e) {
e.printStackTrace();
}
}
}
User
package com.itheima.d6_object_stream;
import java.io.Serializable;
//注意:对象如果需要序列化,必须实现序列化接口,
public class User implements Serializable {
private String loginName;
private String userName;
private int age;
//transient这个成员变量将不参与序列化
private transient String passWord;
public User() {
}
public User(String loginName, String userName, int age, String passWord) {
this.loginName = loginName;
this.userName = userName;
this.age = age;
this.passWord = passWord;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
@Override
public String toString() {
return "User{" +
"loginName='" + loginName + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
", passWord='" + passWord + '\'' +
'}';
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
4.10补充知识:IO框架
什么是框架
- 解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的.
- 好处:在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率
- 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去.
什么是IO框架
- 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等.
Commons-io
- Commons-io是apache开源基金组织提供的一组有关IO操作的小框架,目的是提高IO流的开发效率.
下载框架
点击下载
解压后选择所要的jar包
复制到新建文件夹lib中
右键lib添加为库
示例
Test
package com.itheima.d7_commons_io;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
//目标:使用CommonsIO框架进行IO相关的操作
public class Test {
public static void main(String[] args) throws Exception {
FileUtils.copyFile(new File("src\\01.txt"),new File("src\\a.txt"));
FileUtils.copyDirectory(new File("D:\\迅雷下载\\G-menu"),new File("D:\\迅雷下载\\Ggg"));
FileUtils.deleteDirectory(new File("D:\\迅雷下载\\Ggg"));
/* Java提供的原生的一行代码搞定很多事
Files.copy(Path.of("src\\01.txt"),Path.of("src\\a.txt"));
System.out.println(Files.readString(Path.of("src\\01.txt")));
*/
}
}
十.特殊文件与日志技术
特殊文件
日志技术
- 把程序运行的信息,记录到文件中,方便程序员定位Bug、并了解程序的执行情况等
1.特殊文件:Properties,属性文件
1.1Properties文件的特点与作用
特点
- 都只能是键值对
- 键不能重复
- 文件后缀一般是.properties结尾的
作用
- 存储密码等信息
1.2读取属性文件
Properties
- 是一个Map集合(键值对集合),但是我们一般不会当集合使用.
- 核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容.
使用Properties读取属性文件里的键值对数据
示例:
Test1
package com.itheima.d1_properties;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;
//目标:掌握使用Properties类读取属性文件中的键值对信息,
public class Test1 {
public static void main(String[] args) throws Exception {
//1、创建一个Properties的对象出来(键值对集合,空容器)
Properties properties =new Properties();
System.out.println(properties);
//2、开始加载属性文件中的键值对数据到properties对象中
properties.load(new FileReader("src\\users.properties"));
System.out.println(properties);
//3、根据键取值
System.out.println(properties.getProperty("赵敏"));
System.out.println(properties.getProperty("张无忌"));
//4、遍历全部的健和值.
Set<String> keys= properties.stringPropertyNames();
for (String key: keys){
String value =properties.getProperty(key);
System.out.println(key +"---->"+value);
}
properties.forEach((k,v)->{
System.out.println(k+"---->"+v);
});
}
}
1.3写出到属性文件
使用Properties:把键值对数据写出到属性文件里去
示例:
Test2
package com.itheima.d1_properties;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
//目标:掌握把键值对数据存入到属性文件中去f
public class Test2 {
public static void main(String[] args) throws Exception {
//1、创建Properties对象出来,先用它存储一些键值对数据
Properties properties =new Properties();
properties.setProperty("张三","12345");
properties.setProperty("李四","abcd");
properties.setProperty("王五","123abc");
//2、把properties对象中的键值对数据存入到属性文件中去
//管道作为参数,会自动关
properties.store(new FileWriter("src\\users2.properties")
,"i saved many users");
}
}
Test3
package com.itheima.d1_properties;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;
public class Test3 {
public static void main(String[] args) throws Exception {
//目标:读取属性文件,判断是否存在李方,存在年龄改成18
//1、加载属性文件的键值对到程序中来.
Properties properties = new Properties();//{}
//2、开始加载
properties.load(new FileReader("src\\users.txt"));
//3、判断是否包含李方这个键
if (properties.containsKey("李方")) {
properties.setProperty("李方", "18");
}
//4、把porperties对象的键值对数据从新写出去到属性文件中去."
properties.store(new FileWriter("src\\users.txt"),
"success");
}
}
2. 特殊文件:XML文件
2.1概述
XML(全称EXtensible Markup Language,可扩展标记语言)
- 本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系.
XML的特点
- XML中的"<标签名>"称为一个标签或一个元素,一般是成对出现的.
- XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套.
- XML中只能有一个根标签.
- XML中的标签可以有属性.
- 如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般要写成.xml
XML的创建
- 就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml
IDEA创建XML文件
XML的语法规则
- XML文件的后缀名为:xml,文档声明必须是第一行
- XML中可以定义注释信息:<! --注释内容 -->
- XML中书写"<“、”&"等,可能会出现冲突,导致报错,此时可以用如下特殊字符替代.
- XML中可以写一个叫CDATA的数据区:<![CDATA[...内容...]]>,里面的内容可以随便写.
XML的作用和应用场景
- 本质是一种数据格式,可以存储复杂的数据结构,和数据关系.
- 应用场景:经常用来做为系统的配置文件;或者作为一种特殊的数据结构,在网络中进行传输.
2.2读取XML文件中的数据
解析XML文件
- 使用程序读取XML文件中的数据
- 注意:程序员并不需要自己写原始的IO流代码来解析XML,难度较大!也相当繁琐!
其实,有很多开源的,好用的,解析XML的框架,最知名的是:dom4j(第三方研发的
DOM4J解析XML文件的思想:文档对象模型
Dom4j解析XML-得到Document对象 - SAXReader:Dom4j提供的解析器,可以认为是代表整个Dom4j框架
- Document
Element提供的方法
示例
book.xml
<?xml version="1.0"encoding="UTF-8"?>
<book>
<name>从入门到跑路</name>
<author>dlei</author>r
<price>999.99</price>
</book>
helloworld.xml
<?xml version="1.0" encoding="utf-8" ?>
<!--此为注释:以上抬头声明必须放在第一行,必须有-->
<!-- 根标签只能有一个-->
<users>
<user id="1">
<name>张无忌</name>
<sex>男</sex>
<地址>光明顶</地址>
<password>minmin</password>
<data>3< 2&&5>4 </data>
<data1>
<![CDATA[
3<2&&5>4
]]>
</data1>
</user>
<people>很多人</people>
<user id="2">
<name>敏敏</name>
<sex>女</sex>
<地址>光明顶</地址>
<password>wuji</password>
</user>
</users>
Dom4JTest1
package com.itheima.d2_xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;
//目标:学握使用Dom4j框架解析XML文件.
public class Dom4JTest1 {
public static void main(String[] args) throws Exception {
//1、创建一个Dom4J框架提供的解析器对象
SAXReader saxReader= new SAXReader();
//2.使用saxReader对象把需要解析的XML读成一个Document对象
Document document=saxReader.read("src\\helloworld.xml");
//3、从文档对象中解析XML文件的全部数据了
Element root =document.getRootElement();
System.out.println(root.getName());
//4、获取根元素下的全部一级子元素.
// List<Element> elements = root.elements();
List<Element> elements = root.elements("user");
for (Element element : elements) {
System.out.println(element.getName());
}
//5、获取当前元素下的某个子元素.
Element people = root.element("people");
System.out.println(people.getText());
//如果下面有很多子元泰UseP,默认获取第一个.
Element user = root.element("user");
System.out.println(user.elementText("name"));
//6、获取元素的属性信息呢?
System.out.println(user.attributeValue("id"));
Attribute id=user.attribute("id");
System.out.println(id.getName());
System.out.println(id.getValue());
List<Attribute> attributes=user.attributes();
for (Attribute attribute : attributes) {
System.out.println(attribute.getName()+"="+attribute.getValue());
}
//7、如何获取全部的文本内容:获取当前元素下的子元素文本值
System.out.println(user.elementText("name"));
System.out.println(user.elementText("地址"));
System.out.println(user.elementText("password"));
Element data = user.element("data");
System.out.println(data.getText());
System.out.println(data.getTextTrim());//去除前后空格
}
}
Dom4JTest2
package com.itheima.d2_xml;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/*
目标:如何使用程序把数据写出到XML文件中去.
<?xml version="1.0"encoding="UTF-8"?>
<booK>
<name>从入门到跑路</name>
<author>dlei</author>
<price>999.9</price>
</book>
*/
public class Dom4JTest2 {
public static void main(String[] args) {
//1、使用一个StringBuilder对象来拼接XML格式的数据.
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"encoding=\"UTF-8\"?>\r\n");
sb.append("<book>\r\n");
sb.append("\t<name>").append("从入门到跑路").append("</name>\r\n");
sb.append("\t<author>").append("dlei").append("</author>r\n");
sb.append("\t<price>").append(999.99).append("</price>\r\n");
sb.append("</book>");
try(
BufferedWriter bw =new BufferedWriter(new FileWriter("src/book.xml"))
) {
bw.write(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3补充知识:约束XML文件的编写[了解]
什么是约束XML文件的书写?
- 就是限制XML文件只能按照某种格式进行书写.
约束文档
- 专门用来限制xl书写格式的文档,比如:限制标签、属性应该怎么写.
约束文档的分类
- DTD文档
- Schema文档
3.日志技术
3.1概述
日志技术
- 可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中).
- 可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改.
3.2日志技术的体系、Logback日志框架的概述
日志技术的体系结构
- 日志框架:牛人或者第三方公司已经做好的实现代码,后来者直接可以拿去使用.
- 日志接口:设计日志框架的一套标准,日志框架需要实现这些接口.
- 注意1:因为对Commons Logging接口不满意,有人就搞了SLF4;因为对Log4j的性能不满意,有人就搞了Logback…
- 注意2:Logback是基于slf4j的日志规范实现的框架.
Logback日志框架有以下几个模块:
想使用Logback日志框架,至少需要在项目中整合如下三个模块:
- slf4j-api:日志接口
- logback-core
- logback-classic
3.3Logback快速入门
核心配置文件logback.xml
- 对Logback日志框架进行控制的.
日志的输出位置、输出格式的设置
- 通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中
开启日志(ALL),取消日志(OFF)
3.4Logback设置日志级别
什么是日志级别
-
日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下**(优先级依次升高)**:
为什么要学习日志级别
-
只有日志的级别是大于或等于核心配置文件配置的日志级别,才会被记录,否则不记录.
示例
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>
<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>D:/迅雷下载/data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>D:/迅雷下载/log-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR | ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="info">
<!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
<appender-ref ref = "CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>
LogBackTest
package com.itheima.d3_log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//目标:掌握LogBack日志框架的使用,
public class LogBackTest {
//创建一个Logger日志对象
public static final Logger LOGGER= LoggerFactory.getLogger("LogBackTest");
public static void main(String[] args) {
try {
LOGGER.info("chu法方法开始执行~~~");
chu(10, 0);
LOGGER.info("chu法方法执行成功~~~");
} catch (Exception e) {
LOGGER.error("chu法方法执行失败了,出现了bug~~~");
}
}
public static void chu(int a, int b) {
LOGGER.debug(("参数a")+a);
LOGGER.debug(("参数b")+b);
int c = a / b;
LOGGER.info("结果是"+c);
}
}
十一.多线程
1.概述
什么是线程?
- 线程(Thread)是一个程序内部的一条执行流程.
- 程序中如果只有一条执行流程,那这个程序就是单线程的程序.
多线程是什么?
- 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行).
如何在程序中创建出多条线程?
- Java是通过java.lang.Thread类的对象来代表线程的.
1.多线程的创建
1.1方式一:继承Thread类
多线程的创建方式一:继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run(O方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的
方式一优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展.
多线程的注意事项
- 启动线程必须是调用start方法,不是调用run方法.
- 不要把主线程任务放在启动子线程之前.
示例
MyThread
package com.itheima.d1_create_thread;
//1、让子类继承Thread线程类
public class MyThread extends Thread{
//2.必须重写Thread类的run方法
@Override
public void run() {
//描述线程的执行任务
for (int i = 0; i <5 ; i++) {
System.out.println("子线程MyThread线程输出"+i);
}
}
}
ThreadTest1
package com.itheima.d1_create_thread;
//目标:掌握线程的创建方式一:继承Thread类
public class ThreadTest1 {
//main方法是由一条默认的主线程负责执行.
public static void main(String[] args) {
//3、创建MyThread.线程类的对象代表一个线程
Thread t =new MyThread();
//4、启动线程(自动执行run方法的)
t.start();//main线程t线程
for (int i = 0; i <5 ; i++) {
System.out.println("主线程输出"+i);
}
}
}
1.2方式二:实现Runnable接口
多线程的创建方式二:实现Runnable接口
- 定义一个线程任务类MyRunnable:实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理.
- 调用线程对象的start(0方法启动线程
方式二的优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强.
- 缺点:需要多一个Runnable对象.
线程创建方式二的匿名内部类写法
- 可以创建Runnable的匿名内部类对象.
- 再交给Thread线程对象.
- 再调用线程对象的start()启动线程.
示例
MyRunnable
package com.itheima.d1_create_thread;
//1、定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable {
//2.重写runnable的run方法
@Override
public void run() {
//线程要执行的任务,
for (int i = 1;i <5;i++){
System.out.println("子线程输出===》"+i);
}
}
}
ThreadTest2
package com.itheima.d1_create_thread;
//目标:掌握多线程的创建方式二:实现Runnable接口,
public class ThreadTest2 {
public static void main(String[] args) {
//3、创建任务对象.
Runnable target = new MyRunnable();
//4、把任务对象交给一个线程对象处理.
//public Thread(Runnable target)
new Thread(target).start();
for(int i=1;i<=5;i++)
{
System.out.println("主线程main输出===》" + i);
}
}
}
ThreadTest2_2
package com.itheima.d1_create_thread;
//目标:掌握多线程创建方式二的匿名内部类写法.
public class ThreadTest2_2 {
public static void main(String[] args) {
//1、直接创建Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("子线程1输出" + i);
}
}
};
new Thread(target).start();
//简化形式1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("子线程2输出" + i);
}
}
}).start();
//简化形式2
new Thread(()->{
for (int i = 1; i < 5; i++) {
System.out.println("子线程3输出" + i);
}
}).start();
for (int i = 1; i < 5; i++) {
System.out.println("主线输出" + i);
}
}
}
1.3方式三:实现Callable接口
前两种线程创建方式都存在的一个问题
-假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果.
怎么解决这个问题?
- JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式).
- 这种方式最大的优点:可以返回线程执行完毕后的结果.
多线程的第三种创建方式:利用Callable接▣、FutureTask类来实现.
- 创建任务对象
- 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据.
- 把Callable类型的对象封装成FutureTask(线程任务对象).
- 把线程任务对象交给Thread对象.
- 调用Thread对象的start方法启动线程.
- 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果.
FutureTask的API
线程创建方式三的优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果.
- 缺点:编码复杂一点.
示例
MyCallable
package com.itheima.d1_create_thread;
import java.util.concurrent.Callable;
//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public int getN() {
return n;
}
public MyCallable(int n) {
this.n = n;
}
public void setN(int n) {
this.n = n;
}
//2.重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行返回后的结果
//需求:求1-n的和返回,
int sum= 0;
for (int i=1;i<=n;i++) {
sum += i;
}
return "线程求出了1-"+n+"的和是"+sum;
}
}
ThreadTest3
package com.itheima.d1_create_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//目标:掌握线程的创建方式三:实现Callable接口.
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建一个Callable的对象
Callable<String> call = new MyCallable(100);
//4、把Callable的对象时装成个FutureTask对象(任务对象
//未来任务对象的作用?
//(1)是一个任务对象,实现了Runnable
//(2)可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果.
FutureTask<String> f1 =new FutureTask<>(call);
//5、把任务对象交给一个Thread对象
new Thread(f1).start();
//6、获取线程执行完毕后返回的结果
//注意:如果执行到这儿,假如上面的线程还没有执行完华
//这里的代码会暂停,等待上面线程执行完毕后才会获取结果.
String rs= f1.get();
System.out.println(rs);
}
}
2.Thread的常用方法
Thread提供了很多与线程操作相关的方法
示例
MyThread
package com.itheima.d2_thread_api;
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run(){
//哪个线程执行它,它就会得到哪个线程对象.
Thread t = Thread.currentThread();
for (int i= 1;i<=3;i++){
System.out.println(t.getName()+"输出:"+i);
}
}
}
ThreadTest1
package com.itheima.d2_thread_api;
//目标:掌握Thread的常用方法.
public class ThreadTest1 {
public static void main(String[] args) {
Thread t1 = new MyThread("1号线程");
//启动之前取名字
// t1.setName("1号线程");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread("2号线程");
// t1.setName("2号线程");
t2.start();
System.out.println(t2.getName());
//主线程对象的名字
//哪个线程执行它,它就会得到哪个线程对象.
Thread m = Thread.currentThread();
m.setName("最牛的线程");
System.out.println(m.getName());
for (int i = 1; i <= 5; i++) {
System.out.println("main线程输出:" + i);
}
}
}
ThreadTest2
package com.itheima.d2_thread_api;
//目标:掌握sleep方法,join方法的作用.
public class ThreadTest2 {
public static void main(String[] args) throws Exception {
for (int i = 0; i <= 5; i++) {
System.out.println(i);
//休眠5s
if(i==3){
//会让当前执行的线程暂停5秒,再继续执行
Thread.sleep(5000);
}
}
Thread t1 = new MyThread("1号线程");
t1.start();
t1.join();
Thread t2 = new MyThread("2号线程");
t2.start();
t2.join();
Thread t3 = new MyThread("3号线程");
t3.start();
t3.join();
}
}
3.线程安全
3.1什么是线程安全问题
什么是线程安全问题?
- 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题.
取钱的线程安全问题
-场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?
线程安全问题出现的原因?
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
3.2用程序模拟线程安全问题
示例
Account
package com.itheima.d3_thread_safe;
public class Account {
private String cardId;//卡号
private double money;//余额.
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//小明小红同时来
public void drawMoney(double money) {
//先搞清楚是谁来取钱?
String name =Thread.currentThread().getName();
//1、判断余颜是否足够
if(this.money >=money) {
System.out.println(name+"来取钱"+money+"成功");
this.money-=money;
System.out.println(name+"来取钱后,余额剩余:"+this.money);
}
else
System.out.println(name+"来取钱:余额不足~");
}
}
DrawThread
package com.itheima.d3_thread_safe;
public class DrawThread extends Thread{
private Account acc;
@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
public DrawThread(Account acc,String name) {
super(name);
this.acc=acc;
}
}
ThreadTest
package com.itheima.d3_thread_safe;
//目标:模拟线程安全问题.
public class ThreadTest {
public static void main(String[] args) {
//1、创建1个账户对象,代表两个人的共享账户.
Account acc =new Account("ICBC-110",100000);
//2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
}
}
4.线程同步
4.1认识线程同步
线程同步
- 解决线程安全问题的方案.
线程同步的思想
- 让多个线程实现先后依次访问共享资源,这样就解决了安全问题.
线程同步的常见方案
- **加锁:**每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来.
4.2方式一:同步代码块
同步代码块
- **作用:**把访问共享资源的核心代码给上锁,以此保证线程安全.
- **原理:**每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行.
同步锁的注意事项
- 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug.
锁对象随便选择一个唯一的对象好不好呢?
- 不好,会影响其他无关线程的执行.
锁对象的使用规范
- 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象.
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象.
示例
Account
package com.itheima.d4_synchronized_code;
public class Account {
private String cardId;//卡号
private double money;//余额.
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public static void test(){
//静态方法用类名.class作为锁
synchronized (Account.class){
}
}
//小明小红同时来
public void drawMoney(double money) {
//先搞清楚是谁来取钱?
String name =Thread.currentThread().getName();
//1、判断余颜是否足够
//"黑马"这个字符串在计算机中只有一份,所以对于小红小明来说是同一个对象
// synchronized ("黑马") {
//this正好代表共享资源!
synchronized ("this") {
if(this.money >=money) {
System.out.println(name+"来取钱"+money+"成功");
this.money-=money;
System.out.println(name+"来取钱后,余额剩余:"+this.money);
}
else{
System.out.println(name+"来取钱:余额不足~");
}
}
}
}
DrawThread
package com.itheima.d4_synchronized_code;
public class DrawThread extends Thread{
private Account acc;
@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
public DrawThread(Account acc, String name) {
super(name);
this.acc=acc;
}
}
ThreadTest
package com.itheima.d4_synchronized_code;
//目标:模拟线程安全问题.
public class ThreadTest {
public static void main(String[] args) {
//1、创建1个账户对象,代表两个人的共享账户.
Account acc =new Account("ICBC-110",100000);
//2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
}
}
4.3方式二:同步方法
同步方法
- 作用:把访问共享资源的核心方法给上锁,以此保证线程安全.
- 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行.
同步方法底层原理
- 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码.
- 如果方法是实例方法:同步方法默认用this作为的锁对象.
- 如果方法是静态方法:同步方法默认用类名.class作为的锁对象.
是同步代码块好还是同步方法好一点?
- 范围上:同步代码块锁的范围更小,同步方法锁的范围更大.
- 可读性:同步方法更好.
示例
Account
package com.itheima.d5_synchronized_method;
public class Account {
private String cardId;//卡号
private double money;//余额.
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//小明小红同时来
//同步方法,隐含锁
public synchronized void drawMoney(double money) {
//先搞清楚是谁来取钱?
String name =Thread.currentThread().getName();
//1、判断余颜是否足够
if(this.money >=money) {
System.out.println(name+"来取钱"+money+"成功");
this.money-=money;
System.out.println(name+"来取钱后,余额剩余:"+this.money);
}
else
System.out.println(name+"来取钱:余额不足~");
}
}
DrawThread
package com.itheima.d5_synchronized_method;
public class DrawThread extends Thread{
private Account acc;
@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
public DrawThread(Account acc, String name) {
super(name);
this.acc=acc;
}
}
ThreadTest
package com.itheima.d5_synchronized_method;
//目标:模拟线程安全问题.
public class ThreadTest {
public static void main(String[] args) {
//1、创建1个账户对象,代表两个人的共享账户.
Account acc =new Account("ICBC-110",100000);
//2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
}
}
4.4方式三:Lock锁
Lock锁
- Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大.
- Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象.
Lock的常用方法
示例
Account
package com.itheima.d6_synchronized_lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String cardId;//卡号
private double money;//余额.
//创建了一个锁对象(每个实例都有各自的锁)
private final Lock lk = new ReentrantLock();
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//小明小红同时来
public void drawMoney(double money) {
//先搞清楚是谁来取钱?
String name = Thread.currentThread().getName();
lk.lock();
try {
//1、判断余颜是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
} else {
System.out.println(name + "来取钱:余额不足~");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lk.unlock();
}
}
}
DrawThread
package com.itheima.d6_synchronized_lock;
public class DrawThread extends Thread{
private Account acc;
@Override
public void run() {
//取钱
acc.drawMoney(100000);
}
public DrawThread(Account acc, String name) {
super(name);
this.acc=acc;
}
}
ThreadTest
package com.itheima.d6_synchronized_lock;
//目标:模拟线程安全问题.
public class ThreadTest {
public static void main(String[] args) {
//1、创建1个账户对象,代表两个人的共享账户.
Account acc =new Account("ICBC-110",100000);
//2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
new DrawThread(acc,"小明").start();//小明
new DrawThread(acc,"小红").start();//小红
}
}
5.线程通信
什么是线程通信?
-当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺.
线程通信的常见模型(生产者与消费者模型)
- 生产者线程负责生产数据
- 消费者线程负责消费生产者生产的数据.
- 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产
Object类的等待和唤醒方法:
注意
- 上述方法应该使用当前同步锁对象进行调用.
示例
Desk
package com.itheima.d7_thread_communication;
import java.util.ArrayList;
import java.util.List;
public class Desk {
private List<String> list =new ArrayList<>();
//放1个包子的方法
//厨师1 厨师2 厨师3
public synchronized void put() {
try {
String name = Thread.currentThread().getName();
//判断是否有包子,
if(list.size()==0){
list.add(name+"做的肉包子");
System.out.println(name+"做了一个肉包子~~");
Thread.sleep(2000);
//等待自己,唤醒别人
//先唤醒,后等待
this.notifyAll();
this.wait();
}else {
//有包子了,不做了
//唤醒别人,等待自己
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//吃货1 吃货2
public synchronized void get() {
try {
String name = Thread.currentThread().getName();
if(list.size()==1) {
//有包子,吃了
System.out.println(name + "吃了:" + list.get(0));
list.clear();
Thread.sleep(1000);
this.notifyAll();
this.wait();
}else {
//没有包子
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ThreadTest
package com.itheima.d7_thread_communication;
//目标:了解一下线程通信.
public class ThreadTest {
public static void main(String[] args) {
//需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子
//2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃.
Desk desk = new Desk();
//创建3个生产者线程(3个时师)
new Thread(()->{
while (true) {
desk.put();
}
},"厨师1").start();
new Thread(()->{
while (true) {
desk.put();
}
},"厨师2").start();
new Thread(()->{
while (true) {
desk.put();
}
},"厨师3").start();
//创建2个消费者线程(2个吃货)
new Thread(()->{
while (true) {
desk.get();
}
},"吃货1").start();
new Thread(()->{
while (true) {
desk.get();
}
},"吃货2").start();
}
}
6.线程池
6.1认识线程池
什么是线程池?
- 线程池就是一个可以复用线程的技术.
不使用线程池的问题
- 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能.
线程池的工作原理
- 有任务区和线程区,既可以控制线程数量,又可以控制任务数量
6.2如何创建线程池?
谁代表线程池?
- JDK5.0起提供了代表线程池的接口:ExecutorService.
如何得到线程池对象?
- 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象.
- 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象.
ThreadPoolExecutor:构造器
- 参数一:corePoolSize:指定线程池的核心线程的数量.
- 参数二:maximumPoolSize:指定线程池的最大线程数量.心
- 参数三:keepAliveTime:指定临时线程的存活时间.
- 参数四:uit:指定临时线程存活的时间单位(秒、分、时、天)
- 参数五:workQueue:指定线程池的任务队列.
- 数六:threadFactory:指定线程池的线程工厂.
- 参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
线程池的注意事项
- 临时线程什么时候创建?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程.
- 什么时候会开始拒绝新任务?
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务.
6.3线程池处理Runnable任务
ExecutorService的常用方法
新任务拒绝策略
示例
ThreadTest1
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
//目标:掌握线程池的创建.
public class ThreadTest1 {
public static void main(String[] args) {
/*
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
Runnable target =new MyRunnable();
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//到了临时线程的创建时机了
pool.execute(target);
pool.execute(target);
//到了新任务的拒绝时机了!
pool.execute(target);
// pool.shutdown();//等线程池的任务全部执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池!不管任务是否执行完毕!
}
}
MyRunnable
package com.itheima.d8_thread_pool;
public class MyRunnable implements Runnable {
@Override
public void run() {
//任务是干什么的
System.out.println(Thread.currentThread().getName() + "==>666~~");
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6.4线程池处理Callable任务
ExecutorService的常用方法
示例
ThreadTest2
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
//目标:掌握线程池的创建.
public class ThreadTest2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2、使用线程处理Callable任务,
Future<String> f1= pool.submit(new MyCallable(100));
Future<String> f2= pool.submit(new MyCallable(200));
Future<String> f3= pool.submit(new MyCallable(300));
Future<String> f4= pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
MyCallable
package com.itheima.d8_thread_pool;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;
//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public int getN() {
return n;
}
public MyCallable(int n) {
this.n = n;
}
public void setN(int n) {
this.n = n;
}
//2.重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行返回后的结果
//需求:求1-n的和返回,
int sum= 0;
for (int i=1;i<=n;i++) {
sum += i;
}
return Thread.currentThread().getName()+ "线程求出了1-"+n+"的和是"+sum;
}
}
6.5Executors工具类实现线程池
Executors
- 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象.
- 注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象.
Executors使用可能存在的陷阱
- 大型并发系统环境中使用Executors如果不注意可能会出现系统风险.
示例
ThreadPoolTest3
package com.itheima.d8_thread_pool;
import java.util.concurrent.*;
//目标:掌握线程池的创建.
public class ThreadTest1 {
public static void main(String[] args) {
/*
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,8,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
Runnable target =new MyRunnable();
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//到了临时线程的创建时机了
pool.execute(target);
pool.execute(target);
//到了新任务的拒绝时机了!
pool.execute(target);
// pool.shutdown();//等线程池的任务全部执行完毕后,再关闭线程池
// pool.shutdownNow();//立即关闭线程池!不管任务是否执行完毕!
}
}
6.6其它细节知识:并发、并行
进程
- 正在运行的程序(软件)就是一个独立的进程.
- 线程是属于进程的,一个进程中可以同时运行很多个线程.
- 进程中的多个线程其实是并发和并行同时执行的.
并发的含义
- 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发.
并行的理解
- 在同一个时刻上,同时有多个线程在被CPU调度执行.
6.7其它细节知识:线程的生命周期
线程的生命周期
- 也就是线程从生到死的过程中,经历的各种状态及状态转换
- 理解线程这些状态有利于提升并发编程的理解能力.
Java线程的状态
- Java总共定义了6种状态
- 6种状态都定义在Thread类的内部枚举类中.
线程的6种状态互相转换
拓展
MyRunnable1
package com.itheima.d9_tz;
public class MyRunnable1 implements Runnable {
private int count;//记录浏览人次
@Override
public void run() {
//100次
for (int i = 0; i < 100; i++) {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"count =======>" + (++count));
}
}
}
}
MyRunnable2
package com.itheima.d9_tz;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnable2 implements Runnable{
//整数修改的乐观锁:原子类实现的.
private AtomicInteger count =new AtomicInteger();
@Override
public void run() {
//100次
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"count =======>" + count.incrementAndGet());
}
}
}
Test1
package com.itheima.d9_tz;
public class Test1 {
public static void main(String[] args) {
//目标:拓展悲观锁,乐观锁原理.
//悲观锁:一上来就加锁,没有安全感.每次只能一个线程进入访问完毕后,再解锁.线程安全,性能较差!
//乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制.线程安全,性能较好.
//需求:1变量,100个线程,每个线程对其加100次.
Runnable target =new MyRunnable1();
for(int i=1;i<=100;i++){
new Thread(target).start();
}
}
}
Test2
package com.itheima.d9_tz;
public class Test2 {
public static void main(String[] args) {
//目标:拓展悲观锁,乐观锁原理.
//悲观锁:一上来就加锁,没有安全感.每次只能一个线程进入访问完毕后,再解锁.线程安全,性能较差!
//乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制.线程安全,性能较好.
//需求:1变量,100个线程,每个线程对其加100次.
Runnable target =new MyRunnable1();
for(int i=1;i<=100;i++){
new Thread(target).start();
}
}
}
6.8练习题
示例
Demo1
package com.itheima.d10_test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Demo1 {
public static void main(String[] args) {
//目标:有100份礼物,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出,
//利用多线程模拟该过程并将线程的名称打印出来,并最后在控制台分别打印小红,小明各白送出多少份礼物,
//1.拿100份礼品到程序中来
List<String> gift=new ArrayList<>();
String[] names={"口红","包包","鲜花","剃须刀","皮带","手表"};
Random r=new Random();
for (int i=0;i <100;i++){
gift.add(names[r.nextInt(names.length)]+(i+1));
}
System.out.println(gift);
//2、定义线程类,创建线程对象,去集合中拿礼物给别人
SendThread xm=new SendThread(gift,"小明");
xm.start();
SendThread xh=new SendThread(gift,"小红");
xh.start();
System.out.println("小明一共发了"+xm.getCount()+"件礼物");
System.out.println("小红一共发了"+xh.getCount()+"件礼物");
}
}
SendThread
package com.itheima.d10_test;
import java.util.List;
import java.util.Random;
public class SendThread extends Thread{
private List<String> gift;
private int count;
public SendThread(List<String>gift,String name) {
super(name);
this.gift = gift;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public void run(){
String name=Thread.currentThread().getName();
//小明,小红发礼物出去
// 实现线程安全问题
//注意1:锁必须唯一
Random r=new Random();
while (true) {
synchronized (gift) {
if(gift.size()<10){
System.out.println(name+"一共发了"+this.getCount()+"件礼物");
break;
}
String rs = gift.remove(r.nextInt(gift.size()));
System.out.println(name+"发出了"+rs);
count++;
}
}
}
}
十二.网络编程
1.网络编程概述
什么是网络编程?
- 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的).
基本的通信架构
- 基本的通信架构有2种形式:CS架构(Client:客户端/Server服务端)、BS架构(Browser浏览器/Server服务端).
无论是CS架构,还是BS架构的软件都必须依赖网络编程!
2.网络通信三要素
2.1IP地址
IP地址
- IP(Internet Protocol):全称"“互联网协议地址”,是分配给上网设备的唯一标志.
- IP地址有两种形式:IPV4、IPV6
IPV6地址 - IPV6:共128位,号称可以为地球每一粒沙子编号.
- IPV6分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号(:)分开.
IP域名
公网IP,内网IP - 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用.
- 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0-192.168.255.255,专门为组织机构内部使用.
特殊IP地址:
- 127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机.
IP常用命令:
- ipconfig:查看本机lP地址.
- ping IP地址:检查网络是否连通.
InetAddress
- 代表IP地址.
InetAddress的常用方法如下
示例
InetAddressTest
package com.itheima.d1_ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
//目标:掌握InetAddress类的使用.
public class InetAddressTest {
public static void main(String[] args) throws Exception {
//1、获取本机IP地址对象的
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
System.out.println();
//2、获取指定IP或者域名的IP地址对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
System.out.println(ip2.isReachable(6000));
}
}
2.2端口号
端口
- 标记正在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围是0~65535.
分类
- 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序.
- 动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配.
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错.
2.3协议
通信协议
- 网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议.
开放式网络互联标准:OS引网络参考模型
- OSI网络参考模型:全球网络互联标准.
- TCP/IP网络模型:事实上的国际标准.
传输层的2个通信协议 - UDP(User Datagram Protocol):用户数据报协议;TCP(Transmission Control Protocol):传输控制协议.
UDP协议
- 特点:无连接、不可靠通信.
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等.
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的.
TCP协议
- 特点:面向连接、可靠通信.
- TCP的最终目的:要保证在不可靠的信道上实现可靠的传输.
- TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接.
TCP协议:三次握手建立可靠连接
- 可靠连接:确定通信双方,收发消息都是正常无问题的!(全双工)
TCP协议:四次握手断开连接
3.UDP通信
3.1快速入门
UDP通信
- 特点:无连接、不可靠通信.
- 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP等信息封装成一个数据包,发出去就不管了.
- Java提供了一个java.net.DatagramSocket类来实现UDP通信.
DatagramSocket:用于创建客户端、服务端
DatagramPacket:创建数据包
案例
示例
Client
package com.itheima.d2_udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
//目标:完成UDP通信快速入门:实现1发1收.
public class Client {
public static void main(String[] args) throws Exception {
//1、创建客户端对象(发非菜出去的人)
DatagramSocket socket = new DatagramSocket(8888);
//2、创建数据包对象封装要发出去的数据
/* public DatagramPacket(byte buf[], int offset, int length,
InetAddress address, int port)
参数一:封装要发出去的数据,
参数二:发送出去的数据大小(字节个数)
参数三:服务瑞的IP地址(找到服务端主机)
参数四:服务瑞程序的端口
*/
byte[] bytes="我是快乐的客户端,我爱你abc".getBytes();
DatagramPacket packet= new DatagramPacket(bytes,bytes.length
, InetAddress.getLocalHost(),7777);
//3、开始正式发送这个数据包的数据出去了
socket.send(packet);
System.out.println("客户端数据发送完毕");
socket.close();//释放资源
}
}
Server
package com.itheima.d2_udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//目标:完成UDP通信快速入门-服务端开发
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动------");
//1、创建一个服务端对象
DatagramSocket socket = new DatagramSocket(7777);
//2.创建一个数据包对象,用于接受数据的
byte[] buffer=new byte[1024*64];//64KB
DatagramPacket packet=new DatagramPacket(buffer, buffer.length);
//3.开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
//4、从字节数组中,把接收到的数据直接打印出来
//接收多少就例出多少
//获取本次数据包接收了多少数据
int len =packet.getLength();
String rs = new String(buffer,0,len);
System.out.println(rs);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
}
}
3.2多发多收
示例
Client
package com.itheima.d3_udp2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//目标:完成UDP多发多收
public class Client {
public static void main(String[] args) throws Exception {
//1、创建客户端对象(发非菜出去的人)
DatagramSocket socket = new DatagramSocket();
//2、创建数据包对象封装要发出去的数据
/* public DatagramPacket(byte buf[], int offset, int length,
InetAddress address, int port)
参数一:封装要发出去的数据,
参数二:发送出去的数据大小(字节个数)
参数三:服务瑞的IP地址(找到服务端主机)
参数四:服务瑞程序的端口
*/
Scanner sc=new Scanner(System.in);
while (true) {
System.out.println("请说");
String msg=sc.nextLine();
//一旦发现用户输入的exit命令,就退出客户端
if("exit".equals(msg)){
System.out.println("欢迎下次光临!退出成功!");
socket.close();//释放资
break;//跳出死循环
}
byte[] bytes=msg.getBytes();
DatagramPacket packet= new DatagramPacket(bytes,bytes.length
, InetAddress.getLocalHost(),7777);
//3、开始正式发送这个数据包的数据出去了
socket.send(packet);
}
System.out.println("----------------------");
System.out.println("客户端数据发送完毕");
}
}
Server
package com.itheima.d3_udp2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//目标:完成UDP通信快速入门-服务端开发
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动------");
//1、创建一个服务端对象
DatagramSocket socket = new DatagramSocket(7777);
//2.创建一个数据包对象,用于接受数据的
byte[] buffer=new byte[1024*64];//64KB
DatagramPacket packet=new DatagramPacket(buffer, buffer.length);
//3.开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
while (true) {
//3.开始正式使用数据包来接收客户端发来的数据
socket.receive(packet);
//4、从字节数组中,把接收到的数据直接打印出来
//接收多少就例出多少
//获取本次数据包接收了多少数据
int len =packet.getLength();
String rs = new String(buffer,0,len);
System.out.println(rs);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println("-----------------------");
}
}
}
4.TCP通信
4.1快速入门
TCP通信
- 特点:面向连接、可靠通信.
- 通信双方事先会采用"三次握手"方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端.
- Java提供了一个java.net.Socket类来实现TCP通信.
TCP通信之-客户端开发
- 客户端程序就是通过java.net包下的Socket类来实现的.
TCP通信-服务端程序的开发
- 服务端是通过iava.net包下的ServerSocket类来实现的
ServerSocket
示例
Client
package com.itheima.d4_tcp1;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
//目标:完成TCP通信快速入门-客户端开发:实现1发1收,
public class Client {
public static void main(String[] args) throws Exception {
//1、创建Socket对象,并同时请求与服务端程序的连接,
Socket socket =new Socket("127.0.0.1",
8888);
//2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
OutputStream os =socket.getOutputStream();
//3、把低级的字节输出流包装成数据输出流
DataOutputStream dos =new DataOutputStream(os);
//4、开始写数据出去了
dos.writeUTF("在一起,好吗?");
dos.close();
socket.close();//释放连接资
}
}
Server
package com.itheima.d4_tcp1;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//目标:完成TCP通信快速入门-服务端开发:实现1发1收.
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(8888);
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
//3、从socket通信管道中得到一个字节输入流.
InputStream is =socket.getInputStream();
//4、把原始的字节输入流包技成数据输入流
DataInputStream dis = new DataInputStream(is);
//5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
//其实我们也可以获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
4.2多发多收
Client
package com.itheima.d5_tcp2;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//目标:完成TCP通信多发多收
public class Client {
public static void main(String[] args) throws Exception {
//1、创建Socket对象,并同时请求与服务端程序的连接,
Socket socket =new Socket("127.0.0.1",
8888);
//2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
OutputStream os =socket.getOutputStream();
//3、把低级的字节输出流包装成数据输出流
DataOutputStream dos =new DataOutputStream(os);
Scanner sc=new Scanner(System.in);
while(true){
System.out.println("请说");
String msg=sc.nextLine();
if(msg.equals("exit")){
System.out.println("欢迎您下次光临,退出成功");
dos.close();
socket.close();
break;
}
//4、开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
Server
package com.itheima.d5_tcp2;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//目标:完成TCP通信快速入门-服务端开发:实现1发1收.
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(8888);
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
//3、从socket通信管道中得到一个字节输入流.
InputStream is =socket.getInputStream();
//4、把原始的字节输入流包技成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
//5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了");
socket.close();
dis.close();
System.out.println("服务端关闭");
break;
}
//其实我们也可以获取客户端的IP地址
// System.out.println(socket.getRemoteSocketAddress());
}
}
}
4.3TCP通信-支持与多个客户端同时通信
目前我们开发的服务端程序,是否可以支持与多个客户端同时通信?
- 不可以的.
- 因为服务端现在只有一个主线程,只能处理一个客户端的消息.
示例
Client
package com.itheima.d6_tcp3;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
//1、创建Socket对象,并同时请求与服务端程序的连接,
Socket socket =new Socket("127.0.0.1",
8888);
//2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
OutputStream os =socket.getOutputStream();
//3、把低级的字节输出流包装成数据输出流
DataOutputStream dos =new DataOutputStream(os);
Scanner sc=new Scanner(System.in);
while(true){
System.out.println("请说");
String msg=sc.nextLine();
if(msg.equals("exit")){
System.out.println("欢迎您下次光临,退出成功");
dos.close();
socket.close();
break;
}
//4、开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
Server
package com.itheima.d6_tcp3;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(8888);
while (true){
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
ServerReaderThread
package com.itheima.d6_tcp3;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket=socket;
}
@Override
public void run(){
try {
InputStream is=socket.getInputStream();
DataInputStream dis=new DataInputStream(is);
while (true){
try {
String msg =dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
dis.close();
socket.close();
return;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
4.4TCP通信-综合案例
4.4.1即时通信-群聊
示例
Client
package com.itheima.d7_tcp4;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
//目标:完成TCP通信快速入门-客户端开发:实现1发1收,
public class Client {
public static void main(String[] args) throws Exception {
//1、创建Socket对象,并同时请求与服务端程序的连接,
Socket socket =new Socket("127.0.0.1",
8888);
//创建一个独立的线程,负责随机从s0ckt中接收服务端发送过来的消息.
new ClientReaderThread(socket).start();
//2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
OutputStream os =socket.getOutputStream();
//3、把低级的字节输出流包装成数据输出流
DataOutputStream dos =new DataOutputStream(os);
Scanner sc=new Scanner(System.in);
System.out.println("请说");
while(true){
String msg=sc.nextLine();
if(msg.equals("exit")){
System.out.println("欢迎您下次光临,退出成功");
dos.close();
socket.close();
break;
}
//4、开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
ClientReaderThread
package com.itheima.d7_tcp4;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ClientReaderThread extends Thread {
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
InputStream is=socket.getInputStream();
DataInputStream dis=new DataInputStream(is);
while (true){
try {
String msg =dis.readUTF();
System.out.println(msg);
//把这个消息分发给全部客户端进行接收.
} catch (Exception e) {
System.out.println("自己下线了"+socket.getRemoteSocketAddress());
dis.close();
socket.close();
return;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Server
package com.itheima.d7_tcp4;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
//
public class Server {
public static List<Socket> onlineSockets=new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(8888);
while (true){
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
onlineSockets.add(socket);
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
ServerReaderThread
package com.itheima.d7_tcp4;
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket=socket;
}
@Override
public void run(){
try {
InputStream is=socket.getInputStream();
DataInputStream dis=new DataInputStream(is);
while (true){
try {
String msg =dis.readUTF();
System.out.println(msg);
//把这个消息分发给全部客户端进行接收.
onsendMsgtoAll(msg);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
dis.close();
socket.close();
Server.onlineSockets.remove(socket);
return;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
private void onsendMsgtoAll(String msg) throws Exception {
//发送给全部在线的socket管道接收.
for (Socket onlineSocket : Server.onlineSockets) {
OutputStream os=onlineSocket.getOutputStream();
DataOutputStream dos=new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}
4.4.2实现一个简易版的BS架构
任务
- 要求从浏览器中访问服务器,并立即让服务器响应一个很简单的网页给浏览器展示,网页内容就是"黑马程序员666"
BS架构的基本原理
- HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
示例
Server
package com.itheima.d8_tcp5;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(8888);
while (true){
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
new ServerReaderThread(socket).start();
}
}
}
ServerReaderThread
package com.itheima.d8_tcp5;
import com.itheima.d7_tcp4.Server;
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//立即响应一个网页内容:"黑马程序员"给浏览器展示.
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();//必须换行
ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>");
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用线程池进行优化
示例
Server
package com.itheima.d9_tcp6;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功------");
//1、创建ServerSocket的对象,同时为服务端注册端口,
ServerSocket serverSocket =new ServerSocket(9999);
//创建出一个线程池,负责处理通信管道的任务.
ThreadPoolExecutor pool= new ThreadPoolExecutor(8*2,8*2,0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while (true){
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket =serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
pool.execute(new ServerReaderRunnable(socket));
}
}
}
ServerReaderRunnable
package com.itheima.d9_tcp6;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
//Thread本身就是任务类
public class ServerReaderRunnable extends Thread{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//立即响应一个网页内容:"黑马程序员"给浏览器展示.
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();//必须换行
ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>");
ps.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
十三.Java高级
1.单元测试
1.1概述、Junit框架快速入门
单元测试
- 就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试.
咱们之前是如何进行单元测试的?有啥问题?
- 只能在main方法编写测试代码,去调用其他方法进行测试.
- 无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试.
- 无法得到测试的报告,需要程序员自己去观察测试是否成功.
Junit单元测试框架
- 可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Juit框架,比如IDEA)
优点
- 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立.
-不需要程序员去分析测试的结果,会自动生成测试报告出来.
1.2Junit框架的常见注解
Junit单元测试框架的常用注解( Junit4.xxxx版本)
- 在测试方法执行前执行的方法,常用于:初始化资源.
- 在测试方法执行完后再执行的方法,常用于:释放资源.
Junit单元测试框架的常用注解(Junit5.Xxx版本)
示例
StringUtil
package com.itheima.d1_junit;
//字符串工具类
public class StringUtil {
public static void printNumber(String name) {
if(name==null){
System.out.println("null");
return;
}
System.out.println("名字长度是:" + name.length());
}
/**
*求字符串的最大索引
*/
public static int getMaxIndex(String data){
if (data == null) {
return -1;
}
return data.length()-1;
}
}
StringUtilTest
package com.itheima.d1_junit;
import org.junit.*;
//测试类
public class StringUtilTest {
@Before
public void test1(){
System.out.println("--->test1 Before执行了");
}
@After
public void test2(){
System.out.println("--->test2 After执行了");
}
@BeforeClass
public static void test3(){
System.out.println("--->test3 BeforeClass执行了");
}
@AfterClass
public static void test4(){
System.out.println("--->test4 AfterClass执行了");
}
@Test//测试方法
public void testPrintNumber(){
StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
@Test //测试方法
public void testGetMaxIndex(){
int index1 = StringUtil.getMaxIndex(null);
System.out.println(index1);
int index2 = StringUtil.getMaxIndex("admin");
System.out.println(index2);
Assert.assertEquals("方法内部有bug!",4,index2);
}
}
2.反射
2.1认识反射、获取类
反射(Reflection)
- 反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等).
反射学什么? - 学习获取类的信息、操作它们
- 反射第一步:加载类,获取类的字节码:Class对象
- 获取类的构造器:Constructorj对象
- 获取类的成员变量:Field对象
- 获取类的成员方法:Method对象
- 全部认识完后,再看反射的应用场景
获取Class对象的三种方式
- Class c1=类名.class
- 调用Class提供方法:public static Class forName(String package);
- Objecti提供的方法:public Class getClass();Class c3=对象.getClass(0;
示例
Student
package com.itheima.d2_reflect;
public class Student {
private String name;
private int age;
private char sex;
private double height;
public Student() {
}
public Student(String name, int age, char sex, double height, String hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
this.hobby = hobby;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
private String hobby;
}
Test1Class
package com.itheima.d2_reflect;
//目标:获取Class对象
public class Test1Class {
public static void main(String[] args) throws ClassNotFoundException {
Class c1= Student.class;
System.out.println(c1.getName());//全类名 com.itheima.d2_reflect.Student
System.out.println(c1.getSimpleName());//Student
Class c2=Class.forName("com.itheima.d2_reflect.Student");
//指向同一个对象
System.out.println(c1==c2);//true
Student s=new Student();
Class c3=s.getClass();
System.out.println(c3==c2);
}
}
2.2获取类的构造器
获取类的构造器、并对其进行操作
- Class提供了从类中获取构造器的方法.
获取类构造器的作用:依然是初始化对象返回
示例
Cat
package com.itheima.d2_reflect;
public class Cat {
public static int a;
public static final String COUNTRY="中国";
public String name;
private int age;
public Cat() {
System.out.println("无参数构造器执行了");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private void run(){
System.out.println("w跑的贼快w~");
}
public void eat(){
System.out.println("爱吃猫粮~");
}
private String eat(String name){
return"猫最爱吃:"+name;
}
public Cat(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;
}
}
Test2Constructor
package com.itheima.d2_reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
//目标:掌握获取类的构造器,并对其进行操作.
public class Test2Constructor {
@Test
public void testGetConstructors() {
//1、反第一步:必须先得到这个类的Class对象
Class c = Cat.class;
//2.获取类的全部构造器,getConstructors只能获取public修饰的构造器
// Constructor[] constructors = c.getConstructors();
//获取全部构造器
Constructor[] constructors1=c.getDeclaredConstructors();
//3、遍历数组中的每个构造器对象
for (Constructor constructor : constructors1) {
System.out.println(constructor.getName() + "--->"
+ constructor.getParameterCount());
}
}
@Test
public void testGetConstructor()throws Exception{
//1、反射第一步:必须先得到这个类的Class对象
Class c =Cat.class;
//2、获取某个构造器:无参数构造器(public)
//Constructor constructor =c.getConstructor();
Constructor constructor =c.getDeclaredConstructor();
System.out.println(constructor.getName() + "--->"
+ constructor.getParameterCount());
constructor.setAccessible(true);//禁止检查访问权限
Cat cat=(Cat)constructor.newInstance();
System.out.println(cat);
//3.获取有参数构造器
Constructor constructor1=
c.getDeclaredConstructor(String.class,int.class);
System.out.println(constructor.getName() + "--->"
+ constructor1.getParameterCount());
Cat cat1=(Cat) constructor1.newInstance("叮当猫",3);
System.out.println(cat1);
}
}
2.3获取类的成员变量
获取类的成员变量
获取到成员变量的作用:依然是赋值、取值
示例
Test3Field
package com.itheima.d2_reflect;
import org.junit.Test;
import java.lang.reflect.Field;
//目标:掌握获取类的成员变量,并对其进行操作.
public class Test3Field {
@Test
public void testGetFields() throws Exception {
//1、反射第一步:必须是先得到类的C1ass对象
Class c = Cat.class;
//2、获取类的全部成员变量
Field[] fields=c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+"--->"+field.getType());
}
//4.定位某个成员变量
Field fName=c.getDeclaredField("name");
System.out.println(fName.getName()+"--->"+fName.getType());
Field fAge =c.getDeclaredField("age");
System.out.println(fAge.getName()+"--->"+fAge.getType());
//赋值
Cat cat=new Cat();
fName.setAccessible(true);
fName.set(cat,"咖啡猫");
System.out.println(cat);
//取值
String name=(String) fName.get(cat);
System.out.println(name);
}
}
2.4获取类的成员方法
获取类的成员方法
- Class提供了从类中获取成员方法的API
成员方法的作用:依然是执行
示例
Test4Method
package com.itheima.d2_reflect;
import org.junit.Test;
import java.lang.reflect.Method;
//目标:掌握获取类的成员方法,并对其进行操作.
public class Test4Method {
@Test
public void testGetMethods() throws Exception {
// 1、反射第一步:先得Class对象,
Class c = Cat.class;
//2、获取类的全部成员方法,
Method[] methods =c.getDeclaredMethods();
//3、遍历这个数组中的每个方法对象
for (Method method :methods) {
System.out.println(method.getName() + "--->"
+ method.getParameterCount() + "--->"
+ method.getReturnType());
}
//4、获取某个方法对象
Method run=c.getDeclaredMethod("run");//拿run方法,无参数的
System.out.println(run.getName()+"--->"
+run.getParameterCount()+"--->"
+run.getReturnType());
Method eat =c.getDeclaredMethod("eat",String.class);
System.out.println(eat.getName()+"--->"
+eat.getParameterCount()+"---->"
+eat.getReturnType());
Cat cat = new Cat();
run.setAccessible(true);
Object rs=run.invoke(cat);//调用无参数的rUn方法,用cat对象触发调
System.out.println(rs);//null ,因为没有返回对象
eat.setAccessible(true);
String rs2=(String) eat.invoke(cat, "鱼儿");
System.out.println(rs2);
}
}
2.5作用、应用场景
反射的作用?
- 基本作用:可以得到一个类的全部成分然后操作.
- 可以破坏封装性.
- 最重要的用途是:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能.
案例
示例
ObjectFrame
package com.itheima.d2_reflect;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class ObjectFrame {
//目标:保存任意对象的字段和其数据到文件中去
public static void saveObject(Object obj) throws Exception {
PrintStream ps=new PrintStream(new FileOutputStream("src\\data.txt",true));
//0bj是任意对象,到底有多少个字段要保存.
Class c = obj.getClass();
String cName = c.getSimpleName();
ps.println("---------------"+cName+"--------------");
//2、从这个类中提取它的全部成员变量
Field[] fields =c.getDeclaredFields();
//3.遍历每个成员变量
for (Field field : fields) {
//4、拿到成员变量的名字
String name =field.getName();
//5、拿到这个成员变量在对象中的数据,
field.setAccessible(true);
String value =field.get(obj)+"";
ps.println(name+"="+value);
}
}
}
Student
package com.itheima.d2_reflect;
public class Student {
private String name;
private int age;
private char sex;
private double height;
public Student() {
}
public Student(String name, int age, char sex, double height, String hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
this.hobby = hobby;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
private String hobby;
}
Teacher
package com.itheima.d2_reflect;
public class Teacher {
private String name;
private double salary;
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public Teacher() {
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
Test5Frame
package com.itheima.d2_reflect;
import org.junit.Test;
//目标:使用反射技术:设计一个保存对象的简易版框架.
public class Test5Frame {
@Test
public void save()throws Exception{
Student s1=new Student("黑马吴彦祖",45,'男',185.3,"蓝球,冰球");
Teacher t1=new Teacher("播妞",999.9);
//需求:把任意对象的字段名和其对应的值等信息,保存到文件中去.
ObjectFrame.saveObject(s1);
ObjectFrame.saveObject(t1);
}
}
3.注解
3.1概述、自定义注解
注解(Annotation)
- 就是ava代码里的特殊标记,比如:@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序.
- 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处
自定义注解
- 就是自己定义注解
特殊属性名:value - 如果注解中只有一个value属性,使用注解时,value名称可以不写!
注解的原理
- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的.
- @注解(…):其实就是一个实现类对象,实现了该注解以及Annotation:接口.
示例
AnnotationTest1
package com.itheima.d3_annotation;
@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
@MyTest2("孙悟空")//省略属性名称
public class AnnotationTest1 {
@MyTest1(aaa="铁扇公主",bbb = false,ccc={"Python","前端"})
public void test1(){
}
}
MyTest1
package com.itheima.d3_annotation;
public @interface MyTest1 {
String aaa();
boolean bbb()default true;
String[]ccc();
}
MyTest2
package com.itheima.d3_annotation;
public @interface MyTest2 {
String value();//只有value或者
int age() default 23;//其他属性有默认值
}
3.2元注解
元注解
- 指的是:修饰注解的注解.
示例
AnnotationTest2
package com.itheima.d3_annotation;
//目标:认识元注解,搞清楚元注解的作用.
@Mytest3
public class AnnotationTest2 {
// @Mytest3 规定不能使用成员变量上
private String name;
@Mytest3
public void test(){
}
}
Mytest3
package com.itheima.d3_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})//当前被修饰的注解只能用在类上.
@Retention(RetentionPolicy.RUNTIME)
public @interface Mytest3 {
}
3.3注解的解析
什么是注解的解析?
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来.
如何解析注解? - 指导思想:要解析谁上面的注解,就应该先拿到谁.
- 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解.
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解.
- Class、Method、Field,Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力.
示例
Demo
package com.itheima.d3_annotation;
@MyTest4(value="蜘蛛精",aaa=99.5,bbb={"至尊宝","黑马"})
public class Demo {
@MyTest4(value="孙悟空",aaa=199.9,bbb={"紫霞","牛夫人"})
public void test1(){
}
}
AnnotationTest3
package com.itheima.d3_annotation;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
//目标:掌握注解的解析.
public class AnnotationTest3 {
@Test
public void parseClass() {
//1.先得到Class对象
Class c = Demo.class;
//2、解析类上的注解
//判断类上是否包含了某个注解
if (c.isAnnotationPresent(MyTest4.class)) {
MyTest4 myTest4 = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
@Test
public void parseMethod() throws Exception {
//1.先得到Class对象
Class c= Demo.class;
Method m=c.getDeclaredMethod("test1");
//2、解析方法上的注解
//判断方法上是否包含了某个注解
if(c.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4=(MyTest4) m.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}
MyTest4
package com.itheima.d3_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa()default 100;
String[]bbb();
}
3.4应用场景
示例
MyTest
package com.itheima.d3_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
AnnotationTest4
package com.itheima.d3_annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//目标:模拟Junit框架的设计.
public class AnnotationTest4 {
//@MyTest
public void test1() {
System.out.println("===test1====");
}
@MyTest
public void test2() {
System.out.println("===test2====");
}
//ddddddddddddddddddd @MyTest
public void test3() {
System.out.println("===test3====");
}
@MyTest
public void test4() {
System.out.println("===test4====");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
AnnotationTest4 a =new AnnotationTest4();
//启动程序
//1.得到Class对象
Class c= AnnotationTest4.class;
//2.提取这个类中的全部成员方法
Method[] methods=c.getDeclaredMethods();
//3.遍历所有方法,有MyTest注解就触发执行
for (Method method:methods){
if(method.isAnnotationPresent(MyTest.class)){
//说明当前方法上是存在@MyTest,触发当前方法执行.
method.invoke(a);
}
}
}
}
4.动态代理
4.1程序为什么需要代理?代理长什么样?
程序为什么需要代理?代理长什么样?
- 对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责.
- 对象有什么方法想被代理,代理就一定要有对应的方法
如何为ava对象创建一个代理对象?
- java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
示例
BigStar
package com.itheima.d4_proxy;
public class BigStar implements Star {
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name){
System.out.println(this.name+"正在唱:"+name);
return"谢谢!谢谢!";
}
public void dance(){
System.out.println(this.name+"正在优美的跳舞~~");
}
}
ProxyUtil
package com.itheima.d4_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
/* newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数1:用于指定一个类加载器
参数2:指定生成的代理长什么样子,也就是有哪些方法
参数3:用来指定生成的代理对象要干什么事情
*/
Star starProxy =(Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象要做的事情,会在这里写代码
if(method.getName().equals("sing")){
System.out.println("准备话筒,收钱20万");
//return method.invoke(bigStar,args);
}else if(method.getName().equals("dance")){
System.out.println("准备场地,收钱1000万");
// return method.invoke(bigStar,args);
}
return method.invoke(bigStar,args);
}
});
return starProxy;
}
}
Star
package com.itheima.d4_proxy;
public interface Star {
String sing(String name);
void dance();
}
Test
package com.itheima.d4_proxy;
public class Test {
public static void main(String[] args) {
BigStar s=new BigStar("杨超越");
Star starProxy =ProxyUtil.createProxy(s);
String rs=starProxy.sing("好日子");
System.out.println(rs);
starProxy.dance();
}
}
4.2解决实际问题、掌握使用代理的好处
示例
ProxyUtil
package com.itheima.d5_proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static UserService createProxy(UserService userService){
UserService userServiceProxy =(UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{UserService.class},new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[]args)throws Throwable {
if (method.getName().equals("Login") || method.getName().equals("deleteUsers") ||
method.getName().equals("selectUsers")) {
long startTime = System.currentTimeMillis();
Object rs = method.invoke(userService, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime) / 1000.0 + "s");
return rs;
} else {
Object rs = method.invoke(userService, args);
return rs;
}
}
});
return userServiceProxy;
}
}
Test
package com.itheima.d5_proxy2;
import java.util.Arrays;
//目标:使用动态代理解决实际问题,并掌挥使用代理的好处.
public class Test {
public static void main(String[] args) throws Exception {
//1、创建用户业务对象.
UserService userService = ProxyUtil.createProxy(new UserServiceImpl());
//2、调用用户业务的功能.
userService.login("admin","123456");
System.out.println("---------------------------------");
userService.deleteUsers();
System.out.println("---------------------------------");
String[] names =userService.selectUsers();
System.out.println("查询到的用户是:"+ Arrays.toString(names));
System.out.println("----------------------------------");
}
}
UserService
package com.itheima.d5_proxy2;
//用户业务接口
public interface UserService{
//登录功能
void login(String loginName,String passWord)throws Exception;
//删除用户
void deleteUsers()throws Exception;
//查询用户,返回数组的形式,
String[]selectUsers()throws Exception;
}
UserServiceImpl
package com.itheima.d5_proxy2;
//用户业务实观类(面向接口编程)
public class UserServiceImpl implements UserService {
@Override
public void login(String loginName, String passWord) throws Exception {
if ("admin".equals(loginName) && "123456".equals(passWord)) {
System.out.println("您登录成功,欢迎光临本系统~");
} else {
System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
}
@Override
public void deleteUsers() throws Exception {
System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
}
@Override
public String[] selectUsers() throws Exception {
System.out.println("查询出了3个用户");
String[] names = {"张全蛋", "李二狗", "牛爱花"};
Thread.sleep(500);
return names;
}
}