Java学习笔记(第15章 P554-P568)(第16章P569-P579)(第17章P580-P599)(第18章P600-P610)(第19章P611-P644)(第20章P645-P661)
第15章 泛型
泛型的理解和好处
- 案例引出泛型
案例(传统方式解决):
package com.hspedu.generic;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Generic01 {
public static void main(String[] args) {
//使用传统的方法来解决
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
//假如我们的程序员, 不小心, 添加了一只猫
arrayList.add(new Cat("招财猫", 8));
//遍历
for (Object o : arrayList) {
//向下转型Object -> Dog
Dog dog = (Dog) o;
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
/*
请编写程序,在ArrayList 中,添加3个Dog对象
Dog对象含有name 和 age, 并输出name 和 age (要求使用getXxx())
*/
class Dog{
private String name;
private int age;
public Dog(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;
}
}
class Cat{ //Cat类
private String name;
private int age;
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;
}
}
结果:
程序出现类型转换的错误
- 使用传统方法的问题分析
- 不能对加入到集合ArrayList中的数据类型进行约束(不安全)
2)遍历的时候, 需要进行类型转换, 如果集合中的数据量较大,对效率有影响
- 用泛型来解决前一案例
package com.hspedu.generic.improve;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Generic02 {
public static void main(String[] args) {
//使用传统的方法来解决 ====>使用泛型
//老韩解读
//1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是Dog类型 (细节后面说...)
//2. 如果编译器发现添加的类型, 不满足要求, 就会报错
//3. 在遍历的时候, 可以直接取出 Dog类型而不是 Object
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
//假如我们的程序员, 不小心, 添加了一只猫
//arrayList.add(new Cat("招财猫", 8));
System.out.println("===使用泛型===");
for (Dog dog : arrayList) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
/*
1.请编写程序,在ArrayList 中,添加3个Dog对象
2.Dog对象含有name 和 age, 并输出name 和 age (要求使用getXxx())
3.老韩使用泛型来完成代码
*/
class Dog{
private String name;
private int age;
public Dog(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;
}
}
class Cat{ //Cat类
private String name;
private int age;
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;
}
}
- 泛型的好处
泛型介绍
案例:
package com.hspedu.generic;
public class Generic03 {
public static void main(String[] args) {
//注意,特别强调: E具体的数据类型在定义Person对象的时候指定,即在编译期间, 就确定E是什么类型
Person<String> person = new Person<String>("韩顺平教育");
person.show(); //String
/*
你可以这样理解,上面的Person类
class Person{
String s;
public Person(String s){
this.s = s;
}
public String f(){
return s;
}
}
*/
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E>{
E s; //E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(E s){ //E也可以是参数类型
this.s = s;
}
public E f(){ //返回类型使用E
return s;
}
public void show(){
System.out.println(s.getClass()); //显示s的运行类型
}
}
泛型的语法
-
泛型的声明
-
泛型的实例化
- 泛型使用举例
package com.hspedu.generic;
import java.util.*;
/**
* @author zt
* @version 1.0
*/
@SuppressWarnings({"all"})
public class GenericExercise {
public static void main(String[] args) {
//使用泛型方式给HashSet放入3个学生对象
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("jack", 18));
students.add(new Student("tom", 28));
students.add(new Student("mary", 19));
//遍历
for(Student student : students){
System.out.println(student);
}
//使用泛型方式给HashMap 放入3个学生对象
//K -> String V->Student
HashMap<String, Student> hm = new HashMap<String, Student>();
hm.put("milan", new Student("milan", 38));
hm.put("smith", new Student("smith", 48));
hm.put("hsp", new Student("hsp", 28));
//迭代器 EntrySet
/*
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
*/
Set<Map.Entry<String, Student>> entries = hm.entrySet();
/*
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
*/
Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
System.out.println("=================");
while (iterator.hasNext()) {
Map.Entry<String, Student> next = iterator.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
/**
* 创建 3个学生对象
* 放入到HashSet中学生对象, 使用.
* 放入到 HashMap中,要求 Key 是 String name, Value 就是 学生对象
* 使用两种方式遍历
*/
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 泛型使用的注意事项和细节
泛型练习题
代码实现:
main方法:
package com.hspedu.generic;
import java.util.ArrayList;
import java.util.Comparator;
@SuppressWarnings({"all"})
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("tom", 20000, new MyDate(2000,12,11)));
employees.add(new Employee("jack", 12000, new MyDate(2001,12,12)));
employees.add(new Employee("tom", 50000, new MyDate(2000,12,10)));
System.out.println("employees=" + employees);
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee emp1, Employee emp2) {
//先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
//先对传入的参数进行验证
if(!(emp1 instanceof Employee && emp2 instanceof Employee)){
System.out.println("类型不正确..");
return 0;
}
//比较name
int i = emp1.getName().compareTo(emp2.getName());
if(i != 0){
return i;
}
//下面是对birthday的比较, 因此, 我们最好把比较, 放在MyDate类完成
//封装后, 将来可维护性和复用性, 就大大增强
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println("==对雇员进行排序==");
System.out.println(employees);
}
}
/**
* 定义Employee类
* 1) 该类包含:private成员变量name,sal,birthday,其中 birthday 为 MyDate 类的对象;
* 2) 为每一个属性定义 getter, setter 方法;
* 3) 重写 toString 方法输出 name, sal, birthday
* 4) MyDate类包含: private成员变量month,day,year;并为每一个属性定义 getter, setter 方法;
* 5) 创建该类的 3 个对象,并把这些对象放入 ArrayList 集合中(ArrayList 需使用泛型来定义),对集合中的元素进行排序,并遍历输出:
*
* 排序方式: 调用ArrayList 的 sort 方法 ,
* 传入 Comparator对象[使用泛型],先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
* 有一定难度 15min , 比较经典 泛型使用案例 GenericExercise02.java
*/
MyDate类
package com.hspedu.generic;
public class MyDate implements Comparable<MyDate>{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public int compareTo(MyDate o) { //把对year-month-day比较放在这里
//如果name相同, 就比较 birthday - year
int yearMinus = year - o.getYear();
if(yearMinus != 0){
return yearMinus;
}
//如果year相同, 就比较month
int monthMinus = month - o.getMonth();
if(monthMinus != 0){
return monthMinus;
}
//如果year和month都相同, 就比较日
return day - o.getDay();
}
}
Employee类
package com.hspedu.generic;
public class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
自定义泛型
自定义泛型类(难度)
自定义泛型接口
演示案例:
package com.hspedu.customgeneric;
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
/**
* 泛型接口使用的说明
* 1. 接口中, 静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
*/
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double>{
}
//当我们去实现IA接口时, 因为IA在继承IUsb接口时, 指定了U 为String R为 Double
//, 在实现IUsb接口的方法时, 使用String替换U, 是Double替换R
class AA implements IA{
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时, 直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以, 当我们实现IUsb方法时, 会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float>{
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型, 默认为Object
//建议直接写成 IUsb<Object, Object>
class CC implements IUsb { //等价 class CC implements IUsb<Object, Object>
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
interface IUsb<U, R> {
int n = 10;
//U name; 不能这样使用
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
自定义泛型方法
演示案例:
package com.hspedu.customgeneric;
import java.util.ArrayList;
/**
* 泛型方法的使用
*/
@SuppressWarnings({"all"})
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马", 100); //当调用方法时, 传入参数, 编译器, 就会确定类型
System.out.println("==========");
car.fly(300, 100.1); //当调用方法时, 传入参数, 编译器, 就会确定类型
//测试
//T -> String, R -> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(), 11.3f);
}
}
//泛型方法, 可以定义在普通类中, 也可以定义在泛型类中
class Car{ //普通类
public void run(){ //普通方法
}
//说明 泛型方法
//1. <T, R> 就是泛型
//2. 是提供给 fly使用的
public <T, R> void fly(T t, R r){ //泛型方法
System.out.println(t.getClass()); //String
System.out.println(r.getClass()); //Integer
}
}
class Fish<T, R> { //泛型类
public void run(){ //普通方法
}
public<U, M> void eat(U u, M m){ //泛型方法
}
//说明
//1. 下面hi方法不是泛型方法
//2. 是hi方法使用了类声明的 泛型
public void hi(T t){
}
//泛型方法, 可以使用类声明的泛型, 也可以使用自己声明泛型
public<K> void hello(R r, K k){
System.out.println(r.getClass()); //ArrayList
System.out.println(k.getClass()); //Float
}
}
泛型的继承和通配符
- 泛型的继承和通配符说明
JUnit
-
为什么需要JUint
-
基本介绍
- JUint是一个Java语言的单元测试框架
- 多数Java的开发环境都已经集成了JUnit作为单元测试的工具
第16章 坦克大战【1】
坦克大战游戏演示
-
游戏演示
-
为什么写这个项目
-
写项目前的提醒
java绘图坐标体系
-
坐标体系-介绍
-
坐标体系-像素
-
介绍-快速入门(java绘图技术原理)
√绘图原理
√ Graphics类
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);
//当点击窗口的小x,程序完全退出
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
@Override
public 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);
//填充矩形 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("/bg.png"));
// g.drawImage(image, 10, 10, 175, 221, 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)
}
}
- 绘出坦克
绘制图形以左上角为起始坐标
java事件处理机制
- 事件处理机制-看个问题
package com.hspedu.event_;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* 演示小球通过键盘控制上下左右移动-> 讲解Java的事件控制
*/
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;
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillOval(x, y, 20, 20); //默认黑色
}
//有字符输出时, 该方法就会触发
@Override
public void keyTyped(KeyEvent e) {
}
//当某个键按下,该方法会触发
@Override
public void keyPressed(KeyEvent e) {
//System.out.println((char)e.getKeyCode() + "被按下..");
//根据用户按下的不同键, 来处理小球的移动(上下左右的键)
//在java中,
if(e.getKeyCode() == KeyEvent.VK_DOWN){ //KeyEvent.VK_DOWN就是向下的箭头对应的code
y++;
} 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();
}
//当某个键释放(松开), 该方法会触发
@Override
public void keyReleased(KeyEvent e) {
}
}
-
基本说明
-
示意图
-
机制分析
-
事件处理机制深入理解
坦克大战游戏
- 让你的坦克动起来
本章作业
- 本章内容小结
HspTankGame02.java
mian方法
package com.hspedu.tankgame2;
import javax.swing.*;
public class HspTankGame02 extends JFrame {
//定义MyPanel
MyPanel mp = null;
public static void main(String[] args) {
HspTankGame02 hspTankGame01 = new HspTankGame02();
}
public HspTankGame02(){
mp = new MyPanel();
this.add(mp); //把面板(就是游戏的绘图区域)
this.setSize(1000, 750);
this.addKeyListener(mp); //让JFrame监听mp的键盘事件
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
}
Hero.java
自己的坦克类
package com.hspedu.tankgame2;
/**
* 自己的坦克
*/
public class Hero extends Tank {
public Hero(int x, int y) {
super(x, y);
}
}
EnemyTank.java
敌人的坦克类
package com.hspedu.tankgame2;
/**
* 敌人的坦克
*/
public class EnemyTank extends Tank{
public EnemyTank(int x, int y) {
super(x, y);
}
}
Tank.java
总的坦克类定义坦克的相关属性
package com.hspedu.tankgame2;
public class Tank {
private int x; //坦克的横坐标
private int y; //坦克的纵坐标
private int direct; //坦克的方向 0 上 1 右 2 下 3 左
private int speed = 1;
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
//上下左右移动方法
public void moveUp(){
y -= speed;
}
public void moveRight(){
x += speed;
}
public void moveLeft(){
x -= speed;
}
public void moveDown(){
y += speed;
}
public Tank(int x, int y) {
this.x = x;
this.y = y;
}
public int getDirect() {
return direct;
}
public void setDirect(int direct) {
this.direct = direct;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
MyPanel.java
绘制坦克及坦克的移动的实现
package com.hspedu.tankgame2;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
/**
* 坦克大战的绘图
*/
//为了监听 键盘事件, 实现KeyListener
public class MyPanel extends JPanel implements KeyListener {
//定义我的坦克
Hero hero = null;
//定义敌人坦克, 放入到Vector
Vector<EnemyTank> enemyTanks = new Vector<>();
int enemyTankSize = 3;
public MyPanel() {
hero = new Hero(100, 100); //初始化自己坦克
//初始化敌人的坦克
for(int i = 0; i < enemyTankSize; i++){
//创建一个敌人的坦克
EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
//设置方向
enemyTank.setDirect(2);
//加入
enemyTanks.add(enemyTank);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillRect(0, 0, 1000, 750); //填充矩形,默认黑色
//画出自己坦克-封装方法
drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 1);
//画出敌人的坦克, 遍历Vector
for(int i = 0; i < enemyTanks.size(); i++){
//取出坦克
EnemyTank enemyTank = enemyTanks.get(i);
drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirect(), 0);
}
}
//编写方法,画出坦克
/**
* @param x 坦克的左上角x坐标
* @param y 坦克的左上角y坐标
* @param g 画笔
* @param direct 坦克方向(上下左右)
* @param type 坦克类型
*/
public void drawTank(int x, int y, Graphics g, int direct, int type) {
//根据不同类型坦克,设置不同颜色
switch (type) {
case 0: //敌人的坦克
g.setColor(Color.cyan);
break;
case 1: //我们的坦克
g.setColor(Color.yellow);
break;
}
//根据坦克方向,来绘制对应形状坦克
//direct表示方向(0:向上 1:向右 2:向下 3:向左)
switch (direct) {
case 0: //表示向上
g.fill3DRect(x, y, 10, 60, false); //画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false); //画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false); //画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20); //画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y ); //画出炮筒
break;
case 1: //表示向右
g.fill3DRect(x, y, 60, 10, false); //画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false); //画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false); //画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20); //画出圆形盖子
g.drawLine(x + 30, y + 20, x + 60, y + 20); //画出炮筒
break;
case 2: //表示向下
g.fill3DRect(x, y, 10, 60, false); //画出坦克左边轮子
g.fill3DRect(x + 30, y, 10, 60, false); //画出坦克右边轮子
g.fill3DRect(x + 10, y + 10, 20, 40, false); //画出坦克盖子
g.fillOval(x + 10, y + 20, 20, 20); //画出圆形盖子
g.drawLine(x + 20, y + 30, x + 20, y + 60); //画出炮筒
break;
case 3: //表示向左
g.fill3DRect(x, y, 60, 10, false); //画出坦克上边轮子
g.fill3DRect(x, y + 30, 60, 10, false); //画出坦克下边轮子
g.fill3DRect(x + 10, y + 10, 40, 20, false); //画出坦克盖子
g.fillOval(x + 20, y + 10, 20, 20); //画出圆形盖子
g.drawLine(x + 30, y + 20, x, y + 20); //画出炮筒
break;
default:
System.out.println("暂时没有处理");
}
}
@Override
public void keyTyped(KeyEvent e) {
}
//处理wasd键按下的情况
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_W){ //按下W键, 向上
//改变坦克的方向
hero.setDirect(0);
//修改坦克的坐标y -= 1
hero.moveUp();
} else if(e.getKeyCode() == KeyEvent.VK_D){ //按下D键, 向右
hero.setDirect(1);
hero.moveRight();
} else if(e.getKeyCode() == KeyEvent.VK_S){ //按下S键, 向下
hero.setDirect(2);
hero.moveDown();
} else if(e.getKeyCode() == KeyEvent.VK_A) { //按下A键, 向左
hero.setDirect(3);
hero.moveLeft();
}
//让面板重绘
this.repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
}
第17章 多线程基础
线程相关概念
-
程序(program)
-
进程
-
什么是线程
- 其他相关概念
线程基本使用
-
创建线程的两种方式
-
线程应用案例1-继承Thread类
Thread01.java
package com.hspedu.threaduse;
/**
* 演示通过继承Thread类创建线程
*/
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(), 而不是run
private native void start0();
*/
cat.start(); //启动线程-> 最终会执行cat的run方法
//cat.run(); //run方法就是一个普通的方法, 没有真正的启动一个线程, 就会把run方法执行完毕, 才向下执行
//说明: 当main线程启动一个子线程 Tread-0, 主线程不会阻塞, 会继续执行
//这时 主线程和子线程是交替执行..
System.out.println("主线程继续执行" + Thread.currentThread().getName());
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
//老韩说明
//1. 当一个类继承了Tread 类, 该类可以当做线程使用
//2. 我们会重写run方法, 写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread{
int times = 0;
@Override
public void run() { //重写run方法,写上自己的业务逻辑
while(true){
//该线程每隔1秒, 在控制台输出 "喵喵, 我是小猫咪"
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
//让该线程休眠1秒 ctrl+alt+t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 80){
break; //当times 到80, 退出while, 这时线程也就退出..
}
}
}
}
- 线程应用案例2- 实现Runnable接口
Thread02.java
package com.hspedu.threaduse;
/**
* @author zt
* @version 1.0
* 通过实现接口Runnable 来开发线程
*/
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用start, 没有继承Thread
//创建了Thread对象, 把dog对象(实现Runnable),放入Thread
Thread thread = new Thread(dog);
thread.start();
// Tiger tiger = new Tiger(); //实现了 Runnable
// ThreadProxy threadProxy = new ThreadProxy(tiger);
// threadProxy.start();
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫...");
}
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable{ //你可以把Proxy类当做 ThreadProxy
private Runnable target = null; //属性, 类型是 Runnable
@Override
public void run() {
if(target != null){
target.run(); //动态绑定(运行类型Tiger)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0(); //这个方法是真正实现多线程方法,模拟Thread相关方法
}
public void start0(){
run();
}
}
class Dog implements Runnable{ //通过实现Runnable接口, 开发线程
int count = 0;
@Override
public 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;
}
}
}
}
- 线程如何理解
继承Thread vs 实现Runnable的区别
SellTicket.java
package com.hspedu.ticket;
/**
* 使用多线程, 模拟三个窗口同时售票100张
*/
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
@Override
public 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
@Override
public 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));
}
}
}
两种方式都会出现多卖票的现象:
线程终止
-
基本说明
-
应用案例 (ThreadExit_.java com.hspedu.exit_ )
package com.hspedu.exit_;
/**
* @author zt
* @version 1.0
*/
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去控制t1现成的终止, 必须可以修改 loop
//让t1 退出run方法, 从而终止 t1线程 -> 通知方式
//让主线程休眠10秒,再通知t1线程退出
System.out.println("main线程休眠10s..");
Thread.sleep(10 * 1000);
t1.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while(loop){
try {
Thread.sleep(50); //让当前线程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中...." + (++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用方法
- 常用方法第一组
- 注意事项和细节
应用案例ThreadMethod01.java:
ThreadMethod01.java
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);
t.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()); //1
t.interrupt(); //当执行到这里, 就会中断t线程的休眠
}
}
class T extends Thread { //自定义的线程类
@Override
public 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了");
}
}
}
}
运行结果:
- 常用方法第二组
ThreadMethod02.java 应用案例:
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 {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(老大) 吃了 " + i + " 包子");
}
}
}
join方法结果:
- 用户线程和守护线程
下面我们测试如何将一个线程设置成守护线程
应用案例:
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("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}
线程的生命周期
- JDK中用Thread.State枚举表示了线程的几种状态
- 线程状态准换图
线程的同步
- 先看一个问题
Synchronized
-
线程同步机制
-
同步具体方法- Synchronized
分析同步原理
互斥锁
- 基本介绍
- 使用互斥锁来解决售票问题
package com.hspedu.syn;
/**
* 使用多线程, 模拟三个窗口同时售票100张
*/
public class SellTicket {
public static void main(String[] args) {
//测试一把
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 = 300; //让多个线程共享 ticketNum
private 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));
}
}
@Override
public void run() {
while (loop) {
sell(); //sell方法是一个同步方法
}
}
}
- 注意事项和细节
线程的死锁
- 基本介绍
应用案例:
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();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (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");
}
}
}
}
}
结果:
A,B造成死锁,都无法进入下一步
释放锁
-
下面操作会释放锁
-
下面操作不会释放锁
第18章坦克大战【2】
线程 - 应用到坦克大战
- 坦克大战0.3版
- 坦克大战0.4版
第19章 IO 流
文件
-
什么是文件
-
文件流
常见的文件操作
- 创建文件对象相关构造器和方法
应用案例演示:
请在e盘下,创建文件news1.txt、new2.txt、new3.txt,用三种不同方式创建
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
/**
* 演示创建文件
*/
public class FileCreate {
public static void main(String[] args) {
}
//方式1 new File(String pathname)
@Test
public void create01(){
String filePath = "e:\\news1.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式2 new File(File parent,String child) //根据父目录文件 + 子路径构建
//e:\\news2.txt
@Test
public void create02(){
File parentFile = new File("e:\\");
String fileName = "news2.txt";
//这里的file对象, 在java程序中, 只是一个对象
//只有执行了createNewFile 方法, 才会真正的, 在磁盘创建该文件
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式3 new File(String parent, String child) //根据父目录 + 子路径构建
@Test
public void create03(){
String parentPath = "e:/";
String fileName = "new4.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 获取文件的相关信息
应用案例演示
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.File;
public class FileInformation {
public static void main(String[] args) {
}
//获取文件的信息
@Test
public void info(){
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法, 得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists()); //T
System.out.println("是不是一个文件=" + file.isFile()); //T
System.out.println("是不是一个目录=" + file.isDirectory()); //F
}
}
运行结果:
文件大小(utf-8编码:汉字3个字节 英文1个字节)
- 目录的操作和文件删除
应用案例演示
package com.hspedu.file;
import org.junit.jupiter.api.Test;
import java.io.File;
public class Directory_ {
public static void main(String[] args) {
//
}
//判断 d:\\news1.txt 是否存在, 如果存在就删除
@Test
public 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编程中, 目录也被当做文件
@Test
public 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 目录是否存在, 如果存在就提示已经存在,否则就创建
@Test
public 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 + "创建失败...");
}
}
}
}
Io流原理及流的分类
- Java IO 流原理
- 流的分类
IO 流体系图-常见的类
- IO 流体系图
- 文件 VS 流
FileInputStream 应用实例
要求: 请使用FileInputStream 读取 hello.txt文件, 并将文件内容显示到控制台
package com.hspedu.inputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 演示FileInputStream的使用(字节输入流 文件--> 程序)
*/
public class FileInputStream_ {
public static void main(String[] args) {
}
/**
* 演示读取文件...
* 单个字节的读取, 效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readDate = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象, 用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while((readDate = fileInputStream.read()) != -1) {
System.out.print((char)readDate); //转成char显示
}
} catch (IOException e) {
e.printStackTrace();
} finally{
//关闭文件流, 释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用 read(byte[] b) 读取文件, 提高效率
*/
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
int readDate = 0;
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节
int readLen = 0;
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();
}
}
}
}
FileOutputStream介绍
- FileOutputStream应用实例1 FileOutputStream01.java
要求: 请使用 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 将数据写到文件中,
* 如果该文件不存在, 则创建该文件
*/
@Test
public 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 = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定 byte阵列写入此输出流。
*/
fileOutputStream.write(str.getBytes(), 0, 3);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- FileOutputStream 应用实例2 FileCopy.java
package com.hspedu.outputstream_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
//完成 文件拷贝, 将 e:\\Koala.jpg 拷贝 c:\\
//思路分析
//1. 创建的文件的输入流 , 将文件读入到程序
//2. 创建文件的输出流, 将读取到的文件数据, 写入到指定的文件
String srcFilePath = "e:\\th.jpg";
String destFilePath = "d:\\th.jpg";
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();
}
}
}
}
- FileReader和FileWriter介绍
- FileReader相关方法:
- FileWriter常用方法
- FileReader和FileWriter应用案例FileReader_.java
要求:
1)使用FileReader从story.txt读取内容,并显示
package com.hspedu.reader_;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 单个字符读取文件
*/
@Test
public void readFile01(){
String filePath = "e:\\story.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);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileReader != null){
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符数组读取文件
*/
@Test
public void readFile02(){
System.out.println("~~~readFile02~~~");
String filePath = "e:\\story.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();
}
}
}
}
2)使用FileWriter将"风雨之后,定见彩虹"写入到note.txt文件中,注意细节FileWriter_.java
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("韩顺平教育".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, 一定要关闭流, 或者flash才能真正的把数据写入到文件
//老韩看源码就知道原因
/*
看看代码
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("程序结束..");
}
}
节点流和处理流
- 基本介绍
- 节点流和处理流一览图
- 节点流和处理流的区别和联系
- 处理流的功能主要体现在以下两个方面:
- 处理流 - BufferedReader 和 BufferedWriter
package com.hspedu.reader_;
import java.io.BufferedReader;
import java.io.FileReader;
/**
* 演示bufferedReader 使用
*/
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "e:\\story.txt";
//创建bufferedReader
BufferedReader 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();
}
}
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();
}
}
package com.hspedu.writer_;
import java.io.*;
public class BufferedCopy_ {
public static void main(String[] args) {
//老韩说明
//1. BufferedReader 和 BufferedWriter 是按照字符操作
//2. 不要去操作二进制文件[声音, 视频, doc , pdf等等], 可能造成文件损坏
String srcFilePath = "e:\\ok.txt";
String destFilePath = "e:\\ok2.txt";
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();
}
}
}
}
- 处理流 - BufferedInputStream和BufferedOutputStream
- 介绍BufferedOutputStream
package com.hspedu.outputstream_;
import java.io.*;
/**
* 演示使用BufferedOutputStream 和 BufferedInputStream使用
* 使用他们, 可以完成二进制文件拷贝
* 思考: 字节流可以操作二进制文件, 可以操作文本文件嘛? 当然可以
*/
public class BufferedCopy02 {
public static void main(String[] args) {
// String srcFilePath = "e:\\th.jpg";
// String destFilePath = "e:\\th01.jpg";
String srcFilePath = "e:\\story.txt";
String destFilePath = "e:\\story01.txt";
//创建BufferedOutputStream对象 BufferedInputStream对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件, 并写入到 destFilePath
byte[] 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();
}
}
}
}
-
对象流- ObjectInputStream和ObjectOutputStream
-
对象流介绍
功能:提供了对基本类型或对象类型的序列化和反序列化的方法
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列功能
ObjectOutStream_.java
package com.hspedu.outputstream_;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 演示ObjectOutputStream的使用, 完成数据的序列化
*/
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.dat
oos.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("韩顺平教育"); //String
//保存一个dog对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
Dog.java
package com.hspedu.outputstream_;
import java.io.Serializable;
//如果需要序列化某个类的对象, 实现 Serializable
public 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;
}
@Override
public 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.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 的运行类型是 Dog
Object 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(dog2.getName()); //旺财..
//关闭流, 关闭外层流即可, 底层会关闭 FileInputStream流
ois.close();
}
}
- 标准输入输出流
- 转换流 - InputStreamReader 和 OutputStreamWriter
package com.hspedu.transformation;
import java.io.*;
public class CodeQuestion {
public static void main(String[] args) throws IOException {
//读取e:\\a.txt 文件到程序
//思路
//1. 创建字符输入流 BufferedReader [处理流]
//2. 使用 BufferedReader 对象读取a.txt
//3. 默认情况下, 读取文件是按照 utf-8 编码
String filePath = "e:\\a.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
String s = br.readLine();
System.out.println("读取到的内容: " + s);
br.close();
}
}
**结果:**将txt文件编码方式转化成ANSI,原来方式读取文件出现乱码
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();
}
}
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, 韩顺平教育");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");
}
}
打印流-PrintStream 和 PrintWriter
PrintStream_.java
package com.hspedu.printstream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 演示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("韩顺平, 你好".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, 韩顺平教育~");
}
}
PrintWriter_.java
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类
- 看一个需求
传统方式实现Properties01.java
package com.hspedu.properties_;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties 文件, 并得到ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while((line = br.readLine()) != null){ //循环读取
String[] split = line.split("=");
//如果我们要求指定的ip值
if("ip".equals(split[0])){
System.out.println(split[0] + "值是: " + split[1]);
}
}
br.close();
}
}
- 基本介绍
- 应用案例
Properties02.java
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);
}
}
Properties03.java
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 , 底层就是Hashable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (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值, 就执行addEntry
return 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("保存配置文件成功~");
}
}
第20章坦克大战【3】
IO流-应用到坦克大战
- 坦克大战0.5版
- 坦克大战0.6版
日期
第15章 2022年6月7日-2022年6月10日
第16章 2022年6月12日-2022年6月14日
第17章 2022年6月15日-2022年6月18日
第18章 2022年6月19日 7月6日 - 7月7日
第19章 2022年7月7日-2022年7月16日
第20章 2022年7月17日-2022年7月18日