首页 文章 精选 留言 我的

精选列表

搜索[源码学习],共10000篇文章
优秀的个人博客,低调大师

Java多线程——Thread Runnable源码解析

Java多线程的两种实现方法大家都应该知道了:继承Thread的子类实例化和实现Runnable接口用这个接口实现类去创建Thread实例。 Java的线程在Linux平台上使用的是NPTL机制,JVM线程跟内核轻量线程(LWP)一一对应。KLT是内核线程,它提供轻量进程给程序使用,调度由操作系统内核完成,所以Java程序无法在多个线程就绪状态下预测哪个线程会获得CPU调度。 在JVM的内存分配中线程私有的部分是栈(包括Java栈和本地方法栈)和程序计数器,线程可以访问共有的Java堆和常量池、方法区(这个视虚拟机实现而定,JVM1.8里永久代被移除)。 Java线程的状态有以下这些: public enum State { NEW,//声明一个线程还没有开始 RUNNABLE,//声明一个可运行的线程。一个线程在可运行状态下在Java虚拟机中执行,但它可能在等待操作系统中的其他资源比如处理器 BLOCKED,//声明一个线程阻塞等待一个监视器锁。它需要等待监控器锁来进入一个同步的块/方法或者在调用Object.wait后重新进入一个同步的块/方法 WAITING,//声明一个等待线程。一个线程在等待状态因为调用了以下方法:Object.wait没有超时时间;Thread.join没有超时时间;LockSupport.park。一个线程在等待状态是在等待其他线程执行一个特定的行为。比如,一个线程调用Object.wait(),是在等待另一个线程对它调用Object.notify或者Object.notifyAll()。一个线程调用Thread.join()是在等待这个线程终止。 TIMED_WAITING,//线程处于限时等待状态,因为它调用了以下方法带有正数等待时间:Thread.sleep;Object.wait带有超时时间;Thread.join带有超时时间;LockSupport.parkNanos;LockSupport.parkUntil TERMINATED;//线程执行完毕,进入结束状态 } 跟系统的三状态模型相比:就绪、运行、阻塞,这是可以对应上的 Runnable Runnable是一个函数式接口@FunctionalInterface,它的实例可以通过lamda表达式,方法引用,构造器引用来创建。所谓函数式接口,该注解只能标记在"有且仅有一个抽象方法"的接口上。抽象方法是指接口中声明的方法,不包括加static关键字的静态方法和加default关键字的默认方法,也不包括重写java.lang.Object中的方法。该注解可以不加,但是如果加了编译器会对类进行检查,如果不符合要求会编译失败。 因为Runnable是一个接口,所以可以利用一个类可以实现多个接口的特点来更加灵活的构造。 Runnable接口应该被所有计划实例通过线程执行的类实现。类必须定义一个没有参数的 run方法。 此接口旨在为那些希望在它们活动时执行代码的对象提供一个通用的协议。例如,Runnable被Thread类实现。活动的意思是说一个线程已启动并且尚未停止。 此外,Runnable给不是Thread子类的类提供一个活动的手段。一个类可以通过实现Runnable,将自己传递给实例一个Thread对象的实例化来运行。在大多数情况下,Runnable接口应该是用于你只打算重写run()方法并没有重写其他Thread方法。这是重要的因为除非程序员打算修改或增强类的基本行为,否则类不应该作为子类。 ——以上是关于Runnable的Javadoc说明内容 总结一下:在不需要对Thread方法进行修改或扩展的情况下,使用Runnable接口较好,并且可以利用实现多接口的方法增大灵活性,实现类在实例化之后传给Thread构造函数然后运行。 Thread Thread是在程序中执行的一个线程。java虚拟机允许应用程序可以有多个同时执行的线程。 每一个线程都有一个优先权。具有更高优先级的线程在优先于较低优先级的线程执行。每一个线程可能会或可能不会被标记为一个守护进程。在一些线程中运行的代码创建了一个新的Thread对象时,新线程的优先级被设置为等于创建线程的优先级,新线程是守护线程的当且仅当创建线程是一个守护进程。 当一个java虚拟机启动时,通常有一个单一的非守护线程(通常调用方法为某指定的类中命名为main的)。java虚拟机持续执行线程,直到发生以下情况: Runtime类的exit方法被调用并且安全管理器允许退出操作发生。 非守护线程的所有线程都已经死了,要么从调用到run方法返回或抛出一个异常传播到run方法。 有两种方法来创建一个新的执行线程。一是声明一个类是Thread的子类。这个子类应重写类Thread的run方法。子类的一个实例可以被分配和启动。例如,一个线程计算大于规定值的素数可以写成如下: class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } 下面的代码将创建一个线程并开始运行它: PrimeThread p = new PrimeThread(143); p.start(); 创建一个线程的另一个方法是声明一个类实现Runnable接口。该类实现run方法。这个类的一个实例可以分配,创建Thread时作为一个参数传递,并开始。同样的例子在这个方式下如下: class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } 下面的代码将创建一个线程并开始运行: PrimeRun p = new PrimeRun(143); new Thread(p).start(); 每一个线程都有一个用于识别的名称。可能有一个以上的线程有相同的名称。如果创建一个线程时没有指定名称,则为它生成一个新名称。 除非另有说明,通过null作为参数给类的构造函数或方法会导致一个NullPointerException被抛出。 ——以上是对Thread的Javadoc说明内容 Thread这个类比较难以分析的地方在于绝大部分方法实际上是直接与操作系统内核间的交互,因为需要直接操作轻量级的内核线程,所以绝大部分方法实现都是基于native方法的。以下分析主要是基于使用角度上的分析,native方法分析鉴于目前拙劣的系统内核知识和c++水平暂时无法开展。 构造函数 构造函数最多有4个参数: group,线程组,参数为null的话,如果有安全管理器,组由SecurityManager.getThreadGroup()来决定。如果没有安全管理器或者前面这个方法返回是null,组设为当前线程的组。 target,线程启动时哪个对象的run方法会被调用。如果该对象为null,调用这个线程的run方法。 name,新线程的名字 stackSize,为新线程请求的栈大小,如果是0的话说明这个参数可以被忽略 public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize); } 根据使用参数的不同存在不同的重载,但是stackSize只有在4个参数全在时才可以使用,group不能单独使用。线程名如果不给出则自动生成一个,其它的参数默认为null,stackSize默认为0 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); } public Thread(String name) { init(null, null, name, 0); } public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } public Thread(Runnable target, String name) { init(null, target, name, 0); } public Thread(ThreadGroup group, Runnable target, String name) { init(group, target, name, 0); } 还有一种构造函数式继承给出的AccessControlContext,这不是一个public构造函数 Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); } 从构造函数中可以看到调用了init方法来初始化线程。其实主要做的就是存储各参数到本地变量里,比较麻烦一点的是获取线程组,如果没有给定线程组的话需要从安全管理器中获得,还有从父线程继承上下文类加载器、优先级、是否是守护线程等变量。另外,线程ID和线程名是两个不同的变量。 private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) {//线程名不能为null throw new NullPointerException("name cannot be null"); } this.name = name;//设置线程名,这里可以看出线程名指定的话可能重复 Thread parent = currentThread();//返回当前线程 SecurityManager security = System.getSecurityManager(); if (g == null) {//没有指定线程组 /* Determine if it's an applet or not确定是否是小型应用程序 */ /* If there is a security manager, ask the security manager what to do.如果有安全管理器,询问安全管理器做什么 */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group.如果安全管理器对于问题没有强硬的意见,使用父线程的线程组 */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in.不管线程组是否是明确被传递进来,都需要检查访问权限 */ g.checkAccess(); /* * Do we have the required permissions?是否有要求的权限 */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted();//增加未开始线程的数量 this.group = g; this.daemon = parent.isDaemon();//如果父线程是守护线程,子线程也是,否则不是 this.priority = parent.getPriority();//优先级为父线程的优先级 if (security == null || isCCLOverridden(parent.getClass()))//没有安全管理器或者父类没有重写安全敏感的方法 this.contextClassLoader = parent.getContextClassLoader();//返回的还是父类的上下文类加载器 else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority);//调用底层方法修改线程的优先级 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//可以从父类继承线程本地变量 /* Stash the specified stack size in case the VM cares存储给定的栈大小以免虚拟机需要 */ this.stackSize = stackSize; /* Set thread ID设置线程ID */ tid = nextThreadID(); } start start方法会令一个新的线程进入就绪状态,我们无法知道这个线程到底什么时候会被执行,由java虚拟机调用这个线程的 run方法。这个方法的结果是,两个线程同时运行:当前线程(从调用start方法返回)和其他的线程(执行run方法)。启动线程超过一次永远是不合法的。特别的,一个线程一旦已经完成执行,它不能再被重新启动。 public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); /* 通知线程组这个线程已经准备好被启动,这样它可以被添加到组的线程列表中并且组的未启动计数器会缩减 */ group.add(this); boolean started = false; try { start0();//调用底层方法启动线程 started = true; } finally { try { if (!started) { group.threadStartFailed(this);//通知线程组尝试启动线程失败了 } } catch (Throwable ignore) { /* 什么都不做,这样start0抛出的Throwable会被传递到调用栈 */ } } } run 如果这个线程使用一个单独的Runnable对象来构造运行对象,那么这个Runnable的run方法会被调用,否则这个方法什么也不做世界返回。如果是Thread的子类需要重写这个方法。不同于start是启动一个线程来执行run方法,直接调用run是由当前线程来进行同步调用run,不会创建新的线程。 public void run() { if (target != null) { target.run(); } } yield yield是另这个线程给调度器一个提示,当前线程愿意放弃当前对处理器的使用。调度器可以直接忽视这个提示。yield是一个启发式的尝试以改善线程之间的相对进展,否则将过度使用一个CPU。它的使用应结合详细的分析和确定基准,以确保它实际上有所需的效果。使用这种方法很少是恰当的。它可能是有调试或测试的目的,它可能有助于重现由于竞争环境的错误。在设计并发控制结构如在java.util.concurrent.locks包中的时候可能是有用的。yield会使线程让出当前时间片进入就绪状态,但是由于还在RUNNABLE状态,我们不能预知这个线程会不会由于调度立刻再次开始运行。 public static native void yield(); sleep 引起当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据系统定时器精度和调度的准确性。线程不失去任何监视器的所有权。也就是当前线程会进入限时等待状态但所持有的锁对象不会被释放。 public static native void sleep(long millis) throws InterruptedException; public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); } wait和notify 尽管Thread并没有重写Object中的这个方法,但依然是线程的常用方法,它的作用是让线程进入等待状态,是否是限时等待取决于时间参数,和sleep相比,wait会释放线程持有的监视器锁。 notify()可以让一个等待的线程结束等待状态转入Runnable状态,如果有多个线程的话由系统内核任意选一个线程唤醒。notifyAll()则唤醒所有的等待线程。这里的唤醒代码如果退出之前在一个同步块或执行一个同步方法,需要检查当前能否获得锁。 interrupt 中断这个线程。如果当前线程中断本身,这总是允许的。否则要被中断线程的checkAccess方法被调用,这可能会导致SecurityException被抛出。如果线程在调用Object类的wait(),wait(long),或wait(long, int)方法阻塞,或者调用这个类的join(),join(long),join(long, int),sleep(long),或sleep(long, int)方法导致阻塞,那么它的中断状态将被清除,它会收到InterruptedException。如果该线程在I/O操作被阻塞是因为一个InterruptibleChannel,则通道将被关闭,该线程的中断状态将被设置,并且线程将获得ClosedByInterruptException。如果该线程是在一个Selector被阻塞,然后该线程的中断状态将被设置,它会立即从选择操作返回一个可能是非零的值,就像选择器的wakeup方法调用。如果前面的条件都没有成立,那么这个线程的中断状态将被设置。中断一个不再活动的线程没有任何效果。阻塞状态的进程调用interrupt会抛出ClosedByInterruptException,然后线程终止。 public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // 只是设置一个中断标记 b.interrupt(this); return; } } interrupt0(); } 下面两个方法检查当前线程是否被中断,区别在于是否会把这个结果重置为false。一个线程中断因为当时线程已经不再活动而被无视的情况会通过这个方法返回false来反应 public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); } join 假设a是一个线程,如果一个线程b调用a.join(),那么线程b会进入等待状态,直到监视到a线程终止了才会继续进行下去,不输入等待时间或者0就会在线程活动时一直等待下去。因为调用的是wait,所以会释放当前进程的监控器锁。这个方法经常用于一个线程统计多个线程计算结果这样子的情况,需要等待计算进程全部结束。 public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0);//只要线程还活着就wait下去 } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay);//wait有限的时间 now = System.currentTimeMillis() - base; } } } getState getState方法获取线程的状态,设计是用于系统状态监控而不是用于同步保证。上面那个情形,如果使用这个方法,可以达到循环检测哪个线程已经结束,就先统计它的结果这样的异步操作。但这样会存在一个问题是,检测的线程一直占着时间片,如果长时间没有计算完的线程,会浪费处理器时间。 public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); }

优秀的个人博客,低调大师

Java序列化 ObjectInputStream源码解析

上一篇讲了类的序列化,今天要讲类的反序列化,ObjectInputStream。 从内部变量中我们可以看出,内部包含一个块输入流,因为有handle机制所以也有一个内部缓存表但不是hash表 /** 处理数据块转换的过滤流 */ private final BlockDataInputStream bin; /** 确认调用返回列表 */ private final ValidationList vlist; /** 递归深度 */ private long depth; /** 对任何种类的对象、类、枚举、代理的引用总数 */ private long totalObjectRefs; /** 流是否关闭 */ private boolean closed; /** 线柄->obj/exception映射 */ private final HandleTable handles; /** 传递句柄值上下调用栈的草稿区 */ private int passHandle = NULL_HANDLE; /** 当在字段值末尾因没有TC_ENDBLOCKDATA阻塞时设置的标志 */ private boolean defaultDataEnd = false; /** b读取原始字段值的缓冲区 */ private byte[] primVals; /** 如果为true,调用readObjectOverride()来替代readObject() */ private final boolean enableOverride; /** if true, invoke resolveObject()如果为true调用resolveObject() */ private boolean enableResolve; /** * 向上调用类定义的readObject方法期间的上下文,保持当前在被反序列化的对象和当前类的描述符。非readObject向上调用期间为null。 */ private SerialCallbackContext curContext; /** * 类描述符过滤器和从流中读取的类,可能是null */ private ObjectInputFilter serialFilter; 从构造函数中可以看出,同样需要验证类的安全性,同时在初始化时会自动读取Java序列化头部信息。跟输出流一样,预留了一个生成全空的类输入流初始化方法,用于继承ObjectInputStream的子类进行重写。安全验证和输出流中是相同的,而读取头部信息需要读取魔数和版本号并验证与当前JDK中的是否一致。当然,如果两端使用的JDK中这个类版本号不一致就会出现异常。 /** * 创建一个ObjectInputStream从指定的InputStream中读取。一个序列化流头部从这个流读取和验证。这个构造器会阻塞直到对应的ObjectOutputStream已经写并刷新了头部。 * 如果安装了安全管理器,这个构造器会在被重写了ObjectInputStream.readFields或ObjectInputStream.readUnshared的子类构造器 * 直接或间接调用的时候检查enableSubclassImplementation序列化许可 * * @param in input stream to read from * @throws StreamCorruptedException 如果流头部错误 * @throws IOException 在读取流头部是发生了IO错误 * @throws SecurityException 如果不被信任的子类非法重写了安全敏感方法 * @throws NullPointerException 如果in是null */ public ObjectInputStream(InputStream in) throws IOException { verifySubclass(); bin = new BlockDataInputStream(in); handles = new HandleTable(10); vlist = new ValidationList(); serialFilter = ObjectInputFilter.Config.getSerialFilter(); enableOverride = false; readStreamHeader(); bin.setBlockDataMode(true); } protected ObjectInputStream() throws IOException, SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } bin = null; handles = null; vlist = null; serialFilter = ObjectInputFilter.Config.getSerialFilter(); enableOverride = true; } protected void readStreamHeader() throws IOException, StreamCorruptedException { short s0 = bin.readShort(); short s1 = bin.readShort(); if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) { throw new StreamCorruptedException( String.format("invalid stream header: %04X%04X", s0, s1)); } } 下面直接切入正题,来看一下readObject,读和写一样也是final方法,所以子类要重写必须通过readObjectOverride方法来完成。如果类里面定义了非基本数据类型变量,需要进行嵌套读取,outerHandle用来存储上一层读取对象的句柄,然后调用readObject0读取生成对象。完成之后要记录句柄依赖,并检查有无异常产生。最上层读取完成之后要发起回调。 public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride();//因为是final方法,子类要重写只能通过这里 } // 如果是嵌套读取,passHandle包含封闭对象的句柄 int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle);//记录句柄依赖 ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks();//最上层调用的读取完成后,发起回调 } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear();//最上层调用退出时清除调用返回列表和句柄缓存 } } } readObject0必须在块输入流内为空也就是上一次反序列化完全结束后才能开始,否则会抛出异常。首先,从流中读取一个标志位,判断当前下一个内容是什么类型,上次我们讲到过,输出的时候都会先输出TC_OBJECT,所以在默认情况下也是先读取到TC_OBJECT,也就是先执行readOrdinaryObject private Object readObject0(boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) {//之前是块输入模式且有未消费的数据会抛出异常 int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { /* * 流当前在字段值通过默认序列化块写的末尾,因为没有终止TC_ENDBLOCKDATA标记,模拟通常数据结尾的行为 */ throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) {//读到流reset标志 bin.readByte();//消耗缓存的字节 handleReset();//depth为0时执行clear方法,否则抛出异常 } depth++;//增加递归深度 totalObjectRefs++;//增加引用对象数 try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));//一般会先进入这里 case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); // force header read throw new OptionalDataException( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException( "unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException( "unexpected end of block data"); } default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } } readOrdinaryObject是读取一个自定义类最上层直接调用的方法。几个关键的点,首先是要读取类的描述信息,然后根据类描述符创建一个实例,将对象实例存储到句柄缓存中并将句柄存储到passHandle,然后读取内部变量数据,最后检查有没有替换对象 private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false);//读取类描述信息 desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null;//根据类描述新建一个实例 } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj);//将对象存储到句柄缓存中 ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } //读取序列化内部变量数据 if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle);//标记句柄对应的对象读取完成 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())//存在替换对象 { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; } 那么先来看下怎么读取类的描述信息readClassDesc,从上面方法的case中不能分析出,在没有发生异常使用的情况下,此时的case只会进入TC_PROXYCLASSDESC或者TC_CLASSDESC也就是动态代理类的描述符合普通类的描述符。对于readProxyDesc读取并返回一个动态代理类的类描述符,而readNonProxyDesc则是返回一个普通类的描述符,两个方法都会设置passHandle到动态类描述符的设置句柄。如果类描述符不能被解析为本地虚拟机中的一个类,一个ClassNotFoundException会被关联到描述符的句柄中。关于动态代理和反射的部分下次要再仔细看一下。不过这里又是读取类描述,又是安全检查还有类合法性检查,跟直接在读取端指定类相比额外多出很多开销。 private ObjectStreamClass readClassDesc(boolean unshared) throws IOException { byte tc = bin.peekByte(); ObjectStreamClass descriptor; switch (tc) { case TC_NULL: descriptor = (ObjectStreamClass) readNull(); break; case TC_REFERENCE: descriptor = (ObjectStreamClass) readHandle(unshared); break; case TC_PROXYCLASSDESC: descriptor = readProxyDesc(unshared); break; case TC_CLASSDESC: descriptor = readNonProxyDesc(unshared); break; default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } if (descriptor != null) { validateDescriptor(descriptor); } return descriptor; } private ObjectStreamClass readProxyDesc(boolean unshared) throws IOException { if (bin.readByte() != TC_PROXYCLASSDESC) { throw new InternalError(); } ObjectStreamClass desc = new ObjectStreamClass(); int descHandle = handles.assign(unshared ? unsharedMarker : desc);//添加描述符到句柄缓存中 passHandle = NULL_HANDLE; int numIfaces = bin.readInt();//接口实现类数量 if (numIfaces > 65535) { throw new InvalidObjectException("interface limit exceeded: " + numIfaces); } String[] ifaces = new String[numIfaces]; for (int i = 0; i < numIfaces; i++) { ifaces[i] = bin.readUTF();//读取接口名 } Class<?> cl = null; ClassNotFoundException resolveEx = null; bin.setBlockDataMode(true); try { if ((cl = resolveProxyClass(ifaces)) == null) { resolveEx = new ClassNotFoundException("null class"); } else if (!Proxy.isProxyClass(cl)) { throw new InvalidClassException("Not a proxy"); } else { //这个检查等价于isCustomSubclass ReflectUtil.checkProxyPackageAccess( getClass().getClassLoader(), cl.getInterfaces()); // Filter the interfaces过滤接口 for (Class<?> clazz : cl.getInterfaces()) { filterCheck(clazz, -1); } } } catch (ClassNotFoundException ex) { resolveEx = ex; } // Call filterCheck on the class before reading anything else filterCheck(cl, -1); skipCustomData(); try { totalObjectRefs++; depth++; desc.initProxy(cl, resolveEx, readClassDesc(false));//初始化类描述符 } finally { depth--; } handles.finish(descHandle); passHandle = descHandle; return desc; } private ObjectStreamClass readNonProxyDesc(boolean unshared) throws IOException { if (bin.readByte() != TC_CLASSDESC) { throw new InternalError(); } ObjectStreamClass desc = new ObjectStreamClass(); int descHandle = handles.assign(unshared ? unsharedMarker : desc);//将描述符存储到句柄缓存中,返回句柄值 passHandle = NULL_HANDLE; ObjectStreamClass readDesc = null; try { readDesc = readClassDescriptor(); } catch (ClassNotFoundException ex) { throw (IOException) new InvalidClassException( "failed to read class descriptor").initCause(ex); } Class<?> cl = null; ClassNotFoundException resolveEx = null; bin.setBlockDataMode(true); final boolean checksRequired = isCustomSubclass(); try { if ((cl = resolveClass(readDesc)) == null) {//通过类描述解析类 resolveEx = new ClassNotFoundException("null class"); } else if (checksRequired) { ReflectUtil.checkPackageAccess(cl); } } catch (ClassNotFoundException ex) { resolveEx = ex; } // Call filterCheck on the class before reading anything else filterCheck(cl, -1); skipCustomData(); try { totalObjectRefs++; depth++; desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));//初始化类描述符 } finally { depth--; } handles.finish(descHandle); passHandle = descHandle; return desc; } 接下来是读取序列化内部变量数据,由于readExternalData除了一些安全性的判断外,直接调用了类中的方法,所以就不看了,直接看readSerialData中默认的读取数据部分defaultReadFields。注意到基本数据类型可以直接通过字节输入流来进行读取,而非基本数据类型则需要递归调用readObject0来读取。 private void defaultReadFields(Object obj, ObjectStreamClass desc) throws IOException { Class<?> cl = desc.forClass(); if (cl != null && obj != null && !cl.isInstance(obj)) { throw new ClassCastException(); } int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize];//第一次进行需要初始化缓冲区,缓冲区不足时需要新建一个更大的缓冲区 } bin.readFully(primVals, 0, primDataSize, false);//将字节流读取到缓冲区 if (obj != null) {//从readSerialData进入这个方法时obj为null desc.setPrimFieldValues(obj, primVals); } int objHandle = passHandle; ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()]; int numPrimFields = fields.length - objVals.length; for (int i = 0; i < objVals.length; i++) { ObjectStreamField f = fields[numPrimFields + i]; objVals[i] = readObject0(f.isUnshared());//递归读取非基本数据类型类 if (f.getField() != null) { handles.markDependency(objHandle, passHandle);//记录依赖 } } if (obj != null) { desc.setObjFieldValues(obj, objVals); } passHandle = objHandle; } 然后看一下其他非基本数据类的读取。readNull读取null代码并将句柄设置为NULL_HANDLE,readString读取UTF编码,readArray需要先读取数组长度,然后根据类的类型进行读取。 private Object readNull() throws IOException { if (bin.readByte() != TC_NULL) { throw new InternalError(); } passHandle = NULL_HANDLE; return null; } private String readString(boolean unshared) throws IOException { String str; byte tc = bin.readByte(); switch (tc) { case TC_STRING: str = bin.readUTF(); break; case TC_LONGSTRING: str = bin.readLongUTF(); break; default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } passHandle = handles.assign(unshared ? unsharedMarker : str); handles.finish(passHandle); return str; } private Object readArray(boolean unshared) throws IOException { if (bin.readByte() != TC_ARRAY) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); int len = bin.readInt(); filterCheck(desc.forClass(), len); Object array = null; Class<?> cl, ccl = null; if ((cl = desc.forClass()) != null) { ccl = cl.getComponentType(); array = Array.newInstance(ccl, len); } int arrayHandle = handles.assign(unshared ? unsharedMarker : array); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(arrayHandle, resolveEx); } if (ccl == null) { for (int i = 0; i < len; i++) { readObject0(false); } } else if (ccl.isPrimitive()) { if (ccl == Integer.TYPE) { bin.readInts((int[]) array, 0, len); } else if (ccl == Byte.TYPE) { bin.readFully((byte[]) array, 0, len, true); } else if (ccl == Long.TYPE) { bin.readLongs((long[]) array, 0, len); } else if (ccl == Float.TYPE) { bin.readFloats((float[]) array, 0, len); } else if (ccl == Double.TYPE) { bin.readDoubles((double[]) array, 0, len); } else if (ccl == Short.TYPE) { bin.readShorts((short[]) array, 0, len); } else if (ccl == Character.TYPE) { bin.readChars((char[]) array, 0, len); } else if (ccl == Boolean.TYPE) { bin.readBooleans((boolean[]) array, 0, len); } else { throw new InternalError(); } } else { Object[] oa = (Object[]) array; for (int i = 0; i < len; i++) { oa[i] = readObject0(false); handles.markDependency(arrayHandle, passHandle); } } handles.finish(arrayHandle); passHandle = arrayHandle; return array; } readHandle方法从缓存中寻找该对象 private Object readHandle(boolean unshared) throws IOException { if (bin.readByte() != TC_REFERENCE) { throw new InternalError(); } passHandle = bin.readInt() - baseWireHandle;//因为写的时候handle值加上了baseWireHandle if (passHandle < 0 || passHandle >= handles.size()) { throw new StreamCorruptedException( String.format("invalid handle value: %08X", passHandle + baseWireHandle)); } if (unshared) { // REMIND: what type of exception to throw here? throw new InvalidObjectException( "cannot read back reference as unshared"); } Object obj = handles.lookupObject(passHandle);//缓存中寻找该对象 if (obj == unsharedMarker) { // REMIND: what type of exception to throw here? throw new InvalidObjectException( "cannot read back reference to unshared object"); } filterCheck(null, -1); // just a check for number of references, depth, no class检查引用数量,递归深度,是否有这个类 return obj; } BlockDataInputStream 跟输出时相同,对象输入流也使用的是块输入流,输入流有两个模式:默认模式下,输入数据写入的格式和DataOutputStream相同;在块数据模式,输入的数据被块数据标记归为一类。逻辑上来说,读和写应该是相对应的,主要是要判断读取到的头部信息,然后根据不同的信息来读取实际信息。有些跟输出部分非常重复的就跳过了 内部变量主要需要注意两个不同的输入流,din是块输入流,in是取数输入流,in中的下层输入流是最初在构造ObjectInputStream中传入的变量,所以通常是FileInputStream。 /** maximum data block length最大数据块长度1K */ private static final int MAX_BLOCK_SIZE = 1024; /** maximum data block header length最大数据块头部长度 */ private static final int MAX_HEADER_SIZE = 5; /** (tunable) length of char buffer (for reading strings)可调节的字符缓冲的长度作为供读取的字符串 */ private static final int CHAR_BUF_SIZE = 256; /** readBlockHeader() return value indicating header read may block readBlockHeader()返回的值指示头部可能阻塞 */ private static final int HEADER_BLOCKED = -2; /** buffer for reading general/block data读取一般/块数据的缓冲区 */ private final byte[] buf = new byte[MAX_BLOCK_SIZE]; /** buffer for reading block data headers读取块数据头部的缓冲区 */ private final byte[] hbuf = new byte[MAX_HEADER_SIZE]; /** char buffer for fast string reads快速字符串读取的字符缓冲区 */ private final char[] cbuf = new char[CHAR_BUF_SIZE]; /** block data mode块数据模式 */ private boolean blkmode = false; // block data state fields; values meaningful only when blkmode true块数据状态字段,这些值仅在blkmode为true时有意义 /** current offset into buf当前进入buf的偏移 */ private int pos = 0; /** end offset of valid data in buf, or -1 if no more block data buf中有效值的结束偏移,没有更多块数据buf时为-1 */ private int end = -1; /** number of bytes in current block yet to be read from stream 在当前block中还没有从流中读取的字节数 */ private int unread = 0; /** underlying stream (wrapped in peekable filter stream) 下层流包装在可见过滤流中 */ private final PeekInputStream in; /** loopback stream (for data reads that span data blocks) 回路流用于扩展数据块的数据读取 */ private final DataInputStream din; 构造函数就是初始化两个流变量 BlockDataInputStream(InputStream in) { this.in = new PeekInputStream(in); din = new DataInputStream(this); } setBlockDataMode将块数据模式设为给定的模式,true是打开,off是关闭,并返回之前的模式值。如果新的模式和旧模式一样,什么都不做。如果块数据模式从打开被改为关闭时有未消费的块数据还留在流中,会抛出IllegalStateException boolean setBlockDataMode(boolean newmode) throws IOException { if (blkmode == newmode) { return blkmode; } if (newmode) {//从关闭到打开,重置块数据参数状态 pos = 0; end = 0; unread = 0; } else if (pos < end) {//从打开到关闭且有未消费的块数据 throw new IllegalStateException("unread block data"); } blkmode = newmode; return !blkmode; } skipBlockData如果在块数据模式,跳跃到数据块的当前组的末尾但不会取消块数据模式。如果不在块数据模式,抛出IllegalStateException。这个方法调用了refill,refill用块数据再装满内部缓冲区。任何buf中的数据在调用这个方法时被认为是已经被消费了。设置pos、end、unread字段值来反映有效块数据的数量。如果流中的下一个元素不是一个数据块,设置pos=0和unread=0,end=-1 void skipBlockData() throws IOException { if (!blkmode) { throw new IllegalStateException("not in block data mode"); } while (end >= 0) { refill(); } } private void refill() throws IOException { try { do { pos = 0; if (unread > 0) {//当前block中还有未读取的字节 int n = in.read(buf, 0, Math.min(unread, MAX_BLOCK_SIZE));//读取unread和最大数据块长度中的较小值的数据到buf中 if (n >= 0) { end = n;//end为读取到的字节数 unread -= n;//当前block中还没读取的字节减少n } else { throw new StreamCorruptedException( "unexpected EOF in middle of data block"); } } else { int n = readBlockHeader(true); if (n >= 0) { end = 0; unread = n; } else { end = -1; unread = 0; } } } while (pos == end);//成功读取到数据就会退出循环 } catch (IOException ex) {//出现异常说明下一个元素不是数据块 pos = 0; end = -1; unread = 0; throw ex; } } readBlockHeader尝试读取下一个块数据头部。如果canBlock是false并且一个完整的头部没有阻塞时不能被读取,返回HEADER_BLOCKED。否则如果流中下一个元素是一个块数据头部,返回头部标识的这个块数据的长度,否则返回-1 private int readBlockHeader(boolean canBlock) throws IOException { if (defaultDataEnd) { /* * 流当前在一个字段值块通过默认序列化写入的末尾。因为没有终点TC_ENDBLOCKDATA标志, * 明确地模仿常规数据结束行为。 */ return -1; } try { for (;;) { int avail = canBlock ? Integer.MAX_VALUE : in.available();//可以阻塞时avail是最大整数,不能阻塞时是流中能读取的字节数 if (avail == 0) { return HEADER_BLOCKED;//没有可读取的字节数时返回HEADER_BLOCKED } int tc = in.peek();//从流中取数 switch (tc) { case TC_BLOCKDATA://第二个字节是块数据长度 if (avail < 2) { return HEADER_BLOCKED; } in.readFully(hbuf, 0, 2); return hbuf[1] & 0xFF; case TC_BLOCKDATALONG://块字节长度需要4个字节来表示 if (avail < 5) { return HEADER_BLOCKED; } in.readFully(hbuf, 0, 5); int len = Bits.getInt(hbuf, 1);//位运算获取后4位的整数值 if (len < 0) { throw new StreamCorruptedException( "illegal block data header length: " + len); } return len; /* * TC_RESET可能发生在数据块之间。不幸的是,这个状况必须在比其他类型标志更低的级别被解析, * 因为原始数据读取可能跨越被TC_RESET分割的数据块 */ case TC_RESET: in.read();//跳过这个头部 handleReset();//重置 break; default: if (tc >= 0 && (tc < TC_BASE || tc > TC_MAX)) { throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } return -1; } } } catch (EOFException ex) { throw new StreamCorruptedException( "unexpected EOF while reading block data header"); } } 单个字节的read和peek都是基于refill来完成的,而read多字节时,最大不能超过缓冲区内剩余的字节,如果copy为true,需要先将字节读取到一个内部缓冲区再复制到目标数组,又增加开销。 int read(byte[] b, int off, int len, boolean copy) throws IOException { if (len == 0) { return 0; } else if (blkmode) { if (pos == end) { refill(); } if (end < 0) { return -1; } int nread = Math.min(len, end - pos);//最大不超过缓冲区内剩余的字节 System.arraycopy(buf, pos, b, off, nread); pos += nread; return nread; } else if (copy) {//copy模式先将数据读取到一个缓冲区,再从缓冲区复制到目标数组 int nread = in.read(buf, 0, Math.min(len, MAX_BLOCK_SIZE)); if (nread > 0) { System.arraycopy(buf, 0, b, off, nread); } return nread; } else { return in.read(b, off, len); } } readFully循环调用read直到达到要读取的字节数,如果不足会抛出EOFException public void readFully(byte[] b, int off, int len, boolean copy) throws IOException { while (len > 0) { int n = read(b, off, len, copy); if (n < 0) { throw new EOFException(); } off += n; len -= n; } } HandleTable 比起输入流来,输入流的缓存表多了一个HandleList[] deps用来存储依赖,HandleList可以理解为一个简易版的ArrayList,当数组被填满再次增加元素的时候,自动分配一个新的2倍大小的数组,把旧的元素复制过去再插入新的。另外两个数组,Object[] entries存储对象或异常,byte[] status存储对象的状态。并且这个缓存表并没有使用hash,它是依靠ObjectInputStream中的passHandle来获取位置的。 /** array mapping handle -> object status 句柄->对象状态的数组映射*/ byte[] status; /** array mapping handle -> object/exception (depending on status) */ Object[] entries; /** array mapping handle -> list of dependent handles (if any) */ HandleList[] deps; /** lowest unresolved dependency 最低的未解决依赖*/ int lowDep = -1; /** number of handles in table */ int size = 0; 从assign我们可以看到,对象和它的状态(默认为UNKNOWN)被顺序存储到了数组中,并返回下标值 int assign(Object obj) { if (size >= entries.length) { grow(); } status[size] = STATUS_UNKNOWN; entries[size] = obj; return size++; } 关于deps这个数组到底是干什么的,我们可以一步步来分析。首先,寻找deps在HandleTable中被修改的地方,除了构造时初始化以外,finish中deps对应状态为UNKNOWN状态的全部被设为NULL,clear当中deps全部为设为null,grow当中新建了一个大小是原本2倍加1的deps数组并复制了原有内容来取代之前的数组,这些都不足以分析它的用途,关键在markDependency和markException两个方法中。首先markException我们可以看到所有调用都是发生在出现ClassNotFoundException的时候,传入的参数是这个类的handle值和异常。markDependency关联一个ClassNotFoundException和当前活动句柄并传播它到其他合适的引用对象。这个特定的句柄必须是打开的。简单的来说,如果一个类解析失败,那么添加到entries缓存里的对象会是一个异常,它对应的status为STATUS_EXCEPTION void markException(int handle, ClassNotFoundException ex) { switch (status[handle]) { case STATUS_UNKNOWN: status[handle] = STATUS_EXCEPTION;//标记当前对象状态为异常 entries[handle] = ex;//修改缓存中的对象为异常 // propagate exception to dependents传播异常给依赖 HandleList dlist = deps[handle]; if (dlist != null) { int ndeps = dlist.size(); for (int i = 0; i < ndeps; i++) { markException(dlist.get(i), ex);//递归传播依赖列表内的所有对象 } deps[handle] = null;//清除依赖表中的对象,因为只要再有对象关联到相同类它必然是会从缓存中获取异常,不再需要依赖表项 } break; case STATUS_EXCEPTION: break; default: throw new InternalError(); } } 再来看一下markDependency的调用,一处是在readObject调用readObject0之后,传入参数是上一层对象的句柄和本层对象的句柄;一处是在readArray中读取非基本数据类型调用readObject0之后,传入参数也是上一层对象的句柄和本层对象的句柄;一处是在defaultReadFields中递归读取非基本数据类中,传入参数也是上一层对象的句柄和本层对象的句柄。总之,根据上面这几个例子再结合方法的注释,我们可以推测出这里deps的作用是在一层层读取对象时,比如自定义类中又又自定义类,记录下一层指向上一层的关系,用来传递异常状态。很明显,从代码中可以看出,在存在依赖双方的情况下,如果上一层状态是UNKNOWN,下一层状态是EXCEPTION,则从依次向上将整个依赖表上所有的状态全部置为EXCEPTION,如果下一层也是UNKNOWN状态,则在下一层的依赖表中增加上一层的句柄,并将未知状态最底层设为target。如果上一层状态是OK,则出现InternalError,因为上一层的句柄已经被关闭,不能再增加对它依赖的对象。 void markDependency(int dependent, int target) { if (dependent == NULL_HANDLE || target == NULL_HANDLE) { return; } switch (status[dependent]) { case STATUS_UNKNOWN: switch (status[target]) { case STATUS_OK: // ignore dependencies on objs with no exception忽略没有异常的对象上依赖 break; case STATUS_EXCEPTION: // eagerly propagate exception急切传播的异常 markException(dependent, (ClassNotFoundException) entries[target]);//如果依赖是未知状态也需将它们改为异常状态 break; case STATUS_UNKNOWN: // add to dependency list of target增加到依赖目标列表 if (deps[target] == null) { deps[target] = new HandleList(); } deps[target].add(dependent); // remember lowest unresolved target seen记录被看见最低的未解决目标 if (lowDep < 0 || lowDep > target) { lowDep = target; } break; default: throw new InternalError(); } break; case STATUS_EXCEPTION: break; default: throw new InternalError(); } } finish是关闭句柄,是唯一可以将状态设为STATUS_OK的方法,标记给出的句柄为结束状态,说明这个句柄不会有新的依赖标记。调用设置和结束方法必须以后进先出的顺序。必须要handle之前不存在无法确认的状态才能修改状态为OK void finish(int handle) { int end; if (lowDep < 0) { // 没有还没处理的未知状态,只需要解决当前句柄 end = handle + 1; } else if (lowDep >= handle) { // handle之后存在还没有处理的未知状态,但上层句柄都已经解决了 end = size; lowDep = -1; } else { // handle之前还有没有处理的向前引用,还不能解决所有对象 return; } // change STATUS_UNKNOWN -> STATUS_OK in selected span of handles for (int i = handle; i < end; i++) { switch (status[i]) { case STATUS_UNKNOWN: status[i] = STATUS_OK; deps[i] = null; break; case STATUS_OK: case STATUS_EXCEPTION: break; default: throw new InternalError(); } } }

优秀的个人博客,低调大师

Java序列化 ObjectOutputStream源码解析

概述 众所周知,Java原生的序列化方法可以分为两种: 实现Serializable接口 实现Externalizable接口 其实还有一种,可以完全自己实现转为二进制内容,用Unsafe写到内存里面,然后写入文件 Serializable 可以使用ObjectStream默认实现的writeObject和readObject方法并且可以通过transit关键字来使得变量不被序列化,开发简单 除了输出协议和包名类名外,会额外输出类的变量信息 有缓存机制,对于重复对象会直接输出所在位置,所以类较大且重复内容多时反而效率高,但会消耗额外内存空间 如果父类没有无参构造函数则不会序列化父类 Externalizable 必须完全由自己来实现序列化规则所以可以直接控制哪些变量需要序列化,所以开发工作量较大 可以自己决定输出内容,只会固定输出协议和包名类名,较为简洁,对于小对象的序列化Externalizable会快一些 必须有无参构造函数否则编译会出错 ​ 但是,普遍实际项目开发中对于原生序列化的使用非常少,我觉得这里面的主要原因还是出在原生的对象流本身设计上一些是否安全的判断过多,加上缓冲区本身大小只有1K有点小,很明显一个16K的对象一次写入硬盘是比1K*16次快很多。尤其是大多数情况下重复对象判断就是在浪费时间,比如一个网站的一条用户信息,根本不会有几个重复字段。所以在很多网上的性能测试案例中,Serializable ​ 因为对象流篇幅过长,加上很多内容是系统安全或者是分隔符标志之类的东西,下面就只挑重点来说。 ObjectOutputStream 先看一眼内部变量一大堆,光看注释根本不知道是干吗用的。大致分类一下,内部类Caches用于安全审计缓存。一面一块是用于输出的部分,bout是下层输出流,两个表是用于记录已输出对象的缓存便于之前说的重复输出的时候输出上一个相同内容的位置。接下来两个是writeObject()/writeExternal()上行调用记录上下文用的。debugInfoStack用于存储错误信息。 private static class Caches { /** cache of subclass security audit results 子类安全审计结果缓存*/ static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = new ConcurrentHashMap<>(); /** queue for WeakReferences to audited subclasses 对审计子类弱引用的队列*/ static final ReferenceQueue<Class<?>> subclassAuditsQueue = new ReferenceQueue<>(); } /** filter stream for handling block data conversion 解决块数据转换的过滤流*/ private final BlockDataOutputStream bout; /** obj -> wire handle map obj->线性句柄映射*/ private final HandleTable handles; /** obj -> replacement obj map obj->替代obj映射*/ private final ReplaceTable subs; /** stream protocol version 流协议版本*/ private int protocol = PROTOCOL_VERSION_2; /** recursion depth 递归深度*/ private int depth; /** buffer for writing primitive field values 写基本数据类型字段值缓冲区*/ private byte[] primVals; /** if true, invoke writeObjectOverride() instead of writeObject() 如果为true,调用writeObjectOverride()来替代writeObject()*/ private final boolean enableOverride; /** if true, invoke replaceObject() 如果为true,调用replaceObject()*/ private boolean enableReplace; //下面的值只在上行调用writeObject()/writeExternal()时有效 /** * 上行调用类定义的writeObject方法时的上下文,持有当前被序列化的对象和当前对象描述符。在非writeObject上行调用时为null */ private SerialCallbackContext curContext; /** current PutField object 当前PutField对象*/ private PutFieldImpl curPut; /** custom storage for debug trace info 常规存储用于debug追踪信息*/ private final DebugTraceInfoStack debugInfoStack; 构造函数有两个,第一个是自身的构造需要提供一个输出流,第二个实际上是提供给子类用的,创建一个自身相关内部变量全为空的对象输出流。但是,两个构造器都会进行安全检查,检查序列化的类是否重写了安全敏感方法,如果违反了规则会抛出异常。正常的构造类还会直接输出头部信息,包括对象输出流的魔数和协议版本信息,所以即使只新建一个对象输出流就会输出头部信息。 /** * 创建一个ObjectOutputStream写到指定的OutputStream。这个构造器写序列化流头部到下层流中, * 调用者可能希望立即刷新流来确保接收的ObjectInputStreams构造器不会再读取头部时阻塞。 * 如果一个安全管理器被安装,这个构造器将会在被直接调用和被子类的构造器间接调用时检查enableSubclassImplementation序列化许可, * 如果这个子类重写了ObjectOutputStream.putFields或者ObjectOutputStream.writeUnshared方法 */ public ObjectOutputStream(OutputStream out) throws IOException { verifySubclass(); bout = new BlockDataOutputStream(out);//通过下层流out创建一个块输出流 handles = new HandleTable(10, (float) 3.00); subs = new ReplaceTable(10, (float) 3.00); enableOverride = false; writeStreamHeader(); bout.setBlockDataMode(true);//默认采用块模式 if (extendedDebugInfo) { debugInfoStack = new DebugTraceInfoStack(); } else { debugInfoStack = null; } } /** * 给子类提供一个路径完全重新实现ObjectOutputStream,不会分配任何用于实现ObjectOutputStream的私有数据 * 如果安装了一个安全管理器,这个方法会先调用安全管理器的checkPermission方法来检查序列化许可来确保可以使用子类 */ protected ObjectOutputStream() throws IOException, SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } bout = null; handles = null; subs = null; enableOverride = true; debugInfoStack = null; } /** * 验证这个实例(可能是子类)可以不用违背安全约束被构造:子类不能重写安全敏感的非final方法,或者其他enableSubclassImplementation序列化许可检查 * 这个检查会增加运行时开支 */ private void verifySubclass() { Class<?> cl = getClass(); if (cl == ObjectOutputStream.class) { return;//不是子类直接返回 } SecurityManager sm = System.getSecurityManager(); if (sm == null) { return;//没有安全管理器直接返回 } processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);//从弱引用队列中出队所有类,并移除缓存中相同的类 WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue); Boolean result = Caches.subclassAudits.get(key);//缓存中是否已有这个类 if (result == null) { result = Boolean.valueOf(auditSubclass(cl));//检查这个子类是否安全 Caches.subclassAudits.putIfAbsent(key, result);//将结果存储到缓存 } if (result.booleanValue()) { return;//子类安全直接返回 } sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);//检查子类实现许可 } /** * 提供writeStreamHeader方法这样子类可以扩展或者预先考虑它们自己的流头部。 * 这个方法写魔数和版本到流中。 * * @throws IOException if I/O errors occur while writing to the underlying * stream */ protected void writeStreamHeader() throws IOException { bout.writeShort(STREAM_MAGIC);//流魔数 bout.writeShort(STREAM_VERSION);//流版本 } 接下来开始关键部分,来分析writeObject到底做了什么,首先看这个方法本身是final方法也就是说即使继承了ObjectOutputStream也不能重写这个方法而是重写writeObjectOverride并且enableOverride=true public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj);//如果流子类重写了writeObject则调用这里的方法 return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } } writeObject0这个方法代码很长,一部分一部分来看,首先我们注意到上面的都是缓存替换部分,第一次进入这个方法是不需要考虑的,直接看到writeOrdinaryObject这里,因为用于数据化的类是实现了Serializable接口,所以会进入这个分支。 private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false);//将输出流设置为非块模式 depth++;//增加递归深度 try { // handle previously written and non-replaceable objects处理之前写的不可替换对象 int h; if ((obj = subs.lookup(obj)) == null) { writeNull();//替代对象映射中这个对象为null时,写入null代码 return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h);//不是非共享模式且这个对象在对句柄的映射表中已有缓存,写入该对象在缓存中的句柄值 return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared);//写类名 return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared);//写类描述 return; } // check for replacement object检查替代对象,要求对象重写了writeReplace方法 Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj);//如果不重写这个方法直接返回了obj也就是什么也没做 if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // if object replaced, run through original checks a second time如果对象被替换,第二次运行原本的检查,大部分情况下不执行此段 if (obj != orig) { subs.assign(orig, obj);//将原本对象和替代对象作为一个键值对存入缓存 if (obj == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } } // remaining cases剩下的情况 if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared);//传入流的对象第一次执行这个方法 } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } } writeOrdinaryObject这个方法主要是在Externalizable和Serializable的接口出现分支,如果实现了Externalizable接口并且类描述符非动态代理,则执行writeExternalData,否则执行writeSerialData。同时,这个方法会写类描述信息。 private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException { if (extendedDebugInfo) { debugInfoStack.push( (depth == 1 ? "root " : "") + "object (class \"" + obj.getClass().getName() + "\", " + obj.toString() + ")"); } try { desc.checkSerialize(); bout.writeByte(TC_OBJECT); writeClassDesc(desc, false);//写类描述 handles.assign(unshared ? null : obj);//如果是share模式把这个对象加入缓存 if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); } } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } writeExternalData和writeSerialData(Object, ObjectStreamClass)这里有个上下文的操作,目的是保证序列化操作同一时间只能由一个线程调用。前者直接调用writeExternal,后者如果重写了writeObject则调用它,否则调用defaultWriteFields。defaultWriteFields会先输出基本数据类型,对于非基本数据类型的部分会再递归调用writeObject0,所以这里也就会增加递归深度depth。 private void writeExternalData(Externalizable obj) throws IOException { PutFieldImpl oldPut = curPut; curPut = null; if (extendedDebugInfo) { debugInfoStack.push("writeExternal data"); } SerialCallbackContext oldContext = curContext;//存储上下文 try { curContext = null; if (protocol == PROTOCOL_VERSION_1) { obj.writeExternal(this); } else {//默认协议是2,所以会使用块输出流 bout.setBlockDataMode(true); obj.writeExternal(this);//这里取决于类的方法怎么实现 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); } } finally { curContext = oldContext;//恢复上下文 if (extendedDebugInfo) { debugInfoStack.pop(); } } curPut = oldPut; } private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); for (int i = 0; i < slots.length; i++) { ObjectStreamClass slotDesc = slots[i].desc; if (slotDesc.hasWriteObjectMethod()) {//重写了writeObject方法 PutFieldImpl oldPut = curPut; curPut = null; SerialCallbackContext oldContext = curContext; if (extendedDebugInfo) { debugInfoStack.push( "custom writeObject data (class \"" + slotDesc.getName() + "\")"); } try { curContext = new SerialCallbackContext(obj, slotDesc); bout.setBlockDataMode(true); slotDesc.invokeWriteObject(obj, this);//调用writeObject方法 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); } finally { curContext.setUsed(); curContext = oldContext; if (extendedDebugInfo) { debugInfoStack.pop(); } } curPut = oldPut; } else { defaultWriteFields(obj, slotDesc);//如果没有重写writeObject则输出默认内容 } } } private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException { Class<?> cl = desc.forClass(); if (cl != null && obj != null && !cl.isInstance(obj)) { throw new ClassCastException(); } desc.checkDefaultSerialize(); int primDataSize = desc.getPrimDataSize(); if (primVals == null || primVals.length < primDataSize) { primVals = new byte[primDataSize]; } desc.getPrimFieldValues(obj, primVals);//将基本类型数据的字段值存入缓冲区 bout.write(primVals, 0, primDataSize, false);//输出缓冲区内容 ObjectStreamField[] fields = desc.getFields(false); Object[] objVals = new Object[desc.getNumObjFields()];//获取非基本数据类型对象 int numPrimFields = fields.length - objVals.length; desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; i++) { if (extendedDebugInfo) { debugInfoStack.push( "field (class \"" + desc.getName() + "\", name: \"" + fields[numPrimFields + i].getName() + "\", type: \"" + fields[numPrimFields + i].getType() + "\")"); } try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());//递归输出 } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } } } 然后看一下类描述信息是怎么写的,动态代理类和普通类有一些区别,但都是先写这个类本身的信息再写入父类的信息。 private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { int handle; if (desc == null) { writeNull();//描述符不存在时写null } else if (!unshared && (handle = handles.lookup(desc)) != -1) { writeHandle(handle);//共享模式且缓存中已有该类描述符时,写对应句柄值 } else if (desc.isProxy()) { writeProxyDesc(desc, unshared);//描述符是动态代理类时 } else { writeNonProxyDesc(desc, unshared);//描述符是标准类时 } } private void writeProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_PROXYCLASSDESC); handles.assign(unshared ? null : desc);//存入缓存 //获取类实现的接口,然后写入接口个数和接口名 Class<?> cl = desc.forClass(); Class<?>[] ifaces = cl.getInterfaces(); bout.writeInt(ifaces.length); for (int i = 0; i < ifaces.length; i++) { bout.writeUTF(ifaces[i].getName()); } bout.setBlockDataMode(true); if (cl != null && isCustomSubclass()) { ReflectUtil.checkPackageAccess(cl); } annotateProxyClass(cl);//装配动态代理类,子类可以重写这个方法存储类信息到流中,默认什么也不做 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); writeClassDesc(desc.getSuperDesc(), false);//写入父类的描述符 } private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_CLASSDESC); handles.assign(unshared ? null : desc); if (protocol == PROTOCOL_VERSION_1) { // do not invoke class descriptor write hook with old protocol desc.writeNonProxy(this); } else { writeClassDescriptor(desc); } Class<?> cl = desc.forClass(); bout.setBlockDataMode(true); if (cl != null && isCustomSubclass()) { ReflectUtil.checkPackageAccess(cl); } annotateClass(cl);//子类可以重写这个方法存储类信息到流中,默认什么也不做 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); writeClassDesc(desc.getSuperDesc(), false);//写入父类的描述信息 } 最后看一下几个写方法,写字符串是写入UTF编码的二进制流数据。写枚举会额外写入一次枚举的类描述,然后将枚举名作为字符串写入。如果是写一个数组,先写入数组长度,然后如果数组是基本数据类型则可以直接写入,否则需要递归调用writeObject0 private void writeString(String str, boolean unshared) throws IOException { handles.assign(unshared ? null : str); long utflen = bout.getUTFLength(str);//获得UTF编码长度 if (utflen <= 0xFFFF) { bout.writeByte(TC_STRING); bout.writeUTF(str, utflen); } else { bout.writeByte(TC_LONGSTRING); bout.writeLongUTF(str, utflen); } } private void writeEnum(Enum<?> en, ObjectStreamClass desc, boolean unshared) throws IOException { bout.writeByte(TC_ENUM); ObjectStreamClass sdesc = desc.getSuperDesc(); writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); handles.assign(unshared ? null : en); writeString(en.name(), false); } BlockDataOutputStream BlockDataOutputStream是一个内部类,它继承了OutputStream并实现了DataOutput接口,缓冲输出流有两种模式:在默认模式下,输出数据和DataOutputStream使用同样模式;在块数据模式下,使用一个缓冲区来缓存数据到达最大长度或者手动刷新时将内容写入下层输入流,这点和BufferedOutputStream类似。不同之处在于,块模式在写数据之前,要先写入一个头部来表示当前块的长度。 从内部变量和构造函数中可以看出,缓冲区的大小是固定且不可修改的,其中包含了一个下层输入流和一个数据输出流以及是否采用块模式的标识,在构造时默认不采用块数据模式。 /** maximum data block length 最大数据块长度1K*/ private static final int MAX_BLOCK_SIZE = 1024; /** maximum data block header length 最大数据块头部长度*/ private static final int MAX_HEADER_SIZE = 5; /** (tunable) length of char buffer (for writing strings) 字符缓冲区的可变长度,用于写字符串*/ private static final int CHAR_BUF_SIZE = 256; /** buffer for writing general/block data 用于写一般/块数据的缓冲区*/ private final byte[] buf = new byte[MAX_BLOCK_SIZE]; /** buffer for writing block data headers 用于写块数据头部的缓冲区*/ private final byte[] hbuf = new byte[MAX_HEADER_SIZE]; /** char buffer for fast string writes 用于写快速字符串的缓冲区*/ private final char[] cbuf = new char[CHAR_BUF_SIZE]; /** block data mode 块数据模式*/ private boolean blkmode = false; /** current offset into buf buf中的当前偏移量*/ private int pos = 0; /** underlying output stream 下层输出流*/ private final OutputStream out; /** loopback stream (for data writes that span data blocks) 回路流用于写跨越数据块的数据*/ private final DataOutputStream dout; /** * 在给定的下层流上创建一个BlockDataOutputStream,块数据模式默认关闭 */ BlockDataOutputStream(OutputStream out) { this.out = out; dout = new DataOutputStream(this); } setBlockDataMode可以改变当前的数据模式,从块数据模式切换到非块数据模式时,要讲缓冲区内的数据写入到下层输入流中。getBlockDataMode可以查询当前的数据模式。 /** * 设置块数据模式为给出的模式true是开启,false是关闭,并返回之前的模式值。 * 如果新的模式和旧的一样,什么都不做。 * 如果新的模式和旧的模式不同,所有的缓冲区数据要在转换到新模式之前刷新。 */ boolean setBlockDataMode(boolean mode) throws IOException { if (blkmode == mode) { return blkmode; } drain();//将缓冲区内的数据全部写入下层输入流 blkmode = mode; return !blkmode; } /** * 当前流为块数据模式返回true,否则返回false */ boolean getBlockDataMode() { return blkmode; } drain这个方法在多个方法中被调用,作用是将缓冲区内的数据全部写入下层输入流,但不会刷新下层输入流,在写入实际数据前要先用writeBlockHeader写入块头部,头部包含1字节标志位和1字节或4字节的长度大小 void drain() throws IOException { if (pos == 0) { return;//pos为0说明当前缓冲区为空 } if (blkmode) { writeBlockHeader(pos);//块数据模式下要先写入头部 } out.write(buf, 0, pos);//写入缓冲区数据 pos = 0;//缓冲区被清空 } /** * 写入块数据头部。数据块小于256字节会增加2字节头部前缀,其他会增加5字节头部。 * 第一字节是标识长度范围,因为255字节以内可以用1字节来表示长度,4字节可以表示int范围内的最大整数 */ private void writeBlockHeader(int len) throws IOException { if (len <= 0xFF) { hbuf[0] = TC_BLOCKDATA; hbuf[1] = (byte) len; out.write(hbuf, 0, 2); } else { hbuf[0] = TC_BLOCKDATALONG; Bits.putInt(hbuf, 1, len); out.write(hbuf, 0, 5); } } 下面的方法等价于他们在OutputStream中的对应方法,除了他们参与在块数据模式下写入数据到数据块中的部分有所不同。写入都需要先检查缓冲区有没有达到上限,达到时需要先刷新,然后再将数据复制到缓冲区。刷新和关闭操作都不难理解。 public void write(int b) throws IOException { if (pos >= MAX_BLOCK_SIZE) { drain();//达到块数据上限时,将缓冲区内的数据全部写入下层流 } buf[pos++] = (byte) b;//存储b到buf中 } public void write(byte[] b) throws IOException { write(b, 0, b.length, false); } public void write(byte[] b, int off, int len) throws IOException { write(b, off, len, false); } /** * 将指定的字节段从数组中写出。如果copy是true,复制值到一个中间缓冲区在将它们写入下层流之前,来避免暴露一个对原字节数组的引用 */ void write(byte[] b, int off, int len, boolean copy) throws IOException { if (!(copy || blkmode)) {// 非copy也非块数据模式直接写入下层输入流 drain(); out.write(b, off, len); return; } while (len > 0) { if (pos >= MAX_BLOCK_SIZE) { drain(); } if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) { // 长度大于缓冲区非copy模式且缓冲区为空直接写,避免不必要的复制 writeBlockHeader(MAX_BLOCK_SIZE); out.write(b, off, MAX_BLOCK_SIZE); off += MAX_BLOCK_SIZE; len -= MAX_BLOCK_SIZE; } else { //剩余内容在缓冲区内放得下或者缓冲区不为空或者是copy模式,则将数据复制到缓冲区 int wlen = Math.min(len, MAX_BLOCK_SIZE - pos); System.arraycopy(b, off, buf, pos, wlen); pos += wlen; off += wlen; len -= wlen; } } } /** * 将缓冲区数据刷新到下层流,同时会刷新下层流 */ public void flush() throws IOException { drain(); out.flush(); } /** * 刷新之后关闭下层输出流 */ public void close() throws IOException { flush(); out.close(); } 上面的方法等价于他们在DataOutputStream中的对应方法,除了他们参与在块数据模式下写入数据到数据块中部分有所不同。基本上逻辑都是先检查空间是否足够,不足的话先刷新缓冲区,然后将数据存储到缓冲区中。因为篇幅原因,这里只贴几个方法为例。写一个字符串时,需要先将字符串中的字符存储到字符缓冲数组中,然后再转换成字节存储到buf中。 public void writeBoolean(boolean v) throws IOException { if (pos >= MAX_BLOCK_SIZE) { drain(); } Bits.putBoolean(buf, pos++, v); } public void writeByte(int v) throws IOException { if (pos >= MAX_BLOCK_SIZE) { drain(); } buf[pos++] = (byte) v; } /** * 写入单个字符,块未满时存储到缓冲区,块满时调用的是BlockDataOutputStream.write(int v)方法 */ public void writeChar(int v) throws IOException { if (pos + 2 <= MAX_BLOCK_SIZE) { Bits.putChar(buf, pos, (char) v); pos += 2; } else { dout.writeChar(v); } } /** * 先将String中的内容复制到字符缓冲区,再将其中的内容转为字节复制到块数据缓冲区 */ public void writeBytes(String s) throws IOException { int endoff = s.length(); int cpos = 0;//当前字符串开始位置 int csize = 0;//当前字符串大小 for (int off = 0; off < endoff; ) { if (cpos >= csize) { cpos = 0; csize = Math.min(endoff - off, CHAR_BUF_SIZE); s.getChars(off, off + csize, cbuf, 0);//将字符串中指定位置的片段复制到字符数组缓冲区 } if (pos >= MAX_BLOCK_SIZE) { drain(); } int n = Math.min(csize - cpos, MAX_BLOCK_SIZE - pos); int stop = pos + n; while (pos < stop) { buf[pos++] = (byte) cbuf[cpos++];//将字符数组中的内容复制到块数据缓冲区 } off += n; } } 下面的方法写出连贯的原始数据值。尽管和重复调用对应的原始写方法结果相同,这些方法对于写一组原始数据值进行了效率优化。优化的方式是先计算出缓冲区内的剩余大小,计算可以写入的个数,然后直接写入而不是每次写入之前检查缓冲区是否有空间,减少判断次数。写UTF编码字符串时,如果能够提前知道编码长度,可以省去一次遍历字符串确定大小的过程,因为UTF编码中单个字符可能是一个1-3个字节不等。 void writeBooleans(boolean[] v, int off, int len) throws IOException { int endoff = off + len; while (off < endoff) { if (pos >= MAX_BLOCK_SIZE) { drain(); } int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos)); while (off < stop) {//连续存储数据到缓冲区,减少了判断缓冲区是否满的次数 Bits.putBoolean(buf, pos++, v[off++]); } } } void writeChars(char[] v, int off, int len) throws IOException { int limit = MAX_BLOCK_SIZE - 2; int endoff = off + len; while (off < endoff) { if (pos <= limit) { int avail = (MAX_BLOCK_SIZE - pos) >> 1;//一个字符=2个字节所以要除以2 int stop = Math.min(endoff, off + avail); while (off < stop) { Bits.putChar(buf, pos, v[off++]); pos += 2; } } else { dout.writeChar(v[off++]); } } } /** * 返回给定字符串在UTF编码下的字节长度 */ long getUTFLength(String s) { int len = s.length(); long utflen = 0; for (int off = 0; off < len; ) { int csize = Math.min(len - off, CHAR_BUF_SIZE); s.getChars(off, off + csize, cbuf, 0); for (int cpos = 0; cpos < csize; cpos++) { char c = cbuf[cpos]; if (c >= 0x0001 && c <= 0x007F) { utflen++; } else if (c > 0x07FF) { utflen += 3; } else { utflen += 2; } } off += csize; } return utflen; } /** * 写给定字符串的UTF格式。这个方法用于字符串的UTF编码长度已知的情况,这样可以避免提前扫描一遍字符串来确定UTF长度 */ void writeUTF(String s, long utflen) throws IOException { if (utflen > 0xFFFFL) { throw new UTFDataFormatException(); } writeShort((int) utflen);//先写长度 if (utflen == (long) s.length()) { writeBytes(s);//没有特殊字符 } else { writeUTFBody(s);//有特殊字符 } } HandleTable HandleTable是一个轻量的hash表,它的作用是缓存写过的共享class便于下次查找,内部含有3个数组,spine、next和objs。objs存储的是对象也就是class,spine是hash桶,next是冲突链表,每有一个新的元素插入需要计算它的hash值然后用spine的大小取模,找到它的链表,新对象会被插入到链表的头部,它在objs和next中对应的数据是根据加入的序号顺序存储,spine存储它的handle值也就是在另外两个数组中的下标。 /** number of mappings in table/next available handle 表中映射的个数或者下一个有效的句柄*/ private int size; /** size threshold determining when to expand hash spine 决定什么时候扩展hash脊柱的大小阈值*/ private int threshold; /** factor for computing size threshold 计算大小阈值的因子*/ private final float loadFactor; /** maps hash value -> candidate handle value 映射hash值->候选句柄值*/ private int[] spine; /** maps handle value -> next candidate handle value 映射句柄值->下一个候选句柄值*/ private int[] next; /** maps handle value -> associated object 映射句柄值->关联的对象*/ private Object[] objs; /** * 创建一个新的hash表使用给定的容量和负载因子 */ HandleTable(int initialCapacity, float loadFactor) { this.loadFactor = loadFactor; spine = new int[initialCapacity]; next = new int[initialCapacity]; objs = new Object[initialCapacity]; threshold = (int) (initialCapacity * loadFactor); clear(); } assign就是插入操作,它会检查3个数组大小是否足够,其中spine是根据next.length*负载因子来决定阈值的,数组大小扩大是乘以2加1,这个和HashTable时同样的设计。插入的时候注意到next的值被赋为原本的spine[index]值,说明之前的链表头成为了新结点的后驱,也就是说结点被插入链表头部。 /** * 分配下一个有效的句柄给给出的对象并返回句柄值。句柄从0开始升序被分配。相当于put操作 */ int assign(Object obj) { if (size >= next.length) { growEntries(); } if (size >= threshold) { growSpine(); } insert(obj, size); return size++; } /** * 通过延长条目数组增加hash表容量,next和objs大小变为旧大小*2+1 */ private void growEntries() { int newLength = (next.length << 1) + 1;//长度=旧长度*2+1 int[] newNext = new int[newLength]; System.arraycopy(next, 0, newNext, 0, size);//复制旧数组元素到新数组中 next = newNext; Object[] newObjs = new Object[newLength]; System.arraycopy(objs, 0, newObjs, 0, size); objs = newObjs; } /** * 扩展hash脊柱,等效于增加常规hash表的桶数 */ private void growSpine() { spine = new int[(spine.length << 1) + 1];//新大小=旧大小*2+1 threshold = (int) (spine.length * loadFactor);//扩展阈值=spine大小*负载因子 Arrays.fill(spine, -1);//spine中全部填充-1 for (int i = 0; i < size; i++) { insert(objs[i], i); } } /** * 插入映射对象->句柄到表中,假设表足够大来容纳新的映射 */ private void insert(Object obj, int handle) { int index = hash(obj) % spine.length;//hash值%spine数组大小 objs[handle] = obj;//objs顺序存储对象 next[handle] = spine[index];//next存储spine[index]原本的handle值,也就是说新的冲突对象插入在链表头部 spine[index] = handle;//spine中存储handle大小 } hash值计算就是通过系统函数计算出hash值然后去有符号int的有效位 private int hash(Object obj) { return System.identityHashCode(obj) & 0x7FFFFFFF;//取系统计算出的hash值得有效整数值部分 } lookup是查找hash表中是否含有指定对象,这里相等必须是==,因为class在完整类名相等时就是== /** * 查找并返回句柄值关联给与的对象,如果没有映射返回-1 */ int lookup(Object obj) { if (size == 0) { return -1; } int index = hash(obj) % spine.length;//通过hash值寻找在spine数组中的位置 for (int i = spine[index]; i >= 0; i = next[i]) { if (objs[i] == obj) {//遍历spine[index]位置的链表,必须是对象==才是相等 return i; } } return -1; } clear是清空hash表,size返回当前表中映射对数 /** * 重置表为初始状态,next不需要重新赋值是因为插入第一个元素时,原本的spine[index]一定是-1,链表中不会出现之前存在的值 */ void clear() { Arrays.fill(spine, -1); Arrays.fill(objs, 0, size, null); size = 0; } /** * 返回当前表中的映射数量 */ int size() { return size; }

优秀的个人博客,低调大师

String(JDK1.8) 源码阅读记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_40254498/article/details/82464207 String 在 Java 中字符串属于对象。 Java 提供了 String 类来创建和操作字符串。 定义 使用了final ,说明该类不能被继承。同时还实现了: java.io.Serializable Comparable CharSequence public final class String implements java.io.Serializable, Comparable<String>, CharSequence { } 属性 /** The value is used for character storage. * String就是用char[]实现的。保存的 */ private final char value[]; /** Cache the hash code for the string * hash 值 */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability * Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。 */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * 类字符串在序列化流协议中是特殊的。 * A String instance is written into an ObjectOutputStream according to * 将字符串实例写入ObjectOutputStream中,根据 a标签 * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; 构造方法 String 的构造方法大概有十几种,其中最常用的如下: /** * 根据字符串创建字符串对象 * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; } /** * 根据byte数组创建字符串对象 * byte[] to String 是根据系统的编码来的,但是也可以自己指定编码 * Constructs a new {@code String} by decoding the specified array of bytes * using the platform's default charset. The length of the new {@code * String} is a function of the charset, and hence may not be equal to the * length of the byte array. * * <p> The behavior of this constructor when the given bytes are not valid * in the default charset is unspecified. The {@link * java.nio.charset.CharsetDecoder} class should be used when more control * over the decoding process is required. * * @param bytes The bytes to be decoded into characters * @since JDK1.1 */ public String(byte bytes[]) { this(bytes, 0, bytes.length); } /** * 在Java中,String实例中保存有一个char[]字符数组,char[]字符数组是以unicode码来存储的, * String 和 char 为内存形式,byte是网络传输或存储的序列化形式。 * 所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化。 * 所以,String提供了一系列重载的构造方法来将一个字符数组转化成String, * 提到byte[]和String之间的相互转换就不得不关注编码问题。 * 例如: * public String(byte bytes[], int offset, int length, Charset charset) {} * String(byte bytes[], String charsetName) * String(byte bytes[], int offset, int length, String charsetName) * and so on * String(byte[] bytes, Charset charset)是指通过charset来解码指定的byte数组, * 将其解码成unicode的char[]数组,够造成新的String。 * * 下面这个构造方法可以指定字节数组的编码 * Constructs a new {@code String} by decoding the specified array of * bytes using the specified {@linkplain java.nio.charset.Charset charset}. * The length of the new {@code String} is a function of the charset, and * hence may not be equal to the length of the byte array. * * <p> This method always replaces malformed-input and unmappable-character * sequences with this charset's default replacement string. The {@link * java.nio.charset.CharsetDecoder} class should be used when more control * over the decoding process is required. * * @param bytes * The bytes to be decoded into characters * * @param charset * The {@linkplain java.nio.charset.Charset charset} to be used to * decode the {@code bytes} * * @since 1.6 */ public String(byte bytes[], Charset charset) { this(bytes, 0, bytes.length, charset); } /** * 根据char数组 * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); /** * 根据 StringBuffer 创建 String对象 * Allocates a new string that contains the sequence of characters * currently contained in the string buffer argument. The contents of the * string buffer are copied; subsequent modification of the string buffer * does not affect the newly created string. * * @param buffer * A {@code StringBuffer} */ public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } /** * 根据 StringBuilder 创建 String对象 * Allocates a new string that contains the sequence of characters * currently contained in the string builder argument. The contents of the * string builder are copied; subsequent modification of the string builder * does not affect the newly created string. * * <p> This constructor is provided to ease migration to {@code * StringBuilder}. Obtaining a string from a string builder via the {@code * toString} method is likely to run faster and is generally preferred. * * @param builder * A {@code StringBuilder} * * @since 1.5 */ public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); } /* * 这是一个受保护构造方法,因为不能继承,所以内部使用 * 第二个属性基本没有用,只能是true * 从代码中可以看出来是直接引用,而不是新建一个,为了提高性能,节省内存等。 * 保护的原因也是为了保证字符串不可修改。 * Package private constructor which shares value array for speed. * this constructor is always expected to be called with share==true. * a separate constructor is needed because we already have a public * String(char[]) constructor that makes a copy of the given char[]. */ String(char[] value, boolean share) { // assert share : "unshared not supported"; this.value = value; } 常用的方法 getByte /** * 将字符串转成可用的 byte数组 * 在通信的比较多,例如 网络中传输、8583报文、socket通信 * 要想不乱码,就得搞清楚通信双方所使用的字节编码!!! * Encodes this {@code String} into a sequence of bytes using the named * charset, storing the result into a new byte array. * * <p> The behavior of this method when this string cannot be encoded in * the given charset is unspecified. The {@link * java.nio.charset.CharsetEncoder} class should be used when more control * over the encoding process is required. * * @param charsetName * The name of a supported {@linkplain java.nio.charset.Charset * charset} * * @return The resultant byte array * * @throws UnsupportedEncodingException * If the named charset is not supported * * @since JDK1.1 */ public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException(); return StringCoding.encode(charsetName, value, 0, value.length); } /** * 同上 * Encodes this {@code String} into a sequence of bytes using the given * {@linkplain java.nio.charset.Charset charset}, storing the result into a * new byte array. * * <p> This method always replaces malformed-input and unmappable-character * sequences with this charset's default replacement byte array. The * {@link java.nio.charset.CharsetEncoder} class should be used when more * control over the encoding process is required. * * @param charset * The {@linkplain java.nio.charset.Charset} to be used to encode * the {@code String} * * @return The resultant byte array * * @since 1.6 */ public byte[] getBytes(Charset charset) { if (charset == null) throw new NullPointerException(); return StringCoding.encode(charset, value, 0, value.length); } /** * 将使用系统默认编码。 * 要注意的,部署的时候容易出错的地方就是这里, * windows 环境和linux环境字节编码不一样.所以建议指定编码方法 * Encodes this {@code String} into a sequence of bytes using the * platform's default charset, storing the result into a new byte array. * * <p> The behavior of this method when this string cannot be encoded in * the default charset is unspecified. The {@link * java.nio.charset.CharsetEncoder} class should be used when more control * over the encoding process is required. * * @return The resultant byte array * * @since JDK1.1 */ public byte[] getBytes() { return StringCoding.encode(value, 0, value.length); } hashCode /** * hash算法 * hashCode可以保证相同的字符串的hash值肯定相同, * 但是,hash值相同并不一定是value值就相同。 * 所以要保证两个字符串相等还得用上 equals * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] */ public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } equals /** * * 在hashmap中 * 一定要重写 equals 和 hachcode * 才能保证是同一个字符串 * 正因为String 重写了我们才能愉快的使用字符串作为key */ public boolean equals(Object anObject) { /** 首先判断是不是自己!*/ if (this == anObject) { return true; } /** 在判断是不是String类型 */ if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; /** 判断长度 */ if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; /** 判断字节 */ while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } substring 这个方法在JDK1.6(含1.6)以前和JDK1.7之后(含1.7)有了不一样的变化 JDK1.6 substring /** * 仍然创建新的字符串但是 旧字符串还在 只是新的引用了旧的一部分 * 但旧字符串很大的时候,因为新的引用一小部分而无法回收会导致内存泄漏 * 一般使用加上一个空的字符串来生成新的解决这个问题 * str = str.substring(x, y) + "" */ String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; } public String substring(int beginIndex, int endIndex) { /** 校验数组溢出 */ return new String(offset + beginIndex, endIndex - beginIndex, value); } 内存泄露:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 JDK1.8 substring jdk1.7之后直接新建了一个字符串 。虽然增加了内存,但是解决了内存泄漏问题。 public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); } valueOf /** 调用对象自己的toString方法 */ public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); } public static String valueOf(char data[]) { return new String(data); } public static String valueOf(char data[], int offset, int count) { return new String(data, offset, count); } String + 号重载 String str = "abc"; String str1= str + "def"; /** 反编译之后 */ String str = "abc"; String str1= (new StringBuilder(String.valueOf(str))).append("def").toString(); spilt 按照字符regex将字符串分成limit份。 public String[] split(String regex, int limit) { /* fastpath if the regex is a (1)one-char String and this character is not one of the RegEx's meta characters ".$|()[{^?*+\\", or (2)two-char String and the first char is the backslash and the second is not the ascii digit or ascii letter. */ char ch = 0; if (((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) && (ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE)) { int off = 0; int next = 0; boolean limited = limit > 0; ArrayList<String> list = new ArrayList<>(); while ((next = indexOf(ch, off)) != -1) { if (!limited || list.size() < limit - 1) { list.add(substring(off, next)); off = next + 1; } else { // last one //assert (list.size() == limit - 1); list.add(substring(off, value.length)); off = value.length; break; } } // If no match was found, return this if (off == 0) return new String[]{this}; // Add remaining segment if (!limited || list.size() < limit) list.add(substring(off, value.length)); // Construct result int resultSize = list.size(); if (limit == 0) { while (resultSize > 0 && list.get(resultSize - 1).length() == 0) { resultSize--; } } String[] result = new String[resultSize]; return list.subList(0, resultSize).toArray(result); } return Pattern.compile(regex).split(this, limit); } 按照字符regex将字符串分割 /** 直接调用 split(String regex, int limit) limit 为 零 */ public String[] split(String regex) { return split(regex, 0); } equalsIgnoreCase public boolean equalsIgnoreCase(String anotherString) { return (this == anotherString) ? true : (anotherString != null) && (anotherString.value.length == value.length) && regionMatches(true, 0, anotherString, 0, value.length); } 三目运算符加 && 代替 多个if replaceFirst、replaceAll、replace String replaceFirst(String regex, String replacement) String replaceAll(String regex, String replacement) String replace(CharSequence target, CharSequence replacement) replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换 replaceAll和replaceFirst的参数是regex,即基于规则表达式的替换,replace只要有符合就替换 replaceFirst(),只替换第一次出现的字符串; 其他方法 String 类中还有很多方法。例如: public int length(){} 返回字符串长度 public boolean isEmpty() { } 返回字符串是否为空 public char charAt(int index) {} 返回字符串中第(index+1)个字符 public char[] toCharArray() {} 转化成字符数组 public String trim(){} 去掉两端空格 public String toUpperCase(){} 转化为大写 public String toLowerCase(){} 转化为小写 public String concat(String str) {} 拼接字符串 public boolean matches(String regex){} 判断字符串是否匹配给定的regex正则表达式 public boolean contains(CharSequence s) 判断字符串是否包含字符序列s

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册