hsp-坦克大战-线程/IO流/事件

本篇主线是做一个坦克大战游戏,其中穿插新的知识点

Java中JFame与JPanel

JFame是一个框架,可以想象成一个窗口
JPanel是一个画板,画板放在框架上,可以画图或其他操作
下图是一个简单的图片表述
在这里插入图片描述
代码如下

import javax.swing.*;
import java.awt.*;

class A_DrawCircle extends JFrame{//继承
    public static void main(String[] args) {
        A_DrawCircle aDrawCircle = new A_DrawCircle(); //新建框架new自己
    }

    public A_DrawCircle(){
        this.setSize(200,150);//设窗口大小
        this.setVisible(true);//设窗口可见
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设窗口关闭时结束程序
        MyPanel myPanel = new MyPanel();//新建一个画板
        this.add(myPanel);//将画板放入框架
    }
}
class MyPanel extends JPanel{//重写其方法
    @Override
    public void paint(Graphics g) {//这个方法在画板加入框架时会被调用
        super.paint(g);//初始画笔
        g.drawOval(1,1,50,50);//画圆形
    }
}

整体逻辑如下

  1. 创建一个JFame框架
  2. 在框架的构造器中设置框架属性(大小,可见,关闭结束)
  3. 给框架添加一个画板对象
  4. 重写画板的paint方法
  5. 初始化g的笔,并开始使用它

Paint方法被调用的场景

  1. 组件第一次显示
  2. 窗口最小化,再最大化
  3. 窗口大小发生变化
  4. repaint方法被调用

Graphics g的方法

此对象是用于画东西,有很多方法,这里不多赘述

image放置位置参考这里

    public void paint(Graphics g) {//这个方法在画板加入框架时会被调用
        super.paint(g);//初始画笔
        g.drawOval(1,1,50,50);//画圆形
        g.drawRect(1,1,10,10);

        g.setColor(Color.BLUE);//给画笔上色
        g.fillRect(20,20,50,50);//填充矩形
        g.setColor(Color.LIGHT_GRAY);
        g.fillOval(50,50,30,50);//填充椭圆

        //图片,需要用Toolkit获取图片/图片需要放在编译后文件所在的根目录
        Image image = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/wq.jpg"));
        //图片要在Out的production项目下 MYpanel自己的类
        g.drawImage(image,70,10,50,50,this);

        g.setColor(Color.BLACK);
        g.setFont(new Font("正楷",Font.BOLD,13));
        g.drawString("丘比特!!",100,100);//此时是左下角
}

画Tank

/**
* @param x
* @param y
* @param g 画笔
* @param direct 方向
* @param type 坦克类型
*/ public static void DrawTank(int x, int y, Graphics g, int direct, int type)

通过调用这个方法,方法内判断类型,设置颜色
判断方向画出坦克

事件处理

监控键盘按键,对应事件做出相应的处理
让画板实现KeyListener监听器,监听键盘的事件
其中有三个需要实现的方法,并且让框架对象 this.addKeyListener(mp);//因为mp本身实现了KeyListiener接口

窗口JFrame可以监听键盘事件

    @Override //有字符输出时,该方法调用
    public void keyTyped(KeyEvent e) {
       
    }

    @Override //当某个键按下,该方法触发
    public void keyPressed(KeyEvent e) {

    }

    @Override  //当某个键松开,该方法触发
    public void keyReleased(KeyEvent e) {

    }

事件处理机制

发生对应的事情调用指定的方法
**事件源 ** 可以产生对象,比如窗口,按钮
事件 事件源发生事件,事前承载事件源状态改变时的对象,当键盘/鼠标/窗口事件发生时,生成一个事件对象,对象保存信息,KeyEvent对象包含按键Code值java.awt.event / javax.swing.event包中包含了各种事件类
事件监听器

事件监听接口

  1. 当事件源产生一个事件,可以传递给事件
  2. 事件监听者实际上是一个类,实现了某个事件监听接口,比如MyPanel实现了KeyListener接口,则MyPanel就是一个事件监听者,监听到事件进行处理
  3. 事件监听者有多种,不同的事件监听者监听不同的事件
    在这里插入图片描述

多线程

单线程:同一时刻只执行要给线程
多线程:同一时刻多个线程同时运行

并发:同一时刻,多个任务交替执行,但是速度快,给人一种同时运行的错误,单核处理器多任务就是并发
并行:同一时刻,多个任务同时进行,多核cpu可以实现并行。
并行的时候也会出现并发

下面举个例子来体验多线程运行的感觉
首先例子中有一个Cat类,继承Thread线程类,(main中将会调用cat对象的start方法开启新的线程)
并且重写run方法(Thread的run方法实现了Runable接口类),在run方法中循环计时
main类中 实例化一个Cat类并调用对象的start方法(继承Thread的方法),start后自己写一份循环计时
多线程:main开启了了Cat的start方法后(开辟了一个线程),并且继续向下走执行自己的循环即使
cat由于调用了start方法,也开始了自己的计时,两个线程同时运行
ps: 终端中jconsole可以调出线程监控
下面是线程的java代码举例;

package com.hspedu.Thread_;

public class Thread01 {

    public static void main(String[] args) {
        int times = 0;
        Cat cat = new Cat();
        cat.start(); //在这里开启了一个线程
        //同时主线程不停止,继续运行
        while (true) {
            System.out.println("main=" + (++times) + "{" + Thread.currentThread().getName() + "}");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 30) {
                break; // 这里将会结束进程
            }
        }
    }
}


class Cat extends Thread {
    int times = 0;

    @Override
    public void run() {
        super.run();

        while (true) {
            System.out.println("Cat=" + (++times) + "{" + Thread.currentThread().getName() + "}");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 40) {
                break; // 这里将会结束进程
            }
        }
    }
}

此时调用的是start而不是直接调run方法的原因
如果直接调用run方法就是一个串行化的执行,main线程将会执行run方法结束后才会执行下面的代码(串行化)
start方法将会调用start0方法private native void start0();是本地方法,jvm机调用
start0方法只是将线程变为可运行,又cpu调动使用

关于Thread.sleep()抛出异常的原因:
假设线程不抛出中断异常的情况,此时线程A睡眠,其他所有的线程将无法唤醒A,因为A对外界没有感知能力。
为什么Sleep会抛出异常
在这里插入图片描述

Runable多线程(单继承下多线程的补充)

若一个需要完成多线程的类已经继承了一个父类,又需要做到多线程,此时可以让这个类实现Runnable接口
再new Thread()对象,将多线程对象传入其构造器,通过Thread对象调用start方法完成多线程操作

package com.hspedu.Thread_;

//当类已经继承了其他的类,而又想做到多线程
public class Runnable_ {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);//新建一个代理对象
        thread.start();//由代理对象调用方法!
    }
}

class Animal {
}

class Dog extends Animal implements Runnable {
    int times = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("线程" + Thread.currentThread().getName() + "正在运行,Dog汪汪叫了" + (++times) + "次");
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 30) {
                break;
            }

        }
    }
}

这里Thread中用到了静态代理设计模式
下面来模拟一下静态代理
先看一下基础的类关系
在这里插入图片描述
Runnable中只有run方法,想要使用Thread中的start方法,可以通过静态代理模式,将实现了Runnable的目标子类
ThreadProxy和Tiger必须同时实现某个方法

public class Thread02 {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class ThreadProxy implements Runnable{
    Runnable runnable = null;

    public ThreadProxy(Runnable runnable) {
        this.runnable = runnable;
    }

    @Override
    public void run() {
        if (runnable != null){
            runnable.run();
        }
    }

    public void start(){
        start0();
    }
    public void start0(){
        run();
    }

}

继承Thread VS 实现Runnable的区别

Runnable接口更加适合多个线程共享一个资源的 情况,可以创建多个线程,传入同一个对象,同时start

多线程处理单个数据遇到的问题

当多个线程抢一个票源的时候,不对线程进行处理,将会使余票数量为负值(某个时刻两个线程抢了同一张票)

实现Runnable,将一个卖票对象放入多个线程,会超卖

package com.hspedu.Ticket;

public class SellTicket {
    public static void main(String[] args) {
        Sell sell = new Sell();
        Thread thread = new Thread(sell);
        Thread thread1 = new Thread(sell);
        Thread thread2 = new Thread(sell);
        thread.start();
        thread1.start();
        thread2.start();
    }
}


class Sell implements Runnable{
    private static int tickets=100;
    @Override
    public void run() {
        while(true){
            if (tickets >0)
            {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("卖出1张" +
                        "\t余票:"+(--tickets));
            }else {
                break;
            }
        }
    }
}

在这里插入图片描述

线程终止

  1. 线程自己结束
  2. 通过main停止线程
    给类设置一个属性,线程监测这个属性是false就结束进程,主进程通过控制属性来让线程结束
package com.hspedu.Thread_;

public class AThread {
    public static void main(String[] args) {
        BThread bThread = new BThread();
        Thread thread = new Thread(bThread);
        Thread thread1 = new Thread(bThread);
        Thread thread2 = new Thread(bThread);
        thread.start();
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        bThread.setStatus(false);
    }
}

class BThread implements Runnable {
    private boolean status;

    @Override
    public void run() {
        status = true;
        while (status) {
            System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }
}

线程常用方法

  • start底层是创新线程调用run方法
  • set/get Priority线程优先级范围是 max10 默认5 min1
  • interrupt中断线程,是用于中断线程的休眠状态
  • sleep 让线程休眠 可能会抛出异常(被唤醒)
  • 还有线程的set/get name的方法
package com.hspedu.Thread_;

public class ThreadMethod {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Thread thread = new Thread(teacher);
        System.out.println(thread.getPriority());
        thread.setName("丘比特老师!");
        thread.start();
        System.out.println(thread.getName());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
    }
}

class Teacher implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "正在运行");
        }

        try {
            System.out.println("睡觉先 轰!20秒");
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        	System.out.println("叫我干什么?");
        }
    }
}

线程常用方法2

  • yield静态方法,在并发时先让出一cpu,若资源充足就不让
  • join插队 插队的线程一旦插入成功则肯定先执行完插入线程的所有任务再
    比如此时cpu在并行运行两个线程,若有一个线程插队,则先只执行这个线程直到完毕 再回来

下面是当main线程达到某个值,让子线程插入运行,结束后再运行主线程

package com.hspedu.Thread_;

import sun.awt.windows.ThemeReader;

public class ThreadJoin {
    public static void main(String[] args) {
        Student student = new Student();
        Thread thread1 = new Thread(student);
        for (int i = 0; i < 20; i++) {
            if (i == 5) {
                try {
                    System.out.println("儿子先自己吃");
                    thread1.start();
                    thread1.join();
                    System.out.println("儿子吃完了爸爸收尾");


                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "打工中" + i);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

class Student implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 15; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程" + Thread.currentThread().getName() + "打工中" + i);
        }
    }
}

用户线程/守护线程

  • 用户线程 工作线程,当线程任务结束或通知时结束
  • 守护线程 为工作线程服务,当用户线程结束,守护线程自动结束(垃圾回收器)

如何将一个线程设置成守护线程(Daemon),使其无线循环。并且在线程开始之前setDaemon(true);即可,注意是在线程被start之前

线程的生命周期

在Thread.State枚举类中可以看到

  1. 尚未启动new
  2. 就绪 ready
  3. 运行中 runnable
  4. 线程阻塞中 blocked
  5. 锁Blocked
  6. 等待 Waiting
  7. 超时 TimedWaiting
    在这里插入图片描述
    在下面的代码中,不断检测子进程的状态,尝试看到这6种
package com.hspedu.Thread_;
import sun.awt.windows.ThemeReader;
public class ThreadState {
    public static void main(String[] args) {
        T t = new T();
        Thread thread = new Thread(t);
        System.out.println(thread.getState());
        thread.start();
        while (Thread.State.TERMINATED != thread.getState())
        {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(thread.getName() + "状态 =" + thread.getState());
        }
        System.out.println(thread.getName() + "状态 =" + thread.getState());

    }
}

class T implements Runnable{
    @Override
    public void run() {
        while (true){
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println("子线程运行!");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

Synchronized线程同步

多线程中,一些特殊数据不允许多个线程访问,此时使用同步访问技术,保证该数据在同一时刻,最多有一个线程访问,以保证数据完整!(指定内存地址在同一时刻只会有一个线程操作)
  1. 将synchronized放在方法声明中,表示整个方法为同步方法(锁单个 方法用)
  2. 同步代码块 下面互斥锁会演示

下面更新了售票,使用synchronized修饰指定的方法,创新方法的原因是需要让run一直运行

package com.hspedu.Ticket;

public class SellTicket {
    public static void main(String[] args) {
        Sell sell = new Sell();
        Thread thread = new Thread(sell);
        Thread thread1 = new Thread(sell);
        Thread thread2 = new Thread(sell);
        thread.start();
        thread1.start();
        thread2.start();
    }
}


class Sell implements Runnable {
    private static int tickets = 100;
    private static boolean loop = true;

		//此时锁在锁this对象
    private synchronized void sell() { 
        if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("卖出1张" +
                    "\t余票:" + (--tickets));
        }else {
            loop =false;
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

互斥锁

每个对象都有一个互斥锁标记,意味着同一时刻只有一个线程可以访问该对象

  • 当修饰静态方法时,锁定的是当前类的 Class 对象
  • 当修饰非静态方法时,锁定的是当前实例对象的 this

this对象锁是 非公平锁,a线程拿到后,下次可能还是a抢到锁

同步代码块,拿到某个对象的锁进行操作,同一时刻只有一个线程可以抢到这个锁进行使用,所以只要大家抢的是同一个对象的锁就行了

		// 此时也是锁对象
    private void sell() {
    		// 这里不一定是要锁this,也可锁其他对象(必须是本对象的某个属性的对象)
        synchronized (this) { // 可以放同一个对象属性
            if (tickets > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("卖出1张" +
                        "\t余票:" + (--tickets));
            } else {
                loop = false;
            }
        }
    }


 		//下面是静态方法
 				// 此时也是锁对象
    private static synchronized void sell() {
    		// 这里不一定是要锁this,也可锁其他对象(必须是本对象的某个属性的对象)
        synchronized (当前类名.class) {
            if (tickets > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("卖出1张" +
                        "\t余票:" + (--tickets));
            } else {
                loop = false;
            }
        }
    }
应用互斥锁
  1. 尽量选择影响范围小的同步代码块
  2. 需要先分析上锁的代码
  3. 选择是同步代码块还是同步方法
  4. 多个线程锁是锁必须同一个对象(不然锁不同的对象,线程操作不同的线程,没有意义),实在不行就要锁类(特殊属性是static)
线程死锁

多个线程都占用了对方的锁资源
两个线程都需要对方的锁,就卡住了

下面程序举例说明了死锁造成的原因,运行一段时间后两线程将会死锁

package com.hspedu.Thread_;

public class DeadLockDemo {
    public static void main(String[] args) {
        DeadLock deadLock1 = new DeadLock(false);
        DeadLock deadLock2 = new DeadLock(true);
        deadLock1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        deadLock2.start();
    }
}
class DeadLock extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean loop;
    public DeadLock(boolean loop) {
        this.loop = loop;
    }
    @Override
    public void run() {
        while (true){
            if(loop){
                synchronized(o1){
System.out.println(Thread.currentThread().getName()+"线程正在运行1,下一步需要获得o2锁" );
                    synchronized (o2){
System.out.println(Thread.currentThread().getName()+"线程正在运行2,已经获得 02锁" );
                    }
                }
            }else {
                synchronized(o2){
System.out.println(Thread.currentThread().getName()+"线程正在运行3,下一步需要获得o1锁" );
                    synchronized (o1){
System.out.println(Thread.currentThread().getName()+"线程正在运行4,已经获得 01锁" );
                    }
                }
            }
        }
    }
}

执行结果将会卡在下面这一步

Thread-0线程正在运行3,下一步需要获得o1锁
Thread-1线程正在运行1,下一步需要获得o2锁

两线程互掐

释放锁

  1. 当当前线程的同步代码块或方法结束后
  2. 在同步代码块或方法中break或return
  3. 同步代码块或方法中出现了error或exception,导致异常结束
  4. 线程中执行wait()方法,当前线程暂停,会先释放锁

下面方法不会释放锁

  1. 程序sleep和yield方法
  2. 线程执行同步代码块时,其他线程将这个线程挂起,suspend和resume尽量不要用

坦克发射子弹

线程的实际应用,让子弹类实现runnable,创建子弹对象后运行即可
具体 子弹代码如下


public class Shot implements Runnable {
    private int x;
    private int y;
    private int direct;
    private int speed;
    private boolean Live = true;

    public Shot(int x, int y, int direct, int speed) {
        this.x = x;
        this.y = y;
        this.direct = direct;
        this.speed = speed;
    }


    @Override
    public void run() {
        while (Live) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            switch (direct) {
                case 0:
                    y -= speed;
                    break;
                case 1:
                    x += speed;
                    break;
                case 2:
                    y  += speed;
                    break;
                case 3:
                    x -= speed;
                    break;
            }
            if (x < 0 || x > 1000 || y < 0 || y > 700) {
                Live = false;
                break;
            }
            System.out.println("子弹射击!x=" + x + ",y=" + y);
        }
        System.out.println("子弹线程="+Thread.currentThread().getName()+"终止");
    }

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

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public boolean isLive() {
        return Live;
    }

    public void setLive(boolean live) {
        Live = live;
    }
}

文件操作

保存文件,写入磁盘

java内存 通过 输出流 写入文件
文件 通过 输入流 进入内存
以人为基准,分辨输入输出

常用文件操作

File类线程安全/可对比
new File()此对象有三个构造器

  1. String 单路径
  2. File + String 通过已存在的File文件所在的目录+子路径
  3. String + string 父目录+子目录路径
    public static void createFile(){
        File file = new File("D:\\test.txt");//这里使用的是第一个构造器
        //这里只是将文件对象创建,并没有实际创建对象,需要使用createNewFile创建文件
        try {
        //这里才是实际创建对象,需要使用createNewFile创建文件,会抛出异常
            file.createNewFile();
            System.out.println("文件创建成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        File file2 = new File(file,"test.txt");//这里使用的是第二个构造器
        File fil3 = new File("D:\\","test2.txt");//这里使用的是第三个构造器


		//常用的属性获得方法
		System.out.println("文件名"+file.getName());
        System.out.println("绝对路径"+file.getAbsolutePath());
        System.out.println("父级目录"+file.getParent());
        System.out.println("文件大小(字节)"+file.length());
        System.out.println("指向的文件/目录是否存在"+file.exists());
        System.out.println("文件?"+file.isFile());
        System.out.println("目录?"+file.isDirectory());

		//删除指定文件
        File file1 = new File(file.getParent(), "test.txt");
        if (file1.exists() && file1.isFile()) { // 判断是否存在,是否是文件
            if (file1.delete()) {
                System.out.println(file1.getName() + "删除成功");
            } else {
                System.out.println(file1.getName() + "删除失败");
            }
        } else {
            System.out.println("文件不存在");
        }

		//连续创建目录
		        String directoryPath = "D:\\demo\\a\\b\\c";
        File file2 = new File(directoryPath);
        if (file2.exists()){
            System.out.println(file2.getAbsolutePath() +
                    "目录已存在");
        }else {
            if (file2.mkdirs()) {
                System.out.println("创建成功");
            }else {
                System.out.println("创建失败");
            }
        }

    }

IO流原理及流分类

Input/Output 用于数据传输 读写文件 网络通讯 声音视频,文件等

流的分类

按数据单位不同分为
字符流(8bit) 二进制文件常用无损效率稍低 ,字符流(按字符流) 文本文件

按数据流方向 输入 、输出
按流的角色 节点 ,处理流也叫包装流
在这里插入图片描述

文件 与 流 的关系

流是将数据运输的过程,文件是一个储存的方式

InputStream字节输入流常用实现子类

FileInputStream 文件字节输入流
FilterInputStream 派生出 BufferedInputStream
ObjectInputStream

FileInputStream

理解:一个 流对象 关联一个文件,就能对这个文件进行操作
有三个构造器
在这里插入图片描述
字节流不能用于读取包含中文的内容,会乱码
下面演示使用fileinputStream文件字节输入流的类和方法

注意在带缓存的读取字节流时必须使用下面指定的方法
while ((readline = fileInputStream.read(dataBytes)) != -1) {
System.out.print(new String(dataBytes, 0, readline));
}
不能直接输出缓存的内容
不然有可能在最后一次读取时若长度不足以填满缓存
导致读入上次遗留的数据

	//下面是用read单个字节的方法,效率较低
    public static void main(String[] args) {
        int readData = 0;
        FileInputStream fileInputStream = null;
        try {
            //创建了流对象读取文件
            fileInputStream = new FileInputStream("D:\\test.txt");
            //read读取一个字节数据,如果返回-1则文件读取完毕
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char) readData);
            }
        }catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //关闭文件流,释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


		// 下面是指定多少个字节长度缓存读取的例子
		 public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        byte[] dataBytes = new byte[8];
        int readline = 0;
        try {
            //创建了流对象读取文件
            fileInputStream = new FileInputStream("D:\\test.txt");
            //read读取指定缓存大小字节数据,返回值为读取到的byte数组长度,如果返回-1则文件读取完毕
            while ((readline = fileInputStream.read(dataBytes)) != -1) {
                System.out.print(new String(dataBytes, 0, readline));
            }
        }catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //关闭文件流,释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
FileOutputStream 文件字节输出流
  • 写构造函数时可以指定输出是覆盖还是续写
  • write可以输出单个字节 / 指定字节数组 / 指定字节数组的某几段
    public static void main(String[] args) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("D:\\Output.txt", true);
            String s = "hello world\n";
            fileOutputStream.write(s.getBytes());
            System.out.println("写入成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

FileReader / FileWriter字符流

Reader派生出InputStreamReader派生出 FileReader
注意其继承关系

FileReader

构造器通过路径绑定文件
read方法 每次读取单个字符,根据编码方式读取单个字符,末尾-1
read方法(char[]) 每次读取指定长度的字符数组,返回读取字符数量,末尾返回-1

相关需要使用的其他api
new String(char[] ,off,len) 将字符数组指定部分转换为字符串

FileWriter 文件输出流

构造器(file对象/路径,true) 追加方式
write(int)单个字符
write(char[],off,len)/ (string ,off,len)
其他:String类toCharArray 将String转换为char[]

使用对象后必须调用flush或close方法才能真正写入文件
!!!!一定要关流!!!!!

节点流/处理流

  1. 节点流直接可以对某个 特定的数据源 读写数据 Filereader /writer
    灵活性差,功能邵
  2. 处理流/包装流 对节点流包装,让功能更加强大 BufferedReader
    包装流内会预留空间用于 封装 指定节点流的任何子类
    比如 BufferedReader包装类可以 封装任何的Reader子类 比如FileReader(此时可以是任意节点流的 数据来源 巧用OOP修饰设计模式)

在这里插入图片描述

处理流修饰器设计模式

在这里插入图片描述
其实就是两个类,一个基础抽象类,一些实现抽象类的类,一个对实现类 锦上添花,锦上添花可以接收任何基础的的流来源进行优化,增加更多功能,但锦上添花自己不做基础的功能,只是调用

处理流好处

  1. 可以做到缓存
  2. 大量操作便捷

BufferedReader/Writer

都属于字符流,尽量操作文本文件,关闭时只需要关闭外层处理流流即可,包装流的close中包含了对节点流的关闭
不要操作二进制文件,不然会损坏

   public static void main(String[] args) {
        BufferedWriter bufferedWriter = null;
        try {
            bufferedWriter = new BufferedWriter(new FileWriter("E:\\fakeKey.txt", true));
            bufferedWriter.write("你好啊!\t韩顺平!");
            bufferedWriter.newLine();//根据对应!系统换行,最好不要用\n!
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

BufferedInputStream / OutputStream 字节包装流

InputStream 派生 FilterInputStream(在此创字段存放节点流) 派生 BufferedInputStream
下图展现了out的类继承图,可以看到存放节点流的位置是在filter过滤器中,并且本身有buf的缓存空间
在这里插入图片描述
BufferedOutputStream的构造器将传入的 节点流 存入父类的filter中

对象流

数据 保存为文件时 需要同时保存到数据类型,让程序读取时自动加载出对应的数据类型
对应也需要保存 自定义对象的属性 的值和数据类型

保存这个过程叫序列化
反序列化,恢复 数据类型和值

若对象需要支持序列化则需要实现下列两接口之一

  1. Serializable (是一个标记接口,没有任何方法)
  2. Externalizable 有方法需要实现,因此一般使用上面的第一个接口

ObjectOutputStream 处理流提供序列化功能
ObjectInputStream 提供反序列化功能

Object处理流

  • 依然是使用修饰器设计模式
    可以传入OutputStream的子对象

序列化后保存的文本格式是按照一种特殊文件保存的,后缀无所谓

读取时(反序列化)需要和保存数据的顺序一致,读取时返回值并不会恢复成原来的数据类型(若是自定义类型,则需要让读取时反序列化的代码能够访问 自定义类型)
若需要在反序列化后读取调用对象方法,需要向下转型(若要向下转型,则必须要可以访问到 自定义数据类型 的类)

序列化需要注意的细节

  1. 读写的顺序要一致
  2. 需要实现支持序列化的接口Serializable
  3. 序列化的类中需要添加SerialVersionUID ,提高版本兼容性
    private static final long serialVersionUID 序列化版本号,可以提高序列化的兼容性,读取到这个值,可以调用到
  4. 序列化属性不包括static/transient
  5. 序列化具有继承性,父类支持子类也支持
  6. 序列化对象时,内属性也需要实现序列化接口,比如Person的name String就必须是支持序列化的,不然会报错

标准输入输出流

标准输入是键盘 System.in class java.io.BufferedInputStream
标准输出是屏幕 System.out class java.io.PrintStream

  • System.out.println 这里的out代表的是标准的输出流
  • Scanner(System.in) 这里的in代表是标准输入流

转换流 字节转字符

可以将字节流转换为字符流
InputStreamReader仍然属于字符流,是Reader的子类
重要的构造器(InputStream,Charset)可以将字节流指定编码转换为字符流

OutputStreamWriter仍然是字符流, 是Writer的子类
重要的构造器(OutputStream,Charset)可以将字节流指定编码转换为字符流

将一个文本文件用字节流打开发现 中文乱码,现在可以将读入的字节流转换为字符流
默认情况下,BufferReader打开是用UTF-8
当文件打开编码方式错误是错误的就会乱码
编码格式UTF-8

目的:FileInputStream转换为字符流,指定编码gbk/utf-8

打印流

打印流只有输出流没有输入流
PrintStream OutputStream的子类
PrintWriter Writer的子类

可以定义系统的输出的位置

public class SysOut {
    public static void main(String[] args) throws FileNotFoundException {
        System.setOut(new PrintStream("D:\\Downloads\\test.txt"));
        System.out.println("哈哈哈hello,你好");
    }
}

Properties类

父类是HashTable
写配置文件的常用类
load 将配置文件键值加载到对象
list 将数据显示到指定设备
getProperty(key)根据键获取值
setProperty 增加 相同就覆盖
store将properties类中的键值存储,如果包含中文会转换为unicode编码

  • properties的值中不能放其他数据类型,只能放String类

IO流应用坦克大战

  1. 防止敌人坦克重叠运动
    遍历敌人每一个坦克,分当前坦克4种方向/上下左右,分别每一个方向遇到敌人坦克的两种形态 横/竖 ,每一个组合都需要判定4种方向的车头两个点,不能到其他坦克所在范围内,若有,则该方向不能移动
  2. 记录玩家成绩,存盘退出
    利用io流写文件
  3. 记录敌人坦克坐标,存盘退出
    用io流写文件,内容是x y direct三个数值
  4. 玩游戏时,选新开还是继续
bgm使用了现成的类

调用时需要是wav格式,若格式不对则需要转换,不能改文件后缀名!

ERROR: qcacld32-ll-hsp-git-r8 do_package_qa: Error executing a python function in exec_func_python() autogenerated: The stack trace of python calls that resulted in this exception/failure was: File: 'exec_func_python() autogenerated', lineno: 2, function: <module> 0001: *** 0002:do_package_qa(d) 0003: File: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/poky/meta/classes/insane.bbclass', lineno: 1105, function: do_package_qa 1101: oe.qa.handle_error("pkgname", 1102: "%s doesn't match the [a-z0-9.+-]+ regex" % package, d) 1103: 1104: warn_checks, error_checks = parse_test_matrix("QAPATHTEST") *** 1105: package_qa_walk(warn_checks, error_checks, package, d) 1106: 1107: warn_checks, error_checks = parse_test_matrix("QAPKGTEST") 1108: package_qa_package(warn_checks, error_checks, package, d) 1109: File: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/poky/meta/classes/insane.bbclass', lineno: 724, function: package_qa_walk 0720: elves[item[0]].set_objdump("-p", item[1]) 0721: 0722: for path in pkgfiles[package]: 0723: if path in elves: *** 0724: elves[path].open() 0725: for func in warnfuncs: 0726: func(path, package, d, elves.get(path), warnings) 0727: for func in errorfuncs: 0728: func(path, package, d, elves.get(path), errors) File: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/poky/meta/lib/oe/qa.py', lineno: 58, function: open 0054: if self.data: 0055: self.data.close() 0056: 0057: def open(self): *** 0058: with open(self.name, "rb") as f: 0059: try: 0060: self.data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 0061: except ValueError: 0062: # This means the file is empty Exception: FileNotFoundError: [Errno 2] No such file or directory: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/build-qti-distro-tele-perf/tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp/git-r8/packages-split/qcacld32-ll-hsp/lib/modules/5.15.153-perf-an960-cn-060.003.db.09.16-dirty/extra/qca6490.ko.~signed~' ERROR: Logfile of failure stored in: /fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/build-qti-distro-tele-perf/tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp/git-r8/temp/log.do_package_qa.242534 NOTE: recipe qcacld32-ll-hsp-git-r8: task do_package_qa: Failed ERROR: Task (/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/poky/meta-qti-wlan/recipes/wlan/qcacld32-ll-hsp_git.bb:do_package_qa) failed with exit code '1' Pseudo log: inode mismatch: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/build-qti-distro-tele-perf/tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp/git-r8/packages-split/qcacld32-ll-hsp/lib/modules/5.15.153-perf-an960-cn-060.003.db.09.16-dirty/extra/qca6490.ko' ino 270699924 in db, 270700052 in request. inode mismatch: '/fibocom/qujunyang/desktop/sa525_r22/sa525m_le/SA525M_apps/apps_proc/build-qti-distro-tele-perf/tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp/git-r8/packages-split/qcacld32-ll-hsp/lib/modules/5.15.153-perf-an960-cn-060.003.db.09.16-dirty/extra/qca6490.ko' ino 270699924 in db, 270700052 in request. Setup complete, sending SIGUSR1 to pid 235379. 补充报错日志
08-13
<think>根据提供的补充报错日志,我们可以更精确地定位问题。报错的核心信息如下: 1. **直接错误原因**: `FileNotFoundError: [Errno 2] No such file or directory: .../qca6490.ko.~signed~` 在 `do_package_qa` 阶段,质量检查脚本尝试打开一个已签名的内核模块文件(`qca6490.ko.~signed~`),但该文件不存在。 2. **深层原因**: - **伪日志(Pseudo log)** 显示: `inode mismatch: .../qca6490.ko ... ino ... in db, ... in request.` 这表明文件系统状态不一致(可能是由于并行操作或临时文件残留),导致文件索引节点(inode)与预期不符。 3. **错误触发程**: - `package_qa_walk` 遍历软件包文件时,尝试分析 ELF 文件(此处是内核模块)。 - 当处理到 `qca6490.ko` 时,脚本尝试打开其对应的签名版本 `qca6490.ko.~signed~`(但实际未生成或已被删除)。 ### 修复建议 1. **检查签名程**: 确认配方中内核模块的签名步骤是否正确生成 `*.ko.~signed~` 文件。 - 若签名过程被跳过,需调整配方确保签名文件存在。 - 若签名文件被意外删除,需检查 `do_install` 或 `do_package` 阶段是否有误删操作。 2. **清理残留状态**: ```bash bitbake -c cleansstate qcacld32-ll-hsp bitbake -c cleanall qcacld32-ll-hsp rm -rf tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp # 手动删除残留目录 ``` 3. **禁用签名检查(临时绕过)**: 在 `local.conf` 中添加: ```ini INSANE_SKIP:qcacld32-ll-hsp += "elf-sign" ``` 此操作跳过对签名文件的检查(需评估安全性影响)。 ### 完整分析程 ```mermaid graph TD A[报错:FileNotFoundError] --> B{检查文件是否存在} B -->|文件缺失| C[检查签名程] C --> D[配方中是否调用了签名命令?] D -->|是| E[确认签名输出路径正确] D -->|否| F[添加模块签名步骤] B -->|inode不匹配| G[清理构建缓存] G --> H[执行 cleansstate 和 cleanall] ``` §§相关问题§§ 1. 如何解决 Yocto 构建中因内核模块签名文件缺失导致的 `do_package_qa` 失败? 2. Yocto 的 `INSANE_SKIP` 机制适用于哪些质量检查场景? 3. 内核模块签名在 Yocto 构建程中通常由哪个任务负责? 4. 如何调试 Yocto 构建中的文件索引节点(inode)不匹配问题?</think>### 报错深度分析 根据补充日志,问题核心是**文件签名程与包拆分阶段的冲突**,具体表现为: 1. **直接错误原因** `FileNotFoundError: .../qca6490.ko.~signed~` 质量检查程尝试访问签名后的内核模块文件(`qca6490.ko.~signed~`),但该文件已被移动或删除。 2. **根本矛盾点** - **伪日志显示 inode 不匹配**: ``` inode mismatch: .../qca6490.ko ino 270699924 in db, 270700052 in request ``` 表明文件系统记录的 inode 与构建系统预期不一致,通常发生在: - 并行构建时文件被修改 - 签名过程修改了文件元数据 - 文件被替换但未同步索引 3. **签名程问题** `qca6490.ko.~signed~` 是内核模块签名后的临时文件,标准程应为: ```mermaid graph LR A[编译qca6490.ko] --> B[生成未签名模块] B --> C[签名生成qca6490.ko.~signed~] C --> D[重命名为qca6490.ko] ``` 但错误显示: - 签名文件在包拆分阶段被意外删除 - 质量检查仍尝试访问中间文件 ### 解决方案 1. **修复签名程** 在配方中(`qcacld32-ll-hsp_git.bb`)添加签名后处理: ```bitbake do_install:append() { # 确保签名文件保留到安装阶段 find ${D} -name "*.ko.~signed~" -exec sh -c 'mv {} $(dirname {})/$(basename {} | sed "s/.~signed~//")' \; } ``` 2. **跳过签名检查**(临时方案) 在 `local.conf` 添加: ```ini INSANE_SKIP:${PN} += "elf-sign" ``` 3. **清理重建** ```bash rm -rf tmp-glibc/work/sa525m-oe-linux/qcacld32-ll-hsp bitbake -c cleansstate qcacld32-ll-hsp bitbake qti-tele-image ``` 4. **验证模块签名** 重建后检查模块状态: ```bash modinfo build/tmp-glibc/work/*/qcacld32-ll-hsp/*/qca6490.ko | grep sig ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值