【Java笔记】基础学习笔记汇总(中)

【Java笔记】基础学习笔记汇总(中)

原本笔记都是手写的,为了之后方便保存和查阅,还是下定决心,把他敲出来正好作为一篇博客的内容。也借这个机会,好好的复习一遍Java基础知识。这是中半部分。

自学材料:黑马程序员Java全套视频

1. java.lang.Throwable:Java语言中所有错误或异常的父类

其下有两个子类:

  • java.lang.Error
  • java.lang.Exception

2. JVM 中断处理异常 的过程

1)首先,JVM在代码中检测处程序会出现异常,JVM做两件事

  • JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的信息,包括 内容、原因、位置
  • 在某方法中,若没有异常的处理逻辑(try...catch...),那么JVM就会把异常对象抛出给方法的调用者main方法来处理这个异常

2)main方法接受到这个异常对象,main方法也没有异常的处理逻辑,于是把对象抛出给main方法的调用者JVM处理

3)JVM接收到这个异常对象,做了两件事:

  • 把异常对象(内容、原因、位置)以红色的字体打印在控制台上
  • JVM会终止当前正在执行的Java程序:中断处理

3. throw关键字

作用:可以使用throw关键字在指定的方法中抛出特定的异常

使用格式:

throw new xxxException("原因。。")


注意:

1)throw关键字必须写在方法的内部

2)throw后面的new的对象必须是Exception或Exception的子类对象

3)throw关键字抛出的异常对象,我们必须处理这个异常对象

  • 若是RuntimeException或RuntimeException子类对象,我们可以不处理,默认交给JVM处理(打印异常、中断程序)
  • 若是编译异常(写代码时报错),我们必须处理这个异常,要么throws,要么try...catch...

4. Objects类的一个静态方法:requireNonNull

public static < T > T requireNonNull(T obj):查看指定的索引对象是不是null

可以用来对于参数进行合法性的判断:

  • Objects.requireNonNull(obj)
  • Objects.requireNonNull(obj,"原因。。。")

5. throws关键字(异常处理方式1)

交给JVM处理:中断处理

注意:

1)throws关键字必须写在方法声明处

2)throws后面声明的异常必须是Exception或Exception的子类

3)方法内部如果抛出多个异常对象,那么throws后边必须也声明多个异常

如果抛出多个异常对象有子父类关系,那么直接声明父类异常即可

4)这里throws是用在编译器异常,运行期异常只需throw出来就可以了

6. try...catch...(异常处理方式2)

注意:

1)try中可能会抛出多个异常对象,那么就会使用多个catch来处理这些异常对象

2)如果try中产生异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中处理逻辑,继续执行try...catch...后面的代码

如果try中没有产生异常,那么就不会执行catch中异常处理的逻辑,执行完try中的代码,然后继续执行try...catch...后面的代码

7. Throwable类中定义3个异常处理的方法

1)String getMessage():返回此throwable的简短描述

2)String toString():返回此throwable的详细消息字符串

3)void printStackTrace():JVM打印异常对象,默认此方法,打印的异常信息是最全面的

8. 多个异常try...catch...的处理方法

1)多个异常分别处理

2)多个异常一次性捕获,多次处理:try...catch...catch...

注意:catch里面定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上面,否则会报错

3)多个异常一次性捕获,一次处理:try...catch...

9. 子父类的异常处理

  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者父类异常的子类或者不抛出异常
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出

总结:父类异常是什么样,子类异常是什么样

10. 自定义异常类

格式:

public class xxxException extends Exception或RuntimeException{

​ 添加一个空参数的构造方法

​ 添加一个带异常信息的构造方法

}

注意:

1)自定义异常类一般都是Exception结尾,说明该类是一个异常类

2)自定义异常类,必须继承Exception(编译器异常)或RuntimeException(运行期异常)

11. 并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生(交替执行)
  • 并行:指两个或多个事件在同一时刻发生(同时执行)

12. 线程

线程属于进程。是进程中的一个执行单元,负责程序的执行

线程调度的策略:分时调度;抢占式调度


主线程:

执行主(main)方法的线程

是单线程程序:Java程序中只有一个主线程

13. 多线程

java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行;同一个优先级,随机选择一个执行

多线程的好处:多个线程之间互不影响(在不同的栈空间)

14. 创建多线程程序的第一种方法:创建Thread类的子类

java.lang.Thread

实现步骤:(并发执行)

1)创建一个Thread子类

2)在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)

3)创建Thread类的子类对象

4)调用Thread类中的 .start() 方法,开启新的线程,执行run方法


Thread类的常用方法

1)获取线程的名称(2种方法)

  • 使用Thread类中的方法 .getName():返回线程的名称

  • 可以先获取当前正在执行的线程,使用线程中的方法 .getName() 来获取线程的名称

    • static Thread currentThread():返回当前正在执行的线程对象的引用

2)设置线程的名称(2种方法)

  • 使用Thread类中的 .setName(...) 方法
  • 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给线程起一个名字,例如:

public MyThread(String name){

​ super(name);

}

3)public static void sleep(long millis)

使当前正在执行的线程以指定的毫秒数暂停,毫秒数结束之后,线程继续执行

例如:Thread.sleep(1000);


代码示例:

/*
    获取线程的名称:
        1.使用Thread类中的方法getName()
            String getName() 返回该线程的名称。
        2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
            static Thread currentThread() 返回对当前正在执行的线程对象的引用。
 */
// 定义一个Thread类的子类
public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
        //String name = getName();
        //System.out.println(name);

        //Thread t = Thread.currentThread();
        //System.out.println(t);//Thread[Thread-0,5,main]
        //String name = t.getName();
        //System.out.println(name);

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}


package com.itheima.demo01.getName;
/*
    线程的名称:
        主线程: main
        新线程: Thread-0,Thread-1,Thread-2
 */
public class Demo01GetThreadName {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt = new MyThread();
        //调用start方法,开启新线程,执行run方法
        mt.start();

        new MyThread().start();
        new MyThread().start();

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

15. 创建多线程程序的第二种方法:实现Runable接口

java.lang.Runable

Runable接口在被实现时,需要在类中必须定义一个称为run的无参数方法


java.lang.Thread类的常用构造方法:

  • Thread()

  • Thread(String name)

  • Thread(Runable target)

  • Thread(Runable target, String name)


实现步骤:

1)创建一个Runable接口的实现类

2)在实现类中重写Runable接口的run方法,设置线程任务

3)创建一个Runable接口的实现类对象

4)创建Thread类对象,构造方法中传递Runable接口的实现类对象

5)调用Thread类中的start方法,开启新的线程执行run方法


实现Runable接口创建多线程的优势:

1)避免了单继承的局限性。实现Runable接口,还可以继承其他的类,实现其他接口

2)把设置线程任务和开启新线程进行了分离。增强了程序的扩展性,降低了程序的耦合性(解耦)


代码示例:

package com.itheima.demo04.Runnable;
//1.创建一个Runnable接口的实现类
public class RunnableImpl2 implements Runnable{
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("HelloWorld"+i);
        }
    }
}


public class Demo01Runnable {
    public static void main(String[] args) {
        //3.创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //Thread t = new Thread(run);//打印线程名称
        Thread t = new Thread(new RunnableImpl2());//打印HelloWorld
        //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

16. 匿名内部类方式实现线程的创建

匿名内部类的最终产物:子类/实现类对象,并且没有类名字

格式:

new 父类/接口 (){

​ 重写父类/接口 中的方法

}

创建线程例子:

//第一种
new Thread(){
    //重写run方法
}.start();

//第二种
Runable r = new Runable(){
    //重写run方法
};
new Thread(r).start();

//第二种可简化为
new Thread(new Runable(){
    //重写run方法
}).start();

17. 线程安全问题

多线程访问共享数据,会产生线程安全问题,解决这一问题的方法:线程同步

有三种:1)同步代码块;2)同步方法;3)Lock锁机制

18. 第一种:同步代码块

格式:

synchronized(锁对象){

​ 可能会出现线程安全问题的代码块

}

注意:

1)同步代码中的锁对象,可以使用任意对象

2)但是必须保证多个线程使用的锁对象是同一个

3)锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行


锁对象,叫作同步锁,或对象锁,或对象监视器

同步中的线程,没有执行完毕,不会释放锁;同步外的线程没有锁,进不去同步代码块


用这种同步代码块的方式存在的问题:

程序频繁地判断锁、获取锁、释放锁,程序的效率会降低

19. 第二种:同步方法

先把可能产生线程安全问题的代码抽取出来放到一个方法中,在方法上添加synchronized修饰符

格式:

修饰符 synchronized 返回数据类型 方法名(参数列表){

​ 可能会产生线程安全问题的代码

}

同步方法的同步锁:

  • 对于非static同步方法,同步锁就是this。
  • 对于static同步方法,我们使用当前方法所在类的字节码对象(类名.class)。

20. 第三种:Lock锁

java.util.concurrent.locks.Lock 接口提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁。
  • public void unlock():释放同步锁

java.util.concurrent.locks.ReentrantLock 实现了Lock接口

这种方法的使用步骤:

1)在成员变量位置,创建一个ReetrantLock对象

2)在可能出现安全问题的代码前调用lock()方法获取锁

3)在可能出现安全问题的代码后调用unlock()方法释放锁(最好是写在try...catch...后的finally代码块中)


代码示例:

public class Ticket implements Runnable{
    private int ticket = 100;
    Lock lock = new ReentrantLock();
    /*
    * 执行卖票操作
    */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while(true){
            lock.lock();
            if(ticket>0){//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在卖:"+ticket‐‐);
            }
            lock.unlock();
        }
    }
}

21. 线程状态(6种)

注意:

1)阻塞状态:具有cpu的执行资格,等待cpu执行空间

2)休眠状态(计时等待):放弃cpu的执行资格,cpu空闲也不执行

3)无限等待Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或 Object.notifyAll()方法。

其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。

当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。


完整的线程状态变化图:

22. Object类中的 .wait() 和 .notify() 方法

  • void wait()

在其他线程调用此对象的 .notify() 方法前,导致当前线程无限等待

  • void notify()

唤醒在此对象监视器上无限等待的单个线程

23. 进入到Time Waiting(计时等待)的两种方法和唤醒的方法

1)使用 .sleep(long m)方法

在毫秒值结束之后,线程睡醒进入到Runable/Blocked状态

2)使用 .wait(long m) 方法

在毫秒值结束之前,也没有被 notify() 方法唤醒,那么毫秒值结束之后,会自动醒来。线程睡醒进入到Runable/Blocked状态


唤醒的方法:

1)void notify():唤醒在此对象监视器上等待的单个线程

2)void notifyAll():唤醒在此对象监视器上等待的所有线程


调用wait和notify方法注意事项:

1)wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对
象调用的wait方法后的线程。

2)wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
承了Object类的。

3)wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

24. 等待唤醒机制

解决线程之间通信问题

重点:有效地利用资源

25. 线程池

JDK1.5之后,内置了线程池,可以直接使用

合理利用线程池的好处:

1)降低资源消耗。减少了创建和销毁线程的次数

2)提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行

3)提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内
存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

26. 线程池工厂类:java.util.concurrent.Executors

线程工厂类里面提供了一些静态工厂,生成一些常用的线程池

1)Executors类中 创建线程池的静态方法:

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

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

返回值:ExecutorService线程池接口的实现类对象

2)获取到线程池ExecutorService 对象,使用线程池对象的方法:

  • .submit(Runable task):提交一个Runable任务用于执行
  • .shutdown():关闭/销毁线程池

27. 线程池的使用步骤:

1)使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定线程数的线程池

2)创建一个类,实现Runable接口,重写run方法,设置线程任务

3)调用ExecutorService中方法submit(),传递线程任务(Runable实现类),开启线程执行run方法

4)调用ExecutorService中方法shutdown(),销毁线程池(不建议执行)

28. JDK1.8之后的新特性:Lambda表达式

匿名内部类使用示例:

public class Demo04ThreadNameless {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程任务执行!");
            }
        }).start();
    }
}

Lambda表达式写法:

public class Demo02LambdaRunnable {
    public static void main(String[] args) {
        new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
    }
}

Lambda表达式的标准格式由三部分组成:

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

格式:

(参数类型 参数名称) ‐> { 一些重写方法的代码 }

格式说明:

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

Lambda表达式的省略规则:

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

2)(参数列表):括号中的参数如果只有一个,那么数据类型和 () 都可以省略

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

注意:要省略 { }、return、分号 时,必须一起省略


Lambda的使用前提:

1)使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一
时,才可以使用Lambda。

2)使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

注意:有且仅有一个抽象方法的接口,称为“函数式接口”。

29. java.io.File类

1)File类的4个静态成员变量

  • static String pathSeparator:与系统有关的路径分隔符
  • 类似于:static char pathSeparatorChar

路径分隔符:windows系统 = 分号“;” linux系统 = 冒号“:”

  • static String separator:与系统有关的默认名称分隔符
  • 类似于:static char separatorChar

文件名称分隔符:windows = 反斜杠“\” linux系统 = 正斜杠“/”


File类的构造方法(3种)

1)FIle(String pathname)

注意:

  • 路径可以是文件结尾,也可以是以文件夹结尾
  • 路径可以是相对路径,也可以是绝对路径
  • 路径可以是存在的,也可以是不存在的
  • 创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况

2)File(String parent, String child)

好处:父路径和子路径,可以单独书写,使用起来更灵活;父子路径都可以改变

3)File(File parent, String child)

30. 绝对路径和相对路径

  • 绝对路径:一个完整的路径。从盘符开始的路径
  • 相对路径:相对于当前项目的根目录的路径

31. File类的常用方法 第一类:获取功能

1)public String getAbsolutePath():返回FIle对象的绝对路径名字符串

2)public String getPath():返回的就是调用构造方法时传入的路径

注意:FIle类的toString()方法源码就是调用getPath()方法

3)public String getName():返回这个FIle对象表示的文件或目录的名称

4)public long length():返回FIle对象表示的文件的大小;以“字节”为单位

注意:1)文件家是没有大小的概念的,不能获取文件夹的大小;2)如果构造方法中给出的路径不存在,返回为 0

32. File类的常用方法 第二类:判断功能

1)public boolean exists():判断路径是否存在

2)public boolean isDirectory():判断构造方法中给定的路径是否为文件夹结尾

3)public boolean isFile():判断给定的路径是否以文件结尾

33. File类的常用方法 第三类:创建删除功能

1)public boolean createNewFile()

当且仅当具有该名称的文件不存在时,才会创建一个新的空文件

创建文件的路径和名称在构造方法中给出

返回值:布尔值

  • true:文件不存在,创建文件
  • false:文件存在,不会创建

注意:

  • 此方法只能创建文件,不能创建文件夹
  • 创建文件的路径必须存在,否则会抛出异常

2)创建文件夹

  • public boolean mkdir():创建单极文件夹
  • public boolean mkdirs():既可以单极也可以多级

创建文件夹的路径和名称在构造方法中给出

返回值:布尔值

  • true:文件夹不存在,创建文件夹
  • false:文件夹存在,不会创建;构造方法中给出的路径不存在

注意:

  • 此方法只能创建文件夹,不能创建文件

3)public boolean delete()

返回值:布尔值

  • true:文件/文件夹删除成功
  • false:文件夹中有内容,不会删除;构造方法中的路径不存在,不会删除

注意:

  • delete方法是在硬盘上删除文件/文件夹,不走回收站

34. File类遍历文件夹/目录功能

1)public String[] list():返回一个String数组,表示该FIle目录下的所有子文件或目录

能看到隐藏文件或文件夹

2)public File[] listFIles():返回一个FIle数组,表示该FIle目录下的所有子文件或目录

能看到隐藏文件或文件夹

注意:

  • list和listFIles方法遍历的是构造方法中给出的目录
  • 若构造方法中给出的目录的路径不存在,会抛出空指针异常
  • 若构造方法中给出的路径不是一个目录,也会抛出空指针异常

35. 补充String类中的两个方法:.endWith()和.toLowerCase()

1).endsWith(...):判断字符串是不是以...为结尾

2).toLowerCase():把字符串转换为小写

36. java.io.FileFilter和java.io.FilenameFilter两个文件过滤器

具体可见手写笔记:第192条内容

java.io.FileFilter是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter)作为参数, 接口中只有一个方法。

boolean accept(File pathname):测试pathname是否应该包含在当前File目录中,符合则返回true。

分析

1)接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。

2)accept方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:

  • 要么是.java文件。

  • 要么是目录,用于继续遍历。

  • 通过过滤器的作用,listFiles(FileFilter)返回的数组元素中,子文件对象都是符合条件的,可以直接打印。

代码实现:

public class DiGuiDemo4 {
    public static void main(String[] args) {
        File dir = new File("D:\\aaa");
        printDir2(dir);
    }
  
    public static void printDir2(File dir) {
      	// 匿名内部类方式,创建过滤器子类对象
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".java")||pathname.isDirectory();
            }
        });
      	// 循环打印
        for (File file : files) {
            if (file.isFile()) {
                System.out.println("文件名:" + file.getAbsolutePath());
            } else {
                printDir2(file);
            }
        }
    }
}      

37. IO流

1个字符 = 2个字节

1个字节 = 8个二进制位

输入I:把硬盘中的数据,读取到内存中

输出O:把内存中的数据,写入到硬盘中保存


顶级父类:


目前学到的IO流有:

  • java.io.OutputStream
    • java.io.FileOutputStream
      • java.io.PrintStream
    • java.io.BufferedOutputStream
    • java.io.ObjectOutputStream
  • java.io.InputStream
    • java.io.FileInputStream
    • java.io.BufferedInputStream
    • java.io.ObjectInputStream
  • java.io.Reader
    • java.io.InputStreamReader
      • java.io.FileReader
    • java.io.BufferedReader
  • java.io.Writer
    • java.io.OutputStreamWriter
      • java.io.FileWriter
    • java.io.BufferedWriter
  • java.util.Properties

38. java.io.OutputStream和子类java.io.FileOutputStream

java.io.OutputStream:字节输出流;这个抽象类,是所有输出字节流所有类的父类

java.io.OutputStream抽象类里定义了输出字节流子类的一些共性的成员方法:

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):将指定的字节输出流


java.io.FileOutputStream extends OutputStream:文件字节输出流

构造方法:

  • public FileOutputStream(File file) :创建向指定的 File对象表示的文件中写入数据的文件输出流
  • public FileOutputStream(String name) : 创建向具有指定的名称的文件中写入数据的输出文件流

注意:

当你创建一个流对象时,必须传入一个文件路径。

该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。


文件字节输出流的使用步骤:

1)创建一个FileOutputStream对象,构造方法中传递写入数据的目的地

2)调用FileOutputStream对象的方法write(),把数据写入到文件中

3)释放资源,流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率


注意:

  • 如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
  • 如果写的第一个字节是负数,那第一个字节会和第二个字节组合成一个中文显示,查询系统默认的GBK码表

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

  • FileOutputStream(String name, boolean append)
  • FileOutputStream(File file, boolean append)

其中,boolean append:追加写开关

  • true:创建对象不会覆盖原文件,继续在文件的末尾写数据
  • false:创建一个新文件,覆盖原文件

写出换行符号:

例如:fos.write("\r\n".getBytes());

各个系统中的换行符号:

  • Windows系统:\r\n ;
  • linux系统:/n ;
  • Mac系统:/r;从Mac OS X开始与Linux统一

39. java.io.InputStream和子类java.io.FileInputStream

java.io.InputStream:字节输入流,这个抽象类是字节输入流的所有类的父类

java.io.InputStream抽象类里定义了输入字节流子类的一些共性的成员方法:

1)public abstract int read():从输入流读取下一个字节。读一个字节,读到文件末尾,返回-1

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

3)public void close():流的关闭原则:先开后关,后开先关。


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

构造方法:

  • FileInputStream(String name)
  • FileInputStream(File file)

参数:读取文件的数据源


文件字节输入流的使用步骤:

1)创建FileInputStream对象,构造方法中绑定要读取的数据源

2)使用FileInputStream对象中的方法read()读取文件

3)释放资源


循环一个个字节读取的代码示例:

public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象
       	FileInputStream fis = new FileInputStream("read.txt");
      	// 定义变量,保存数据
        int len ;
        // 循环读取
        while ((len = fis.read())!=-1) {
            System.out.println((char)b);
        }
		// 关闭资源
        fis.close();
    }
}

使用字节数组 int read(byte[] b) 循环从输入流中读取一定数量的字节的代码演示:

public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象.
       	FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
      	// 定义变量,作为有效个数
        int len ;
        // 定义字节数组,作为装字节数据的容器   
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
           	// 每次读取后,把数组的有效字节部分,变成字符串打印
            System.out.println(new String(b,0,len));//  len 每次读取的有效字节个数
        }
		// 关闭资源
        fis.close();
    }
}

回顾复习String类构造方法中的两个:

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

offset:开始索引;length:长度


注意:int read(byte[] b)

1)byte[] b的作用:起到缓冲作用,存储每次读取到的多个字节

数组长度一般定义为1024(1KB)或1024的整数倍

2)方法的返回值int是什么:每次读取到的有效字节个数

40. java.io.Reader和子类java.io.FileReader

java.io.Reader:字符输入流,是所有字符输入流的最顶层的父类

java.io.Reader抽象类里,定义了一些共性的成员方法:

1)int read():读取单个字符返回

2)int read(char[] cbuf):一次性读取多个字符,将字符存入数组

3)void close():关闭该流并释放资源


java.io.FileReader extends InputStreamReader extends Reader:文件字符输入流

构造方法:

  • FileReader(String name)
  • FileReader(File file)

使用步骤类似于文件字节输入流


回顾复习String类构造方法中的两个:

  • String(char[] value):把字符数组转换为字符串
  • String(char[] bytes,int offset, int len):把字符数组的一部分转换为字符串

offset:开始索引;length:长度


循环一个个字符读取的代码示例:

public class FRRead {
    public static void main(String[] args) throws IOException {
      	// 使用文件名称创建流对象
       	FileReader fr = new FileReader("read.txt");
      	// 定义变量,保存数据
        int b ;
        // 循环读取
        while ((b = fr.read())!=-1) {
            System.out.println((char)b);
        }
		// 关闭资源
        fr.close();
    }
}

使用字符数组 int read(char[] cbuf) 循环从输入流中读取一定数量的字符的代码演示:

public class FISRead {
    public static void main(String[] args) throws IOException {
      	// 使用文件名称创建流对象
       	FileReader fr = new FileReader("read.txt");
      	// 定义变量,保存有效字符个数
        int len ;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf,0,len));
        }
    	// 关闭资源
        fr.close();
    }
}

41. java.io.Writer和子类java.io.FileWriter

java.io.Writer:字符输出流,是所有字符输出流的最顶层的父类

java.io.Writer抽象类定义了一些子类共性的成员方法:

1)void write(int c):写入单个字符。

2)void write(char[] cbuf):写入字符数组。

3)abstract void write(char[] cbuf, int off, int len):写入字符数组的某一部分

4)void write(String str):写入字符串。

5)void write(String str, int off, int len):写入字符串的某一部分

6)void flush():刷新该流的缓冲

7)void close():关闭此流,但要先刷新它


java.io.FileWriter extends OutputStreamWriter extends Writer:文件字符输出流

构造方法:

  • FileWriter(File file)
  • FileWriter(String name)

使用步骤类似于文件字节输出流,就是多了一个刷新缓冲区的步骤


.flush() 和 .close() 方法的区别:

  • .flush():刷新缓冲区,流对象还可以继续使用
  • .close():先刷新缓冲区,然后通知系统释放资源,流对象不可在被使用了

续写和输出换行:类似于上面的文件输出字节流那样

使用2个参数的构造方法:

  • FileWriter(String name, boolean append)
  • FileWriter(File file, boolean append)

42. IO异常的处理

1)JDK1.7之前,使用try...catch...finally 来处理IO流中的异常


2)JDK1.7的新特性:

在try的后面可以添加一个 (),在括号里可以定义流对象,那么这个流对象的作用域就在try'中有效;try中的代码执行完毕后,会自动把流对象释放,不用写finally

格式:

try (创建流对象语句,如果多个,使用';'隔开) {
	// 读写数据
} catch (IOException e) {
	e.printStackTrace();
}


//代码演示:
public class HandleException2 {
    public static void main(String[] args) {
      	// 创建流对象
        try ( FileWriter fw = new FileWriter("fw.txt"); ) {
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3)JDK9的新特性:

try的前面可以先定义流对象;在try后面的()中直接引入流对象的名称;在try代码块执行完毕之后,流对象会被自动释放掉,也不用写finally

代码演示:

public class TryDemo {
    public static void main(String[] args) throws IOException {
       	// 创建流对象
        final  FileReader fr  = new FileReader("in.txt");
        FileWriter fw = new FileWriter("out.txt");
       	// 引入到try中
        try (fr; fw) {
          	// 定义变量
            int b;
          	// 读取数据
          	while ((b = fr.read())!=-1) {
            	// 写出数据
            	fw.write(b);
          	}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

43. java.util.Properties集合 extends Hashtable< k,v >

  • Properties类表示一个持久的属性集
  • Properties可保存在流中或从流中加载
  • Properties集合是唯一和IO流相结合的集合

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

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

  • 属性列表中每个键及其对应的值都是一个字符串,Properties集合是一个双列集合

Properties集合中一些操作字符串特有的方法:

1)public Object setProperty(String key, String value):保存一对属性;相当于Hashtable的put方法

2)public String getProperty(String key):通过key找value,相当于Map集合中的get(key)方法

3)public Set< String > stringPropertyNames():返回此属性列表中的键值,相当于Map集合中的keySet()方法


store() 方法:

把集合中的临时数据,持久化写入到硬盘中存储

  • void store(OutputStream out, String comments)
  • void store(Writer writer, String comments)

参数:

  • OutputStream out:字节输出流,不能写入中文
  • Writer writer:字符输出流,可以写入中文
  • String comments:注释,不能写入中文,一般使用空字符串

使用步骤:

  • 创建Properties集合对象,添加数据
  • 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
  • 使用Properties集合中的store()方法,把集合中的临时数据,写入硬盘
  • 释放资源

load() 方法:

把硬盘中保存的文件(键值对)读取到集合中使用

  • void load(InputStream inStream)
  • void load(Reader reader)

参数:

  • InputStream inStream:字节输入流,不能读取含有中文的键值对
  • Reader reader:字符输入流,能读含有中文的键值对

注意:

1)存储键值对properties文件中,键与值默认的连接符号可以是 = 或 空格 等

2) 存储键值对properties文件中,可以使用#号进行注释,被注释的键值对不会被读取

3)存储键值对properties文件中,键与值默认是字符串,不能再加 引号

44. java.io.BufferedOutputStream字节缓冲输出流 extends OutputStream

构造方法:

  • BufferedOutputStream(OutputStream out)
  • BufferedOutputStream(OutputStream out, int size)

参数:

  • OutputStream out:字节输出流,可以传递入FileOutputStream对象
  • int size:指定缓冲流内部缓冲区大小,不指定则为默认

使用步骤:

1)创建FileOutputStream对象,构造方法中传入要输出的目的地

2)创建BufferedOutputStream对象,构造方法中传递入FileOutputStream对象,提高FileOutputStream对象的效率

3)使用BufferedOutputStream对象中的write()方法,把数据写入到内部的缓冲区

4)使用BufferedOutputStream对象中的flush()方法,把内部缓冲区中的数据刷新到文件中

5)close()方法释放资源(如果不再使用这个流对象,可以省略第4步,close会先刷新缓冲区,在关闭资源)

45. java.io.BufferedInputStream字节缓冲输入流 extends InputStream

构造方法:

  • BufferedInputStream(InputStream in)
  • BufferedInputStream(InputStream in, int size)

参数:

  • InputStream in:字节输入流,可以传递入FileInputStream对象
  • int size:指定缓冲流内部缓冲区大小,不指定则为默认

使用步骤:

1)创建FileInputStream对象,构造方法中传入要读取的数据源

2)创建BufferedInputStream对象,构造方法中传递入FileInputStream对象,提高FileInputStream对象的读取效率

3)使用BufferedInputStream对象中的read()方法,读取文件

4)释放资源

46. 读取效率最高:缓冲流+缓冲数组 代码示例

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
          	// 读写数据
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
    }
}

47. java.io.BufferedWriter字符缓冲输出流 extends Writer

构造方法:

  • BufferedWriter(Writer out)
  • BufferedWriter(Writer out, int size)

参数:

  • Writer out:字符输出流,可以传递入FileWriter对象
  • int size:指定缓冲区大小,不写则默认大小

字符缓冲输出流的特有的成员方法:

  • void newLine():写入一个行分隔符(换行符号);会根据不同的操作系统获取不同的行分隔符

使用步骤:

1)创建字符缓冲输出流对象,构造方法中传递入字符输出流

2)调用字符缓冲输出流中的write()方法,把数据写入到内存缓冲区中

3)调用字符缓冲输出流中的flush()方法,把内存缓冲区中的数据,刷新到文件中

4)释放资源

48. java.io.BufferedReader字符缓冲输入流 extends Reader

构造方法:

  • BufferedReader(Reader in)
  • BufferedReader(Reader in, int size)

字符缓冲输入流特有的成员方法:

  • String readLine():读取一个文本行;读取一行数据

返回值:包含该行内容的字符串,不包含任何行终止符,如果已经到达流末尾,则返回null

使用步骤:类似于字符缓冲输出流

49. 字符缓冲输入流和输出流 代码示例

public class BufferedTest {
    public static void main(String[] args) throws IOException {
        // 创建map集合,保存文本数据,键为序号,值为文字
        HashMap<String, String> lineMap = new HashMap<>();

        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));

        // 读取数据
        String line  = null;
        while ((line = br.readLine())!=null) {
            // 解析文本
            String[] split = line.split("\\.");
            // 保存到集合
            lineMap.put(split[0],split[1]);
        }
        // 释放资源
        br.close();

        // 遍历map集合
        for (int i = 1; i <= lineMap.size(); i++) {
            String key = String.valueOf(i);
            // 获取map中文本
            String value = lineMap.get(key);
          	// 写出拼接文本
            bw.write(key+"."+value);
          	// 写出换行
            bw.newLine();
        }
		// 释放资源
        bw.close();
    }
}

50. FileReader读取不同编码方式的文本时遇到的问题

java.io.FileReader可以读取IDEA默认的编码格式(UTF-8)的文件

但是,java.io.FileReader读取windows系统默认的中文编码格式GBK时,会产生乱码


  • GBK:中文码表,使用2个字节存储一个中文
  • UTF-8:国际标准码表,使用3个字节存储一个中文

FileReader的底层是FileInputStream读取字节,然后FileReader解码

FileWriter的底层是先编码为字节,然后用FIleOutputStream输出字节

51. 转换流java.io.OutputStreamWriter extends Writer

是字符流转换为字节流的桥梁;可以使用指定的编码表,将要写入字节流中的字符编码为字节

构造方法:

  • OutputStreamWriter(OutputStream out):创建使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream out, String charsetName):创建使用指定字符集的字符流

参数:

  • OutputStream out:字节输出流
  • String charsetName:指定编码表名称

不区分大小写,可以是 utf-8/UTF-8、gbk/GBK、...

不指定,默认使用UTF-8


使用步骤:

1)创建OutputStreamWriter对象,构造方法传递字节输出流和指定的编码表名称

2)使用OutputStreamWriter对象中的write()方法,把字符转换为字节存储到缓冲区中

3)使用OutputStreamWriter对象中的flush()方法,把内存缓冲区的字节刷新到文件中

4)释放资源

52. 转换流java.io.InputStreamReader extends Reader

是字节流转换为字符流的桥梁;它使用指定的编码表读取字节并将其解码为字符

构造方法:

  • InputStreamReader(InputStream in):创建一个使用默认字符集的字符流
  • InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流

注意:

构造方法中指定的编码表名称要和读取的原文件的相同,否则会乱码

使用步骤:

类似上面的那个转换流

53. 转换流 代码示例

public class TransDemo {
   public static void main(String[] args) {      
    	// 1.定义文件路径
     	String srcFile = "file_gbk.txt";
        String destFile = "file_utf8.txt";
		// 2.创建流对象
    	// 2.1 转换输入流,指定GBK编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
    	// 2.2 转换输出流,默认utf8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
		// 3.读写数据
    	// 3.1 定义数组
        char[] cbuf = new char[1024];
    	// 3.2 定义长度
        int len;
    	// 3.3 循环读取
        while ((len = isr.read(cbuf))!=-1) {
            // 循环写出
          	osw.write(cbuf,0,len);
        }
    	// 4.释放资源
        osw.close();
        isr.close();
  	}
}

54. java.io.ObjectOutputStream对象的序列化流 extends OutputStream

把对象以流的方式写入到文件中保存

构造方法:

  • public ObjectOutputStream(OutputStream out)

参数:字节输出流


特有的成员方法:

  • void writeObject(Object obj):将指定的对象写入ObjectOutputStream

使用步骤:

1)创建ObjectOutputStream对象,构造方法中传递入字节输出流

2)使用ObjectOutputStream对象中的writeObject方法,把对象写入到文件中

3)释放资源


代码示例:

public class SerializeDemo{
   	public static void main(String [] args)   {
    	Employee e = new Employee();
    	e.name = "zhangsan";
    	e.address = "beiqinglu";
    	e.age = 20; 
    	try {
      		// 创建序列化流对象
          ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        	// 写出对象
        	out.writeObject(e);
        	// 释放资源
        	out.close();
        	fileOut.close();
        	System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
        } catch(IOException i)   {
            i.printStackTrace();
        }
   	}
}

注意:

  • 该类必须实现java.io.Serializable接口来启用序列化或反序列化功能
  • Serializable是一个标记型接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException没有序列化异常
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。

55. java.io.ObjectInputStream对象的反序列化流 extends InputStream

把文件中保存的对象,以流的方式读取到出来用

构造方法:

  • public ObjectInputStream(InputStream in)

参数:字节输入流


特有的成员方法:

  • Object readObject():从ObjectInputStream读取对象

使用步骤:

类似于对象的序列化流


示例代码:

public class DeserializeDemo {
   public static void main(String [] args)   {
        Employee e = null;
        try {		
             // 创建反序列化流
             FileInputStream fileIn = new FileInputStream("employee.txt");
             ObjectInputStream in = new ObjectInputStream(fileIn);
             // 读取一个对象
             e = (Employee) in.readObject();
             // 释放资源
             in.close();
             fileIn.close();
        }catch(IOException i) {
             // 捕获其他异常
             i.printStackTrace();
             return;
        }catch(ClassNotFoundException c)  {
        	// 捕获类找不到异常
             System.out.println("Employee class not found");
             c.printStackTrace();
             return;
        }
        // 无异常,直接打印输出
        System.out.println("Name: " + e.name);	// zhangsan
        System.out.println("Address: " + e.address); // beiqinglu
        System.out.println("age: " + e.age); // 0
    }
}

能够反序列化的前提:

  • 类必须实现了Seriallizable接口
  • 必须存在类对应的class文件;当不存在对象的那个类的class文件时,readObject()方法会抛出ClassNotFoundException异常(class文件找不到异常)

InvalidClassException 异常:

当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操
作也会失败,抛出一个InvalidClassException 异常。

InvalidClassException 异常原理:

  • 编译器(javac.exe)会把.java文件编译成.class文件时,类实现了Seriallizable接口,就会根据类的定义,给.class文件添加一个序列号"serialVersionUID"
  • 反序列化的时候,会使用.class文件中的序列号和保存得到的.txt文件中的序列号进行比较;

如果一样,反序列化成功;如果不一样,抛出序列化冲突异常:InvalidClassException


手动给类添加一个序列号"serialVersionUID",格式在Seriallizable接口规定了:

声明名为"serialVersionUID"字段,必须是静态(static),最终的(final)的long型字段

例如:

public class Employee implements java.io.Serializable {
     // 加入序列版本号
     private static final long serialVersionUID = 1L;
     public String name;
     public String address;
     // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

56. 序列化集合

若要在文件中保存多个对象,可以把多个对象保存到一个集合中,然后对集合进行序列化和反序列化的操作

57. transient关键字 和static关键字 修饰的成员变量不能被序列化

  • transient关键字:瞬态关键字

被瞬态关键字修饰的成员变量,不能被序列化

  • static关键字:静态关键字

静态优先于非静态加载到内存中(静态资源优先于对象进入到内存中),所以被static修饰的成员变量不能被序列化

58. 打印流java.io.PrintStream extends FilterOutputStream extends OutputStream

为其他输出流添加了功能,使他们能够方便地打印各种数据值表示形式

特点:

  • 只负责数据的输出,不负责数据的读取
  • 与其他输出流不同,PrintStream永远不会抛出IOException异常

特有的成员方法:

  • void print(任意数据类型的值)
  • void println(任意数据类型的值并换行)

构造方法:

  • PrintStream(File file):目的地是文件
  • PrintStream(OutputStream out):目的地是字节输出流
  • PrintStream(String filename):目的地是文件路径

注意:

  • 如果使用继承父类来的write()方法写数据,那么查看数据的时候会查询编码表,例如:97 -> a
  • 如果使用自己特有的方法print/println写数据,会原样输出,例如:97 -> 97

System.setOut(PrintStream out)方法:

输出语句默认在控制台输出,使用System.setOut()方法来改变输出语句的目的地(打印流的流向)

代码示例:

public class PrintDemo {
    public static void main(String[] args) throws IOException {
		// 调用系统的打印流,控制台直接输出A
        System.out.println("A");
      
		// 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("ps.txt");
      	
      	// 设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
      	// 调用系统的打印流,ps.txt中输出B
        System.out.println("B");
        ps.close();
    }
}
# Java 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×