`
maloveqiao
  • 浏览: 99550 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

java精华(3)

    博客分类:
  • java
 
阅读更多
1.1.1.1.1 读写文本文件

早些时候曾提到从文件里面读取字符的方法调用的消耗可能是重大的。这个问题在计算文本文件的行数的另一个例子中也可以找到。:

import java.io.*;

p lic class line1 {

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

FileInputStream fis = new FileInputStream(args[0]);

B?redInputStream bis = new B?redInputStream(fis);

DataInputStream dis = new DataInputStream(bis);

int cnt = 0;

while (dis.readLine() != null)

cnt++;

dis.close();

System.out.println(cnt);

} catch (IOException e) {

System.err.println(e);

}

}

}这个程序使用老的DataInputStream.readLine 方法,该方法是使用用读取每个字符的 read 方法实现的。一个新方法是:

import java.io.*;

p lic class line2 {

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

FileReader fr = new FileReader(args[0]);

B?redReader br = new B?redReader(fr);

int cnt = 0;

while (br.readLine() != null)

cnt++;

br.close();

System.out.println(cnt);

} catch (IOException e) {

System.err.println(e);

}

}

}这个方法更快。例如在一个有200,000行的 6 MB文本文件上,第二个程序比第一个快大约20%。

但是即使第二个程序不是更快的,第一个程序依然有一个重要的问题要注意。第一个程序在JavaTM 2编译器下引起了不赞成警告,因为DataInputStream.readLine太陈旧了。它不能恰当的将字节转换为字符,因此在操作包含非ASCII字符的文本文件时可能是不合适的选择。(Java语言使用Unicode字符集而不是ASCII)

这就是早些时候提到的字节流和字符流之间的区别。像这样的一个程序:

import java.io.*;

p lic class conv1 {

p lic static void main(String args[]) {

try {

FileOutputStream fos = new FileOutputStream("out1");

PrintStream ps = new PrintStream(fos);

ps.println("\?\?\?");

ps.close();

} catch (IOException e) {

System.err.println(e);

}

}

}向一个文件里面写,但是没有保存实际的Unicode字符输出。Reader/Writer I/O 类是基于字符的,被设计用来解决这个问题。OutputStreamWriter 应用于字节编码的字符。

一个使用PrintWriter写入Unicode字符的程序是这样的:

import java.io.*;

p lic class conv2 {

p lic static void main(String args[]) {

try {

FileOutputStream fos = new FileOutputStream("out2");

OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");

PrintWriter pw = new PrintWriter(osw);

pw.println("\?\?\?");

pw.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

这个程序使用UTF8编码,具有ASCII文本是本身而其他字符是两个或三个字节的特性。

1.1.1.1.2 格式化的代价

实际上向文件写数据只是输出代价的一部分。另一个可观的代价是数据格式化。考虑一个三部分程序,它像下面这样输出一行:

The sq re of 5 is 25

方法 1

第一种方法简单的输出一个固定的字符串,了解固有的I/O开销:

  p lic class format1 {

p lic static void main(String args[]) {

final int COUNT = 25000;

for (int i = 1; i <= COUNT; i++) {

String s = "The sq re of 5 is 25\n";

System.out.print(s);

}

}

}

方法2

第二种方法使用简单格式"+":

p lic class format2 {

p lic static void main(String args[]) {

int n = 5;

final int COUNT = 25000;

for (int i = 1; i <= COUNT; i++) {

String s = "The sq re of " + n + " is " + n * n + "\n";

System.out.print(s);

}

}

}

方法 3

第三种方法使用java.text包中的 MessageFormat 类:

import java.text.*;

p lic class format3 {

p lic static void main(String args[]) {

MessageFormat fmt = new MessageFormat("The sq re of {0} is {1}\n");

Object vals[] = new Object[2];

int n = 5;

vals[0] = new Integer(n);

vals[1] = new Integer(n * n);

final int COUNT = 25000;

for (int i = 1; i <= COUNT; i++) {

String s = fmt.format(vals);

System.out.print(s);

}

}

}

这些程序产生同样的输出。运行时间是:

  format1   1.3 format2   1.8 format3   7.8

或者说最慢的和最快的大约是6比1。如果格式没有预编译第三种方法将更慢,使用静态的方法代替:

方法 4

MessageFormat.format(String, Object[])

import java.text.*;

p lic class format4 {

p lic static void main(String args[]) {

String fmt = "The sq re of {0} is {1}\n";

Object vals[] = new Object[2];

int n = 5;

vals[0] = new Integer(n);

vals[1] = new Integer(n * n);

final int COUNT = 25000;

for (int i = 1; i <= COUNT; i++) {

String s = MessageFormat.format(fmt, vals);

System.out.print(s);

}

}

}

这比前一个例子多花费1/3的时间。

第三个方法比前两种方法慢很多的事实并不意味着你不应该使用它,而是你要意识到时间上的开销。

在国际化的情况下信息格式化是很重要的,关心这个问题的应用程序通常从一个绑定的资源中读取格式然后使用它。

1.1.1.1.3 随机访问

RandomAccessFile 是一个进行随机文件I/O(在字节层次上)的类。这个类提供一个seek方法,和 C/C++中的相似,移动文件指针到任意的位置,然后从那个位置字节可以被读取或写入。

seek方法访问底层的运行时系统因此往往是消耗巨大的。一个更好的代替是在RandomAccessFile上建立你自己的缓冲,并实现一个直接的字节read方法。read方法的参数是字节偏移量(>= 0)。这样的一个例子是:

import java.io.*;

p lic class ReadRandom {

private static final int DEFAULT_BSIZE = 4096;

private RandomAccessFile raf;

private byte inb[];

private long startpos = -1;

private long endpos = -1;

private int bsize;

p lic ReadRandom(String name) throws FileNotFoundException {

this(name, DEFAULT_BSIZE);

}

p lic ReadRandom(String name, int b) throws FileNotFoundException {

raf = new RandomAccessFile(name, "r");

bsize = b;

inb = new byte[bsize];

}

p lic int read(long pos) {

if (pos < startpos || pos > endpos) {

long blockstart = (pos / bsize) * bsize;

int n;

try {

raf.seek(blockstart);

n = raf.read(inb);

} catch (IOException e) {

return -1;

}

startpos = blockstart;

endpos = blockstart + n - 1;

if (pos < startpos || pos > endpos)

return -1;

}

return inb[(int) (pos - startpos)] & 0xffff;

}

p lic void close() throws IOException {

raf.close();

}

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

ReadRandom rr = new ReadRandom(args[0]);

long pos = 0;

int c;

byte b[] = new byte[1];

while ((c = rr.read(pos)) != -1) {

pos++;

b[0] = (byte) c;

System.out.write(b, 0, 1);

}

rr.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

这个程序简单的读取字节序列然后输出它们。

如果有访问位置,这个技术是很有用的,文件中的附近字节几乎在同时被读取。例如,如果你在一个排序的文件上实现二分法查找,这个方法可能很有用。如果你在一个巨大的文件上的任意点做随机访问的话就没有太大价值。

1.1.1.1.4 压缩

Java提供用于压缩和解压字节流的类,这些类包含在java.util.zip 包里面,这些类也作为 Jar 文件的服务基础 ( Jar 文件是带有附加文件列表的 Zip 文件)。

下面的程序接收一个输入文件并将之写入一个只有一项的压缩的 Zip 文件:

import java.io.*;

import java.util.zip.*;

p lic class compress {

p lic static void doit(String filein, String fileout) {

FileInputStream fis = null;

FileOutputStream fos = null;

try {

fis = new FileInputStream(filein);

fos = new FileOutputStream(fileout);

ZipOutputStream zos = new ZipOutputStream(fos);

ZipEntry ze = new ZipEntry(filein);

zos.putNextEntry(ze);

final int BSIZ = 4096;

byte inb[] = new byte[BSIZ];

int n;

while ((n = fis.read(inb)) != -1)

zos.write(inb, 0, n);

fis.close();

fis = null;

zos.close();

fos = null;

} catch (IOException e) {

System.err.println(e);

} finally {

try {

if (fis != null)

fis.close();

if (fos != null)

fos.close();

} catch (IOException e) {

}

}

}

p lic static void main(String args[]) {

if (args.length != 2) {

System.err.println("missing filenames");

System.exit(1);

}

if (args[0].eq ls(args[1])) {

System.err.println("filenames are identical");

System.exit(1);

}

doit(args[0], args[1]);

}

}

下一个程序执行相反的过程,将一个假设只有一项的Zip文件作为输入然后将之解压到输出文件:

import java.io.*;

import java.util.zip.*;

p lic class uncompress {

p lic static void doit(String filein, String fileout) {

FileInputStream fis = null;

FileOutputStream fos = null;

try {

fis = new FileInputStream(filein);

fos = new FileOutputStream(fileout);

ZipInputStream zis = new ZipInputStream(fis);

ZipEntry ze = zis.getNextEntry();

final int BSIZ = 4096;

byte inb[] = new byte[BSIZ];

int n;

while ((n = zis.read(inb, 0, BSIZ)) != -1)

fos.write(inb, 0, n);

zis.close();

fis = null;

fos.close();

fos = null;

} catch (IOException e) {

System.err.println(e);

} finally {

try {

if (fis != null)

fis.close();

if (fos != null)

fos.close();

} catch (IOException e) {

}

}

}

p lic static void main(String args[]) {

if (args.length != 2) {

System.err.println("missing filenames");

System.exit(1);

}

if (args[0].eq ls(args[1])) {

System.err.println("filenames are identical");

System.exit(1);

}

doit(args[0], args[1]);

}

}

压缩是提高还是损害I/O性能很大程度依赖你的硬件配置,特别是和处理器和磁盘驱动器的速度相关。使用Zip技术的压缩通常意味着在数据大小上减少50%,但是代价是压缩和解压的时间。一个巨大(5到10 MB)的压缩文本文件,使用带有IDE硬盘驱动器的300-MHz Pentium PC从硬盘上读取可以比不压缩少用大约1/3的时间。

压缩的一个有用的范例是向非常慢的媒介例如软盘写数据。使用高速处理器(300 MHz Pentium)和低速软驱(PC上的普通软驱)的一个测试显示压缩一个巨大的文本文件然后在写入软盘比直接写入软盘快大约50% 。

1.1.1.1.5 高速缓存

关于硬件的高速缓存的详细讨论超出了本文的讨论范围。但是在有些情况下软件高速缓存能被用于加速I/O。考虑从一个文本文件里面以随机顺序读取一行的情况,这样做的一个方法是读取所有的行,然后把它们存入一个ArrayList (一个类似Vector的集合类):

import java.io.*;

import java.util.ArrayList;

p lic class LineCache {

private ArrayList list = new ArrayList();

p lic LineCache(String fn) throws IOException {

FileReader fr = new FileReader(fn);

B?redReader br = new B?redReader(fr);

String ln;

while ((ln = br.readLine()) != null)

list.add(ln);

br.close();

}

p lic String getLine(int n) {

if (n < 0)

throw new IllegalArgumentException();

return (n < list.size() ? (String) list.get(n) : null);

}

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

LineCache lc = new LineCache(args[0]);

int i = 0;

String ln;

while ((ln = lc.getLine(i++)) != null)

System.out.println(ln);

} catch (IOException e) {

System.err.println(e);

}

}

}

getLine 方法被用来获取任意行。这个技术是很有用的,但是很明显对一个大文件使用了太多的内存,因此有局限性。一个代替的方法是简单的记住被请求的行最近的100行,其它的请求直接从磁盘读取。这个安排在局域性的访问时很有用,但是在真正的随机访问时没有太大作用。

1.1.1.1.6 分解

分解 是指将字节或字符序列分割为像单词这样的逻辑块的过程。Java 提供StreamTokenizer 类, 像下面这样操作:

import java.io.*;

p lic class token1 {

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

FileReader fr = new FileReader(args[0]);

B?redReader br = new B?redReader(fr);

StreamTokenizer st = new StreamTokenizer(br);

st.resetSyntax();

st.wordChars('a', 'z');

int tok;

while ((tok = st.nextToken()) != StreamTokenizer.TT_EOF) {

if (tok == StreamTokenizer.TT_WORD)

;// st.sval has token

}

br.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

这个例子分解小写单词 (字母a-z)。如果你自己实现同等地功能,它可能像这样:

  import java.io.*;

p lic class token2 {

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

try {

FileReader fr = new FileReader(args[0]);

B?redReader br = new B?redReader(fr);

int maxlen = 256;

int currlen = 0;

char wordb[] = new char[maxlen];

int c;

do {

c = br.read();

if (c >= 'a' && c <= 'z') {

if (currlen == maxlen) {

maxlen *= 1.5;

char xb[] = new char[maxlen];

System.arraycopy(wordb, 0, xb, 0, currlen);

wordb = xb;

}

wordb[currlen++] = (char) c;

} else if (currlen > 0) {

String s = new String(wordb, 0, currlen); // do something

// with s

currlen = 0;

}

} while (c != -1);

br.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

第二个程序比前一个运行快大约 20%,代价是写一些微妙的底层代码。

StreamTokenizer 是一种混合类,它从字符流(例如 B?redReader)读取, 但是同时以字节的形式操作,将所有的字符当作双字节(大于 0xff) ,即使它们是字母字符。

1.1.1.1.7 串行化

串行化 以标准格式将任意的Java数据结构转换为字节流。例如,下面的程序输出随机整数数组:



import java.io.*;

import java.util.*;

p lic class serial1 {

p lic static void main(String args[]) {

ArrayList al = new ArrayList();

Random rn = new Random();

final int N = 100000;

for (int i = 1; i <= N; i++)

al.add(new Integer(rn.nextInt()));

try {

FileOutputStream fos = new FileOutputStream("test.ser");

B?redOutputStream bos = new B?redOutputStream(fos);

ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(al);

oos.close();

} catch (Throwable e) {

System.err.println(e);

}

}

}

而下面的程序读回数组:

  import java.io.*;

import java.util.*;

p lic class serial2 {

p lic static void main(String args[]) {

ArrayList al = null;

try {

FileInputStream fis = new FileInputStream("test.ser");

B?redInputStream bis = new B?redInputStream(fis);

ObjectInputStream ois = new ObjectInputStream(bis);

al = (ArrayList) ois.readObject();

ois.close();

} catch (Throwable e) {

System.err.println(e);

}

}

}

注意我们使用缓冲提高I/O操作的速度。

有比串行化更快的输出大量数据然后读回的方法吗?可能没有,除非在特殊的情况下。例如,假设你决定将文本输出为64位的整数而不是一组8字节。作为文本的长整数的最大长度是大约20个字符,或者说二进制表示的2.5倍长。这种格式看起来不会快。然而,在某些情况下,例如位图,一个特殊的格式可能是一个改进。然而使用你自己的方案而不是串行化的标准方案将使你卷入一些权衡。

除了串行化实际的I/O和格式化开销外(使用DataInputStream和 DataOutputStream), 还有其他的开销,例如在串行化恢复时的创建新对象的需要。

注意DataOutputStream 方法也可以用于开发半自定义数据格式,例如:

import java.io.*;

import java.util.*;

p lic class binary1 {

p lic static void main(String args[]) {

try {

FileOutputStream fos = new FileOutputStream("outdata");

B?redOutputStream bos = new B?redOutputStream(fos);

DataOutputStream dos = new DataOutputStream(bos);

Random rn = new Random();

final int N = 10;

dos.writeInt(N);

for (int i = 1; i <= N; i++) {

int r = rn.nextInt();

System.out.println(r);

dos.writeInt(r);

}

dos.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

和:

import java.io.*;

p lic class binary2 {

p lic static void main(String args[]) {

try {

FileInputStream fis = new FileInputStream("outdata");

B?redInputStream bis = new B?redInputStream(fis);

DataInputStream dis = new DataInputStream(bis);

int N = dis.readInt();

for (int i = 1; i <= N; i++) {

int r = dis.readInt();

System.out.println(r);

}

dis.close();

} catch (IOException e) {

System.err.println(e);

}

}

}

这些程序将10个整数写入文件然后读回它们。

1.1.1.1.8 获取文件信息

迄今为止我们的讨论围绕单一的文件输入输出。但是加速I/O性能还有另一方面--和得到文件特性有关。例如,考虑一个打印文件长度的小程序:

import java.io.*;

p lic class length1 {

p lic static void main(String args[]) {

if (args.length != 1) {

System.err.println("missing filename");

System.exit(1);

}

File f = new File(args[0]);

long len = f.length();

System.out.println(len);

}

}

Java运行时系统自身并不知道文件的长度,因此必须向底层的操作系统查询以获得这个信息,对于文件的其他信息这也成立,例如文件是否是一个目录,文件上次修改时间等等。 java.io包中的File 类提供一套查询这些信息的方法。这些方法总体来说在时间上开销很大因此应该尽可能少用。

下面是一个查询文件信息的更长的范例,它递归整个文件系统写出所有的文件路径:

  import java.io.*;

p lic class roots {

p lic static void visit(File f) {

System.out.println(f);

}

p lic static void walk(File f) {

visit(f);

if (f.isDirectory()) {

String list[] = f.list();

for (int i = 0; i < list.length; i++)

walk(new File(f, list[i]));

}

}

p lic static void main(String args[]) {

File list[] = File.listRoots();

for (int i = 0; i < list.length; i++) {

if (list[i].exists())

walk(list[i]);

else

System.err.println("not accessible: " + list[i]);

}

}

}

这个范例使用 File 方法,例如 isDirectory 和 exists,穿越目录结构。每个文件都被查询一次它的类型 (普通文件或者目录)。


1.1.1 与时间有关的类Date,DateFormat,Calendar

Date类用于表示日期和时间。它没考虑国际化问题,所以又设计了另外两个类。

Calendar类:

主要是进行日期字段之间的相互操作。

编程实例:计算出距当前日期时间315天后的日期时间,并使用”xxxx年xx月xx日xx小时:xx分:xx秒”的格式输出。

import java.util.*;

import java.text.SimpleDateFormat; //由于simpledateformat和dateformat在这个包中

p lic class TestCalendar

{

p lic static void main(String[] args)

{

Calendar cl=Calendar.getInstance(); //创建一个实例

System.out.println(cl.get(Calendar.YEAR)+"年"+cl.get(cl.MONTH)+"月"+cl.get(cl.DAY_OF_MONTH)+"日 "+cl.get(cl.HOUR)+":"+cl.get(cl.MINUTE)+":"+cl.get(cl.SECOND));

/*

使用get方法来取得日期中的年月日等等,参数为类中的常数,可以直接使用类名调用常数,也可以使用对象名。

*/

cl.add(cl.DAY_OF_MONTH,315);

//加上315天,使用add方法,第一个参数为单位,也是常数。

System.out.println(cl.get(Calendar.YEAR)+"年"+cl.get(cl.MONTH)+"月"+cl.get(cl.DAY_OF_MONTH)+"日 "+cl.get(cl.HOUR)+":"+cl.get(cl.MINUTE)+":"+cl.get(cl.SECOND));

SimpleDateFormat sdf1=new SimpleDateFormat("yyyy-MM-dd"); //定义了格式

SimpleDateFormat sdf2=new SimpleDateFormat("yyyy年MM月dd日"); //定义了格式

try

{

Date d=sdf1.parse("2003-03-15"); //将字符串强制转换成这种格式,使用parse()

System.out.println(sdf2.format(d));将格式1的日期转换成格式2,使用format()

}

catch(Exception e)

{

e.printStackTrace();

}

}

}

编程实例:将“2002-03-15“格式的日期转换成“2003年03月15日”的格式。代码在上例中的黑体部分。

1.1 深入理解嵌套类和内部类
1.1.1 什么是嵌套类及内部类?

可以在一个类的内部定义另一个类,这种类称为嵌套类(nested classes),它有两种类型:
静态嵌套类和非静态嵌套类。静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为
内部类(inner)。嵌套类从JDK1.1开始引入。其中inner类又可分为三种:
    其一、在一个类(外部类)中直接定义的内部类;
    其二、在一个方法(外部类的方法)中定义的内部类;
    其三、匿名内部类。
下面,我将说明这几种嵌套类的使用及注意事项。

1.1.2 静态嵌套类

如下所示代码为定义一个静态嵌套类,
p lic class StaticTest {
private static String name = "javaJohn";
private String id = "X001";
static class Person{
private String address = "swjtu,chenDu,China";
p lic String mail = "josserchai@yahoo.com";//内部类公有成员
p lic void display(){
//System.out.println(id);//不能直接访问外部类的非静态成员
System.out.println(name);//只能直接访问外部类的静态成员
System.out.println("Inner "+address);//访问本内部类成员。
}
}
p lic void printInfo(){
Person person = new Person();
person.display();
//System.out.println(mail);//不可访问
//System.out.println(address);//不可访问
System.out.println(person.address);//可以访问内部类的私有成员
System.out.println(person.mail);//可以访问内部类的公有成员
}
p lic static void main(String[] args) {
StaticTest staticTest = new StaticTest();
staticTest.printInfo();
}
}
在静态嵌套类内部,不能访问外部类的非静态成员,这是由Java语法中"静态方法不能直接访问非静态成员"所限定。
若想访问外部类的变量,必须通过其它方法解决,由于这个原因,静态嵌套类使用很少。注意,外部类访问内
部类的的成员有些特别,不能直接访问,但可以通过内部类来访问,这是因为静态嵌套内的所有成员和方法默认为
静态的了。同时注意,内部静态类Person只在类StaticTest 范围内可见,若在其它类中引用或初始化,均是错误的。

1.1.3 在外部类中定义内部类

    如下所示代码为在外部类中定义两个内部类及它们的调用关系:
p lic class Outer{
int outer_x = 100;
class Inner{
p lic int y = 10;
private int z = 9;
int m = 5;
p lic void display(){
System.out.println("display outer_x:"+ outer_x);
}
private void display2(){
System.out.println("display outer_x:"+ outer_x);
}
}
void test(){
Inner inner = new Inner();
inner.display();
inner.display2();
//System.out.println("Inner y:" + y);//不能访问内部内变量
System.out.println("Inner y:" + inner.y);//可以访问
System.out.println("Inner z:" + inner.z);//可以访问
System.out.println("Inner m:" + inner.m);//可以访问
InnerTwo innerTwo = new InnerTwo();
innerTwo.show();
}
class InnerTwo{
Inner innerx = new Inner();
p lic void show(){
//System.out.println(y);//不可访问Innter的y成员
//System.out.println(Inner.y);//不可直接访问Inner的任何成员和方法
innerx.display();//可以访问
innerx.display2();//可以访问
System.out.println(innerx.y);//可以访问
System.out.println(innerx.z);//可以访问
System.out.println(innerx.m);//可以访问
}
}
p lic static void main(String args[]){
Outer outer = new Outer();
outer.test();
}
}
以上代码需要说明有,对于内部类,通常在定义类的class关键字前不加p lic 或 private等限制符,若加了
没有任何影响,同时好像这些限定符对内部类的变量和方法也没有影响(?)。另外,就是要注意,内部类Inner及
InnterTwo只在类Outer的作用域内是可知的,如果类Outer外的任何代码尝试初始化类Inner或使用它,编译就不
会通过。同时,内部类的变量成员只在内部内内部可见,若外部类或同层次的内部类需要访问,需采用示例程序
中的方法,不可直接访问内部类的变量。

1.1.4 在方法中定义内部类

    如下所示代码为在方法内部定义一个内部类:
p lic class FunOuter {
int out_x = 100;
p lic void test(){
class Inner{
String x = "x";
void display(){
System.out.println(out_x);
}
}
Inner inner = new Inner();
inner.display();
}
p lic void showStr(String str){
//p lic String str1 = "test Inner";//不可定义,只允许final修饰
//static String str4 = "static Str";//不可定义,只允许final修饰
String str2 = "test Inner";
final String str3 = "final Str";
class InnerTwo{
p lic void testPrint(){
System.out.println(out_x);//可直接访问外部类的变量
//System.out.println(str);//不可访问本方法内部的非final变量
//System.out.println(str2);//不可访问本方法内部的非final变量
System.out.println(str3);//只可访问本方法的final型变量成员
}
}
InnerTwo innerTwo = new InnerTwo();
innerTwo.testPrint();
}
p lic void use(){
//Inner innerObj = new Inner();//此时Inner己不可见了。
//System.out.println(Inner.x);//此时Inner己不可见了。
}
p lic static void main(String[] args) {
FunOuter outer = new FunOuter();
outer.test();
}
}
从上面的例程我们可以看出定义在方法内部的内部类的可见性更小,它只在方法内部
可见,在外部类(及外部类的其它方法中)中都不可见了。同时,它有一个特点,就是方法
内的内部类连本方法的成员变量都不可访问,它只能访问本方法的final型成员。同时另一个
需引起注意的是方法内部定义成员,只允许final修饰或不加修饰符,其它像static等均不可用。

1.1.5 匿名内部类

    如下所示代码为定义一个匿名内部类:匿名内部类通常用在Java的事件处理上
import java.applet.*;
import java.awt.event.*;
    p lic class AnonymousInnerClassDemo extends Applet{
    p lic void init(){
        addMouseListener(new MouseAdapter(){
            p lic void mousePressed(MouseEvent me){
             showStatus("Mouse Pressed!");
        }
        })
    }
    p lic void showStatus(String str){
        System.out.println(str);
    }
    }
在 上面的例子中,方法addMouseListener接受一个对象型的参数表达式,于是,在参数里,我们定义了一个匿名内部类这个类是一个 MouseAdapter类型的类,同时在这个类中定义了一个继承的方法mousePressed,整个类做为一个参数。这个类没有名称,但是当执行这个 表达式时它被自动实例化。同时因为,这个匿名内部类是定义在AnonymousInnerClassDemo 类内部的,所以它可以访问它的方法 showStatus。这同前面的内部类是一致的。

1.1.6 内部类使用的其它的问题


通过以上,我们可以清楚地看出内部类的一些使用方 法,同时,在许多时候,内部类是在如Java的事件处理、或做为值对象来使用的。同时,我们需注意最后一个问题,那就是,内部类同其它类一样被定义,同样 它也可以继承外部其它包的类和实现外部其它地方的接口。同样它也可以继承同一层次的其它的内部类,甚至可以继承外部类本身。下面我们给出最后一个例子做为 结束:
p lic class Layer {
//Layer类的成员变量
private String testStr = "testStr";
//Person类,基类
class Person{
String name;
Email email;
p lic void setName(String nameStr){
this.name = nameStr;
}
p lic String getName(){
return this.name;
}
p lic void setEmail(Email emailObj){
this.email = emailObj;
}
p lic String getEmail(){
return this.email.getMailStr();
}
//内部类的内部类,多层内部类
class Email{
String mailID;
String mailNetAddress;
Email(String mailId,String mailNetAddress){
this.mailID = mailId;
this.mailNetAddress = mailNetAddress;
}
String getMailStr(){
return this.mailID +"@"+this.mailNetAddress;
}
}
}
//另一个内部类继承外部类本身
class ChildLayer extends Layer{
void print(){
System.out.println(super.testStr);//访问父类的成员变量
}
}
//另个内部类继承内部类Person
class OfficePerson extends Person{
void show(){
System.out.println(name);
System.out.println(getEmail());
}
}
//外部类的测试方法
p lic void testFunction(){
//测试第一个内部类
ChildLayer childLayer = new ChildLayer();
childLayer.print();
//测试第二个内部类
OfficePerson officePerson = new OfficePerson();
officePerson.setName("abner chai");
//注意此处,必须用 对象.new 出来对象的子类对象
//而不是Person.new Email(...)
//也不是new Person.Email(...)
officePerson.setEmail(officePerson.new Email("josserchai","yahoo.com"));
officePerson.show();
}
p lic static void main(String[] args) {
Layer layer = new Layer();
layer.testFunction();
}
}

1.2 文件和流

Java I/O系统的类实在是太多了,这里我们只学习一些基本的和常用的,相信能够掌握这些就可以解决我们以后的普通应用了

1.2.1 什么是数据流

数据流是指所有的数据通信通道
有两类流,InputStream and OutputStream,Java中每一种流的基本功能依赖于它们
InputStream 用于read,OutputStream 用于write, 读和写都是相对与内存说的,读就是从其他地方把数据拿进内存,写就是把数据从内存推出去
这两个都是抽象类,不能直接使用

1.2.2 InputStream 的方法有:

read() 从流中读入数据 有3中方式:
int read() 一次读一个字节
int read(byte[]) 读多个字节到数组中
int read(byte[],int off,int len) 指定从数组的哪里开始,读多长
skip() 跳过流中若干字节
available() 返回流中可用字节数,但基于网络时无效,返回0
markSupported() 判断是否支持标记与复位操作
mark() 在流中标记一个位置,要与markSupported()连用
reset() 返回标记过的位置
close() 关闭流

1.2.3 OutputStream 的方法:

write(int) 写一个字节到流中
write(byte[]) 将数组中的内容写到流中
write(byte[],int off,int len) 将数组中从off指定的位置开始len长度的数据写到流中
close() 关闭流
flush() 将缓冲区中的数据强制输出

1.2.4 File 类

File 可以表示文件也可以表示目录,File 类控制所有硬盘操作
构造器:
File(File parent,String child) 用父类和文件名构造
File(String pathname) 用绝对路径构造
File(String parent,String child) 用父目录和文件名构造
File(URI uri) 用远程文件构造
常用方法:
boolean createNewFile();
boolean exists();
例子:
//建立 test.txt 文件对象,判断是否存在,不存在就创建
import java.io.*;
p lic class CreateNewFile{
p lic static void main(String args[]){
File f=new File("test.txt");
try{
if(!f.exists())
f.createNewFile();
else
System.out.println("exists");
}catch(Exception e){
e.printStackTrace();
}
}
}
boolean mkdir()/mkdirs()
boolean renameTo(File destination)
例子://看一下这 mkdir()/mkdirs() 的区别和 renameTo 的用法
import java.io.*;
p lic class CreateDir{
p lic static void main(String args[]){
File f=new File("test.txt");
File f1=new File("Dir");
File f2=new File("Top/Bottom");
File f3=new File("newTest.txt");
try{
f.renameTo(f3);
f1.mkdir();
f2.mkdirs();
}catch(Exception e){
e.printStackTrace();
}
}
}
String getPath()/getAbsolutePath()
String getParent()/getName()
例子://硬盘上并没有parent 目录和 test.txt 文件,但我们仍然可以操作,因为我们创建了他们的对象,是对对象进行操作
import java.io.*;
p lic class Test{
p lic static void main(String args[]){
File f=new File("parent/test.txt");
File f1=new File("newTest.txt");
try{
System.out.println(f.getParent());
System.out.println(f.getName());
System.out.println(f1.getPath());
System.out.println(f1.getAbsolutePath());
}catch(Exception e){
e.printStackTrace();
}
}
}
String list[] //显示目录下所有文件
long lastModified() //返回 1970.1.1 到最后修改时间的秒数
boolean isDirectory()
例子://列出目录下的所有文件和目录,最后修改时间,是目录的后面标出<DIR>,是文件的后面标出文件长度
import java.io.*;
import java.util.*;
p lic class Dir{
p lic static void main(String args[]){
File f=new File("Dir");
String[] listAll=null;
File temp=null;
try{
listAll=f.list();
for(int i=0;i<listAll.length;i++){
temp=new File(listAll<i>);
System.out.print(listAll<i>+"\t");
if(temp.isDirectory())
System.out.print("\t<DIR>\t");
else
System.out.print(temp.length()+"\t");
System.out.println(new Date(temp.lastModified()));
}
}catch(Exception e){
e.printStackTrace();
}
}
}

1.2.5 文件流的建立

File f=new File("temp.txt");
FileInputStream in=new FileInputStream(f);
FileOutputStream out=new FileOutputStream(f);
例子:文件拷贝
import java.io.*;
p lic class Copy{
p lic static void main(String args[]){
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis=new FileInputStream("c2.gif");
fos=new FileOutputStream("c2_copy.gif");
int c;
while((c=fis.read()) != -1)
fos.write(c);
}catch(Exception e){
e.printStackTrace();
}finally{
if(fis != null) try{ fis.close(); }catch(Exception e){ e.printStackTrace(); }
if(fos!= null) try{ fos.close(); }catch(Exception e){ e.printStackTrace(); }
}
}
}

1.2.6 缓冲区流

B?redInputStream
B?redOutputStream
他们是在普通文件流上加了缓冲的功能,所以构造他们时要先构造普通流
例子:文件拷贝的缓冲改进
import java.io.*;
p lic class Copy{
p lic static void main(String args[]){
B?redInputStream bis=null;
B?redOutputStream bos=null;
byte b[]=new byte[100];
try{
bis=new B?redInputStream(new FileInputStream("persia.mp3"));
bos=new B?redOutputStream(new FileOutputStream("persia_copy.mp3"));
int len=0;
while( tr ){
len=bis.read(b);
if(len<=0) break;
bos.write(b,0,len);
}
bos.flush();//缓冲区只有满时才会将数据输出到输出流,用flush()将未满的缓冲区中数据强制输出
}catch(Exception e){
e.printStackTrace();
}finally{
if(bis != null) try{ bis.close(); }catch(Exception e){ e.printStackTrace(); }
if(bos!= null) try{ bos.close(); }catch(Exception e){ e.printStackTrace(); }
}
}
}

1.2.7 原始型数据流

DataInputStream
DataOutputStream
他们是在普通流上加了读写原始型数据的功能,所以构造他们时要先构造普通流
方法:
readBoolean()/writeBoolean()
readByte()/writeByte()
readChar()/writeByte()
......
例子://这个流比较简单,要注意的就是读时的顺序要和写时的一样
import java.io.*;
p lic class DataOut{
p lic static void main(String args[]){
DataOutputStream dos=null;
try{
dos=new DataOutputStream(new FileOutputStream("dataout.txt"));
dos.writeInt(1);
dos.writeBoolean(tr);
dos.writeLong(100L);
dos.writeChar('a');
}catch(Exception e){
e.printStackTrace();
}finally{
if(dos!=null)
try{
dos.close();
}catch(Exception e){
}
}
}
}
import java.io.*;
p lic class DataIn{
p lic static void main(String args[]){
DataInputStream dis=null;
try{
dis=new DataInputStream(new FileInputStream("dataout.txt"));
System.out.println(dis.readInt());
System.out.println(dis.readBoolean());
System.out.println(dis.readLong());
System.out.println(dis.readChar());
}catch(Exception e){
e.printStackTrace();
}finally{
if(dis!=null)
try{
dis.close();
}catch(Exception e){
}
}
}
}

1.2.8 对象流

串行化:对象通过写出描述自己状态的数值来记述自己的过程叫串行话
对象流:能够输入输出对象的流
将串行化的对象通过对象流写入文件或传送到其他地方
对象流是在普通流上加了传输对象的功能,所以构造对象流时要先构造普通文件流
注意:只有实现了Serializable接口的类才能被串行化
例子:
import java.io.*;
class St?nt implements Serializable{
private String name;
private int age;
p lic St?nt(String name,int age){
this.name=name;
this.age=age;
}
p lic void greeting(){
System.out.println("hello ,my name is "+name);
}
p lic String toString(){
return "St?nt["+name+","+age+"]";
}
}
p lic class ObjectOutTest{
p lic static void main(String args[]){
ObjectOutputStream oos=null;
try{
oos=new ObjectOutputStream(
new FileOutputStream("st?nt.txt"));
St?nt s1=new St?nt("Jerry",24);
St?nt s2=new St?nt("Andy",33);
oos.writeObject(s1);
oos.writeObject(s2);
}catch(Exception e){
e.printStackTrace();
}finally{
if(oos!=null)
try{
oos.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
import java.io.*;
p lic class ObjectInTest{
p lic static void main(String args[]){
ObjectInputStream ois=null;
St?nt s=null;
try{
ois=new ObjectInputStream(
new FileInputStream("st?nt.txt"));
System.out.println("--------------------");
s=(St?nt)ois.readObject();
System.out.println(s);
s.greeting();
System.out.println("--------------------");
s=(St?nt)ois.readObject();
System.out.println(s);
s.greeting();
}catch(Exception e){
e.printStackTrace();
}finally{
if(ois!=null)
try{
ois.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}

1.2.9 字符流 InputStreamReader/OutputStreamWriter

上面的几种流的单位是 byte,所以叫做字节流,写入文件的都是二进制字节,我们无法直接看,下面要学习的是字节流
Java采用 Unicode 字符集,每个字符和汉字都采用2个字节进行编码,ASCII 码是 Unicode 编码的自集
InputStreamReader 是 字节流 到 字符桥的桥梁 ( byte->char 读取字节然后用特定字符集编码成字符)
OutputStreamWriter是 字符流 到 字节流的桥梁 ( char->byte )
他们是在字节流的基础上加了桥梁作用,所以构造他们时要先构造普通文件流
我们常用的是:
B?redReader 方法:readLine()
PrintWriter 方法:println()
例子:
import java.io.*;
p lic class PrintWriterTest{
p lic static void main(String args[]){
PrintWriter pw=null;
try{
pw=new PrintWriter(
new OutputStreamWriter(
new FileOutputStream("b?redwriter.txt")));
pw.println("hello world");
}catch(Exception e){
e.printStackTrace();
}finally{
if(pw!=null)
try{
pw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
import java.io.*;
p lic class B?redReaderTest{
p lic static void main(String args[]){
B?redReader br=null;
try{
br=new B?redReader(
new InputStreamReader(
new FileInputStream("b?redwriter.txt")));
System.out.println(br.readLine());
}catch(Exception e){
e.printStackTrace();
}finally{
if(br!=null)
try{
br.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}

1.2.10 随机存取文件 RandomAccessFile

可同时完成读写操作
支持随机文件操作的方法:
readXXX()/writeXXX()
seek() 将指针调到所需位置
getFilePointer() 返回指针当前位置
length() 返回文件长度
例子:把若干个32位的整数写到一个名为 “temp.txt”的文件中,然后利用seek方法,以相反的顺序再读取这些数据
import java.io.*;
p lic class RandomFile{
p lic static void main(String args[]){
RandomAccessFile raf=null;
int data[]={12,31,56,23,27,1,43,65,4,99};
try{
raf=new RandomAccessFile("temp.txt","rw");
for(int i=0;i<data.length;i++)
raf.writeInt(data<i>);
for(int i=data.length-1;i>=0;i--){
raf.seek(i*4);
System.out.println(raf.readInt());
}
}catch(Exception e){
e.getMessage();
}finally{
if(raf!=null)
try{
raf.close();
}catch(Exception e){
e.getMessage();
}
}
}
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics