上一篇《Java中的IO流(四)》记录了一下Properties类,此类不属于IO流,它属于集合框架。接下来说一下IO流中的其它流
一,打印流PrintStream
PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。并且此注永远不会抛出IOException。
此流的构造函数大致分三类
1,接收File文件类型的
2,接收OutputStream类型的
3,接收文件名形式的
下演示一下此流的两个方法
private static void function_demo1() throws IOException {
PrintStream ps = new PrintStream("print.txt");
ps.write(97);
ps.println();
ps.print(97);
}
运行结果 :
a
97
注:PrintStream的write方法继承自父类,此方法的说明是向输出流写入一个字节。要写入的字节是参数 b
的八个低位。b
的 24 个高位将被忽略。所以我们看到的第一行输出转为了”a“;而print方法是直接把参数调用String的ValueOf方法转为字符串直接输出的,所以若想把一个数据的直接表现形式则用print方法。
二,打印流PrintWriter
向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream
中的所有 print 方法。
此类的构造函数大致分为四类
1,接收File文件类型
2,接收OutputStream类型
3,接收文件名形式
4,接收Writer类型
此类有构造函数接收第二个参数类型为boolean类型的,若传为true则可将数据自动flush到流中
演示如下:
private static void function_demo2() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
PrintWriter writer = new PrintWriter(System.out);
String line = null;
while ((line = reader.readLine()) != null) {
if (line.equals("over")) {
break;
} else {
writer.println(line.toUpperCase());
}
}
writer.close();
reader.close();
}
注:以上代码在演示的时候每次输入完后并不会立刻输入到控制台,当在第九行后加writer.flush();时每次输入完后即可把相应信息打印到控制台,但printwriter类提供了自动刷新的方法就是用两个参数的构造函数,演示如下
private static void function_demo2() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
PrintWriter writer = new PrintWriter(System.out, true);
String line = null;
while ((line = reader.readLine()) != null) {
if (line.equals("over")) {
break;
} else {
writer.println(line.toUpperCase());
}
}
writer.close();
reader.close();
}
private static void function_demo2() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
PrintWriter writer = new PrintWriter(new FileWriter("writer.txt"), true);
String line = null;
while ((line = reader.readLine()) != null) {
if (line.equals("over")) {
break;
} else {
writer.println(line.toUpperCase());
}
}
writer.close();
reader.close();
}
三,序列流SequenceInputStream
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
此流相当于是一个集合,把若干个流放入到此集合,然后一个流接着一个流的读取,当完第一个判断此集合里是否还有其它流,若有接着读取,读到最后一个返回-1。
此流有两个构造函数,一个是接收两个InputStream类型的参数,即接收两个流,若想读取多个流,则用接收Enumeration类型的参数。
接下来我们用一个需求把此流演示一下,需求是把三个txt文档合并为一个文档
private static void function_demo3() throws IOException {
Vector<InputStream> v = new Vector<InputStream>();// 定义Vector集合用于存储所有的流
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
Enumeration<InputStream> enu = v.elements();// 因为SequenceInputStream接收的是Enumeration类型的参数,所以用Vector集合
SequenceInputStream sis = new SequenceInputStream(enu);
OutputStream out = new FileOutputStream("4.txt");
byte[] bt = new byte[1024];
int len = 0;
while ((len = sis.read(bt)) != -1) {
out.write(bt, 0, len);
}
out.close();
sis.close();// 序列流的关闭会把其中的所有的流都关闭
}
大家都知道,Vector对象效率太低,开发中一般会用ArrayList,那么接下来就把Vector换成ArrayList,演示如下
private static void function_demo3() throws IOException {
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();// 定义ArrayList集合接所有的流
al.add(new FileInputStream("1.txt"));
al.add(new FileInputStream("2.txt"));
al.add(new FileInputStream("3.txt"));
Iterator<FileInputStream> it = al.iterator();// 获取一个Iterator对象
// 没有枚举对象就new一个出来,然后重写里面的两个方法返回iterator对象的hasNext和next方法
Enumeration<FileInputStream> enumeration = new Enumeration<FileInputStream>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
} @Override
public FileInputStream nextElement() {
return it.next();
}
};
SequenceInputStream sis = new SequenceInputStream(enumeration);
OutputStream out = new FileOutputStream("4.txt");
byte[] bt = new byte[1024];
int len = 0;
while ((len = sis.read(bt)) != -1) {
out.write(bt, 0, len);
}
out.close();
sis.close();// 序列流的关闭会把其中的所有的流都关闭
}
看起来很麻烦的样子,有没有办法简化一下呢?Collections集合类里为我们提供了一个获取Enumeration的工具,演示如下:
private static void function_demo3() throws IOException {
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();// 定义ArrayList集合接所有的流
al.add(new FileInputStream("1.txt"));
al.add(new FileInputStream("2.txt"));
al.add(new FileInputStream("3.txt"));
Iterator<FileInputStream> it = al.iterator();// 获取一个Iterator对象
Enumeration<FileInputStream> enumeration = java.util.Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(enumeration);
OutputStream out = new FileOutputStream("4.txt");
byte[] bt = new byte[1024];
int len = 0;
while ((len = sis.read(bt)) != -1) {
out.write(bt, 0, len);
}
out.close();
sis.close();// 序列流的关闭会把其中的所有的流都关闭
}
四,文件切割及合并
把一个大的文件切割成若干个小的文件,方便文件上传的时候对大小的限制,实现代码如下
private static final int SIZE = 1024 * 1024; private static void function_demo4() throws IOException {
File dir = new File("c:\\");
File file = new File(dir, "1.bmp");// 需要分隔的源文件
FileInputStream inputStream = new FileInputStream(file);// 字节流对象
byte[] bt = new byte[SIZE];// 缓冲数组,大小由常量固定
// 由于需要分隔文件,所以此处不能实例化输出流对象,需要在读取的时候每读取一次实例化一个输出流对象
FileOutputStream outputStream = null;
File dest = null;// 由于切割文件,每个被切割出来的文件名称不同,所以此处File不能实例化,需要在每切割出来一个文件则实例化一个File对象
int len;
int i = 0;// 为切割出来的每个文件名添加序列
while ((len = inputStream.read(bt)) != -1) {
i++;
dest = new File(dir, "\\temp\\" + i + ".part");//切割出来的文件对象
outputStream = new FileOutputStream(dest);//实例化每个输出流对象
outputStream.write(bt, 0, len);//读取每个切割出来的文件
outputStream.close();//关闭每个读取流对象
}
inputStream.close();//关闭输入流对象
}
把切割后的文件合并为一个文件,实现代码如下
private static void function_demo5() throws IOException {
File dir = new File("C:\\temp");//源目录对象
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();//存储输入流的集合对象
FileInputStream inputStream = null;//根据源文件的个数实例化输入流对象,因需要循环源文件数量,所以此处实例为null
File file = null;//源文件名字不一样,所以此处实例化为null
for (int i = 1; i < 4; i++) {
file = new File(dir, i + ".part");//源文件名
inputStream = new FileInputStream(file);//封装输入流对象
al.add(inputStream);//将输入流对象添加到流集合对象中
}
Enumeration<FileInputStream> enu = java.util.Collections.enumeration(al);//从输入流集合对象中获取Enumeration对象以便传给序列流SequenceInputStream
SequenceInputStream sis = new SequenceInputStream(enu);//封装序列流对象
BufferedInputStream bis = new BufferedInputStream(sis);//缓冲输入流对象
File dest = new File(dir, "4.bmp");//封装合并全的文件名及路径
FileOutputStream outputStream = new FileOutputStream(dest);//输出流对象
BufferedOutputStream bos = new BufferedOutputStream(outputStream);//输出缓冲对象
//以下代码是循环读取及写入流对象,关闭流
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.close();
bis.close();
}
五,完整的文件切割与合并
由上面的例子大家可以考虑几个问题
1,被切割后的文件数量在合并的时候并不知道
2,源文件的文件扩展名在合并的时候并不知道
鉴于这两点,在切割文件的时候应当把这两个信息写入到配置文件中,在合并文件的时候先读取配置文件中的信息然后检验配置文件中的信息和切割后的文件信息是否一致,若一致再进行合并操作,具体操作演示如下
private static final int SIZE = 1024 * 1024; private static void function_demo6() throws IOException {
File dir = new File("c:\\");
File file = new File(dir, "1.bmp");// 需要分隔的源文件
FileInputStream inputStream = new FileInputStream(file);// 字节流对象
byte[] bt = new byte[SIZE];// 缓冲数组,大小由常量固定
// 由于需要分隔文件,所以此处不能实例化输出流对象,需要在读取的时候每读取一次实例化一个输出流对象
FileOutputStream outputStream = null;
File dest = null;// 由于切割文件,每个被切割出来的文件名称不同,所以此处File不能实例化,需要在每切割出来一个文件则实例化一个File对象
int len;
int i = 0;// 为切割出来的每个文件名添加序列
while ((len = inputStream.read(bt)) != -1) {
i++;
dest = new File(dir, "\\temp\\" + i + ".part");// 切割出来的文件对象
outputStream = new FileOutputStream(dest);// 实例化每个输出流对象
outputStream.write(bt, 0, len);// 读取每个切割出来的文件
outputStream.close();// 关闭每个读取流对象
}
inputStream.close();// 关闭输入流对象
String fileName = file.getName();// 获取源文件名
File fileCountFilter = new File("C:\\temp");// 被切割的文件的存放目录
String[] fileCountArray = fileCountFilter.list(new PartFileNameFilter(".part"));// 过滤被切割的文件
int count = fileCountArray.length;// 被切割的文件数量
Properties prop = new Properties();// 把切割的文件信息存储到配置文件中
prop.setProperty("fileName", fileName);// 源文件名
prop.setProperty("count", String.valueOf(count));// 被切割出来的文件数量
Writer writer = new FileWriter("C:\\temp\\config.properties");
prop.store(writer, "cut file info");
}
private static void function_demo7() throws IOException {
File dir = new File("C:\\temp");// 源目录对象
String[] partFileArray = dir.list(new PartFileNameFilter(".part"));
int partFileCount = partFileArray.length;// 获取源目录中被切割的文件数量
File[] propertiesFile = dir.listFiles(new PartFileNameFilter(".properties"));// 过滤配置文件
String fileName = null;
int cutFileCount = 0;
if (propertiesFile.length != 1) {// 若切割文件配置文件不唯一则抛出异常
throw new RuntimeException("切割文件的配置信息没有或多于一个,无法完成文件合并");
} else {// 否则读取配置文件信息
Properties prop = new Properties();
prop.load(new FileReader(propertiesFile[0]));
fileName = prop.getProperty("fileName");// 获取配置文件中的源文件名
cutFileCount = Integer.parseInt(prop.getProperty("count"));// 获取配置文件中被切割后的文件数量
if (partFileCount != cutFileCount) {// 若读取到的配置文件中的文件数与源目录中被切割的文件数量不一样,则抛出异常
throw new RuntimeException("文件不全,无法进行文件合并");
}
}
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();// 存储输入流的集合对象
FileInputStream inputStream = null;// 根据源文件的个数实例化输入流对象,因需要循环源文件数量,所以此处实例为null
File file = null;// 源文件名字不一样,所以此处实例化为null
for (int i = 1; i <= cutFileCount; i++) {
file = new File(dir, i + ".part");// 源文件名
inputStream = new FileInputStream(file);// 封装输入流对象
al.add(inputStream);// 将输入流对象添加到流集合对象中
}
Enumeration<FileInputStream> enu = java.util.Collections.enumeration(al);// 从输入流集合对象中获取Enumeration对象以便传给序列流SequenceInputStream
SequenceInputStream sis = new SequenceInputStream(enu);// 封装序列流对象
BufferedInputStream bis = new BufferedInputStream(sis);// 缓冲输入流对象
File dest = new File(dir, fileName);// 封装合并全的文件名及路径
FileOutputStream outputStream = new FileOutputStream(dest);// 输出流对象
BufferedOutputStream bos = new BufferedOutputStream(outputStream);// 输出缓冲对象
// 以下代码是循环读取及写入流对象,关闭流
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.close();
bis.close();
}