关联文章:
Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

  上篇分析AsyncTask的一些基本用法以及不同android版本下的区别,接着本篇我们就来全面剖析一下AsyncTask的工作原理。在开始之前我们先来了解一个多线程的知识点——Callable<V>
、Future<V>和FutureTask类

一、理解Callable<V> 、Future<V>以及FutureTask类

Callable<V>

Callable的接口定义如下:

  1. public interface Callable<V> {
  2. V call() throws Exception;
  3. }

  Callable接口声明了一个名称为call()的方法,该方法可以有返回值V,也可以抛出异常。Callable也是一个线程接口,它与Runnable的主要区别就是Callable在线程执行完成后可以有返回值而Runnable没有返回值,Runnable接口声明如下:

  1. public interface Runnable {
  2. public abstract void run();
  3. }

  那么Callable接口如何使用呢,Callable需要和ExcutorService结合使用,其中ExecutorService也是一个线程池对象继承自Executor接口,对于线程池的知识点不了解可以看看我的另一篇文章,这里就不深入了,接着看看ExecutorService提供了那些方法供我们使用:

  1. <T> Future<T> submit(Callable<T> task);
  2. <T> Future<T> submit(Runnable task, T result);
  3. Future<?> submit(Runnable task);
  • submit(Callable task),传递一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
  • submit(Runnable task, T result),传递一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
  • submit(Runnable task),传递一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。

  因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。Callable接口介绍就先到这,再来看看Future时什么鬼。

Future<V>

  Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。其方法如下:

  1. public interface Future<V> {
  2. //取消任务
  3. boolean cancel(boolean mayInterruptIfRunning);
  4. //如果任务完成前被取消,则返回true。
  5. boolean isCancelled();
  6. //如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
  7. boolean isDone();
  8. //获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  9. V get() throws InterruptedException, ExecutionException;
  10. // 获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,
  11. //如果阻塞时间超过设定的timeout时间,该方法将返回null。
  12. V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
  13. }

总得来说Future有以下3点作用:

  • 能够中断执行中的任务
  • 判断任务是否执行完成
  • 获取任务执行完成后额结果。

  但是Future只是接口,我们根本无法将其创建为对象,于官方又给我们提供了其实现类FutureTask,这里我们要知道前面两个接口的介绍都只为此类做铺垫,毕竟AsncyTask中使用到的对象是FutureTask。

FutureTask

先来看看FutureTask的实现:

  1. public class FutureTask<V> implements RunnableFuture<V> {
  • 1

显然FutureTask类实现了RunnableFuture接口,我们再看一下RunnableFuture接口的实现:

  1. public interface RunnableFuture<V> extends Runnable, Future<V> {
  2. void run();
  3. }
  • 1
  • 2
  • 3

  从接口实现可以看出,FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask既可以当做Future对象也可是Runnable对象,当然FutureTask也就可以直接提交给线程池来执行。接着我们最关心的是如何创建FutureTask对象,实际上可以通过如下两个构造方法来构建FutureTask

  1. public FutureTask(Callable<V> callable) {
  2. }
  3. public FutureTask(Runnable runnable, V result) {
  4. }

  从构造方法看出,我们可以把一个实现了Callable或者Runnable的接口的对象封装成一个FutureTask对象,然后通过线程池去执行,那么具体如何使用呢?简单案例,CallableDemo.java代码如下:


  1. package com.zejian.Executor;
  2. import java.util.concurrent.Callable;
  3. /**
  4. * Callable接口实例 计算累加值大小并返回
  5. */
  6. public class CallableDemo implements Callable<Integer> {
  7. private int sum;
  8. @Override
  9. public Integer call() throws Exception {
  10. System.out.println("Callable子线程开始计算啦!");
  11. Thread.sleep(2000);
  12. for(int i=0 ;i<5000;i++){
  13. sum=sum+i;
  14. }
  15. System.out.println("Callable子线程计算结束!");
  16. return sum;
  17. }
  18. }

CallableTest.java测试代码如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. import java.util.concurrent.FutureTask;
  6. public class CallableTest {
  7. public static void main(String[] args) {
  8. //第一种使用方式
  9. // //创建线程池
  10. // ExecutorService es = Executors.newSingleThreadExecutor();
  11. // //创建Callable对象任务
  12. // CallableDemo calTask=new CallableDemo();
  13. // //提交任务并获取执行结果
  14. // Future<Integer> future =es.submit(calTask);
  15. // //关闭线程池
  16. // es.shutdown();
  17. //第二中使用方式
  18. //创建线程池
  19. ExecutorService es = Executors.newSingleThreadExecutor();
  20. //创建Callable对象任务
  21. CallableDemo calTask=new CallableDemo();
  22. //创建FutureTask
  23. FutureTask<Integer> futureTask=new FutureTask<>(calTask);
  24. //执行任务
  25. es.submit(futureTask);
  26. //关闭线程池
  27. es.shutdown();
  28. try {
  29. Thread.sleep(2000);
  30. System.out.println("主线程在执行其他任务");
  31. if(futureTask.get()!=null){
  32. //输出获取到的结果
  33. System.out.println("futureTask.get()-->"+futureTask.get());
  34. }else{
  35. //输出获取到的结果
  36. System.out.println("futureTask.get()未获取到结果");
  37. }
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. }
  41. System.out.println("主线程在执行完成");
  42. }
  43. }

  代码非常简单,注释也很明朗,这里我们分析一下第2种执行方式,先前声明一个CallableDemo类,该类实现了Callable接口,接着通过call方法去计算sum总值并返回。然后在测试类CallableTest中,把CallableDemo实例类封装成FutureTask对象并交给线程池去执行,最终执行结果将封装在FutureTask中,通过FutureTask#get()可以获取执行结果。第一种方式则是直接把Callable实现类丢给线程池执行,其结果封装在Future实例中,第2种方式执行结果如下:

  1. Callable子线程开始计算啦!
  2. 主线程在执行其他任务
  3. Callable子线程计算结束!
  4. futureTask.get()-->12497500
  5. 主线程在执行完成

  ok~,到此我们对Callable、Future和FutureTask就介绍到这,有了这个知识铺垫,我们就可以愉快的撩开AsyncTask的内部工作原理了。

二、AsyncTask的工作原理完全解析

  在上篇中,使用了如下代码来执行AsyncTask的异步任务:

  1. new AysnTaskDiff("AysnTaskDiff-1").execute("");
  • 1

  从代码可知,入口是execute方法,那我们就先看看execute的源码:

  1. @MainThread
  2. public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  3. return executeOnExecutor(sDefaultExecutor, params);
  4. }

  很明显execute方法只是一个壳子,直接调用了executeOnExecutor(sDefaultExecutor, params),其中sDefaultExecutor是一个串行的线程池,接着看看sDefaultExecutor内部实现:

  1. private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
  2. /**
  3. * An {@link Executor} that executes tasks one at a time in serial
  4. * order. This serialization is global to a particular process.
  5. */
  6. public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
  7. //串行线程池类,实现Executor接口
  8. private static class SerialExecutor implements Executor {
  9. final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
  10. Runnable mActive;
  11. public synchronized void execute(final Runnable r) {
  12. mTasks.offer(new Runnable() { //插入一个Runnble任务
  13. public void run() {
  14. try {
  15. r.run();
  16. } finally {
  17. scheduleNext();
  18. }
  19. }
  20. });
  21. //判断是否有Runnable在执行,没有就调用scheduleNext方法
  22. if (mActive == null) {
  23. scheduleNext();
  24. }
  25. }
  26. protected synchronized void scheduleNext() {
  27. //从任务队列mTasks中取出任务并放到THREAD_POOL_EXECUTOR线程池中执行.
  28. //由此也可见任务是串行进行的。
  29. if ((mActive = mTasks.poll()) != null) {
  30. THREAD_POOL_EXECUTOR.execute(mActive);
  31. }
  32. }
  33. }

  从源码可以看出,ArrayDeque是一个存放任务队列的容器(mTasks),任务Runnable传递进来后交给SerialExecutor的execute方法处理,SerialExecutor会把任务Runnable插入到任务队列mTasks尾部,接着会判断是否有Runnable在执行,没有就调用scheduleNext方法去执行下一个任务,接着交给THREAD_POOL_EXECUTOR线程池中执行,由此可见SerialExecutor并不是真正的线程执行者,它只是是保证传递进来的任务Runnable(实例是一个FutureTask)串行执行,而真正执行任务的是THREAD_POOL_EXECUTOR线程池,当然该逻辑也体现AsyncTask内部的任务是默认串行进行的。顺便看一下THREAD_POOL_EXECUTOR线程池的声明:

  1. //CUP核数
  2. private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
  3. //核心线程数量
  4. private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
  5. //最大线程数量
  6. private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
  7. //非核心线程的存活时间1s
  8. private static final int KEEP_ALIVE = 1;
  9. //线程工厂类
  10. private static final ThreadFactory sThreadFactory = new ThreadFactory() {
  11. private final AtomicInteger mCount = new AtomicInteger(1);
  12. public Thread newThread(Runnable r) {
  13. return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
  14. }
  15. };
  16. //线程队列,核心线程不够用时,任务会添加到该队列中,队列满后,会去调用非核心线程执行任务
  17. private static final BlockingQueue<Runnable> sPoolWorkQueue =
  18. new LinkedBlockingQueue<Runnable>(128);
  19. /**
  20. * An {@link Executor} that can be used to execute tasks in parallel.
  21. * 创建线程池
  22. */
  23. public static final Executor THREAD_POOL_EXECUTOR
  24. = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
  25. TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

  ok~,关于sDefaultExecutor,我们先了解到这,回到之前execute方法内部调用的executeOnExecutor方法的步骤,先来看看executeOnExecutor都做了些什么事?其源码如下:

  1. public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
  2. Params... params) {
  3. //判断在那种状态
  4. if (mStatus != Status.PENDING) {
  5. switch (mStatus) {
  6. case RUNNING:
  7. throw new IllegalStateException("Cannot execute task:"
  8. + " the task is already running.");
  9. case FINISHED://只能执行一次!
  10. throw new IllegalStateException("Cannot execute task:"
  11. + " the task has already been executed "
  12. + "(a task can be executed only once)");
  13. }
  14. }
  15. mStatus = Status.RUNNING;
  16. //onPreExecute()在此执行了!!!
  17. onPreExecute();
  18. //参数传递给了mWorker.mParams
  19. mWorker.mParams = params;
  20. //执行mFuture任务,其中exec就是传递进来的sDefaultExecutor
  21. //把mFuture交给线程池去执行任务
  22. exec.execute(mFuture);
  23. return this;
  24. }

  从executeOnExecutor方法的源码分析得知,执行任务前先会去判断当前AsyncTask的状态,如果处于RUNNING和FINISHED状态就不可再执行,直接抛出异常,只有处于Status.PENDING时,AsyncTask才会去执行。然后onPreExecute()被执行的,该方法可以用于线程开始前做一些准备工作。接着会把我们传递进来的参数赋值给 mWorker.mParams ,并执行开始执行mFuture任务,那么mWorker和mFuture到底是什么?先看看mWorker即WorkerRunnable的声明源码:

  1. //抽象类
  2. private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
  3. Params[] mParams;
  4. }

  WorkerRunnable抽象类实现了Callable接口,因此WorkerRunnable本质上也算一个Callable对象,其内部还封装了一个mParams的数组参数,因此我们在外部执行execute方法时传递的可变参数最终会赋值给WorkerRunnable的内部数组mParams,这些参数最后会传递给doInBackground方法处理,这时我们发现doInBackground方法也是在WorkerRunnable的call方法中被调用的,看看其源码如下:

  1. public AsyncTask() {
  2. //创建WorkerRunnable mWorker,本质上就是一个实现了Callable接口对象
  3. mWorker = new WorkerRunnable<Params, Result>() {
  4. public Result call() throws Exception {
  5. //设置标志
  6. mTaskInvoked.set(true);
  7. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  8. //执行doInBackground,并传递mParams参数
  9. Result result = doInBackground(mParams);
  10. Binder.flushPendingCommands();
  11. //执行完成调用postResult方法更新结果
  12. return postResult(result);
  13. }
  14. };
  15. //把mWorker(即Callable实现类)封装成FutureTask实例
  16. //最终执行结果也就封装在FutureTask中
  17. mFuture = new FutureTask<Result>(mWorker) {
  18. //任务执行完成后被调用
  19. @Override
  20. protected void done() {
  21. try {
  22. //如果还没更新结果通知就执行postResultIfNotInvoked
  23. postResultIfNotInvoked(get());
  24. } catch (InterruptedException e) {
  25. android.util.Log.w(LOG_TAG, e);
  26. } catch (ExecutionException e) {
  27. throw new RuntimeException("An error occurred while executing doInBackground()",
  28. e.getCause());
  29. } catch (CancellationException e) {
  30. //抛异常
  31. postResultIfNotInvoked(null);
  32. }
  33. }
  34. };
  35. }

   可以看到在初始化AsyncTask时,不仅创建了mWorker(本质实现了Callable接口的实例类)而且也创建了FutureTask对象,并把mWorker对象封装在FutureTask对象中,最后FutureTask对象将在executeOnExecutor方法中通过线程池去执行。给出下图协助理解:

  AsynTask在初始化时会创建mWorker实例对象和FutureTask实例对象,mWorker是一个实现了Callable线程接口并封装了传递参数的实例对象,然后mWorker实例会被封装成FutureTask实例中。在AsynTask创建后,我们调用execute方法去执行异步线程,其内部又直接调用了executeOnExecutor方法,并传递了线程池exec对象和执行参数,该方法内部通过线程池exec对象去执行mFuture实例,这时mWorker内部的call方法将被执行并调用doInBackground方法,最终通过postResult去通知更新结果。关于postResult方法,其源码如下:

  1. private Result postResult(Result result) {
  2. @SuppressWarnings("unchecked")
  3. Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
  4. new AsyncTaskResult<Result>(this, result));
  5. message.sendToTarget();
  6. return result;
  7. }

  显然是通过Handler去执行结果更新的,在执行结果成返回后,会把result封装到一个AsyncTaskResult对象中,最后把MESSAGE_POST_RESULT标示和AsyncTaskResult存放到Message中并发送给Handler去处理,这里我们先看看AsyncTaskResult的源码:

  1. private static class AsyncTaskResult<Data> {
  2. final AsyncTask mTask;
  3. final Data[] mData;
  4. AsyncTaskResult(AsyncTask task, Data... data) {
  5. mTask = task;
  6. mData = data;
  7. }
  8. }

  显然AsyncTaskResult封装了执行结果的数组以及AsyncTask本身,这个没什么好说的,接着看看AsyncTaskResult被发送到handler后如何处理的。

  1. private static class InternalHandler extends Handler {
  2. public InternalHandler() {
  3. //获取主线程的Looper传递给当前Handler,这也是为什么AsyncTask只能在主线程创建并执行的原因
  4. super(Looper.getMainLooper());
  5. }
  6. @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
  7. @Override
  8. public void handleMessage(Message msg) {
  9. //获取AsyncTaskResult
  10. AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
  11. switch (msg.what) {
  12. //执行完成
  13. case MESSAGE_POST_RESULT:
  14. // There is only one result
  15. result.mTask.finish(result.mData[0]);
  16. break;
  17. //更新进度条的标志
  18. case MESSAGE_POST_PROGRESS:
  19. //执行onProgressUpdate方法,自己实现。
  20. result.mTask.onProgressUpdate(result.mData);
  21. break;
  22. }
  23. }
  24. }

  从Handler的源码分析可知,该handler绑定的线程为主线线程,这也就是为什么AsyncTask必须在主线程创建并执行的原因了。接着通过handler发送过来的不同标志去决定执行那种结果,如果标示为MESSAGE_POST_RESULT则执行AsyncTask的finish方法并传递执行结果给该方法,finish方法源码如下:

  1. private void finish(Result result) {
  2. if (isCancelled()) {//判断任务是否被取消
  3. onCancelled(result);
  4. } else {//执行onPostExecute(result)并传递result结果
  5. onPostExecute(result);
  6. }
  7. //更改AsyncTask的状态为已完成
  8. mStatus = Status.FINISHED;
  9. }

  该方法先判断任务是否被取消,如果没有被取消则去执行onPostExecute(result)方法,外部通过onPostExecute方法去更新相关信息,如UI,消息通知等。最后更改AsyncTask的状态为已完成。到此AsyncTask的全部流程执行完。
  这里还有另一种标志MESSAGE_POST_PROGRESS,该标志是我们在doInBackground方法中调用publishProgress方法时发出的,该方法原型如下:

  1. protected final void publishProgress(Progress... values) {
  2. if (!isCancelled()) {
  3. //发送MESSAGE_POST_PROGRESS,通知更新进度条
  4. getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
  5. new AsyncTaskResult<Progress>(this, values)).sendToTarget();
  6. }
  7. }

  ok~,AsyncTask的整体流程基本分析完,最后来个总结吧:当我们调用execute(Params… params)方法后,其内部直接调用executeOnExecutor方法,接着onPreExecute()被调用方法,执行异步任务的WorkerRunnable对象(实质为Callable对象)最终被封装成FutureTask实例,FutureTask实例将由线程池sExecutor执行去执行,这个过程中doInBackground(Params… params)将被调用(在WorkerRunnable对象的call方法中被调用),如果我们覆写的doInBackground(Params… params)方法中调用了publishProgress(Progress… values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;最后如果FutureTask任务执行成功并返回结果,则通过postResult方法发送一条MESSAGE_POST_RESULT的消息去执行AsyncTask的finish方法,在finish方法内部onPostExecute(Result result)方法被调用,在onPostExecute方法中我们可以更新UI或者释放资源等。这既是AsyncTask内部的工作流程,可以说是Callable+FutureTask+Executor+Handler内部封装。结尾我们献上一张执行流程,协助大家理解整个流程:

好~,本篇到此结束。。。

Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

android多线程-AsyncTask之工作原理深入解析(下)的更多相关文章

  1. android多线程-AsyncTask之工作原理深入解析(上)

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  2. Android 多线程-----AsyncTask详解

    您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内 ...

  3. Android 多线程----AsyncTask异步任务详解

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  4. 深入理解AsyncTask的工作原理

    一.为什么需要工作者线程 我们知道,Android应用的主线程(UI 线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免“用户点击按钮后没反应”这样的糟糕用户体验,我们就要确保主线程时刻保持着 ...

  5. AsyncTask的工作原理

    AsyncTask是Android本身提供的一种轻量级的异步任务类.它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新UI.实际上,AsyncTask内部是封装了Thread和 ...

  6. 路由及路由器工作原理深入解析3:路由与port

        日志"路由及路由器工作原理深入解析1"http://user.qzone.qq.com/2756567163/blog/1438322342介绍了"为什么要使用路 ...

  7. NS域名工作原理及解析

    DNS域名工作原理及解析   0x00 定义 DNS( Domain Name System)是“域名系统”的英文缩写,它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网.D ...

  8. Android ListView工作原理完全解析,带你从源码的角度彻底理解

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[+] Adapter的作用 RecycleBin机制 第一次Layout 第二次Layout 滑动加载更多数据   转载请注明出处:h ...

  9. Android ListView工作原理完全解析(转自 郭霖老师博客)

    原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879 在Android所有常用的原生控件当中,用法最复杂的应该就是ListVie ...

随机推荐

  1. arcgis for flex 学习笔记(一)

    初步认识 地图由图层.要素.样式等组成.地图上有N个图层,图层上有N个要素,每个要素可以存放点.线.面等,每个要素可以设置样式,如果显示图片.或文字均可以先创建一个mxml组件,然后设置到要素上. 面 ...

  2. dj定时任务

    参考:http://www.mknight.cn/post/13/ https://blog.csdn.net/sicofield/article/details/50937338 一.INSTALL ...

  3. ios 里如何处理四舍五入的问题

    http://blog.sina.com.cn/s/blog_a2774bb10101293j.html 今天朋友问我一个问题,就是如何只舍不入.比如 float price = 0.126,怎么样才 ...

  4. linux内核同步之信号量、顺序锁、RCU、完成量、关闭中断【转】

    转自:http://blog.csdn.net/goodluckwhh/article/details/9006065 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 一信 ...

  5. SSM+Maven的JavaWeb项目中的异常的可能性

    1.404 可能:1):被拦截了,即:springmvc中的controller可能不存在,可能没有被配置,可能配置出错 2):资源确实不存在 3):路径出错 2.500,程序异常,但是业务逻辑什么都 ...

  6. Tk写的发邮件小程序

    from tkinter import * import smtplib from email.mime.text import MIMEText from email.header import H ...

  7. Selenium2+python自动化53-unittest批量执行(discover)【转载】

    前言 我们在写用例的时候,单个脚本的用例好执行,那么多个脚本的时候,如何批量执行呢?这时候就需要用到unittet里面的discover方法来加载用例了. 加载用例后,用unittest里面的Text ...

  8. #6034. 「雅礼集训 2017 Day2」线段游戏 李超树

    #6034. 「雅礼集训 2017 Day2」线段游戏 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:Special Judge 上传者: 匿名 提交提交记录统 ...

  9. (四)mysql数据类型

    数据类型基本介绍 数值类型 整形类型:tinyint,int,bigint 浮点类型:float,double 字符串类型 char系列:char varchar text系列:text blob系列 ...

  10. java 访问 kerberos 认证的 kafka

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...