解析Tars-Java客户端源码

目录
  • 一、基本RPC框架简介
    • 1.1、RPC调用流程
  • 二、Tars Java客户端设计介绍
    • 2.1、Tars Java客户端初始化过程
    • 2.2、使用范例
    • 2.3、代理生成
    • 2.4、远程服务寻址方法
    • 2.5、网络模型
    • 2.6、远程调用交互模型
      • 2.6.1、写 IO 流程
      • 2.6.2、同步和异步调用的底层技术实现
  • 三、总结

一、基本RPC框架简介

在分布式计算中,远程过程调用(Remote Procedure Call,缩写 RPC)允许运行于一台计算机的程序调用另一个地址空间计算机的程序,就像调用本地程序一样,无需额外地为这个交互作用涉及到的代理对象构建、网络协议等进行编程。

一般RPC架构,有至少三种结构,分别为注册中心,服务提供者和服务消费者。如图1.1所示,注册中心提供注册服务和注册信息变更的通知服务,服务提供者运行在服务器来提供服务,服务消费者使用服务提供者的服务。

服务提供者(RPC Server),运行在服务端,提供服务接口定义与服务实现类,并对外暴露服务接口。注册中心(Registry),运行在服务端,负责记录服务提供者的服务对象,并提供远程服务信息的查询服务和变更通知服务。服务消费者(RPC Client),运行在客户端,通过远程代理对象调用远程服务。

1.1、RPC调用流程

如下图所示,描述了RPC的调用流程,其中IDL(Interface Description Language)为接口描述语言,使得在不同平台上运行的程序和用不同语言编写的程序可以相互通信交流。

1)客户端调用客户端桩模块。该调用是本地过程调用,其中参数以正常方式推入堆栈。

2)客户端桩模块将参数打包到消息中,并进行系统调用以发送消息。打包参数称为编组。

3)客户端的本地操作系统将消息从客户端计算机发送到服务器计算机。

4)服务器计算机上的本地操作系统将传入的数据包传递到服务器桩模块。

5)服务器桩模块从消息中解包出参数。解包参数称为解组。

6)最后,服务器桩模块执行服务器程序流程。回复是沿相反的方向执行相同的步骤。

二、Tars Java客户端设计介绍

Tars Java客户端整体设计与主流的RPC框架基本一致。我们先介绍Tars Java客户端初始化过程。

2.1、Tars Java客户端初始化过程

如图2.1所示,描述了Tars Java的初始化过程。

1)先出创建一个CommunicatorConfig配置项,命名为communicatorConfig,其中按需设置locator, moduleName, connections等参数。

2)通过上述的CommunicatorConfig配置项,命名为config,那么调用CommunicatorFactory.getInstance().getCommunicator(config),创建一个Communicator对象,命名为communicator。

3)假设objectName="MESSAGE.ControlCenter.Dispatcher",需要生成的代理接口为Dispatcher.class,调用communicator.stringToProxy(objectName, Dispatcher.class)方法来生成代理对象的实现类。

4)在stringToProxy()方法里,首先通过初始化QueryHelper代理对象,调用getServerNodes()方法获取远程服务对象列表,并设置该返回值到communicatorConfig的objectName字段里。具体的代理对象的代码分析,见下文中的“2.3 代理生成”章节。

5)判断在之前调用stringToProxy是否有设置LoadBalance参数,如果没有的话,就生成默认的采用RR轮训算法的DefaultLoadBalance对象。

6)创建TarsProtocolInvoker协议调用对象,其中过程有通过解析communicatorConfig中的objectName和simpleObjectName来获取URL列表,其中一个URL对应一个远程服务对象,TarsProtocolInvoker初始化各个URL对应的ServantClient对象,其中一个URL根据communicatorConfig的connections配置项确认生成多少个ServantClient对象。然后使用ServantClients等参数初始化TarsInvoker对象,并将这些TarsInvoker对象集合设置到TarsProtocolInvoker的allInvokers成员变量中,其中每个URL对应一个TarsInvoker对象。上述分析表明,一个远程服务节点对应一个TarsInvoker对象,一个TarsInvoker对象包含connections个ServantClient对象,对于TCP协议,那么就是一个ServantClient对象对应一个TCP连接。

7)使用api, objName, servantProxyConfig,loadBalance,protocolInvoker, this.communicator参数生成一个实现JDK代理接口InvocationHandler的ObjectProxy对象。

8)生成ObjectProxy对象的同时进行初始化操作,首先会执行loadBalancer.refresh()方法刷新远程服务节点到负载均衡器中便于后续tars远程调用进行路由。

9)然后注册统计信息上报器,其中是上报方法采用JDK的ScheduledThreadPoolExecutor进行定时轮训上报。

10)注册服务列表刷新器,采用的技术方法和上述统计信息上报器基本一致。

2.2、使用范例

以下代码为最简化示例,其中CommunicatorConfig里的配置采用默认值,communicator通过CommunicatorConfig配置生成后,直接指定远程服务对象的具体服务对象名、IP和端口生成一个远程服务代理对象。

Tars Java代码使用范例// 先初始化基本Tars配置CommunicatorConfig cfg = new CommunicatorConfig();// 通过上述的CommunicatorConfig配置生成一个Communicator对象。Communicator communicator = CommunicatorFactory.getInstance().getCommunicator(cfg);// 指定Tars远程服务的服务对象名、IP和端口生成一个远程服务代理对象。

// 先初始化基本Tars配置
CommunicatorConfig cfg = new CommunicatorConfig();
// 通过上述的CommunicatorConfig配置生成一个Communicator对象。
Communicator communicator = CommunicatorFactory.getInstance().getCommunicator(cfg);
// 指定Tars远程服务的服务对象名、IP和端口生成一个远程服务代理对象。
HelloPrx proxy = communicator.stringToProxy(HelloPrx.class, "TestApp.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 18601 -t 60000");
//同步调用,阻塞直到远程服务对象的方法返回结果
String ret = proxy.hello(3000, "Hello World");
System.out.println(ret);
//异步调用,不关注异步调用最终的情况
proxy.async_hello(null, 3000, "Hello World");
    //异步调用,注册一个实现TarsAbstractCallback接口的回执处理对象,该实现类分别处理调用成功,调用超时和调用异常的情况。
proxy.async_hello(new HelloPrxCallback() {
    @Override
    public void callback_expired() { //超时事件处理
    }
    @Override
    public void callback_exception(Throwable ex) { //异常事件处理
    }
    @Override
    public void callback_hello(String ret) { //调用成功事件处理
        Main.logger.info("invoke async method successfully {}", ret);
    }
}, 1000, "Hello World");

在上述例子中,演示了常见的两种调用方式,分别为同步调用和异步调用。其中异步调用,如果调用方想捕捉异步调用的最终结果,可以注册一个实现TarsAbstractCallback接口的实现类,对tars调用的异常,超时和成功事件进行处理。

2.3、代理生成

Tars Java的客户端桩模块的远程代理对象是采用JDK原生Proxy方法。如下文的源码所示,ObjectProxy实现了java.lang.reflect.InvocationHandler的接口方法,该接口是JDK自带的代理接口。

代理实现

public final class ObjectProxy<T> implements ServantProxy, InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        InvokeContext context = this.protocolInvoker.createContext(proxy, method, args);
        try {
            if ("toString".equals(methodName) && parameterTypes.length == 0) {
                return this.toString();
            } else if
                //***** 省略代码 *****
            } else {
                // 在负载均衡器选取一个远程调用类,进行应用层协议的封装,最后调用TCP传输层进行发送。
                Invoker invoker = this.loadBalancer.select(context);
                return invoker.invoke(context);
            }
        } catch (Throwable var8) {
            // ***** 省略代码 *****
        }
    }
}

当然生成上述远程服务代理类,涉及到辅助类,Tars Java采用ServantProxyFactory来生成上述的ObjectProxy,并存储ObjectProxy对象到Map结构,便于调用方二次使用时直接复用已存在的远程服务代理对象。

具体相关逻辑如源码所示,ObjectProxyFactory是生成ObjectProxy的辅助工厂类,和ServantProxyFactory不同,其本身不缓存生成的代理对象。

class ServantProxyFactory {
    private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap();
    // ***** 省略代码 *****
    public <T> Object getServantProxy(Class<T> clazz, String objName, ServantProxyConfig servantProxyConfig, LoadBalance loadBalance, ProtocolInvoker<T> protocolInvoker) {
        Object proxy = this.cache.get(objName);
        if (proxy == null) {
            this.lock.lock(); // 加锁,保证只生成一个远程服务代理对象。
            try {
                proxy = this.cache.get(objName);
                if (proxy == null) {
                    // 创建实现JDK的java.lang.reflect.InvocationHandler接口的对象
                    ObjectProxy<T> objectProxy = this.communicator.getObjectProxyFactory().getObjectProxy(clazz, objName, servantProxyConfig, loadBalance, protocolInvoker);
                    // 使用JDK的java.lang.reflect.Proxy来生成实际的代理对象
                    this.cache.putIfAbsent(objName, this.createProxy(clazz, objectProxy));
                    proxy = this.cache.get(objName);
                }
            } finally {
                this.lock.unlock();
            }
        }
        return proxy;
    }
    /** 使用JDK自带的Proxy.newProxyInstance生成代理对象 */
    private <T> Object createProxy(Class<T> clazz, ObjectProxy<T> objectProxy) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz, ServantProxy.class}, objectProxy);
    }
    // ***** 省略代码 *****
}

从以上的源码中,可以看到createProxy使用了JDK的Proxy.newProxyInstance方法来生成远程服务代理对象。

2.4、远程服务寻址方法

作为一个RPC远程框架,在分布式系统中,调用远程服务,涉及到如何路由的问题,也就是如何从多个远程服务节点中选择一个服务节点进行调用,当然Tars Java支持直连特定节点的方式调用远程服务,如上文的2.2 使用范例所介绍。

如图下图所示,ClientA某个时刻的一次调用使用了Service3节点进行远程服务调用,而ClientB某个时刻的一次调用采用Service2节点。Tars Java提供多种负载均衡算法实现类,其中有采用RR轮训算法的RoundRobinLoadBalance,一致性哈希算法的ConsistentHashLoadBalance和普通哈希算法的HashLoadBalance。

(客户端按特定路由规则调用远程服务)

如下述源码所示,如果要自定义负载均衡器来定义远程调用的路由规则,那么需要实现com.qq.tars.rpc.common.LoadBalance接口,其中LoadBalance.select()方法负责按照路由规则,选取对应的Invoker对象,然后进行远程调用,具体逻辑见源码代理实现。由于远程服务节点可能发生变更,比如上下线远程服务节点,需要刷新本地负载均衡器的路由信息,那么此信息更新的逻辑在LoadBalance.refresh()方法里实现。

负载均衡接口

public interface LoadBalance<T> {
    /** 根据负载均衡策略,挑选invoker */
    Invoker<T> select(InvokeContext invokeContext) throws NoInvokerException;
    /** 通知invoker列表的更新 */
    void refresh(Collection<Invoker<T>> invokers);
}

2.5、网络模型

Tars Java的IO模式采用的JDK的NIO的Selector模式。这里以TCP协议来描述网络处理,如下述源码所示,Reactor是一个线程,其中的run()方法中,调用了selector.select()方法,意思是如果除非此时网络产生一个事件,否则将一直线程阻塞下去。

假如此时出现一个网络事件,那么此时线程将会被唤醒,执行后续代码,其中一个代码是dispatcheEvent(key),也就是将进行事件的分发。

其中将根据对应条件,调用acceptor.handleConnectEvent(key)方法来处理客户端连接成功事件,或acceptor.handleAcceptEvent(key)方法来处理服务器接受连接成功事件,或调用acceptor.handleReadEvent(key)方法从Socket里读取数据,或acceptor.handleWriteEvent(key)方法来写数据到Socket 。

Reactor事件处理

public final class Reactor extends Thread {
    protected volatile Selector selector = null;
    private Acceptor acceptor = null;
    //***** 省略代码 *****
    public void run() {
        try {
            while (!Thread.interrupted()) {
                // 阻塞直到有网络事件发生。
                selector.select();
                //***** 省略代码 *****
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    if (!key.isValid()) continue;
                    try {
                        //***** 省略代码 *****
                        // 分发传输层协议TCP或UDP网络事件
                        dispatchEvent(key);
                //***** 省略代码 *****
            }
        }
        //***** 省略代码 *****
    }
        //***** 省略代码 *****
    private void dispatchEvent(final SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            acceptor.handleConnectEvent(key);
        } else if (key.isAcceptable()) {
            acceptor.handleAcceptEvent(key);
        } else if (key.isReadable()) {
            acceptor.handleReadEvent(key);
        } else if (key.isValid() && key.isWritable()) {
            acceptor.handleWriteEvent(key);
        }
    }
}

网络处理采用Reactor事件驱动模式,Tars定义一个Reactor对象对应一个Selector对象,针对每个远程服务(整体服务集群,非单个节点程序)默认创建2个Reactor对象进行处理。

上图中的处理读IO事件(Read Event)实现和写IO事件(Write Event)的线程池是在Communicator初始化的时候配置的。具体逻辑如源码所示,其中线程池参数配置由CommunicatorConfig的corePoolSize, maxPoolSize, keepAliveTime等参数决定。

读写事件线程池初始化

private void initCommunicator(CommunicatorConfig config) throws CommunicatorConfigException {
    //***** 省略代码 *****
    this.threadPoolExecutor = ClientPoolManager.getClientThreadPoolExecutor(config);
    //***** 省略代码 *****
}
​
public class ClientPoolManager {
    public static ThreadPoolExecutor getClientThreadPoolExecutor(CommunicatorConfig communicatorConfig) {
        //***** 省略代码 *****
        clientThreadPoolMap.put(communicatorConfig, createThreadPool(communicatorConfig));
        //***** 省略代码 *****
        return clientPoolExecutor;
    }    

    private static ThreadPoolExecutor createThreadPool(CommunicatorConfig communicatorConfig) {
        int corePoolSize = communicatorConfig.getCorePoolSize();
        int maxPoolSize = communicatorConfig.getMaxPoolSize();
        int keepAliveTime = communicatorConfig.getKeepAliveTime();
        int queueSize = communicatorConfig.getQueueSize();
        TaskQueue taskqueue = new TaskQueue(queueSize);
​
        String namePrefix = "tars-client-executor-";
        TaskThreadPoolExecutor executor = new TaskThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, taskqueue, new TaskThreadFactory(namePrefix));
        taskqueue.setParent(executor);
        return executor;
    }
}

2.6、远程调用交互模型

调用代理类的方法,那么会进入实现InvocationHandler接口的ObjectProxy中的invoke方法。

下图描述了远程服务调用的流程情况。这里着重讲几个点,一个是如何写数据到网络IO。第二个是Tars Java通过什么方式进行同步或者异步调用,底层采用了什么技术。

2.6.1、写 IO 流程

如图(底层代码写IO过程)所示,ServantClient将调用底层网络写操作,在invokeWithSync方法中,取得ServantClient自身成员变量TCPSession,调用TCPSession.write()方法,如图(底层代码写IO过程)和以下源码( 读写事件线程池初始化)所示,先获取Encode进行请求内容编码成IoBuffer对象,最后将IoBuffer的java.nio.ByteBuffer内容放入TCPSession的queue成员变量中,然后调用key.selector().wakeup(),唤醒Reactor中run()方法中的Selector.select(),执行后续的写操作。

具体Reactor逻辑见上文2.5 网络模型内容,如果Reactor检查条件发现可以写IO的话也就是key.isWritable()为true,那么最终会循环从TCPSession.queue中取出ByteBuffer对象,调用SocketChannel.write(byteBuffer)执行实际的写网络Socket操作,代码逻辑见源码中的doWrite()方法。

读写事件线程池初始化

public class TCPSession extends Session {
    public void write(Request request) throws IOException {
        try {
            IoBuffer buffer = selectorManager.getProtocolFactory().getEncoder().encodeRequest(request, this);
            write(buffer);
        //***** 省略代码 *****
    }
    protected void write(IoBuffer buffer) throws IOException {
        //***** 省略代码 *****
        if (!this.queue.offer(buffer.buf())) {
            throw new IOException("The session queue is full. [ queue size:" + queue.size() + " ]");
        }
        if (key != null) {
            key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            key.selector().wakeup();
        }
    }
    protected synchronized int doWrite() throws IOException {
        int writeBytes = 0;
        while (true) {
            ByteBuffer wBuf = queue.peek();
            //***** 省略代码 *****
            int bytesWritten = ((SocketChannel) channel).write(wBuf);
            //***** 省略代码 *****
        return writeBytes;
    }
}

2.6.2、同步和异步调用的底层技术实现

对于同步方法调用,如图(远程调用流程)和源码(ServantClient的同步调用)所示,ServantClient调用底层网络写操作,在invokeWithSync方法中创建一个Ticket对象,Ticket顾名思义就是票的意思,这张票唯一标识本次网络调用情况。

ServantClient的同步调用

public class ServantClient {
    public <T extends ServantResponse> T invokeWithSync(ServantRequest request) throws IOException {
            //***** 省略代码 *****
            ticket = TicketManager.createTicket(request, session, this.syncTimeout);
            Session current = session;
            current.write(request);
            if (!ticket.await(this.syncTimeout, TimeUnit.MILLISECONDS)) {
            //***** 省略代码 *****
            response = ticket.response();
            //***** 省略代码 *****
            return response;
            //***** 省略代码 *****
        return response;
    }
}

如代码所示,在执行完session.write()操作后,紧接着执行ticket.await()方法,该方法线程等待直到远程服务回复返回结果到客户端,ticket.await()被唤醒后,将执行后续操作,最终invokeWithSync方法返回response对象。其中Ticket的等待唤醒功能内部采用java.util.concurrent.CountDownLatch来实现。

对于异步方法调用,将会执行ServantClient.invokeWithAsync方法,也会创建一个Ticket,并且执行Session.write()操作,虽然不会调用ticket.await(),但是在Reactor接收到远程回复时,首先会先解析Tars协议头得到Response对象,然后将Response对象放入如图(Tars-Java的网络事件处理模型)所示的IO读写线程池中进行进一步处理,如下述源码(异步回调事件处理)所示,最终会调用WorkThread.run()方法,在run()方法里执行ticket.notifyResponse(resp),该方法里面会执行类似上述代码2.1中的实现TarsAbstractCallback接口的调用成功回调的方法。

异步回调事件处理

public final class WorkThread implements Runnable {
    public void run() {
        try {
            //***** 省略代码 *****
                Ticket<Response> ticket = TicketManager.getTicket(resp.getTicketNumber());
            //***** 省略代码 *****
                ticket.notifyResponse(resp);
                ticket.countDown();
                TicketManager.removeTicket(ticket.getTicketNumber());
            }
            //***** 省略代码 *****
    }
}

如下述源码所示,TicketManager会有一个定时任务轮训检查所有的调用是否超时,如果(currentTime - t.startTime) > t.timeout条件成立,那么会调用t.expired()告知回调对象,本次调用超时。

调用超时事件处理

public class TicketManager {
            //***** 省略代码 *****
    static {
        executor.scheduleAtFixedRate(new Runnable() {
            long currentTime = -1;
            public void run() {
                Collection<Ticket<?>> values = tickets.values();
                currentTime = System.currentTimeMillis();
                for (Ticket<?> t : values) {
                    if ((currentTime - t.startTime) > t.timeout) {
                        removeTicket(t.getTicketNumber());
                        t.expired();
                    }
                }
            }
        }, 500, 500, TimeUnit.MILLISECONDS);
    }
}

三、总结

代码的调用一般都是层层递归调用,代码的调用深度和广度都很大,通过调试代码的方式一步步学习源码的方式,更加容易理解源码的含义和设计理念。

Tars与其他RPC框架,并没有什么本质区别,通过类比其他框架的设计理念,可以更加深入理解Tars Java设计理念。

以上就是解析Tars-Java客户端源码的详细内容,更多关于Tars-Java 客户端源码的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java源码解析之HashMap的put、resize方法详解

    一.HashMap 简介 HashMap 底层采用哈希表结构 数组加链表加红黑树实现,允许储存null键和null值 数组优点:通过数组下标可以快速实现对数组元素的访问,效率高 链表优点:插入或删除数据不需要移动元素,只需要修改节点引用效率高 二.源码分析 2.1 继承和实现 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

  • Java并发编程之CountDownLatch源码解析

    一.前言 CountDownLatch维护了一个计数器(还是是state字段),调用countDown方法会将计数器减1,调用await方法会阻塞线程直到计数器变为0.可以用于实现一个线程等待所有子线程任务完成之后再继续执行的逻辑,也可以实现类似简易CyclicBarrier的功能,达到让多个线程等待同时开始执行某一段逻辑目的. 二.使用 一个线程等待其它线程执行完再继续执行 ...... CountDownLatch cdl = new CountDownLatch(10); Executor

  • Java实时获取基金收益项目源码分享

    本文章向大家介绍JAVA爬取天天基金网数据,主要包括JAVA爬取天天基金网数据使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下. 天天基金网网址:http://quote.eastmoney.com/center/gridlist.html#fund_lof Java爬虫实时获取基金收益历史记录代码: 首先要自己定义几个参数:基金编码,页数,每页显示条数 开始时间结束时间等 (我这直接写的静态方法使用的 大家可以改成Test方法自行进行测试) /**

  • java实现简易超市管理系统 附源码下载

    java超市管理系统 1.0(含源文件,后续会继续优化~) 前言 一个月零零散散的时间学习了java,通过这次"超市管理系统"的练习,希望可以给一同开始学习java的朋友一些参考,更希望大佬们多多指点和批评~ 一.确定需求 程序概述: 小型超市商品销售管理系统选择小型超市的四类商品进行管理. 这四类商品是:食品.化妆品.生活用品和饮料(四个类). 每类商品都包含有商品名和商品利润 (其中包括商品的售价.进价.库存量).(五个属性) 每类不同的商品还有区别于其他商品的特殊信息(子类特有属

  • Java实现多人聊天室的原理与源码

    多人聊天室原理图 源码 工具类: 该类用于关闭各种流. public class CloseUtil { public static void CloseAll(Closeable... closeable){ for(Closeable c:closeable){ if (c != null) { try { c.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务器: 服务器端创建一个serverSocket

  • 解析Tars-Java客户端源码

    目录 一.基本RPC框架简介 1.1.RPC调用流程 二.Tars Java客户端设计介绍 2.1.Tars Java客户端初始化过程 2.2.使用范例 2.3.代理生成 2.4.远程服务寻址方法 2.5.网络模型 2.6.远程调用交互模型 2.6.1.写 IO 流程 2.6.2.同步和异步调用的底层技术实现 三.总结 一.基本RPC框架简介 在分布式计算中,远程过程调用(Remote Procedure Call,缩写 RPC)允许运行于一台计算机的程序调用另一个地址空间计算机的程序,就像调用

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • java String源码和String常量池的全面解析

    1. String 介绍,常用方法源码分析 2. String 常量池分析 常用方法 equals trim replace concat split startsWith 和 endsWith substring toUpperCase() 和 toLowerCase() compareTo String 介绍 String类被final所修饰,也就是说String对象是不可变量,并发程序最喜欢不可变量了.String类实现了Serializable, Comparable, CharSequ

  • java集合类源码分析之Set详解

    Set集合与List一样,都是继承自Collection接口,常用的实现类有HashSet和TreeSet.值得注意的是,HashSet是通过HashMap来实现的而TreeSet是通过TreeMap来实现的,所以HashSet和TreeSet都没有自己的数据结构,具体可以归纳如下: •Set集合中的元素不能重复,即元素唯一 •HashSet按元素的哈希值存储,所以是无序的,并且最多允许一个null对象 •TreeSet按元素的大小存储,所以是有序的,并且不允许null对象 •Set集合没有ge

  • Java集合源码全面分析

    Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组.链表.栈.队列.集合.哈希表等.学习Java集合框架下大致可以分为如下五个部分:List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Arrays.Collections). 从上图中可以看出,集合类主要分为两大类:Collection和Map. Collection是List.Set等集合高度抽象出来的接口,它包含了这些集合的基本操作,它主要又分为两大部分:List和Se

  • Java String源码分析并介绍Sting 为什么不可变

    Java String源码分析 什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变. 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑.看下面代码: String s =

  • 微信小程序之侧边栏滑动实现过程解析(附完整源码)

    一.效果图 讲什么都不如直接上效果图好,所以我们先来看下实现效果如何. 通过滑动屏幕,或者点击左上角的图标按钮,都能实现侧边栏的划出效果. 二.原理解析 1.先实现侧边栏的内容,让侧边栏的内容居左,然后将侧边栏的内容置于屏幕的最底部. 2.接着实现主页的内容,并且让主页的内容默认是覆盖在侧边栏的内容上面. 3.然后,实现手指滑动屏幕的时候,主页的内容,向左滑动一定的宽度,让侧边栏的内容显示出来,而滑动的效果是通过 css 的 transition 来实现的. 三.源码 由于实现过程的时,我对代码

  • 解析Mybatis判断表达式源码分析

    在我们开发过程中用 Mybatis 经常会用到下面的例子 Mapper如下 Map<String ,String > testArray(@Param("array") String [] array); XMl中的sql如下 <select id="testArray" resultType="map"> select * from t_ams_ac_pmt_dtl where cpt_pro=#{cptProp} &l

  • 以武侠形式理解Java LinkedList源码

    目录 一.LinkedList 的剖白 二.LinkedList 的内功心法 三.LinkedList 的招式 1)招式一:增 2)招式二:删 3)招式三:改 4)招式四:查 四.LinkedList 的挑战 一.LinkedList 的剖白 大家好,我是 LinkedList,和 ArrayList 是同门师兄弟,但我俩练的内功却完全不同.师兄练的是动态数组,我练的是链表. 问大家一个问题,知道我为什么要练链表这门内功吗? 举个例子来讲吧,假如你们手头要管理一推票据,可能有一张,也可能有一亿张

  • Java从源码看异步任务计算FutureTask

    目录 了解一下什么是FutureTask? FutureTask 是如何实现的呢? FutureTask 运行流程 FutureTask 的使用 前言: 大家是否熟悉FutureTask呢?或者说你有没有异步计算的需求呢?FutureTask就能够很好的帮助你实现异步计算,并且可以实现同步获取异步任务的计算结果.下面我们就一起从源码分析一下FutureTask. 了解一下什么是FutureTask? FutureTask 是一个可取消的异步计算. FutureTask提供了对Future的基本实

随机推荐