Java IO体系 - 第二部分

时间:2022-09-29 08:56:55


一.打印流PrintStream和PrintWriter

PrintStream

构造函数可以接受的参数类型:

1.           File对象:File对象

2.           字符串路径:String

3.           字节流输出流:OutputStream

PrintWriter(写出时候需要刷新)

构造函数可以接受的参数类型:

1.           File类型:File对象

2.           字符串类型路径:String

3.           字节输出流: OutputStream

4.           字符输出流:Writer

PrintWriter示例代码:

package com.IODemo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
publicclass PrintWriterDemo
{
publicstatic void main(String[] args)throws IOException
{
InputStream in = System.in;
//字节字符转换流
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);

OutputStream os = System.out;
//一般我们知道,使用缓冲输出流,都要flash()刷新才能是数据写入出去,可是//PrintWriter的构造函数可以接收一个true的boolean值类型的参数,来确定是
//否要自动刷新,而且次参数仅仅适用在println,print,printf方法上。
PrintWriter pw =new PrintWriter(os,true);
String s= null;

while((s = br.readLine()) !=null)
{
//pw.write(s);
pw.println(s);
// pw.flush();
}
pw.close();
br.close();
}
}


二.  合并流以及文件切割

文件的分割:在I/O中并没有提供这样的文件分割功能的流,但是要达到这样的效果,无非就是利用字节流byte缓冲数组,将数据分多次写出到不同的文件中,即可。

文件分割的示例代码:将一个图片文件进行分割

package com.IODemo;
importjava.io.BufferedInputStream;
importjava.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
publicclass SplitFileDemo
{
publicstatic void main(String[] args)throws IOException
{
//如下是建立了一个文件字节输入流,相当于确认了要被分割的对象,这里是一
//图片个文件,图片当然要用字节流来操作。
FileInputStream fis = new FileInputStream("e:\\A\\img0.jpg");
//因为要分割成多个文件碎片,所以这里定义文件字节输出流的时候,不能将其//定义死,应在while循环中去动态new出来对象,达到产生多个文件碎片的效
//果。
FileOutputStream fos = null;
//创建一个byte类型的缓冲数组。
byte[] b =new byte[1024*200];
int result = 0;
int part = 1;
while( (result =fis.read(b)) != -1 )
{
//每当作一个文件碎片写出去。
fos = new FileOutputStream("e:\\A\\"+part+".part");
//这里移动要设置将0,到实际读到的个数resut,不然最后一次读到的不满//1024*200个字节,就会出现问题,或分割后的总大小会大于原始文件的问题。
fos.write(b,0,result);
part++;
fos.close();
}
fis.close();
}
}


以上就很简单的将要一个文件分割成了4个部分。

 

字节合并流: SequenceInputStream,可知这个合并字节流可以将多个字节流进行合并为一个大体的流里面进行操作。其可选的构造方法如下:

SequenceInputStream(Enumeration<? extendsInputStream> e) 这个构造方法中的参数是一个Enumeration集合,而这个集合中存放的都是待合并的InputStream类型的字节流,集合中可以存放多个这样的InputStream对象,合并之后,最后可以将作为一个整体的流输出。

另外一个构造方法SequenceInputStream(InputStream s1,InputStream s2)是接收两个字节输入流,并对这两个输入流进行合并操作。
字节流合并的示例代码:(实现将以上分割后的文件进行合并输出)

package com.IODemo;
import java.io.BufferedInputStream;
importjava.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
publicclass SquenceDemo
{
publicstatic void main(String[] args)throws IOException
{
merge();
}
publicstatic void merge()throws IOException
{
ArrayList<FileInputStream> list =new ArrayList<FileInputStream>();
//建立一个ArrayList集合,并使用for循环将动态的将如上分割后的四个文件碎//片添加到集合中。
for(int i = 1 ; i <= 4 ; i++)
{
list.add(new FileInputStream("e:\\A\\"+i+".part"));
}
//因为这个构造方法必须接收一个Enumeration类型的集合,而且里面必须是
//InputStream类型的元素,所以,这里要先从list中拿到Iterator迭代器,然
//后利用迭代器和匿名内部内的形式去实现Enumeration中的两个方法,便可
//以将ArrayList中的元素存放在了Enumeration集合对象中去了,不过这里
//要注意,匿名内部类只能访问方法内部或外部的用final修饰的变量。
final Iterator<FileInputStream> iter= list.iterator();
Enumeration<FileInputStream>en = new Enumeration<FileInputStream>()
{
@Override
publicboolean hasMoreElements()
{
return iter.hasNext();
}
@Override
public FileInputStream nextElement()
{
return iter.next();
}
};
//然后定一个合并流,将Enumeration对象传入,在创建一个字节输出流,
//将文件写出去,并使用字节缓冲流对其进行包装。
SequenceInputStream sis = new SequenceInputStream(en);
BufferedInputStream bis = new BufferedInputStream(sis);

FileOutputStream fos = new FileOutputStream("e:\\A\\image.jpg");
BufferedOutputStream bos =new BufferedOutputStream(fos);

byte[] b =new byte[1024];
int result = 0;
while( (result = bis.read(b)) != -1)
{
bos.write(b,0,result);
bos.flush();
}
bos.close();
bis.close();
}


}




 

三.  管道流

传送输出流可以连接到传送输入流,以创建通信管道。传送输出流是管道的发送端。通常,数据由某个线程写入PipedOutputStream 对象,并由其他线程从连接的PipedInputStream 读取。不建议对这两个对象尝试使用单个线程,因为这样可能会死锁该线程。
PipedOutputStream()创建尚未连接到传送输入流的传送输出流,倘若使用这个构造方法创建管道输出对象时,在创建完毕需要connect(PipedInputStream snk)
将此传送输出流连接到接收者。

PipedOutputStream(PipedInputStream snk) 创建连接到指定传送输入流的传送输出流,而这使用这个构造方法创建管道输出流时,不需要手动去创建,而是传入管道输出流参数时候,内部就已经自动为我们连接好了

PipedInputStream()创建尚未连接的PipedInputStream,倘若使用这个构造方法创建管道输入流对象的时候,在创建完毕需要调connect(PipedOutputStream src)
方法使此传送输入流连接到传送输出流 src

PipedInputStream(PipedOutputStream src) 创建PipedInputStream,以使其连接到传送输出流src,而这使用这个构造方法创建管道输入流时,不需要手动去创建,而是传入管道输出流参数时候,内部就已经自动为我们连接好了

管道流的数据提供或是输出方是PipedOutputStream,而接收方PipedInputStream。

通常在多线程中使用,不建议在单线程中使用,否则导致死锁,当接收方没有接读取到数据的时候会,进入阻塞状态,等待放松方数据的发送,直到数据接收到位置。

管道流示例代码:

package com.IODemo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
publicclass PipedStreamDemo
{
publicstatic void main(String[] args)throws IOException
{
PipedInputStreampis = new PipedInputStream();
PipedOutputStreampos = new PipedOutputStream();
//建立连接
pis.connect(pos);
ReadThreadrt = new ReadThread(pis);
WriteThreadwt = new WriteThread(pos);
new Thread(rt).start();
new Thread(wt).start();
}
}
class ReadThreadimplements Runnable
{
private InputStreamin;
public ReadThread(InputStream in)
{
this.in = in;
}
@Override
publicvoid run()
{
try
{
BufferedInputStreambis =new BufferedInputStream(in);
byte[] b =new byte[1024];
System.out.println("读取前,没有接收到数据,阻塞状态.......");

int result = bis.read(b);

System.out.println("已经读取到数据,阻塞状态结束!");
System.out.println(new String(b,0,result));
}
catch(Exception e)
{
thrownew RuntimeException("管道流读取失败!");
}
}
}
class WriteThreadimplements Runnable
{
private OutputStreamout;
public WriteThread( OutputStream out)
{
this.out = out;
}
@Override
publicvoid run()
{
try
{
BufferedOutputStreambos = new BufferedOutputStream(out);
System.out.println("正在准备开始写入,正等待6s......");
Thread.sleep(6000);
bos.write("大家好,我来啦!".getBytes());
bos.flush();
}
catch(Exception e)
{
thrownew RuntimeException("管道流写入失败!");
}
}
}


         

四.  File类

简单说File类对象表示的是一个文件或者一个目录,但我们可以将其看作是一个文件或一个目录,只不过文件被封装成File对象后,可以得到更多更加方便的操作。

1. File 类中的重要字段,separator表示与系统有关的默认目录名称分隔符,出于方便考虑,它被表示为一个字符串。

2.File类中的的创建、删除、修改、获取、查询、判断功能:
创建:
createNewFile():当某个File对象调用此方法时,可以创建一个该File对象所表示的文件,如果该文件已经存在,返回false该方法只能创建文件,不能创建目录,

File f = newFile(“g:\\test.log”);

f.createNewFile();

mkdir() 当某个File对象调用此方法时候,可以创建一个该File对象所表示的文件目录,当该文件已经存在时,返回false,如果指定的文件目录的上一级目录不存在,将无法创建,返回为false。

mkdirs()当某个File对象调用此方法时候,可以创建一个该File对象所表示的文件目录,当该文件已经存在时,返回false,如果指定的文件目录的上一级目录不存在,则依然可以创建成功。

删除:

delete()方法,当某个File对象调用此方法时,可以删除该对象所表示的一个文件活一个目录,当次文件或者该目录不存在的时候,返回为false,若要删除的对象是一个非空目录,将无法删除,返回false,需要遍历该目录将该目录下的文件都删除后,才可删除。

deleteOnExit() 在虚拟机终止时,请求删除此File对象所表示的文件或目录。

判断:

isFile()方法用来判断一个File对象所表示的是否为文件,当所表示的文件不存在时,返回为false。

isDirectory()方法用来判断一个File对象多表示的是否为一个文件目录, 当所表示的文件目录不存在时,返回为false。

isHidden()测试File对象所表示的文件或目录是否是一个隐藏类型的,当所表示的文件或目录不存在的时候,返回false。

 

exists()测试File对象所表示的文件或目录是否存在,当所表示的文件或目录不存在的时候,返回false。

List功能:

list():返回一个String类型的数组,存储着这个File对象所表示的目录中的文件和目录的名称所组成字符串数组,当该对象为一个文件时,返回null,当所表示的文件目录不存在则抛出空指向异常。

list(FilenameFilter filter):一个带有过滤功能的list()方法使用匿名内部内去实现该接口的方式去传递一个FilenameFilter类的对象,但是必须实现其accept()做为过滤的条件,当返回值为true时,遍历到的File对象会被保存带File数组中

package com.IO_Practice;
import java.io.File;
import java.io.FilenameFilter;
publicclass FilterFileDemo
{
publicstatic void main(String[] args)
{
onlyTypeFiles(new File("c:\\windows\\system32") ,".exe");
}
publicstatic void onlyTypeFiles(File file ,final String type)
{
File[]files = file.listFiles
(
new FilenameFilter()
{
publicboolean accept(File dir, String name)
{
return name.endsWith(type);
};
}
) ;
for(File fs : files)
{
System.out.println(fs.toString());
}
}
}


listFiles():返回这个File对象所表示的路径目录中的文件File数组对象。

listRoots() 列出可用的文件系统根目录。

File类的几个典型例子:

代码1:利用递归调用将一个非空的目录删除

package com.IO_Practice;
import java.io.File;
publicclass DeleteDirDemo
{
//删除一个给定的目录。
publicstatic void main(String[] args)
{
Filef = new File("G:\\A");
deleteDir(f);
}
publicstatic void deleteDir(File f)
{
String s ;
File[] files = f.listFiles();
for(File fs : files)
{
if(fs.isFile())
{
s = fs.delete() == true ? fs.toString()+"删除成功!" : fs.toString()+"删除失败!" ;
System.out.println(s);
}
else
{
deleteDir(fs);
}
}
s= f.delete() == true ?f.toString()+"删除成功!" : f.toString()+"删除失败!" ;
System.out.println(s);
}
}


代码2:给定一个非空目录,使用递归的方法以树形结果将其现实。

package com.IO_Practice;
import java.io.File;
import java.util.ArrayList;
publicclass TreeListDirDemo
{
//遍历一个给定的文件目录,要求文件夹统一在前面。
privatestatic intlevel = 0;
publicstatic void main(String[] args)
{
TreeListDir(new File("D:\\YouCam"));
}
publicstatic void TreeListDir(File f)
{
if(f.isFile() || f.listFiles().length == 0)
{

return;
}
else
{
Filefiles[] =f.listFiles();
files= SortFile(files);
for(File fs : files)
{
StringBuilder sb = new StringBuilder();
if(fs.isFile())
{
sb.append(getTab(level)).append(fs.getName()).append("\n");
}
else
{
sb.append(getTab(level)).append(fs.getName()).append("\n");
}
System.out.println(sb.toString());
if(fs.isDirectory())
{
++level;
TreeListDir(fs);
--level;
}
}
}
}
//定一个方法用来现实文件和文件夹前面的横线
publicstatic String getTab(int n)
{
StringBuilder sb = new StringBuilder("|-");
for(int i = 1 ; i <= n ; i++)
{
//sb.append("\t");
sb.insert(0,"--");
}
return sb.toString();
}
//定一个方法用来将File[]排序,是目录放在前面
publicstatic File[] SortFile(File[] files)
{
ArrayList<File> list = new ArrayList<File>();
for(File f : files)
{
if(f.isDirectory())
{
list.add(f);
}
}
for(File f : files)
{
if(f.isFile())
{
list.add(f);
}
}
File newFiles[] = list.toArray(new File[files.length]);
return newFiles;
}
}



五.RandomAccessFile类

1.       该类不属于I/O体系中任何类的子类,而是直接继承自Object类

2.       该类内部封装了字节输入流和字节输出流,实现了基本的字节read和write操作,另外还增强了功能,可以写入和读取基本的数据类型。

3.       该类内部有一个大型字节数组,还有一个文件指针,用来指向该数组的索引和下标,该文件指针可以通过getFilePointer 方法读取,并通过seek 方法设置开始读取和写入字节的索引位置。

4.       通过查看该类的API,RandomAccessFile(File file,String mode)RandomAccessFile(String name,String mode)发现它的构造函数必须接收一个文件,可以是以字符串的表现形式传入,当然也可以是以File对象类型封装文件的方法传入,另外其第二个参数是必须要传入一个字符串,该字符串传入后,用来表示对文件的读写操作模式的,其可使用值只有四种,如下所示:


“r”: 以只读方式打开。调用结果对象的任何write 方法都将导致抛出IOException
“rw”: 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
“rws”:打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到基础存储设备。
"rwd":打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到基础存储设备。

5.      使用RandomAccessFile对象的write方法是向文件中写入字节数据,而我们打开记事看的的信息不是字节数据,是因为记事本在做了一个解码的动作,将写入的字节码去查平台默认的码表,将码表上对应的字符显示出来。

6.       而使用read的方法是将读取到的是字节码,但是会自动将其提升为int类型的,而提升为int类型。

代码示例:

package com.IODemo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
publicclass RandomAccessFileDemo2
{
publicstatic void main(String[] args)throws IOException
{
RandomAccessFile raf = new RandomAccessFile("c:\\ABC.txt","rw");
//将字符串”张三”转换为byte数组之后将其写入文件。
raf.write("张三".getBytes());
//将97以int类型写入文件。
raf.writeInt(97);
//倘若要继续写入信息的话,它不会去覆盖上面写入的数据,而是往后追加。
raf.write("李四".getBytes());
raf.writeInt(99);
raf.close();

}
package com.IODemo;
importjava.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
publicclass RandomAccessFileDemo2
{
publicstatic void main(String[] args)throws IOException
{
RandomAccessFile raf = new RandomAccessFile("c:\\ABC.txt","rw");
// raf.write("张三".getBytes());
// raf.writeInt(97);
// raf.write("李四".getBytes());
// raf.writeInt(99);
//一般我们的平台都使用GBK码表,所以一个字符用两个字节表示,所以要我只//要读取李四的姓名和年龄,那么就要将文件指针移动过8个字节去读取出来
raf.seek(8);
//读取姓名用字节数组存取之后用String去将其抓换成字符串
byte[] buf =new byte[4];
raf.read(buf);
String name = new String(buf);
//而年龄就可以直接用其特有的方法去读取了,十分方便。
int age = raf.readInt();
System.out.println(name);
System.out.println(age);
raf.close();
}
}

package com.IODemo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
publicclass RandomAccessFileDemo2
{
publicstatic void main(String[] args)throws IOException
{
RandomAccessFile raf = new RandomAccessFile("c:\\ABC.txt","rw");
//将字符串”张三”转换为byte数组之后将其写入文件。
raf.write("张三".getBytes());
//将97以int类型写入文件。
raf.writeInt(97);
//倘若要继续写入信息的话,它不会去覆盖上面写入的数据,而是往后追加。
raf.write("李四".getBytes());
raf.writeInt(99);
raf.close();

}
package com.IODemo;
importjava.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
publicclass RandomAccessFileDemo2
{
publicstatic void main(String[] args)throws IOException
{
RandomAccessFile raf = new RandomAccessFile("c:\\ABC.txt","rw");
// raf.write("张三".getBytes());
// raf.writeInt(97);
// raf.write("李四".getBytes());
// raf.writeInt(99);
//一般我们的平台都使用GBK码表,所以一个字符用两个字节表示,所以要我只//要读取李四的姓名和年龄,那么就要将文件指针移动过8个字节去读取出来
raf.seek(8);
//读取姓名用字节数组存取之后用String去将其抓换成字符串
byte[] buf =new byte[4];
raf.read(buf);
String name = new String(buf);
//而年龄就可以直接用其特有的方法去读取了,十分方便。
int age = raf.readInt();
System.out.println(name);
System.out.println(age);
raf.close();
}
}


所以最后的打印结果就是:

李四

99

package com.IODemo;
importjava.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
publicclass RandomAccessFileDemo2
{
publicstatic void main(String[] args)throws IOException
{
RandomAccessFile raf = new RandomAccessFile("c:\\ABC.txt","rw");
// raf.write("张三".getBytes());
// raf.writeInt(97);
// raf.write("李四".getBytes());
// raf.writeInt(99);
// raf.skipBytes(10);
//设置跳过前面16个字节,去写入新的信息,即将王五的姓名和年龄存入文件。
raf.seek(8*2);
raf.write("王五".getBytes());
raf.writeInt(103);
raf.close();
}
}