文章目录
- 绘图技术
- 基本原理
- 常用方法
- `java.awt.Graphics`类
- **基本图形绘制**:
- `java.awt.Toolkit`类
- `java.awt.Font`类
- `javax.swing.JComponent`类
- 代码示例
- 事件处理机制
- 相关介绍
- 常用方法
- JFrame类
- JPanel类
- Graphics类
- KeyListener接口
- KeyEvent类
- JComponent类(JPanel的父类)
- 代码示例
- 线程
- 相关介绍
- 线程使用方法
- 代码示例
- 继承Thread类,重写run方法代码示例
- 实现Runnable接口,重写run方法
- 常用方法
- 代码示例
- 线程的声明周期
- 线程状态
- 代码示例
- 多线程下的安全问题
- 介绍
- 示例代码
- 修复方法
- 线程同步机制
- 介绍
- 同步具体方法
- 示例
- 互斥锁
- 介绍
- 注意细节
- 代码示例
- 线程死锁
- 介绍
- 代码示例
- 释放锁
- 介绍
- IO流
- 相关介绍
- 流的分类
- IO流的常用类
- 字节流
- FileInputStream--读取文件
- 类关系图
- 常用方法
- 代码示例
- OutputStream--写入文件
- 类体系图
- 常用方法
- 代码示例
- 综合代码示例
- 字符流
- FileReader--读取文件
- 类关系图
- 常用方法
- 代码示例
- FileWriter--写入文件
- 类关系图
- 相关方法
- 代码示例
- 节点流与处理流
- 相关介绍
- BufferedReader--读取文件
- 代码示例
- BufferedWriter--写入文件
- 代码示例
- BufferedCopy--复制文件
- 代码示例
- 代码示例
- 对象流
- 相关介绍
- 常用方法
- 注意事项
- 代码示例
- 标准输入输出流
- 介绍
- 代码示例
- 转换流
- 相关介绍
- 代码示例
- 打印流
- 类体系图
- 代码示例
- PrintStream演示
- PrintWriter演示
- Properties 类
- 介绍
- 常用方法
- 代码示例
- 查看文件的键值对
- 写入修改文件的键值对
- 文件创建
- 常用方法
- 代码示例
- 查看文件相关信息
- 常用方法
- 代码示例
- 文件操作
- 常用方法
- 代码示例
绘图技术
-
基本原理
-
Component类提供了两个和绘图相关最重要的方法:
- paint(Graphics g)组的外
- repaint()刷新组件的外观。
-
当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件。
-
在以下情况paint() 将会被调用:
- 窗口最小化,再最大化
- 窗口的大小发生变化
- repaint方法被调用
-
-
常用方法
-
java.awt.Graphics
类-
基本图形绘制:
-
绘制直线
-
g.drawLine(10, 10, 100, 100);
// 绘制从(10,10)到(100,100)的直线
-
-
绘制矩形边框
-
g.drawRect(10, 10, 100, 100);
// 绘制左上角在(10,10),宽高均为100的矩形边框
-
-
绘制椭圆或圆形边框
-
g.drawOval(10, 10, 100, 100);
// 绘制外接矩形左上角在(10,10),宽高均为100的椭圆边框
-
-
填充矩形
-
g.fillRect(10, 10, 100, 100);
// 绘制并填充一个左上角在(10,10),宽高均为100的矩形
-
-
填充椭圆或圆形
-
g.fillOval(10, 10, 100, 100);
// 绘制并填充一个外接矩形左上角在(10,10),宽高均为100的椭圆
-
-
-
颜色设置:
-
g.setColor(Color.blue);
// 设置画笔颜色为蓝色 -
g.setColor(new Color(255, 0, 0));
// 设置画笔为RGB颜色(红色)
-
-
字体设置:
-
g.setFont(new Font("隶书", Font.BOLD, 50));
// 设置字体为隶书,粗体,大小为50
-
-
文本绘制:
-
g.drawString("北京你好", 100, 100);
// 在(100,100)位置绘制字符串“北京你好”
-
-
图像绘制:
-
g.drawImage(image, 10, 10, 568, 624, this);
// 在指定位置(10,10)绘制指定大小的图片(568x624)
-
-
-
java.awt.Toolkit
类-
Toolkit.getDefaultToolkit()
// 获取默认的工具包 -
Toolkit.getImage(String filename)
// 获取指定路径的图片资源
-
-
java.awt.Font
类-
new Font(String name, int style, int size)
// 创建一个指定名称、样式和大小的字体对象-
Font.PLAIN
// 普通样式 -
Font.BOLD
// 粗体样式 -
Font.ITALIC
// 斜体样式
-
-
-
javax.swing.JComponent
类-
repaint()
// 重绘组件,触发paint()
方法 -
setBackground(Color color)
// 设置组件的背景颜色 -
setPreferredSize(Dimension size)
// 设置组件的首选大小
-
-
-
代码示例
-
package com.hspedu.draw;import javax.swing.*;import java.awt.*;@SuppressWarnings({"all"})public class DrawCircle extends JFrame { //JFrame对应窗口,可以理解成是一个画框//定义一个面板private MyPanel mp = null;public static void main(String[] args) {new DrawCircle();System.out.println("退出程序~");}public DrawCircle() {//构造器//初始化面板mp = new MyPanel();//把面板放入到窗口(画框)this.add(mp);//设置窗口的大小this.setSize(400, 300);//当点击窗口的小×,程序完全退出.this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);//可以显示}}//1.先定义一个MyPanel, 继承JPanel类, 画图形,就在面板上画class MyPanel extends JPanel {//说明://1. MyPanel 对象就是一个画板//2. Graphics g 把 g 理解成一支画笔//3. Graphics 提供了很多绘图的方法//Graphics g@Overridepublic void paint(Graphics g) {//绘图方法super.paint(g);//调用父类的方法完成初始化.System.out.println("paint 方法被调用了~");//画出一个圆形.//g.drawOval(10, 10, 100, 100);//演示绘制不同的图形..//画直线 drawLine(int x1,int y1,int x2,int y2)//g.drawLine(10, 10, 100, 100);//画矩形边框 drawRect(int x, int y, int width, int height)//g.drawRect(10, 10, 100, 100);//画椭圆边框 drawOval(int x, int y, int width, int height)//填充矩形 fillRect(int x, int y, int width, int height)//设置画笔的颜色// g.setColor(Color.blue);// g.fillRect(10, 10, 100, 100);//填充椭圆 fillOval(int x, int y, int width, int height)// g.setColor(Color.red);// g.fillOval(10, 10, 100, 100);//画图片 drawImage(Image img, int x, int y, ..)//1. 获取图片资源, /bg.png 表示在该项目的根目录去获取 bg.png 图片资源Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/congsec.png"));g.drawImage(image, 10, 10, 568, 624, this);//画字符串 drawString(String str, int x, int y)//写字//给画笔设置颜色和字体// g.setColor(Color.red);// g.setFont(new Font("隶书", Font.BOLD, 50));//这里设置的 100, 100, 是 "北京你好"左下角// g.drawString("北京你好", 100, 100);//设置画笔的字体 setFont(Font font)//设置画笔的颜色 setColor(Color c)}}输出结果:退出程序~paint 方法被调用了~paint 方法被调用了~paint 方法被调用了~
-
输出结果
-
-
思考总结
- 这里的paint方法会自动被调用,方法里面写的是图案的信息
-
public class DrawCircle extends JFrame
中继承了JFrame
类,目的是开启一个面板(白色框框),可以设置窗口的大小,退出程序等 -
class MyPanel extends JPanel
中继承的JPanel
类可以实现自定义绘图的画板
-
事件处理机制
-
相关介绍
-
java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象,会把此”信息”传递给“事件的监听者”处理,这里所说的“信息”实际上就是java.awt.event事件类库里某个类所创建的对象,把它称为“事件的对象”。
-
-
事件源
- 事件源是一个产生事件的对象,比如按钮,窗口等。
-
事件
-
事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对象保存着当前事件很多信息,比如KeyEvent对象有含有被按下键的Code值。java.awt.event包和javax.swing.event包中定义了各种事 件类型
-
类型
-
事件类 说明 ActionEvent 通常在按下按钮、或双击一个列表项或选中某个菜单时发生。 AdjustmentEvent 当操作一个滚动条时发生。 ComponentEvent 当一个组件隐藏,移动,改变大小时发送。 ContainerEvent 当一个组件从容器中加入或者删除时发生。 FocusEvent 当一个组件获得或是失去焦点时发生。 ItemEvent 当一个复选框或是列表项被选中时,当一个选择框或选择菜单项被选中。 KeyEvent 当从键盘的按键被按下,松开时发生。 MouseEvent 当鼠标被拖动,移动,点击,按下… TextEvent 当文本区和文本域的文本发生改变时发生。 WindowEvent 当一个窗口激活,关闭,失效,恢复,最小化…
-
-
-
事件监听器接口
- 当事件源产生一个事件,可以传送给事件监听者处理
- 事件监听者实际上就是一个类,该类实现了某个事件监听器接口比如前面我们案例中的MyPanle就是一个类,它实现了
KeyListener接口,它就可以作为一个事件监听者,对接受到的事件进行处理 - 事件监听器接口有多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个监听接口
- 这些接口在java.awt.event包和javax.swing.event包中定义。 列出常用的事件监听器接口,查看jdk 文档聚集了.
-
-
-
常用方法
-
JFrame类
-
setSize(int width, int height)
: 设置窗口大小 -
add(Component comp)
: 添加组件到窗口 -
addKeyListener(KeyListener l)
: 添加键盘监听器 -
setDefaultCloseOperation(int operation)
: 设置窗口关闭操作 -
setVisible(boolean b)
: 设置窗口可见性
-
-
JPanel类
-
paint(Graphics g):
绘制面板内容
-
-
Graphics类
-
fillOval(int x, int y, int width, int height)
: 绘制并填充椭圆(在此例中用于绘制小球)
-
-
KeyListener接口
-
keyTyped(KeyEvent e)
: 当有字符输出时触发 -
keyPressed(KeyEvent e)
: 当按键被按下时触发 -
keyReleased(KeyEvent e)
: 当按键被释放时触发
-
-
KeyEvent类
-
getKeyCode()
: 获取按键的键码- 常量:VK_DOWN(向下), VK_UP(向上), VK_LEFT(向左), VK_RIGHT(向右) 等,表示特定按键的键码
-
-
JComponent类(JPanel的父类)
-
repaint()
: 请求重绘组件
-
-
-
代码示例
-
package com.hspedu.event_;import javax.swing.*;import java.awt.*;import java.awt.event.KeyEvent;import java.awt.event.KeyListener;import java.awt.event.MouseListener;import java.awt.event.WindowListener;public class BallMove extends JFrame { //窗口MyPanel mp = null;public static void main(String[] args) {BallMove ballMove = new BallMove();}//构造器public BallMove() {mp = new MyPanel();this.add(mp);this.setSize(400, 300);//窗口JFrame 对象可以监听键盘事件, 即可以监听到面板发生的键盘事件this.addKeyListener(mp);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);}}//面板, 可以画出小球//KeyListener 是监听器, 可以监听键盘事件class MyPanel extends JPanel implements KeyListener {//为了让小球可以移动, 把他的左上角的坐标(x,y)设置变量int x = 10;int y = 10;@Overridepublic void paint(Graphics g) {super.paint(g);g.fillOval(x, y, 20, 20); //默认黑色}//有字符输出时,该方法就会触发@Overridepublic void keyTyped(KeyEvent e) {}//当某个键按下,该方法会触发@Overridepublic void keyPressed(KeyEvent e) {//System.out.println((char)e.getKeyCode() + "被按下..");//根据用户按下的不同键,来处理小球的移动 (上下左右的键)//在java中,会给每一个键,分配一个值(int)if(e.getKeyCode() == KeyEvent.VK_DOWN) {//KeyEvent.VK_DOWN就是向下的箭头对应的codey++;} else if(e.getKeyCode() == KeyEvent.VK_UP) {y--;} else if(e.getKeyCode() == KeyEvent.VK_LEFT) {x--;} else if(e.getKeyCode() == KeyEvent.VK_RIGHT) {x++;}//让面板重绘this.repaint();}//当某个键释放(松开),该方法会触发@Overridepublic void keyReleased(KeyEvent e) {}}
-
思考
-
class MyPanel extends JPanel implements KeyListener
,这里实现监听接口是一直监听的状态,然后重写接口键盘检测的方法,也就是说只要有键盘输入,这个方法的所有代码会执行一遍
-
-
-
线程
-
相关介绍
-
线程
- 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
-
单线程:同一个时刻,只允许执行一个线程
-
多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
-
用户线程 也叫工作线程,当线程的任务执行完或通知方式结束
-
守护线程 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
-
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
-
-
线程使用方法
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
-
-
代码示例
-
继承Thread类,重写run方法代码示例
-
package com.hspedu.threaduse;public class Thread01 {public static void main(String[] args) throws InterruptedException {//创建Cat对象,可以当做线程使用 Cat cat = new Cat();//老韩读源码/*(1)public synchronized void start() {start0();}(2)//start0() 是本地方法,是JVM调用, 底层是c/c++实现//真正实现多线程的效果, 是start0(), 而不是 runprivate native void start0();*/cat.start();//启动线程-> 最终会执行cat的run方法//cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行//说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行//这时 主线程和子线程是交替执行..System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字mainfor(int i = 0; i < 60; i++) {System.out.println("主线程 i=" + i);//让主线程休眠Thread.sleep(1000);}}}//说明//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用//2. 我们会重写 run方法,写上自己的业务代码//3. run Thread 类 实现了 Runnable 接口的run方法/*@Overridepublic void run() {if (target != null) {target.run();}}*/class Cat extends Thread {int times = 0;@Overridepublic void run() {//重写run方法,写上自己的业务逻辑while (true) {//该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());//让该线程休眠1秒 ctrl+alt+ttry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if(times == 80) {break;//当times 到80, 退出while, 这时线程也就退出..}}}}
-
思考
-
cat类继承了extend类之后就可以把它当做一个线程了。然后重写了线程类的run方法
-
运行此线程不需要执行run方法,直接使用线程的start方法即可运行此线程
-
此时代码运行有两个线程,一个是main线程,一个是Thread-0线程,运行此代码可以发现他们的输出是交互的
-
-
这就说明了main线程和thread线程是同时发生的,同时运行的,main线程可以控制thread线程的存亡,但是main线程即使结束了,不会影响thread线程这里的逻辑可以在JConsole控制台中看出
-
-
如果不使用start方法直接使用run方法的话就会看到代码先执行完cat类的代码在回到main方法上执行,此过程只有main一个线程
-
-
-
-
实现Runnable接口,重写run方法
-
package com.hspedu.threaduse;public class Thread02 {public static void main(String[] args) {Dog dog = new Dog();//dog.start(); 这里不能调用start//创建了Thread对象,把 dog对象(实现Runnable),放入ThreadThread thread = new Thread(dog);thread.start();//模拟代理的设置模式Tiger tiger = new Tiger();//实现了 RunnableThreadProxy threadProxy = new ThreadProxy(tiger);threadProxy.start();}}class Animal {}class Tiger extends Animal implements Runnable {@Overridepublic void run() {System.out.println("老虎嗷嗷叫....");}}//线程代理类 , 模拟了一个极简的Thread类class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxyprivate Runnable target = null;//属性,类型是 Runnable@Overridepublic void run() {if (target != null) {target.run();//动态绑定(运行类型Tiger)}}public ThreadProxy(Runnable target) {this.target = target;}public void start() {start0();//这个方法时真正实现多线程方法}public void start0() {run();}}class Dog implements Runnable { //通过实现Runnable接口,开发线程int count = 0;@Overridepublic void run() { //普通方法while (true) {System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());//休眠1秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 10) {break;}}}}输出结果:小狗汪汪叫..hi1Thread-0老虎嗷嗷叫....小狗汪汪叫..hi2Thread-0小狗汪汪叫..hi3Thread-0小狗汪汪叫..hi4Thread-0小狗汪汪叫..hi5Thread-0小狗汪汪叫..hi6Thread-0小狗汪汪叫..hi7Thread-0小狗汪汪叫..hi8Thread-0小狗汪汪叫..hi9Thread-0小狗汪汪叫..hi10Thread-0
-
思考
- 通过直接实现runnable接口来达到开启线程的目的
- 但是,直接实现接口这种方式是没有start方法的,这是就需要一个代理的类来帮助他实现start方法
- 其中上面的
tiger
方法和threadProxy
类是模拟代理模式的原理,真正的代理方法是Thread
-
-
-
常用方法
-
setName 设置线程名称,使之与参数 name 相同
-
getName 返回该线程的名称
-
start 使该线程开始执行;Java 虚拟机底层调用该线程的 start0方法
-
run 调用线程象run方法;
-
setPriority 更改线程的优先级,其中MIN_PRIORITY为1,NORM_PRIORITY为5,MAX_PRIORITY为10,也给填1-10
-
getPriority 获取线程的优先级
-
sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
-
interrupt 中断线程
-
yield 线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
-
join 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
-
setDaemon 将线程设置为守护线程,
线程.setDaemon(true);
-
代码示例
-
示例一
-
package com.hspedu.method;public class ThreadMethod01 {public static void main(String[] args) throws InterruptedException {//测试相关的方法T t = new T();t.setName("老韩");t.setPriority(Thread.MIN_PRIORITY);//1t.start();//启动子线程//主线程打印5 hi ,然后我就中断 子线程的休眠for(int i = 0; i < 5; i++) {Thread.sleep(1000);System.out.println("hi " + i);}System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1t.interrupt();//当执行到这里,就会中断 t线程的休眠.}}class T extends Thread { //自定义的线程类@Overridepublic void run() {while (true) {for (int i = 0; i < 100; i++) {//Thread.currentThread().getName() 获取当前线程的名称System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);}try {System.out.println(Thread.currentThread().getName() + " 休眠中~~~");Thread.sleep(20000);//20秒} catch (InterruptedException e) {//当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码//InterruptedException 是捕获到一个中断异常.System.out.println(Thread.currentThread().getName() + "被 interrupt了");}}}}.......老韩 吃包子~~~~98老韩 吃包子~~~~99老韩 休眠中~~~hi 0hi 1hi 2hi 3hi 4老韩 线程的优先级 =1老韩被 interrupt了老韩 吃包子~~~~0老韩 吃包子~~~~1.......
-
思考
- interrupt的中断不是退出进程的意思,只是暂停进程
- setPriority方法可以填数字来设置优先级
-
-
-
示例二
-
案例:main线程创建一个子线程,每隔1s 输出 hello,输出20次,主线程每隔1秒,输出hi,输出20次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续
-
package com.hspedu.method;public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {T2 t2 = new T2();t2.start();for(int i = 1; i <= 20; i++) {Thread.sleep(1000);System.out.println("主线程(小弟) 吃了 " + i + " 包子");if(i == 5) {System.out.println("主线程(小弟) 让 子线程(老大) 先吃");//join, 线程插队//t2.join();// 这里相当于让t2 线程先执行完毕Thread.yield();//礼让,不一定成功..System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");}}}}class T2 extends Thread {@Overridepublic void run() {for (int i = 1; i <= 20; i++) {try {Thread.sleep(1000);//休眠1秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("子线程(老大) 吃了 " + i + " 包子");}}}
-
思考
- 如果使用join方法的话是强行插队,所有进程都停止让T2线程先完成
- 如果使用yield方法的话就是先让给T2执行,但是礼让不代表自己要停止,只有资源不够的话就会停止
-
-
-
示例三
-
package com.hspedu.method;public class ThreadMethod03 {public static void main(String[] args) throws InterruptedException {MyDaemonThread myDaemonThread = new MyDaemonThread();//如果我们希望当main线程结束后,子线程自动结束//,只需将子线程设为守护线程即可myDaemonThread.setDaemon(true);myDaemonThread.start();for( int i = 1; i <= 10; i++) {//main线程System.out.println("宝强在辛苦的工作...");Thread.sleep(1000);}}}class MyDaemonThread extends Thread {public void run() {for (; ; ) {//无限循环try {Thread.sleep(1000);//休眠1000毫秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");}}}输出结果:.......宝强在辛苦的工作...马蓉和宋喆快乐聊天,哈哈哈~~~马蓉和宋喆快乐聊天,哈哈哈~~~宝强在辛苦的工作...马蓉和宋喆快乐聊天,哈哈哈~~~
-
思考
- 通过
myDaemonThread.setDaemon(true);
,将线程设置为守护线程,就是守着主线程,主线程结束了,子线程也就结束
- 通过
-
-
-
-
-
线程的声明周期
-
线程状态
- NEW 尚未启动的线程处于此状态。
- RUNNABLE 在Java虚拟机中执行的线程处于此状态。
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED 己退出的线程处于此状态。
-
-
代码示例
-
package com.hspedu.state_;public class ThreadState_ {public static void main(String[] args) throws InterruptedException {T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());t.start();while (Thread.State.TERMINATED != t.getState()) {System.out.println(t.getName() + " 状态 " + t.getState());Thread.sleep(500);}System.out.println(t.getName() + " 状态 " + t.getState());}}class T extends Thread {@Overridepublic void run() {while (true) {for (int i = 0; i < 10; i++) {System.out.println("hi " + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}break;}}}输出结果:Thread-0 状态 NEWThread-0 状态 RUNNABLEhi 0Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 1Thread-0 状态 TIMED_WAITINGhi 2Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 3Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 4Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 5Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 6Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 7Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 8Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGhi 9Thread-0 状态 TIMED_WAITINGThread-0 状态 TIMED_WAITINGThread-0 状态 TERMINATED
-
思考
- 以上时线程运行原理的部分演示
-
-
-
-
多线程下的安全问题
-
介绍
- (挖洞的常用套路),根据这篇文章可以了解到并发的根本原理就是逻辑处理不当,在多线程下,如果判断条件放在前面再执行结果且条件判断与结果处理相关的话就会可能导致多个线程进入条件,其中有些线程在那时候已经不满足条件的,就会造成一些安全问题,例如超卖等
-
示例代码
-
package com.hspedu.ticket;public class SellTicket {public static void main(String[] args) {//测试// SellTicket01 sellTicket01 = new SellTicket01();// SellTicket01 sellTicket02 = new SellTicket01();// SellTicket01 sellTicket03 = new SellTicket01();//// //这里我们会出现超卖..// sellTicket01.start();//启动售票线程// sellTicket02.start();//启动售票线程// sellTicket03.start();//启动售票线程System.out.println("===使用实现接口方式来售票=====");SellTicket02 sellTicket02 = new SellTicket02();new Thread(sellTicket02).start();//第1个线程-窗口new Thread(sellTicket02).start();//第2个线程-窗口new Thread(sellTicket02).start();//第3个线程-窗口}}//使用Thread方式class SellTicket01 extends Thread {private static int ticketNum = 100;//让多个线程共享 ticketNum@Overridepublic void run() {while (true) {if (ticketNum <= 0) {System.out.println("售票结束...");break;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));}}}//实现接口方式class SellTicket02 implements Runnable {private int ticketNum = 100;//让多个线程共享 ticketNum@Overridepublic void run() {while (true) {if (ticketNum <= 0) {System.out.println("售票结束...");break;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2}}}
-
输出结果
-
-
-
修复方法
- 只需要将
public void run()
代码改为public synchronized void run()
即可,这个原理就是只能通过一个线程执行该方法
- 只需要将
-
-
线程同步机制
-
介绍
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
-
同步具体方法
-
同步代码块
- synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码;
- synchronized(对象){//得到对象的锁,才能操作同步代码
-
sysnchronized还可以同步放在方法声明中,表示整个方法为同步方法
- public synchronized void m (String name){
//需要被同步的代码
}
- public synchronized void m (String name){
-
-
示例
- 示例一
-
-
互斥锁
-
介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只
能有一个线程访问该对象。 - 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身。
-
注意细节
-
同步方法如果没有使用static修饰:默认锁对象为this
-
如果方法使用static修饰,默认锁对象:当前类.class
-
实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可!
-
-
代码示例
-
package com.hspedu.syn;public class SellTicket {public static void main(String[] args) {//测试// SellTicket01 sellTicket01 = new SellTicket01();// SellTicket01 sellTicket02 = new SellTicket01();// SellTicket01 sellTicket03 = new SellTicket01();//// //这里我们会出现超卖..// sellTicket01.start();//启动售票线程// sellTicket02.start();//启动售票线程// sellTicket03.start();//启动售票线程// System.out.println("===使用实现接口方式来售票=====");// SellTicket02 sellTicket02 = new SellTicket02();//// new Thread(sellTicket02).start();//第1个线程-窗口// new Thread(sellTicket02).start();//第2个线程-窗口// new Thread(sellTicket02).start();//第3个线程-窗口//测试一把SellTicket03 sellTicket03 = new SellTicket03();new Thread(sellTicket03).start();//第1个线程-窗口new Thread(sellTicket03).start();//第2个线程-窗口new Thread(sellTicket03).start();//第3个线程-窗口}}//实现接口方式, 使用synchronized实现线程同步class SellTicket03 implements Runnable {private int ticketNum = 100;//让多个线程共享 ticketNumprivate boolean loop = true;//控制run方法变量Object object = new Object();//同步方法(静态的)的锁为当前类本身//老韩解读//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class//2. 如果在静态方法中,实现一个同步代码块./*synchronized (SellTicket03.class) {System.out.println("m2");}*/public synchronized static void m1() {}public static void m2() {synchronized (SellTicket03.class) {System.out.println("m2");}}//老韩说明//1. public synchronized void sell() {} 就是一个同步方法//2. 这时锁在 this对象//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法synchronized (/*this*/ object) {if (ticketNum <= 0) {System.out.println("售票结束...");loop = false;return;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2}}@Overridepublic void run() {while (loop) {sell();//sell方法是一共同步方法}}}//使用Thread方式// new SellTicket01().start()// new SellTicket01().start();class SellTicket01 extends Thread {private static int ticketNum = 100;//让多个线程共享 ticketNum// public void m1() {// synchronized (this) {// System.out.println("hello");// }// }@Overridepublic void run() {while (true) {if (ticketNum <= 0) {System.out.println("售票结束...");break;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));}}}//实现接口方式class SellTicket02 implements Runnable {private int ticketNum = 100;//让多个线程共享 ticketNum@Overridepublic void run() {while (true) {if (ticketNum <= 0) {System.out.println("售票结束...");break;}//休眠50毫秒, 模拟try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2}}}
-
思考
-
同步锁有两种方式
- 第一种是在代码块中上锁
- 第二种是在方法上上锁
-
在代码块中上锁可以直接
synchronized(this){同步代码}
这种方式,也可以先Object object = new Object();
,在synchronized(object){同步代码}
这种方式,这两种方式需要注意的是传入的对象需要同一个对象才行,其次注意的是如果代码块放在静态放在静态方法内的话代码块需要直接调用类名,即synchronized (SellTicket03.class)
-
也可以在方法中声明,public synchronized void m (String name)
-
-
-
-
-
线程死锁
-
介绍
- 多个线程都占用了对方的锁资源,但但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
- 例如,妈妈:你先完成作业,才让你玩手机,小明:你先让我玩手机,我才完成作业.
-
代码示例
-
package com.hspedu.syn;public class DeadLock_ {public static void main(String[] args) {//模拟死锁现象DeadLockDemo A = new DeadLockDemo(true);A.setName("A线程");DeadLockDemo B = new DeadLockDemo(false);B.setName("B线程");A.start();B.start();}}//线程class DeadLockDemo extends Thread {static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用staticstatic Object o2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {//构造器this.flag = flag;}@Overridepublic void run() {//下面业务逻辑的分析//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁//2. 如果线程A 得不到 o2 对象锁,就会Blocked//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁//4. 如果线程B 得不到 o1 对象锁,就会Blockedif (flag) {synchronized (o1) {//对象互斥锁, 下面就是同步代码System.out.println(Thread.currentThread().getName() + " 进入1");synchronized (o2) { // 这里获得li对象的监视权System.out.println(Thread.currentThread().getName() + " 进入2");}}} else {synchronized (o2) {System.out.println(Thread.currentThread().getName() + " 进入3");synchronized (o1) { // 这里获得li对象的监视权System.out.println(Thread.currentThread().getName() + " 进入4");}}}}}
-
思考
-
死锁产生的关键点
- 两个静态对象
o1
和o2
作为锁。 - 两个线程 A 和 B 以不同的顺序尝试获取这两个锁。
- 两个静态对象
-
死锁发生的原因:
- 如果线程 A 获取了
o1
的锁,同时线程 B 获取了o2
的锁 - 线程 A 会等待
o2
的锁被释放 - 线程 B 会等待
o1
的锁被释放 - 两个线程互相等待对方释放锁,但都不会释放自己持有的锁,从而形成死锁
- 如果线程 A 获取了
-
-
-
-
-
释放锁
-
介绍
-
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
-
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方 法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和re?sume()来控制线程,方法不再推荐使用
-
-
IO流
-
相关介绍
-
文件流
- 流:数据在数据源(文件)和程序(内存)之间经历的路径
- 输入流:数据聪数据源(文件)到程序(内存)的路径
- 输出流:数据从程序(内存)到数据源(文件)的路径
-
-
-
流的分类
-
- 按操作数据单位不同分为:字节流(8bit)二进制文件,字符流(按字符)文本文件
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
-
IO流的常用类
-
字节流
-
FileInputStream–读取文件
-
类关系图
-
-
常用方法
-
单字节读取
-
fileInputStream.read()
: 读取单个字节,返回 int 值(0-255)或 -1(文件结束)
-
-
多字节读取
-
fileInputStream.read(byte[] buf)
: 读取多个字节到缓冲数组,返回实际读取的字节数或 -1
-
-
-
代码示例
-
package com.hspedu.inputstream_;import org.junit.jupiter.api.Test;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileInputStream_ {public static void main(String[] args) {FileInputStream_ fileInputStream_ = new FileInputStream_();fileInputStream_.readFile01();fileInputStream_.readFile02();}/*** 演示读取文件...* 单个字节的读取,效率比较低* -> 使用 read(byte[] b)*/public void readFile01() {String filePath = "e:\\hello.txt";int readData = 0;FileInputStream fileInputStream = null;try {//创建 FileInputStream 对象,用于读取 文件fileInputStream = new FileInputStream(filePath);//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。//如果返回-1 , 表示读取完毕while ((readData = fileInputStream.read()) != -1) {System.out.print((char)readData);//转成char显示}} catch (IOException e) {e.printStackTrace();} finally {//关闭文件流,释放资源.try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}/*** 使用 read(byte[] b) 读取文件,提高效率*/public void readFile02() {String filePath = "e:\\hello.txt";//字节数组byte[] buf = new byte[8]; //一次读取8个字节.int readLen = 0;System.out.println("");FileInputStream fileInputStream = null;try {//创建 FileInputStream 对象,用于读取 文件fileInputStream = new FileInputStream(filePath);//从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。//如果返回-1 , 表示读取完毕//如果读取正常, 返回实际读取的字节数while ((readLen = fileInputStream.read(buf)) != -1) {System.out.print(new String(buf, 0, readLen));//显示}} catch (IOException e) {e.printStackTrace();} finally {//关闭文件流,释放资源.try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}}//hello,word//hello,word
-
思考
- 字节流读取文件有两种方式,一个是一个一个字节读取,一个是8个字节的读取
-
fileInputStream = new FileInputStream(filePath);
,这段代码的意思是将文件读取放进这个对象里,然后在通过read
函数来读取这个对象,例如只有一个字节的readData
就会每次赋予其一个数,而有8个字节存放空间的readLen
变量就每次尽量塞满,不够的8个剩余的就是之前未改变的字符,最后用string函数输出出来,即System.out.print(new String(buf, 0, readLen));
-
-
-
-
OutputStream–写入文件
-
类体系图
-
-
常用方法
-
FileOutputStream 创建
-
new FileOutputStream(filePath)
: 创建文件输出流,覆盖原有内容 -
new FileOutputStream(filePath, true)
: 创建文件输出流,追加内容
-
-
写入单个字节
-
fileOutputStream.write('H')
: 写入单个字符(作为字节)
-
-
写入字节数组
-
fileOutputStream.write(str.getBytes())
: 将字符串转换为字节数组并写入
-
-
写入字节数组的一部分
-
fileOutputStream.write(byte[] b, int off, int len)
: 写入字节数组的指定部分
-
-
字符串转字节数组
-
str.getBytes()
: 将字符串转换为字节数组
-
-
-
代码示例
- 请使用FileOutputStream 在 a.txt 文件,中写入 “hello,world”.[老师代码演示], 如果文件不存在,会创建 文件(注意:前提是目录已经存在.)
-
package com.hspedu.outputstream_;import org.junit.jupiter.api.Test;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class FileOutputStream01 {public static void main(String[] args) {}/*** 演示使用FileOutputStream 将数据写到文件中,* 如果该文件不存在,则创建该文件*/@Testpublic void writeFile() {//创建 FileOutputStream对象String filePath = "e:\\a.txt";FileOutputStream fileOutputStream = null;try {//得到 FileOutputStream对象 对象//老师说明//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面fileOutputStream = new FileOutputStream(filePath, true);//写入一个字节//fileOutputStream.write('H');////写入字符串String str = "cong,world!";//str.getBytes() 可以把 字符串-> 字节数组//fileOutputStream.write(str.getBytes());/*write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流*/fileOutputStream.write(str.getBytes(), 0, 4);} catch (IOException e) {e.printStackTrace();} finally {try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}}
-
-
综合代码示例
-
编程完成图片/音乐的拷贝
-
package com.hspedu.outputstream_;import com.hspedu.inputstream_.FileInputStream_;import java.io.*;public class FileCopy {public static void main(String[] args) {//完成 文件拷贝,将 e:\\Koala.jpg 拷贝 c:\\//思路分析//1. 创建文件的输入流 , 将文件读入到程序//2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件.String srcFilePath = "e:\\congsec.png";String destFilePath = "e:\\congsec2.png";FileInputStream fileInputStream = null;FileOutputStream fileOutputStream = null;try {fileInputStream = new FileInputStream(srcFilePath);fileOutputStream = new FileOutputStream(destFilePath);//定义一个字节数组,提高读取效果byte[] buf = new byte[1024];int readLen = 0;while ((readLen = fileInputStream.read(buf)) != -1) {//读取到后,就写入到文件 通过 fileOutputStream//即,是一边读,一边写fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法}System.out.println("拷贝ok~");} catch (IOException e) {e.printStackTrace();} finally {try {//关闭输入流和输出流,释放资源if (fileInputStream != null) {fileInputStream.close();}if (fileOutputStream != null) {fileOutputStream.close();}} catch (IOException e) {e.printStackTrace();}}}}//拷贝ok~
-
思考
- 自理拷贝文件是采用一边读一边写
- 写文件需要注意的是
fileOutputStream.write(buf, 0, readLen);
后面的结尾需要用readLen变量,因为图片可能字节数不能被8整除
-
-
-
-
字符流
-
FileReader–读取文件
-
类关系图
-
-
常用方法
-
new FileReader(File/String)
-
read:每次读取单个字符,返回该字符,如果到文件末尾返回—1
-
read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回—1
-
相关API:
- new String(char[]):将char[]转换成String
- new String(char[],off,len):将char[]的指定部分转换成String
-
-
代码示例
-
package com.hspedu.reader_;import org.junit.jupiter.api.Test;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileReader_ {public static void main(String[] args) {FileReader_ fileReader_ = new FileReader_();fileReader_.readFile01();fileReader_.readFile02();}/*** 单个字符读取文件*/public void readFile01() {String filePath = "e:\\cong.txt";FileReader fileReader = null;int data = 0;//1. 创建FileReader对象try {fileReader = new FileReader(filePath);//循环读取 使用read, 单个字符读取while ((data = fileReader.read()) != -1) {System.out.print((char) data);}System.out.println("");} catch (IOException e) {e.printStackTrace();} finally {try {if (fileReader != null) {fileReader.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 字符数组读取文件*/public void readFile02() {System.out.println("~~~readFile02 ~~~");String filePath = "e:\\cong.txt";FileReader fileReader = null;int readLen = 0;char[] buf = new char[8];//1. 创建FileReader对象try {fileReader = new FileReader(filePath);//循环读取 使用read(buf), 返回的是实际读取到的字符数//如果返回-1, 说明到文件结束while ((readLen = fileReader.read(buf)) != -1) {System.out.print(new String(buf, 0, readLen));}} catch (IOException e) {e.printStackTrace();} finally {try {if (fileReader != null) {fileReader.close();}} catch (IOException e) {e.printStackTrace();}}}}输出结果:测试文档,congsec!!!~~~readFile02 ~~~测试文档,congsec!!!
-
-
-
FileWriter–写入文件
-
类关系图
-
-
相关方法
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[],off,len):写入指定数组的指定部分
- write(string):入整个字符
- write(string,off,len):写入字符串的指定部分
相关API:String类:toCharArray:将String转换成char[] - 注意:FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
-
代码示例
-
package com.hspedu.writer_;import java.io.FileWriter;import java.io.IOException;public class FileWriter_ {public static void main(String[] args) {String filePath = "e:\\note.txt";//创建FileWriter对象FileWriter fileWriter = null;char[] chars = {'a', 'b', 'c'};try {fileWriter = new FileWriter(filePath);//默认是覆盖写入// 3) write(int):写入单个字符fileWriter.write('H');// 4) write(char[]):写入指定数组fileWriter.write(chars);// 5) write(char[],off,len):写入指定数组的指定部分fileWriter.write("CongSec".toCharArray(), 0, 3);// 6) write(string):写入整个字符串fileWriter.write(" 你好北京~");fileWriter.write("风雨之后,定见彩虹");// 7) write(string,off,len):写入字符串的指定部分fileWriter.write("上海天津", 0, 2);//在数据量大的情况下,可以使用循环操作.} catch (IOException e) {e.printStackTrace();} finally {//对应FileWriter , 一定要关闭流,或者flush才能真正的把数据写入到文件//老韩看源码就知道原因./*看看代码private void writeBytes() throws IOException {this.bb.flip();int var1 = this.bb.limit();int var2 = this.bb.position();assert var2 <= var1;int var3 = var2 <= var1 ? var1 - var2 : 0;if (var3 > 0) {if (this.ch != null) {assert this.ch.write(this.bb) == var3 : var3;} else {this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);}}this.bb.clear();}*/try {//fileWriter.flush();//关闭文件流,等价 flush() + 关闭fileWriter.close();} catch (IOException e) {e.printStackTrace();}}System.out.println("程序结束...");}}//程序结束...
-
-
-
-
-
节点流与处理流
-
相关介绍
-
分类 字节输入流 字节输出流 字符输入流 字符输出流 流的分类 抽象基类 InputStream OutputStream Reader Writer 访问文件 FileInputStream FileOutputStream FileReader FileWriter 节点流 访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 节点流 访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter 节点流 访问字符串 StringReader StringWriter 节点流 缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter 处理流 转换流 InputStreamReader OutputStreamWriter 处理流 对象流 ObjectInputStream ObjectOutputStream 处理流 抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter 处理流 打印流 PrintStream PrintWriter 处理流 推回输入流 PushbackInputStream PushbackReader 处理流 特殊流 DataInputStream DataOutputStream 处理流 -
节点流与处理流的区别与联系
- 节点流是底层流/低级流,直接跟数据源相接。
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连[模拟修饰器设计模式]
-
处理流的底层源码分析
-
以BufferedReader源码如下
- public class BufferedReader extends Reader;
private Reader in;
private char cb[];
- public class BufferedReader extends Reader;
-
可以看到,他有一个私有类的属性和继承Reader这个类,说明可以接受一个reader子类的特性,也就是说通过这一个特性,他可以达到既可以访问文件也可以访问数组的目的
-
-
处理流的功能
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
-
-
BufferedReader–读取文件
-
代码示例
-
package com.hspedu.reader_;import java.io.BufferedReader;import java.io.FileReader;public class BufferedReader_ {public static void main(String[] args) throws Exception {String filePath = "e:\\cong.txt";//创建bufferedReaderBufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));//读取String line; //按行读取, 效率高//说明//1. bufferedReader.readLine() 是按行读取文件//2. 当返回null 时,表示文件读取完毕while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流//FileReader。/*public void close() throws IOException {synchronized (lock) {if (in == null)return;try {in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了.} finally {in = null;cb = null;}}}*/bufferedReader.close();}}输出结果:第一行:测试文档,congsec!!!第二行:测试文档,congsec!!!
-
思考
-
bufferedReader
是按行读取的,效率较高 - 关闭处理流自动会关闭字节流
-
-
-
-
-
BufferedWriter–写入文件
-
代码示例
-
package com.hspedu.writer_;import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException;public class BufferedWriter_ {public static void main(String[] args) throws IOException {String filePath = "e:\\ok.txt";//创建BufferedWriter//说明://1. new FileWriter(filePath, true) 表示以追加的方式写入//2. new FileWriter(filePath) , 表示以覆盖的方式写入BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));bufferedWriter.write("hello, 韩顺平教育!");bufferedWriter.newLine();//插入一个和系统相关的换行bufferedWriter.write("hello2, 韩顺平教育!");bufferedWriter.newLine();bufferedWriter.write("hello3, 韩顺平教育!");bufferedWriter.newLine();//说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭bufferedWriter.close();}}
-
-
-
BufferedCopy–复制文件
-
代码示例
-
package com.hspedu.writer_;import java.io.*;public class BufferedCopy_ {public static void main(String[] args) {//说明//1. BufferedReader 和 BufferedWriter 是安装字符操作//2. 不要去操作 二进制文件[声音,视频,doc, pdf ], 可能造成文件损坏//BufferedInputStream//BufferedOutputStreamString srcFilePath = "e:\\cong.txt";String destFilePath = "e:\\cong2.txt";// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";// String destFilePath = "e:\\a2韩顺平.avi";BufferedReader br = null;BufferedWriter bw = null;String line;try {br = new BufferedReader(new FileReader(srcFilePath));bw = new BufferedWriter(new FileWriter(destFilePath));//说明: readLine 读取一行内容,但是没有换行while ((line = br.readLine()) != null) {//每读取一行,就写入bw.write(line);//插入一个换行bw.newLine();}System.out.println("拷贝完毕...");} catch (IOException e) {e.printStackTrace();} finally {//关闭流try {if(br != null) {br.close();}if(bw != null) {bw.close();}} catch (IOException e) {e.printStackTrace();}}}}
-
-
-
代码示例
-
package com.hspedu.outputstream_;import java.io.*;/*** 演示使用BufferedOutputStream 和 BufferedInputStream使用* 使用他们,可以完成二进制文件拷贝.* 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以*/public class BufferedCopy02 {public static void main(String[] args) {// String srcFilePath = "e:\\Koala.jpg";// String destFilePath = "e:\\hsp.jpg";// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";// String destFilePath = "e:\\hsp.avi";String srcFilePath = "e:\\congsec.png";String destFilePath = "e:\\congsec2.png";//创建BufferedOutputStream对象BufferedInputStream对象BufferedInputStream bis = null;BufferedOutputStream bos = null;try {//因为 FileInputStream 是 InputStream 子类bis = new BufferedInputStream(new FileInputStream(srcFilePath));bos = new BufferedOutputStream(new FileOutputStream(destFilePath));//循环的读取文件,并写入到 destFilePathbyte[] buff = new byte[1024];int readLen = 0;//当返回 -1 时,就表示文件读取完毕while ((readLen = bis.read(buff)) != -1) {bos.write(buff, 0, readLen);}System.out.println("文件拷贝完毕~~~");} catch (IOException e) {e.printStackTrace();} finally {//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流try {if(bis != null) {bis.close();}if(bos != null) {bos.close();}} catch (IOException e) {e.printStackTrace();}}}}
-
-
对象流
-
相关介绍
-
ObjectOutputStream 提供 序列化功能
-
-
ObjectInputStream 提供 反序列化功能
-
-
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该
-
类必须实现如下两个接口之一:
- Serializable/这是一个标记接口,没有方法
- Externalizable//该接口有方法需要实现,因此我们一般实现上面的Serializable接口
-
-
常用方法
-
序列化
-
writeInt(int v)
: 写入一个int值 -
writeBoolean(boolean v)
: 写入一个boolean值 -
writeChar(char v)
: 写入一个char值 -
writeDouble(double v)
: 写入一个double值 -
writeUTF(String str)
: 写入一个UTF-8编码的字符串 -
writeObject(Object obj)
: 写入一个对象
-
-
反序列化
-
readInt()
: 读取一个int值 -
readBoolean()
: 读取一个boolean值 -
readChar()
: 读取一个char值 -
readDouble()
: 读取一个double值 -
readUTF()
: 读取一个UTF-8编码的字符串 -
readObject()
: 读取一个对象(需要强制类型转换)
-
-
-
注意事项
- 读写顺序要一致
- 要求序列化或反序列化对象,需要实现Serializable
- 序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
- 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
-
代码示例
-
需求:模拟序列化传输,反序列化出来
-
序列化示例
-
ObjectOutStream_.javapackage com.hspedu.outputstream_;import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class ObjectOutStream_ {public static void main(String[] args) throws Exception {//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存String filePath = "e:\\data.dat";ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));//序列化数据到 e:\data.datoos.writeInt(100);// int -> Integer (实现了 Serializable)oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)oos.writeChar('a');// char -> Character (实现了 Serializable)oos.writeDouble(9.5);// double -> Double (实现了 Serializable)oos.writeUTF("CongSec");//String//保存一个dog对象oos.writeObject(new Dog("旺财", 10, "日本", "白色"));oos.close();System.out.println("数据保存完毕(序列化形式)");}}
-
-
其余类代码
-
Dog.javapackage com.hspedu.outputstream_;import java.io.Serializable;//如果需要序列化某个类的对象,实现 Serializablepublic class Dog implements Serializable {private String name;private int age;//序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员private static String nation;private transient String color;//序列化对象时,要求里面属性的类型也需要实现序列化接口private Master master = new Master();//serialVersionUID 序列化的版本号,可以提高兼容性private static final long serialVersionUID = 1L;public Dog(String name, int age, String nation, String color) {this.name = name;this.age = age;this.color = color;this.nation = nation;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +", color='" + color + '\'' +'}' + nation + " " +master;}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;}}==============================================================================package com.hspedu.outputstream_;import java.io.Serializable;public class Master implements Serializable {}
-
-
反序列化代码
-
ObjectInputStream_.javapackage com.hspedu.inputstream_;import com.hspedu.outputstream_.Dog;import java.io.*;public class ObjectInputStream_ {public static void main(String[] args) throws IOException, ClassNotFoundException {//指定反序列化的文件String filePath = "e:\\data.dat";ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));//读取//解读//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致//2. 否则会出现异常System.out.println(ois.readInt());System.out.println(ois.readBoolean());System.out.println(ois.readChar());System.out.println(ois.readDouble());System.out.println(ois.readUTF());//dog 的编译类型是 Object , dog 的运行类型是 DogObject dog = ois.readObject();System.out.println("运行类型=" + dog.getClass());System.out.println("dog信息=" + dog);//底层 Object -> Dog//这里是特别重要的细节://1. 如果我们希望调用Dog的方法, 需要向下转型//2. 需要我们将Dog类的定义,放在到可以引用的位置Dog dog2 =(Dog)dog;System.out.println("==================");System.out.println(dog2.getName()); //旺财..//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流ois.close();}}输出结果:100truea9.5CongSec运行类型=class com.hspedu.outputstream_.Dogdog信息=Dog{name='旺财', age=10, color='null'}null com.hspedu.outputstream_.Master@7b23ec81==================旺财
-
-
思考
- 使用
ObjectOutputStream
方法来序列化数据并保存为字符流文件,可以保存数据类型,对象等信息 - 反序列化是需要按顺序进行反序列化,不能调换顺序
-
Object dog = ois.readObject();
,这段代码接受了对象流的Dog对象,如果我们想要调用其方法的话,首先需要导入dog类,然后向下转型,因为上面的代码的编译类型是Object
- 使用
-
-
-
标准输入输出流
-
转换流
-
相关介绍
- InputStreamReader:Reader的子类,可以将InputStream(字节流)包 装成(转换)Reader(字符流)
- OutputStreamWriter:Writer的子类,实现将OutputStream(字节流) 包装成Writer(字符流)
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf—8,gbk,gb2312,ISO8859—1等)
-
代码示例
-
代码示例一
- 编程将 字节流FilelnputStream 包装成(转换成) 字符流InputStreamReader, 对文件进行读取(按照 utf—8/gbk 格式),进而在包装成 BufferedReader
-
package com.hspedu.transformation;import java.io.*;/*** 演示使用 InputStreamReader 转换流解决中文乱码问题* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8*/public class InputStreamReader_ {public static void main(String[] args) throws IOException {String filePath = "e:\\a.txt";//解读//1. 把 FileInputStream 转成 InputStreamReader//2. 指定编码 gbk//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");//3. 把 InputStreamReader 传入 BufferedReader//BufferedReader br = new BufferedReader(isr);//将2 和 3 合在一起BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));//4. 读取String s = br.readLine();System.out.println("读取内容=" + s);//5. 关闭外层流br.close();}}//读取内容=congsec,测试文档
-
代码示例二
- 编程将字节流 FileOutputStream 包装成(转换成)字符流OutputStreamWriter, 对文件进行写入(按照gbk格式,可以指定其他,比如utf—8)
-
package com.hspedu.transformation;import java.io.*;/*** 演示 OutputStreamWriter 使用* 把FileOutputStream 字节流,转成字符流 OutputStreamWriter* 指定处理的编码 gbk/utf-8/utf8*/public class OutputStreamWriter_ {public static void main(String[] args) throws IOException {String filePath = "e:\\hsp.txt";String charSet = "utf-8";OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);osw.write("hi, CongSec");osw.close();System.out.println("按照 " + charSet + " 保存文件成功~");}}
-
-
-
打印流
-
类体系图
-
-
-
代码示例
-
PrintStream演示
-
package com.hspedu.printstream; import java.io.IOException;import java.io.PrintStream;public class PrintStream_ {public static void main(String[] args) throws IOException {PrintStream out = System.out;//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器/*public void print(String s) {if (s == null) {s = "null";}write(s);}*/out.print("john, hello");//因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出out.write("CongSec,你好".getBytes());out.close();//我们可以去修改打印流输出的位置/设备//1. 输出修改成到 "e:\\f1.txt"//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt//3. public static void setOut(PrintStream out) {// checkIO();// setOut0(out); // native 方法,修改了out// }System.setOut(new PrintStream("e:\\f1.txt"));System.out.println("hello, CongSec~");}}//john, helloCongSec,你好
-
思考
-
PrintStream out = System.out;
,out.print("john, hello");
这个的底层原理是调用了标准的输出流 - System.out.print(“1231321”)与System.out.write(“1231321”.getBytes());的调用机制是一样的
-
System.setOut(new PrintStream("e:\\f1.txt"));
,标准输出流的默认输出是屏幕,这里设置到一个文件路径,就会输出成一个文件
-
-
-
-
PrintWriter演示
-
package com.hspedu.transformation;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;/*** 演示 PrintWriter 使用方式*/public class PrintWriter_ {public static void main(String[] args) throws IOException {//PrintWriter printWriter = new PrintWriter(System.out);PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));printWriter.print("hi, 北京你好~~~~");printWriter.close();//flush + 关闭流, 才会将数据写入到文件..}}
-
-
-
-
Properties 类
-
介绍
-
专门用于读写配置文件的集合类,默认类型是String
-
配置文件的格式:
- 键=值
- 键=值
-
-
-
常用方法
- load:加载配置文件的键值对到Properties对象
- list:将数据显示到指定设备
- getProperty(key) :根据键获取值
- setProperty(key,value) :设置键值对到Properties对象
- store(Writer,String) :将Properties中的键值对存储到配置文件,在idea 中,保存信息到配置文件,如果含有中文,会存储为unicode码
-
代码示例
-
查看文件的键值对
-
package com.hspedu.properties_;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.util.Properties;public class Properties02 {public static void main(String[] args) throws IOException {//使用Properties 类来读取mysql.properties 文件//1. 创建Properties 对象Properties properties = new Properties();//2. 加载指定配置文件properties.load(new FileReader("src\\mysql.properties"));//3. 把k-v显示控制台properties.list(System.out);//4. 根据key 获取对应的值String user = properties.getProperty("user");String pwd = properties.getProperty("pwd");System.out.println("用户名=" + user);System.out.println("密码是=" + pwd);}}输出结果:-- listing properties --user=rootpwd=12345ip=192.168.100.100用户名=root密码是=12345
-
-
写入修改文件的键值对
-
package com.hspedu.properties_;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.util.Properties;public class Properties03 {public static void main(String[] args) throws IOException {//使用Properties 类来创建 配置文件, 修改配置文件内容Properties properties = new Properties();//创建//1.如果该文件没有key 就是创建//2.如果该文件有key ,就是修改/*Properties 父类是 Hashtable , 底层就是Hashtable 核心方法public synchronized V put(K key, V value) {// Make sure the value is not nullif (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];for(; entry != null ; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;//如果key 存在,就替换return old;}}addEntry(hash, key, value, index);//如果是新k, 就addEntryreturn null;}*/properties.setProperty("charset", "utf8");properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值properties.setProperty("pwd", "888888");//将k-v 存储文件中即可properties.store(new FileOutputStream("src\\mysql2.properties"), null);System.out.println("保存配置文件成功~");}}输出结果:#Sun Oct 20 23:35:17 CST 2024user=\u6C64\u59C6pwd=888888charset=utf8
-
-
-
-
-
文件创建
-
常用方法
- new File(String pathname) /根据路径构建一个File对象
- new File(File parent,String child)/根据父目录文件+子路径构建
- new File(String parent,String child) //根据父目录+子路径构建
-
代码示例
-
package com.hspedu.file;import org.junit.jupiter.api.Test;import java.io.File;import java.io.InputStream;import java.io.OutputStream;/*** @author 韩顺平* @version 1.0*/public class Directory_ {public static void main(String[] args) {//}//判断 d:\\news1.txt 是否存在,如果存在就删除@Testpublic void m1() {String filePath = "e:\\news1.txt";File file = new File(filePath);if (file.exists()) {if (file.delete()) {System.out.println(filePath + "删除成功");} else {System.out.println(filePath + "删除失败");}} else {System.out.println("该文件不存在...");}}//判断 D:\\demo02 是否存在,存在就删除,否则提示不存在//这里我们需要体会到,在java编程中,目录也被当做文件@Testpublic void m2() {String filePath = "D:\\demo02";File file = new File(filePath);if (file.exists()) {if (file.delete()) {System.out.println(filePath + "删除成功");} else {System.out.println(filePath + "删除失败");}} else {System.out.println("该目录不存在...");}}//判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建@Testpublic void m3() {String directoryPath = "D:\\demo\\a\\b\\c";File file = new File(directoryPath);if (file.exists()) {System.out.println(directoryPath + "存在..");} else {if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()System.out.println(directoryPath + "创建成功..");} else {System.out.println(directoryPath + "创建失败...");}}}}输出结果:123文件创建成功创建成功~创建成功~
-
-
-
查看文件相关信息
-
常用方法
- 文件名:使用
file.getName()
方法 - 文件的绝对路径:使用
file.getAbsolutePath()
方法 - 文件的父级目录:使用
file.getParent()
方法 - 文件大小(以字节为单位):使用
file.length()
方法 - 文件是否存在:使用
file.exists()
方法 - 是否为文件:使用
file.isFile()
方法 - 是否为目录:使用
file.isDirectory()
方法
- 文件名:使用
-
代码示例
-
package com.hspedu.file;import org.junit.jupiter.api.Test;import java.io.File;public class FileInformation {public static void main(String[] args) {FileInformation fileInformation=new FileInformation();fileInformation.info();}//获取文件的信息@Testpublic void info() {//先创建文件对象File file = new File("e:\\news1.txt");//调用相应的方法,得到对应信息System.out.println("文件名字=" + file.getName());//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectorySystem.out.println("文件绝对路径=" + file.getAbsolutePath());System.out.println("文件父级目录=" + file.getParent());System.out.println("文件大小(字节)=" + file.length());System.out.println("文件是否存在=" + file.exists());//TSystem.out.println("是不是一个文件=" + file.isFile());//TSystem.out.println("是不是一个目录=" + file.isDirectory());//F}}输出结果:文件名字=news1.txt文件绝对路径=e:\news1.txt文件父级目录=e:\文件大小(字节)=7文件是否存在=true是不是一个文件=true是不是一个目录=false
-
-
-
文件操作
-
常用方法
-
文件存在性检查
-
File.exists()
: 检查文件或目录是否存在
-
-
文件删除
-
File.delete()
: 删除文件或空目录
-
-
目录创建
-
File.mkdir()
: 创建单层目录 -
File.mkdirs()
: 创建多层目录结构
-
-
-
代码示例
-
package com.hspedu.file;import org.junit.jupiter.api.Test;import java.io.File;import java.io.InputStream;import java.io.OutputStream;public class Directory_ {public static void main(String[] args) {Directory_ directory_ = new Directory_();directory_.m1();directory_.m2();directory_.m3();}//判断 d:\\news1.txt 是否存在,如果存在就删除@Testpublic void m1() {String filePath = "e:\\news1.txt";File file = new File(filePath);if (file.exists()) {if (file.delete()) {System.out.println(filePath + "删除成功");} else {System.out.println(filePath + "删除失败");}} else {System.out.println("该文件不存在...");}}//判断 D:\\demo02 是否存在,存在就删除,否则提示不存在//这里我们需要体会到,在java编程中,目录也被当做文件@Testpublic void m2() {String filePath = "D:\\demo02";File file = new File(filePath);if (file.exists()) {if (file.delete()) {System.out.println(filePath + "删除成功");} else {System.out.println(filePath + "删除失败");}} else {System.out.println("该目录不存在...");}}//判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建@Testpublic void m3() {String directoryPath = "D:\\demo\\a\\b\\c";File file = new File(directoryPath);if (file.exists()) {System.out.println(directoryPath + "存在..");} else {if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()System.out.println(directoryPath + "创建成功..");} else {System.out.println(directoryPath + "创建失败...");}}}}输出结果:e:\news1.txt删除成功D:\demo02删除成功D:\demo\a\b\c创建成功..
-
-