Java SE 14th IO (1)

来源:互联网 发布:建立寝室学生信息表sql 编辑:程序博客网 时间:2024/06/07 03:30

Java SE 14th day:Java IO(上)

1、知识点

Java基础部分有四个部分是必须掌握的:

1、面向对象,包括各个概念及使用

2、Java类集

3、Java IO,如果是自学的人员,面对IO庞大的开发包基本上都会比较郁闷

4、JDBC

2、本次课程知识点

1、Java IO的主要分类

2、File类

3、RandomAccessFile

4、字节流和字符流

3、具体内容

3.1 Java IO

在Java IO实际上很好的体现了Java的面向对象的设计思想。

一个接口或抽象类的具体行为由子类决定,那么根据实例化子类的不同完成的功能也不同。

Java IO 中的所有操作类都放在java.io包中。

主要的5个类和1个接口是:File、InputStream、OutputStream、Reader、Writer、Serialzable接口。

3.2 File(重点)

File类在整个java.io包中是一个独立的类,此类的主要功能是完成与平台无关的文件操作,例如:创建文件、删除文件等等。

在File类中提供了以下的构造方法:public File(String pathname)

● 在使用的时候需要依靠其指定一个文件的具体路径。

3.2.1 创建文件

范例:在D盘创建文件“demo.txt”。

创建文件的方法:public boolean createNewFile() throws IOException

package org.lxh.filedemo;

import java.io.File;

import java.io.IOException;

public class CreateFileDemo01 {

    public static void main(String[] args) {

       File file = new File("d:\\demo.txt");//找到File类的实例

       try {

           file.createNewFile(); // 创建文件

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

程序本身只是作为一个文件的创建出现,而如果要使用具体的内容的输出,则必须依靠IO操作流。

3.2.2 删除文件

如果要删除文件则使用:public boolean delete()

package org.lxh.filedemo;

import java.io.File;

 

public class DeleteFileDemo {

    public static void main(String[] args) {

       File file = new File("d:\\demo.txt");//找到File类的实例

       file.delete() ;// 删除文件

    }

}

以上的两个操作确实完成了文件的创建及删除,可是代码却同样存在问题。

在各个操作系统中,文件的分隔符是不一样的:

● windows:\

● linux:/

Java本身属于跨平台的语言,那么应该适应各个不同的平台要求,那么为了解决可以自动适应不同系统的分隔符要求,所以在File类中提供了以下几个常量:

● 路径分隔符:public static final String pathSeparator

● 分隔符:public static final String separator

注意:此处的常量之所以没有大写,是因为java历史发展原因。

所以,对于实际的开发来讲,必须使用这样的常量进行声明。因此,以后的程序都将采用File.separator进行分隔。

 

3.2.3 判断文件是否存在

提供了以下的方法:public boolean exists()

package org.lxh.filedemo;

import java.io.File;

 

public class ExistsFileDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator +"demo.txt");//找到File类的实例

       if (file.exists()) {//判断文件是否存在

           System.out.println("文件存在。");

       } else {

           System.out.println("文件不存在。");

       }

    }

}

那么,此时就可以利用此种特点完成以下要求:

编写一个程序:如果文件存在,则删除;如果文件不存在则创建新文件。

package org.lxh.filedemo;

import java.io.File;

import java.io.IOException;

 

public class CreateDeleteFileDemo {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator +"demo.txt");//找到File类的实例

        if (file.exists()) {//如果文件存在

            file.delete();

        } else { // 文件不存在创建

            try {

                file.createNewFile(); // 创建文件

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

}

程序此时确实完成功能,但是在操作的时候会存在延迟。

3.2.4 判断路径是文件还是文件夹

在程序中可以使用如下的方法进行判断

● 判断是否是文件:publicboolean isFile()

● 判断是否是文件夹:publicboolean isDirectory()

package org.lxh.filedemo;

import java.io.File;

public class IsDemo {

    public static void main(String[] args) {

       File file1 = new File("d:" + File.separator +"demo.txt");

       File file2 = new File("d:" + File.separator +"javatest");

       System.out.println(file1.isFile());//判断是否是文件

       System.out.println(file2.isDirectory());//判断是否是目录

    }

}

true

true

3.2.5 列出目录中的内容

在File类中提供了以下的两个方法进行目录的列表操作:

● public String[] list()

● public File[] listFiles()

package org.lxh.filedemo;

import java.io.File;

public class ListDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator +"Java SE--By Wolex");//找到File类的实例

       String path[] = file.list(); // 列出全部的内容

       for (int i = 0; i < path.length; i++) {

           System.out.println(path[i]);

       }

    }

}

Java SE 01_The Basics.doc

Java SE 02_Object-oriented01.doc

Java SE 03_Object-oriented02.doc

javatest

但是,此处列出来的只是目录下的文件或者文件夹的名称而已。

范例:通过使用listFiles()方法列出一个目录下的完整路径

package org.lxh.filedemo;

import java.io.File;

 

public class ListFilesDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator +"Java SE--By Wolex");//找到File类的实例

       File path[] = file.listFiles(); // 列出全部的子文件或文件夹

       for (int i = 0; i < path.length; i++) {

           // System.out.print(path[i].getParent() + " --> ");

           System.out.println(path[i]);

       }

    }

}

d:\Java SE--By Wolex\Java SE 01_The Basics.doc

d:\Java SE--By Wolex\Java SE 02_Object-oriented01.doc

d:\Java SE--By Wolex\Java SE 03_Object-oriented02.doc

d:\Java SE--By Wolex\javatest

这两个操作同样属于列表的操作,但是后者却可以列出完整的路径。实际上此时,如果为了更加清楚的表示出列出的是路径,可以通过以下代码实现:

package org.lxh.filedemo;

import java.io.File;

public class ListFilesDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator +"Java SE--By Wolex");//找到File类的实例

       File path[] = file.listFiles(); // 列出全部的子文件或文件夹

       for (int i = 0; i < path.length; i++) {

           System.out.print(path[i].getParent() +" --> ");

           System.out.println(path[i].getPath());?有什么区别

       }

    }

}

d:\Java SE--By Wolex --> d:\Java SE--By Wolex\Java SE 01_The Basics.doc

d:\Java SE--By Wolex --> d:\Java SE--By Wolex\Java SE 02_Object-oriented01.doc

d:\Java SE--By Wolex --> d:\Java SE--By Wolex\Java SE 03_Object-oriented02.doc

d:\Java SE--By Wolex --> d:\Java SE--By Wolex\javatest

如果想要操作文件,则肯定使用后者最为方便,因为如果使用listFiles()方法,则通过找到File类的对象,实际上就找到了完整的路径。

答:没什么区别。其实path[i]、path[i].toString和path[i].getPath()输出的内容都是一样的。因为File.toString调用的就是File.getPath()方法,而path[i]调用的也就是toString()方法。

3.2.6 创建目录

       先对比两种取得当前给定路径的父路径方法:

n  public StringgetParent()   → 返回String类型

n  public FilegetParentFile()  → 返回File类型

对于创建目录,也有两种方法:

n  public boolean mkdir() :只创建当前给定路径的文件夹,如果给定路径的父路径不存在着会出错;

n  public boolean mkdirs() :级联创建文件夹,推荐!

范例:使用mkdir()方法创建文件夹

package org.lxh.filedemo;

import java.io.File;

import java.io.IOException;

 

public class MkDirDemo {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator +"test.txt");//找到File类的实例

        file.mkdir();//创建test.txt文件夹

    }

}

在windows中有很多文件的后缀,例如:txt、doc、jpg,实际上这些文件的后缀与文件本身的内容没有任何的联系,加入后缀只是为了方便程序的管理而已。

当然,在创建的时候,也可以在一个文件夹下创建文件。

范例:在D盘下先创建“demo”文件夹,然后在“demo”下创建文件“test.txt”文件。

package org.lxh.filedemo;

import java.io.File;

import java.io.IOException;

 

public class CopyOfMkDirDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator +"demo" + File.separator

              + "test.txt"); // 找到File类的实例

       file.getParentFile().mkdir();// 创建d:\demo文件夹

       try {

           file.createNewFile(); // 创建文件

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

       上面程序存在明显的缺陷,如果当前给定文件的父父级文件夹路径不存在,则mkdir()方法则会失效,什么也不干,所以在开发中应该使用mkdirs()。

范例:使用mkdirs()级联创建文件夹

package myio;

import java.io.File;

 

public class MkdirsDemo {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"IOTest" + File.separator

                + "myIO" + File.separator +"myIO.txt");

        if (!file.getParentFile().exists()) {

            file.getParentFile().mkdirs();

        }

        file.createNewFile();

    }

}

 

       除了以上的文件操作信息之外,也可以取得一些文件的属性信息:

n  文件长度:public long length()

n  是否可读:public booleancanRead()

n  是否可写:public booleancanWrite()

n  是否可执行:public booleancanExecute()

n  判断路径是否是文件夹:publicbooleanisDirectory(

n  判断路径是否是文件:publicbooleanisFile()

n  最后一次修改日期:public longlastModified()

范例:获取文件属性信息。

package myio;

import java.io.File;

import java.util.Date;

 

public class FileAttribute {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator +"download"

                + File.separator +"lockdir.exe");

        System.out.println("文件长度:" + file.length() / 1024 +"KB");

        System.out.println("是否可读:" + file.canRead());

        System.out.println("是否可写:" + file.canWrite());

        System.out.println("是否可执行:" + file.canExecute());

        System.out.println("是否是文件夹:" + file.isDirectory());

        System.out.println("是否是文件:" + file.isFile());

        System.out.println("最后一次修改日期:" +new Date(file.lastModified()));//long类型转java.util.Date类型

    }

}

文件长度:688KB

是否可读:true

是否可写:true

是否可执行:true

是否是文件夹:false

是否是文件:true

最后一次修改日期:Thu Jan 01 11:12:32 CST 2009

3.2.7 思考题 —— 列出指定目录的全部内容

现在给定一个目录,要求可以列出此目录中的所有文件的路径,包括各个子文件夹也都要列出。

package org.lxh.filedemo;

import java.io.File;

public class ListDirectoryDemo {

    public static void main(String[] args) {

       File file = new File("d:" + File.separator);

       list(file);

    }

 

    public static void list(File file) {

       if (file.isDirectory()) {

           File lists[] = file.listFiles();

           for (int i = 0; i < lists.length; i++) {

              list(lists[i]);// 列出内容

           }

       }

       System.out.println(file);

    }

}

       以上程序并不完善,因为在windows中存在这样一些文件夹:

       如果此时程序尝试列出此文件夹下的内容,则肯定出错,如下演示了这个错误:

package myio;

 

import java.io.File;

 

public class ListDirectoryDemo {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator

                + "System Volume Information");

        list(file);

    }

 

    public static void list(File file) {

        if (file.isDirectory()) {

            File lists[] = file.listFiles();

            //不要尝试打印其长度,这里也打印不了它的信息!会出错!

            // System.out.println(lists.length);

            for (int i = 0; i <lists.length; i++) {

                list(lists[i]);//列出内容

            }

        }

        System.out.println(file);

    }

}

Thread [main] (Suspended (exceptionNullPointerException)) 

    ListDirectoryDemo.list(File) line: 17  

    ListDirectoryDemo.main(String[]) line: 9   

       修改后的程序:

package myio;

import java.io.File;

 

public class ListDirectoryDemo {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator

                + "System Volume Information");

        list(file);

    }

 

    public static void list(File file) {

        if (file.isDirectory()) {

            File lists[] = file.listFiles();

            // System.out.println(lists.length); //这里也打印不了它的信息!

            if (lists !=null) {//有可能无法列出目录中的文件

                for (int i = 0; i < lists.length; i++) {

                    list(lists[i]);//列出内容

                }

            }

        }

        System.out.println(file);

    }

}

d:\System Volume Information

       递归会存在内存的溢出操作,知道就行了,能不用就不用。

范例:把代码再简单修改一下,可以成为一个恶性程序,如果此时把输出换成了删除呢?

package myio;

 

import java.io.File;

 

public class ListDirectoryDemo {

    public static void main(String[] args) {

        File file = new File("d:" + File.separator

                + "Downloads");

        list(file);

    }

 

    public static void list(File file) {

        if (file.isDirectory()) {

            File lists[] = file.listFiles();

            // System.out.println(lists.length); //这里也打印不了它的信息!

            if (lists !=null) {//有可能无法列出目录中的文件

                for (int i = 0; i < lists.length; i++) {

                    list(lists[i]);//列出内容

                }

            }

        }

        file.delete(); //不管是文件还是文件夹都删!

    }

}

       对于此种操作一定要小心,因为File.delete()方法是直接删除而不是放到回收站!

此操作可以作为一个备用方法。

3.3 RandomAccessFile

RandomAccessFile类的主要功能是完成随机的读取操作,本身也可以直接向文件中保存内容。

如果要想实现随机读取,则在存储数据的时候要保证长度的一致性,否则是无法实现功能的。

RandomAccessFile的构造方法

public RandomAccessFile(File file, String mode) throws FileNotFoundException

需要接收一个File类的实例,并设置一个操作的模式:

● 读模式:r

● 写模式:w

● 读写模式:rw

-| 其中最重要的是读写模式,如果操作的文件不存在,则会帮用户自动创建。

3.3.1 使用RandomAccessFile进行写入的操作

使用从DataOutput接口中实现的一系列的writeXxx()方法写入数据。

package org.lxh.randomaccessdemo;

import java.io.File;

import java.io.RandomAccessFile;

 

public class RandomAccessFileDemo01 {

    public static void main(String[] args)throws Exception {//所有异常抛出

        File file = new File("d:" + File.separator +"demo.txt");//指定要操作的文件

        RandomAccessFile raf = new RandomAccessFile(file,"rw");//以读写的形式进行操作

        // 写入第一条数据

        String name = "zhangsan";//表示姓名

        int age = 30; // 表示年龄

        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();// 文件操作的最后一定要关闭

    }

}

3.3.2 使用RandomAccessFile进行读取的操作

在RandomAccessFile操作的时候读取的方法是从DataInput接口实现而来,有一系列的readXxx()方法,可以读取各种类型的数据。

但是在RandomAccessFile中因为可以实现随机得读取,所以有一系列的控制方法:

● 回到读取点:public void seek(long pos)throws IOException

● 跳过多少个字节:public int skipBytes(int n)throws IOException

下面就进行读取的操作:

package org.lxh.randomaccessdemo;

 

import java.io.File;

import java.io.RandomAccessFile;

 

public class RandomAccessFileDemo02 {

    public static void main(String[] args)throws Exception {//所有异常抛出

        File file = new File("d:" + File.separator +"demo.txt");//指定要操作的文件

        RandomAccessFile raf = new RandomAccessFile(file,"r");//以读的形式进行操作

        byte b[] = null;// 定义字节数组

        String name = null;

        int age = 0;

        b = new byte[8];

        raf.skipBytes(12); // 跨过第一个人的信息

        System.out.println("第二个人的信息:");

        for (int i = 0; i < 8; i++) {

            b[i] = raf.readByte(); // 读取字节

        }

        age = raf.readInt();// 读取数字

        System.out.println("\t|-姓名:" +new String(b));

        System.out.println("\t|-年龄:" + age);

        raf.seek(0);// 回到开始位置

        System.out.println("第一个人的信息:");

        for (int i = 0; i < 8; i++) {

            b[i] = raf.readByte(); // 读取字节

        }

        age = raf.readInt();// 读取数字

        System.out.println("\t|-姓名:" +new String(b));

        System.out.println("\t|-年龄:" + age);

        raf.skipBytes(12); // 跨过第二个人的信息

        System.out.println("第三个人的信息:");

        for (int i = 0; i < 8; i++) {

            b[i] = raf.readByte(); // 读取字节

        }

        age = raf.readInt();// 读取数字

        System.out.println("\t|-姓名:" +new String(b));

        System.out.println("\t|-年龄:" + age);

        raf.close();// 文件操作的最后一定要关闭

    }

}

第二个人的信息:

    |- 姓名:lisi   

    |- 年龄:31

第一个人的信息:

    |- 姓名:zhangsan

    |- 年龄:30

第三个人的信息:

    |- 姓名:wangwu 

    |- 年龄:32

在文件中提供了一个指针,完成具体得操作功能。

RandomAccessFile可以方便的进行写操作,但是其操作起来毕竟很麻烦,所以在java中要想进行io的操作一般都实用字节流或字符流完成。

3.4 字节流和字符流(重点

在整个IO包中,流的操作分为两种:

● 字节流:

-| 字节输出流OutputStream、字节输入流InputStream

● 字符流:

-| 字符输出流Writer、字符输入流Reader

3.4.1、IO操作的基本步骤

在java中使用IO操作必须按照以下的步骤完成:

1、使用File找到一个文件

2、使用字节流或字符流的子类为OutputStream、InputStream、Writer、Reader进行实例化操作

3、进行读写操作

4、关闭:close(),在流的操作中最终必须进行关闭。

3.4.2、字节输出流:OutputStream

在java.io包中,OutputStream是字节输出流的最大父类。

public abstract class OutputStream

extends Object

implements Closeable, Flushable

此类是一个抽象类,所以使用时需要依靠子类进行实例化操作。

如果此时要完成文件的输出操作,则使用FileOutputStream为OutputStream进行实例化操作。

OutputStream提供了以下的写入数据方法:

● 写入全部字节数组:public voidwrite(byte[] b) throws IOException

● 写入部分字节数组:public void write(byte[] b, int off, int len) throws IOException

● 写入一个数据:public abstractvoid write(int b) throws IOException

而对于子类FileOutputStream,我们只关心它的构造方法:

构造:public FileOutputStream(File file) throwsFileNotFoundException

package org.lxh.outputstreamdemo;

 

import java.io.File;

import java.io.FileOutputStream;

import java.io.OutputStream;          

 

public class OutputStreamDemo01 {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo" + File.separator

              + "demo.txt"); // 要操作的文件

       OutputStream out = null;//声明字节输出流

       out = new FileOutputStream(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       byte b[] = str.getBytes();//将String变为byte数组

       out.write(b); // 写入数据

       out.close(); // 关闭

    }

}

                      

以上的操作是将全部的字节数组的内容输出,当然,也可以通过循环一个个的输出:

package org.lxh.outputstreamdemo;

 

import java.io.File;

import java.io.FileOutputStream;

import java.io.OutputStream;

 

public class OutputStreamDemo02 {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo" + File.separator

              + "demo.txt"); // 要操作的文件

       OutputStream out = null;//声明字节输出流

       out = new FileOutputStream(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       byte b[] = str.getBytes();//将String变为byte数组

       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,则表示增加内容。

package org.lxh.outputstreamdemo;

 

import java.io.File;

import java.io.FileOutputStream;

import java.io.OutputStream;

 

public class AppendOutputStreamDemo {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       OutputStream out = null;//声明字节输出流

       out = new FileOutputStream(file,true);//通过子类实例化,表示追加

       String str = "\r\nJava\r\tmldn";//要输出的信息,“\r\n”表示换行,“\r\t”表示制表

       byte b[] = str.getBytes();//将String变为byte数组

       out.write(b); // 写入数据

       out.close(); // 关闭

    }

}

3.4.3、字节输入流:IntputStream

使用InputStream可以读取输入流的内容,那么此类的定义如下:

public abstract class InputStream

extends Object

implements Closeable

此类也属于一个抽象类,那么如果想要使用的话,则肯定还是依靠其子类,如果现在是文件操作则使用的是FileInputStream,FileInputStream类的构造方法:

public FileInputStream(File file) throws FileNotFoundException

实例化之后就可以通过如下的方法取得数据:

● 读取到字节数组之中,返回读取个数:public int read(byte[] b) throws IOException

● 读取部分数据到数组,并返回读取个数:public int read(byte[]b, int off, int len)

                                               throws IOException

● 读取单个字节:public abstract int read()throws IOException,这个方法返回的int类型数据和以上两个不一样,返回的是字节的int类型编码。

这三个read()方法和OutputStream类中的三个write()方法是完全对应的。

范例:将文件的内容读取进来(其中“,”和“!”都属性中文字符,即全角)

package myio;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

 

public class InputStreamDemo01 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

        InputStream input = null;//字节输入流

        input = new FileInputStream(file);//通过子类进行实例化操作

        byte b[] =newbyte[1024];//开辟空间接收读取的内容

        int len = input.read(b);//将内容读入到byte数组中

        System.out.println("" +new String(b, 0, len) + "");//输出内容

        input.close(); // 关闭

    }

}

【hello,中国!】

以上是一种比较常见的读取形式,但是以上的代码有一个缺点:会受到开辟空间的限制。如果现在想动态开辟数组的空间,则可以根据文件的大小来决定,采用read()方法一个个的读取数据。

package org.lxh.inputstreamdemo;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

 

public class InputStreamDemo02 {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       InputStream input = null;//字节输入流

       input = new FileInputStream(file);//通过子类进行实例化操作

       byte b[] = new byte[(int)file.length()];//开辟空间接收读取的内容

        // 由于File类的length()返回的是long类型,所以需要强制向下转型

       for(int i=0;i<b.length;i++){

           b[i] = (byte)input.read() ;//一个个的读取数据

       }

       System.out.println("" +new String(b) +"");//输出内容,直接转换

       input.close(); // 关闭

    }

}

【hello,中国!】

       观察此程序,说出它FOR循环的次数。如果此时打印file.length()的话,返回的结果将是13!虽然demo.txt中内容只有9个字符,但是file.length()返回的是文件字节数。我们知道,一个中文字符是占两个字节的,所以13字节 = 5个英文字符(hello)+ 4个中文字符(,中国!)。因此,在上面的FOR循环中也就执行了13次。

       至于为什么两个byte字节可以在这里组成一个中文字符,那是因为此java文件的编码属性是GBK,见下图(Eclipse中按Alt + Enter)。如果换成UTF-8、ASCII等其他不包含中文编码的,则肯定出现乱码!

还可以使用StringBuffer类来完成。

package myio;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

 

public class InputStreamDemo02 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

        InputStream input = null;//字节输入流

        input = new FileInputStream(file);//通过子类进行实例化操作

        int temp = 0;

        StringBuffer buf =new StringBuffer();

        while ((temp = input.read()) != -1) {

            buf.append((char) temp);

        }

        System.out.println("" + buf +"");//输出内容,直接转换

        input.close(); // 关闭

    }

}

hello?????ú??

       当此时如果读取的文件中存在中文,则会出现乱码。原因是:input.read()每次取出的是一个字节的int类型编码,而每次取出后就进行了强制char转换,因为一个char类型是可以存一个中文字符的(前提当前*.java文件编码支持中文),所以把两个字节的中文字符分开两个转换,肯定会出现乱码。

解决办法是使用字符流FileReader。请保持好奇心继续往下看!

3.4.4、字符输出流:Writer

Writer类是在io包中操作字符的最大父类,主要功能是完成字符流的输出。Writer类的定义格式:

public abstract class Writer

extends Object

implements Appendable, Closeable, Flushable

既然是进行字符的输出流,那么其提供的输出方法也肯定和字符有关:

n  输出字符串数据:public void write(String str) throwsIOException,不再需要转换。

n  输出字符数组:public void write(char[] cbuf) throws IOException

与OutputStream一样,都属于抽象类,如果要进行文件中的保存,则使用FileWriter子类。

package org.lxh.writerdemo;

 

import java.io.File;

import java.io.FileWriter;

import java.io.Writer;

 

public class WriterDemo01 {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       Writer out = null; // 声明字符输出流

       out = new FileWriter(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       out.write(str); // 写入数据

       out.close(); // 关闭

    }

}

FileWriter实现文件内容的追加:

● public FileWriter(File file, boolean append) throws IOException

package org.lxh.writerdemo;

 

import java.io.File;

import java.io.FileWriter;

import java.io.Writer;

 

public class AppendWriterDemo01 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       Writer out = null; // 声明字符输出流

       out = new FileWriter(file,true);//通过子类实例化,表示可以追加

       String str = "\r\nJAVA"; // 要输出的信息

       out.write(str); // 写入数据

       out.close(); // 关闭

    }

}

3.4.5、字符输入流:Reader

字符输入流与字节输入流不同的地方在于,使用的是char数组,Reader类的定义:

public abstract class Reader

extends Object

implements Readable, Closeable

Reader也是一个抽象类,要读取文件则需要使用FileReader。

读取的方法:

● 读取一组字符:public int read(char[] cbuf) throws IOException

● 读取部分内容:public abstract int read(char[] cbuf, int off, int len)

                                 throwsIOException

● 读取一个个字符:public int read()throws IOException

范例:读取下面文件内容:

package org.lxh.readerdemo;

 

import java.io.File;

import java.io.FileReader;

import java.io.Reader;

 

public class ReaderDemo01 {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       Reader input = null;//字符输入流

       input = new FileReader(file);//通过子类进行实例化操作

       char c[] = new char[1024];//开辟空间接收读取的内容

       int len = input.read(c);//将内容读入到char数组中

       System.out.println("" +new String(c,0, len) + "");//输出内容

       input.close(); // 关闭

    }

}

【hello,中国!

加油!】

要切记如果定义的char类型是指定长度数组的话,在下面转为String时,一定要只截取到文件的长度就够了,否则会有不必要的空格出现接后。

以上完成了一个字符的输入流,那么当然也可以通过循环的方式,一个个的进行读取操作。

范例:使用read()方法循环读取数据

package myio;

 

import java.io.File;

import java.io.FileReader;

import java.io.Reader;

 

public class ReaderDemo02 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

        Reader input = null;//字符输入流

        input = new FileReader(file);//通过子类进行实例化操作

        char c[] =newchar[(int) file.length()];//开辟空间接收读取的内容

        for (int i = 0; i < c.length; i++) {

            c[i] = (char) input.read();//一个个的读取数据

        }

        System.out.println("" +new String(c) + "");//输出内容,直接转换

        input.close(); // 关闭

    }

}

【hello,中国!

加油!???????

       可见,以上这种做法是存在问题的!如果读取的文件存在中文的话则会出现上面的问题:多余的问号!细心观察一下,其实问号的数量并不是随机的,而是中文字符的个数(这里是7个)。那为什么会造成此种错误呢?

       为了简化分析,我们把读取文件内容简化:

       添加一行输出文件长度的代码,输出结果如下:

……

char c[] = new char[(int) file.length()];

System.out.println(file.length());

for (int i = 0; i < c.length; i++)

……

6

【甲骨文???】

       个人愚见:可见刚才的猜测是正确的,3个中文字符则最后结果输出3个“?”。 造成原因是,在开辟字符数组c[]时,使用的长度是文件的大小file.length(),而由于每个中文字符是用两个字节来存放的,所以开辟的时候相当于new char[6]。在下面for循环中循环的次数也就是6次了,但实际input.read()只能有效执行3次(注意:因为Reader属于字符流,read()每次读取的是一个字符,而在InputStream字节流中每次读取的是一个字节,务必要区分清楚!),所以剩下执行的3次相当于往c[3~5]中填充“-1”,而“-1”对于char字符型来说它并不认识(char数据类型的范围是0~255),因此char字符型将用“?”来代替。

       好,既然现在知道了问题所在,我们尝试来“patching”。

范例:修改后的程序。

package myio;

 

import java.io.File;

import java.io.FileReader;

import java.io.Reader;

 

public class ReaderDemo03 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

        Reader input = null;//字节输入流

        input = new FileReader(file);//通过子类进行实例化操作

        char c[] =newchar[(int) file.length()];//开辟空间接收读取的内容

        int temp = 0; //由于read()不能直接判断(因为read()是直接执行的),所以要借助此变量。

        for (int i = 0; i < c.length; i++) {

            temp = input.read();//注意,read()在一个程序的循环体中只能写一次

            if(temp == -1){

                break;

            }

            c[i] = (char) temp;//一个个的读取数据

        }

        System.out.println("" +new String(c).trim() +"");//输出内容,直接转换

        input.close(); // 关闭

    }

}

【甲骨文】

       很明显, 这样的代码不但繁琐,而且还存在问题,trim()是把字符串前后的空格都去掉,而如果我不希望去掉文件内容本来在开头的空格呢?方法肯定是有的,但这里不再深入探究了。下面我们使用StringBuffer的方法来处理。

package myio;

 

import java.io.File;

import java.io.FileReader;

import java.io.Reader;

 

public class ReaderDemo01 {

    public static void main(String[] args)throws Exception {

        File file = new File("d:" + File.separator +"demo.txt");

        Reader input = new FileReader(file);

        StringBuffer buf = new StringBuffer();

        int temp = 0;

        while ((temp = input.read()) != -1) {

            buf.append((char) temp);

        }

        System.out.println("" + buf +"");

    }

}

【甲骨文】

       上面使用FileReader字符流不但很好的解决了之前遇到的InputStream字节流读取中文时出现的错误,而且基本上也不限制读取文件的长度,并且代码简洁易懂。还有一点需要注意的是,使用while(…read()…)循环是比较好的做法,因为for并不能确定循环次数,而且read()放在while判断条件中可以保证read()读取到文件底(即返回-1)。

       小结,如果使用的是字节流就不要使用char来处理数据而使用byte处理;如果使用的是字符流则需要使用char来接收处理数据。

3.4.6、字节流与字符流的区别

以上操作的代码有两组,那么实际中应该使用哪组更好呢?

在硬盘上保存的或者是通过网络传输的肯定都是字节流数据,而且所有的图片,音乐等,也都是字节文件,那么只有文本文件才有可能是字符,而且文本文件也可以使用字节表示,所以字符流和字节流的关系非常类似于Oracle中的CLOB和BLOB字段的区别,即:字节流包含了字符流,但是字符流在处理中文的时候肯定要比字节流方便。

除了以上的区别之外,字节流在进行操作的时候直接跟终端进行IO访问,而字符流操作的时候中间要经过一个缓冲区,此缓冲区可以完成(字节↔ 字符)转换。

下面通过两个向文件中保存内容的程序为例,说明其区别。

范例:使用OutputStream完成:

package org.lxh.outputstreamdemo;

 

import java.io.File;

import java.io.FileOutputStream;

import java.io.OutputStream;

 

public class OutputStreamDemo {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       OutputStream out = null;//声明字节输出流

       out = new FileOutputStream(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       byte b[] = str.getBytes();//将String变为byte数组

       out.write(b); // 写入数据

    }

}

以上的程序执行时没有关闭操作,发现内容可以正常的输出,下面再看字符流。

package org.lxh.writerdemo;

 

import java.io.File;

import java.io.FileWriter;

import java.io.Writer;

 

public class WriterDemo {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

        Writer out = null;//声明字符输出流

       out = new FileWriter(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       out.write(str); // 写入数据

    }

}

以上的字符流并没有关闭。但是执行之后只能创建文件,而文件中并不存在内容,意味着并没有输出。

如果现在使用Writer类中的一个flush()方法,则可以输出数据。

package org.lxh.writerdemo;

 

import java.io.File;

import java.io.FileWriter;

import java.io.Writer;

 

public class WriterDemo {

    public static void main(String[] args)throws Exception {

       File file = new File("d:" + File.separator +"demo.txt");//要操作的文件

       Writer out = null; // 声明字符输出流

       out = new FileWriter(file);//通过子类实例化

       String str = "hello world"; // 要输出的信息

       out.write(str); // 写入数据

       out.flush(); //刷新

    }

}

实际上来讲,最早的操作中,并没有刷新,但是因为使用了关闭,所以表示会强制刷新,刷新的是缓冲区(内存)。

结论:

● 字节流在操作的时候是直接与文件本身关联,不使用缓冲区

-| 字节 → 文件

● 字符流在操作的时候是通过缓冲区与文件操作。

-| 字符  → 缓冲 → 文件

综合比较来讲,在传输或者在硬盘上保存的内容都是以字节的形式存在的,所以字节流的操作较多,但是在操作中文的时候字符流比较好用。

4、总结

1、在java中所有的io操作都定义在java.io包中。

2、File类表示与平台无关的文件操作,只负责文件的本身,而不负责文件的内容。

3、RandomAccessFile类完成的是随机的读取功能,相当于在文件中设置了一个指针,通过移动指针,可以进行数据的随机读取。

● 实现了DataInput、DataOutput

4、OutputStream和InputStream是字节的输出、输入流,通过FileXxx实例化。

5、Writer和Reader是字符的输出、输入流,通过FileXxx实例化。

6、字节流是直接操作文件本身的,而字符流是需要通过缓存操作文件本身。

5、作业 —— Copy

使用IO操作完成一个文件的拷贝功能,默认dos中的copy命令

代码开发的时候使用java的初始参数完成,例如:定义Copy的类,执行:

● java Copy 源文件 目标文件

本程序应该使用字节流的方式进行操作,操作的方法现在有如下两种:

● 讲要复制的内容全部读取进来之后,一次性写入

● 边读边写,使用此种方式最合适

package org.lxh.copydemo;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

 

public class Copy {

 

    public static void main(String[] args)throws Exception {//所有异常抛出

       if (args.length != 2) {//参数不是两个

           System.out.println("操作的语法不正确,应该输入要拷贝的文件路径。");

           System.out.println("例:java Copy源文件路径 目标文件路径");

           System.exit(1); // 系统退出

       }

       if (args[0].equals(args[1])) {

           System.out.println("无法复制自身文件。");

           System.exit(1); // 系统退出

       }

       File file1 = new File(args[0]);//找到第一个文件的File对象

       if (file1.exists()) {

           File file2 = new File(args[1]);//找到目标文件路径

           InputStream input = new FileInputStream(file1);//输入流

           OutputStream output = new FileOutputStream(file2);//输出流

           int temp = 0; // 定义一个整数表示接收的内容

           while ((temp = input.read()) != -1) {//表示还有内容可以继续读

              output.write(temp);// 写入数据

           }

           System.out.println("文件复制成功。");

           input.close(); // 关闭

           output.close();// 关闭

       } else {

           System.out.println("源文件不存在。");

       }

    }

}

java d:\demo\demo.txt d:\demo\demoCopy.txt

文件复制成功。

本道程序涵盖了之前所学习到的全部IO中的重点知识。

while ((temp = input.read()) != -1) {//表示还有内容可以继续读

output.write(temp);// 写入数据

}

如果文件没有读到底的话,则不会返回-1,表示还有内容。但以上程序有明显的缺点:如果拷贝的文件很大,则拷贝的速度非常慢,而且如果拷贝的中文编码的word文档则也有可能出现编码错误。因此应该文件分块拷贝,即每次读取一定的数据,之后再输出到指定路径。

范例:改进的Copy程序,每次读取进10M。

package org.lxh.copydemo;

 

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.Date;

 

public class Copy {

 

    public static void main(String[] args)throws Exception {//所有异常抛出

        if (args.length != 2) {//参数不是两个

            System.out.println("操作的语法不正确,应该输入要拷贝的文件路径。");

            System.out.println("例:java Copy源文件路径目标文件路径");

            System.exit(1); // 系统退出

        }

        if (args[0].equals(args[1])) {

            System.out.println("无法复制自身文件。");

            System.exit(1); // 系统退出

        }

        File file1 = new File(args[0]);//找到第一个文件的File对象

        if (file1.exists()) {

            File file2 = new File(args[1]);//找到目标文件路径

            InputStream input = new FileInputStream(file1);//输入流

            OutputStream output = new FileOutputStream(file2);//输出流

            long start =new Date().getTime();//截取开始时间

            int temp = 0;//定义一个整数表示接收的内容

            byte date[] =newbyte[10485760];

            while ((temp = input.read(date)) != -1) {//表示还有内容可以继续读

                output.write(date, 0, temp);//写入数据

            }

            long interval =new Date().getTime() - start;//花费时间

            System.out.println("文件复制时间:" + interval / 1000 +"");

            System.out.println("文件复制成功。");

            input.close(); // 关闭

            output.close();// 关闭

        } else {

            System.out.println("源文件不存在。");

        }

    }

}

java org.lxh.copydemo.Copy D:\IOTest\bigFile.zip D:\IOTest\copybigFile.zip

文件复制时间:3秒

文件复制成功。

       可以看到,文件按10M分块后,拷贝速度明显提高,测试时只需3秒(文件约600M)。对于此程序,还有需要注意的是,程序运行时间的计算。

 

 

问题(temp = input.read()) != -1,其中temp的值是多少?如果input.read() = ‘a’(实际并非a,而是二进制数字ASCII值),那么temp此时等于 97

0 0
原创粉丝点击