线程

多线程实现的方式

创建Thread子类实现多线程

实现步骤:

  1. 创建一个Thread类的子类
  2. 在Thread类的子类中重写run方法,在run方法中写线程任务,或者在run方法中调用要执行的线程任务方法
  3. 创建Thread类的子类对象,
  4. 调用start()方法,开启新的线程,JVM会去调用run方法(注意:不能直接调用run方法

注意事项:

  1. 同一个对象不能多次执行start()方法,否者会报异常
  2. 不是直接调用run方法,而是调用start方法,让JVM去调用run方法
  3. java程序属于抢占式调度

ex:

public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {//传递线程名字
        super(name);//调用父类方法给线程命名
    }

    @Override
    public void run() {
            try{
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName());//获取当前线程名字
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }
}

实现Runnable接口来实现多线程

实现步骤:

  1. 创建Runnable接口的实现类
  2. 在实现类中重写Runnable中的run方法
  3. 创建一个Runnable接口实例
  4. 创建Thread类的实例,构造方法中传递Runnable接口的实例对象
  5. 让Thread对象实例调用Thread类中的start方法,然后JVM会去调用run方法开启新的线程(注意JVM并不能开启新的线程,是JVM调用底层系统方法去开启新线程)

ex:

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
    //2.在实现类中重写Runnable接口中的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo05Runnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实例
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread类的实例,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run);
        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

实现Callable接口来实现多线程

使用匿名内部类简化多线程的创建

匿名:没有名字
内部类:写在其他类的内部的类

匿名内部类的作用:简化代码(把子类继承父类,重写父类的方法,创建子类对象合成一步完成
匿名内部类的最终产物是实现类对象/子类,而这个类没有名字

格式:new 父类/接口(){重写父类/接口中的方法};

ex:

		//使用匿名内部类实现多线程
        new Thread("王冰冰"){//相当于Thread的子类
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();//开启新线程
        //匿名接口实现多线程
        Runnable run = new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        new Thread(run,"高圆圆").start();//开启新的线程

        //简化写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        },"古力娜扎").start();

使用Runnable接口创建多线程的优点

  1. 避免了单继承的局限性
    一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其它类了
    而实现Runnable接口,还可以继承其他的类,实现其他的接口
  2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
    实现Runnable接口的方式,把设置线程任务和开启新的线程分离(解耦)
    实现类中重写run方法:用来设置线程任务
    创建Thread类对象,调用start方法:用来开启新的线程新线程

设置线程名称,获取线程名称

设置线程的名称有两种方法:

  • 有参构造,传递线程的名称,调用父类的有参构造,让父类给线程起一个名称

  • 调用setName(要设置的线程名称)方法

获取线程名称的方法有两种:

  • 在Thread类或它的子类中调用getName()

  • 使用Thread类的静态方法currentThread()方法,然后调用getName()方法

    注意:没有继承Thread的类中只能用方法2

线程休眠

线程休眠,调用sleep(long millis)//单位为毫秒值,sleep方法是静态方法

线程安全问题

解决办法:

  1. 同步代码块

    格式:synchronized(锁对象){
    可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

    注意:
    1.通过代码块中的锁对象,可以是任意对象
    2.但是必须要保证多线程使用的是同一个锁对象
    3.锁对象的作用:
    把同步代码块锁住,只让一个线程在同步代码块中执行

  2. 同步方法

    格式:
    修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

    注意:注意如果是静态方法的锁对象,不能使用this,因为静态优先于对象产生
    静态方法的锁对象是本类的class属性-->class文件对象(反射)

  3. Lock锁

    解决线程安全问题的第三种方法:使用Lock锁

    java.util.concurrent.locks.Lock接口

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作

    Lock接口中的常用方法:

    1. void lock()获取锁
    2. void unlock()释放锁

    java.util.concurrent.locks.ReentrantLock(可重入锁)是Lock接口的实现类

    使用步骤:

    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能会出现安全问题的代码调用Lock接口中的方法lock()获取锁
    3. 在可能会出现安全问题的代码调用Lock接口中的方法unlock()释放锁

ex:

			lock.lock();//获取锁
            if (ticket > 0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "-->" + ticket + "张票");
                    ticket--;//卖了一张,就减一
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();//释放锁
                }

线程之间的通信

wait(),notify()方法放在同步代码块中,因为wait()和notify()方法只能同时执行一个

ex:

public static void main(String[] args) {
        //创建锁对象
        Object obj = new Object();
        //创建一个顾客线程
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("我要买10个豆沙包子");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后会继续执行下面的代码
                    System.out.println("包子做好了,可以开吃了");
                }
            }
        }.start();

        //老板线程
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("老板做了5秒钟的包子");
                    obj.notify();
                }
            }
        }.start();
    }

线程池

JDK1.5之后提供的线程池

java.util.concurrent.Executors:线程池的工厂类,用来生成线程池

Executors类中的静态方法:

static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池

​ 参数:

​ int nThreads:创建线程池中包含的线程数量

​ 返回值:

​ ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)java.util.concurrent.ExecutorService:线程池接口用来从线程池中获取线程,调用start方法,执行线程任务

**submit(Runnable task)**提交一个Runnable任务用于执行
关闭/销毁线程池的方法void shutdown()

线程池的使用步骤:

  1. 使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool()生产一个指定线程数量的线程池
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
  4. 调用ExecutorService中的方法shutdown销毁线程池(不推荐)

ex:

public class Demo01ThreadPool {
    public static void main(String[] args) {
        //1.调用Executors中的静态方法newFixedThreadPool来创建指定数量的线程池
        ExecutorService services = Executors.newFixedThreadPool(2);
        //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
        services.submit(new RunnableImpl());
        services.submit(new RunnableImpl());
        services.submit(new RunnableImpl());
        //会一直运行,因为线程池一直都在,除非调用shutdown方法,销毁线程池,但不推荐这样做
        services.shutdown();
        //如果还继续调用submit方法,则会报错RejectedExecutionException
        services.submit(new RunnableImpl());//RejectedExecutionException
    }
}
public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "创建了一个新的线程");
    }
}

Lambda表达式

面向对象编程思想:做一件事情,找一个能够解决这个事情的对象,然后调用对象的方法完成事情
函数式编程思想:只要获取到结果,谁去做的,如何做的都不重要,重视的是结果,而不是过程

解决冗余的Runnable代码

使用匿名内部类实现多线程

ex:

//使用匿名内部类实现多线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "创建了新的线程");
            }
        }).start();

使用Lambda表达式实现多线程

ex:

//使用Lambda表达式实现多线程
        new Thread(()-> {
                System.out.println(Thread.currentThread().getName() + "创建了新的线程");
            }
        ).start();

Lambda的标准格式

由三部分组成

  • 一些参数
  • 一个箭头
  • 一段代码

格式:

​ (参数列表)-> {一些重写方法的代码};

解释说明:

():接口的中的抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
->:传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法的方法体

ex:

public class Dome04Cook {
    public static void main(String[] args) {
        //调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象
        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭了");
            }
        });

        //使用Lambda
        invokeCook(()->{
            System.out.println("吃饭了");
        });
    }

    //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood()
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}
public interface Cook {
    public abstract void makeFood();
}

Lambda表达式是可推导,可以省略

凡是根据上下文能够推导出来的内容,都可以省略

  1. (参数列表) 括号中的参数列表的数据类型可以省略

  2. (一个参数) 可以省略类型和()

  3. {一些代码} 如果{}中的代码只有一行,无论是否有返回值都可以省略return和{}和分号

    注意:要省略三个必须一起省略,参数中的类型也可以省略

    ex:invokeCalc(20,30,(a,b) -> a + b);

ex:

package top.lukeewin.demo12;

/*
* 给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和
* */
public interface Calculator {
    //定义一个计算两个int 类型的和的方法,并返回结果
    public abstract int calc(int a,int b);
}

package top.lukeewin.demo12;

public class Demo06Lambda {
    public static void main(String[] args) {
        //调用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
        invokeCalc(10, 20, new Calculator() {
            @Override
            public int calc(int a, int b) {
                return a + b;
            }
        });

        //使用Lambda表达式简化匿名内部类的书写
        invokeCalc(120,130,(int a,int b)->{
            return a + b;
        });

        //优化Lambda表达式
        invokeCalc(20,30,(a,b) -> a + b);
    }

    /*
    * 定义一个方法,参数传递两个int
    * 参数传递Calculator接口
    * 方法内部调用Calculator中的方法calc计算两个数的和
    * */
    public static void invokeCalc(int a,int b,Calculator c){
        int sum = c.calc(a,b);
        System.out.println(sum);
    }
}

使用Lambda表达式的前提条件

  1. 必须是接口,且接口中只有一个抽象方法
  2. 必须具有上下文推断

备注:有且只有一个抽象方法的接口称为函数式接口

文件File类

位置:java.io.File

文件和目录路径名的抽象表示形式

java把电脑中的文件和文件夹(目录)封装成了一个File类,我们可以使用File类中的方法对文件和文件夹进行操作

有哪些操作呢?如下:

  1. 创建文件/文件夹
  2. 删除文件/文件夹
  3. 获取文件/文件夹
  4. 判断文件/文件夹
  5. 对文件夹进行遍历
  6. 获取文件的大小

File类是一个与操作系统无关的类,任何的操作系统都可以使用这个类中的方法

重点记住这三个单词:

  • file:文件
  • directory:文件夹(目录)
  • path:路径

File类中的字段

static String pathSeparator //与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar//与系统有关的路径分隔符

static String separator //与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串
static char separatorChar //与系统有关的默认名称分隔符

所以路径不能写死,因为不同的操作系统,路径分隔符不同
   c:\develop\a\a.txt windows
   c:/develop/a/a.txt linux
   推荐的写法是:
       "c:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt"
package top.lukeewin.demo13File;

import java.io.File;

public class Demo01File {
    public static void main(String[] args) {
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);//;
        System.out.println(File.separator);//\
        
    }
}

路径分为:==绝对路径==,==相对路径==

  • 绝对路径:是一个完整的路径,以盘符开始的路径(以根路径开始)
    • C:\\a.txt
    • D:\\java\\demo\\Hello.java
  • 相对路径:是一个简化的路径,相对是指相对于当前项目的根目录
    • 例如完整的路径为:D:\\java\\demo\\Hello.java
    • 如果当前项目在D:\\java\\demo目录下,那么写程相对路径为:Hello.java

==注意==:路径是不区分大小写的

​ 路径中的目录分隔符,在windows中是使用反斜杠\,反斜杠是转义字符,所以路径中的反斜杠要写两个,才能转义为一个反斜杠

File类的构造方法

File类中构造方法1

​ File(String pathname)//通过将给定的路径名字字符串转化为抽象路径名来创建一个新File实例

参数:String pathname:字符串的路径名称

​ 路径可以是以文件结尾,也可以是以文件夹结尾

​ 路径可以是相对路径,也可以是绝对路径

​ 路径可以是已经存在的,也可以是不存在的

​ 创建File对象,只是把字符串格式的路径封装为File对象,不考虑路径的真假情况

private static void show01() {
        File f1 = new File("D:\\Works\\IdeaProjects\\advanced_code\\java_code\\src\\top\\lukeewin\\demo13File\\a.txt");//不存在的文件
        System.out.println(f1);//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\a.txt
        File f2 = new File("D:\\Works\\IdeaProjects\\advanced_code\\java_code\\src\\top\\lukeewin\\demo13File\\Demo01File.java");//存在的文件
        System.out.println(f2);//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\Demo01File.java
        File f3 = new File("b.txt");//相对路径
        System.out.println(f3);//b.txt
    }

FIle类中的构造方法2

​ File(String parent,String child)根据parent路径名称字符串和child路径名称字符串创建一个新File实例

​ 参数:==String== parent:父路径

​ String child:子路径

好处:父路径和子路径可以单独写,使用起来比较灵活,父路径和子路径都可以变化

public static void main(String[] args) {
//        show01();
        show02("D:\\","a.txt");
    }

private static void show02(String parent,String child) {
        File file = new File(parent,child);
        System.out.println(file);//D:\a.txt
    }

File类中的构造方法3

File(File parent,String child) 根据parent抽象路径和child路径字符串创建一个新File实例

参数:把路径划分为两部分:

​ ==File== parent:父路径

​ String child:子路径

好处:1. 父路径和子路径可以单独写,使用起来比较灵活,父路径和子路径都可以变化

​ 2. 父路径是File类型,可以使用File中的方法对路径进行一些操作,再使用路径创建对象

private static void show03() {
        File parent = new File("D:\\demo");
        File file = new File(parent,"HelloWorld");
        System.out.println(file);//D:\demo\HelloWorld
    }

File类中的常用方法

获取方法

  1. public String getAbsolutePath()返回此File的绝对路径名字字符串

  2. public String getPath() 将此File转换为路径字符串

  3. public String getName()返回由此File表示的文件或目录的名称

  4. public long length() 返回由此FIle表示的文件的长度

  5. getAbsolutePath()方法,不论File类型是绝对路径还是相对路径都返回绝对路径

import java.io.File;

public class Demo03File {
    public static void main(String[] args) {
        show01();
    }

    private static void show01() {
        File file1 = new File("D:\\Works\\IdeaProjects\\advanced_code\\java_code\\src\\top\\lukeewin\\demo13File\\Demo01File.java");
        String absolutePath = file1.getAbsolutePath();
        System.out.println(absolutePath);//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\Demo01File.java

        File file2 = new File("Demo01File.java");
        String absolutePath1 = file2.getAbsolutePath();
        System.out.println(absolutePath1);//D:\Works\IdeaProjects\advanced_code\Demo01File.java


    }
}
  1. getPath()方法返回的是File传递的路径,传递的是绝对路径返回的就是绝对路径,传递的是相对路径返回的就是相对路径。
private static void show02() {
        File f1 = new File("D:\\Works\\IdeaProjects\\advanced_code\\java_code\\src\\top\\lukeewin\\demo13File\\Demo01File.java");
        File f2 = new File("Demo01File.java");
        System.out.println(f1.getPath());//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\Demo01File.java
        System.out.println(f2.getPath());//Demo01File.java

        //这了的toString方法实质是调用了getPath方法
        System.out.println(f1.toString());//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\Demo01File.java
        System.out.println(f1);//D:\Works\IdeaProjects\advanced_code\java_code\src\top\lukeewin\demo13File\Demo01File.java
    }
  1. getName()方法获取File中的最后一个字符串
private static void show03() {
        File f1 = new File("D:\\works\\java\\java.txt");
        File f2 = new File("D:\\works\\java");
        System.out.println(f1.getName());//java.txt
        System.out.println(f2.getName());//java
    }
  1. length()方法返回File中指定的文件或文件夹的大小,单位为字节
private static void show04() {
        File f1 = new File("D:\\Works\\javalianxi");
        long length = f1.length();
        System.out.println(length);
        File f2 = new File("D:\\Works\\javalianxi\\a.txt");
        System.out.println(f2.length());
        File f3 = new File("D:\\Works\\javalianxi\\abc.txt");
        System.out.println(f3.length());//0 当文件不存在时,length返回0
    }

判断方法

  1. public boolean exists()判断File中构造方法传递的文件或文件夹是否存在

    存在返回true

    不存在返回false

    package top.lukeewin.demo13File;
    
    import java.io.File;
    
    public class Demo04File {
        public static void main(String[] args) {
            show01();
        }
    
        /*
        * public boolean exists()//表示File中的文件或文件夹是否存在
        * 存在:true;
        * 不存在:false;
        * */
        private static void show01() {
            File f1 = new File("D:\\Works\\java");
            System.out.println(f1.exists());//true
            File f2 = new File("D:\\hello");
            System.out.println(f2.exists());//false
        }
    }
    
  2. public boolean isDirectory()用于判断File构造方法中指定的路径是否是文件夹

  3. public boolean isFile()用于判断File构造方法中指定的路径是否是文件

    注意:文件夹与文件是互斥的

    ​ 这两个方法使用的前提是路径必须存在,否则返回false

    private static void show02() {
            File f1 = new File("D:\\Works\\javalianxi\\chap10");
            File f2 = new File("D:\\Works\\javalianxi\\a.txt");
    
            System.out.println(f1.isDirectory());//true
            System.out.println(f1.isFile());//false
    
            System.out.println(f2.isDirectory());//false
            System.out.println(f2.isFile());//true
        }
    

    对上面方法的改进,在使用isDirectory()isFile()方法前先要判断是否存在

    private static void show02() {
            File f1 = new File("D:\\Works\\javalianxi\\chap10");
            File f2 = new File("D:\\Works\\javalianxi\\a.txt");
            //构造一个不存在的文件或文件夹,返回的是false
            File f3 = new File("D:\\Works\\javalianxi\\abc.txt");
    
            if (f1.exists()){
                System.out.println(f1.isDirectory());//true
                System.out.println(f1.isFile());//false
            }
    
            if (f2.exists()){
                System.out.println(f2.isDirectory());//false
                System.out.println(f2.isFile());//true
            }
    
            if (f3.exists()){
                System.out.println(f3.isDirectory());
                System.out.println(f3.isFile());
            }else {
                System.out.println(f3.getPath() + "路径中不存在" + f3.getName() + "文件或文件夹");
            }
        }
    

创建和删除方法

  • public boolean createNewFile()当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
package top.lukeewin.demo13File;

/*
	注意:1.此方法只能创建文件,不能创建文件夹
		 2.当文件已经存在时,返回false
		 3.创建文件的路径必须存在,否则会抛出异常
		 4.createNewFile方法存在IOException,所以必须要处理该异常,要么继续抛出,要么捕获处理
*/

import java.io.File;
import java.io.IOException;

public class Demo05File {
    public static void main(String[] args) {
        show01();
    }
    
    private static void show01() {
        File f1 = new File("D:\\Works\\javalianxi\\c.txt");
        try {
            boolean b1 = f1.createNewFile();
            System.out.println("是否创建成功: " + b1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • public boolean delete()删除由此File表示的文件或文件夹。
/*
* delete()方法可以删除文件夹和文件,注意删除的内容不会经过回收站
* */
    private static void show02() {
        //删除文件夹,注意java.txt是show01()方法创建的文件夹,而不是文件
        File f1 = new File("D:\\Works\\javalianxi\\java.txt");
        boolean b1 = f1.delete();
        System.out.println(b1);
        //删除文件
        System.out.println(new File("D:\\Works\\javalianxi\\a.txt").delete());
 }
  • public boolean mkdir()创建由此File表示的文件夹
  • public boolean mkdirs()创建由此File表示的文件夹,包括任何必须但不存在的父文件夹
       public boolean mkdir()//创建单级空的文件夹
       public boolean mkdirs()//创建多级空的文件夹
          //创建文件夹的路径和名称是在构造方法中给出的(即构造方法中的参数)
           //返回值:boolean
           //true:文件夹不存在,成功创建文件夹
           //false:文件夹已经存在了,创建文件夹失败
           
       private static void show01() {
               //创建单级文件夹
               File f1 = new File("D:\\Works\\javalianxi\\a");
               boolean b1 = f1.mkdir();
               System.out.println(b1);
               
               //mkdir()不能创建多级文件夹
               File f2 = new File("D:\\Works\\javalianxi\\a\\b\\c");
               System.out.println(f2.mkdir());
       
               //创建多级文件夹
               File f3 = new File("D:\\Works\\javalianxi\\a\\b\\c");
               System.out.println(f3.mkdirs());
       
               //注意:mkdir和mkdirs方法创建的只能是文件夹,不会是文件
               File f4 = new File("D:\\Works\\javalianxi\\java.txt");
               System.out.println(f4.mkdir());
        }

File类遍历文件夹

  • public String[] list():返回一个String类型的数组,表示该File目录的所有文件和目录
  • public File[] listFiles():返回一个File数组,表示该File目录中的所有的子文件夹

注意:

  1. 如果构造方法中给定的目录的路径不存在,则会抛出空指针异常

    2.如果构造方法中给出的路径不是一个目录,也会抛出空指针异常

/*
* list()方法能够获取隐藏的文件和文件夹
* */
private static void show01() {
    File f1 = new File("D:\\Works\\javalianxi\\chap10");
    String[] list = f1.list();
    for (String s : list) {
        System.out.println(s);
     }
    System.out.println("=============");
      
    //遍历的不是文件夹,会报空指针异常java.lang.NullPointerException
    File f2 = new File("D:\\Works\\javalianxi\\b.txt");
    String[] list1 = f2.list();
    for (String s : list1) {
        System.out.println(s);//java.lang.NullPointerException
    }
}
//获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
private static void show02() {
        File f1 = new File("D:\\Works\\javalianxi\\chap10");
        File[] files = f1.listFiles();
        for (File f : files) {
            System.out.println(f);//会把完整的路径打印出来,与list()方法不同,list()方法只是打印出指定目录下的目录和文件
        }
    }

递归

  • 递归的分类:
    • 直接递归
    • 间接递归

直接递归

main(){
    a();
}
a(){
    a();
}

间接递归

main(){
    b();
}
b(){
    c();
}
c(){
    b();
}

注意事项:

  1. 递归一定要有条件限制能够保证递归能够停止下来,否则会发生栈内存溢出
  2. 递归的次数不能太多,否则会发生栈内存溢出
  3. 构造方法禁止递归

递归的使用前提:

​ 当调用方法的时候方法的主体不变,每次调用方法的参数不同,可以使用递归

package top.lukeewin.demo14Recursion;

public class Demo01DiGui {
    public static void main(String[] args) {
//        method01();
//        method02(1);
    }

    /*
    * 禁止递归构造方法,直接编译报错
    * */
    public Demo01DiGui() {
//        Demo01DiGui();
    }

    //不能无限递归,会报StackOverflowError
    private static void method01() {
        System.out.println("method01方法");//java.lang.StackOverflowError
        method01();
    }

    //递归的次数也不能太多,会报StackOverflowError
    private static void method02(int i){
        System.out.println(i);//StackOverflowError
        if (i == 20000){
            return;
        }
        method02(++i);
    }
}

练习1:计算1到n之间的和

package top.lukeewin.demo14Recursion;

public class Demo02RecursionSum {
    public static void main(String[] args) {
        int s = sum(100);
        System.out.println(s);
    }

    //计算1到n之间的和
    private static int sum(int n){
        if (n == 1){
            return 1;
        }
        return n + sum(n-1);
    }
}

练习2:求n!

package top.lukeewin.demo14Recursion;

public class Demo03JieCheng {
    public static void main(String[] args) {
        System.out.println(jc(5));
    }

    //求n!
    private static int jc(int n){
        if (n == 1){
            return 1;
        }
        return n*jc(n-1);
    }
}

练习3:递归打印多级目录

public class Demo04RecursionFile {
    public static void main(String[] args) {
        File f = new File("D:\\Works\\javalianxi");
        getAllFiles(f);
    }

    /*
    * 定义一个方法递归遍历传过来的文件夹
    * */
    private static void getAllFiles(File dir){
        System.out.println(dir);//把文件夹输出
        File[] files = dir.listFiles();
        for (File f : files) {
            //判断是否为文件夹,如果是文件夹则递归调用
            if (f.isDirectory()){
                getAllFiles(f);
            }else {//否则输出文件
                System.out.println(f);
            }
        }
    }
}

练习4:打印指定文件夹下的指定类型的文件

package top.lukeewin.demo14Recursion;

import java.io.File;

public class Demo05RecursionFile {
    public static void main(String[] args) {
        File f = new File("D:\\Works\\javalianxi");
        getFiles(f);
    }
    //打印目录下的指定类型的文件
    private static void getFiles(File dir){
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()){
                getFiles(file);
            }else {
                if (file.getName().toLowerCase().endsWith(".txt")){
                    System.out.println(file);
                }
            }
        }
    }
}

FileFilter和FilenameFilter文件过滤器接口

File[] listFiles(FileFilter filter)

作用:用来过滤文件

抽象方法:用来过滤文件的方法

boolean accept(File pathname)测试指定的抽象路径名是否应该包含某个路径名列表中

参数:File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象

File[] listFiles(FilenameFilter filter)

作用:用于过滤文件名称

抽象方法:用来过滤文件的方法

​ boolean accept(File dir,String name)测试指定的文件是否应该包含摸一个文件列表中。

​ 参数:File dir :构造方法中传递的被遍历的目录

​ String name:使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称

注意:两个过滤器接口没有实现类,需要自己编写,重写accept方法,并定义过滤的规则

使用FileFilter接口实现文件名称过滤

package top.lukeewin.demo14Recursion;

import java.io.File;

public class Demo07GetFile {
    public static void main(String[] args) {
        File f = new File("D:\\Works\\javalianxi");
        getFiles(f);
    }

    //编写获取参数中指定路径下的指定格式的文件
    private static void getFiles(File dir){
//        System.out.println(dir);
        File[] files = dir.listFiles(pathname -> {
            if (pathname.isDirectory()){
                return true;
            }
            return pathname.getName().toLowerCase().endsWith(".txt");
        });//把过滤好的文件放到文件数组中
        for (File file : files) {//遍历文件数组
            if (file.isDirectory()){//判断是否是文件夹,是则继续调用自己
                getFiles(file);
            }else {//不是文件夹则可以打印输出
                System.out.println(file);
            }
        }
    }
}

使用FilenameFilter接口实现文件名称过滤

package top.lukeewin.demo14Recursion;

import java.io.File;
import java.io.FilenameFilter;

public class Demo08GetFile {
    public static void main(String[] args) {
        File f = new File("D:\\Works\\javalianxi");
        getFiles(f);
    }

    private static void getFiles(File f) {
        //使用匿名内部类
        /*File[] files = f.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java");
            }
        });*/
        //使用lambda表达式
        File[] files = f.listFiles((dir,name) -> new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java"));
        for (File file : files) {
            if (file.isDirectory()){
                getFiles(file);
            }else {
                System.out.println(file);
            }
        }
    }
}

分析:new File(dir,name).isDirectory()是把传递过来的dir和name封装为File对象,然后调用File类中的isDirectory()

IO流

字节流

I:表示input

O:表示output

流:数据

分类:

  • 字节流

字节输入流:InputStream抽象类,是所有字节输入流的父类

字节输出流:OutputStream抽象类,是所有字节输出流的父类

  • 字符流

字符输入流:Reader抽象类,是所有字符输入流的父类

字符输出流:Writer抽象类,是所有字符输出流的父类

这里的输入:磁盘->内存

这里的输出:内存->磁盘

java.io.OutputStream字节输出流

​ 此抽象类是表示输出字节流的所有类的父类

该抽象类中定义了一些方法:

public void close()//关闭此输出流并释放此流相关的任何系统资源

public void flush()//刷新此输出流并强制任何缓冲的输出字节流被写

public void write(byte[] b)//将b.length字节从指定的字节数组中写入到此输出流上

public void write(byte[] b, int off,int len)//从指定的字节数组写入len字节,从偏移量off开始输出到此输出流上

public abstract void write(int b)//将指定的字节输出流输出

java.io.FileOutputStream extends OutputStream

FileOutputStream:文件字节输出流

作用:把内存中的数据写入到磁盘的文件中

构造方法:

​ FileOutputStream(String name)//创建一个具有指定名称的文件中写入数据的输出文件流

​ FileOutputStream(File file)//创建一个指定File对象的文件中写入数据的文件输出流

参数:写入数据的目的地

​ String name:目的地是一个字符串类型的文件路径

​ File file:目的地是一个文件

构造方法的作用:

​ 1.创建一个FileOutputStream对象

​ 2.会根据构造方法中的参数创建一个空文件

​ 3.会把FileOutputStream对象指向创建好的文件

重点

  1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
  2. 调用FileOutputStream对象中的方法write,把数据写入到硬盘中
  3. 释放资源

字节输出流

FileOuputStream中的3中方法写入到文件中

  1. public viod write(int b)//b是ascii值,例如:fos.write(97)//表示向文件中写入了a
  2. public void write(byte[] b)//以字节数组的形式向文件中写入数据
  3. .public void write(byte[] b)//先把字符串转为字节数组,然后再写入到文件中

**注意:**当数组中的第一个数值是0~127之间的数,那么在ascii表中查找对应的值即可

​ 如果数组中的第一个数值是负数,那么在系统默认的格式表中查找对应的值,简体中文是GBK格式,GBK中一个汉字占2个字节, UTF-8中是3个字节表示一个汉字

1.public viod write(int b)//b是ascii值,例如:fos.write(97)//表示向文件中写入了a

//一次写入一个字节数据到文件中,write(int b)
    private static void show01() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\d.txt");
        fos.write(97);//写入的是acsii对应的字母a
        fos.close();
    }

2.public void write(byte[] b)//以字节数组的形式向文件中写入数据

//通过字节数组写入到文件中,write(byte[] b)
    private static void show02() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\d.txt");
        byte[] b = {-26, -120, -111, -25, -120, -79, -28, -67, -96};//A,B,C,D
        fos.write(b);//不是一次写一个字节,而是把字节数组写入到文件中
        fos.close();
    }

3.public void write(byte[] b)//先把字符串转为字节数组,然后再写入到文件中

//字符串转为字节数组然后写入文件中
    private static void show03() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\d.txt");
        byte[] b = "我爱你".getBytes();
        //使用数组工具类中的toString方法转换为字符串类型输出
        //注意:GBK默认是2个字节表示一个汉字,但UFT-8是3个字节表示一个汉字
        System.out.println(Arrays.toString(b));//[-26, -120, -111, -25, -120, -79, -28, -67, -96]
        fos.write(b);
        fos.close();
    }

如何续写

//续写:使用两个参数的构造方法即可
 FileOutputStream(String name,boolean append)//创建一个指向具体路径的文件中,参数append表示是否可以追加
 FileOutputStream(File file,boolean append)//创建一个指向File对象表示的文件中,参数append表示是否可以追加
//不换行的续写
private static void show01() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\e.txt",true);//表示可以追加
        fos.write("我爱你".getBytes());
        fos.close();
    }
//换行的续写
private static void show02() throws IOException {
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\f.txt",true);//表示可以追加
        for (int i = 0; i < 10; i++) {
            fos.write("我爱你".getBytes());
            /*
                添加换行符,注意,不同系统换行符不一样,在Windows中是\r\n
                Linux或Unix中是:/r
                在Mac中是:/n
                注意也需要调用getBytes()方法,转为字节数组才能写到文件中
            */
            fos.write("\r\n".getBytes());
        }
        fos.close();
    }

字节输入流

java.io.InputStream:字节输入流

此抽象类是表示字节输入流的所有类的超类

定义了所有子类的共有方法:

int read()//从输入流中读取数据的一个字节数据到内存中

int read(byte[] b)//从输入流中读取整个字节数组到内存中

void close()//关闭此输入流并释放与该流相关联的所有系统资源

文件字节输入流:java.io.FileInputStream

构造方法:

  1. FileInputStream(String name)
  2. FileInputStream(File file)

参数:

String name:文件的路径

File file:文件

构造方法的作用:

  1. 会创建一个FileInputStream对象
  2. 会把FileInputStream对象指向构造方法中要读取的文件

读取数据的原理:

java程序-->JVM-->OS-->OS读取数据的方法-->读取文件

字节输入流的使用步骤:

  1. 创建FIleInputStream对象,构造方法中绑定要读取的数据源
  2. 使用FileInputStream对象中的方法read,读取文件
  3. 释放资源
package top.lukeewin.demo15IO;

import java.io.FileInputStream;
import java.io.IOException;

public class Demo04InputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileInputStream对象,构造方法中绑定需要读取的文件的数据源
        FileInputStream fis = new FileInputStream("D:\\Works\\javalianxi\\a.txt");
        //2.使用FileInputStream对象中的read方法读取文件
        //int read()读取文件中的一个字节并返回,当读取到最后时返回-1
        /*int len = fis.read();//一次只能读取一个字节
        System.out.println(len);

        int len = fis.read();
        System.out.println(len);*/

        /*
        * 发现以上读取是一个重复的过程,所以可以使用循环来优化
        * */
        int len = 0;//记录读取到的字节
        while ((len = fis.read())!=-1){
            System.out.println((char)len);
        }
        fis.close();
    }
}

一次读取多个字节数据:

int read(byte[] b)从输入流中读取一定量的字节,并将其存储在缓冲区数组b中

需要明确两件事:

  1. 方法的参数byte[]的作用是什么?

    ​ 相当于缓冲区可以更高效地读取数据,一般定义大小为1024,即一次读取1KB

  2. 方法的返回值int是什么?

    ​ 表示有效读取的长度,当读取到文件的最后时返回-1

package top.lukeewin.demo15IO;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

public class Demo05FileInputStream {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:\\Works\\javalianxi\\a.txt");
        //byte[] b = new byte[2];
        //int len = fis.read(b);//返回的是缓冲区中的读取的字节个数
        /*System.out.println(len);
        System.out.println(Arrays.toString(b));//输出的是ascii值,[97, 98]
        System.out.println(new String(b));//ab

        len = fis.read(b);
        System.out.println(len);
        System.out.println(Arrays.toString(b));
        System.out.println(new String(b));

        len = fis.read(b);
        System.out.println(len);
        System.out.println(Arrays.toString(b));
        System.out.println(new String(b));

        len = fis.read(b);
        System.out.println(len);
        System.out.println(Arrays.toString(b));
        System.out.println(new String(b));*/

        byte[] bytes = new byte[1024];//一般是定义为1024字节,即1KB
        int len = 0;//读取的有效长度
        while ((len = fis.read(bytes))!=-1){
            //使用String中的构造方法public String(byte bytes[], int offset, int length)来输出显示指定长度的字符串数据
            System.out.println(new String(bytes,0,len));
        }

        fis.close();
    }
}

==注意不能使用Arrays.toString(bytes)方法直接输出字符串,而是应该使用字符串的构造方法==

String(byte[] bytes):把字节数组转为字符串

String(byte[] bytes,int offset, int length):把字节数组中的一部分转换为字符串

​ offset:数组的开始索引,length:要转换的长度

byte[] bytes = new byte[1024];//一般是定义为1024字节,即1KB
int len = 0;//读取的有效长度
while ((len = fis.read(bytes))!=-1){
//使用String中的构造方法public String(byte bytes[], int offset, int length)来输出显示指定长度的字符串数据
System.out.println(new String(bytes,0,len));
}

案例:文件的复制

步骤:
*   1.创建一个字节输入流对象,构造方法中绑定需要读取的数据的源
*   2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
*   3.使用字节输入流中的read方法读取数据到内存中
*   4.使用字节输出流中的write的方法把内存中的数据写入到目的地
*   5.释放资源
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo06CopyFile {
    public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();

        //1.创建一个字节输入流对象,构造方法中绑定需要读取的数据的源
        FileInputStream fis = new FileInputStream("D:\\Works\\Photoshop\\ps作品\\index.png");
        //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\a\\b\\c\\index.png");
        //3.使用字节输入流中的read方法读取数据到内存中
        /*//一次只读写一个字节
        int len = 0;
        while ((len = fis.read())!=-1){
            //4.使用字节输出流中的write的方法把内存中的数据写入到目的地
            fos.write(len);
        }*/

        //使用数组缓冲来一次读写多个字节
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        //5.释放资源(先关闭写的流,最后关闭读的流,因为写完了,必定能读完)
        fos.close();
        fis.close();

        long e = System.currentTimeMillis();
        System.out.println("复制该图片一共消耗了" + (e-s) + "毫秒");
    }
}

字符流

如果使用字节流读取文件时会出现乱码,因为编码格式的问题,GBK是一个汉字占2个字节,UTF-8是占3个字节,当读取的时候是一次读取一个字节时,很容易产生乱码,所以应该使用字符流,而是不字节流。

字符输入流Reader

Reader:是字符输入流的顶层父类,方法:

  1. int read()//一次读取一个==字符==并返回
  2. int read(char[] cbuf)//一次读取==多个字符==,将字符写入到数组中
  3. void close()//关闭该流并释放与之相关联的所有资源

==FileReader==是==Reader==的子类,文件字符输入流

**作用:**把硬盘中文件的数据以字符的方式读取到内存中

构造方法:

FileReader(String fileName)

FileReader(File file)

参数:

​ 读取文件的数据源

​ String fileName:文件的路径

​ File file:一个文件

FileReader构造方法的作用:

  1. 创建一个FileReader对象
  2. 会把FileReader对象指向要读取的文件

字符输入流的使用步骤:

  1. 创建FileReader对象,构造方法中绑定要读取的数据源
  2. 使用FileReader对象中的方法read读取文件
  3. 释放资源
package top.lukeewin.demo15IO;

import java.io.FileReader;
import java.io.IOException;

public class Demo07FileReader {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("D:\\Works\\javalianxi\\b1.txt");
        /*int len = 0;
        while ((len = fr.read())!=-1){
            System.out.print((char)len);
        }*/

        //一次读取多个字符
        char[] cs = new char[1024];
        int len = 0;//记录每次读取的有效字符个数
        while ((len = fr.read(cs))!=-1){
            /*
            * String类中的构造方法
            * String(char[] value)//把字符数组转为字符串
            * String(char[] value,int offset,int count )//把字符数组中的一部分转为字符串,offset表示开始索引,count表示要转为的字符的个数
            * */
            System.out.println(new String(cs,0,len));
        }
        fr.close();
    }
}

字符输出流Writer

Writer类是抽象类,是所有字符输出流的父类

共有的方法有:

  1. void write(int c)写入单个字符
public static void main(String[] args) throws IOException {
        //1.创建FileWriter对象
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a1.txt");
        //2.写入数据到内存缓冲区中(在内存中有字符转字节的过程)
        fw.write(97);
        //3.刷新数据到文件中,必须要调用flush方法,把内存中的字节数据刷新到文件中
        fw.flush();
        //4.关闭文件字符输出流
        fw.close();
    }
  1. void write(char[] cbuf)写入字符数组
private static void show01() throws IOException {
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\c1.txt");
        char[] c = {'a','b','c'};
        fw.write(c);//写入字符数组的内容到内存中
        fw.flush();//把内存中的数据刷新到文件中
        fw.close();
    }
  1. abstract void write(char[] cbuf,int off,int len)写入字符数组中的某一部分,off数组的开始索引,len写的字符的个数
private static void show02() throws IOException {
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a2.txt");
        char[] c = {'d','e','f','g'};
        fw.write(c,1,2);//写入固定位置开始的固定长度的字符数组
        fw.flush();
        fw.close();
    }
  1. void write(String str)写入字符串
private static void show03() throws IOException {
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a3.txt");
        fw.write("你好java");//把字符串写入到内存中
        fw.flush();
        fw.close();
    }
  1. void write(String str,int off,int len)写入字符串的某一部分
private static void show04() throws IOException {
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a4.txt");
        fw.write("java程序员",4,3);
        fw.flush();
        fw.close();
    }
  1. void flush()刷新该流的缓冲区

  2. void close()关闭此流,会刷新缓冲区

java.io.FileWriter extends OuputStreamWriter exends Writer

FileWriter:文件字符输出流

作用:把内存中的字符数据写入到文件中

构造方法:

1.FileWriter(File file)根据给定的File对象构造一个FIleWriter对象

2.FileWriter(String fileName)根据给定的文件名构造一个FileWriter对象

参数:写入数据的目的地

​ String fileName:文件的路径

​ File file:是一个文件

构造方法的作用:

  1. 会创建一个FileWriter对象
  2. 会根据构造方法中传递的文件/文件夹的路径,创建文件
  3. 会把FileWriter对象指向创建好的文件

字符流的使用步骤:

  1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
  2. 使用FileWriter中的方法write,把数据写入到==内存缓冲区中==(字符转换为字节的过程)
  3. ==使用FileWriter中的方法flush,把内存缓冲区中的数据刷新到文件中==
  4. 释放资源(会把内存缓冲区中的数据刷新到文件中)
public class Demo08FileWriter {
    public static void main(String[] args) throws IOException {
        //1.创建FileWriter对象
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a1.txt");
        //2.写入数据到内存缓冲区中
        fw.write(97);
        //3.刷新数据到文件中
        fw.flush();
        //4.关闭文件字符输出流
        fw.close();
    }
}

flush()和close()的区别

  • flush()后可以继续写入
  • close()后不能继续写入

续写和换行:

续写,追加:使用两个参数的构造方法

FileWriter(String fileName ,boolean append)

FileWriter(File file ,boolean append)

参数:

​ String fileName,File file:写入数据的目的地

​ boolean append:是否追加数据,true表示可以追加数据

换行:

​ Windows: \r\n

​ Linux: /n

​ mac: /r

例子:

public class Demo10FIleWriter {
    public static void main(String[] args) throws IOException {
        show01();
    }

    private static void show01() throws IOException {
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a1.txt",true);
        for (int i = 0; i < 10; i++) {
            fw.write("牛年大吉" + i + "\r\n");
        }
        fw.flush();
        fw.close();
    }
}

使用try-catch-finally处理流中的异常

格式:

try{

​ 可能会产生异常的代码

​ }catch(异常类变量 变量名){

​ 异常的处理逻辑

​ }finally{

​ 一定会指定的代码

​ 资源释放

​ }

package top.lukeewin.demo15IO;

import java.io.FileWriter;
import java.io.IOException;

public class Demo11FileTryCatch {
    public static void main(String[] args) {
        FileWriter fw = null;
        try{
            fw = new FileWriter("D:\\Works\\javalianxi\\a3.txt",true);
            for (int i = 0; i < 10; i++) {
                fw.write("大家好" + "\r\n");
            }
        }catch (IOException e){
            System.out.println(e);
        }finally {
            //fw.close()有IOException异常抛出,所以必须要处理异常,可以抛出,也可以捕获处理
            //如果路径不对会报空指针异常,所以还要先判断fw是否为空,当不为空时才能调用close()方法
            if (fw!=null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

==JDK7之前的新特性==

​ 在try的后边增加一个(),在小括号中可以定义流对象

那么这个流对象的作用域就在try中有效

try中的代码执行完毕会自动把流对象释放,不用写finally

格式:

​ try(定义流对象){

​ 可能会产生异常的代码

​ }catch(异常类型变量 变量名){

​ 异常的处理逻辑

​ }

==注意:多个对象流使用分号分隔开==

==不用finally来关闭流了,因为对象流的作用范围只在大括号内有效==

package top.lukeewin.demo15IO;

/*
* JDK1.7之后的新特性:
*   try(定义对象流,多个对象流之间用分号分隔开){
*
*   }catch(异常对象 异常变量名){
*       需要处理的异常逻辑
*   }
* */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo12FileTryCatch {
    public static void main(String[] args) {
        try(FileInputStream fis = new FileInputStream("D:\\Works\\javalianxi\\a.txt");
            FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\chap10\\a.txt")){
            int len = 0;
            while ((len = fis.read())!=-1){
                fos.write(len);
            }
        }catch (IOException e){
            System.out.println(e);
        }
    }
}

==JDK9的新特性:==

​ try的前边可以定义流对象

在try后边的()中直接引用流对象的名称即可(即变量名)

在try代码执行完毕之后,流对象也可以释放掉,所以不用写finally

==格式:==

​ A a = new A();

​ B b = new B();

​ try(a,b){

​ 可能会产生异常的代码

​ }catch(异常变量 变量名){

​ 异常的处理逻辑

​ }

package top.lukeewin.demo15IO;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo13 {
    public static void main(String[] args) throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("D:\\Works\\javalianxi\\a1.txt");
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\a\\b\\c\\a1.txt");
        try(fis;fos){
            int len = 0;
            while ((len = fis.read())!=-1){
                fos.write(len);
            }
        }catch (IOException e){
            System.out.println(e);
        }
    }
}

Properties集合

java.util.Properties extends Hashtable<k,v> implements Map<k,v>

Properties类表示了一个持久的属性集合。Properties可保持在流中或从流中加载。

Properties集合是一个唯一和IO流相结合的集合

可以使用Properties集合中的方法==store==,把集合中的临时数据持久化,写入到硬盘中存储

可以使用Properties集合中的方法==load==,把硬盘中的文件(键值对),读取到集合中使用

属性列表中每一个键对应一个值都是字符串。

Properties集合是一个双列集合,keyvalue默认都是字符串

​ 常用方法有:

  1. Object setProperties(String key,String value)调用Hashtable的方法put
  2. String getProperties(String key)通过key找到value,此方法相当于Map集合中的get(key)方法
  3. set<String> StringPropertiesNames() 返回此属性列表中的键的set集合,其中该键对应的值是字符串,此方法相当于Map集合中的ketSet方法
private static void show01() {
        //创建Properties集合
        Properties prop = new Properties();
        //使用setProperties方法,往集合中添加数据
        //注意必须是字符串
//        prop.setProperty("赵丽颖",168);//错误写法,必须k和v都为字符串类型
        prop.setProperty("赵丽颖","168");
        prop.setProperty("古力娜扎","165");
        prop.setProperty("迪丽热巴","160");
//        prop.put(1,true);//也可以用put方法,put方法不规定类型,也不推荐使用put方法

        //使用stringPropertiesNames方法把Properties集合中的每一个键都取出来,放到Set集合中
        Set<String> set = prop.stringPropertyNames();
        //遍历Set集合
        for (String key : set) {
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
    }

可以使用Properties集合中的方法store,把集合中的临时数据,持久化地写入到硬盘中

void store(OutputStream out ,String comments)

void store(Writer writer ,String comments)

参数:

​ OutputStream out:字节输出流,不能写入中文

​ Writer writer:字符输出流,可以写中文

​ String comments:注释,用来解释说明保存的文件是做什么用的

​ ==注意:不能使用中文,会产生乱码,因为默认是使用Unicode编码,而系统是使用GBK编码,所以一般使用空字符串==

使用步骤:

  1. 创建Properties集合对象,添加数据
  2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
  3. 使用Properties集合中的方法store,把集合中的临时数据持久化写入到硬盘中
  4. 释放资源
private static void show02() throws IOException {
        //1.创建Properties集合
        Properties prop = new Properties();
        prop.setProperty("赵丽颖","168");
        prop.setProperty("古力娜扎","165");
        prop.setProperty("迪丽热巴","160");

        //2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
        FileWriter fw = new FileWriter("D:\\Works\\javalianxi\\a\\b\\c\\prop.txt");

        //使用字节流,注意中文会产生乱码
        //prop.store(new FileOutputStream("D:\\Works\\javalianxi\\a\\b\\c\\prop2.txt"),"");

        //3.使用Properties集合中的方法store,把集合中的数据,持久化地写入到硬盘中
        prop.store(fw,"save data");
        fw.close();
    }

可以使用Properties集合中的方法load,把硬盘中的数据读取到集合中使用

void load(InputStream inStream)

void load(Reader reader)

参数:

​ InputStream inStream:字节输入流,不能读取含中文的键值对

​ Reader reader:字符输入流,能读取含有中文的键值对

使用步骤:

  1. 创建Properties集合对象
  2. 使用Properties集合对象中的方法load获取保存键值对的文件
  3. 遍历Properties集合

注意:

  1. 存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其它符号)
  2. 存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
  3. 存储键值对的文件中,键与值默认都是字符串,不用加上双引号
private static void show03() throws IOException {
        //1.创建Properties集合
        Properties prop = new Properties();
        //2.使用load方法读取文件到内存中
        prop.load(new FileReader("D:\\Works\\javalianxi\\a\\b\\c\\prop.txt"));
        //如果使用字节输入流,含有中文,会乱码
        //prop.load(new FileInputStream("D:\\Works\\javalianxi\\a\\b\\c\\prop.txt"));
        //3.遍历Properties集合
        Set<String> set = prop.stringPropertyNames();
        for (String key : set) {
            String value = prop.getProperty(key);
            System.out.println(key + "=" + value);
        }
    }

缓冲流

java.io.BufferedOutputStream extends OutputStream

BufferedOutputStream:字节缓冲输出流

继承了父类的方法:

  1. public void close():关闭流
  2. public void flush():刷新数据
  3. public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流
  4. public void write(byte[] b,int off,int len):从指定的字节数组中写入len字节,从偏移量off开始输出到输出流
  5. public abstract void write(int b):将指定的字节输出流

==构造方法==:

  • BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
  • BufferedOutputStream(OutputStream out ,int size) 创建一个新的缓冲输出流,指定缓冲区大小

参数:

OutputStream out :字节输出流

我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率

int size:指定缓冲区的大小,不指定,则默认

==使用步骤:==

  1. 创建FileOutputStream 对象,构造方法中绑定要输出的目的地
  2. 创建BufferedOutputStream对象,构造方法中传递FileOutputStream实例
  3. 使用BufferedOutputStream对象中的方法write,把数据写入到内部的缓冲区中
  4. 使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
  5. 释放资源(会先调用flush()刷新数据,所以第4步可以省略)
package top.lukeewin.demo17Buffered;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        //1.创建FileOutputStream 对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("D:\\Works\\javalianxi\\a2.txt");
        //2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream实例
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //3.使用BufferedOutputStream对象中的方法write,把数据写入到内部的缓冲区中
        bos.write("数据写入到缓冲区中,然后写入到文件中".getBytes());
        //4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        bos.flush();
        //5.释放资源(会先调用flush()刷新数据,所以第4步可以省略)
        bos.close();
    }
}

Q.E.D.


热爱生活,热爱程序