Java Executor 框架

Executor框架是指java5中引入的一系列并发库中与executor相关的功能类,包括Executor、Executors、 ExecutorService、CompletionService、Future、Callable等。(图片引用自 http://www.javaclubcn.com/a/jichuzhishi/2012/1116/170.html)

本篇博文分析Executor中几个比较重要的接口和类。

Executor

1 public interface Executor {
2      void execute(Runnable command);
3 }

Executor接口是Executor框架中最基础的部分,定义了一个用于执行Runnable的execute方法。它没有直接的实现类,有一个重要的子接口ExecutorService。

ExecutorService

 1 //继承自Executor接口
 2 public interface ExecutorService extends Executor {
 3     /**
 4      * 关闭方法,调用后执行之前提交的任务,不再接受新的任务
 5      */
 6     void shutdown();
 7     /**
 8      * 从语义上可以看出是立即停止的意思,将暂停所有等待处理的任务并返回这些任务的列表
 9      */
10     List<Runnable> shutdownNow();
11     /**
12      * 判断执行器是否已经关闭
13      */
14     boolean isShutdown();
15     /**
16      * 关闭后所有任务是否都已完成
17      */
18     boolean isTerminated();
19     /**
20      * 中断
21      */
22     boolean awaitTermination(long timeout, TimeUnit unit)
23         throws InterruptedException;
24     /**
25      * 提交一个Callable任务
26      */
27     <T> Future<T> submit(Callable<T> task);
28     /**
29      * 提交一个Runable任务,result要返回的结果
30      */
31     <T> Future<T> submit(Runnable task, T result);
32     /**
33      * 提交一个任务
34      */
35     Future<?> submit(Runnable task);
36     /**
37      * 执行所有给定的任务,当所有任务完成,返回保持任务状态和结果的Future列表
38      */
39     <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
40         throws InterruptedException;
41     /**
42      * 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。
43      */
44     <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
45                                   long timeout, TimeUnit unit)
46         throws InterruptedException;
47     /**
48      * 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。
49      */
50     <T> T invokeAny(Collection<? extends Callable<T>> tasks)
51         throws InterruptedException, ExecutionException;
52     /**
53      * 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
54      */
55     <T> T invokeAny(Collection<? extends Callable<T>> tasks,
56                     long timeout, TimeUnit unit)
57         throws InterruptedException, ExecutionException, TimeoutException;
58 }

ExecutorService接口继承自Executor接口,定义了终止、提交任务、跟踪任务返回结果等方法。

ExecutorService涉及到Runnable、Callable、Future接口,这些接口的具体内容如下。

 1 // 实现Runnable接口的类将被Thread执行,表示一个基本的任务
 2 public interface Runnable {
 3     // run方法就是它所有的内容,就是实际执行的任务
 4     public abstract void run();
 5 }
 6 // Callable同样是任务,与Runnable接口的区别在于它接收泛型,同时它执行任务后带有返回内容
 7 public interface Callable<V> {
 8     // 相对于run方法的带有返回值的call方法
 9     V call() throws Exception;
10 }
Future

ExecutorService有一个子接口ScheduledExecutorService和一个抽象实现类AbstractExecutorService。

ScheduledExecutorService

 1 // 可以安排指定时间或周期性的执行任务的ExecutorService
 2 public interface ScheduledExecutorService extends ExecutorService {
 3     /**
 4      * 在指定延迟后执行一个任务,只执行一次
 5      */
 6     public ScheduledFuture<?> schedule(Runnable command,
 7                        long delay, TimeUnit unit);
 8     /**
 9      * 与上面的方法相同,只是接受的是Callable任务
10      */
11     public <V> ScheduledFuture<V> schedule(Callable<V> callable,
12                        long delay, TimeUnit unit);
13     /**
14      * 创建并执行一个周期性的任务,在initialDelay延迟后每间隔period个单位执行一次,时间单位都是unit
15      * 每次执行任务的时间点是initialDelay, initialDelay+period, initialDelay + 2 * period...
16      */
17     public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
18                           long initialDelay,
19                           long period,
20                           TimeUnit unit);
21     /**
22      * 创建并执行一个周期性的任务,在initialDelay延迟后开始执行,在执行结束后再延迟delay个单位开始执行下一次任务,时间单位都是unit
23      * 每次执行任务的时间点是initialDelay, initialDelay+(任务运行时间+delay), initialDelay + 2 * (任务运行时间+delay)...
24      */
25     public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
26                              long initialDelay,
27                              long delay,
28                              TimeUnit unit);
29 }

ScheduledExecutorService定义了四个方法,已经在上面给出基本的解释。ScheduledExecutorService有 两个实现类,分别是DelegatedScheduledExecutorService和ScheduledThreadPoolExecutor,将 在后面介绍。还需要解释的是ScheduledFuture。

ScheduledFuture继承自Future和Delayed接口,自身没有添加方法。Delayed接口定义了一个获取剩余延迟的方法。

AbstractExecutorService

  1 // 提供ExecutorService的默认实现
  2 public abstract class AbstractExecutorService implements ExecutorService {
  3     /*
  4      * 为指定的Runnable和value构造一个FutureTask,value表示默认被返回的Future
  5      */
  6     protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  7         return new FutureTask<T>(runnable, value);
  8     }
  9     /*
 10      * 为指定的Callable创建一个FutureTask
 11      */
 12     protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
 13         return new FutureTask<T>(callable);
 14     }
 15     /*
 16      * 提交Runnable任务
 17      */
 18     public Future<?> submit(Runnable task) {
 19         if (task == null) throw new NullPointerException();
 20         // 通过newTaskFor方法构造RunnableFuture,默认的返回值是null
 21         RunnableFuture<Object> ftask = newTaskFor(task, null);
 22         // 调用具体实现的execute方法
 23         execute(ftask);
 24         return ftask;
 25     }
 26     /*
 27      * 提交Runnable任务
 28      */
 29     public <T> Future<T> submit(Runnable task, T result) {
 30         if (task == null) throw new NullPointerException();
 31         // 通过newTaskFor方法构造RunnableFuture,默认的返回值是result
 32         RunnableFuture<T> ftask = newTaskFor(task, result);
 33         execute(ftask);
 34         return ftask;
 35     }
 36     /*
 37      * 提交Callable任务
 38      */
 39     public <T> Future<T> submit(Callable<T> task) {
 40         if (task == null) throw new NullPointerException();
 41         RunnableFuture<T> ftask = newTaskFor(task);
 42         execute(ftask);
 43         return ftask;
 44     }
 45
 46     /*
 47      * doInvokeAny的具体实现(核心内容),其它几个方法都是重载方法,都对这个方法进行调用
 48      * tasks 是被执行的任务集,timed标志是否定时的,nanos表示定时的情况下执行任务的限制时间
 49      */
 50     private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
 51                             boolean timed, long nanos)
 52         throws InterruptedException, ExecutionException, TimeoutException {
 53         // tasks空判断
 54         if (tasks == null)
 55             throw new NullPointerException();
 56         // 任务数量
 57         int ntasks = tasks.size();
 58         if (ntasks == 0)
 59             throw new IllegalArgumentException();
 60         // 创建对应数量的Future返回集
 61         List<Future<T>> futures= new ArrayList<Future<T>>(ntasks);
 62         ExecutorCompletionService<T> ecs =
 63             new ExecutorCompletionService<T>(this);
 64         try {
 65             // 执行异常
 66             ExecutionException ee = null;
 67             // System.nanoTime()根据系统计时器当回当前的纳秒值
 68             long lastTime = (timed)? System.nanoTime() : 0;
 69             // 获取任务集的遍历器
 70             Iterator<? extends Callable<T>> it = tasks.iterator();
 71
 72             // 向执行器ExecutorCompletionService提交一个任务,并将结果加入futures中
 73             futures.add(ecs.submit(it.next
 74             // 修改任务计数器
 75             --ntasks;
 76             // 活跃任务计数器
 77             int active = 1;
 78             for (;;) {
 79                 // 获取并移除代表已完成任务的Future,如果不存在,返回null
 80                 Future<T> f = ecs.poll();
 81                 if (f == null) {
 82                     // 没有任务完成,且任务集中还有未提交的任务
 83                     if (ntasks > 0) {
 84                         // 剩余任务计数器减1
 85                         --ntasks;
 86                         // 提交任务并添加结果
 87                         futures.add(ecs.submit(it.next()));
 88                         // 活跃任务计数器加1
 89                         ++active;
 90                     }
 91                     // 没有剩余任务,且没有活跃任务(所有任务可能都会取消),跳过这一次循环
 92                     else if (active == 0)
 93                         break;
 94                     else if (timed) {
 95                         // 获取并移除代表已完成任务的Future,如果不存在,会等待nanos指定的纳秒数
 96                         f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
 97                         if (f == null)
 98                             throw new TimeoutException();
 99                         // 计算剩余可用时间
100                         long now = System.nanoTime();
101                         nanos -= now - lastTime;
102                         lastTime = now;
103                     }
104                     else
105                         // 获取并移除表示下一个已完成任务的未来,等待,如果目前不存在。
106                         // 执行到这一步说明已经没有任务任务可以提交,只能等待某一个任务的返回
107                         f = ecs.take();
108                 }
109                 // f不为空说明有一个任务完成了
110                 if (f != null) {
111                     // 已完成一个任务,所以活跃任务计数减1
112                     --active;
113                     try {
114                         // 返回该任务的结果
115                         return f.get();
116                     } catch (InterruptedException ie) {
117                         throw ie;
118                     } catch (ExecutionException eex) {
119                         ee = eex;
120                     } catch (RuntimeException rex) {
121                         ee = new ExecutionException(rex);
122                     }
123                 }
124             }
125             // 如果没有成功返回结果则抛出异常
126             if (ee == null)
127                 ee = new ExecutionException();
128             throw ee;
129
130         } finally {
131             // 无论执行中发生异常还是顺利结束,都将取消剩余未执行的任务
132             for (Future<T> f : futures)
133                 f.cancel(true);
134         }
135     }
136
137     public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
138         throws InterruptedException, ExecutionException {
139         try {
140             // 非定时任务的doInvokeAny调用
141             return doInvokeAny(tasks, false, 0);
142         } catch (TimeoutException cannotHappen) {
143             assert false;
144             return null;
145         }
146     }
147     // 定时任务的invokeAny调用,timeout表示超时时间,unit表示时间单位
148     public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
149                            long timeout, TimeUnit unit)
150         throws InterruptedException, ExecutionException, TimeoutException {
151         return doInvokeAny(tasks, true, unit.toNanos(timeout));
152     }
153     // 无超时设置的invokeAll方法
154     public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
155         throws InterruptedException {
156         // 空任务判断
157         if (tasks == null)
158             throw new NullPointerException();
159         // 创建大小为任务数量的结果集
160         List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
161         // 是否完成所有任务的标记
162         boolean done = false;
163         try {
164             // 遍历并执行任务
165             for (Callable<T> t : tasks) {
166                 RunnableFuture<T> f = newTaskFor(t);
167                 futures.add(f);
168                 execute(f);
169             }
170             // 遍历结果集
171             for (Future<T> f : futures) {
172                 // 如果某个任务没完成,通过f调用get()方法
173                 if (!f.isDone()) {
174                     try {
175                         // get方法等待计算完成,然后获取结果(会等待)。所以调用get后任务就会完成计算,否则会等待
176                         f.get();
177                     } catch (CancellationException ignore) {
178                     } catch (ExecutionException ignore) {
179                     }
180                 }
181             }
182             // 标志所有任务执行完成
183             done = true;
184             // 返回结果
185             return futures;
186         } finally {
187             // 假如没有完成所有任务(可能是发生异常等情况),将任务取消
188             if (!done)
189                 for (Future<T> f : futures)
190                     f.cancel(true);
191         }
192     }
193     // 超时设置的invokeAll方法
194     public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
195                                          long timeout, TimeUnit unit)
196         throws InterruptedException {
197         // 需要执行的任务集为空或时间单位为空,抛出异常
198         if (tasks == null || unit == null)
199             throw new NullPointerException();
200         // 将超时时间转为纳秒单位
201         long nanos = unit.toNanos(timeout);
202         // 创建任务结果集
203         List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
204         // 是否全部完成的标志
205         boolean done = false;
206         try {
207             // 遍历tasks,将任务转为RunnableFuture
208             for (Callable<T> t : tasks)
209                 futures.add(newTaskFor(t));
210             // 记录当前时间(单位是纳秒)
211             long lastTime = System.nanoTime();
212             // 获取迭代器
213             Iterator<Future<T>> it = futures.iterator();
214             // 遍历
215             while (it.hasNext()) {
216                 // 执行任务
217                 execute((Runnable)(it.next()));
218                 // 记录当前时间
219                 long now = System.nanoTime();
220                 // 计算剩余可用时间
221                 nanos -= now - lastTime;
222                 // 更新上一次执行时间
223                 lastTime = now;
224                 // 超时,返回保存任务状态的结果集
225                 if (nanos <= 0)
226                     return futures;
227             }
228
229             for (Future<T> f : futures) {
230                 // 如果有任务没完成
231                 if (!f.isDone()) {
232                     // 时间已经用完,返回保存任务状态的结果集
233                     if (nanos <= 0)
234                         return futures;
235                     try {
236                         // 获取计算结果,最多等待给定的时间nanos,单位是纳秒
237                         f.get(nanos, TimeUnit.NANOSECONDS);
238                     } catch (CancellationException ignore) {
239                     } catch (ExecutionException ignore) {
240                     } catch (TimeoutException toe) {
241                         return futures;
242                     }
243                     // 计算可用时间
244                     long now = System.nanoTime();
245                     nanos -= now - lastTime;
246                     lastTime = now;
247                 }
248             }
249             // 修改是否全部完成的标记
250             done = true;
251             // 返回结果集
252             return futures;
253         } finally {
254             // 假如没有完成所有任务(可能是时间已经用完、发生异常等情况),将任务取消
255             if (!done)
256                 for (Future<T> f : futures)
257                     f.cancel(true);
258         }
259     }
260 }

AbstractExecutor实现了ExecutorService接口的部分方法。具体代码的分析在上面已经给出。

AbstractExecutor有两个子类:DelegatedExecutorService、ThreadPoolExecutor。将在后面介绍。

下面是AbstractExecutor中涉及到的RunnableFuture、FutureTask、ExecutorCompletionService。

RunnableFuture继承自Future和Runnable,只有一个run()方法(Runnable中已经有一个run方法了,为什么 RunnableFuture还要重新写一个run方法呢?求高手指教)。RunnableFuture接口看上去就像是Future和Runnable 两个接口的组合。

FutureTask实现了RunnableFuture接口,除了实现了Future和Runnable中的方法外,它还有自己的方法和一个内部类Sync。

ExecutorCompletionService实现了CompletionService接口,将结果从复杂的一部分物种解耦出来。这些内容后续会介绍,不过这里先介绍框架中的其它内容,弄清整体框架。

下面看继承自AbstractExecutorService的ThreadPoolExecutor。

ThreadPoolExecutor

ThreadPoolExecutor(好长)

可以参考http://xtu-xiaoxin.iteye.com/blog/647744

从上面的框架结构图中可以可以看出剩下的就是ScheduledThreadPoolExecutor和Executors。Executors是一个工具类,提供一些工厂和实用方法。

下面看ScheduledThreadPoolExecutor,它继承自ThreadPoolExecutor并实现了ScheduledExecutorService接口。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor

在代码中都加了注释,我想大致能解释清楚吧。

Executor涉及的类还是比较多的,到此为止剩下的还有Executors

Executors

Executors中所定义的 ExecutorExecutorServiceScheduledExecutorServiceThreadFactoryCallable 类的工厂和实用方法。此类支持以下各种方法:

  • 创建并返回设置有常用配置字符串的 ExecutorService 的方法。
  • 创建并返回设置有常用配置字符串的 ScheduledExecutorService 的方法。
  • 创建并返回“包装的”ExecutorService 方法,它通过使特定于实现的方法不可访问来禁用重新配置。
  • 创建并返回 ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。
  • 创建并返回非闭包形式的 Callable 的方法,这样可将其用于需要 Callable 的执行方法中。

Executors提供的都是工具形式的方法,所以都是static的,并且这个类也没有必要实例化,所以它的构造方法时private的。下面主要看一下几个内部类。

   RunnableAdapter

 1 static final class RunnableAdapter<T> implements Callable<T> {
 2         final Runnable task;
 3         final T result;
 4         RunnableAdapter(Runnable  task, T result) {
 5             this.task = task;
 6             this.result = result;
 7         }
 8         public T call() {
 9             task.run();
10             return result;
11         }
12 }

适配器。以Callable的形式执行Runnable并且返回给定的result。

PrivilegedCallable

 1 static final class PrivilegedCallable<T> implements Callable<T> {
 2     private final AccessControlContext acc;
 3     private final Callable<T> task;
 4     private T result;
 5     private Exception exception;
 6     PrivilegedCallable(Callable<T> task) {
 7         this.task = task;
 8         this.acc = AccessController.getContext();
 9     }
10
11     public T call() throws Exception {
12         AccessController.doPrivileged(new PrivilegedAction<T>() {
13             public T run() {
14                 try {
15                     result = task.call();
16                 } catch (Exception ex) {
17                     exception = ex;
18                 }
19                 return null;
20             }
21         }, acc);
22         if (exception != null)
23             throw exception;
24         else
25             return result;
26     }
27 }

在访问控制下运行的Callable。涉及到Java.security包中的内容。

PrivilegedCallableUsingCurrentClassLoader类与上面的PrivilegedCallable类似,只是使用的是CurrentClassLoader。

DefaultThreadFactory

 1     static class DefaultThreadFactory implements ThreadFactory {
 2         static final AtomicInteger poolNumber = new AtomicInteger(1);
 3         final ThreadGroup group;
 4         final AtomicInteger threadNumber = new AtomicInteger(1);
 5         final String namePrefix;
 6
 7         DefaultThreadFactory() {
 8             SecurityManager s = System.getSecurityManager();
 9             group = (s != null)? s.getThreadGroup() :
10                                  Thread.currentThread().getThreadGroup();
11             namePrefix = "pool-" +
12                           poolNumber.getAndIncrement() +
13                          "-thread-";
14         }
15
16         public Thread newThread(Runnable r) {
17             // 调用Thread构造方法创建线程
18             Thread t = new Thread(group, r,
19                                   namePrefix + threadNumber.getAndIncrement(),
20                                   0);
21             // 取消守护线程设置
22             if (t.isDaemon())
23                 t.setDaemon(false);
24             // 设置默认优先级
25             if (t.getPriority() != Thread.NORM_PRIORITY)
26                 t.setPriority(Thread.NORM_PRIORITY);
27             return t;
28         }
29     }

DefaultThreadFactory 是默认的线程工程,提供创建线程的方法。

PrivilegedThreadFactory继承自DefaultThreadFactory,区别在于线程执行的run方法指定了classLoader并受到权限的控制。

DelegatedExecutorService继承自AbstractExecutorService,是一个包装类,暴露ExecutorService的方法。

DelegatedScheduledExecutorService继承自DelegatedExecutorService,实现了 ScheduledExecutorService接口。它也是一个包装类,公开ScheduledExecutorService方法。

Java Executor 框架的更多相关文章

  1. Java Executor框架使用

    Java Executor框架是Jdk1.5之后推出的,是为了更加方便的开发多线程应用而封装的框架: 相比传统的Thread类,Java Executor使用方便,性能更好,更易于管理,而且支持线程池 ...

  2. Java Executor框架

    java.util.concurrent 包中包含灵活的线程池实现,但是更重要的是,它包含用于管理实现 Runnable 的任务的执行的整个框架,该框架称为 Executor 框架.该框架基于生产者- ...

  3. 使用Java Executor框架实现多线程

    本文将涵盖两个主题: 通过实现Callable接口创建线程 在Java中使用Executor框架 实现Callable接口 为了创建一段可以在线程中运行的代码,我们创建了一个类,然后实现了Callab ...

  4. Java Executor 框架学习总结

    大多数并发都是通过任务执行的方式来实现的.一般有两种方式执行任务:串行和并行. class SingleThreadWebServer { public static void main(String ...

  5. java并发编程(十七)Executor框架和线程池

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17465497   Executor框架简介 在Java 5之后,并发编程引入了一堆新的启动 ...

  6. Java并发和多线程(二)Executor框架

    Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...

  7. java并发编程-Executor框架

    Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,Completion ...

  8. Java并发——线程池Executor框架

    线程池 无限制的创建线程 若采用"为每个任务分配一个线程"的方式会存在一些缺陷,尤其是当需要创建大量线程时: 线程生命周期的开销非常高 资源消耗 稳定性 引入线程池 任务是一组逻辑 ...

  9. JAVA的Executor框架

    Executor框架分离了任务的创建和执行.JAVA SE5的java.util.concurrent包中的执行器(Executor)管理Thread对象,从而简化了并发编程.Executor引入了一 ...

随机推荐

  1. hdu 4687 Boke and Tsukkomi

    Dancing link twice. Find the maximum combination numbers in the first time. Enumerate each node, dan ...

  2. Windows 编程之 对话框总结

    关于对话框 1 对话框种类 对话框也是一种资源,Windows中对话框分为模态的和非模态的,以及Windows系统中已经定义好的那些通用对话框,比方打开文件,目录,调色板,字符串查找等. 2 模态对话 ...

  3. 在界面线程不能使用Sleep和WaitForSingleObject之类的函数, 使用 MsgWaitForMultipleObjects

    http://blog.csdn.net/wishfly/article/details/3726985 你在主线程用了WaitForSingleObject,导致了消息循环的阻塞,界面假死. 然后在 ...

  4. Lucene.Net 2.3.1开发介绍 —— 三、索引(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(三) 3.Field配置所产生的效果 索引数据,简单的代码,只要两个方法就搞定了,而在索引过程中用到的一些类里最简单,作用也不小的就是F ...

  5. 【Node.js 自己封装的库 http_parse, libuv】

    [Node.js 自己封装的库 http_parse, libuv] Node.js 介绍:一个网络框架,更多:http://www.oschina.net/p/nodejs 官网:http://no ...

  6. STL 二分查找三兄弟(lower_bound(),upper_bound(),binary_search())

    一:起因 (1)STL中关于二分查找的函数有三个:lower_bound .upper_bound .binary_search  -- 这三个函数都运用于有序区间(当然这也是运用二分查找的前提),以 ...

  7. AMR音频编码器概述及文件格式分析

    全称Adaptive Multi-Rate,自适应多速率编码,主要用于移动设备的音频,压缩比比较大,但相对其他的压缩格式质量比较差,由于多用于人声,通话,效果还是很不错的. 一.分类 1. AMR: ...

  8. Activity组件的生命周期

    一.Activiy组件的三个状态: 1.前台状态(active) : 在屏幕的最上层,页面获得焦点,可以响应用户的操作2.可视状态(paused) : 不能与用户交互,但是还存在于可视区域内,它依然存 ...

  9. 分类器是如何做检测的?——CascadeClassifier中的detectMultiScale函数解读

    原地址:http://blog.csdn.net/delltdk/article/details/9186875 在进入detectMultiScal函数之前,首先需要对CascadeClassifi ...

  10. 【ALearning】第二章 Androidproject知识介绍

    本章介绍了主要的初步Androidproject成立了一个开发环境.为了Android意识的整体项目和理解.本章包含Android开发环境的搭建.第一Android工程Hello World与Andr ...