Java 核心技术卷 II(第 8 版) – 读书笔记 – 第 1 章(下)

Java知己
22、一旦获得了一个 Charset,就可以在 Java 的 Unicode 和指定的编码格式之间进行转化,下面以 GBK 和 Unicode 之间做为例子。

从 Unicode 到 GBK:

import java.nio.charset.Charset;
import java.nio.ByteBuffer;
import java.util.Map;

public class ConvCharset {

    public static void main(String [] args)throws Exception {
        Charset gbk_cset = Charset.forName("gbk");
        String utf_str = new String("计算所"); // utf-8 string
        String gbk_str = new String(utf_str.getBytes(), gbk_cset); // gbk string
        System.out.println(utf_str.length());
        System.out.println(gbk_str.length());
    }
}

从 GBK 到 unicode:

暂时没有成功…… 诡异。。

23、DataOutput 接口定义了以二进制格式输出,写各种类型数据的方法:

writeInt
writeByte
writeDouble
….
writeUTF

它们的写都是固定的,如 int 固定 4 字节,double 是 8。

总之是从 Java 内部类型转换到二进制表示,使用的都是大端。

对于 UTF,使用的是一个 UTF-8 的修改版本,只有 Java 虚拟机是这么用的,因此跨语言时不要使用。

DataOutputStream 实现了 DataOutput 接口,当然要用需要包装一层 FileOutputStream,甚至 Buffered 神马的,比如:

DataOutputStream out  = new DataOutputStream(new FileOutputStream("file.dat"));

24、类似的 DataInput 定义了和上面对应的读取方法。一般来说 writeInt 之后(输出成了二进制),是必须要能够从 readInt 反解析回来的。

25、RandomAccessFile 同时实现了 DataInput 和 DataOutput,主要是用于磁盘上的文件:

RandomAccessFile in = new RandomAccessFile("file.dat", "r");
RandomAccessFile inOut = new RandomAccessFile("file.dat", "r");

这个模式还可以选 rws 和 rwd,rws 要求每个 I/O 操作都要同步,rwd 可以将多次 I/O 合并成一次。

seek() 用于将文件指针放到文件内部任何位置。

getFilePointer() 返回当前文件指针的位置。

length() 返回文件总字节数。

关注公众号:「Java知己」,发送「1024」,免费领取 30 本经典编程书籍。​与 10 万程序员一起进步。每天更新Java知识哦,期待你的到来!

26、下面的例子使用 **DataOutputStream、RandomAccessFile **等来实现固定字节 Record 的读取和随机访问。

切记:String 的一个 char 占用两个字节!!

import java.io.*;

public class RAFTest {

	public static void main(String [] args) {
		Person [] ps = new Person[3];

		ps[0] = new Person("lxr", 1);
		ps[1] = new Person("lhy", 2);
		ps[2] = new Person("coder3", 3);

		int max_str = 80;
		int REC_LEN = max_str*2+4;

		try {
			//Write
			DataOutputStream out = new DataOutputStream(new FileOutputStream("person.dat"));
			for(int i=0; i<ps.length; i++) {
				ps[i].Write(max_str, out);
			}
			out.close();

			//Read one by one and output
			RandomAccessFile raf = new RandomAccessFile("person.dat", "r");
			int nps = (int)raf.length()/REC_LEN;
			Person [] ps2 = new Person[nps];
			for(int i=0; i< nps; i++) {
				raf.seek(i*REC_LEN);
				ps2[i] = new Person();
				ps2[i].Read(max_str, raf);
				System.out.println(ps2[i]);
			}
			raf.close();

		} catch(Exception e) {
			e.printStackTrace();
		}

	}
}

class Person {

	public Person(String name, int id) {
		this.name = name;
		this.id = id;
	}

	public Person() {
	}

	public String getName() {
		return name;
	}

	public int getId() {
		return id;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setId(int id) {
		this.id = id;
	}

	public void Write(int str_len, DataOutput out) throws IOException {
		//Write name, at most str_len *2 bytes
		for(int i=0; i < str_len; i++) {
			char c = 0;
			if(i<name.length()) {
				c = name.charAt(i);
			}
			out.writeChar(c);
		}
		//Output id
		out.writeInt(id);
	}

	public void Read(int str_len, DataInput in) throws IOException {
		//Read name, skip if len < str_len
		StringBuilder sb = new StringBuilder();
		for(int i=0; i < str_len; i++) {
			char c = in.readChar();
			if(c!=0) {
				sb.append(c);
			}
		}
		this.name = sb.toString();
		//Read id
		this.id = in.readInt();
	}

	public String toString() {
		return this.name + " " + this.id;
	}

	private String name;
	private int id;
}

27、ZipInputStream 和 ZipOutputStream 是用于操作 zip 文件的。

它们都是嵌套结构,一个 Stream 内含若干个 ZipEntry(就是 Zip 内置的文件)

对于 ZipInputStream,需要首先调用 getNextEntry(),之后再调用 read(),当然也可以用其他 DataInput 等包装读取。当 - 1 时,表示当前 entry 完毕,需要先调用 CloseEntry(),然后再读取 getNextEntry(),直到返回 null,表示全部都读取完毕。

对于 ZipOutputStream,新建一个文件时,需要 putNextEntry(),写入完毕后,调用 closeEntry()。

一个读写 zip 文件的例子如下:

import java.io.*;
import java.util.*;
import java.util.zip.*;

public class ZipTest {

    public static void main(String [] args) throws Exception {
        //Write an zip file
        ZipOutputStream zout = new ZipOutputStream(new FileOutputStream("test.zip"));
        DataOutputStream dout = new DataOutputStream(zout);
        zout.putNextEntry(new ZipEntry("file1.txt"));
        dout.writeUTF(new String("I'm file1.txt"));
        zout.closeEntry();
        zout.putNextEntry(new ZipEntry("file2.txt"));
        dout.writeUTF(new String("I'm file2.txt"));
        zout.closeEntry();
        dout.close();
        zout.close();

        //Read zip file
        ZipInputStream zin = new ZipInputStream(new FileInputStream("test.zip"));
        ZipEntry entry = null;
        while( (entry = zin.getNextEntry() )!=null) {
            Scanner scan = new Scanner(zin); //一定要新建Scanner!!
            String str = "";
            while(scan.hasNextLine()){
                str+=scan.nextLine();
                str+="\n";
            }
            System.out.println(entry.getName());
            System.out.println(str);
            System.out.println();
            zin.closeEntry();
        }
        zin.close();
    }
}

需要注意的是,读取时,每切换一个 Entry,要新建一个 Scanner!!!

28、Java 提供了 “对象序列化” 机制,可以将任何对象写入到流中,并在将来取回。

29、ObjectOutputStream 用于输出对象,ObjectInputStream 用于读取。对象必须实现了 Serialize 接口。但这个接口不含任何方法。所以序列化理论上不需要做其他事情,Stream 会自动扫描所有域,并逐一序列化 / 反序列化。

30、为了保证对象的一致性(一个对象可能被多个其他对象引用)。每个对象内部有唯一的 ID。

31、相同序号重复出现将被替换为只存储对象序号的引用。

32、如果不想让某个域被序列化,将其表为** transient**(瞬时的)即可,如:

public class LabelPoint implements Serializable {
    private String label;
    private transient int tmp_id;
}

33、如果想大规模重写序列化、反序列化,可以自己写 readObject 和 writeObject:

private void writeObject(ObjectOutputStream out);
private void readObject(ObjectInputStream out);

34、如果想同时重写超类中的数据域,则要使用 **Externalizable **接口。如果你对继承结构复杂的类序列化,并且想要更快的性能,应该使用 Externalizable,它会更快。

35、对于 Enum 的枚举,序列化可以正常工作。对于 public static final xxx 这种定义的常量,一般不会正常工作,此时慎重使用对象序列化。

36、因为序列化会存储类结构的指纹,因此如果类结构变化了,想搞一个版本号怎么办?简单,加上:

public static final long serialVersionUID = 42L;

37、其实也可以用序列化做** clone**:先 Output 再 Input。当然这比 clone 慢很多。

38、前面的各种 Stream 关注的是文件内容。而文件的管理,与文件系统,则由 File 类完成。

File f = new File("file.dat");
System.out.println(f.getAbsolutePath());
System.out.println(f.exists());

39、File 还可以有 dir 和 name 的构造:

File(File dir, String name);

File 既表示文件也可以是目录,用 isFile() 和 isDirectory() 区分。

File 中的 separator 是路径分隔符,windows 为 \,Linux 为 /

File 还有很多功能,如创建临时文件,遍历目录等等。。

40、JDK 1.4 后引入了** New I/O(java.nio)**,包含了下述操作系统的新特性:

  • 字符编码
  • 非阻塞 I/O
  • 内存映射文件
  • 文件加锁

41、操作系统支持将文件映射到内存的一块中进行操作,以减少不断 I/O 的时间浪费。

用 nio 进行文件映射很简单:

(1) 和往常一样打开 FileInputStream
(2)FileChannel channel = stream.getChannel() 获得这个通道。
(3)FileChannel 的 map 方法获得映射:

public abstract MappedByteBuffer map(FileChannel.MapMode mode,
                   long position,
                   long size);

有三种默认:只读、可写、私人(可以更改,但不会更新到文件)。

之后就可以用 ByteBuffer 进行操作了。

可以一次 get() 出一个 byte,也可以用 getInt(),getDouble() 等以二进制的方式读取基本类型。可以用 order() 设置大、小端。

42、Buffer 这个超类和其子类 IntBuffer、ByteBuffer、DoubleBuffer 等,也是 nio 新引进的。

43、缓冲区是相同类型的信息块,每个缓冲区都有:固定的容量(设置的)、读写位置、界限(超出后无意义)、标记(用于重复读等)。

44、缓冲区主要是用来循环执行 “写、读” 等操作。
(1) 初始:位置为 0,界限等于容量,不断用 put 写入到缓冲区。
(2) 当数据全写完或者到达容量限制时,需要切换到读操作。
(3) 调用 flip,将位置复位到 0。
(4) 此时不断 get,当 remainning() 返回正是,表示还有未读取完的。最后调用 clear() 重回写状态
如果要重新读入,可以用 rewind 或者 mark/reset。

关于 Buffer 和 Channel 可以围观一下这篇神文,解释的很好:

http://www.cnblogs.com/focusj/archive/2011/11/03/2231583.html

45、有时候我们想对文件加锁,还是用 FileChannel,它有 lock() 方法:

public final FileLock lock()

它会阻塞,直到获得一个锁,也可以用 trylock,不阻塞。

lock 也有设置独占、共享的版本:

public abstract FileLock lock(long position,
            long size,
            boolean shared)

如果 shared=true,则共享,但放置别人独占。false 则独占,排斥其他全部。

46、锁为 JVM 不可重入(同一个 JVM 启动的类不能全占有),实际是进程不可重入。

47、正则表达式,模式化的字符串:

[Jj]ava.+

匹配 Java、java,java/Java**

正则表达式的规则不再多说了,用的太多了。

Java 与正则相关的 API 主要是 Pattern 和 Matcher 类。

Pattern pattern = Pattern.compile("patternString");
Matcher matcher = pattern.matcher("string to be matched");
//模式1:匹配全串
if (matcher.matches()) {
...
}
//模式2:匹配子串
while(matcher.find()) {
......
}

compile 时可以加选项:

CASE_INSENSITIVE:大小写不敏感
MULTILINE:可以跨行
DOTALL:匹配所有终止。

Java 的正则也支持组群。

group(int gourpNum)

0 是全串,从 1 开始,按照在 pattern 中按照括号的逐一递增排序。

Matcher 的 replaceAll 替换所有,之中可以用 $n 表示引用组群。

Pattern 的 split 可以用正则分割字符串。

一个提取网页中所有 ,并把所有 url 替换为 #的例子:

import java.io.*;
import java.util.regex.*;

public class RETest {
    public static void main(String [] args) throws IOException {
        //Read html
        BufferedReader reader = new BufferedReader(new FileReader("test.html"));
        char buf [] = new char[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while((len = reader.read(buf, 0, 1024))!=-1) {
            sb.append(buf, 0, len);
        }
        String html = sb.toString();
        //System.out.println(html);
        reader.close();

        //Regular Exp
        Pattern pt = Pattern.compile("<a\\s.*?href=\"([^\"]+)\"[^>]*>(.*?)</a>", Pattern.MULTILINE|Pattern.DOTALL);
        Matcher ma = pt.matcher(html);
        while(ma.find()) {
            int ng = ma.groupCount();
            if(ng>0){
                    System.out.println(ma.group(1));
            }
        }
    }
}

本章完毕。

关注公众号:「Java知己」,发送「1024」,免费领取 30 本经典编程书籍。​与 10 万程序员一起进步。每天更新Java知识哦,期待你的到来!

相关文章:

Java 核心技术卷 II(第 8 版) – 读书笔记 – 第 1 章(上)

推荐文章:

反射是框架设计的灵魂

腾讯工作近十年大佬:不是我打击你!你可能真的不会写Java

Java知己

每日福利

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值