JAVA12_IO

在整个java.io包中最重要的就是5个类和一个接口:File,OutputStream,InputStream,Writer,Reader;一个接口是Serializable.

1.操作文件的类

(1)File类的基本介绍

在整个io包中,唯一与文件本身有关的类就是File类。使用File类可以进行创建或删除文件等常用操作。

构造方法:
public File(String pathname)
    通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。如果给定字符串是空字符串,那么结果是空抽象路径名.
    如果要使用一个File类,则必须向File类的构造方法中传递一个文件路径。例如,现在要操作D盘下的test.txt文件,则路径必须写为“d:\\test.txt”,其中“\\”表示一个“\"。
常用方法和常量:
1.public static final String pathSeparator  表示路径的分隔符(windows是:“;”)
2.public static final String separator  表示路径的分隔符(windows是:“\”)
3.public File(String pathname)  创建File类对象,传入完整路径
4.public boolean createNewFile()throws IOException  创建新文件
5.public boolean delete()   删除文件
6.public boolean exists()   判断文件是否存在
7.public boolean isDirectory()  判断给定的文件是否是一个目录
8.public long length()  返回文件的大小
9.public String[] list()    列出指定目录的全部内容,知识列出了名称
10.public File[] listFiles()    列出指定目录的全部内容,会列出路径
11.public boolean mkdir()   创建一个目录
12.public boolean renameTo(File dest)   为已有的文件重新命名
问题:为什么File类中的常量定义的命名规则不符合标准命名规则?
File类出现较早,当时并没有对命名规范有严格的要求,这些属于Java的历史遗留问题。

(2)使用File类操作文件

实例操作一:创建一个新文件

实例1:创建一个新文件
/**
 * 现在要在d盘创建一个名为test.txt的文件
 */
    @Test
    public void test01(){
        File file = new File("d:\\test.txt");//必须给出完整路径
        try {
            file.createNewFile();//根据给定的路径创建新文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
实例2:观察File中提供的两个常量
注意:如果在不同的操作系统中,路径的分隔符表示是不一样的。
windows系统中使用反斜杠表示目录的分隔符“\”
linux系统中使用正斜杠表示目录的分隔符“/”
在编写路径时最好可以根据程序所在的操作系统自动使用符合本地操作系统要求的分隔符,这样才能达到可移植性的目的。需要观察File类中提供的两个常量。
@Test
    public void test02(){
        System.out.println("pathSeparator:"+File.pathSeparator);//调用静态常量
        System.out.println("Separator:"+File.separator);//调用静态常量
    }

程序运行结果:
pathSeparator:;
Separator:\

所以对于之前创建文件的操作来说,最好的做法是使用以上的常量表示路径分隔符。
实例3:修改创建文件的代码:
@Test
    public void test03(){
        String path = "d:"+File.separator+"test.txt";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        try {
            file.createNewFile();//根据给定的路径创建新文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注意:在操作文件时一定要使用File.separator表示分隔符。这样即使在Windows操作系统开发程序,在Linux或其他操作系统部署程序都不会出现问题。

实例操作二:删除一个指定的文件

实例1:删除文件
@Test
    public void test04(){
        String path = "d:"+File.separator+"test.txt";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        file.delete();
    }

注意:文件虽然删除,但是以上做法也存在问题,如果文件不存在呢?
实例2:在删除文件中新增判断
@Test
    public void test05(){
        String path = "d:"+File.separator+"test.txt";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        if(file.exists()){//判断文件是否存在
            file.delete();//如果存在,则删除文件
        }
    }

实例操作三:综合创建文件和删除文件的操作

实例1:现在给定一个文件的路径,如果此文件存在,则将其删除,如果文件不存在则创建一个新的文件。
@Test
    public void test06(){
        String path = "d:"+File.separator+"test.txt";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        if(file.exists()){
            file.delete();
        }
        else {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

注意:
1:在每次程序执行完毕之后,文件并不会立刻创建或删除,会有一些延迟,这是因为所有的操作都需要通过JVM完成所造成的。
2:文件后缀可有可无,并不会影响文件本身的内容。

实例操作四:创建一个文件夹

实例1:创建一个文件夹
@Test
    public void test07(){
        String path = "d:"+File.separator+"test";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        file.mkdir();//创建文件夹
    }

实例操作五:列出指定目录的全部内容

实例1:使用list()方法列出一个目录中的全部内容
@Test
    public void test08(){
        String path = "d:"+File.separator+"tomcat6";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        String[] list = file.list();
        for (String str : list) {
            System.out.println(str);
        }
    }
运行结果:
backup
bin
conf
lib
LICENSE
logs
NOTICE
RELEASE-NOTES
RUNNING.txt
temp
webapps
work
实例2:使用listFiles()方法列出一个目录中的全部内容
@Test
    public void test09(){
        String path = "d:"+File.separator+"tomcat6";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        File[] listFiles = file.listFiles();
        for (File file2 : listFiles) {
            System.out.println(file2);
        }
    }

运行结果:
d:\tomcat6\backup
d:\tomcat6\bin
d:\tomcat6\conf
d:\tomcat6\lib
d:\tomcat6\LICENSE
d:\tomcat6\logs
d:\tomcat6\NOTICE
d:\tomcat6\RELEASE-NOTES
d:\tomcat6\RUNNING.txt
d:\tomcat6\temp
d:\tomcat6\webapps
d:\tomcat6\work

实例操作六:判断一个给定的路径是否是目录

实例1:判断给定的路径是否是目录
@Test
    public void test10(){
        String path = "d:"+File.separator+"tomcat6";//拼凑出可以适应操作系统等的路径
        File file = new File(path);//必须给出完整路径
        if(file.isDirectory()){
            System.out.println(file.getPath()+"路径是目录");
        }else {
            System.out.println(file.getPath()+"路径不是目录");
        }
    }

(3)范例-列出指定目录的全部内容

实例1:给定一个目录,要求列出此目录下的全部内容,因为给定目录可能存在子文件夹,此时要求也可以把所有的子文件夹列出来。
思路:要先判断给定的路径是否是目录,然后使用listFiles()列出一个目录中的全部内容,一个文件夹中可能包含子文件夹,子文件夹中又可能包含文件,所以此处需要使用递归。
public static void main(String[] args) {
        String path = "d:"+File.separator+"testfile.txt";
        File file = new File(path);
        print(file);
    }

    public static void print(File file){
        if(file!=null){
            if(file.isDirectory()){
                File[] listFiles = file.listFiles();
                for (File file2 : listFiles) {
                    System.out.println(file2);
                    print(file2);
                }
            }else {
                System.out.println(file);
            }
        }
    }

2.RandomAccessFile类(待完善)

    File类只是针对文件本身进行操作,而如果要对文件的内容进行操作,则可以使用RandomAccessFile类。此类属于随机读取类,可以随机地读取一个文件中指定位置的数据。
    例如:现在在文件中保存了以下3个数据。
    zhangsan,30.
    lisi,31。
    wangwu,32。
    那么如果使用RandomAccessFile类读取“lisi”的信息时,就可以将“zhangsan”的信息跳过,相当于在文件中设置了一个指针,根据此指针的位置进行读取。但是如果想实现这个功能,则每个数据的长度应该保持一致。所以在设置姓名时应统一设置为8位长度,数字为4位长度。
    要实现此功能,则必须依靠RandomAccess中的几种设置模式,然后在构造方法中传递此模式。
常用操作方法:
1.public RandomAccessFile(File file,String mode) throws FileNotFoundException   接收File类的对象,指定操作路径,但是在设置时需要设置模式,r为只读,w为只写,rw为读写。
2.public RandomAccessFile(String name,String mode) throws FileNotFoundException 不再使用File类对象表示文件,而是直接输入一个固定的文件路径。
3.public void close()throws IOException 关闭操作
4.public int read(byte[] b) throws IOException  将内容读取到一个byte数组中
5.public final byte readByte() throws IOException   读取一个字节
6.public final int readInt() throws IOException     从文件中读取整型数据
7.public void seek(long pos) throws IOException     设置读指正的位置
8.public final void writeBytes(String s) throws IOException     将一个字符串写入到文件中,按字节的方式处理
9.public final void writeInt(int v) throws IOException      将一个int型数据写入文件,长度为4位
10.public int skipBytes(int n) throws IOException       指针跳过指定的字节

注意:如果使用rw方式声明RandomAccessFile对象时,要写入的文件不存在,系统将自动进行创建。

(1)使用RandomAccessFile类写入数据

实例1:写文件。
写入三个数据,为了保证能够随机读取,所有写入的名字都是8个字节,写入的数字是固定的4个字节。不够8位的用空格补全。
@Test
    public void test01() throws Exception{
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        RandomAccessFile raf = null;//声明一个RandomAccessFile类对象
        raf = new RandomAccessFile(file, "rw");//以读写方式打开文件,会自动创建新文件
        String name = null;
        int age = 0;
        name = "lijishen";//字符串长度为8
        age = 1000;//数字长度为4
        raf.writeBytes(name);//将姓名写入文件中
        raf.writeInt(age);//将年龄写入文件中
        name = "lisi    ";
        age = 31;
        raf.writeBytes(name);
        raf.writeInt(age);
        name = "wangwu  ";
        age = 32;
        raf.writeBytes(name);
        raf.writeInt(age);
        raf.close();
    }
结果显示:
lijisheng   lisi   wangwu    
发现int整数没有显示出来

(2)使用RandomAccessFile类读取数据

实例1:读取文件
读取时直接使用r的模式即可,以只读的方式打开文件。
读取时所有的字符串只能按照byte数组的方式读取出来,而且所有的长度都是8位。
@Test
    public void test02() throws Exception{
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        RandomAccessFile raf = null;//声明一个RandomAccessFile类对象
        raf = new RandomAccessFile(file, "r");//以读写方式打开文件,会自动创建新文件
        String name = null;
        int age = 0;
        byte[] b = new byte[8];//准备空间读取姓名
        raf.skipBytes(12);
        for (int i = 0; i < b.length; i++) {
            b[i] = raf.readByte();//循环读取前8个内容
        }
        name = new String(b);//将读取出来的byte数组变为string类型
        age = raf.readInt();//读取数字
        System.out.println("第二个人的信息:姓名:"+name+",年龄:"+age);

        raf.seek(0);//指针回到文件的开头
        b = new byte[8];//准备空间读取姓名
        for (int i = 0; i < b.length; i++) {
            b[i] = raf.readByte();//循环读取前8个内容
        }
        name = new String(b);//将读取出来的byte数组变为string类型
        age = raf.readInt();//读取数字
        System.out.println("第一个人的信息:姓名:"+name+",年龄:"+age);

        raf.seek(24);//指针回到文件的开头
        b = new byte[8];//准备空间读取姓名
        for (int i = 0; i < b.length; i++) {
            b[i] = raf.readByte();//循环读取前8个内容
        }
        name = new String(b);//将读取出来的byte数组变为string类型
        age = raf.readInt();//读取数字
        System.out.println("第三个人的信息:姓名:"+name+",年龄:"+age);

    }
结果显示:

第二个人的信息:姓名:lisi    ,年龄:31
第一个人的信息:姓名:lijishen,年龄:30
第三个人的信息:姓名:wangwu  ,年龄:32

注意:随机读写流可以实现对文件内容的操作,但是却过于复杂,所以一般情况下操作文件使用字符或字节流。

3.字节流与字符流基本操作

    在程序中所有的数据都是以流的方式进行传输或保存的。程序需要数据时要使用输入流读取数据;而当程序需要将一些数据保存起来时,就要使用输出流。
    java.io包中流的操作主要有字节流,字符流两大类,两类都有输入和输出操作。在字节流中输出数据主要使用OutputStream类完成,输入使用InputStream类。在字符流中输出使用的是Writer类完成,输入主要使用Reader类完成。
    java中IO操作也是有相应步骤的,以文件的操作为例,主要的操作流程如下:
    (1)使用File类打开一个文件
    (2)通过字节流或字符流的子类指定输出的位置
    (3)进行读/写操作
    (4)关闭输入/输出

(1)字节流

    字节流主要操作byte类型数据,以byte数组为准,主要操作类是OutputStream类和InputStream类。
    1.字节输出流:OutputStream,OutputStream是整个IO包中字节输出流的最大父类,此类的定义如下:
    public abstract class OutputStream 
    extends Object 
    implements Closeable, Flushable
    OutputStream类是一个抽象类,如果要使用此类,则首先必须通过子类实例化对象。如果现在要操作的是一个文件,则可以使用FileOutputStream类,通过向上转型后,可以为OutputStream实例化。
在OutputStream类中的主要操作方法如下:
1.public void close() throws IOException    关闭输出流
2.public void flush() throws IOException    刷新缓冲区
3.public void write(byte[] b) throws IOException    将一个byte数组写入数据流
4.public void write(byte[] b,int off,int len)throws IOException     将一个指定范围的byte数组写入数据流
5.public abstract void write(int b)throws IOException   将一个字节数据写入数据流

此时使用FileOutputStream子类,此类的构造方法如下:
public FileOutputStream(File file)throws FileNotFoundException  
操作时必须接收File类的实例,指明要输出的文件路径。

OutputStream 类实现了两个接口:Closeable(可关闭)  Flushable(可刷新)

实例一:向文件中写入字符串

实例1:
@Test
    public void test01() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        OutputStream out = null;//准备好一个输出流对象
        out = new FileOutputStream(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "hello world";//准备一个字符串
        byte[] b = str.getBytes();//只能输出byte数组,所以将字符串变为byte数组。
        out.write(b);//将内容输出,保存文件
        //第四步:关闭输出流
        out.close();//关闭输出流

    }

注意:如果文件不存在会自动创建。
以上操作是直接将一个字符串变为byte数组,然后将byte数组直接写入到文件中,当然也可以通过循环把每一个字节一个个地写入到文件中。
实例2:使用write(int t)的方式写入文件内容
@Test
    public void test01() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        OutputStream out = null;//准备好一个输出流对象
        out = new FileOutputStream(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "hello world";//准备一个字符串
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);//将内容输出
        }
        //第四步:关闭输出流
        out.close();//关闭输出流
    }

实例二:追加新内容

    之前的所有操作中,如果重新执行程序,则肯定会覆盖文件中的已有内容,那么此时可以通过FileOutputStream类的另外一个构造方法进行实例化,这样在写入的时候就表示向文件中追加内容,
    public FileOutputStream(File file, boolean append)
throws FileNotFoundException    在构造方法中,如果将append的值设置为true,则表示在文件的末尾追加内容。
实例1:修改之前的程序,追加文本内容
@Test
    public void test01() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        OutputStream out = null;//准备好一个输出流对象
        out = new FileOutputStream(file, true);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "hello world";//准备一个字符串
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);//将内容输出
        }
        //第四步:关闭输出流
        out.close();//关闭输出流
    }
实例2:追加的时候增加换行(直接在字符串要换行的地方加一个“\r\n”)
@Test
    public void test01() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        OutputStream out = null;//准备好一个输出流对象
        out = new FileOutputStream(file, true);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "\r\n hello world";//准备一个字符串
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);//将内容输出
        }
        //第四步:关闭输出流
        out.close();//关闭输出流
    }

实例三:字节输入流 InputStream

public abstract class InputStream
extends Object
implements CloseableOutputStream类一样,InputStream本身也是一个抽象类,必须依靠其子类,如果现在从文件中读取,子类肯定也是FileInputStream
主要方法如下:
1.public int available() throws IOException     可以取得输入文件的大小
2.public void close() throws IOException    关闭输入流
3.public abstract int read() throws IOException     读取内容,以数字的方式读取
4.public int read(byte[] b) throws IOException      将内容读取到byte数组中,同时返回读入的个数。

FileInputStream类的构造方法如下:
public FileInputStream(File file)throws FileNotFoundException

实例一:从文件中读取内容

实例1:从文件中读取内容
@Test
    public void test02() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        InputStream input = null;//准备好一个输出流对象
        input = new FileInputStream(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        byte[] b = new byte[1024];//把所有的内容读取到此数组中
        input.read(b);//把内容取出,内容读到byte数组中
        //第四步:关闭输出流
        input.close();//关闭输出流
        System.out.println("内容为:"+new String(b));//把byte数组变为字符串输出
    }

注意:内容已经被读取出来,但是后面有很多空格,因为开辟的空间太大,在将byte数组变为字符串的时候也将无用的空间转换为字符串,这样的操作是不合理的。要想解决此问题,则要观察read方法,在此方法上有一个返回值,此返回值表示向数组中插入了多少个数据。
实例2:修正以上的错误
@Test
    public void test02() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        InputStream input = null;//准备好一个输出流对象
        input = new FileInputStream(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        byte[] b = new byte[1024];//把所有的内容读取到此数组中
        int len = input.read(b);//把内容取出,内容读到byte数组中
        //第四步:关闭输出流
        input.close();//关闭输出流
        System.out.println("读入数据的长度:"+len);
        System.out.println("内容为:"+new String(b, 0, len));//把byte数组变为字符串输出
    }

    注意:在使用FileInputStream读取时如果指定的路径不存在,则程序运行会出现异常。
    上面的代码虽然也可以从文件中读取内容,可同时也会发现,由于要通过byte数组接收全部的内容,所以如果文件内容小的话,则会造成很多空间的浪费,那么此时能否根据文件的数据量来选择开辟空间的大小呢?要想完成这样的操作,则要从File类中着手,因为在File类中存在一个length()的方法,此方法可以取得文件的大小。
实例3:开辟指定大小的byte数组。
@Test
    public void test02() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        InputStream input = null;//准备好一个输出流对象
        input = new FileInputStream(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        byte[] b = new byte[(int) file.length()];//把所有的内容读取到此数组中
        int len = input.read(b);//把内容取出,内容读到byte数组中
        //第四步:关闭输出流
        input.close();//关闭输出流
        System.out.println("内容为:"+new String(b));//把byte数组变为字符串输出
    }


除以上方法外,也可以通过循环从文件中一个个地把内容读取进来,直接使用read()方法即可。
实例4:使用read()通过循环读取
@Test
    public void test02() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        InputStream input = null;//准备好一个输出流对象
        input = new FileInputStream(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        byte[] b = new byte[(int) file.length()];//把所有的内容读取到此数组中
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) input.read();
        }
        //第四步:关闭输出流
        input.close();//关闭输出流
        System.out.println("内容为:"+new String(b));//把byte数组变为字符串输出
    }

但是以上程序是在明确知道了具体数组大小的前提下开展的,如果此时不知道要输入的内容多大,则只能通过判断是否读到文件的末尾的方式来读取文件。
实例5:另一种读取的方式
@Test
    public void test03() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        InputStream input = null;//准备好一个输出流对象
        input = new FileInputStream(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        int len = 0;//用于记录读取的数据个数
        byte[] b = new byte[1024];//所有的内容读到此数组中
        int temp = 0;//接收读取的每一个内容
        while((temp=input.read())!=-1){
            b[len] = (byte) temp;
            len++;
        }
        //第四步:关闭输出流
        input.close();//关闭输出流
        System.out.println("内容为:"+new String(b, 0, len));//把byte数组变为字符串输出
    }

注意:文件读到末尾了,则返回的内容为-1

(2)字符流

在程序中一个字符==两个字节,那么java提供了Reader和Writer两个专门操作字符流的类。

实例一:字符输出流Writer

Writer本身是一个字符流的输出类,此类的定义如下:
public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable
此类本身也是一个抽象类,如果要使用此类,则肯定要使用其子类,此时如果是向文件中导入内容,用过使用FileWriter子类。
Writer类的常用方法如下:
1.public abstract void close() throws IOException   关闭输出流
2.public void write(String str) throws IOException  将字符串输出
3.public void write(char[] cbuf) throws IOException 将字符数组输出。
4.public abstract void flush() throws IOException   强制性清空缓存

FileWriter类的构造方法如下:
public FileWriter(File file) throws IOException
关于Appendable接口的说明:此接口表示的是内容可以被追加,接收的参数是CharSequence,实际上String类就实现了此接口,所以可以直接通过此接口的方法向输出流中追加内容。
实例1:向文件中写入数据
@Test
    public void test04() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Writer out  = null;//准备好一个输出流对象
        out = new FileWriter(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "lijisheng";
        out.write(str);
        //第四步:关闭输出流
        out.close();//关闭输出流
    }

整个程序与OutputStream的操作流程并没有什么太大的区别,唯一的好处是:可以直接输出字符串,而不用讲字符串变为byte数组之后再输出。

实例二:使用FileWriter追加文件的内容

在使用字符流操作时,也可以实现文件的追加功能,直接使用FileWriter类中的以下构造即可实现追加
public FileWriter(File file, boolean append)
throws IOException
将append的值设置为true,表示追加。
实例1:使用FileWriter追加文件的内容
@Test
    public void test04() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Writer out  = null;//准备好一个输出流对象
        out = new FileWriter(file, true);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "\r\n 七夕一个人过";
        out.write(str);
        //第四步:关闭输出流
        out.close();//关闭输出流
    }

实例三:字符输入流Reader

Reader是使用字符的方式从文件中取出数据,Reader类的定义如下:
public abstract class Reader
extends Object
implements Readable, Closeable
Reader本身也是抽象类,如果现在要从文件中读取内容,则可以直接使用FileReader子类。
Reader类的常用方法如下:
1.public abstract void close() throws IOException   关闭输出流
2.public int read() throws IOException  读取单个字符
3.public int read(char[] cbuf) throws IOException   将内容读到字符数组中,返回读入的长度。

FileReader的构造方法定义如下:
public FileReader(File file) throws FileNotFoundException
实例1:从文件中读取内容
@Test
    public void test05() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Reader reader  = null;//准备好一个输出流对象
        reader = new FileReader(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        char[] c = new char[1024];//所有的内容读到此数组中
        int len = reader.read(c);//将内容输出
        //第四步:关闭输入流
        reader.close();//关闭输出流
        System.out.println("内容为:"+new String(c, 0, len));//把char数组变为字符串输出。
    }

注意:如果此时不知道数组的长度,也可以像之前操作字节流那样,使用循环的方式进行内容的读取。
实例2:使用循环的方式读取内容
@Test
    public void test06() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Reader reader  = null;//准备好一个输出流对象
        reader = new FileReader(file);//通过对象多态性,进行实例化
        //第三步:进行读操作
        int len = 0;
        char[] c = new char[1024];
        int temp = 0;
        while((temp = reader.read())!=-1){
            //将每次的读取的内容给temp变量,如果temp懂得值不是-1,则表示文件没有读完。
            c[len] = (char) temp;
            len++;
        }
        //第四步:关闭输入流
        reader.close();//关闭输出流
        System.out.println("内容为:"+new String(c, 0, len));//把char数组变为字符串输出。
    }

(3)字节流和字符流的区别

实际上字节流在操作时,本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件。
下面以两个写文件的操作为主进行比较,但是在操作时字节流和字符流的操作完成之后都不关闭输出流。

实例一:字节流和字符流的区别 :

1.使用字节流不关闭执行
@Test
    public void test07() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        OutputStream out = null;//准备好一个输出流对象
        out = new FileOutputStream(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "hello world";//准备一个字符串
        byte[] b = str.getBytes();//只能输出byte数组,所以将字符串变为byte数组。
        out.write(b);//将内容输出,保存文件
        //第四步:关闭输出流
        //out.close();//关闭输出流
    }

此时没有关闭字节流操作,但是文件中也依然存在了输出的内容,证明字节流是直接操作文件本身的。而下面再继续使用字符流完成,再观察效果。
实例2:使用字符流不关闭执行
@Test
    public void test08() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Writer out  = null;//准备好一个输出流对象
        out = new FileWriter(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "lijisheng";
        out.write(str);
        //第四步:关闭输出流
        //out.close();//关闭输出流
    }

程序运行后发现文件中并没有任何内容,这是因为字符流操作时使用了缓冲区,而在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的,所以得出结论:字符流使用了缓冲区,而字节流没有使用缓冲区。
    缓冲区:可以简单地理解为一段内存区域。
    某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。
    在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。
    如果想在不关闭的情况下也可以将字符流的内容全部输出,则可以使用Writer类中的flush()方法完成。
实例3:强制性清空缓存区
@Test
    public void test08() throws Exception{
        //第一步:通过File类找到一个文件
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        //第二步:通过子类实例化父类对象
        Writer out  = null;//准备好一个输出流对象
        out = new FileWriter(file);//通过对象多态性,进行实例化
        //第三步:进行写操作
        String str = "lijisheng";
        out.write(str);
        out.flush();
        //第四步:关闭输出流
        //out.close();//关闭输出流
    }
提问:使用字节流好还是使用字符流好?

首先所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只在内存中才会形成。所以在开发中,字节流使用较为广泛。

(4)范例:文件复制

在DOS命令中存在一个文件的复制命令(copy),例如,现在要将d盘中的test.txt文件复制到d盘中的demo.txt文件,则只要在命令行输入copy即可完成。
d:
copy d:\test.txt d:\demo.txt
思路分析:
1.运行格式:java copy 源文件 目标文件
2.从运行格式中可以发现,要想输入源文件或者目标文件的路径,可以通过命令行参数完成,但是此时就必须对输入的参数进行验证,如果输入的参数格式不是两个,或者输入的源文件路径不存在,则程序都应该给出错误信息并推出。
3.使用字节流还是字符流完成操作呢?
因为要复制的文件不一定都是文本文件,也可能包含图片或声音等,所以如果此时使用字符流的话肯定不能很好的完成操作,所以必须使用字节流完成,使用OutputStream,InputStream类。而且要完成这样的复制程序可以有以下两种方式操作。
    a:将源文件中的内容全部读取到内存中,并一次性写入到目标文件中农
    b:不将源文件的内容全部读取出来,而是采用边读边写的方式。
    很明显采用第二种方式更加合理,因为将源文件的内容一次性读取进来的话,如果文件内容过多,则整个内存是无法装下的,程序肯定会出现异常;而如果采取边读边写的方式,则肯定要比全部读进来性能要高。

实例一:实现复制功能

实例1:实现复制功能:
@Test
    public void testCopy() {
        String[] args = new String[2];
        args[0] = "d:\\test.txt";
        args[1] = "d:\\demo.txt";
        if(args.length!=2){//判断是否是两个参数
            System.out.println("输入的参数个数不正确");
            System.out.println("例:java copy 源文件路径,目标文件路径");
            System.exit(1);//系统退出
        }
        File file1 = new File(args[0]);//源文件的File对象
        File file2 = new File(args[1]);//目标文件的File对象
        if(!file1.exists()){
            System.out.println("源文件不存在!");
            System.exit(1);
            }
        InputStream input = null;//准备好输入流对象,读取源文件
        OutputStream out = null;//准备好输出流对象,写入到目标文件
        try {
            input = new FileInputStream(file1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try {
            out = new FileOutputStream(file2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        if(input!=null && out!=null){//判断输入或输出是否准备好
            int temp = 0;
            try {
                while((temp = input.read())!=-1){//开始复制
                    out.write(temp);//边读边写
                }
                System.out.println("复制完成!");
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("复制失败!");
            }
        }
        try {
            input.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

4.转换流-OutputStreamWriter类与InputStreamReader类

    整个io包实际上分为字符流和字节流,但是除了这两个流之外,还存在一组字节流-字符轮流的转换流。
    OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为字节流的输出对象。
    InputStreamReader: 是Reader的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为字符流的输入对象。
    以文件操作为例:内存中的字符数据需要通过OutputStreamWriter变为字节流才能保存到文件中,读取时需要将读入的字节流通InputStreamReader变为字符流。不管如何操作,最终都是以字节的形式保存在文件中。
    OutputStreamWriter的构造方法如下:
    public OutputStreamWriter(OutputStream out)

实例一:将字节输出流变为字符输出流

实例1:将字节输出流变为字符输出流
@Test
    public void test01() throws Exception{
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        Writer out = null;
        out = new OutputStreamWriter(new FileOutputStream(file));//字节流变为字符流
        out.write("good morning");
        out.close();
    }

实例二:将字节输入流变为字符输入流

实例2:将字节输入流变为字符输入流
@Test
    public void test02() throws Exception{
        String path = "d:"+File.separator+"test.txt";
        File file = new File(path);
        Reader reader = null;
        reader = new InputStreamReader(new FileInputStream(file));//将字节流变为字符流
        char[] c = new char[1024];
        int len = reader.read(c);
        reader.close();
        System.out.println(new String(c, 0, len));
    }
以上两个操作都是将字节流的操作类以字符流的形式进行输出和输入。
在JDK文档中可以知道FileOutputStream是OutputStream的子类,FileInputStream是InputStream的子类。但是在字符流中这两个操作类却有一些特殊,FileWriter并不直接是Writer的子类,而是OutputStreamWriter的子类,而FileReader也不直接是Reader的子类,而是InputStreamReader的子类。这两个子类中间都需要进行转换的操作,从这两个类的继承关系就可以清楚的发现,不管是使用字节流还是使用字符流实际上都是以字节的形式操作输入/输出流的。

5.内存操作流

    前面讲解的程序中输出和输入都是从文件中来的,当然,也可以将输入和输出的位置设置到内存上。此时就要使用ByteArrayInputStream,ByteArrayOutputStream来完成内存的输入和输出功能。
    ByteArrayInputStream主要完成将内容写入到内存中,而ByteArrayOutputStream的功能主要是将内存中的数据输出。
ByteArrayInputStream类的主要方法如下:
1.public ByteArrayInputStream(byte[] buf)       将全部的内容写入内存中
2.public ByteArrayInputStream(byte[] buf, int offset, int length)       将制定范围的内容写入到内存中

ByteArrayInputStream主要是使用构造方法将全部的内容读取到内存中,如果想把内容从内存中取出,则可以使用ByteArrayOutputStream类
ByteArrayOutputStream类的主要方法如下:
1.public ByteArrayOutputStream()        创建对象
2.public void write(int b)      将内容从内存中输出

实例一:使用内存操作流完成一个大写字母转换为小写字母的程序性

@Test
    public void test01(){
        String str = "HELLO WORLD";
        ByteArrayInputStream bis = null;//声明一个内存的输入流
        ByteArrayOutputStream bos = null;//声明一个内存的输出流
        bis = new ByteArrayInputStream(str.getBytes());//向内存中输入内容
        bos = new ByteArrayOutputStream();//准备从bis中读取数据
        int temp = 0;
        while((temp = bis.read())!= -1){
            char c = (char) temp;//将读取的数字变为字符
            bos.write(Character.toLowerCase(c));//将字符变为小写
        }
        String newStr = bos.toString();
        try {
            bis.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(newStr);
    }

内存操作流的使用:内存操作流一般在生成一些临时信息时才会使用,如果将这些临时信息保存在文件中,则代码执行完后肯定删除这个临时文件会比较麻烦,这时使用内存操作流是最合适的。

6.管道流

管道流的主要作用是可以进行两个线程间的通信。分管道输出流(PipedOutputStream)和管道输入流(PipedInputStream)。如果要进行管道输出,则必须把输出流连接在输入流上,在PipedOutputStream类上有如下方法用于管道连接。

public void connect(PipedInputStream snk) throws IOException        

实例一:验证管道流:

管道输出流:
package file;

import java.io.IOException;
import java.io.PipedOutputStream;

public class Send implements Runnable{//实现Runnable接口

    private PipedOutputStream pos = null;//管道输出流

    public Send() {
        this.pos = new PipedOutputStream();//实例化输出流
    }

    @Override
    public void run() {
        String str = "HELLO WORLD";
        try {
            this.pos.write(str.getBytes());//输出信息
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.pos.close();//关闭输出流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public PipedOutputStream getPos(){//通过线程类得到输出流
        return pos;
    }

}
管道输入流:
package file;

import java.io.IOException;
import java.io.PipedInputStream;

public class Receive implements Runnable{//实现Runnable接口

    private PipedInputStream pis = null;

    public Receive() {
        this.pis = new PipedInputStream();//实例化输入流
    }

    @Override
    public void run() {
        byte[] b = new byte[1024];
        int len = 0;
        try {
            len = this.pis.read(b);//接收数据
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            pis.close();//关闭输入流
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("接收的内容为:"+new String(b, 0, len));
    }

    public PipedInputStream getPis(){
        return pis;
    }
}
测试代码:
public static void main(String[] args) {
        Send s = new Send();
        Receive r = new Receive();
        try {
            s.getPos().connect(r.getPis());//连接管道
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread(s).start();//启动线程
        new Thread(r).start();//启动线程
    }

以上程序定义了两个线程对象,在发送的线程类中定义了管道输出流,在接收的线程类中定义了管道的输入流,在操作时只需要使用PipedOutputStream类中提供的connect()方法就可以将两个线程管道连接在一起,线程启动后会自动进行管道的输入和输出操作。

7.打印流

(1)打印流的基本操作

在整个IO包中,打印流是输出信息最方便的类,主要包含字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,可以打印任何的数据类型,如小数,整数,字符串等。本节主要使用字节打印流(PrintStream)进行讲解。
PrintStream是OutputStream的子类,PrintStream类的常用方法如下:
1.public PrintStream(File file) throws FileNotFoundException        通过一个File对象实例化PrintStream类
2.public PrintStream(OutputStream out)      接收OutputStream对象,实例化PrintStream类
3.public PrintStream printf(Locale l, String format, Object... args)        根据指定的Locale进行格式化输出
4.public PrintStream printf(String format, Object... args)
        根据本地环境格式化输出
5.public void print(boolean b)      此方法被重载很多次,输出任意数据
6.public void println(boolean x)        此方法被重载很多次,输出任意数据后换行
通过public PrintStream(OutputStream out)  这个构造方法发现:把一个输出流的实例传递到打印流后,可以更加方便地输出内容。

实例一:使用PrintStream输出

@Test
    public void test01() throws Exception{
        PrintStream ps = null;
        //通过FileOutputStream实例化,意味着所有的输出是向文件中打印
        ps = new PrintStream(new FileOutputStream("d:"+File.separator+"test.txt"));
        ps.print("hello");
        ps.println("world");
        ps.print("1+1="+2);
        ps.close();
    }

(2)使用打印流进行格式化

在JDK1.5之后,java又对PrintStream类进行了扩充,增加了格式化的输出方式,直接使用printf()方法可以完成操作。但是在进行格式化输出时需要指定其输出的数据类型。
数据类型的格式化表如下所示:
%s          表示内容为字符串
%d          表示内容为整数
%s          表示内容为小数
%c          表示内容为字符

实例1:格式化输出

实例1@Test
    public void test02() throws Exception{
        PrintStream ps = null;
        //通过FileOutputStream实例化,意味着所有的输出是向文件中打印
        ps = new PrintStream(new FileOutputStream("d:"+File.separator+"test.txt"));
        String name = "李纪生";
        int age = 23;
        float score = 999.345f;
        char sex = 'M';
        ps.printf("姓名:%s;年龄:%d;成绩:%f;性别:%c", name,age,score,sex);
        ps.close();
    }

运行结果:姓名:李纪生;年龄:23;成绩:999.344971;性别:M

8.System类对IO的支持

System表示系统类,在Java中System类也对IO给与了一定的支持,在System类中定义了如下的3个常量。这3个常量在IO操作中有着非常大的作用。
1.public static final PrintStream err   错误信息输出
2.public static final InputStream in    对应着标准输入,一般是键盘
3.public static final PrintStream out   对应系统标准输出,一般是显示器
注意:这三个常量的命名也不符合命名规则,这也是历史遗留问题。

(1)System.out

    System.out是PrintStream的对象,在PrintStream中定义了一系列的print()和println()方法,所以前面使用的System.out.print()或System.out.println()语句调用的实际上就是PrintStream类的方法。
    既然此对象表示的是向显示器上输出,而PrintStream又是OutputStream的子类。所以可以直接利用此对象直接实例化OutputStream类的对象,并利用此对象向屏幕上输出信息

实例一:使用OutputStream向屏幕上输出

@Test
    public void test01(){
        OutputStream out = System.out;
        try {
            out.write("Hello World!".getBytes());//此时的输出流是向屏幕上输出
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            out.close();//关闭输出流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

以上信息是直接使用OutputStream向屏幕上进行输出的,也就是说,OutputStream的哪一个子类为其实例化,就具备了向哪里输出的能力。如果是使用了FileOutputStream则表示向文件输出,如果使用了System.out则表示向显示器输出,这里完全显示了Java的多态性的好处,即根据子类的不同完成的功能也不同。

(2)System.err

System.err表示的是错误信息的输出,如果程序出现来了错误,则可以直接使用System.err进行输出。

实例一:错误信息显示:

@Test
    public void test02(){
        String str = "Hello World!";
        try {
            System.out.println(Integer.parseInt(str));
        } catch (Exception e) {
            System.out.println(e);
        }
    }

以上程序要把字符串“hello”变为整型数据,这样肯定会引发NumberFormatException异常信息,所以当捕捉到异常后,直接在catch中使用System.err进行信息的输出。
提问:System.out和System.err的功能似乎是一样的。
回答:只能从概念上解释System.out和System.err
    System.out和System.err都是PrintStream的实例化对象。一般来讲,System.out是将信息显示给用户看,是正常的信息显示,而System.err是不希望用户看到的,会直接在后台答应,是专门显示错误的。
    开发工具Eclipse层面上的支持:System.err会显示红色。

(3)System.in

System.in实际上是一个键盘的输入流,其本身是InputStream类型的对象。那么此时就可以利用System.in完成从键盘读取数据的功能。

实例一:从键盘读取数据

实例1.
@Test
    public void test03() throws Exception{
        InputStream input = System.in;
        byte[] b = new byte[1024];
        System.out.println("请输入内容:");
        int len = input.read(b);
        System.out.println("输入的内容为:"+new String(b, 0, len));
    }

注意:从键盘读取的字符默认是GBK编码格式,需要将IDE的编码格式转换为GBK才不会出现乱码。
问题:
1.指定了输入数据的长度,如果现在输入的数据超过了其长度范围,则只能输入部分数据。
2.如果指定的byte数组长度是奇数,则可能出现中文乱码。
实例2.
@Test
    public void test03() throws Exception{
        InputStream input = System.in;
        byte[] b = new byte[5];
        System.out.println("请输入内容:");
        int len = input.read(b);
        System.out.println("输入的内容为:"+new String(b, 0, len));
    }

结果显示:
请输入内容:
今天天气非常好
输入的内容为:今天?

因为一个中文等于两个字节,最后一个字只能装半个,故显示?。

实例二:如果此时不指定byte数组长度,是否可以完成要求呢?

实例1.不指定大小:
@Test
    public void test04() throws Exception{
        InputStream input = System.in;
        StringBuffer buf = new StringBuffer();
        System.out.println("请输入内容:");
        int temp = 0;
        while((temp = input.read())!=-1){
            char c = (char) temp;//将数字变为字符
            if(c=='\n'){//退出循环,按Enter键表示输入完成
                break;
            }
            buf.append(c);//追加数据
        }
        System.out.println("输入的内容为:"+buf);
        input.close();
    }

    以上程序中如果输入的是英文字母,没有任何问题,但是如果输入的是中文,则同样会产生乱码,这是因为数据是以一个个字节的防水读取进来的,一个汉字是分两次读取的,所以造成了乱码。
    最好的输入方式是将全部输入的数据暂时存放到一块内存中,然后一次性从内存中读取出数据,这样所有的数据只读取了一次,不会造成乱码,而且也不会受长度的限制。需要使用BufferedReader类完成。

(4)输入/输出重定向

通过System类也可以改变System.in的输入流来源以及System.out和System.err两个输出流的输出位置,方法如下:
1.public static void setOut(PrintStream out)    重定向“标准”输出流
2.public static void setErr(PrintStream err)    重定向“标准”错误输出流
3.public static void setIn(InputStream in)      重定向“标准”输入流

实例一:位System.out输出重定向

@Test
    public void test05() throws Exception{
        System.setOut(new PrintStream(new FileOutputStream("d:"+File.separator+"test.txt")));
        System.out.println("www.baidu.com");//输出时不再向屏幕输出
        System.out.println("李纪生");
    }

结果显示:
www.baidu.com
李纪生

实例二:为用户保存错误信息

@Test
    public void test06(){
        String str = "hello";
        try {
            System.out.println(Integer.parseInt(str));
        } catch (Exception e) {
            try {
                System.setOut(new PrintStream(new FileOutputStream("d:"+File.separator+"test.txt")));
            } catch (FileNotFoundException e1) {
                e1.printStackTrace();
            }
            System.out.println(e);
        }
    }

实例三:为System.err输出重定向

@Test
    public void test07(){
        ByteArrayOutputStream bos = null;
        bos = new ByteArrayOutputStream();
        System.setErr(new PrintStream(bos));
        System.err.println("www.baidu.com");
        System.out.println("李纪生");
        System.out.println(bos);
    }

结果显示:
李纪生
www.baidu.com

    以上操作将System.err的输出定位到内存输出流中,所以此时使用System.err完成的各个输出操作的输出结果都保存在内存中。
    在使用以上两种操作时一定要注意一点:setOut()方法只负责System.out的输出重定向,而setErr()方法只负责System.err的输出重定向,两者不可混用。
提示:不要修改System.err()的输出重定向
虽然在System类中提供了setErr()这个错误输出的重定向方法,但是一般情况下读者不要使用这个方法修改System.err的重定向,因为从概念上讲System.err的错误信息是不希望用户看到的。

实例四:设置System.in的输入重定向

@Test
    public void test08() throws IOException{
        System.setIn(new FileInputStream("d:"+File.separator+"test.txt"));
        InputStream input = System.in;
        byte[] b = new byte[1024];
        int len = input.read(b);
        System.out.println("输入的内容为:"+new String(b, 0, len));
        input.close();
    }
提示:尽量不要修改输入的位置
对于System.in来讲主要针对键盘的输入,如果将其修改,则肯定要损失其功能,所以开发中不建议用户修改System.in的操作。

9.BufferedReader类

BufferedReader类用于从缓冲区中读取内容,所有的输入字节数据都将存放在缓存区中。常用的方法如下:
1.public BufferedReader(Reader in)  接受一个Reader类的实例
2.public String readLine() throws IOException   一次性从缓冲区中将内容全部读取进来
    BufferedReader中定义的构造犯法只能接收字符输入流的实例,所以必须使用字符输入流和字节输入流的转换流InputStreamReader将字节输入流System.in变为字符流。
    BufferedReader只能接收字符流的缓冲区,因为每一个中文要占2个字节,所以需要将System.in这个字节的输入流变为字符的输入流。

(1)键盘读入数据的标准格式

将System.in变为字符流放入到BufferReader后,可以通过readLine()方法等待用户输入信息。

实例一:从键盘输入数据

@Test
    public void test01() throws Exception{
        BufferedReader buf = null;
        buf = new BufferedReader(new InputStreamReader(System.in));
        String str = null;
        System.out.println("请输入内容");
        try {
            str = buf.readLine();//璇诲彇杈撳叆鍐呭
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("输入的内容为:"+str);
        buf.close();
    }
可以发现,程序非但没有来了长度的限制,也可以正确的接收中文了,所以以上代码就是键盘输入数据的标准格式。

(2)相关操作实例

实例操作一:加法操作

    现在要求从键盘输入两个数字,然后完成这两个整数的加法操作。因为从键盘接收过来的内容全部是采用字符串的形式存放的,所以直接将字符串通过包装类Integer将字符串变为基本数据类型。
实例1.完成最基本的功能:输入两个数字,并让两个数字相加
@Test
    public void test02() throws Exception{
        int i = 0;
        int j = 0;
        BufferedReader buf = null;
        buf = new BufferedReader(new InputStreamReader(System.in));
        String str = null;
        System.out.println("请输入第一个数字:");
        str = buf.readLine();
        i = Integer.parseInt(str);
        System.out.println("请输入第二个数字:");
        str = buf.readLine();
        j = Integer.parseInt(str);
        System.out.println(i+"+"+j+"="+(i+j));
    }

存在的问题:
1.如果输入的字符串不是数字,则肯定无法转换,会出现数字格式化异常,所以在转换时应该使用正则进行验证,如果验证成功来了,则表示可以进行转换;如果验证失败了,表示无法进行转换,需要等到用户重新输入数字才可以。
2.只能输入整数
3.代码重复,只要输入数据,则肯定使用BufferReader,重复出现readLine()调用。
对类进行合理的划分:
对于输入数据,最常见的可能是整数,小数,日期,字符串,所以此时最好将其设计一个专门的输入数据类,完成输入数据的功能。

实例2.完成一个专门处理输入数据的类,只能得到整数和字符串。
package file;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputData {

    private BufferedReader buf = null;

    public InputData() {
        this.buf = new BufferedReader(new InputStreamReader(System.in));
    }

    public String getString(String info){//从此方法中得到字符串的信息
        String temp = null;
        System.out.println(info);
        try {
            temp = this.buf.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return temp;
    }

    public int getInt(String info,String err){
        int temp = 0;
        String str = null;
        boolean flag = true;
        while(flag){
            str = this.getString(info);
            if(str.matches("^\\d+$")){
                temp = Integer.parseInt(str);
                flag = false;//更改标志位之后,将退出循环。
            }else {
                System.err.println(err);
            }
        }
        return temp;
    }

}
@Test
    public void test03(){
        int i = 0;
        int j = 0;
        InputData input = new InputData();
        i = input.getInt("请输入第一个数字,", "输入的数据必须是数字,请重新输入");
        j = input.getInt("请输入第二个数字,", "输入的数据必须是数字,请重新输入");
        System.out.println(i+"+"+j+"="+(i+j));
    }
实例3:对输入数据类进一步扩充
public float getFloat(String info,String err){
        float temp = 0;
        String str = null;
        boolean flag = true;
        while(flag){
            str = this.getString(info);
            if(str.matches("^\\d+.?\\d+$")){//判断是否是小数
                temp = Float.parseFloat(str);
                flag = false;
            }else {
                System.err.println(err);
            }
        }
        return temp;
    }

    public Date getDate(String info,String err){
        Date d = null;
        String str = null;
        boolean flag = true;
        while(flag){
            str = this.getString(info);
            if(str.matches("^\\d{4}-\\d{2}-\\d{2}$")){
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                try {
                    d = sdf.parse(str);//将字符串变为Date型数据
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                flag = false;//更改标志位之后将退出循环
            }else {
                System.err.println(err);
            }
        }
        return d;
    }

在得到日期类型时使用了SimpleDateFormat类,并指定了日期额转换模板,将一个字符串变为一个Date类型的数据。

实例操作二:菜单显示

如果用户输入的编号不正确,则要给出相应的提示,并等待用户重新选择。
实例1:操作类
package file;

public class Operate {

    public void add(){
        System.out.println("选择的是新增的操作!");
    }

    public void delete(){
        System.out.println("选择的是删除的操作!");
    }

    public void update(){
        System.out.println("选择的是修改的操作!");
    }

    public void find(){
        System.out.println("选择的是查找的操作!");
    }

}
实例2:菜单类
package file;

public class Menu {
    Operate op = new Operate();

    public Menu() {
        while(true){
            this.show();
        }
    }

    private void show() {
        System.out.println("=====XXX系统XXX=====");
        System.out.println("【1】,增加数据");
        System.out.println("【2】,删除数据");
        System.out.println("【3】,修改数据");
        System.out.println("【4】,查询数据");
        System.out.println("【0】,退出系统");
        InputData input = new InputData();
        int i = input.getInt("请选择:", "请输入正确的选项");
        switch (i) {
        case 1:
            op.add();       
            break;
        case 2:
            op.delete();        
            break;
        case 3:
            op.update();        
            break;
        case 4:
            op.find();      
            break;
        case 0:
            System.exit(1);//系统退出
            break;

        default:
            System.out.println("请选择正确的操作!");
        }
    }

}

以上的菜单因为菜单的内容要不断地显示,所以使用循环打印的方式,每一次操作完成后都会重新显示出所有的菜单内容以供用户选择。
实例3:验证以上的菜单
@Test
    public void test05(){
        new Menu();
    }

10.Scanner类

(1)Scanner类简介

在JDK1.5之后,Java提供了专门的输入数据类,此类不只可以完成输入数据操作,也可以方便地对输入数据进行验证。此类存放在java.util包中,常用方法如下:

1.public Scanner(File source) throws FileNotFoundException  从文件中接收内容
2.public Scanner(InputStream source)    从指定的字节输入流中接收内容
3.public boolean hasNext(Pattern pattern)   判断输入的数据是否符合指定的正则标准
4.public boolean hasNextInt()   判断输入的数是否是整数
5.public boolean hasNextFloat() 判断输入的数是否是小数
6.public String next()  接收内容
7.public String next(Pattern pattern)   接收内容,进行正则校验
8.public int nextInt()  接收数字
9.public float nextFloat()  接收小数
10.public Scanner useDelimiter(String pattern)  设置读取的分隔符
提示:Scanner类可以接收任意的输入流
在Scanner类中提供了一个可以接收InputStream类型的构造方法,这就表示只要是字节数入流的子类都可以为Scanner类实例化,以进行方便的读取。

(2)使用Scanner类输入数据

实例操作一:实现基本的数据输入

最简单的数据输入直接使用Scanner类的next()方法即可
实例1:输入数据
@Test
    public void test01(){
        Scanner scan = new Scanner(System.in);//从键盘接收数据
        System.out.println("请输入数据:");
        String str = scan.next();
        System.out.println("输入的数据为:"+str);
    }

问题:如果在以上程序中输入了带有空格的内容,则只能取出空格之前的数据。
     造成这样的结果是因为:Scanner将空格当做了一个分隔符,所以为了保证程序的正确,可以将分隔符修改为"\n"(回车)。
实例2.修改输入数据的分隔符
@Test
    public void test02(){
        Scanner scan = new Scanner(System.in);//从键盘接收数据
        scan.useDelimiter("\n");//修改输入数据的分隔符
        System.out.println("请输入数据:");
        String str = scan.next();
        System.out.println("输入的数据为:"+str);
    }
如果想要输入int,float类型的数据,在Scanner类中也有支持,但是在输入之前最好先使用hasNextXxx()方法进行验证,
实例3.输入int ,float
@Test
    public void test03(){
        Scanner scan = new Scanner(System.in);//从键盘接收数据
        int i = 0;
        float f = 0.0f;
        System.out.println("请输入整数:");
        if(scan.hasNextInt()){
            i = scan.nextInt();
            System.out.println("整数数据:"+i);
        }else {
            System.out.println("输入的不是整数!");
        }
        System.out.println("请输入小数数据:");
        if(scan.hasNextFloat()){
            f = scan.nextFloat();
            System.out.println("小数数据:"+f);
        }else {
            System.out.println("输入的不是小数!");
        }
    }

实例操作二:实现日期格式的数据输入

在Scanner类中没有专门的日期格式输入操作,所以,如果想要得到一个日期类型的数据,则必须自己编写正则验证,并手工转换。
实例1:得到日期
@Test
    public void test04(){
        Scanner scan = new Scanner(System.in);
        System.out.println("输入日期:(yyyy-MM-dd)");
        String str = null;
        Date date = null;
        if(scan.hasNext("^\\d{4}-\\d{2}-\\d{2}$")){
            str = scan.next("^\\d{4}-\\d{2}-\\d{2}$");
            try {
                date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println("输入的日期格式错误");
        }
        System.out.println(date);
    }

以上程序使用hasNext()对输入的数据进行正则验证,如果合法,则转换为Date类型。

实例操作三:从文件中得到数据

如果要从文件中取得数据,则直接将File类的实例传入到Scanner的构造方法中即可。
实例1:读取test.txt文件
@Test
    public void test05(){
        File file = new File("d:"+File.separator+"test.txt");
        Scanner scan = null;
        try {
            scan = new Scanner(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        StringBuffer str = new StringBuffer();
        while(scan.hasNext()){
            str.append(scan.next()).append("\n");
        }
        System.out.println(str);
    }

注意:
1.如果是文本中有汉字,则运行程序时需要确保是以GBK编码格式运行,否则会打印出空白。
2.从Scanner类的操作中可以发现,Scanner类有默认的分隔符,这样如果在文件中存在换行,则表示一次输入结束,所以程序采用循环的方式读取,并在每次读完一行之后加入换行符。因为读取时内容需要反复修改,所以使用StringBuffer类以提升操作性能。

11.数据操作流(待完善)

    在IO包中,提供了两个与平台无关的数据操作流,分别为数据输出流(DataOutputStream)和数据输入流(DataInputStream),通常数据输出流会按照一定的格式将数据输出,再通过数据输入流按照一定的格式将数据读入,这样就可以方便地对数据进行处理。

例如:有如下订单:
商品名称                商品价格                商品数量
衬衣                  98.3                3   
手套                  30.3                2
围巾                  50.5                1

    如果要将以上数据保存到文件中,就可以使用数据输出流将内容保存到文件,然后再使用数据输入流从文件中读取进来。

(1)DataOutputStream类

DataOutputStream是OutputStream的子类,此类的定义如下:
public class DataOutputStream extends FilterOutputStream implements DataOutput
提示:DataOutput接口的作用:
DataOutput是数据的输出接口,其中定义了各种数据的输出操作方法,例如:在DataOutputStream类中的各种writeXxx()方法就是此接口定义的,但是在数据输出的时候一般都会直接使用DataOutputStream,只有在对象序列化时才有可能直接操作此接口。
常用方法如下:
1.public DataOutputStream(OutputStream out) 实例化对象
2.public final void writeInt(int v) throws IOException  将一个int值以4-byte值形式写入基础输出流中
3.public final void writeDouble(double v) throws IOException    写入一个double类型,该值以8-byte值形式写入基础输出流中
4.public final void writeChars(String s) throws IOException     将一个字符串写入到输出流中
5.public final void writeChar(int v) throws IOException 将一个字符写入到输出流中

实例操作一:将订单数据写入到文件test.txt中

@Test
    public void test01() throws Exception{
        DataOutputStream dos = null;
        File file = new File("d:"+File.separator+"test.txt");
        dos = new DataOutputStream(new FileOutputStream(file));
        String[] names = {"衬衣","手套","围巾"};
        float[] prices = {98.3f,30.3f,50.5f};
        int[] nums = {3,2,1};
        for (int i = 0; i < names.length; i++) {
            dos.writeChars(names[i]);
            dos.writeChar('\t');
            dos.writeFloat(prices[i]);
            dos.writeChar('\t');
            dos.writeInt(nums[i]);
            dos.writeChar('\n');
        }
        dos.close();
    }

结果:乱码
坙坈  B臋?         
bKYW    A騠f         
V鬩?     BJ          

(2)DataInputStream类

DataInputStream是InputStream的子类,专门负责读取使用DataOutputStream输出的数据,定义如下:
public class DataInputStream extends FilterInputStream implements DataInput
提示:DataInput接口是读取数据的操作接口,与DataOutput接口提供的各种writeXxx()方法对应,在此接口中定义了一系列的readXxx()方法,这些方法在DataInputStream类中都有实现。一般在操作时不会直接使用到此接口,而主要使用DataInputStream类完成读取功能,只有在对象序列化时才有可能直接利用此接口读取数据。
DataInputStream类的常用方法如下:
1.public DataInputStream(InputStream in)    实例化对象
2.public final int readInt() throws IOException     从输入流中读取整数
3.public final float readFloat() throws IOException     从输入流中读取小数
4.public final char readChar() throws IOException       从输入流中读取一个字符

实例操作二:从test.txt中读取数据

@Test
    public void test02() throws Exception{
        DataInputStream dis = null;
        File file = new File("d:"+File.separator+"test.txt");
        dis = new DataInputStream(new FileInputStream(file));
        String name = null;
        float price = 0.0f;
        int num = 0;
        char[] temp = null;//接收字符串数据
        char c = 0;//声明字符变量
        int len = 0;//接收读取数据
        try {
            while(true){
                temp = new char[200];
                len = 0;
                while((c=dis.readChar())!='\t'){
                    temp[len] = c;
                    len++;
                }
                name = new String(temp, 0, len);
                price = dis.readFloat();
                dis.readChar();//读出\t
                num = dis.readInt();
                dis.readChar();//读出\n
                System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",name,price,num);
            }
        } catch (Exception e) {//如果读到底,则会出现异常
            e.printStackTrace();
        }
        dis.close();
    }

结果:
名称:衬衣;价格:98.30;数量:3
名称:手套;价格:30.30;数量:2
名称:围巾;价格:50.50;数量:1
报错java.io.EOFException:因为不知道流的末尾,当到达末尾的时候,自然抛出了此异常

12.合并流

合并流的主要功能是将两个文件的内容合并成一个文件。
如果要使用合并流,则必须使用SequenceInputStream类,此类的常用方法如下:
1.public SequenceInputStream(InputStream s1,InputStream s2) 使用两个输入流对象实例化本类对象
2.public int available() throws IOException 返回文件的大小

实例操作一:

实例1.合并两个文件
@Test
    public void test01() throws Exception{
        InputStream input1 = null;
        InputStream input2 = null;
        OutputStream out = null;
        SequenceInputStream sis = null;
        input1 = new FileInputStream("d:"+File.separator+"test.txt");
        input2 = new FileInputStream("d:"+File.separator+"test2.txt");
        out = new FileOutputStream("d:"+File.separator+"testab.txt");
        sis = new SequenceInputStream(input1,input2);
        int temp = 0;
        while((temp=sis.read())!=-1){
            out.write(temp);
        }
        sis.close();
        input1.close();
        input2.close();
        out.close();
    }

13.压缩流

在日常的使用中经常会使用到WinRAR或WinZIP等压缩文件,通过这些软件可以把一个很大的文件进行压缩以方便传输。在JAVA中为了减少传输时的数据量也提供了专门的压缩流,可以将文件或文件夹压缩成ZIP,JAR,GZIP等文件形式。

(1)ZIP压缩输入/输出流简介

ZIP是一种较为常见的压缩格式,在Java中要实现ZIP的压缩需要导入java.util.zip包,可以使用此包中的ZipFile,ZipOutputStream,ZipInputStream和ZipEntry几个类完成操作。

提示:
1:JAR压缩常用类:
JAR压缩输出流:JarOutputStream;JAR压缩输入流:JarInputStream
JAR文件:JARFile;JAR实体:JAREntry
2:GZIP是用于UNIX系统中的文件压缩,在Linux中经常会使用到*.gz的文件,就是GZIP格式。常用类如下:
GZIP压缩输出流:GZIPOutputStream;GZIP压缩输入流:GZIPInputStream
在每一个压缩文件中都会存在多个子文件,那么每一个子文件在JAVA中就使用ZipEntry表示。常用方法如下:
1.public ZipEntry(String name)  创建对象并指定要创建的ZipEntry名称
2.public boolean isDirectory()  判断此ZipEntry是否是目录

注意:压缩的输入/输出类文件定义在java.util.zip包中。
压缩的输入/输出流也属于InputStream或OutputStream的子类,但是却没有定义在java.io包中,而是以一种弄工具类的形式提供的,在操作时还需要使用java.io包的支持。

(2)ZipOutputStream类

如果要完成一个文件或文件夹的压缩,则要使用ZipOutputStream类,ZipOutputStream是OutputStream类的子类,常用方法如下:

1.public ZipOutputStream(OutputStream out)  创建新的ZIP输出流
2.public void putNextEntry(ZipEntry e) throws IOException   设置每一个ZipEntry对象
3.public void setComment(String comment)    设置ZIP文件的注释
实例1:压缩一个文件
@Test
    public void test01() throws Exception{
        File file = new File("d:"+File.separator+"test.txt");
        File zipFile = new File("d:"+File.separator+"test.zip");
        InputStream input = new FileInputStream(file);//定义输入文件流
        ZipOutputStream zipOut = null;//定义压缩输出流
        //实例化压缩输出流对象
        zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
        //每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
        zipOut.putNextEntry(new ZipEntry(file.getName()));
        zipOut.setComment("www.lijisheng.com");//设置注释
        int temp = 0;
        while((temp=input.read())!=-1){
            zipOut.write(temp);
        }
        input.close();
        zipOut.close();
    }

以上程序将test.txt作为源文件,然后使用ZipOutputStream将所有的压缩数据输出到test.zip文件中,在压缩时同样采用了边读边写的方式完成。
实例2:压缩一个文件夹:
在实现时就应该列出文件夹中的全部内容,并把每一个内存设置成ZipEntry对象,保存到压缩文件中。
@Test
    public void test02() throws Exception{
        File file = new File("d:"+File.separator+"tomcat7");//要压缩的文件夹
        File zipFile = new File("d:"+File.separator+"tomcat7.zip");//压缩文件的名称
        InputStream input = null;
        ZipOutputStream zipOut = null;
        zipOut = new ZipOutputStream(new FileOutputStream(zipFile));//实例化压缩输出流
        zipOut.setComment("www.lijisheng.com");
        if(file.isDirectory()){//判断是否是目录
            File[] listFiles = file.listFiles();
            for (int i = 0; i < listFiles.length; i++) {
                input = new FileInputStream(listFiles[i]);//设置文件输入流
                //每一个被压缩的文件都用ZipEntry表示,需要为每一个压缩后的文件设置名称
                zipOut.putNextEntry(new ZipEntry(file.getName()+File.separator+listFiles[i].getName()));
                int temp = 0;
                while((temp = input.read())!=-1){
                    zipOut.write(temp);
                }
                input.close();
            }
        }
        zipOut.close();
    }

注意:待压缩文件夹中不可以有子文件夹,否则会拒绝访问,即使有结果也无法正确打开。

(3)ZipFile类

在java中,每一个压缩文件都可以使用ZipFile表示,还可以使用ZipFile根据压缩后的文件名称找到每一个压缩文件的ZipEntry并将其进行解压缩操作,ZipFile类的常用方法如下:

1.public ZipFile(File file) throws ZipException, IOException        根据File类实例化ZipFile对象
2.public ZipEntry getEntry(String name)     根据名称找到其对应的ZipEntry
3.public InputStream getInputStream(ZipEntry entry) throws IOException      根据ZipEntry取得InputStream实例
4.public String getName()   得到压缩文件的路径名称
ZipFile类实例化时需要File指定的路径。
实例1:实例化ZipFile类对象
@Test
    public void test03() throws Exception, IOException{
        File file = new File("d:"+File.separator+"tomcat7.zip");
        ZipFile zipFile = new ZipFile(file);
        System.out.println("压缩文件的名称为:"+zipFile.getName());
    }
实例2:解压缩文件:
报错:空指针异常

(4)ZipInputStream类

14.回退流

15.字符编码

16.对象序列化

17.实例操作–单人信息管理程序

猜你喜欢

转载自blog.csdn.net/m0_37301141/article/details/77639377
今日推荐