《Java编程思想第四版》学习笔记--18至20章

第十八章 Java I/O系统

1 File 类

File是一个文件和目录路径名的抽象表示,通过File可以查看文件的各种信息,也可以增加删除文件。
File构造器接受一个路径字符串并把它与实际文件目录映射起来,也能接受父子目录,无论是相对路径还是绝对路径
File(File parent, String child)
File(String pathname)
File(URI uri)
File 对文件和目录操作的功能几乎都有,如查看读写权限,查看父子目录,创建、删除、重命名文件等等。

2 输入和输出

输入流 InputStream / Reader 就是把数据从某处(一般就是构造器中的对象)读到本身,再read( )到别处。
输出流 OutputStream / Writer 就是从本身writer( )数据写入到其他对象(构造器中的对象)
InputStream、OutputStream 是字节流
Reader、Writer 是字符流,一般使用字符流,再只能使用字节流解决问题的地方使用字节流。

2.1 输入流

InputStream :所有有关输入的流都从这继承 (抽象类),提供了输入流所具备的基本方法。因为读取的数据源(构造器中的数据源)不相同,所以产生了很多子类。

int available() 
返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。  
void close() 
关闭此输入流并释放与流相关联的任何系统资源。  
void mark(int readlimit) 
标记此输入流中的当前位置。  
boolean markSupported() 
测试这个输入流是否支持 mark和 reset方法。  
abstract int read() 
从输入流读取数据的下一个字节。  
int read(byte[] b) 
从输入流读取一些字节数,并将它们存储到缓冲区 b 。  
int read(byte[] b, int off, int len) 
从输入流读取最多 len字节的数据到一个字节数组,从数组off开始写入。  
void reset() 
将此流重新定位到上次在此输入流上调用 mark方法时的位置。  
long skip(long n) 
跳过并丢弃来自此输入流的 n字节数据。  

ByteArrayInputSteram( byte[ ] b) :从缓冲区(byte[ ])读取数据,可以把b中的字节read出去。
FileInputStream(File / String ):可提供一个File,或者 文件路径String 或者(FileDescriptor)给构造器 ,从文件中读数据,找不到文件报异常。
PipedInputStream() :管输入道流 用来和管道输出流对接,构造器接受一个PipedOutputStream或者同时指定int值为缓冲区的大小。
典型地使用方法,在一个线程中数据从PipedInputStream读入,此数据是由另一个线程写入到对应的PipedOutputStream中。 单线程下使用可能还会造成死锁。
SequenceInputStream:将多个输入流连接起来。它从一个有序的输入流集合开始,从第一个读取到文件的结尾,然后从第二个文件读取,依此类推,直到最后一个输入流达到文件的结尾。
FilterInputStream :装饰器,其他输入流提供有用功能。装饰器的构造器接收的都是流
DataInputStream(InputStream in):可以从流中按基本数据类型和String读取数据,上面的只能按字节读取,输出也只能输出int 或者 byte。(字节流为了解决字符的问题产生)

int read(byte[] b)
boolean readBoolean()
byte readByte()
char readChar()
double readDouble()
float readFloat()
int readInt()
long readLong()
short readShort()
String readUTF(string)

DataInputStream必须 DataOutputStream 搭配使用,才能正确得到结果。DataOutputStream 写入非字节数据时会附带数据长度信息,其他输入流 read 出来数据会出现乱码,正是因为这些长度信息使得他与平台无关,不管你在什么环境中DataOutputStream 写入,在什么环境中DataInputStream读出都不会错。
BufferedInputStream 缓冲输入 接收一个输入流,也可以指定缓冲区大小。它比InputStream的优点就是读满缓冲区才写入指定位置,而不是读一个写一个,减少IO次数。
PushbackInputStream 编译器使用,程序员可能永远不会使用。将读到的最后一个字符回退到流。

2.2 输出流

OutputStream:所有有关输出的流都从这继承 (抽象类),提供了输出流所具备的基本方法,flush()方法 是输出流的标配。

void close() 
关闭此输出流并释放与此流相关联的任何系统资源。  
void flush() 
刷新此输出流并强制任何缓冲的输出。  
void write(byte[] b) 
将 b.length字节从指定的字节数组写入此输出流。  
void write(byte[] b, int off, int len) 
从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。  
abstract void write(int b) 
将指定的字节写入此输出流。  

flush( )方法是为了在缓冲区不满时强制刷出数据,因为缓冲区不满时不会向文件写数据。
输入流和输出流的方法相对但都大同小异 read 对应 write
·ByteArrayOutputSteram 
  ·toByteArray()可以返回已经写的字节数组
·FileOutputStream 往文件中写数据,找不到文件则新建一个文件。
·PipedOutputStream
·FilterOutputStream
· DataOutputStream
· PrintStream : 格式化输出到流 (字节流为了解决字符的问题产生),System.out 就对应它。 它有 print(),println(),format( ),append(), write(byte[] buf, int off, int len) 从一个字节数组中写入到流。write(int b)一次只写一个字节,也就是说只取b 的低八位。
· BufferedOutputStream

3 Reader 和 Writer

面向字符的流。最好使用 Reader和Writer因为它们操作比较快。
因为它们自带Unicode操作所有某些情况下并不适用,而必须要使用字节流,如java.util.zip类库就是面向字节的而不是面向字符的。

3.1Reader

所有有关输入的流都从这继承 (抽象类),提供了字符输入流所具备的基本方法。

abstract void close()
        关闭流并释放与之相关联的任何系统资源。
        void mark(int readAheadLimit)
        标记流中的当前位置。
        boolean markSupported()
        告诉这个流是否支持mark()操作。
        int read()
        读一个字符
        int read(char[] cbuf)
        将字符读入数组。
        abstract int read(char[] cbuf, int off, int len)
        将字符读入数组中的一部分。
        int read(CharBuffer target)
        尝试将字符读入指定的字符缓冲区。
        boolean ready()
        告诉这个流是否准备好被读取。
        void reset()
        重置流。
        long skip(long n)
        跳过字符

CharArrayReader(char[] buf / char[] buf, int offset, int length) 从字符数组中读取字符
StringReader(String s) 从字符串中读取字符。
PipedReader (PipedWriter) 管道流,和PipedWriter搭配使用,最好在多线程下分别使用,单一线程同时使用一对可能造成死锁
BufferedReader(Reader in) 缓冲流,此流有readLine()方法可以读一行字符串。
 lineNumberReader 它的2个方法 setLineNumber(int)和getLineNumber()用于分别设置和获得当前行号。没多大用处,建议不使用。
InputStreamReader(InputStream in, String charsetName)字节流到字符流的桥梁,in 读取字节,根据charseName编码转为字符。
 FileReader 按字符读取文件.它没有任何方法,作用就是按字符读取文件作为InputStreamReader让别的流控制。
FilterReader
 PushbackReader (Reader in, int size) 推回输入流 使用pushback缓冲区大小是size,它有unread()方法可以将字符回推到Reader。

3.2 Writer

所有有关输出的流都从这继承 (抽象类)

 Writer append(char c)
        将指定的字符追加到后面。
        Writer append(CharSequence csq)
        将指定的字符序列追加到后面。
        Writer append(CharSequence csq, int start, int end)
        将指定字符序列的子序列追加到后面。
        abstract void close()
        关闭流,
        abstract void flush()
        刷新流。
        void write(char[] cbuf)
        写入一个字符数组。
        abstract void write(char[] cbuf, int off, int len)
        写入字符数组的一部分。
        void write(int c)
        写一个字符
        void write(String str)
        写一个字符串
        void write(String str, int off, int len)
        写一个字符串的一部分。

CharArrayWriter 自身就是一个缓冲区,构造方法只能设置大小。
  ·增加writeTo(Writer out) 方法 可以将 write() 写入缓冲区的内容写入另一个字符流。
  ·toCharArray()返回写入的内容的字符数组
StringWriter 字符串缓冲区,用于构造字符串,构造方法只能设置大小,toString方法输出字符串
  ·toString() 返回写入的字符串。
PipedWriter(PipedReader snk) 可在构造器中关联也可使用 connect(PipedReader snk) 与PipedReader关联起来
BufferWrite(Writer out, int sz)   newLine() 添加一行分隔符。
OutputStreamWriter(InputStream in, String charsetName)
·FileWriter 按字符写入文件.它没有任何方法,作用就是按字符写入文件作为OutputStreamReader让别的流控制。
FilterWriter 抽象类却没有任何子类
PrintWritre 格式化输出到流,流可以是文件,OutPutStream,

4 输入输出流总结

所有输入流从构造器中的对象中read数据,输出流除缓存流外都把数据输出到构造器中的对象。构造器就是输入流读取数据的地方,输出流写入数据的地方。
字节流的read() 读取的是字节,字符流的read()读取的是字符,write方法也一样。
如果read返回-1或者readLine( )返回null 说明读到了末尾。
读一行数据不能使用DateInputStream,读一行要用BufferReader 的 readLine().
所有输出流在close()之前一定要flush()否则在缓冲区中的一部分数据会丢失。
格式化输出流 PrintStream 和 PrintWrite 都有 print 方法 和 write 方法 这2种方法没有区别,但如果是println方法会调用newLine(),而newLine()会执行flush,因此如果执行print或者write后不执行flush那么有可能写入不了数据,而println一定会写入。
InputStreamReader 和InputStreamWriter 为编码提供了支持。

5 自我独立的类:RandomAccessFile

RandomAccessFile 可以任意访问文件的某处,既可以读又可以写,功能比较强大。
RandomAccessFile(File file, String mode)
创建一个任意访问文件流从File参数指定的文件中读取,可设置文件权限。
RandomAccessFile(String name, String mode)
创建一个任意访问文件流,以从中指定名称的文件读取,可设置文件权限。

6 I/O流的典型使用方式

从文件中读数据取数据,如果不是压缩文件那么文件中肯定都是各种字符所以使用字符流,使用字节流比较麻烦。

/*
        * 字符流读取文件
        * 设置编码格式
        * BufferedReader in =  new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
        */
          BufferedReader in = new BufferedReader(new FileReader(file));
          String s;
          StringBuilder sb = new StringBuilder();
          while ((s = in.readLine()) != null){
                    sb.append(s+"\n");
          }
        in.close();
        System.out.println(sb);
 /*
          * 字节流读取文件
          * 不使用缓冲区
          * FileInputStream in = new FileInputStream(file);
          *
          */
          //默认缓冲区大小8192字节=8M
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
        byte[] bytes = new byte[1024];
        //读到一个字节数组中,然后转换字符串时可以指定编码
        // 如果使用read只读一个字节,单个字节没有编码,这样遇到中文无法处理
        while (in.read(bytes) != -1){ 
            System.out.print(new String(bytes,"UTF-8"));
        }
        in.close();

基本文件输出

/*字节流只能写入字节*/
        FileOutputStream out = new FileOutputStream(file2);
        BufferedOutputStream bo = new BufferedOutputStream(out);
        bo.write(98);
        bo.flush();
        bo.close();
        /*字符流可以写入 char  string  char[] */
        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
        bw.write(85);
        bw.write("koko");
        bw.flush();
        bw.close();
        //快速写入文件
        PrintWriter p = new PrintWriter(file);
        p.write(9);
        p.write("koko");
        p.flush();
        p.close();

读写随机访问文件

//打开或创建一个文件,赋予读写权限
        RandomAccessFile accessFile = new RandomAccessFile(file3,"rw");
        for (int i = 0; i < 5; i++) {
            accessFile.writeDouble(i*1.314);
        }
        accessFile.close();
        accessFile = new RandomAccessFile(file3,"rw");
        /*seek()以字节定位到某一位置
        * 一个double8字节长,这里定位到第三个double开始
        * 重新写入一个并覆盖
        */
        accessFile.seek(2*8);
        accessFile.writeDouble(8888D);
        //重新执行开始位置
        accessFile.seek(0);
        for (int i = 0; i < 5; i++) {
            System.out.println(accessFile.readDouble());
        }
        accessFile.close();
    }
    /*output
    0.0
    1.314
    8888.0
    3.942
    5.256 */

7 文件读写工具

8 标准 I/O

标准I/O就是程序使用的单一信息流。
Java 提供了三种 System.in System.out System.err
System.out System.err 被包装成了PrintStream
System.in 是未经包装的InputStream。
标准I/O重定向
System.setIn(InputStream) 从屏幕接收信息改为从文件接收
System.setOut(PrintStream)输出到屏幕改为输出到文件
System.setErr(PrintStream)输出错误信息到屏幕改为到文件

 PrintStream p  = System.out;// p 必须在重定向之前设定,
        PrintStream stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(file4)));
        System.setOut(stream);
        for (int i = 0; i < 100; i++) {
            System.out.println("Spring 实战第"+(i+1)+"页");

        }
        stream.close();
        System.setOut(p);//不能使用System.setOut(System.out);这样是无效的
        System.out.println("回来"); 

9 进程控制

public abstract class Processextends Object;
       
 ProcessBuilder.start()和Runtime.exec方法创建一个本机进程并返回一个Process子类的Process ,
可以用来控制进程并获取有关它的信息。
Process类提供了用于执行进程输入,
执行到进程的输出,等待进程完成,
检查进程的退出状态以及破坏(杀死)进程的方法

10 新 I/O

jdk1.4 的 java.nio.* 包中引入了新的I/O类,目的在于提高速度。
速度的提高是由于这种I/O方式接近于操作系统的I/O方式: 通道和缓冲器。
流 形式的I/O 是读取写入一个或多个字节,而nio是以块操作数据。
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

10.1 FileChannel 通道

FileChannel 通道:数据好比自来水,通道就是一截自来水管,我能操作这截自来水管中的数据。
ByteBuffer 缓冲器:我们操作数据是通过缓冲器来操作,缓冲器就是在我们和通道之间运输数据的对象。所以我们并不和通道直接交互。
获得FileChannel : IO中的三个字节流可以获得通道,调用getChannel()返回 FileChannel对象
FileInputStream
FileOutputStream
RandimAccessFile
FileChannel由于只和缓冲器交互,所以 通道的read()和write()方法只接受ByteBuffer和ByteBuffer[ ]类型的参数。
read(ByteBuffer[] dsts) 从该通道读取数据到缓冲区。
write(ByteBuffer[] srcs) 从给定的缓冲区向该通道写入一系列字节。
通道直接相连 in.transferTo(0, in.size(),out) 或者out.transferfrom(in , 0, in.size()) ,这方法不稳定,有可能成功,有可能失败,有可能传一半,所以不常用。

10.2 缓冲器 ByteBuffer

我们操作ByteBuffer 字节缓冲器 与通道交互,与通道并不直接交互。
创建缓冲器不能 new 而是调用静态方法allocate(size)来创建,size 是缓冲区大小。
静态方法ByteBuffer.wrap(byte[] b)将字符数组包装到缓冲器,此时b或其一部分就是缓冲器,用put(byte[ ] b) 把b添加到缓冲区会复制数组,不如wrap高效。
执行read(ByteBuffer[] dsts) 后应该立刻调用缓冲器的flip()它会重置缓冲器中的几个指针,让read之后的write从正确的地方开始和结束。
执行write(ByteBuffer[] srcs) 后应该立刻调用缓冲器的clean()方法将缓冲指针重置为初始态,为下一次read重写缓冲区做准备。
hasRemaining()判断缓冲区是否还有字节,有则true

10.3 数据转换

内存中的数据都是字节byte,要想读出字符,字符串,或者汉字则写入数据的编码要和读出时的编码一致,才能正确显示。
如果不指定编码则使用默认编码。
对输入内存的数据编码:转为字节数组时指定编码, “中国1994”.getBytes(“UTF-8”)
解码: Charset.forName(“UTF-8”).decode(buffer) 把缓冲器按指定编码输出为字符缓冲器,相当于buffer.asCharBuffer()

10.4基本类型插入获取

ByteBuffer只能存储字节类型数据,但可以从容纳的字节中产生各种不同的基本类型值。也可以插入基本数据类型。
插入基本数据类型先要获取基本类型视图,调用asXXXBuffer()方法获取视图,再调用视图的put方法插入。获取直接调用缓冲器对应的get方法。
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.asIntBuffer().put(2018);
int i = buffer.getInt();
插入short值时要将值强制转换,其他类型不用转换。

10.5视图缓冲器

只有ByteBuffer能与通道交互其他不能,只能创建一个独立的基本类型视图缓冲器,底层还是ByteBuffer,通过它操作ByteBuffer内部数据。
任何视图缓冲器底层还是ByteBuffer,任何对视图的修改都将修改底层ByteBuffer。
不能将视图缓冲器强制转换为ByteBuffer。
获取视图缓冲器只需ByteBuffer调用asXXXBuff方法返回对应的视图缓冲器
ByteBuffer bb= ByteBuffer.allocate(1024);
CharBuffer charBuffer = bb.asCharBuffer();
DoubleBuffer doubleBuffer = bb.asDoubleBuffer();
获得视图缓冲器之后就可以在该视图缓冲器上操作数据,但底层操作的还是ByteBuffer。
所有对ByteBuffer的方法对应的视图缓冲器都有,如array()返回视图的数组形式。
操作视图缓冲器时不应该操作ByteBuffer。

10.6 缓冲器的细节

所有缓冲器都继承自Buffer, 缓冲器就是一段内存,它有四个索引,可以表示对缓冲器操作的位置和操作结束位置。
mark 标记,调用bb.mark()可以将标记设置到当前position位置。mark 指针不一定存在。
position 位置,当前位置就是pos位置。缓冲器的put(),get()都是在pos所指位置,之后pos向前移一字节。但是如果是get( i )或者put(i, n)则直接在到 i 处操作,pos不会移动。
limit 界限 , 这是安全操作的界限,如果读取值时pos超过了lim那么读取到的就是初始化值0
capacity 容量,就是缓冲器的大小(字节)。
mark( ) 将mark 设置为当前pos位置
position(n)将pos设置为n
limit(n) 设置limit位置为n
flip( ) 将lim设置为pos,再将pos设置到开始(0)。read()之后要调用flip(),因为read之后pos再数据末尾,flip之后读取范围就是pos到lim,不会超过缓冲器存有数据的范围。
clean()将pos设置 为0,limit设置为缓冲器大小capacity。write之后要调用clean()这相当于清空了缓冲器,可以从头开始写入数据,当前位置有数据会直接覆盖。
rewind( ) 间pos设置为0,其他不变,它可以重新获得数据。
remaining( ) 返回 lim - pos 结果,-1 说明操作文件数据到了末尾。也可使用 hasRemaining( )
以上对索引的操作都不会改变ByteBuffer数据。

10.7 内存映射文件

内存映射文件可以创建和操作那些太大而不能放入内存的文件。
获得通道后调用map() 产生MappedByteBuffer,这是一个特殊的直接缓冲器它是继承自ByteBuffer,有ByteBuffer所有方法。可以像使用ByteBuffer一样使用它,只不过一般用它来映射大文件
map()方法必须指定映射文件的开始位置和长度。

File file = new File("C:\\Users\\Administrator\\Desktop\\file\\com\\nio1.txt");
        File path = new File("H:\\WinXP-2018.07.GHO");
        PrintStream p = System.out;
        MappedByteBuffer map = new FileInputStream(path).getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 0x8FFFFFF); //数据太大可以使用16进制
        System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(file))));
        for (int i = 0; i < 0x8FFFFFF; i++) {
            System.out.println(map.get());

        }

数据太大可以使用16进制数。这里只使用了读通道,把结果重定向到其他文件了。
最好使用RandomAccessFile来产生既可以读又可以写的通道。因为映射写必须要使用RandomAccessFile来产生写通道,不能使用FileOutputStream。

10.8文件加锁

jdk 1.4 引入文件加锁机制。
Java的文件加锁直接映射到操作系统的加锁工具,对所有线程可见。
通过调用FileChannel的tryLock()或者lock()来获取整个文件的FileLock,tryLock是非阻塞的拿不到锁就返回,lock()是阻塞式的直到拿到锁才返回。没有参数则对整个文件加锁,哪怕加锁后文件变大如此。
lock(long position, long size, boolean shared) 可以给文件部分加锁,shared为true则加共享锁,但这需要操作系统支持,不支持则加独占锁。
FileLock.release()可以释放锁,或者关闭通道或者lock线程中断都可以释放锁。
处理大文件的文件映射也可以加锁
加锁是在通道上加,缓冲器不能加锁。

11 压缩

压缩 GZIPOutputStream ZIPOutputStream
解压 GZIPInputStream ZIPInputStream
压缩流属于字节流,但是字节流无法指定字符集,如果内容有中文会出现压缩乱码,使用InputStreamReader OutputStreamWriter 可以设置字符集。

BufferedReader in  = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
        //BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(file2)));
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(file2)),"UTF-8"));
       int c;
       while ((c = in.read()) != -1){
           out.write(c);
       }
       in.close();
       out.flush();
       out.close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file2)),"UTF-8"));
        String s;
        while ((s = reader.readLine()) != null){
            System.out.println(s);
        }
        reader.close();

压缩多个文件,基本就这四步

ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file3));
  BufferedOutputStream out = new BufferedOutputStream(zip);
  zip.putNextEntry(new ZipEntry(file));
  BufferedReader in = new BufferedReader(new FileReader(file)); 

12 对象序列化

对象在程序结束后就不复存在,对象序列化就是把对象转化成一个字节序列保存在某处,下次要使用时反序列化成原来保存前的对象。
一个对象要能序列化必须是实现 Serializable接口,这个接口没有任何方法。
序列化的信息可以保存在任何地方,可以在流中可以在文件中,可以通过网络传递。
序列化和反序列化需要2个流 ObjectOutputStream 和 ObjectInputStream,调用writeObject()序列化,readObject()反序列化。

People people = new People();
        people.setName("序列化到文件");
        people.setAge(20);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./people.out"));
        out.writeObject(people);
        people.setName("序列化到流");
        people.setAge(15);
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oi = new ObjectOutputStream(bo);
        oi.writeObject(people);
        ObjectInputStream bytes = new ObjectInputStream(new ByteArrayInputStream(bo.toByteArray()));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("./people.out"));
        People p = (People) in.readObject();
        System.out.println(p);
        p = (People)bytes.readObject();
        System.out.println(p);

序列化可以保存对象的全景图,如果对象中有其他对象,那么也会保存其他对象相关信息,如果其他对象还引用了其他对象,仍然会保,它会保存整个对象网。
反序列化时是从字节序列中直接恢复的,并不会调用对象的构造器
反序列化对象时必须确保jvm可以找到对应对象的 .class文件,否则抛 ClassNotFoundException

12.1 序列化控制

如果对序列化有要求可以实现Externlizable接口代替Serializable接口,而且增加了2个方法会在序列化和反序列化时自动调用。
writeExternal(ObjectOutput out) 序列化时自动调用,可以将一些属性的信息通过out.writeXXX()保存
readExternal(ObjectInput in) 反序列化时自动调用,相当于构造器给某些属性赋值,可以通过in.readXXX()将保存的信息赋值给某些属性。
实现Externlizable接口的对象反序列化时会调用对象的默认构造器,因此默认构造器必须是public
Externlizable 与 Serializable 接口区别
Serializable 将初始化完成的对象写入二进制文件,故无需构造器。
Externlizable 默认不保存对象字段属性,对象的属性只有通过writeExternal 方法才可以保存。
Serializable 想要做到控制序列化可以对不想被序列化的字段使用 transient 关键字,使用transient 声明就不会被保存值。
如果不想使用Externlizable 就是使用Serializable 也可以做到同样的序列化,那就是 添加2个和Externlizable 方法很像的方法,序列化时会执行这两个方法而不会执行默认的序列化动作。
private void writeObject(ObjectOutputStream stream) throws IOExpection
private void readObject(ObjectInputStream stream) throws IOExpection,ClassNotFoundException
2个方法作用和上面一模一样,要注意 方法签名一点不能错
所有的非 transient 字段要保存在 stream.defaultWriteObject() 中且在2个方法的第一句调用。
transient 字段要逐个使用stream.writerObject()保存。
本质:序列化对象时即ObjectOutputStream.writeObject(b)时 通过反射检查是否有这两个方法如果有则放弃默认序列化执行这两个方法,没有则执行默认序列化。

public class SerializableDemo {
    public static void main(String[] args) throws Exception{
        People people = new People();
        people.setName("序列化到文件");
        people.setAge(20);
        people.setSex("难");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./people.out"));
        out.writeObject(people);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("./people.out"));
        People p = (People) in.readObject();
        System.out.println(p);
    }

}


class People implements Serializable {
    String name;
    transient String sex;
    transient int age;
    Date date;
    private void  writeObject(ObjectOutputStream stream) throws IOException{
        stream.defaultWriteObject();
            stream.writeObject(sex);
            stream.writeInt(age);
    }
    private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException{
            stream.defaultReadObject();
           sex = (String) stream.readObject();
           age  = stream.readInt();
    }
    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public People() {
        this.date = new Date();
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", date=" + date +
                '}';
    }
}

12.2 持久性

深度复制deep copy 会复制整个对象网,对象序列化就属于深度复制。
对象序列化到不同流,反序列化出来的对象地址也就不同,同一流反序列化出来的对象地址相同。

第十九章 枚举类型

Java SE5 添加了一个新的特性, enum 关键字. 用来声明枚举类型集. 可以代替 常量类型集,即类似功能的一组常量.

1. 基本enum特性

values()返回enum实例的数组,而且保持声明的顺序:

enum Shrubbery {GROUND, CRAWLING, HANGING}

public class EnumClass {
    public static void main(String[] args) {
        for (Shrubbery s : Shrubbery.values()) {
            print(s + " ordinal: " + s.ordinal());
            printnb(s.compareTo(Shrubbery.CRAWLING) + " ");
            printnb(s.equals(Shrubbery.CRAWLING) + " ");
            print(s == Shrubbery.CRAWLING);
            print(s.getDeclaringClass());
            print(s.name());
            print("----------------------");
        }
        // Produce an enum value from a string name:
        for (String s : "HANGING CRAWLING GROUND".split(" ")) {
            Shrubbery shrub = Enum.valueOf(Shrubbery.class, s);
            print(shrub);
        }
    }
} 

运行结果:
GROUND ordinal: 0
-1 false false
class Shrubbery
GROUND
----------------------
CRAWLING ordinal: 1
0 true true
class Shrubbery
CRAWLING
----------------------
HANGING ordinal: 2
1 false false
class Shrubbery
HANGING
----------------------
HANGING
CRAWLING
GROUND

(1)ordinal方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
(2)==来比较enum实例,编译器会自动提供equals和hashCode方法。
(3)getDeclaringClass()获取其所属的enum类。
(4)name()返回enum实例声明时的名字,与使用toString()效果相同。
(5)valueOf()实在Enum中定义的static方法,他根据给定的名字返回相应的enum实例,如果不存在会抛出异常。

1.1 将静态导入用于enum

使用static import能够将enum实例的标识符代入当前的命名空间,所以无需再用enum类型来修饰enum实例。唯一担心的是使用静态导入会不会导致代码令人难以理解。

1.2 向enum中添加新方法

除了不能继承自一个enum之外,基本上可以将enum看做一个常规类。也就是说,可以添加方法,甚至可以有main方法。

public enum OzWitch {
    // Instances must be defined first, before methods:
    WEST("Miss Gulch, aka the Wicked Witch of the West"),
    NORTH("Glinda, the Good Witch of the North"),
    EAST("Wicked Witch of the East, wearer of the Ruby " +
            "Slippers, crushed by Dorothy's house"),
    SOUTH("Good by inference, but missing");
    private String description;

    // Constructor must be package or private access:
    private OzWitch(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public static void main(String[] args) {
        for (OzWitch witch : OzWitch.values()) {
            print(witch + ": " + witch.getDescription());
        }
    }
}

必须在enum实例序列的最后添加一个分号。Java要求必须先定义enum实例,如果在实例之前定义任何方法或属性,编译时会报错。有意将构造器声明为private,但对于他的可访问性并没有什么影响,因为即使不声明为private,我们只能在enum内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许在使用其构造器来创建任何实例了。

1.3 覆盖enum的方法

public enum SpaceShip {
    SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;

    @Override
    public String toString() {
        String id = name();
        String lower = id.substring(1).toLowerCase();
        return id.charAt(0) + lower;
    }

    public static void main(String[] args) {
        for (SpaceShip s : values()) {
            System.out.println(s);
        }
    }
}

2. switch语句中的enum

一般来说switch中只能使用整形值,而枚举类型天生就具备整形值的次序,并且可以通过ordinal()方法获取其次序。

enum Signal {
    GREEN, YELLOW, RED,
}

public class TrafficLight {
    Signal color = Signal.RED;

    public void change() {
        switch (color) {
            // Note that you don't have to say Signal.RED
            // in the case statement:
            case RED:
                color = Signal.GREEN;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
        }
    }

    @Override
    public String toString() {
        return "The traffic light is " + color;
    }

    public static void main(String[] args) {
        TrafficLight t = new TrafficLight();
        for (int i = 0; i < 7; i++) {
            print(t);
            t.change();
        }
    }
}

3.values()的神秘之处

enum类都继承自Enum类,我们可以查看Enum中并没有values()方法。利用反射机制查看究竟:

values()是由编译器添加的static方法。同时创建Explore的过程中,编译器还添加了valueOf()方法。不是Enum类不是已经有valueOf()方法了吗,不过Enum中的ValueOf()方法需要两个参数,这个新增方法只需一个参数。由于Set只存储方法的名字,不考虑签名,所以removeAll只剩下values。

由于values方法有编译器插入到enum定义中的static方法,所以enum向上转型为Enum,那么values就不可访问了,不过Class中有一个getEnumConstants方法,所以即便Enum接口中没有vlaues方法,仍然可以通过Class对象取得所有enum实例:

enum Search {HITHER, YON}

public class UpcastEnum {
    public static void main(String[] args) {
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // Upcast
        // e.values(); // No values() in Enum
        for (Enum en : e.getClass().getEnumConstants()) {
            System.out.println(en);
        }
    }
}

getEnumConstants() 获取所有Enum对象的实例

4. 实现,而非继承

创建一个新的enum,可以同时实现一个或多个接口

enum CartoonCharacter implements Generator<CartoonCharacter> {
    SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
    private Random rand = new Random(47);

    @Override
    public CartoonCharacter next() {
        return values()[rand.nextInt(values().length)];
    }
}

public class EnumImplementation {
    public static <T> void printNext(Generator<T> rg) {
        System.out.print(rg.next() + ", ");
    }

    public static void main(String[] args) {
        // Choose any instance:
        CartoonCharacter cc = CartoonCharacter.BOB;
        for (int i = 0; i < 10; i++) {
            printNext(cc);
        }
    }
} /*
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY,
*/

5. 使用接口组织枚举

有时希望使用子类将一个enum中的元素进行分组。在一个接口内部创建实现该接口的枚举,以此将元素分组。

public interface Food {
  enum Appetizer implements Food {
    SALAD, SOUP, SPRING_ROLLS;
  }
  enum MainCourse implements Food {
    LASAGNE, BURRITO, PAD_THAI,
    LENTILS, HUMMOUS, VINDALOO;
  }
  enum Dessert implements Food {
    TIRAMISU, GELATO, BLACK_FOREST_CAKE,
    FRUIT, CREME_CARAMEL;
  }
  enum Coffee implements Food {
    BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
    LATTE, CAPPUCCINO, TEA, HERB_TEA;
  }
}
public class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
}

6. 使用EnumSet替代标志

Set是一种集合不能添加重复元素。enum也要求其成员是唯一的。

Java SE5引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种开关信息,不过,使用这种标志,最终操作的只是一些bit。使用EnumSet的有点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。

EnumSet 包含的使用方法:

EnumSet points =EnumSet.noneOf(AlarmPoints.class); // Empty set

points.addAll() 添加所有Enum元素
EnumSet.of() 返回参数中添加的Enum元素集合
points.removeAl() 移除参数中包含的Enum元素集合
EnumSet.complementOf() 用于创建包含与指定的Enum_Set类型相同的元素的EnumSet
研究EnumSet文档,会发现of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接受2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。

7. 使用EnumMap

EnumMap要求其中的键必须来自于一个enum由于enum本身的限制,所以EnumMap内部是数组实现。因此EnumMap速度很快,可以放心进行查找操作。

EnumMaps 包含的方法

EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);

interface Command {
    void action();
}

public class EnumMaps {
    public static void main(String[] args) {
        EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
        em.put(KITCHEN, new Command() {
            public void action() {
                print("Kitchen fire!");
            }
        });
        em.put(BATHROOM, new Command() {
            public void action() {
                print("Bathroom alert!");
            }
        });
        for (Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
            printnb(e.getKey() + ": ");
            e.getValue().action();
        }
        try { // If there's no value for a particular key:
            em.get(UTILITY).action();
        } catch (Exception e) {
            print(e);
        }
    }
}

与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。

main()方法的最后部分说明,enum的每一个实例作为一键,总是存在的。但是,如果没有为这个键调用put()方法来存入相应的值的话,其对应的值就是null。

8. 常量相关的方法

在Enum中定义的元素都是Enum类中的各个实例对象。每个Enum元素都是一个Enum类型的staic final类型对象。
Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法:

public enum ConstantSpecificMethod {
    DATE_TIME {
        String getInfo() {
            return
                    DateFormat.getDateInstance().format(new Date());
        }
    },
    CLASSPATH {
        String getInfo() {
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        String getInfo() {
            return System.getProperty("java.version");
        }
    };

    abstract String getInfo();

    public static void main(String[] args) {
        for (ConstantSpecificMethod csm : values()) {
            System.out.println(csm.getInfo());
        }
    }
}

第二十章 注解

1.什么是注解

注解(元数据)在一定程度上是在把元数据与源代码文件结合在一起,为我们在代码中添加信息提供了一种形式化的方法,是我们在稍后某个时刻非常方便的使用这些数据。

Java se5内置了三种注解;

@Override,@Deprecated,@SuppressWarnings;

2.定义注解:

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {

}
上面就是注解@Test的定义。非常像接口的定义,事实上,与其他任何Java接口一样,注解也将会编译成class文件。

@Target用来定义你的注解将应用在什么地方,(例如一个方法或者一个域)。

在插入图片描述

@Retention用来定义该注解在哪一个级别可用,在源代码中(SOURCE),类文件(CLASS),或者(RUNTIME)。

@Documented注解表明制作javadoc时,是否将注解信息加入文档。如果注解在声明时使用了@Documented,则在制作javadoc时注解信息会加入javadoc。

@Inherited修饰的注解作用于一个类,则该注解将被用于该类的子类。

3.注解可用类型:

· 所有的基本类型(如int,short,char)
· String
· Class
· enum
· Annotation
· 以上类型的数组

4.获取注解中的值

public class MainTest {
 
    @Test()
    public void sys() {
 
    }
    
    
    public static void main(String[] args) {
        Class<MainTest> sd = MainTest.class;
        //判断类上面是否有注解
        boolean sj = sd.isAnnotationPresent(Test.class);
        System.out.println(sj);           
        Method[]  sf = sd.getMethods();
               
        for (Method method : sf) {
            //判断方法是否添加了@Test注释
            boolean sh = method.isAnnotationPresent(Test.class);
            if (sh) {
                //打印出注释的值
                Test sg =  method.getAnnotation(Test.class);
                System.out.println(sg.id()+"****"+sg.name());            
            }                  
        }     
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值