javaSE复习(4)(IO流与多线程)

视频出处 提取码: 24s1
IO流感觉都是大同小异 会了最基本的流操作 其他的流就差不多了
直观感觉多线程的学习还是比较简单的 但是对多线程的思想要求较高 以后在多做项目 领会多线程吧

随看随记

io流

File类

在这里插入图片描述

  • 静态成员变量
    • String类型 File.pathSeparator 与系统相关的路径分割符 win ; unix :
    • String类型 File.separator 与系统相关的目录名称分割符 win \ unix /
  • 构造函数
    • 输入字符串构造 File(“D:\idea”)
    • 输入父字符串路径和子字符串路径 File(“D:”,“idea”)
    • 输入File类型父串路径和子字符串路径File(file , “idea”)
    • 注意: 在输入单个字符串时要注意\ 要写成 \ \ 将其进行转义
    • 当带有分隔符时 File myFile = new File(“C:” + File.separator + “tmp” + File.separator+ “test.txt”);
  • FIle类的功能 默认已经进行了构造:File file = new File(“filepath”);
    • idea中的相对路径默认是在被idea接管的文件夹内创建
    • 创建功能
      • 创建文件: 返回Boolean file.creatNewFile(); 根据构造函数中的路径和文件名创建文件 只能创建文件如果没有文件类型 默认记事本类型 创建成功返回true 如果文件已经存在则不再创建 返回false
        • windows默认不区分大小写 而Linux和MACos时区分大小写的 windowsDOS 创建文件命令为echo LInux终端中创建文件命令为touch
      • 创建文件夹: 返回Boolean file.mkdirs(); 根据构造函数中的路径和文件名创建多层文件夹 创建成功返回true 如果文件夹已经存在则不再创建 返回false
        • windowsDOS 创建文件夹命令为md LInux终端中创建文件夹命令为mkdir
    • 删除功能
      • 返回Boolean file.delete(); 根据构造函数中的路径和名字删除文件或文件夹 删除成功返回true 删除失败返回false
      • 注意:
        • 删除失败包括两种:1.没有该路径 2. 该路径的文件正在打开
        • 删除方法直接从硬盘中删除,不经过回收站。
        • windowsDOS 删除文件命令为del 删除文件夹为rd
        • LInux终端中 删除文件命令为rm 删除文件夹为rm -d
    • 获取功能
      • 获取文件名 返回String file.getName(); 返回构造函数中的路径最后部分的名字
      • 获取文件的字节数 返回long file.length(); 返回构造函数中的路径的文件的字节数
      • 注意: length() 方法只能获得文件的字节数 对于文件夹的字节数是一个不确定数值,目前只知道文件夹不行,后面再看看原因什么的吧
      • 获取该绝对路径的文件对象 返回File file.getAbsoluteFile();返回该构造函数的绝对路径的文件 file.getAbsolutePath();是返回String类型 可以通过第一个的toString方法获得
      • 获取该父路径的文件对象返回File file.getParentFile();返回该构造函数的父路径的文件 file.getParen();是返回String类型 可以通过第一个的toString方法获得
      • 获取File构造方法中封装的路径中的文件和文件名()遍历一个目录 返回File[] file.listFiles()返回的是目录或文件的全路径 file.list()返回的是String类型 只返回文件或文件夹的名称 可以通过第一个方法遍历时进行getName()来获得
      • windowsDOS 查看文件夹的命令为dir Linux终端查看文件夹的命令为ls 一般使用ll
    • 判断功能
      • 判断路径是否存在 返回Boolean file.exists(); 存在该路径则返回True ,不存在则返回False
      • 判断文件夹是否存在 返回Boolean file.isDirectory(); 该路径是个文件夹则返回True ,不不是则返回False
  • 文件过滤器
    文件名传递参数 在FileFilter中调用方法accept(); 在accept()中进行判断如果返回true则放到数组中 false则过滤掉
    过滤器的流程

文件过滤器的简单lambda表达式写法 ->后面是过滤器的过滤条件

File[] listFiles = file.listFiles(pathname -> pathname.getName().endsWith(".java"));
//等同于
File[] listFiles = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".java");
            }
        });
  • 递归遍历所有的目录包括子目录中的java
  public void getAllDir(File pathname) {
        File[] files = pathname.listFiles(pathname1 -> pathname1.isDirectory()
                || pathname1.getName().toLowerCase().endsWith(".java"));
                
		//lambda表达式完全展开
        File[] files1 = pathname.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if (pathname.isDirectory()) {
                    return true;
                } else {
                    return pathname.getName().toLowerCase().endsWith("java");
                }
            }
        });

        for (File file : files) {
            if (file.isDirectory()) {
                getAllDir(file);
            } else {
                System.out.println(file);
            }
        }
    }

字节流

字节输出流

  • OutPutStream是所有字节输出流的超类 抽象类 每次只操作一个字节
  • 常用方法 均是无返回值
    • 写入内容
      • outPutStream.write(int b) 写入一个字节
      • outPutStream.write(byte[] b); 写入一个字节数组
      • outPutStream.write(byte[] b ,int index ,int length); 写入一个字节数组,从index开始写入length长度个字节
    • 关闭流对象
      • outputStream.close(); 关闭该输出流对象

FileOutputStream

  • 构造方法参数可以是 FIle:已经封装好的文件 或 String:字符串的文件名
    • OutputStream outputStream = new FileOutputStream(“pathName” 或 file); 注意需要抛出异常FileNotFoundException 继承于IOException
  • 流对象的使用流程
    1. 创建流的子类对象,绑定数据目的(路径文件或封装好的文件对象)
    2. 调用流对象的write方法
    3. close释放资源
    • 注意
      • 流对象的构造方法可以创建一个文件,如果该路径已经存在文件,则直接覆盖。
      • 对路径进行续写需要使用重载构造函数,将append参数赋值为true
      • 流对象是通过字节来进行写入的 会将int转成字节 经过编码表输出到记事本中 因此 outputStream.write(100);会写入一个d (d的ASCII码是100)
      • 输入的最简便办法是出入字符串,并将字符串转化成字节数组 outputStream.write(“hello\r\nworld”.getBytes());
  • 流对象的异常处理
			OutputStream outputStream = null;
        try {
            outputStream=new FileOutputStream("pathnamet",true);
            outputStream.write("hello\r\nworld".getBytes());
        } catch (IOException e) {
            System.out.println(e.getMessage());
            throw new RuntimeException("输出文件失败");
        }finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("输出文件失败");
            }
  • 注意:
  1. 保证流对象的作用域足够大 流文件的定义放在所有异常作用域的最外面
  2. catch里要写出怎么处理异常 要输出异常信息,寻找并解决问题
  3. 如果流对象创建失败不需要关闭资源

字节输入流

  • 构造方法和输入流读取文件的步骤与输出流相似 这里就不做记录了
  • 常用方法
    • 返回int read(); 执行一次就会自动读取下一个字节 ,返回读取到的字节
    • 使用该方法读取文件所有内容
		InputStream inputStream = new FileInputStream("pathname");
        int length = 0;
        while ((length = inputStream.read()) != -1) {
            System.out.print((char) length);
        }
  • 返回int read(byte[] b); 执行一次就会自动装满整个字节数组,返回读取到的字节的个数 数组主要起到缓冲的作用
  • 使用该方法读取文件所有内容
		InputStream inputStream = new FileInputStream("pathname");
        byte[] bytes = new byte[10];
        int length=0;
        while ((length=inputStream.read(bytes))!=-1){
            System.out.print(new String(bytes,0,length));
            /*当字节数组没有被装满时,会输出打印上一次数组未被覆盖的内容
            用length来限定打印的数组长度,正好打印到接受的字节数*/
            
        }

字节流复制文件

其本质就是在源文件出读取,在目标文件写入

		InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = new FileInputStream("inputPathName");
            outputStream = new FileOutputStream("outputPathName");
            byte[] bytes = new byte[1024*50];
            int length = 0;
            while ((length=inputStream.read(bytes))!=-1){
                outputStream.write(bytes,0,length);
            }
        } catch (IOException e) {
            new RuntimeException("文件复制异常");
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                new RuntimeException("输出流释放异常");
            } finally {
                try {
                    if (inputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    new RuntimeException("输入流释放异常");
                }
            }
        }
  • 注意:
    • 输入输出流对象一定要指向数据源文件和目标源文件,不要仅仅指向该文件的文件夹
    • 缓冲字节数组大小建议为1024byte即1kb的整数倍
    • 文件流的异常处理比较复杂 需要理清

字符流

字符流只能用于文本文件 尤其对于有中文汉字的文本文件更为适合
也是抽象类 需要使用其子类FileWriter 和 FileReader

字符输出流 Writer

  • 常用方法
    • 写内容
      • writer.write(int c) 写一个字符
      • writer.write(char[] c); 写一个字符数组
      • writer.write(char[] c ,int index ,int length); 写一个字符数组,从index开始写length长度个字节
      • writer.write(String s); 写一个字符串
    • 关闭流对象
      • writer.close(); 关闭该输出流对象
    • 输入流刷新 注意 每进行一次输出操作都必须对输出流进行刷新
      • writer.flush(); 刷新输出流

字符输入流 Reader

  • 常用方法
    • 写入内容
      • reader.read() 读取一个字符
      • reader.read(char[] c); 读取一个字符数组
      • 不能读取字符串
    • 关闭流对象
      • reader.close(); 关闭该输出流对象
        注意 输入流不需要每一次输出操作进行刷新
        其操作和字符流操作类似 这里就不多记录了

转换流

两个转换流的类 都是继承于字符流 Writer 和 Reader类

  • 接收字符输出字节 OutputStreamWriter
  • 接收字节输出字符 inputStreamReader
  • 其流程如图所示
    在这里插入图片描述
    inputStream = new FileInputStream("inputPathName");
    outputStream = new FileOutputStream("outputPathName");
    InputStreamReader ipsw = new InputStreamReader(inputStream,"GBK");
    OutputStreamWriter opsw = new OutputStreamWriter(outputStream,"UTF-8");
    char[] cbff= new  char[2];
    int length = 0;
    while ((length = ipsw.read(cbff))!=-1){
        opsw.write(cbff,0,length);
        opsw.flush();
    }
    ipsw.close();
    opsw.close();

注意: 当创建一个txt是GBK格式的时候 inputStreamReader的编码是GBK 但是要输出一个UTF-8格式时 需要在输出加“UTF-8” 其他和字符流 字节流差不多。

缓冲流

缓冲流的目的就是提高文件读写的顺序

字节缓冲流

除了构造方法不一样外 其余和不同字节流是一样的 主要是通过缓冲流对字节流进行缓冲 高效读写

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("outputPathname"));
BufferedInputStream bis= new BufferedInputStream(new FileInputStream("inputPathname"));

字符缓冲流

构造方法如下所示

BufferedWriter bw= new BufferedWriter( new FileWriter("writerPathNane"));
BufferedReader br= new BufferedReader( new FileReader("readerPathNane"));
  • 字符输出缓冲流特有方法
    • 无返回值 bw.newLine(); 输出一个换行 win为\r\n linux为\n
  • 字符输入缓冲流
    • 返回值String br.readLine(); 读取一行文本 以换行为结束 如果读取结束则返回-1
    • 它只识别换行 但是不会输出读取换行 如果要输出还需要 bw.newLine();
  • 带有编码表转换的字符缓冲流
BufferedWriter bw= new BufferedWriter( new OutputStreamWriter(new FileOutputStream(
        "E:\\......\\b.txt"),"UTF-8"));
BufferedReader br= new BufferedReader(new InputStreamReader(new FileInputStream(
        "E:\\......\\a.txt"),"GBK"));
  • 实现文本复制的流程
		BufferedWriter bw= new BufferedWriter( new OutputStreamWriter(new FileOutputStream(
                "E:\\......\\b.txt"),"UTF-8"));
        BufferedReader br= new BufferedReader(new InputStreamReader(new FileInputStream(
                 "E:\\......\\a.txt"),"GBK"));
        String str=null;
        while ((str= br.readLine())!=null){
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        bw.close();
        br.close();

小结

  1. 实线表示继承 虚线表示调用
  2. 虽然调用的是超类 但是都是创建其子类的对象 或者通过多态进行创建超类
    在这里插入图片描述

Properties

继承自Hashtable ,实现了Map接口
在.properties文件中 用#表示注释 # 1=a

  • 特有方法
    • setProperty(String key , String Value) 等同于map的put方法
    • getProperty(String key) 等同意map的get方法
    • 加载 无返回值 load 读取字节或字符输入流的键值对,保存到集合中
      • pro.load(InputStream in)
      • pro.load(Reader read)
    • 存储 无返回值 store 接受字节或字符输出流的键值对,写到文件中保存
      • pro.store(outputStream, “” ) ; 后面是一个字符串参数,必须要加,一般只加一个空字符串
      • pro.store( Writer write)

序列化

序列化是对象的序列化 对于静态的成员变量无法将其序列化

  • 要序列化的类必须要实现serializable接口

对象流

  • ObjectOutputStream
		InputStream inputStream = new FileInputStream("FileInputPathName");
        OutputStream outputStream = new FileOutputStream("FileOutputPathName");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(new Person("zhangsan", 23));
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object obj = objectInputStream.readObject();
        System.out.println(obj);
        objectInputStream.close();
        objectOutputStream.close();
  • ObjectOutputStream 构造方法如上
  • 特有方法 无返回值 objectOutputStream.writeObject(new Person(“zhangsan”, 23)); 写入特有对象进行序列化
  • ObjectInputStream 构造方法如上 其输入流必须是序列化的文件
  • 特有方法 返回Object objectInputStream.readObject(); 对序列化文件进行反序列化读取
    readObject的返回值是Object类型 如果需要低权限类需要强转
  • 关键字 transient 阻止类中的成员变量序列化
  • 当对象流写入后 但是在读取前修改了类 这时类的序列号会被改变 会产生序列号冲突异常
    在这里插入图片描述
  • 因此需要对序列化类设置一个特定的序列号 号是随意的
private static final long serialVersionUID = -1499574590977726397L;

打印流

  1. 打印流只能做输出的动作 不做输入 因此只需要关注数据目的即可
  2. 为其他流添加功能
  3. 永远不会抛出IOExeption 但是会抛出其他异常
  • PrintStream
    • 可以接收File String OutputStream三个类型
  • PrintWriter
    • 可以接收File String OutputStream Writer三个类型
    • 他们的方法都是一样的 Print(cont) Println(cont) Print 的输出是原样输出 而write()输出是需要走编码表的。
    • Print() 和 Println() 中针对char[] 重写了方法 因此对字符数组进行Print操作时会输出字符数组中的内容 而其他类型的数组只输出其数组的地址
  • 打印流的自动刷新 其需要满足的条件
    • 输出的数据目的必须是流对象
    • 必须调用Println(), Printf(),format() 方法三个中的一个
    • 构造方法的第二个参数autoFlush 置为true
  • 打印流的文本复制
    • 读取数据源用BufferedInputStream/BufferedReader
    • 写数据用PringWriter 和 Println() 方法、

多线程

线程类的创建与使用

继承Thread 重写了run()方法

public class Subthread01 extends Thread{
    @Override
    public void run() {
       运行的方法内容
    }
}
  • 直接调用类中的run()时 与普通方法没有任何区别 通过start()调用的run方法才会开启线程
  • 线程开启的步骤
  1. 创建和启动一个线程
  2. 创建Thread子类对象
  3. 子类对象调用方法start()
  4. 让线程程序执行, JVM调用线程中的run
		Subthread01 subthread01 = new Subthread01();
        subthread01.setName("SbuThread01");
        subthread01.start();
        for (int i = 0; i < 100; i++) {
            Thread td=Thread.currentThread();
            System.out.println(td.getName());
        }

  • 当直接打印Thread.currentThread()时 内容为
    Thread[main,5,main] Thread[Thread-0,5,main]
    分别为 [当前线程 线程优先级 程序的主线程]
  • 获取线程名字方法
    • 静态方法 返回值Thread Thread.currentThread(); 获取当前线程
    • 返回值String td.getName(); 获取线程的线程名字 继承自Thread而且无法被重写
  • 设置线程的名字
    • 无返回值 subthread01.setName(“SbuThread01”);
  • 线程休眠
    • Thread.sleep(millis) 根据millis毫秒值进行休眠 时间过后苏醒 要处理异常 在线程类中无法抛出异常 因为父类中没有该异常 因此只能try catch finally 进行处理

实现Runable接口

创建方法与继承Thread类似

public class SubThread02 implements Runnable{
	@Override
    public void run() {
       运行的方法内容
    }
 }

主要实现了线程类与对象的解耦

 Thread thread = new Thread(new SubThread02());
    thread.start();
    for (int i = 0; i < 100; i++) {
        Thread td=Thread.currentThread();
        System.out.println(td.getName());
    }

通过匿名内部类的方式

			Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread");
                }
            }
        });
      	 	 Thread thread = new Thread(() -> {
      	   		 for (int i = 0; i < 100; i++) {
       	    	 System.out.println("Thread");
        	    }
        	});
  • 线程的六个状态 (线程的一生)
    在这里插入图片描述
  • 线程池
    1. 使用工厂类Executors中的静态方法创建线程对象,指定线程的个数
    2. static ExecutorService newFixedThreadPool(int个数)返回线程池对象 返回的是ExecutorService接口的实现类(线程池对象)
    3. 接口实现类对象,调用方法submit (Ruunable r)提交线程执行任务
 		ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.submit(() -> {for (int i = 0; i < 100; i++)
            System.out.println(Thread.currentThread().getName()+"...."+i);});
        executorService.submit(() -> {for (int i = 0; i < 100; i++)
            System.out.println(Thread.currentThread().getName()+"...."+i);});
        executorService.submit(() -> {for (int i = 0; i < 100; i++)
            System.out.println(Thread.currentThread().getName()+"...."+i);});

线程安全问题

同步代码块

 private Object object = new Object();
 synchronized (object){
                if (ticketsTotal>0){
                    System.out.println(
                    Thread.currentThread().getName()+"还有"+ticketsTotal--+"张票");
                }
            }

()中object被称为同步锁 只有得到同步锁的线程才能去执行同步代码块的程序 即在同代码块中的线程是单线程的 无法多线程同时执行

同步方法

  • 将同步代码块的内容封装成一个方法 并将该方法设置 synchronized 关键字 这个方法就是同步方法 其同步锁是 this 对于静态方法的同步锁是本类类名.class
public synchronized void sailTickets() {
        if (ticketsTotal > 0) {
            System.out.println(
            Thread.currentThread().getName() + "还有" + ticketsTotal-- + "张票");
        }
    }

Lock接口

通过Lock() 接口的方法 代替同步代码块 进一步提高其安全性 主要防止当线程出现异常的时候 锁不会被释放的情况
在不管是否出现异常 都会在finally中释放锁

 private int ticketsTotal = 1000;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (ticketsTotal > 0) {
                try {
                    Thread.sleep(20);System.out.println(
                            Thread.currentThread().getName() + 
                            "还有" + ticketsTotal-- + "张票");
                } catch (InterruptedException e) {

                }finally {
                    lock.unlock();
                }
            }
        }
    }

死锁现象

(感觉这个需要在后面真的做项目才遇到 看了视频的案例感觉有点空洞)
在这里插入图片描述

线程之间的通信

  • 判断同步代码块是否成功主要看两个方面
  1. 同步代码块是否覆盖所有执行内容
  2. 同步代码块的同步锁是不是同一个内容
  • 线程之间的交替通信
    • input 线程执行完后唤醒notify() output线程
    • output 线程执行完后唤醒notify() input线程
      其思想
      在这里插入图片描述
      其代码如下所示
public class Resource {
    private String name;
    private String sax;
    private boolean flag = true;
    private Resource() {}

    public static final Resource RESOURCE = new Resource();
    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public String getSax() {
        return sax;
    }

    public void setSax(String sax) {
        this.sax = sax;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Input implements Runnable {
    public Resource resource;

    public Input(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            synchronized (resource) {
                if (!resource.isFlag()) {
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                    }
                }
                if ((i % 2) == 0) {
                    resource.setName("张三");
                    resource.setSax("男");
                } else {
                    resource.setName("lisi");
                    resource.setSax("nv");
                }
                resource.setFlag(!resource.isFlag());
                resource.notify();
            }
            i++;
        }
    }
}

public class Output implements Runnable {
    public Resource resource;

    public Output(Resource resource) {
        this.resource = resource;
    }
    @Override
    public void run() {

        while (true) {
            synchronized (resource) {
                if (resource.isFlag()) {
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {}
                }
                System.out.println( resource.getName() + "......" + resource.getSax());
                resource.setFlag(!resource.isFlag());
                resource.notify();
            }
        }
    }
}
public static void main(String[] args) {
        Resource resource=Resource.RESOURCE;
        
        Thread threadInput = new Thread(new Input(resource));
        Thread threadOutput = new Thread(new Output(resource));
        threadInput.start();
        threadOutput.start();
    }

  1. 锁是否唯一 如果要使得锁唯一 则以定不能去new新对象 本案例是通过构造函数去接受main中的Resource对象 实现同步锁的唯一
  2. 线程的唤醒问题 在使用wait() 将线程等待之前 除了要将标志置反以外 还要对等待的线程进行唤醒操作 即notify()
  3. wait() 和 nofify() 的 是同步锁的对象方法 应该使用resource.wait() 和resource.nofify()
  4. 只有在同步代码块内的resource才能被称之为同步锁
  5. 一定是要输出先等待 输入后等待 输入反了的话 第一个输出就是null了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值