Java SE 14th IO (1),14thio
Java SE 14th IO (1),14thio
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,我们只关心它的构造方法:
n 构造: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 ?
相关文章
- 暂无相关文章
用户点评