17.IO、文件、NIO【草案三】

(这一个章节将讲到Java里面比较重要的一个章节,这里说一句抱歉,因为最近换工作的原因,一直没有时间继续书写教程,不过接下来我会一直坚持写下去的哈,希望大家能够支持。这个章节主要涉及到常用的文件读写,包括高级的文件IO内容——java.nio,因为这些内容在如今的一些项目里面也属于相当常见的一部分,如果有什么遗漏或者笔误的话,希望读者来Email告知:[email protected],谢谢!这一部分篇幅可能比前边章节长很多,也是为了保证能够将Java里面IO和文件操作部分内能写的都写入,如果有遗漏希望读者来Email,概念上有混淆的地方请告知,里面有些内容参考了一些原文数据进行了翻译以及思考注解。)
本章目录:

1.IO类相关内容
2.文件和目录
3.文件高级操作
【Jar文档的读写(.jar)】
  JAR文件介绍:
  JAR文件格式是以流行的ZIP文件格式为基础,用于将许多文件聚集压缩到一个文件里面,与ZIP文件不同的是,JAR文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和JVM这样的工具直接使用。在JAR文件中包含特殊的文件,如manifests和部署描述符,用来只是工具如何处理特定的JAR文件。
  JAR文件的作用如下:
  • 用于发布和使用类库
  • 作为应用程序和扩展的构建单元
  • 作为组件、applet或者插件程序的部署单位
  • 用于打包与组件相关联的辅助资源
  JAR文件格式提供了许多优势和功能,其中很多是传统的压缩格式如ZIP或者TAR所没有提供的,包括:
  • 安全性:可以对JAR文件内容加上数字化签名,这样,能够识别签名的工具就可以有选择地为您授予软件安全特权,这是其他文件做不到的,它还可以检测代码是否被篡改过
  • 减少下载时间:如果一个Applet捆绑到一个JAR文件中,那么浏览器就可以在一个HTTP事务中下载这个Applet的类文件和相关资源,而不是对每一个文件打开一个新连接
  • 压缩:JAR格式允许您压缩文件以提高存储效率
  • 传输平台扩展:Java扩展框架(Java Extensions Framework提供向Java核心平台添加功能的方法,这些扩展是用JAR文件打包的(Java 3D和JavaMail就是扩展的例子
  • 包密封:存储在JAR文件中的包可以选择进行密封,以增强版本一致性和安全性,密封一个包意味着包中的所有类都必须在同一个JAR文件中找到
  • 包版本控制:一个JAR文件可以包含有关它所包含的文件的数据,如厂商和版本信息
  • 可移植性:处理JAR文件的机制就是Java核心平台API的标准部分
  META-INF目录:
  大多数JAR文件包含一个META-INF目录,它用于存储包和扩展的配置数据,如安全性和版本信息,Java 2平台识别并解释META-INF目录中的下述文件和目录,以便配置应用程序、扩展应用和类装载器:
  • MANIFEST.MF:这个manifest文件定义了与扩展和包相关的数据源
  • INDEX.LIST:这个文件由jar工具的新选项-i生成,它包含在应用程序或者扩展中定义的包的位置信息,它是JarIndex实现的一部分,并且由类装载器装载过程。
  • XXX.SF:这个是JAR文件的签名文件。占位符XXX表示了签名者
  • XXX.DSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名
  Pack200类:
  Pack200类的全名为:java.util.jar.Pack200,这个类是JDK 1.5过后才有的类,该类主要作用是针对JAR文件进行高效压缩,该类的实现是根据Java类特有的结构——合并常量池、去掉误用信息、保存内联数据结构、使用变量长度编码、选择优化的代码类型进行二次压缩来实现高效压缩。但是该类是针对Java类进行压缩的,所以对普通文件的压缩和普通压缩软件没有什么两样,但是对于Jar文件却能轻易达到10-40%的压缩率,这在Java应用部署中很有用,尤其针对Java移动应用程序的压缩和解压是尤其不错的做法。其使用主要用于class文件比较多的情况,当jar中包含的非Java类的资源文件比较多的时候,如JPEG或者GIF,使用gzip格式是最好的选择,但是如果Jar包中绝大部分都是class内容的话,使用pack200绝对是首选,因为pack200会针对class的java类进行优化设计,Pack200的压缩和解压缩的速度是很快的,而且压缩率也是惊人的,试试就知道了。Java命令行也提供了相关的命令工具:pack200
  先提供简单的代码段,再示例其操作:
  压缩:
Pack200.Packer packer = Pack200.newPacker();
OutputStream output =newBufferedOutputStream(newFileOutputStream(outfileName));
packer.pack(newJarFile(jarFile),output);
output.close();
  解压:
Pack200.Unpacker unpacker = Pack200.newUnpacker();
OutputStream output =newJarOutputStream(newFileOutputStream(jarFile));
unpacker.unpack(pack200File,output);
output.close();
  ——[$]Pack200类例子——
packageorg.susan.java.io;

importjava.io.File;
importjava.io.FileOutputStream;
importjava.io.OutputStream;
importjava.util.jar.JarFile;
importjava.util.jar.Pack200;

public classPack200Tester {
public static voidmain(Stringargs[])throwsException{
JarFile file =newJarFile("D:/work/study.jar");
Pack200.Packer packer = Pack200.newPacker();
OutputStream out =newFileOutputStream("D:/work/study.pack");
packer.pack(file, out);
out.close();
File inputFile =newFile("D:/work/study.jar");
File outputFile =newFile("D:/work/study.pack");
System.out.println("Before Pack Size: "+ inputFile.length());
System.out.println("After Pack Size: "+ outputFile.length());
}
}
  这段程序运行后我这里有这样的输出:
Before Pack Size: 293695
After Pack Size: 130423
  【需要说明的就是:如果JAR本身是一个可执行的JAR,当被Pack200压缩过后,如果要执行的话必须解压才能执行,否则这个JAR文件会直接抛出错误告诉你不能执行,也就是说Pack200针对Jar文件不是单纯的压缩,是进行了高效率的优化,主要目的是为了使得这个Jar在压缩过后体积减小,但是里面的类照样可以引用的,也就是如果是一个java库的话使用这样的方式未尝是一个提供网络jar的比较不错的方式。】
  ——[$]Jar文件列表——
packageorg.susan.java.io;

importjava.io.IOException;
importjava.sql.Date;
importjava.util.Enumeration;
importjava.util.jar.Attributes;
importjava.util.jar.JarEntry;
importjava.util.jar.JarFile;

public classJarListReader {
public static voidmain(Stringargs[])throwsIOException {
JarFile file =newJarFile("D:/work/study.jar");
Enumeration<JarEntry> e = file.entries();
while(e.hasMoreElements()) {
JarEntry entry = e.nextElement();
System.out.println(entry.getName());
longuncompressedSize = entry.getSize();
longcompressedSize = entry.getCompressedSize();
longcrc = entry.getCrc();
intmethod = entry.getMethod();
Stringcomment = entry.getComment();
System.out.println(newDate(entry.getTime()));
System.out.println("From "+ uncompressedSize +" bytes to "+ compressedSize);
if(method == ZipEntry.STORED) {
System.out.println("ZipEntry.STORED");
}else if(method == ZipEntry.DEFLATED) {
System.out.println(ZipEntry.DEFLATED);
}
System.out.println("Its CRC is "+ crc);
System.out.println(comment);
System.out.println(entry.isDirectory());

Attributes a = entry.getAttributes();
if(a !=null) {
Object[] nameValuePairs = a.entrySet().toArray();
for(intj = 0; j < nameValuePairs.length; j++) {
System.out.println(nameValuePairs[j]);
}
}
System.out.println();
}
}
}
  上边这个类会读取Jar文件的清单,这里输出我不一一列举主要是这个文件里面太多内容,仅仅列举其中一部分:
META-INF/MANIFEST.MF
2009-12-19
From 74 bytes to 75
8
Its CRC is 3423671674
null
false

org/susan/java/basic/BreakContinueMain.class
2009-12-18
From 924 bytes to 538
8
Its CRC is 1903539533
null
false
  但是这样并没有解压,只是一个读取过程,这一点希望读者谨记,如果要解压,直接使用ZIP解压的方式也可以操作。
  ——[$]从一个URL地址获取Jar文件——
packageorg.susan.java.io;

importjava.net.JarURLConnection;
importjava.net.URL;
importjava.util.jar.JarFile;

public classJarMainEntry {
public static voidmain(Stringargs[])throwsException{
//在线地址“ jar:http://hostname/study.jar!/”
URL url =newURL("jar:file:/D://work//study.jar!/");
JarURLConnection con = (JarURLConnection)url.openConnection();
System.out.println(con.getEntryName());
JarFile jarFile = con.getJarFile();
//JarEntry entry = con.getJarEntry();
System.out.println(jarFile.getName());
//System.out.println(entry.getName());
}
}
  这段代码可以直接从一个在线的URL地址获取jar文件,其输出为:
null
D:\work\study.jar
  在线地址的方式在上边也有说明,这里就不多讲,读者在这里可以举一反三学会使用jar文件的加载,在线读取一个jar文件过后进行jar文件的加载,以及读取jar文件的内容过后找到入口进行程序的运行等!
  【这里做个简单的小结*:从这里可以知道Jar文件实际上就是一种特殊的Zip文件,里面的内容存在一定的规范而已,而Jar文件和Zip不一样的就在于不仅仅有特殊工具进行二次压缩,而且还可以直接从一个在线地址加载过来进行运行或者载入,而且在线地址的格式是很特殊的,其地址代码里面已经有说明,这里不多讲。】
  [3]Scanner和Print类
  Print*类:
  Java语言里面的输出具有一些特殊的类型:PrintStreamPrintWriter,这里先提供几个简单的例子演示Print*类的用法:【*:Print*类只有Java的输出类里面有,输入类里面不存在这个类型。】
  PrintStream类是过滤器类中一个不可忽视的成员,最基本的标准输出就要借助于它——我们常用的System.out变量就是PrintStream实例。与之对应的字符流类是PrintWriter类。
  PrintStream有两个构造函数(在新版API中已标记为过时):
  publicPrintStream(OutputStream out)
  publicPrintStream(OutputStream out,boolean autoFlush)
其中,autoFlush置为true时,每当输出遇到换行符,缓冲区的内容就被强制全部输出,如同调用了一次flush()。但要注意,如果没遇到换行符,还是会有数据“憋”在缓冲区里。
  方法(已熟悉的就不解释):
  public voidwrite(intb)
  public voidwrite(byteb,intoffset,intlength)
  public voidflush()
  public voidclose()
  public voidprint(Object obj)
  这个方法功能是 非常强大的,它可以输出任何对象,而不必另加说明。此外print()方法有许多重载形式,即有多种参数。它们是字符串(String)、字符数组 (char[])、字符(char)、整数(int)、长整数(long)、浮点数(float)、双精度浮点数(double)、布尔值 (boolean)。其中,输出多个数单位的print()方法(也就是指参数为String和char[]的)是同步(synchronized)方 法。
  public voidprintln()输出一个换行符。
  public synchronized voidprintln(Object obj)
  println()方法有9个重载形式,几乎就是print()方法的翻版。唯一的区别在于println()方法都是同步的。
  public booleancheckError()
  检查输出过程中有什么错误,如有,返回true值。只要输出流中出现一次错误,则出错后的任意对checkError()的调用均会返回真值。
  ——[$]创建PrintWriter和使用——
packageorg.susan.java.io;

importjava.io.PrintWriter;

public classPrintWriterDemo {
public static voidmain(Stringargs[])throwsException{
// System.out是一个特殊的OutputStream
PrintWriter writer =newPrintWriter(System.out);
// 一些方法举例
writer.println(true);
writer.println('A');
writer.println(500);
writer.println(40000L);
writer.println(45.67f);
writer.println(45.67);
writer.println("Hello");
writer.println(new Integer("99"));
// 关闭PrintWriter
writer.close();
}
}
  这段程序的输出为:
true
A
500
40000
45.67
45.67
Hello
99
  另外一种创建方式可以通过下边这段代码来实现:
// 从BufferedWriter创建PrintWriter
FileWriter fileWriter =newFileWriter(args[0]);
BufferedWriter bWriter =newBufferedWriter(fileWriter);
PrintWriter pWriter =newPrintWriter(bWriter);
  PrintStream使用的三个弊端:
  第一个问题是println的输出是与平台有关的,所以写入控制台不会产生任何的问题。但是对于网络客户端和服务器而言就会出现大的问题!大多数网络协议,如Http和Gnutela,指明换行应当为。所以使用println能编写出能正常工作的windows下的程序但是不能工作在Unix和Mac下,在加上readLine()中本身的bug,如果让带有prinln()的程序会使得服务器和客户端都挂起。
  第二个问题是,如果PrintSteam使用的是所在平台使用的默认编码方式。但是,乐中编码方式并不是服务期或客户端所期望的。例如一个接收XML文件的WEB希望以UTF-8或UTF16编码,但是一个使用PrintStream的WEB服务器可能在中国——本地化环境系统上发送GBK或GB2312的编码的文件,而不管客户端是否期望或理解这些方式。那么出现可能出现编码失败或者挂起。
  第三个问题是PrintStraem吞掉所有的异常。这就是得PrintStream很适合作为教科书程序,如HelloWorld为了讲受简单的控制台输出,不让学生去理解复杂的异常处理。但是在WEB程序中往往会出现连接中断、带宽提供商的错误、远程系统崩溃和其他不可预知得原因而断开。所以网络程序必须具备处理数据流中意料之外的中断。完成这一点的方法是处理异常。但是PrintStream捕获了低层输出流抛出的所有异常。并且在PrintStream中5个标准的方法并没有throws IOException()的声明:
public abstract voidwrite();
public voidwrite(byte[] data);
public voidwrite(byte[] data,intoffset,intlength);
public voidflush();
public voidclose();
  作为替代PrintStream要依靠一个过时标志。如果在底层出现异常,就会设置这个标志,并且程序员要通过checkError()方法检查此标志的值:public boolean checkError();简单地说printStream提供的错误通知对于不可靠的网络连接而言,是不完全的
  Scanner类的使用:
  Scanner类是JDK1.5才出来的一个新的类,以前编程的时候,学过C++的人很多人都说Java里面缺乏像C++一样直接从控制台录入的cin类似的操作,而Scanner的改进就可以使得Java也可以完成了,不过Scanner的最初设计目的铁定不仅仅只是为了这么简单的原因的!这个类是Java 5的新特性,主要是简化文本扫描,这个类最实用的地方表现在获取控制台输入,其他的功能相对使用范围比较少,尽管Java API里面针对这个类有大量的方法,但是都不怎么常用。Scanner里面比较实用的几个方法:
publicPattern delimiter():
返回该Scanner当前正在用于匹配分隔符的Pattern
publicScanner useDelimiter(Pattern pattern):
将此扫描器的分隔模式设置为指定模式
publicScanner useDelimiter(Stringpattern):
将此扫描器的分隔模式设置为从指定 String 构造的模式
public booleanhasNext()throwsIllegalStateException:
如果此扫描器的输入中有另一个标记,则返回 true。在等待要扫描的输入时,此方法可能阻塞。扫描器将不执行任何输入。
public booleanhasNextLine()throwsIllegalStateException:
如果在此扫描器的输入中存在另一行,则返回 true。在等待输入信息时,此方法可能阻塞。扫描器不执行任何输入。
publicStringnextLine()throwsNoSuchElementException,IllegalStateException:
此扫描器执行当前行,并返回跳过的输入信息。 此方法返回当前行的其余部分,不包括结尾处的行分隔符。当前位置移至下一行的行首。因为此方法会继续在输入信息中查找行分隔符,所以如果没有行分隔符,它可能会缓冲所有输入信息,并查找要跳过的行。
publicStringnext()throwsNoSuchElementException:
查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息。即使以前调用 hasNext() 返回了 true,在等待要扫描的输入时此方法也可能阻塞。
  ——[$]扫描控制台输入——
packageorg.susan.java.io;

importjava.util.Scanner;

public classScannerDemo {
public static voidmain(Stringargs[]){
Scanner scanner =newScanner(System.in);
System.out.println("Please input the string:");
while(true){
Stringline = scanner.nextLine();
if(line.equals("exit"))
break;
System.out.println(">>>"+ line);
}
}
}
  上边这段代码的输出为【可交互的控制台】:
Please input the string:
LangYu
>>>LangYu
HelloWorld
>>>HelloWorld
exit
  *:这里的LangYu、HelloWorld和exit都是用户从控制台输入的字符串,这里就演示了用户从控制台和应用程序交互
分享到:
评论
shangjava
  • 浏览: 298083 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

你可能感兴趣的:(设计模式,应用服务器,浏览器,网络协议,网络应用)