参考(黑马):https://www.bilibili.com/video/BV1Cv411372m/
特殊文件
Properties属性文件
- Properties是一个Map集合(键值对集合),但是我们一般不会当集合使用。
- 核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容。
Properties读取键值对
PS:
- 如果要在控制台显示,load()方法最好接受字符流
public static void main(String[] args) throws Exception {
//1.创建一个Properties对象(空容器)
Properties properties = new Properties();
//2.开始加载属性文件中的键值对数据到properties中去
properties.load(new FileReader("src\\SpecialFile\\users.properties"));
System.out.println(properties);
//3.根据键取值
System.out.println("0:"+properties.getProperty("李笑"));
//遍历全部键和值
Set<String> keys = properties.stringPropertyNames();
for(String key : keys){
System.out.println("2:"+key+"-->"+properties.getProperty(key));
}
}
Properties写出键值对
PS:
- store方法中的第二个参数comments仅仅只是个注释
public static void main(String[] args) throws Exception {
//1.创建一个Properties对象(空容器)
Properties properties = new Properties();
properties.setProperty("李笑","兰大");
properties.store(new FileWriter("\"src\\\\SpecialFile\\\\users2.properties\""),"i save Properties File");
//遍历全部键和值
Set<String> keys = properties.stringPropertyNames();
for(String key : keys){
System.out.println("2:"+key+"-->"+properties.getProperty(key));
}
}
练习
调用Properties继承的Map方法,判断键值知否包含某一Key,并修改对象value。
public static void main(String[] args) throws Exception {
//1.创建一个Properties对象(空容器)
String fileName = "data\\test_user.txt";
Properties properties = new Properties();
properties.load(new FileReader(fileName));
//是Map的继承,当然包含map方法!!
if(properties.containsKey("李笑")){
properties.setProperty("李笑", "25");
}
properties.store(new FileWriter(fileName),"李笑年龄更改");
}
XML文件
- XML(可扩展标记语言)
- XML 中的“<标签名>”称为一个标签或是一个元素。一般是成对出现的
- XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套。
- XML中只能有一个根标签。
- XML中的标签可以有属性
<?xml version="1.0" encoding="UTF-8" ?>
<!--注释:以上头声明必须放在第一行,必须有-->
<users> <!--根标签只能有一个-->
<user id = "1"> <!-- 子标签-->
<name>李笑</name>
<sex>男</sex>
<password>minmin</password>
<data> 2 < 5 </data>
<data2>
<!--特殊数据区-->
<![CDATA[
2< 5
]]>
</data2>
</user>
<user id = "2"> <!-- 子标签-->
<name>笑笑</name>
<sex>女</sex>
<password>minmin</password>
</user>
</users>
读取XML(Dmo4j框架)
-
DOM4J概述
-
常用方法
- 拿根元素
public static void main(String[] args) throws Exception {
//1.创建Dom4J框架提供的解析器对象
SAXReader saxReader = new SAXReader();
//2.使用saxReaderd对象把需要解析的XML文件读成一个Document对象
Document document =
saxReader.read("src\\helloworld.xml");
//3.从文档对象中解析XML数据
Element root = document.getRootElement();
System.out.println(root.getName());
}
- 取下一级对象
//1.创建Dom4J框架提供的解析器对象
SAXReader saxReader = new SAXReader();
//2.使用saxReaderd对象把需要解析的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();
for(Element element:elements){
System.out.println(element.getName());
}
//5.获取某个子元素
Element people = root.element("people");
System.out.println(people.getText());
//有很多user标签,默认获取第一个
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();
//7.获取全部文本内容
System.out.println(user.elementText("name"));
System.out.println(user.elementText("password"));
Element data = user.element("data");
System.out.println(data.getText());
System.out.println(data.getTextTrim()); //取出文本删除欠货空格
}
写出XML(不建议用Dmo4j框架)
- 推荐直接把程序里的数据拼接成XML格式,然后用IO流写出去!
约束XML文件的编写
- 什么是约束XML书写:就是限制XML文件只能按照某种格式进行书写。
- 约束文档:专门用来限制xml书写格式的文档,比如:限制标签、属性应该怎么写
- DTD约束文档
- Schema约束文档
日志技术
体系结构
Logback框架
核心模块
导入方式
- 需要自行寻找三个包
PS:
对Logback日志框架的控制,都是通过其核心配置文件logback.xml来实现的,
核心配置文件logback.xml
- 日志输出位置、输出格式设置
- 通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中
- 例子
(控制台)
(系统文件)
- 开启日志、取消日志
- 例子
Logback日志级别
- 日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级依次升高):
- 设置日志级别(在核心配置文件中配置)
多线程
线程创建
方式一:继承Thread类
优缺点
- 优点:编码简单
- 缺点:线程类以及继承Thread,无法继承其他类,不利于功能的扩展
PS:
//main()方法是由一条默认的主线程负责执行
public static void main(String[] args) {
//3.创建对象
MyThread thread = new MyThread();
//4.启动线程
thread.start(); //main线程 thread线程
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出:"+ i);
}
}
//1.自定义子类继承Thread线程类
public class MyThread extends Thread{
//2.必须重写run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程MyThread输出:"+ i);
}
}
}
方式二:实现Runnable接口
优缺点
- 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强,
- 缺点:需要多一个Runnable对象
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程:"+i);
}
}
}
public static void main(String[] args) {
Runnable target = new MyRunnable();
//把任务对象交给一个线程对象处理!!!
new Thread(target).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程:"+i);
}
}
- 匿名内部类写法
public static void main(String[] args) {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程1-->:"+i);
}
}
};
new Thread(target).start();
//简化形式1:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程2-->:"+i);
}
}
}).start();
//简化形式
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程3-->:"+i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程-->:"+i);
}
}
方式三:实现Callable接口
- 方法
- FutureTask API
优缺点
- 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
- 缺点:编码复杂
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
//描述线程任务,并返回线程执行后的结果
//需求:求1~n的和
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return "1~n的和为:"+sum;
}
}
public static void main(String[] args) throws Exception{
Callable<String> call = new MyCallable(100);
//未来任务对象
//1.是一个任务对象,实现了Runnable接口
//2.可以在线程结束后,用FutureTask的get方法拿到返回结果
FutureTask<String> f1 = new FutureTask<>(call);
new Thread(f1).start();
//如果执行到这,上面线程还未执行完毕,这里代码会暂停,等待线程执行完毕
String rs = f1.get();
System.out.println(rs);
}
Thread常用方法
- 取线程名字
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String s) {
super(s); //调用父类Thread的有参构造器
}
@Override
public void run() {
Thread n = Thread.currentThread();
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+n.getName()+"输出:"+ i);
}
}
}
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.setName("1号线程");
t1.start();
System.out.println(t1.getName()); //1号线程
Thread t2 = new MyThread();
t2.start();
System.out.println(t2.getName()); //Thread-1
Thread t3 = new MyThread("3号线程");
t3.start();
System.out.println(t3.getName()); //3号线程
//主线程对象的名字
//哪个线程执行它,他就会得到哪个线程的名字
Thread n = Thread.currentThread();
System.out.println(n.getName()); //main
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出:"+ i);
}
}
线程安全
出现原因:
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
案例 (出现安全问题)
- Account类
ublic class Account {
private double money;
private String carID;
public Account() {
}
public Account(double money, String carID) {
this.money = money;
this.carID = carID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCarID() {
return carID;
}
public void setCarID(String carID) {
this.carID = carID;
}
//小明、小红同时来取钱
public void drawMoney(double money) {
//先搞清楚谁来取钱
String name = Thread.currentThread().getName();
if(this.money>=money){
System.out.println(name+"来取钱"+money+"成功");
this.money -= money;
System.out.println(name+"取钱后,余额:"+this.money);
}else {
System.out.println("余额不足");
}
}
}
- DrawThread类
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc, String name){
super(name);
this.acc = acc;
}
public DrawThread(Account acc) {
this.acc = acc;
}
@Override
public void run() {
acc.drawMoney(10000);
}
}
- 主程序
public static void main(String[] args) {
//1.创建一个对象,代表两个人共享账户
Account acc = new Account(10000, "ICBC-110");
//2.创建两个线程分别代表两人,两种不同设置线程名字的方式
Thread n1 = new DrawThread(acc); //小明
n1.setName("小明");
n1.start();
new DrawThread(acc,"小红").start(); //小红
}
结果:
线程同步
常见方案:
- 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
方式一:同步代码块
- 作用:把访问共享资源的核心代码给上锁,以此保证线程安全。(把响应代码块上锁)
- 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
- 注意:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
- 案例解决——Account类(改)
同步锁
- 使用字符串"lx"作为同步锁,因为" " 括起来的对象在字符串池只有一份(不好)
- 如果在实例方法中,使用共享资源作为锁,本案例为:this
- 如果在静态方法中,使用 类名.class 作为锁,本案例为Account.class(因为Accunt.class为字节码文件,只有一份)
public class Account {
private double money;
private String carID;
public Account() {
}
public Account(double money, String carID) {
this.money = money;
this.carID = carID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCarID() {
return carID;
}
public void setCarID(String carID) {
this.carID = carID;
}
public static void s_func(){ //静态方法
synchronized (Account.class){
}
}
//小明、小红同时来取钱
public void drawMoney(double money) {
//先搞清楚谁来取钱
String name = Thread.currentThread().getName();
//同步锁
synchronized (this){ //共享资源作为锁
if(this.money>=money){
System.out.println(name+"来取钱"+money+"成功");
this.money -= money;
System.out.println(name+"取钱后,余额:"+this.money);
}else {
System.out.println("余额不足");
}
}
}
}
结果:
方式二:同步方法
-
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。(把整个方法上锁)
-
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
-
底层原理:内部含有隐式锁,实例方法使用this,静态方式使用 类名.class
-
案例解决——Account类(改)
public class Account {
private double money;
private String carID;
public Account() {
}
public Account(double money, String carID) {
this.money = money;
this.carID = carID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCarID() {
return carID;
}
public void setCarID(String carID) {
this.carID = carID;
}
public synchronized static void s_func(){
}
//小明、小红同时来取钱
public synchronized void drawMoney(double money) {
//先搞清楚谁来取钱
String name = Thread.currentThread().getName();
if(this.money>=money){
System.out.println(name+"来取钱"+money+"成功");
this.money -= money;
System.out.println(name+"取钱后,余额:"+this.money);
}else {
System.out.println("余额不足");
}
}
}
同步代码块 vs 同步方法哪个好?
- 范围上::同步代码块锁的范围更小,同步方法锁的范围更大。锁的范围越小,性能越好。(但当今电脑性能,可以忽略不记)
- 可读性上:同步方法更好
方式三:Lock锁
-
是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
-
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
-
案例解决——Account类(改)
public class Account {
private double money;
private String carID;
//锁对象,final修饰防止修改
private final Lock lk = new ReentrantLock();
public Account() {
}
public Account(double money, String carID) {
this.money = money;
this.carID = carID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCarID() {
return carID;
}
public void setCarID(String carID) {
this.carID = carID;
}
//小明、小红同时来取钱
public void drawMoney(double money) {
//先搞清楚谁来取钱
String name = Thread.currentThread().getName();
try {
lk.lock(); //加锁
if(this.money>=money){
System.out.println(name+"来取钱"+money+"成功");
this.money -= money;
System.out.println(name+"取钱后,余额:"+this.money);
}else {
System.out.println("余额不足");
}
} catch (Exception e) {
e.printStackTrace();
} finally { //防止中间代码出bug,没有解锁,导致别的线程无法继续执行
lk.unlock(); //解锁
}
}
}
线程通信
-
等待、唤醒方法
-
案例:三个生产者,每次生成一个包子放在桌子上,且桌子上只能由一个包子;两个消费者,每人每次只能取一个吃
主程序
public class ProduceConsumeT {
public static void main(String[] args) {
Desk desk = new Desk();
//创建三个生产者线程
new Thread(()->{
while(true){
desk.put();
}
},"厨师1").start();
new Thread(()->{
while(true){
desk.put();
}
},"厨师2").start();
new Thread(()->{
while(true){
desk.put();
}
},"厨师3").start();
//创建两个消费者线程
new Thread(()->{
while(true){
desk.get();
}
},"吃货1").start();
new Thread(()->{
while(true){
desk.get();
}
},"吃货2").start();
}
}
Desk类
public class Desk {
private List<String> list = new ArrayList<>();
public synchronized void put(){
try {
String name = Thread.currentThread().getName();
if(list.size() == 0){
Thread.sleep(2000);
System.out.println(name+"做了一个肉包子");
list.add(name+"的肉包子");
//唤醒别人,等待自己
this.notifyAll();
this.wait();
}else{
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void get(){
String name = Thread.currentThread().getName();
try {
if(list.size() != 0){
Thread.sleep(1000);
System.out.println(name+"吃了"+list.get(0));
list.clear();
//唤醒别人,等待自己
this.notifyAll();
this.wait();
}else{
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程池
概念
- 线程池就是一个可以复用线程的技术。
- 工作原理
PS:任务必须实现任务接口,成为任务对象
创建线程池
- JDK5.0起提供了代表线程池的接口:ExecutorService
- 如何获得线程池对象?
PS:方法二见后
- TreadPoolExecutor
public static void main(String[] args) {
//1.创建线程池对象
/*
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handle
*/
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
- 注意事项:
任务队列是指处于等待任务的队列,被线程执行的任务不在任务队列
- 新任务拒绝策略
线程池处理Runnable任务
- 方法
主程序
public static void main(String[] args) {
//1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2.
Runnable target = new MyRunnable();
pool.execute(target); //线程池自动创建一个新线程、自动处理整个任务,自动执行!
pool.execute(target); //线程池自动创建一个新线程、自动处理整个任务,自动执行!
pool.execute(target); //线程池自动创建一个新线程、自动处理整个任务,自动执行!
pool.execute(target); //复用核心线程
}
MyRunnable类
public class MyRunnable implements Runnable{
@Override
public void run() {
//描述任务
System.out.println(Thread.currentThread().getName()+":输出666");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池处理Callable任务
- 方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
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());
}
Executors工具类实现线程池
- 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象
- 常用线程池:
注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
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());
}
线程池核心线程数配置多少?
- 计算密集型任务:CPU核心数+1
- IO密集型任务:CPU核心数*2
Executors使用可能存在的陷阱
并发、并行
线程生命周期
-
Java线程的六种状态
-
线程的6种状态转换
PS:
- sleep方法不会释放同步锁
- wait方法会释放同步锁
网络通信
- java.net.*包下提供了网络编程解决方案
基本通信架构
- 基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端
网络通信三要素
- 三要素:IP、端口、协议
IP(InetAddress)
- InetAddress:代表IP对象
- 常用方法:
public static void main(String[] args) throws Exception {
//获取本机IP
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName()); //本机名字
System.out.println(ip1.getHostAddress()); //本机ip地址
//获取指定IP或是域名的ip地址对象
InetAddress ipBaidu = InetAddress.getByName("www.baidu.com");
System.out.println(ipBaidu.getHostName());
System.out.println(ipBaidu.getHostAddress());
//判断主机与指定ip是否连通
System.out.println(ipBaidu.isReachable(6000));
}
端口
协议
UDP(User Datagram Protocol):用户数据报协议;
TCP(Transmission ControlProtocol):传输控制协议。
- UDP
- TCP
UDP通信-快速入门
- Java提供了一个java.net.DatagramSocket类来实现UDP通信
- DatagramSocket:
- DatagramPacket:
- 简单的通讯
客户端
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地址
参数四:服务端的端口
*/
byte[] bytes = "我是快乐的客户端".getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,
InetAddress.getLocalHost(),6666);
//3.正式发送
socket.send(packet);
System.out.println("客户端数据发送完毕");
socket.close();
}
}
服务端
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("------服务器启动------");
//1.创建服务端对象
DatagramSocket socket = new DatagramSocket(6666);
//2.创建一个接受数据的数据包对象
byte[] buffer = new byte[1024 * 64];
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()); //客户端ip地址
System.out.println(packet.getPort()); //客户端端口
socket.close();
}
}
结果
UDP通信-多发多收
客户端
public class Client {
public static void main(String[] args) throws Exception{
//1.创建客户端对象
DatagramSocket socket = new DatagramSocket();
//2.创建要发送的数据包对象
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
socket.close();
break;
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,
InetAddress.getLocalHost(),6666);
//3.正式发送
socket.send(packet);
}
System.out.println("客户端关闭");
}
}
服务端
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("------服务器启动------");
//1.创建服务端对象
DatagramSocket socket = new DatagramSocket(6666);
//2.创建一个接受数据的数据包对象
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
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("------------------------------");
}
}
}
结果