第10章 I/O及文件操作
I/O是指流的输入与输出。输入和输出是指程序与外部设备和其他计算机进行交流的操作。其中对磁盘文件的读写操作,是计算机程序非常重要的一项功能。流式输入/输出是一种很常见的输入和输出方式,输入流代表从外设流入到计算机内存的数据序列,输出流代表从计算机内存流向外设的数据序列。本章将详细讲解多种输入/输出操作以及对文件的管理。
实例78 创建文件和目录
本实例介绍如何在文件系统中创建文件和目录。在指定的目录下创建文件时目录不存在则新建目录,还可以生成临时文件。
技术要点
创建文件和目录的技术要点如下:
• File类的createNewFile根据抽象路径创建一个新的空文件,当抽象路径指定的文件存在时,创建失败。
• File类的mkdir方法根据抽象路径创建目录。
• File类的mkdirs方法根据抽象路径创建目录,包括创建必需但不存在的父目录。
• File类的createTempFile方法创建临时文件,可以指定临时文件的文件名前缀、后缀,以及文件所在的目录。如果不指定目录,则存放在系统的临时文件夹下。
• 除mkdirs方法外,以上方法在创建文件和目录时,必须保证目标文件不存在,而且父目录存在,否则会创建失败。
实现步骤
(1)新建一个类名为TextCreateFileAndDir.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; import java.io.IOException; //引入类 public class TextCreateFileAndDir { //创建新文件和目录 public static boolean createFile(String filePath) { //创建单个文件 File file = new File(filePath); if (file.exists()) { //判断文件是否存在 System.out.println("目标文件已存在"+filePath); return false; } if (filePath.endsWith(File.separator)) { //判断文件是否为目录 System.out.println("目标文件不能为目录!"); return false; } if (!file.getParentFile().exists()) { //判断目标文件所在的目录是否存在 //如果目标文件所在的文件夹不存在,则创建父文件夹 System.out.println("目标文件所在目录不存在,准备创建它!"); if (!file.getParentFile().mkdirs()) { //判断创建目录是否成功 System.out.println("创建目标文件所在的目录失败!"); return false; } } try { if (file.createNewFile()) { //创建目标文件 System.out.println("创建文件成功:"+filePath); return true; } else { System.out.println("创建文件失败!"); return false; } } catch (IOException e) { //捕获异常 e.printStackTrace(); System.out .println("创建文件失败!" + e.getMessage()); return false; } } public static boolean createDir(String destDirName) {//创建目录 File dir = new File(destDirName); if (dir.exists()) { //判断目录是否存在 System.out.println("创建目录失败,目标目录已存在!"); return false; } if (!destDirName.endsWith(File.separator)) { //结尾是否以"/"结束 destDirName = destDirName + File.separator; } if (dir.mkdirs()) { //创建目标目录 System.out.println("创建目录成功!"+destDirName); return true; } else { System.out.println("创建目录失败!"); return false; } } public static String createTempFile(String prefix, String suffix, String dirName) { //创建临时文件 File tempFile = null; if (dirName == null) { //目录如果为空 try { tempFile = File.createTempFile(prefix, suffix);//在默认文件夹下创建临时文件 return tempFile.getCanonicalPath(); //返回临时文件的路径 } catch (IOException e) { //捕获异常 e.printStackTrace(); System.out.println("创建临时文件失败:" + e.getMessage()); return null; } } else { //指定目录存在 File dir = new File(dirName); //创建目录 if (!dir.exists()) { //如果目录不存在则创建目录 if (TextCreateFileAndDir.createDir(dirName)) { System.out.println("创建临时文件失败,不能创建临时文件所在的目录!"); return null; } } try { tempFile = File.createTempFile(prefix, suffix, dir);//在目录下创建临时文件 return tempFile.getCanonicalPath(); //返回临时文件的路径 } catch (IOException e) { //捕获异常 e.printStackTrace(); System.out.println("创建临时文件失败!" + e.getMessage()); return null; } } } public static void main(String[] args) { //Java程序的主入口处 String dirName = "E:/createFile/"; //创建目录 TextCreateFileAndDir.createDir(dirName); //调用方法创建目录 String fileName = dirName + "/file1.txt"; //创建文件 TextCreateFileAndDir.createFile(fileName); //调用方法创建文件 String prefix = "temp"; //创建临时文件 String surfix = ".txt"; //后缀 for (int i = 0; i < 10; i++) { //循环创建多个文件 System.out.println("创建临时文件: " //调用方法创建临时文件 + TextCreateFileAndDir.createTempFile(prefix, surfix, dirName)); } } }
(3)运行结果如下所示:
创建目录成功!E:/createFile/\ 创建文件成功:E:/createFile//file1.txt 创建临时文件: E:\createFile\temp18113.txt 创建临时文件: E:\createFile\temp18114.txt 创建临时文件: E:\createFile\temp18115.txt 创建临时文件: E:\createFile\temp18116.txt 创建临时文件: E:\createFile\temp18117.txt 创建临时文件: E:\createFile\temp18118.txt 创建临时文件: E:\createFile\temp18119.txt 创建临时文件: E:\createFile\temp18120.txt 创建临时文件: E:\createFile\temp18121.txt 创建临时文件: E:\createFile\temp18122.txt
源程序解读
(1)createFile方法创建一个新的文件。首先通过File的exists方法和isDirectory方法判断目标文件是否存在,若文件存在,则返回false,新建文件失败。如果目标文件不存在,通过File的getParentFile方法获得目标文件的父目录,如果父目录不存在,调用File的mkdirs方法创建父目录(如果父目录的父目录也不存在,则会一起创建),此时能确定目标文件不存在,而且父目录已经存在,使用File的createNewFile方法便能成功地创建一个新的空文件。
(2)createDir方法创建一个新目录。当目标文件夹不存在时,直接调用File的mkdirs创建目录即可。
(3)createTempFile方法创建新的临时文件。用户可以指定临时文件的文件名前缀和后缀,以及临时文件所在的目录。当指定的文件名后缀为null时,将使用默认的文件名后缀“.tmp”;当指定的目录为null时,临时文件存放在系统的默认临时文件夹下。首先通过createDir方法创建临时文件所在的目录,然后使用File的createTempFile方法创建临时文件。
实例79 查找文件
在文件系统中存在很多文件夹和文件,为了快速地从一个文件结构中找出指定类型的所有文件,我们使用文件类型的过滤器,方便对文件夹或文件进行过滤和筛选。
技术要点
运用文件过滤器对文件进行查找的技术要点如下:
文件类型过滤器:FileFilter,在文件拷贝、移动、删除和压缩时,指定多个文件类型、修改时间、大小限制、覆盖条件、是否包含子目录等条件进行过滤。文件备份、中间文件清理等时非常便利。便利的履历管理功能,重复相同的操作时非常简便。操作前检索满足条件的文件,并估计所需容量,操作完成后可查看记录,完成情况一目了然。
实现步骤
(1)创建一个类名为TextSearchFile.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.FileFilter; import java.util.ArrayList; import java.util.List; public class TextSearchFile { //操作查找文件的类 static int countFiles = 0; //声明统计文件个数的变量 static int countFolders = 0; //声明统计文件夹的变量 public static File[] searchFile(File folder, final String keyWord) { //递归查找包含关键字的文件 File[] subFolders = folder.listFiles(new FileFilter(){//运用内部匿名类获得文件 public boolean accept(File pathname){ //实现FileFilter类accept方法 if (pathname.isFile()) //如果是文件 countFiles++; else //如果是目录 countFolders++; if (pathname.isDirectory() || (pathname.isFile() && pathname.getName().contains( keyWord))) //目录或文件包含关键字 return true; return false; } }); List result = new ArrayList(); //声明一个集合 for (int i = 0; i < subFolders.length; i++){ //循环显示文件夹或文件 //如果是文件则将文件添加到结果列表中 if (subFolders[i].isFile()) { result.add(subFolders[i]); } else { //如果是文件夹,则递归调用本方法,然后把所有的文件加到结果列表中 File[] foldResult = searchFile(subFolders[i], keyWord); for (int j = 0; j < foldResult.length; j++){ //循环显示文件 result.add(foldResult[j]); //文件保存到集合中 } } } //声明文件数组,长度为集合的长度 File files[] = new File[result.size()]; result.toArray(files); //集合数组化 return files; } public static void main(String[] args) { //Java程序的主入口处 File folder = new File("E:/createFile"); //默认目录 String keyword = "temp"; if (!folder.exists()) { //如果文件夹不存在 System.out.println("目录不存在:" + folder.getAbsolutePath()); return; } File[] result = searchFile(folder, keyword); //调用方法获得文件数组 System.out.println("在" + folder + "以及所有子文件时查找对象" + keyword); System.out.println("查找了" + countFiles + "个文件," + countFolders + "个文件夹,共找到" + result.length + "个符合条件的文件:"); for (int i = 0; i < result.length; i++) { //循环显示文件 File file = result[i]; System.out.println(file.getAbsolutePath() + " "); //显示文件绝对路径 } } }
(3)运行结果如下所示:
在 E:\createFile 以及所有子文件时查找对象temp 查找了14 文件,2个文件夹,共找到13个符合条件的文件: E:\createFile\temp\temp1\temp18120.txt E:\createFile\temp\temp1\temp18121.txt E:\createFile\temp\temp1\temp18122.txt E:\createFile\temp18113.txt E:\createFile\temp18114.txt E:\createFile\temp18115.txt E:\createFile\temp18116.txt E:\createFile\temp18117.txt E:\createFile\temp18118.txt E:\createFile\temp18119.txt E:\createFile\temp18120.txt E:\createFile\temp18121.txt E:\createFile\temp18122.txt
源程序解读
(1)searchFile()方法运用内部匿名类获得文件集。在内部匿名类中,必须实现FileFilter接口的accept()方法,此方法主要统计目录中文件夹与文件的个数,并且搜索文件夹或文件中是否包含指定的关键字。执行完accept方法后获得包含关键字的文件夹和文件。再接着循环遍历文件集,文件集中文件直接存入声明的集合中,文件集中的文件夹则需要递归调用searchFile()方法,将文件夹中的文件遍历保存在集合中。再声明一个新的文件集,将集合中的元素数组化保存到新的文件集中。
(2)程序的main()方法声明一个默认目录和关键字,用来在默认目录中查找包含关键字的文件夹或文件。调用searchFile()方法获得指定的文件集。循环遍历将文件集中的文件的绝对路径输出到控制台。
实例80 删除文件夹和文件
本实例介绍如何验证传入路径是否正确、如何删除系统上的文件或文件夹,包括删除文件夹下的所有文件。
技术要点
删除文件与文件夹的技术要点如下:
• File的delete()方法删除文件或文件夹。
• 当File指向一个文件夹时,必须保证文件夹下面的子文件或子目录为空,才能用delete()方法将这个文件夹删除。
实现步骤
(1)创建一个类名为TextDeleteFileAndDir.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 public class TextDeleteFileAndDir { //操作删除文件夹和文件的类 //验证字符串是否为正确路径名的正则表达式 private static String matches = "[A-Za-z]:\\\\[^:?\"><*]*"; //通过sPath.matches(matches)方法的返回值判断是否正确 //sPath为路径字符串 boolean flag = false; File file; //根据路径删除指定目录或文件,无论存在与否 public boolean DeleteFolder(String deletePath) { flag = false; if (deletePath.matches(matches)) { file = new File(deletePath); if (!file.exists()) { //判断目录或文件是否存在 return flag; //不存在返回false } else { if (file.isFile()) { //判断是否为文件 return deleteFile(deletePath); //为文件时调用删除文件方法 } else { return deleteDirectory(deletePath);//为目录时调用删除目录方法 } } }else{ System.out.println("要传入正确路径!"); return false; } } public boolean deleteFile(String filePath) { //删除单个文件 flag = false; file = new File(filePath); if (file.isFile() && file.exists()) { //路径为文件且不为空则进行删除 file.delete(); //文件删除 flag = true; } return flag; } //删除目录(文件夹)以及目录下的文件 public boolean deleteDirectory(String dirPath) { //如果sPath不以文件分隔符结尾,自动添加文件分隔符 if (!dirPath.endsWith(File.separator)) { dirPath = dirPath + File.separator; } File dirFile = new File(dirPath); //如果dir对应的文件不存在,或者不是一个目录,则退出 if (!dirFile.exists() || !dirFile.isDirectory()) { return false; } flag = true; File[] files = dirFile.listFiles(); //获得传入路径下的所有文件 //遍历删除文件夹下的所有文件(包括子目录) for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { //删除子文件 flag = deleteFile(files[i].getAbsolutePath()); System.out.println(files[i].getAbsolutePath() + " 删除成功"); if (!flag) break; //如果删除失败,则跳出 } else { //运用递归,删除子目录 flag = deleteDirectory(files[i].getAbsolutePath()); if (!flag) break; //如果删除失败,则跳出 } } if (!flag) return false; if (dirFile.delete()) { //删除当前目录 return true; } else { return false; } } public static void main(String[] args) { //Java程序主入口处 TextDeleteFileAndDir td = new TextDeleteFileAndDir(); //创建对象实例 String path = "E:/createFile"; //声明目录路径 String filePath = "E:/createFile/temp18120.txt"; //单个文件 System.out.println("1.删除单个文件:"); boolean result = td.deleteFile(filePath); //删除单个文件 if (result) System.out.println(filePath + " 文件删除成功 ? " + result); System.out.println("2.删除目录以及目录下的文件:"); result = td.DeleteFolder(path); //删除目录路径 System.out.println(" 全部删除成功?" + result); } }
(3)运行结果如下所示:
1.删除单个文件: E:/createFile/temp18120.txt 文件删除成功 ? true 2.删除目录以及目录下的文件: E:\createFile\file1.txt 删除成功 E:\createFile\temp\temp1\temp18121.txt 删除成功 E:\createFile\temp\temp1\temp18122.txt 删除成功 E:\createFile\temp18113.txt 删除成功 E:\createFile\temp18114.txt 删除成功 E:\createFile\temp18115.txt 删除成功 E:\createFile\temp18116.txt 删除成功 E:\createFile\temp18117.txt 删除成功 E:\createFile\temp18118.txt 删除成功 E:\createFile\temp18119.txt 删除成功 E:\createFile\temp18120.txt 删除成功 E:\createFile\temp18121.txt 删除成功 E:\createFile\temp18122.txt 删除成功 全部删除成功?true
源程序解读
(1)deleteFolder()方法中,使用正则表达式验证传入路径是否正确。如果路径指向单个文件,则调用deleteFile()方法删除文件;如果路径指向目录,则调用删除目录方法deleteDirectory()删除目录。
(2)deleteFile()方法删除单个文件。直接调用File对象的delete方法,能够删除指定路径下的文件。
(3)deleteDirectory()方法删除指定目录,包括该目录下的文件和目录。方法中先判断目录是否是以文件分隔符结尾,将传入目录下的所有文件存入文件集中。循环显示文件集下的文件和文件夹。如果是文件,则直接删除其绝对路径;如果是文件夹,则运用递归的方法调用deleteDirectory()方法进行遍历,直到找到文件夹下的文件并将其删除。最后将没有文件和文件夹的空目录删除。
实例81 文件复制与移动
本实例介绍文件和目录的复制与移动。包括单个文件的移动复制、复制目录到指定目录、连同目录下的子目录一起复制、将目录以及目录下的文件和子目录全部复制。
技术要点
文件复制与移动的技术要点如下:
• FileInputStream类是文件输入流,根据文件路径可以构造一个FileInputStream对象。
• FileInputStream的read实例方法从文件输入流中读取数据,即读取文件内容。
• FileOutputStream类是文件输出流,根据文件路径可以构造一个FileOutputStream对象。
• FileOutputStream的write实例方法将数据写入到输出流中,即往文件中写内容。
• 读写文件结束后,需要关闭文件输入、输出流。
实现步骤
(1)创建一个类名为TextCopyFileAndMove.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class TextCopyFileAndMove { //实现文件的简单处理,复制和移动文件、目录等 //移动指定文件夹内的全部文件 public static void fileMove(String from, String to) throws Exception { try { File dir = new File(from); File[] files = dir.listFiles(); //将文件或文件夹放入文件集 if (files == null) //判断文件集是否为空 return; File moveDir = new File(to); //创建目标目录 if (!moveDir.exists()) { //判断目标目录是否存在 moveDir.mkdirs(); //不存在则创建 } for (int i = 0; i < files.length; i++) { //遍历文件集 //如果是文件夹或目录,则递归调用fileMove方法,直到获得目录下的文件 if (files[i].isDirectory()) { fileMove(files[i].getPath(), to + "\\" + files[i].getName()); //递归移动文件 files[i].delete(); //删除文件所在原目录 } //将文件目录放入移动后的目录 File moveFile = new File(moveDir.getPath() + "\\" + files[i].getName()); if (moveFile.exists()) { //目标文件夹下存在的话,删除 moveFile.delete(); } files[i].renameTo(moveFile); //移动文件 System.out.println(files[i]+" 移动成功"); } } catch (Exception e) { throw e; } } //复制目录下的文件(不包括该目录)到指定目录,会连同子目录一起复制过去 public static void copyFileFromDir(String toPath, String fromPath) { File file = new File(fromPath); createFile(toPath, false); //true表示创建文件,false表示创建目录 if (file.isDirectory()) { //如果是目录 copyFileToDir(toPath, listFile(file)); } } //复制目录到指定目录,将目录以及目录下的文件和子目录全部复制到目标目录 public static void copyDir(String toPath, String fromPath) { File targetFile = new File(toPath); //创建文件 createFile(targetFile, false); //创建目录 File file = new File(fromPath); //创建文件 if (targetFile.isDirectory() && file.isDirectory()){//如果传入是目录 copyFileToDir(targetFile.getAbsolutePath() + "/" + file.getName(), listFile(file)); //复制文件到指定目录 } } //复制一组文件到指定目录。targetDir是目标目录,filePath是需要复制的文件路径 public static void copyFileToDir(String toDir, String[] filePath) { if (toDir == null || "".equals(toDir)) {//目录路径为空 System.out.println("参数错误,目标路径不能为空"); return; } File targetFile = new File(toDir); if (!targetFile.exists()) { //如果指定目录不存在 targetFile.mkdir(); //新建目录 } else { if (!targetFile.isDirectory()) { //如果不是目录 System.out.println("参数错误,目标路径指向的不是一个目录!"); return; } } for (int i = 0; i < filePath.length; i++) { //遍历需要复制的文件路径 File file = new File(filePath[i]); //创建文件 if (file.isDirectory()) { //判断是否是目录 //递归调用方法获得目录下的文件 copyFileToDir(toDir + "/" + file.getName(), listFile(file)); System.out.println("复制文件 "+file); } else { copyFileToDir(toDir, file, ""); //文件到指定目录 } } } //复制文件到指定目录 public static void copyFileToDir(String toDir, File file, String newName) { String newFile = ""; if (newName != null && !"".equals(newName)) { newFile = toDir + "/" + newName; } else { newFile = toDir + "/" + file.getName(); } File tFile = new File(newFile); copyFile(tFile, file); //调用方法复制文件 } public static void copyFile(File toFile, File fromFile) { //复制文件 if (toFile.exists()) { //判断目标目录中文件是否存在 System.out.println("文件" + toFile.getAbsolutePath()+"已经存在,跳过该文件!"); return; } else { createFile(toFile, true); //创建文件 } System.out.println("复制文件" + fromFile.getAbsolutePath() + "到" + toFile.getAbsolutePath()); try { //创建文件输入流 InputStream is = new FileInputStream(fromFile); FileOutputStream fos = new FileOutputStream(toFile); //文件输出流 byte[] buffer = new byte[1024]; //字节数组 while (is.read(buffer) != -1) { //将文件内容写到文件中 fos.write(buffer); } is.close(); //输入流关闭 fos.close(); //输出流关闭 } catch (FileNotFoundException e) { //捕获文件不存在异常 e.printStackTrace(); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } public static String[] listFile(File dir) { //获取文件绝对路径 String absolutPath = dir.getAbsolutePath(); //获得文件目录的绝对路径 String[] paths = dir.list(); //文件名数组 String[] files = new String[paths.length]; //声明字符串数组,长为传入文件的个数 for (int i = 0; i < paths.length; i++) { //遍历显示文件绝对路径 files[i] = absolutPath + "/" + paths[i]; } return files; } public static void createFile(String path, boolean isFile) {//创建文件或目录 createFile(new File(path), isFile); //调用方法创建新文件或目录 } public static void createFile(File file, boolean isFile) { //创建文件 if (!file.exists()) { //如果文件不存在 if (!file.getParentFile().exists()) { //如果文件父目录不存在 createFile(file.getParentFile(), false); } else { //存在文件父目录 if (isFile) { //创建文件 try { file.createNewFile(); //创建新文件 } catch (IOException e) { e.printStackTrace(); } } else { file.mkdir(); //创建目录 } } } } public static void main(String[] args) { //Java程序主入口处 String fromPath = "E:/createFile"; //目录路径 String toPath = "F:/createFile"; //源路径 System.out.println("1.移动文件:从路径" + fromPath +"移动到路径 "+ toPath); try { fileMove(fromPath, toPath); //调用方法实现文件的移动 } catch (Exception e) { System.out.println("移动文件出现问题" + e.getMessage()); } System.out.println("2.复制目录"+toPath+"下的文件(不包括该目录)到指定目录"+ fromPath + ",会连同子目录一起复制过去。"); copyFileFromDir(fromPath, toPath); //调用方法实现目录复制 System.out.println("3.复制目录 " + fromPath + "到指定目录 " + toPath + " ,将目录以及目录下的文件和子目录全部复制到目标目录"); //调用方法实现目录以及目录下的文件和子目录全部复制 copyDir(toPath, fromPath); } }
(3)运行结果如下所示:
1.移动文件:从路径 E:/createFile 移动到路径 F:/createFile E:\createFile\file1.txt 移动成功 E:\createFile\temp1\file1.txt 移动成功 E:\createFile\temp1 移动成功 E:\createFile\temp26661.txt 移动成功 E:\createFile\temp26662.txt 移动成功 ...... 2.复制目录 F:/createFile 下的文件(不包括该目录)到指定目录E:/createFile,会连同子目录一起复制过去。 复制文件F:\createFile\createFile\file1.txt到E:\createFile\createFile\file1.txt 复制文件F:\createFile\createFile\temp1\file1.txt到E:\createFile\createFile\temp1\file1.txt 复制文件 F:\createFile\createFile\temp1 复制文件F:\createFile\createFile\temp26661.txt到E:\createFile\createFile\temp26661.txt ...... 3.复制目录 E:/createFile到指定目录 F:/createFile ,将目录以及目录下的文件和子目录全部复制到目标目录 复制文件E:\createFile\createFile\file1.txt到F:\createFile\createFile\createFile\file1.txt 复制文件 E:\createFile\createFile\temp1 复制文件 E:\createFile\createFile 文件F:\createFile\createFile\file1.txt已经存在,跳过该文件! 文件F:\createFile\createFile\temp1\file1.txt已经存在,跳过该文件! 文件F:\createFile\createFile\temp26661.txt已经存在,跳过该文件! ......
源程序解读
(1)TextCopyFileAndMove类的fileMove()方法移动指定文件夹内的全部文件。根据传入的路径创建文件目录,根据listFiles()方法获得目录中的文件与子文件夹。创建目标目录并判断目标是否存在。利用循环将目录中的文件移动到指定的目录,如果循环得到的是文件夹运用递归获得该文件夹中的文件,再删除文件所在的原目录,将获得的文件放入指定的目录。如果目标文件夹存在则删除该文件夹。将获得的文件根据renameTo()方法移动到指定的文件夹。
(2)copyFileFromDir()方法复制目录下的文件到指定的目录,会连同子目录一起复制过去,其中不包括该目录。根据指定目录创建文件对象,调用createFile()方法创建文件对象,由于传递的第二个参数为false则是创建目录,如果参数为true,则是创建文件。根据目标路径创建文件对象。如果创建的都是目录,则调用copyFileToDir()方法将指定目录中的文件复制到目标目录中。
(3)copyFileToDir()方法复制一组文件到指定目录。判断指定目录是否为空,如果为空则返回。根据目标路径创建文件对象,如果该文件对象不存在,则新建该目录对象,否则判断文件对象是否是目录,如果不是目录则返回。运用循环遍历需要复制的文件路径,根据路径创建相应的文件对象,如果该文件对象是目录,则调用copyFileDir()方法通过listFile()方法获得目录中的文件并将文件复制到指定目录中,如果是文件则直接将文件复制到目录中。
(4)copyFile()方法是复制文件到指定的目录中。File类的exists()方法判断指定文件是否存在,如果存在则返回,否则调用createFile()方法创建文件。根据传入的目标文件创建输入流,再根据流对象创建文件输出流,创建字节数组用来存储流读取的数据。根据读取的数据不为空进行循环,利用文件输出流的write()方法将目标文件的内容写入到指定文件中,读取完毕后释放相关的流资源。
(5)listFile()方法获得指定目录下的文件,并将文件的绝对路径放入字符串数组中返回。文件对象的list()方法获得目录中的文件,运用循环将目录中的文件的绝对路径放在字符串数组中返回。
(6)createFile()方法判断文件是否存在,如果不存在则判断该文件的上级目录是否存在,如果不存在则调用createFile()方法创建该目录,否则判断上级目录是否是文件,如果是文件则创建新文件,否则创建目录。
实例82 多种方式读取文件内容
本实例介绍Java多种方式读文件,包括按字节读取文件内容、按字符读取文件内容、按行读取文件内容。
技术要点
多种方式读取文件内容的技术要点如下:
• 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
• 以字符为单位读取文件,常用于读文本、数字等类型的文件。要得到标准输入的字节流,然后通过桥接转换为字符流,再经过缓冲得到带缓冲的标准输入流。
• 以行为单位读取文件,常用于读面向行的格式化文件。
实现步骤
(1)创建一个类名为TextReadFile.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.BufferedReader; //引入类 import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.io.Reader; public class TextReadFile { //操作运用多种方式读取文件的类 public static void readFileByBytes(String fileName){ //以字节为单位读取文件 File file = new File(fileName); //创建文件 InputStream in = null; try { System.out.println("①以字节为单位读取文件内容,一次读一个字节:"); in = new FileInputStream(file); //将文件放入文件输入流中 int tempbyte; while ((tempbyte = in.read()) != -1){ //一次读一个字节,循环将内容读出来 System.out.write(tempbyte); } in.close(); //关闭文件输入流 } catch (IOException e) { //捕获异常 e.printStackTrace(); return; } try { System.out.println("②以字节为单位读取文件内容,一次读多个字节:"); //一次读多个字节 byte[] tempbytes = new byte[100]; //声明长度为100的字节数组 int byteread = 0; in = new FileInputStream(fileName); //将文件放入文件输入流中 TextReadFile.showAvailableBytes(in); //显示输入流中还剩的字节数 //读入多个字节到字节数组中,byteread为一次读入的字节数 while((byteread = in.read(tempbytes))!= -1){//一次读多个字节,循环将内容读出来 System.out.write(tempbytes, 0, byteread); } } catch (Exception e1) { //捕获异常 e1.printStackTrace(); } finally {//内容总执行 if (in != null) { try { in.close(); //确保文件输入流关闭 } catch (IOException e1) { } } } } public static void readFileByChars(String fileName){//以字符为单位读取文件 File file = new File(fileName); //创建文件 Reader read = null; try { System.out.println("①以字符为单位读取文件内容,一次读一个字节:");
//一次读一个字符 read = new InputStreamReader(new FileInputStream(file)); int tempchar; while ((tempchar = read.read()) != -1) { //在Windows下,rn这两个字符在一起时,表示一个换行。 //但如果这两个字符分开显示,会换两次行。 //因此,屏蔽掉r,或者屏蔽n。否则,将会多出很多空行。 if (((char) tempchar) != 'r') { //只要是不换行就读取 System.out.print((char) tempchar); } } read.close(); } catch (Exception e) { e.printStackTrace(); } try { System.out.println("②以字符为单位读取文件内容,一次读多个字节:"); char[] tempchars = new char[30]; int charread = 0; read = new InputStreamReader(new FileInputStream(fileName));//创建文件读入流 while ((charread = read.read(tempchars)) != -1) { //一次读多个字符 //同样屏蔽掉r不显示 if ((charread == tempchars.length) && (tempchars[tempchars.length - 1] != 'r')) { System.out.print(tempchars); } else { for (int i = 0; i < charread; i++) { if (tempchars[i] == 'r') { //停止执行当前的迭代,然后退回循环开始处 continue; } else { System.out.print(tempchars[i]); } } } } } catch (Exception e1) { //捕获异常 e1.printStackTrace(); } finally {//内容总执行 if (read != null) { try { read.close(); //确保关闭 } catch (IOException e1) { } } } } public static void readFileByLines(String fileName){ //以行为单位读取文件 File file = new File(fileName); BufferedReader reader = null; //创建缓存读取 try { System.out.println("以行为单位读取文件内容,一次读一整行:"); reader = new BufferedReader(new FileReader(file));//将文件放在缓存读取中 String tempString = null; int line = 1; //一次读入一行,直到读入null为文件结束 while ((tempString = reader.readLine()) != null){ //显示行号 System.out.println("line " + line + ": " + tempString); line++; } reader.close(); } catch (IOException e) { //捕获异常 e.printStackTrace(); } finally { //内容总执行 if (reader != null) { try { reader.close(); //关闭缓存读取 } catch (IOException e1) { } } } } private static void showAvailableBytes(InputStream in){ //显示输入流中还剩的字节数 try { System.out.println("当前字节输入流中的字节数为:" + in.available()); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } public static void main(String[] args) { //Java程序主入口处 String fileName = "F:/poem.txt"; System.out.println("1.按字节为单位读取文件:"); readFileByBytes(fileName); System.out.println("2.按字符为单位读取文件:"); readFileByChars(fileName); System.out.println("3.按行为单位读取文件:"); readFileByLines(fileName); } }
(3)运行结果如下所示:
1.按字节为单位读取文件: ①以字节为单位读取文件内容,一次读一个字节: 枫桥夜泊 张继 月落乌啼霜满天, 江枫渔火对愁眠。 姑苏城外寒山寺, 夜半钟声到客船。 ②以字节为单位读取文件内容,一次读多个字节: 当前字节输入流中的字节数为:107 枫桥夜泊 张继 月落乌啼霜满天, 江枫渔火对愁眠。 姑苏城外寒山寺, 夜半钟声到客船。 2.按字符为单位读取文件: ①以字符为单位读取文件内容,一次读一个字节: 枫桥夜泊 张继 月落乌啼霜满天, 江枫渔火对愁眠。 姑苏城外寒山寺, 夜半钟声到客船。 ②以字符为单位读取文件内容,一次读多个字节: 枫桥夜泊 张继 月落乌啼霜满天, 江枫渔火对愁眠。 姑苏城外寒山寺, 夜半钟声到客船。 3.按行为单位读取文件: 以行为单位读取文件内容,一次读一整行: line 1: 枫桥夜泊 line 2: 张继 line 3: 月落乌啼霜满天, line 4: 江枫渔火对愁眠。 line 5: 姑苏城外寒山寺, line 6: 夜半钟声到客船。
源程序解读
(1)readFileByBytes()方法中,实现一次读一个字节的方式和一次读多个字节的方式读取文件内容。在一次读一个字节的方式中,read()方法返回一个0~255之间的整数或-1。如果返回-1,则代表遇到流的结束。在一次读多个字节的方式中,read(byte[])是一次读入一定长度的数据放到缓冲区中,返回的是读入到缓冲区中的字节数或-1。读出时也是按一次显示多个字节输出。
(2)readFileByChars()方法中,InputStreamReader类是从字节流到字符流的桥梁:它读入字节,并根据指定的编码方式,将其转换为字符流。如果没有指定编码方式,平台可接受默认编码方式,默认的编码方式是GBK。可以根据new InputStreamReader(new FileInputStream (file),“编码方式”)进行设置。方法中屏蔽r元素是为了防止有回车。
(3)readFileByLines()方法中,声明缓存流BufferedReader,可以提高字符流处理的效率和速度,使用readLine()方法可以一行一行地读取文本,当遇到null时读取文件结束。
(4)showAvailableBytes()方法中的输入流InputStream方法available()用来获得流中的字节数,但一定要在调用read()方法至少一次之后使用,不然就只能返回零值。
实例83 多种方式写文件
本实例介绍用Java程序写文件,能写文本文件、二进制文件,采用多种写文件的方式:按字节写、按字符写和按行写。
技术要点
用Java写文件有多种方法,对于不同类型的数据,有不同的写入方法的技术要点如下:
• FileOutputStream打开文件输出流,通过write方法以字节为单位写入文件,是写文件最通用的方法,能写入任何类型的文件,特别适合写二进制数据文件。
• OutputStreamWriter打开文件输出流,通过write方法以字符为单位写入文件,能够将字符数组和字符串写入文件。
• PrintWriter打开文件输出流,通过print和println方法写字符串到文件,与System.out的用法相似,常用于写入格式化的文本。
• 当文件写完后关闭输出流。
实现步骤
(1)新建一个类名为TextWriteFile.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; public class TextWriteFile { //操作多种方式写文件的类 public static void writeFileByBytes(String fileName) {//以字节为单位写文件 File file = new File(fileName); //创建一个文件 OutputStream out= null; try { out = new FileOutputStream(file); //打开文件输出流 String content = "枫桥夜泊\n张继\n月落乌啼霜满天,\n"+ "江枫渔火对愁眠。\n姑苏城外寒山寺,\n夜半钟声到客船"; byte[] bytes = content.getBytes(); //读取输出流中的字节 out.write(bytes); //写入文件 System.out.println("写文件" + file.getAbsolutePath() + "成功!"); } catch (IOException e){ System.out.println("写文件" + file.getAbsolutePath() + "失败!"); e.printStackTrace(); } finally { //内容总执行 if (out != null){ try { out.close(); //关闭输出文件流 } catch (IOException e1) { } } } } public static void write FileByChars(String fileName){//以字符为单位写文件 File file = new File(fileName); //创建文件 Writer writer = null; try { writer = new OutputStreamWriter( new FileOutputStream(file)); //打开文件输出流 String content = "枫桥夜泊\n张继\n月落乌啼霜满天,\n"+ "江枫渔火对愁眠。\n姑苏城外寒山寺,\n夜半钟声到客船"; writer.write(content); //写入文件 System.out.println("写文件" + file.getAbsolutePath() + "成功!"); } catch (IOException e){ System.out.println("写文件" + file.getAbsolutePath() + "失败!"); e.printStackTrace(); } finally { //内容总执行 if (writer != null){ try { writer.close(); //关闭输出文件流 } catch (IOException e1) { } } } } public static void writeFileByLines(String fileName){ //以行为单位写文件 File file = new File(fileName); //创建文件 PrintWriter writer = null; try { writer = new PrintWriter( new FileOutputStream(file)); //打开文件输出流 writer.println("枫桥夜泊"); //写字符串 writer.print(true); //写入布尔类型 writer.print(155); //写入整数类型 writer.println(); //换行 writer.flush(); //写入刷新文件 System.out.println("写文件" + file.getAbsolutePath() + "成功!"); } catch (FileNotFoundException e) { System.out.println("写文件" + file.getAbsolutePath() + "失败!"); e.printStackTrace(); } finally { //内容总执行 if (writer != null){ writer.close(); //关闭输出文件流 } } } public static void main(String[] args) { //Java程序主入口处 String fileName = "F:/poem.txt"; System.out.println("1.以字节为单位写文件:"); TextWriteFile.writeFileByBytes(fileName); //调用方法写文件 fileName = "F:/poem1.txt"; System.out.println("2.以字符为单位写文件:"); TextWriteFile.writeFileByChars(fileName); //调用方法写文件 fileName = "F:/poem2.txt"; System.out.println("3.以行为单位写文件:"); TextWriteFile.writeFileByLines(fileName); //调用方法写文件 } }
(3)运行结果如下所示:
1.以字节为单位写文件: 写文件F:\poem.txt成功! 2.以字符为单位写文件: 写文件F:\poem1.txt成功! 3.以行为单位写文件: 写文件F:\poem2.txt成功!
源程序解读
(1)writeFileByBytes()方法以字节为单位将内容写到文件中。通过FileOutputStream的write()方法将指定数组字节写入缓冲的输出流中。
(2)writeFileByChars()方法中字符输出流OutputStreamWriter封装FileOutputStream对象,再调用write()方法将内容写到文件中。
(3)writeFileByLines()方法使用PrintWriter类封装FileOutputStream对象,再调用print()或println()方法将内容写到文件中。print()方法与println()方法的区别在于:println()在写入字符串后进行换行。
实例84 随机访问文件
你经常会发现你只想读取文件的一部分数据,而不需要从头至尾读取整个文件。你可能想访问一个作为数据库的文本文件,此时你会移动到某一条记录并读取它的数据,接着移动到另一个记录,然后再到其他记录—每一条记录都位于文件的不同部分。Java编程语言提供了一个RandomAccessFile类来处理这种类型的输入/输出。
技术要点
访问随机文件的技术要点如下:
• 运用文件名随机访问文件,语法:
myFile=new RandomAccessFile(String name,String mode);
• 运用文件对象随机访问文件,语法:
myFile=new RandomAccessFile(File file,String mode);
其中mode参数决定你对这个文件的存取是只读(r)还是读写(rw)。
实现步骤
(1)新建一个类名为TextRandomAccess.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.IOException; import java.io.RandomAccessFile; import java.util.Scanner; class Student { //创建一个学生对象,用于辅助随机写数据 private String name; //学生名字 private int score; //学生成绩 public Student() { //默认赋值 setName("noname"); } public Student(String name, int score) { //对信息初始化 setName(name); this.score = score; } public void setName(String name) { StringBuilder builder = null; if (name != null) builder = new StringBuilder(name); else //构造一个不带任何字符的字符串生成器,其初始容量由 capacity 参数指定 builder = new StringBuilder(15);//StringBuilder(int capacity) //最长15个字符,1个字符占用2个字节,设置字符序列的长度 builder.setLength(15); this.name = builder.toString(); } public void setScore(int score) { this.score = score; } public String getName() { return name; } public int getScore() { return score; } public static int size() { return 34; } } public class TextRandomAccess { //操作随机访问文件的类 public static void randomAccessByName(String fileName){ //按文件名随机读取文件内容 RandomAccessFile randomFile = null; //创建文件随机访问 try { //打开一个随机访问文件流,按只读方式 randomFile = new RandomAccessFile(fileName, "r"); long fileLength = randomFile.length(); //文件长度,字节数 int beginIndex = (fileLength > 4) ? 14 : 0; //读文件的起始位置 //将读文件的开始位置移到beginIndex位置 randomFile.seek(beginIndex); byte[] bytes = new byte[10]; int byteread = 0; //一次读10个字节,如果文件内容不足10个字节,则读剩下的字节 //将一次读取的字节数赋给byteread while ((byteread = randomFile.read(bytes)) != -1) { System.out.write(bytes, 0, byteread); } } catch (IOException e) { //捕获异常 e.printStackTrace(); } finally { //内容总执行 if (randomFile != null) { try { randomFile.close(); //确保文件随机访问流关闭 } catch (IOException e1) { } } } } private static void randomAccessByFile(File file){ //按文件对象随机读写文件 RandomAccessFile randomFile = null; //创建文件随机访问 Student[] students={ //对学生对象赋值 new Student("Justi",90), new Student("momor",95), new Student("Bush",88), new Student("catepillar",84) }; char[] name = new char[15]; try { //打开一个随机访问文件流,按读/写方式 randomFile = new RandomAccessFile(file, "rw"); for(int i=0;i<students.length;i++){ //循环将信息写入到文件中 randomFile.writeChars(students[i].getName());//writeChars方法写字符串 randomFile.writeInt(students[i].getScore()); //writeInt方法写整型数据 } System.out.println("读取第几个数据?"); Scanner scan=new Scanner(System.in); //从键盘中获得数据 int num=scan.nextInt(); randomFile.seek((num-1)*Student.size()); //操作存取位置 Student student = new Student(); for(int i=0;i<name.length;i++) //循环对数组赋值 name[i]=randomFile.readChar(); student.setName(new String(name).replace('\0', ' ')); //将空字符取代为空格符 student.setScore(randomFile.readInt()); //读取整型数据 System.out.println("姓名:"+student.getName()); //输出显示 System.out.println("分数"+student.getScore()); } catch (Exception e) { //捕获异常 System.out.println("读取数据失败"+e.getMessage()); } } public static void main(String[] args){ //Java程序主入口处 String fileName="F:/poem.txt"; File file=new File("F:/student.data"); System.out.println("1.按文件名随机读取文件内容:"); randomAccessByName(fileName); //调按文件名随机读内容方法 System.out.println(); //回车换行 System.out.println("2.按文件对象读写文件:"); randomAccessByFile(file); //调按文件对象随机读写文件 } }
(3)运行结果如下所示:
1.按文件名随机读取文件内容: 月落乌啼霜满天, 江枫渔火对愁眠。 姑苏城外寒山寺, 夜半钟声到客船。 2.按文件对象读写文件: 读取第几个数据? 2 姓名:momor 分数95
源程序解读
(1)randomAccessByName()方法中,按文件名创建随机访问流对象,参数r是read只读方式,文件位置的指针默认位于文件的开头处。通过seek()方法来设置文件指针位置。通过getFilePointer()方法可以获得文件位置指针的位置。声明长度为10字节的字节数组,循环一次读10个字节,文件内容不足10个字节,则读剩下的字节。一次读取的字节数赋给变量。
(2)randomAccessByFile()方法中,按文件对象创建一个随机访问文件流对象,其中rw是read/write,按读/写方式,使用writeChars()、writeInt()方法将学生对象的信息写到指定文件中。通过读取键盘输入的值设置读到文件的开始位置,循环读取文件的内空并将读到的字符串内容中空字符转化为空格符输出到控制台。
实例85 追加文件内容
将内容添加到文件中,会出现将原有的文件内容覆盖的问题。本实例介绍在文件尾部追加新内容,文件原有的内容不覆盖。
技术要点
在文件尾部追加新内容的技术要点如下:
• 通过RandomAccessFile以读写的方式打开文件输出流,使用它的seek方法可以将读写指针移到文件尾,再使用它的write方法将数据写到读写指针后面,完成文件的追加。
• 通过FileWriter打开文件输出流,构造FileWriter时指定写入模式,是一个布尔值,为true时表示写入的内容添加到已有文件内容的后面,为false时重新写文件,以前的数据被清空,默认为false。
实现步骤
(1)新建一个类名为TextSuperaddition.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.FileWriter; //引入类 import java.io.IOException; import java.io.RandomAccessFile; public class TextSuperaddition { //将内容追加到文件尾部 public static void addFirst(String fileName){//使用RandomAccessFile对象实现内容追加 try { //打开一个随机访问文件流,按读写方式 RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw"); long fileLength = randomFile.length();//获得文件字节数 randomFile.seek(fileLength); //将写文件指针移到文件尾。 String content="\nIf you would know the value of money, go and try to borrow some.\r\n要想知道钱的价值,就想办法去借钱试试。\n"; randomFile.write(content.getBytes("gb2312"));//内容写到文件内容尾部 randomFile.close(); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } public static void addSecond(String fileName){ //使用FileWriter对象实现内容追加 try { //打开写文件器,true表示以追加形式写文件 FileWriter writer = new FileWriter(fileName, true); String content="That man is the richest whose pleasure are the cheapest. \n"+ "能处处寻求快乐的人才是最富有的人。\n"; writer.write(content); //内容写到文件内容尾部 writer.close(); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } public static void main(String[] args) { //Java程序主入口处 String fileName = "F:/poem.txt"; System.out.println("1.RandomAccessFile对象实现内容追加:"); addFirst(fileName); //调用方法1 System.out.println("2.FileWriter对象实现内容追加:"); addSecond(fileName); //调用方法2 System.out.println("追加后文件内容显示:"); TextReadFile.readFileByLines(fileName);//调用TextReadFile类的按行读取文件方法 } }
(3)运行结果如下所示:
1.RandomAccessFile对象实现内容追加: 2.FileWriter对象实现内容追加: 追加后文件内容显示: 以行为单位读取文件内容,一次读一整行: line 1: A contented mind is the greatest blessing a man can enjoy in this world. line 2: 知足是人生在世最大的幸事。 If you would know the value of money, go and try to borrow some. line 3: 要想知道钱的价值,就想办法去借钱试试。 line 4: That man is the richest whose pleasure are the cheapest. line 5: 能处处寻求快乐的人才是最富有的人。
源程序解读
(1)addFirst()方法中运用RandomAccessFile类,实现将内容追加到文件内容尾部。需要注意的是,RandomAccessFile对象的操作模式设置为rw读/写模式,通过seek()方法将读写指针移到文件原有内容的尾部。将新添加的内容编码格式转换后通过write()方法写入到文件中。
(2)addSecond()方法中通过构造FileWriter对象,将第二个参数设置为true,表示可追加文件。如果设置为false,则新内容会将文件原有内容覆盖掉。再通过write()方法,实现将内容追加到文件内容尾部。
实例86 文件锁定
有时候打开一个文件会遇到“该文件已经被另一个程序占用,打开失败”的问题。这是因为另一个程序正在编辑该文件,并且不希望编辑过程中其他程序来修改这个文件,因此进行了文件锁定。本实例讲解如何实现文件的锁定。
技术要点
通过使用特定类进行文件锁定的技术要点如下:
• 通过FileLock类实现文件锁定。文件锁定可以是独占也可以是共享。共享锁定可阻止其他并发运行的程序获取重叠的独占锁定,但是允许该程序获得重叠的共享锁定。独占锁定则阻止其他程序获取任一类型的重叠锁定。一旦释放某个锁定,它就不会再对其他程序所获取的锁定产生任何影响。
• 可以通过锁定的isShared()方法确定是独占还是共享。
实现步骤
(1)新建一个类名为TextLockFile.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.FileOutputStream; //引入类 import java.nio.channels.FileLock; public class TextLockFile { //操作文件锁定的类 public static void fileLock(String file){ //锁定文件 FileOutputStream fous=null; FileLock lock=null; try { fous=new FileOutputStream(file); //创建文件输出流 lock=fous.getChannel().tryLock(); //锁定文件 //本线程锁定1分钟,过后任何程序对该文件的写操作将被禁止 Thread.sleep(60*1000); } catch (Exception e) { //捕获异常 e.printStackTrace(); }finally{ try { if(lock!=null) lock.release(); //锁释放 if(fous!=null) fous.close(); } catch (Exception e) { System.out.println("出现异常"); System.exit(1); } } } public static void main(String []args){ //Java程序的主入口处 String file="F:/poem.txt"; System.out.println("锁定文件:"); fileLock(file); //调用方法锁定文件 } }
(3)运行结果如下所示:
在打开F:/poem.txt文件时进行编辑保存会出现,如图10-1所示提示。
锁定文件:
图10-1 锁定文件
源程序解读
(1)程序中fous.getChannel().tryLock()实现文件的锁定。FileLock.tryLock()实现整个文件的锁定。实际上,也可以实现文件的部分锁定。语法:FileLock.tryLock(long position,long size,Boolean shared)是锁定文件中从position开始的size个字节,shared参数指定是否共享。
(2)程序运行1分钟后对锁定文件写操作被禁止。
实例87 分割与合并文件
应用程序中导入一个大的文件需要很长时间,有时会提示导入失败。本实例将讲解如何将文件分割与合并,便于文件的导入与还原。
技术要点
实现文件的分割与合并的技术要点如下:
• 分割文件时,如果是程序自动拆分为多个文件,那么后缀分别为“.part序号”,这样就可以方便文件合并了。
• 分割文件,指定小文件的长度,即字节数,根据File的length()方法获得大文件的长度,以确定目标小文件的数目。用文件输入流顺序地读取大文件的数据,将数据分流到每个小文件的输出流里。
• 如果指定的小文件比原文件都还要大,为了不动原文件,就生成另一个文件,后缀为“.bat”,这样可以保证原文件。
• 合并时,读取每个小文件的输入流,将所有内容依次按顺序写入到目标大文件的输出流里。
实现步骤
(1)新建一个类名为TextSeparatorFileAndDiv.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.RandomAccessFile; import java.util.Scanner; public class TextSeparatorFileAndDiv{ //操作文件分割的类 String FileName = null; //原文件名 long FileSize = 0; //原文件的大小 long BlockNum = 0; //可分的块数 public TextSeparatorFile() { } private void getFileAttribute(String fileAndPath)//取得原文件的属性 { File file = new File(fileAndPath); //创建文件,fileAndPath包括路径文件名 FileName = file.getName(); //获得文件的名字 FileSize = file.length(); //获得文件大小 } private long getBlockNum(long blockSize) //取得分块数 { long fileSize = FileSize; //赋值 if (fileSize <= blockSize) //如果分块小只够分一个块 return 1; //返回 else { if (fileSize % blockSize > 0) { //判断原文件大小与每一块是否整除 return (fileSize / blockSize) + 1; } else return fileSize / blockSize; } } private String generateSeparatorFileName(String fileAndPath, int currentBlock) //获得拆分后每块的文件名包括路径 { String separatorFile=fileAndPath + ".part" + currentBlock; System.out.println("文件拆分成:"+separatorFile); return separatorFile; //返回拆分的块的文件名 } private boolean writeFile(String fileAndPath, String fileSeparateName, long blockSize, long beginPos) //往硬盘写文件 { RandomAccessFile raf = null; FileOutputStream fos = null; byte[] bt = new byte[1024]; //创建数组存取数据 long writeByte = 0; int len = 0; try { raf = new RandomAccessFile(fileAndPath, "r");//创建随机访问文件对象,只读方式 raf.seek(beginPos); //设置对象开始读取的位置 fos = new FileOutputStream(fileSeparateName);//根据传入文件名创建文件输出流 while ((len = raf.read(bt)) > 0) { //循环读取数据 if (writeByte < blockSize) //如果当前块还没有写满 { writeByte = writeByte + len; if (writeByte <= blockSize) fos.write(bt, 0, len); //如果当前块没有写满 else { //当前块写满 len = len - (int) (writeByte - blockSize); fos.write(bt, 0, len); } } } fos.close(); //关闭文件输出流 raf.close(); //关闭随机访问对象 } catch (Exception e) { //捕获异常 e.printStackTrace(); try { if (fos != null) //确保文件输出流关闭 fos.close(); if (raf != null) //确保随机访问对象关闭 raf.close(); } catch (Exception f) { //捕获异常 f.printStackTrace(); } return false; } return true; } private boolean separatorFile(String fileAndPath, long blockSize)//拆分文件 { getFileAttribute(fileAndPath); //将文件的名及大小属性取出来 BlockNum = getBlockNum(blockSize); //取得分块总数 System.out.println("共拆分成"+BlockNum+"个文件"); if (BlockNum == 1) //如果只能够分一块,就一次性写入 blockSize = FileSize; long writeSize = 0; //每次写入的字节 long writeTotal = 0; //已经写了的字节 String currentNameAndPath = null; for (int i = 1; i <= BlockNum; i++) { if (i < BlockNum) writeSize = blockSize; //取得每次写入文件大小 else writeSize = FileSize - writeTotal; if (BlockNum == 1) currentNameAndPath = fileAndPath + ".bat"; //每个块的后缀加.bat else currentNameAndPath = generateSeparatorFileName(fileAndPath, i); if (!writeFile(fileAndPath, currentNameAndPath, writeSize, writeTotal)) //循环往硬盘写文件 return false; writeTotal = writeTotal + writeSize; } return true; } private String [] separatorFiles(String fileAndPath,long blockSize){//获得拆分文件名 BlockNum = getBlockNum(blockSize); //取得分块总数 if (BlockNum == 1) //如果分一块,就一次性写入 blockSize = FileSize; String []nameAndPath =new String[(int) BlockNum]; String currentName=null; for (int i = 1; i <= BlockNum; i++) { if (BlockNum == 1) currentName = fileAndPath + ".bat"; //每个块的后缀加.bat else currentName = generateSeparatorFileName(fileAndPath, i); nameAndPath[i-1]=currentName; //将文件绝路径放入数组中 } return nameAndPath; } public static String unite(String[] fileNames, String TargetFileName) throws Exception { //合并文件 File inFile = null; File outFile = new File(TargetFileName); //构建文件输出流 FileOutputStream out = new FileOutputStream(outFile); //创建文件输出流 for (int i = 0; i < fileNames.length; i++) { //循环显示文件 inFile = new File(fileNames[i]); //打开文件输入流 FileInputStream in = new FileInputStream(inFile); int c; while ((c = in.read()) != -1) {//从输入流中读取数据,并写入到文件输出流中 out.write(c); } in.close(); } out.close(); return outFile.getAbsolutePath(); //返回合并后文件绝对路径 } public static void main(String[] args) throws Exception { //Java程序主入口处 TextSeparatorFileAndDiv separator = new TextSeparatorFileAndDiv (); System.out.println("1.输入要拆分的文件(包括路径和后缀)"); Scanner scan=new Scanner(System.in); //键盘输入 String fileAndPath=scan.next(); //获得键盘输入值 long blockSize = 20*20; //每一文件块大小按字节计算 if (separator.separatorFile(fileAndPath, blockSize)) { //调用方法进行拆分 System.out.println("文件拆分成功!"); } else { System.out.println("文件拆分失败!"); } System.out.println("2.输入合并后的目标文件(包括路径和后缀)"); String targetName=scan.next(); //获得键盘输入值 String result=unite(separator.separatorFiles(fileAndPath, blockSize),targetName); System.out.println("合并结果:"+result); } }
(3)运行结果如下所示:
输入要拆分的文件(包括路径和后缀) E:/createFile.rar 共拆分成3个文件 文件拆分成:E:/createFile.rar.part1 文件拆分成:E:/createFile.rar.part2 文件拆分成:E:/createFile.rar.part3 文件拆分成功! 输入合并后的目标文件(包括路径和后缀) F:/123.rar 文件拆分成:E:/createFile.rar.part1 文件拆分成:E:/createFile.rar.part2 文件拆分成:E:/createFile.rar.part3 合并结果:F:\123.rar
源程序解读
(1)TextSeparatorFileAndDiv类的getFileAttribute()方法根据路径创建文件对象并获得该文件对象的名称和大小。
(2)getBlockNum()方法根据文件的大小与传入的数值进行比较,如果文件大小大于传入的数值则返回1,否则判断文件大小与传入的数值的余数,如果余数大于0则返回文件大小与传入的数值的整除的值+1,否则返回文件大小与传入的数值的整除的值。
(3)generateSeparatorFileName()方法获得拆分后每块文件名的路径,包括后缀。writeFile()方法以只读的方式创建随机访问对象并设置对象开始读数据的指针。根据传入的路径创建文件输出流,利用读取的数据不为空进行循环,调用文件输出流的write()方法将读取的内容写入硬盘中,读取完毕后释放相关的流资源。
(4)separatorFile()方法调用getFileAttribute()方法获得文件的名称以大小属性信息。调用getBlockNum()方法获得分块总数,如果只能够分一块,则一次性写入。运用循环将文件分为几个块,每个块的后缀为“.bat”,这样可以方便文件的合并,读取文件的内容将内容写入指定的块中。
(5)unite()方法用于将拆分的文件合并。根据目标路径创建文件,再根据该文件创建文件输出流对象,运用循环获得字符串数组中的文件路径并根据此文件路径创建文件对象,再创建相应的文件输入流对象,循环读取文件输入流中的内容,调用文件输出流的write()方法将读取的内容写入到指定文件中,读取完毕后释放相关的流资源,并返回合并后的文件的绝对路径。
实例88 序列化和反序列化
序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。在编程中常用序列化把不能放在虚拟机(JVM)内存的对象暂时持久化到文件中。本实例介绍如何实现对象的序列化,将对象序列化到文件中。
技术要点
本实例通过实现序列化的接口来对对象序列化。技术要点如下:
• 如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
• 类通过实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
• 要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和还原超类型的公用(public)、受保护的(protected)和(如果可访问)包(package)字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此责任。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。
实现步骤
(1)新建一个类名为TextSerializable.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; public class TextSerializable { //序列化和反序列化对象 //必须实现Serializable接口才能够被序列化和反序列化 static class MyClass implements Serializable{ private int a,b; //实例变量会被序列化和反序列化 private transient int c; //transient实例变量不会被序列化 private static int d; //类变量不会被序列化和反序列化 public MyClass(){ } public MyClass(int a, int b, int c, int d){ this.a = a; this.b = b; this.c = c; MyClass.d = d; } public String toString(){ return this.a + " "+ this.b + " "+ this.c + " "+ MyClass.d; } } public static void serialize(String fileName) throws Exception{//序列化对象到文件 //创建一对象输出流,将对象输出到文件 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName)); out.writeObject("Today:"); //序列化一个字符串对象到文件 out.writeObject(new Date()); //序列化当前日期对象到文件 MyClass my1 = new MyClass(5, 6, 7, 8); //序列化一个MyClass对象 out.writeObject(my1); out.close(); } public static void deserialize(String fileName) throws Exception{//从文件反序列化到对象 //创建一个对象输入流,从文件读取对象 ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName)); //注意读对象时必须按照序列化对象时的顺序读,否则会出错 String today = (String)(in.readObject()); //读取字符串对象 System.out.println(today); Date date = (Date)(in.readObject()); //读日期对象 System.out.println(date.toString()); //读MyClass对象,并调用它的add方法 MyClass my1 = (MyClass)(in.readObject()); System.out.println(my1.toString()); in.close(); } public static void main(String[] args) throws Exception{ //Java程序主入口处 String fileName = "F:/poem.txt"; serialize(fileName); //注释掉第二行,只运行下面一行,将会发现输出不同 deserialize(fileName); } }
(3)运行结果如下所示:
Today: Tue Mar 31 14:33:44 CST 2009 5600
源程序解读
(1)MyClass内部类是被序列化的对象,它实现了java.io.Serializable接口。
(2)serialize方法实现了对象的序列化功能,将对象序列化到文件,使用了对象输出流ObjectOutputStream,writeObject方法一次写入一个对象,对象在文件中按写入顺序存储。
(3)deserialize方法实现了对象的反序列化功能,从文件中将对象反序列化到内存,使用了对象输入流ObjectInputStream,readObject方法一次读入一个对象。因为,在序列化对象时是按写入顺序存储的,所以,在反序列化时,必须按照写入的顺序读取对象。
(4)类变量不属于一个对象,因此不能被序列化和反序列化。transient实例变量也不会被序列化和反序列化。
实例89 Zip格式压缩、解压缩文件
压缩文件在实际应用中用途很广泛,其中,zip文件是Windows平台下比较普遍的一种。Java中内置了对Zip压缩包的支持。本实例介绍如何将文件压缩为Zip文件以及将Zip文件解压缩。
技术要点
Zip压缩文件与解压缩文件技术要点如下:
• Java.util.zip包实现了Zip格式相关的类库,使用格式zip格式压缩和解压缩时需要导入该包。
• 使用ZipOutputStream可以实现文件压缩,所有写入到ZipOutputStream输入流中的数据都会被Zip格式压缩。
• 每个被压缩的文件或目录在zip文件中都对应一个ZipEntry对象,每个ZipEntry都有一个name属性,表示它相对于zip文件目录的相对路径。对于目录,路径以“/”结尾;对于文件,路径以文件名结尾。
• ZipOutputStream的putNextEntry()方法往zip文件中添加ZipEntry对象,紧接着写入到该文件ZipOutputStream流中的数据都被保存到ZipEntry中,调用ZipOutputStream的closeEntry()方法。
实现步骤
(1)新建一个类名为TextReadZiP.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; public class TextReadZiP { //用ZIP压缩和解压缩文件或目录 public static void zipFile(String baseDirName, String fileName, String toFileName) { //压缩文件或者目录 if (baseDirName == null) { //检测根目录是否存在 System.out.println("压缩失败,根目录不存在:" + baseDirName); return; } File baseDir = new File(baseDirName); //创建文件 if (!baseDir.exists() || (!baseDir.isDirectory())) {//判断文件是否是目录 System.out.println("压缩失败,根目录不存在:" + baseDirName); return; } String baseDirPath=baseDir.getAbsolutePath();//获得目录绝对路径 File targetFile = new File(toFileName); //目标文件 try { ZipOutputStream out = new ZipOutputStream(new FileOutputStream( targetFile)); //创建一个zip输出流来压缩数据并写入到zip文件 if (fileName.equals("*")) {//如果是*号,则将指定目录下的所有文件压缩到ZIP TextReadZiP.dirToZip(baseDirPath, baseDir, out);//调用方法进行压缩 } else {//压缩指定文件 File file=new File(baseDir,fileName);//创建文件 if (file.isFile()) { //压缩的是否是文件 //压缩文件到ZIP输出流 TextReadZiP.fileToZip(baseDirPath, file, out); } else { //压缩目录到ZIP输出流 TextReadZiP.dirToZip(baseDirPath, file, out); } } out.close(); //ZIP输出流关闭 System.out.println("压缩文件成功,目标文件名:" + toFileName); } catch (IOException e) { //捕获异常 System.out.println("压缩失败:" + e); e.printStackTrace(); } } //解压缩ZIP文件,zipFileName为待解压缩的ZIP文件名 public static void upzipFile(String zipFileName, String targetBaseDirName) { if (!targetBaseDirName.endsWith(File.separator)) { targetBaseDirName += File.separator; } try { ZipFile zipFile = new ZipFile(zipFileName);//根据ZIP文件创建ZipFile对象 ZipEntry entry = null; //ZIP实体 String entryName = null; //ZIP实体名 String targetFileName = null; byte[] buffer = new byte[4096]; //创建复制缓冲区 int bytes_read; Enumeration entrys = zipFile.entries(); //获取ZIP文件里所有的entry while (entrys.hasMoreElements()) { //遍历所有entry entry = (ZipEntry) entrys.nextElement();//依次获得所有entry entryName = entry.getName(); //获得entry的名字 targetFileName = targetBaseDirName + entryName; if (entry.isDirectory()) { //判断是否是目录,如果是 new File(targetFileName).mkdirs();//创建目录 continue; } else { //如果是一个文件,则创建父目录 new File(targetFileName).getParentFile().mkdirs(); } //创建文件 File targetFile = new File(targetFileName); //输出绝对路径 System.out.println("创建文件:" + targetFile.getAbsolutePath()); //打开文件输出流 FileOutputStream os = new FileOutputStream(targetFile); //从ZipFile对象中打开entry的输入流 InputStream is = zipFile.getInputStream(entry); //循环将内容写入文件输出流 while ((bytes_read = is.read(buffer)) != -1) { os.write(buffer, 0, bytes_read); } os.close(); //关闭流 is.close(); } System.out.println("解压缩文件成功!"); } catch (IOException err) { System.err.println("解压缩文件失败: " + err); } } private static void dirToZip(String baseDirPath, File dir, ZipOutputStream out) { //将目录压缩到ZIP输出流 if (dir.isDirectory()) { //判断是否是目录 File[] files = dir.listFiles(); //将目录下的所有文件放在文件集中 if (files.length == 0) { //判断文件集是否为空 ZipEntry entry = new ZipEntry(getEntryName(baseDirPath, dir)); try { out.putNextEntry(entry); //复制字节到压缩文件 out.closeEntry(); //关闭流实体 } catch (IOException e) { e.printStackTrace(); } return; } for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { //如果是文件,调用fileToZip方法 TextReadZiP.fileToZip(baseDirPath, files[i], out); } else { //如果是目录,递归调用 TextReadZiP.dirToZip(baseDirPath, files[i], out); } } } } private static void fileToZip(String baseDirPath, File file, ZipOutputStream out) { //将文件压缩到ZIP输出流 FileInputStream in = null; ZipEntry entry = null; byte[] buffer = new byte[4096]; //创建复制缓冲区 int bytes_read; if (file.isFile()) { //如果是文件 try { in = new FileInputStream(file); //创建一个文件输入流 //创建ZIP实体 entry = new ZipEntry(getEntryName(baseDirPath, file)); out.putNextEntry(entry); //存储相关信息到压缩文件 //复制字节到压缩文件 while ((bytes_read = in.read(buffer)) != -1) { out.write(buffer, 0, bytes_read); } out.closeEntry(); //关闭流实体 in.close(); System.out.println("添加文件" + file.getAbsolutePath() + "到ZIP文件中!"); } catch (IOException e) { //捕获异常 e.printStackTrace(); } } } private static String getEntryName(String baseDirPath, File file) { //获取待压缩文件相对于根目录的相对路径名 if (!baseDirPath.endsWith(File.separator)) { baseDirPath += File.separator; } String filePath = file.getAbsolutePath(); //获得绝对路径 if (file.isDirectory()) { //判断是否是目录 filePath += "/"; //是则加文件分隔符将以目录项存储 } int index = filePath.indexOf(baseDirPath); //获得根目录 //返回待压缩文件相对根目录的路径 return filePath.substring(index + baseDirPath.length()); } public static void main(String[] args) { //Java程序主入口处 //压缩F盘下的createFile目录,压缩后的文件是F:/createFilel.zip String baseDirName = "F:/"; //根目录 String fileName = "createFile/"; //文件名 String zipFileName = "F:/createFile.zip"; //目录文件名 //调用方法压缩文件或目录 TextReadZiP.zipFile(baseDirName, fileName, zipFileName); System.out.println(); //换行 fileName = "F:/createFile"; //解压缩后文件名 TextReadZiP.upzipFile(zipFileName, fileName); //调用方法解压缩文件或目录 } }
(3)运行结果如下所示:
添加文件F:\createFile\temp1\file1.txt到ZIP文件中! 添加文件F:\createFile\temp26661.txt到ZIP文件中! ...... 压缩文件成功,目标文件名:F:/createFile.zip
创建文件:F:\createFile\createFile\temp1\file1.txt 创建文件:F:\createFile\createFile\temp26661.txt ...... 解压缩文件成功!
源程序解读
(1)zipFile方法实现了压缩文件的功能。使用了ZIP输出流ZipOutputStream,输出到该流中的数据都会被压缩。在压缩文件中,每个文件都是一个ZipEntry,压缩时先根据待压缩文件的文件名获得ZipEntry的名字,并创建ZipEntry对象,再调用ZipOutputStream的putNextEntry方法将ZipEntry信息存入压缩文件中,然后把待压缩文件的数据复制到ZipOutputStream中,此时写入的所有数据都被存在此前插入的ZipEntry中,最后调用closeEntry方法关闭ZipEntry,关闭文件输入流。压缩完毕。
(2)upzipFile方法实现了解压缩文件的功能。新建一个代表压缩文件的ZipFile对象,再调用ZipFile的entries方法获得所有的ZipEntry,如果ZipEntry对应的是一个文件,则使用ZipFile的getInputStream方法获取该ZipEntry对应的压缩文件的输入流,从输入流中读取数据,然后将数据写到目标文件中,如果ZipEntry是一个目录,则在本地创建相应的目录。解压缩完毕。
实例90 从Jar中读取文本
Jar文件是基本Java技术的打包方案。允许开发人员把所有相关的内容(.class、图片、声音和支持文件等)打包到一个单一的文件中。JAR文件格式支持压缩、身份验证和版本,以及许多其他特性。本实例介绍如何读取JAR文件中文件信息以及如何查看JAR文件中指定的单个文件信息。
技术要点
从JAR文件中读取文本和图片的技术要点如下:
• 在DOC命令行中将文件打包的语法有:jar cvf jar包名.jar 文件或目录。如将F盘下的poem.txt与poems.txt文件打包:
jar cvf poem.jar poem.txt poems.txt
• Java 2 SDK标准版提供一个jar工具,可以通过它在控制台读写JAR文件。用到的类都在java.util.jar包中。
• 主要用到JarFile类,它是一个.jar文件自身的引用。其中的每个文件则由JarEntry引用。
实现步骤
(1)新建一个类名为TextOperatorJAR.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.util.*; //引入类 import java.util.jar.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class TextOperatorJAR { //操作JAR文件的类 public static void readJARList(String fileName) throws IOException{ //显示JAR文件内容列表 JarFile jarFile = new JarFile(fileName); //创建JAR文件对象 Enumeration en = jarFile.entries(); //枚举获得JAR文件内实体,即相对路径 System.out.println("文件名\t文件大小\t压缩后的大小"); while (en.hasMoreElements()) { //遍历显示JAR文件中的内容信息 process(en.nextElement()); //调用方法显示内容 } } private static void process(Object obj) { //显示对象信息 JarEntry entry = (JarEntry) obj; //对象转化成Jar对象 String name = entry.getName(); //文件名称 long size = entry.getSize(); //文件大小 long compressedSize = entry.getCompressedSize(); //压缩后的大小 System.out.println(name + "\t" + size + "\t" + compressedSize); } public static void readJARFile(String jarFileName, String fileName) throws IOException { //读取JAR文件中的单个文件信息 JarFile jarFile = new JarFile(jarFileName); //根据传入JAR文件创建JAR文件对象 JarEntry entry = jarFile.getJarEntry(fileName); //获得JAR文件中单个文件的JAR实体 InputStream input = jarFile.getInputStream(entry); //根据实体创建输入流 readFile(input); //调用方法获得文件信息 jarFile.close(); //关闭JAR文件对象流 } public static void readFile(InputStream input) throws IOException { //读JAR文件中单个文件信息 InputStreamReader in = new InputStreamReader(input);//创建输入读流 BufferedReader reader = new BufferedReader(in); //创建缓冲读流 String line; while ((line = reader.readLine()) != null) { //循环显示文件内容 System.out.println(line); } reader.close(); //关闭缓冲读流 } public static void main(String args[]) throws IOException {//Java程序主入口处 TextOperatorJAR j=new TextOperatorJAR(); System.out.println("输入一个JAR文件(包括路径和后缀)"); Scanner scan = new Scanner(System.in); //键盘输入值 String jarFileName = scan.next(); //获得键盘输入的值 readJARList(jarFileName); //显示JAR文件的文件信息 System.out.println("查看该JAR文件中的哪个文件信息?"); String fileName=scan.next(); //键盘输入值 readJARFile(jarFileName,fileName); //获得键盘输入的值 } }
(3)运行结果如下所示:
1.输入一个JAR文件(包括路径和后缀) F:/poem.jar 文件名 文件大小 压缩后的大小 META-INF/ 0 2 META-INF/MANIFEST.MF 75 75 poem.txt 300 267 poems.txt 305 270 2.查看该JAR文件中的哪个文件信息? poems.txt A contented mind is the greatest blessing a man can enjoy in this world. 知足是人生在世最大的幸事。 If you would know the value of money, go and try to borrow some. 要想知道钱的价值,就想办法去借钱试试。 That man is the richest whose pleasure are the cheapest. 能处处寻求快乐的人才是最富有的人。
源程序解读
(1)readJARList()方法中,声明JarFile类,它是一个.jar文件自身的引用。通过JarFile类获得JAR文件中每个文件的实体,即文件相对路径。循环调用process()方法解释内部文件的具体信息。
(2)process()方法将传入的对象转化成JAR实体,并获得每个文件的名称和大小以及压缩后的大小等信息,输出到控制台。
(3)readJARFile()方法中,根据JAR文件创建JAR文件对象。根据指定的JAR文件里的文件创建文件实体,并将实体封装在输入流中。调用readFile()方法读取指定文件的内容。读取完毕后关闭JAR文件对象。
(4)readFile()方法中,封装传入的输入流创建输入读流。再在这基础上创建缓冲读流,按每行读取输入流的信息。记住要关闭缓冲读流,因为不关闭则会占很多资源,还可能导致异常。
实例91 流标记分割和统计字符串
本实例介绍如何分割文件中的字符,分割一个文本文件中包含英文语句与中文语句。
技术要点
实现文件中字符的分割与统计的技术要点如下:
• 类java.io.StreamTokenizer 可以获取输入流并将其分析为Token(标记)。StreamTokenizer的nextToken方法读取下一个标记。
• StreamTokenizer定义了几种基本的常量用于标识解析过程:TT_EOF(流结尾)、TT_EOL(行结尾)、TT_NUMBER(数字符号、0~9⋯都属于数字语法)、TT_WORD (一个单词)。
• 要进行字符串分割,需注明字符的范围。从输入流中读取的每个字节被当作是'\u0000'和'\u00FF'之间的一个字符。字符值用于查找此字符的五个可能的属性:空格、字母、数字、字符串引号和注释字符。每个字符可以具有这些属性中的零个或多个。
• 要统计文件的字符数,不能简单地统计Token数,因为字符数不等于Token。因为,按照Token的规定,引号中的内容就算是10页也算一个Token。如果希望引号和引号中的内容都算作Token,应该通过StreamTokenizer的ordinaryCha方法将单引号和双引号当作普通字符处理。
实现步骤
(1)新建一个类名为TextStreamtoken.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.*; //引入类 import java.util.*; public class TextStreamtoken { //操作Streamtoken进行字符串分割和统计 public static void separator(String fileName){ //进行字符串分割 Hashtable worldList=new Hashtable(); //创建哈希表 try { FileReader fr = new FileReader(fileName); //创建读文件对象 BufferedReader br = new BufferedReader(fr); //创建读缓冲对象 StreamTokenizer st = new StreamTokenizer(br); //创建流标记 //重置此标记生成器的语法,使所有字符都成为普通字符 st.resetSyntax(); st.wordChars('A', 'Z'); //字符范围在'A'与'Z'之间 st.wordChars('a', 'z'); //字符范围在'a'与'z'之间 int type; Object dummy = new Object(); //循环直到读到流尾(TT_EOF) while ((type = st.nextToken()) != StreamTokenizer.TT_EOF) { //是否等于已读到的一个字标记常量 if (type == StreamTokenizer.TT_WORD) worldList.put(st.sval, dummy); //st.sval是当前标记一个字 } br.close(); //关闭读缓冲对象 } catch (Exception e) { //捕获异常 System.err.println(e); } Enumeration e = worldList.keys(); //根据键值获行枚举集合 while (e.hasMoreElements()) //循环显示哈希表中的值信息 System.out.println(e.nextElement()); } public static long statisFile(String fileName) { //统计字符数 FileReader fileReader = null; try { fileReader = new FileReader(fileName); StreamTokenizer st = new StreamTokenizer(new BufferedReader( fileReader)); //创建分析给定字符流的标记生成器 st.ordinaryChar('\''); //标记'\'字符为普通字符 st.ordinaryChar('\"'); //标记'\"'字符为普通字符 st.ordinaryChar('/'); //标记'/'字符为普通字符 String s; int number = 0; int wordSum = 0; int symbolSum = 0; int total = 0; //循环到读到流尾 while (st.nextToken() != StreamTokenizer.TT_EOF) { switch (st.ttype) { //这个域包含刚被读取的标记的类型 case StreamTokenizer.TT_EOL://TT_EOL指示已读到行末尾的常量 break; //TT_NUMBER指示已读到一数字标记常量 case StreamTokenizer.TT_NUMBER: //st.nval标记是一个数字 s = String.valueOf((st.nval)); number += s.length(); break; //标记为一个文字标记 case StreamTokenizer.TT_WORD: s = st.sval; //st.sval标记为字符串 wordSum += s.length(); break; default: //这个域包含刚被读取的标记的类型 s = String.valueOf((char) st.ttype); symbolSum += s.length(); } } System.out.println("含有数字的个数为 " + number+"个"); System.out.println("含有中文的个数为 " + wordSum+"个"); System.out.println("含有其他字符的个数为" + symbolSum+"个"); total = symbolSum + number + wordSum; System.out.println("文件中总共含有字符的个数为" + total+"个"); return total; } catch (Exception e) { //捕获异常 e.printStackTrace(); return -1; } finally { //内容总执行 if (fileReader != null) { try { fileReader.close(); //确保读文件流关闭 } catch (IOException e1) { } } } } public static void main(String args[]) { //Java程序主入口处 System.out.println("输入进行字符串分割的文件(包括路径和后缀)"); Scanner scan=new Scanner(System.in); //键盘输入 String fileName=scan.next(); //获得键盘输入的值 System.out.println("1.显示分割后的文件信息:"); separator(fileName); //调用方法进行字符串分割 System.out.println("2.统计文件字符数:"); statisFile(fileName); } }
(3)运行结果如下所示:
输入进行字符串分割的文件(包括路径和后缀) F:/poem2.txt 1.显示分割后的文件信息: ddd 能处处寻求快乐的人才是最富有的人。 2.统计文件字符数: 含有数字的个数为 12个 含有中文的个数为 20个 含有其他字符的个数为1个 文件中总共含有字符的个数为33个
源程序解读
(1)separator()方法中,根据传入的文件创建读文件对象。封装读文件对象创建了读缓冲对象。在这基础上创建流标记对象,对此流标记重置语法,将字符转化成普通字符。通过wordChars()方法,将所有的字符在A与Z以及在a与z之间的字符转为普通字符。通过循环读取流信息直到读到流尾,控制参数为TT_EOF,如果所读取的字符是一个字标记常量,则将之放入哈希表中。最后将分割后的字符用循环输出。
(2)statisFile()方法中,首先打开待统计文件的输入流,封装成StreamTokenizer对象,通过ordinaryChar方法将单引号、双引号和注释符号“/”都设为普通字符,使得在用StreamTokenizer分析流时,把所有字符都当作Token处理。通过StreamTokenizer的nextToken方法读取下一个标记,当读到文件末尾时,读到的标记为StreamTokenizer.TT_EOF常量。标记的ttype属性标示了标记的类型,有3种标记类型,分别是到达行尾的标记,用StreamTokenizer.TT_EOL常量表示;数字类型标记,用StreamTokenizer.TT_NUMBER常量表示;字母类型标记,用StreamTokenizer.TT_WORD常量表示。对于其他类型的标记,可以认为是英文的标点符号。
实例92 Java操作Excel文件
开发人员查看每月工资的情况、电子邮件投递的订单报表以及文件都是以Excel格式进行查看和接收的。本实例讲解如何动态生成Excel文档、读取Excel格式的文档、修改以及合并单元格、设置行高和列宽。用户可以到http://sourceforge.net/project/showfiles.php?group_id =79926下载jexcelapi_2_6_9_1.4.zip,解压缩后将jxl.jar放在项目中。具体操作:右击Java项目中的“JRE系统库”,在弹出的快捷菜单中选择“构建路径”→“配置构建路径”命令,在库(L)中点击“添加外部JAR(X)”按钮,将jxl.jar文件导入并单击“确定”按钮。
技术要点
Java操作Excel文件的技术要点如下:
• 一个Excel文档从大到小可以分成如下几个要素:文档、页、行、单元格,在poi的类库中用不同的类描述。
• org.apache.poi.hssf.usermodel.HSSFWorkbook表示一个Excel文档。它的createSheet方法为文档创建新页;getSheet方法获取文档的页;write方法将文档对象保存到文件中。
• org.apache.poi.hssf.usermodel.HSSFSheet表示Excel文档中的一页。它的createRow方法为当前页创建新行;getRow方法获得当前页的某行。
• org.apache.poi.hssf.usermodel.HSSFRow表示Excel文档中的行。它的createCell方法为当前行创建一个单元格;getCell方法获得当前行的某单元格。
• org.apache.poi.hssf.usermodel.HSSFCell表示Excel文档中的单元格。它的setCellType方法设置单元格的字符类型,如日期类型、数字类型等;setEncoding方法设置单元格的字符编码方式;setCellStyle方法设置单元格的格式,如字体、居中对齐等;setCellValue方法设置单元格的值。
实现步骤
(1)新建一个类名为TextOperatorExcel.java。
(2)代码如下所示:
package com.zf.s10; //创建一个包 import java.io.File; //引入类 import java.io.IOException; import java.util.Scanner; import jxl.Cell; import jxl.Sheet; import jxl.Workbook; import jxl.format.UnderlineStyle; import jxl.read.biff.BiffException; import jxl.write.DateFormat; import jxl.write.DateTime; import jxl.write.Label; import jxl.write.NumberFormat; import jxl.write.WritableCellFormat; import jxl.write.WritableFont; import jxl.write.WritableImage; import jxl.write.WritableSheet; import jxl.write.WritableWorkbook; import jxl.write.WriteException; import jxl.write.biff.RowsExceededException; public class TextOperatorExcel { //操作Excel文件的类 public static String readExcel(File file) { //读取Excel文件的内容 StringBuffer sb = new StringBuffer(); Workbook wb = null; try { wb = Workbook.getWorkbook(file); //构造Workbook(工作簿)对象 } catch (Exception e) { //捕获异常 } e.printStackTrace(); if (wb == null) //判断(工作簿)对象是否为空 return null; Sheet[] sheet = wb.getSheets(); //创建Sheet(工作表)对象 if (sheet != null && sheet.length > 0) { //工作表对象不为空 for (int i = 0; i < sheet.length; i++) {//对每个工作表进行循环 int rowNum = sheet[i].getRows(); //得到当前工作表的行数 for (int j = 0; j < rowNum; j++) { //得到当前行的所有单元格 Cell[] cells = sheet[i].getRow(j); if (cells != null && cells.length > 0) { //对每个单元格进行循环 for (int k = 0; k < cells.length; k++) { //读取当前单元格的值 String cellValue = cells[k].getContents(); sb.append(cellValue + "\t"); } } sb.append("\r\n"); } sb.append("\r\n"); } } wb.close(); //最后关闭资源,释放内存 return sb.toString(); } //将内容写入 public static void writeContentToExcel(String fileName) throws Exception{ File tempFile=new File(fileName); WritableWorkbook workbook = Workbook.createWorkbook(tempFile); WritableSheet sheet = workbook.createSheet("TestCreateExcel", 0); //一些临时变量,用于写到Excel中 Label l=null; jxl.write.Number n=null; jxl.write.DateTime d=null; //预定义的一些字体和格式,同一个Excel中最好不要有太多格式 WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.BLUE); WritableCellFormat headerFormat = new WritableCellFormat (headerFont); WritableFont titleFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED); WritableCellFormat titleFormat = new WritableCellFormat (titleFont); WritableFont detFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format. Colour.BLACK); WritableCellFormat detFormat = new WritableCellFormat (detFont); //用于Number的格式 NumberFormat nf=new NumberFormat("0.00000"); WritableCellFormat priceFormat = new WritableCellFormat (detFont, nf); //用于日期的 DateFormat df=new DateFormat("yyyy-MM-dd"); WritableCellFormat dateFormat = new WritableCellFormat (detFont, df); //创建一些单元格,再加到sheet中 l=new Label(0, 0, "用于测试的Excel文件", headerFormat); sheet.addCell(l); //添加标题 int column=0; l=new Label(column++, 2, "标题", titleFormat); sheet.addCell(l); l=new Label(column++, 2, "日期", titleFormat); sheet.addCell(l); l=new Label(column++, 2, "货币", titleFormat); sheet.addCell(l); l=new Label(column++, 2, "价格", titleFormat); sheet.addCell(l); //添加内容 int i=0; column=0; l=new Label(column++, i+3, "标题 "+i, detFormat); sheet.addCell(l); d=new DateTime(column++, i+3, new java.util.Date(), dateFormat); sheet.addCell(d); l=new Label(column++, i+3, "CNY", detFormat); sheet.addCell(l); n=new jxl.write.Number(column++, i+3, 5.678, priceFormat); sheet.addCell(n); i++; column=0; l=new Label(column++, i+3, "标题 "+i, detFormat); sheet.addCell(l); d=new DateTime(column++, i+3, new java.util.Date(), dateFormat); sheet.addCell(d); l=new Label(column++, i+3, "SGD", detFormat); sheet.addCell(l); n=new jxl.write.Number(column++, i+3, 98832, priceFormat); sheet.addCell(n); //设置列的宽度 column=0; sheet.setColumnView(column++, 20); sheet.setColumnView(column++, 20); sheet.setColumnView(column++, 10); sheet.setColumnView(column++, 20); workbook.write(); workbook.close(); System.out.println("内容写入"+fileName+"成功"); } public static void writeExcel(String fileName) {//生成一个Excel文件 WritableWorkbook wwb = null; try { //创建一个可写入的工作簿(Workbook)对象 wwb = Workbook.createWorkbook(new File(fileName)); } catch (IOException e) { //捕获流异常 e.printStackTrace(); } if (wwb != null) { //创建一个可写入的工作表 //Workbook的createSheet方法有两个参数,第一个是工作表的名称, //第二个是工作表在工作簿中的位置 WritableSheet ws = wwb.createSheet("sheet1", 0); for (int i = 0; i < 10; i++) { //循环添加单元格 for (int j = 0; j < 5; j++) { Label labelC = new Label(j, i, "这是第" + (i + 1) + "行,第" + (j + 1) + "列"); try { ws.addCell(labelC); //将生成的单元格添加到工作表中 } catch (Exception e) { //捕获异常 e.printStackTrace(); } } } try { wwb.write(); //从内存中写入文件中 wwb.close(); //关闭相关资源 } catch (Exception e) { //捕获异常 e.printStackTrace(); } } System.out.println("生成一个Excel文件:"+fileName+"成功!"); } //搜索某一个文件中是否包含某个关键字 public static boolean searchKeyWord(File file, String keyWord) { boolean res = false; Workbook wb = null; try { wb = Workbook.getWorkbook(file); //构造Workbook(工作簿)对象 } catch (Exception e) { //捕获异常 return res; } if (wb == null) return res; Sheet[] sheet = wb.getSheets(); //创建Sheet(工作表)对象 boolean breakSheet = false; if (sheet != null && sheet.length > 0) { for (int i = 0; i < sheet.length; i++) { //对每个工作表进行循环 if (breakSheet) break; int rowNum = sheet[i].getRows(); //得到当前工作表的行数 boolean breakRow = false; for (int j = 0; j < rowNum; j++) { //循环获得行信息 if (breakRow) break; //得到当前行的所有单元格 Cell[] cells = sheet[i].getRow(j); if (cells != null && cells.length > 0) { boolean breakCell = false; //对每个单元格进行循环 for (int k = 0; k < cells.length; k++) { if (breakCell) break; //读取当前单元格的值 String cellValue = cells[k].getContents(); if (cellValue == null) continue; //判断是否包含关键字 if (cellValue.contains(keyWord)) { res = true; breakCell = true; breakRow = true; breakSheet = true; } } } } } } wb.close(); //关闭资源,释放内存 return res; } public static void updateExcel(String fileName) {//修改Excel文件 try { //构造Workbook(工作簿)对象 Workbook wb = Workbook.getWorkbook(new File(fileName)); //打开一个文件的副本,并且指定数据写回到原文件 WritableWorkbook book = Workbook.createWorkbook(new File(fileName), wb); //添加一个工作表 WritableSheet sheet = book.createSheet(" 第二页 ", 1); //添加一个单元格 sheet.addCell(new Label(0, 0, " 第二页的测试数据 ")); book.write(); //副本对象打开 book.close(); //工作簿对象关闭 } catch (Exception e) { //捕获异常 System.out.println(e); } System.out.println("修改Excel文件"+fileName+"成功"); } public static void readExcelInfo(String fileName) throws Exception { //获得Excel文件多少行多少列 //构造Workbook(工作簿)对象 Workbook book = Workbook.getWorkbook(new File(fileName)); Sheet sheet = book.getSheet(0); //得到第一列第一行的单元格//获得第一个工作表对象 int columnum = sheet.getColumns(); //得到列数 int rownum = sheet.getRows(); //得到行数 System.out.println(columnum); System.out.println(rownum); for (int i = 0; i < rownum; i++) //循环进行读写 { for (int j = 0; j < columnum; j++) { Cell cell1 = sheet.getCell(j, i); String result = cell1.getContents(); System.out.print(result); System.out.print(" \t "); } System.out.println(); } book.close(); //关闭(工作簿)对象 } public static void main(String[] args) { //Java程序主入口处 try { System.out.println("1.创建Excel文件,输入Excel文件名称(包括路径和后缀)"); Scanner scan = new Scanner(System.in); final String fileName = scan.next(); //获得键盘值 writeExcel(fileName); //调用生成Excel方法 System.out.println("2.将内容写入Excel文件"); writeContentToExcel(fileName); //调用将内容写入Excel方法 System.out.println("3.读取Excel文件"); System.out.println(readExcel(new File(fileName)));//读取Excel方法 System.out.println("4.搜索关键字—'标题'"); System.out.println("找到关键字?"+ searchKeyWord(new File(fileName),"标题"));//调用搜索关键字方法 System.out.println("5.修改Excel文件"); updateExcel(fileName); //调用修改Excel文件 System.out.println("6.读取Excel文件的行和列"); readExcelInfo(fileName); } catch (Exception e) { //捕获异常 e.printStackTrace(); } } }
(3)运行结果如下所示:
1.创建Excel文件,输入Excel文件名称(包括路径和后缀) F:/poem.xls 生成一个Excel文件:F:/poem.xls成功! 2.将内容写入Excel文件 内容写入F:/poem.xls成功 3.读取Excel文件 用于测试的Excel文件 标题 日期 货币 价格 标题0 2009-04-01 CNY 5.67800 标题1 2009-04-01 SGD 98832.00000 4.搜索关键字—'标题' 找到关键字?true 5.修改Excel文件 修改Excel文件F:/poem.xls成功 6.读取Excel文件的行和列 4 5 用于测试的Excel文件 标题 日期 货币 价格 标题 0 2009-04-01 CNY 5.67800 标题 1 2009-04-01 SGD 98832.00000
程序运行结果如图10-2所示。
图10-2 运行结果
源程序解读
(1)readExcel()方法用于读Excel中文件的内容。首先打开Excel文件的文件输入流,根据该输入流创建一个工作簿HSSFWorkbook;根据表的名字或者索引,利用HSSFWorkbook的getSheet方法获取指定的工作表HSSFSheet。HSSFSheet类的getRow()方法获取一行表格HSSFRow对象,getCell()方法获取一个单元格HSSFCell对象,HSSFCell类的getStringCellValue()方法获得该单元格里的文本。
(2)writeExcel()方法用于将内容写入到Excel文件。创建一个Excel工作簿HSSFWorkbook对象,HSSFWorkbook类的createSheet()方法在工作簿中建立工作表HSSFSheet对象, HSSFSheet类的createRow()方法创建一行表格,返回一个HSSFRow对象。HSSFWorkbook类的createFont()方法创建字体,得到一个HSSFFont对象,通过setColor()方法设置字体颜色为红色,setBoldweight()方法设置字体为加粗。HSSFWorkbook类的createCellStyle()方法创建单元格类型,得到一个HSSFCellStyle对象,setAlignment()方法设置它在水平方向上居中对齐, setVerticalAlignment()方法设置它在垂直方向上也居中对齐,setFont()方法设置它的字体为先前创建的HSSFFont对象。HSSFRow类的createCell()方法创建一个单元格,返回一个HSSFCell对象。setCellType()方法设置单元格为字符串类型,setEncoding()方法设置用UTF16格式编码, setCellStyle()方法把先前创建的HSSFCellStyle对象设置为单元格的格式,setCellValue()方法为单元格填数据。如果将多个标准单元格合并成大的单元格,要借助于Region类,在Region的构造方法中指定大单元格的左上角和右下角所在的行、列号,HSSFSheet类的addMergedRegion()方法实现小单元格的合并。工作簿HSSFWorkbook建立完成后,打开文件输出流,调用HSSFWorkbook类的write()方法将工作簿中的内容写入到文件输出流,便形成了Excel文件。