Java笔记(韩顺平Java基础15-20章)

第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;
    }
}

结果:
程序出现类型转换的错误

  • 使用传统方法的问题分析
  1. 不能对加入到集合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
    在这里插入图片描述

  • 基本介绍

  1. JUint是一个Java语言的单元测试框架
  2. 多数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 流体系图-常见的类

  1. IO 流体系图

在这里插入图片描述

  1. 文件 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日

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值