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知识哦,期待你的到来!
相关文章: