美文网首页Java干货分享
Java 核心技术卷 II(第 8 版) – 读书笔记 – 第

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

作者: 淡定_蜗牛 | 来源:发表于2019-07-17 10:05 被阅读0次
    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 可以用正则分割字符串。

    一个提取网页中所有 <a href=””>,并把所有 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知己 每日福利

    相关文章

      网友评论

        本文标题:Java 核心技术卷 II(第 8 版) – 读书笔记 – 第

        本文链接:https://www.haomeiwen.com/subject/ewbflctx.html