Android -- AsyncTask源码解析
1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结。本来早就想写这篇文章的,当时写《Android -- 从源码解析Handle+Looper+MessageQueue机制》的时候就是想为这篇文章做铺垫的,因为AsyncTask说里面还是使用的handle,所以先就写了handle这一篇。记得15年底去美团面试的时候,面试官就问我既然存在handle为什么google还要出AsyncTask(毕竟底层还是用的handle+Executor),当时也是不太懂。只是简单的说了下AsyncTask是对handle的封装,可能更加优化,性能好之类的。所以今天就带大家一起看一下它底层到底是怎么实现的。
2,分析+实例
在12年之前那时候还没有一些xtuils、volley的第三方的网络框架,也没有asyncTask的出现,那时候要请求一个网络数据,首先build request参数,然后由于请求网络是耗时操作,所以你得有个Executer或者线程,然后enqueue后,通过线程去run你的请求,得到服务器数据后,callback回调给你的上层。
在没有框架的年代,想要做一次请求,是万分痛苦的,你需要自己管理线程切换,需要自己解析读取数据,解析数据成对象,切换回主线程,回调给上层。
然后我们的AsyncTask顺势而生了,它不需要我们程序员再手动管理线程,动手写回调之类,为了防止有些同学压根都不知道这个类,所以这里我还是带着大家从一些概念到实例,再到源码。
public abstract class AsyncTask<Params, Progress, Result> { }
当我们使用asynctask的时候一般都会创建一个类继承自它,且需要确定它的三个泛型,这里的三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用Java.lang.Void类型代替。
在一般的get请求情况下我们第一个参数是传递的我们请求的地址,所以这里我们会传递一个String,而第二个参数是我们的后台执行的进度,如果你的需求需要实时的向用户展示请求的进度的话这里就可以填写Integer类型,否写可以写上Void,第三个参数是我们请求的结果,一般是byte[],String等,可以根据具体的需求。
再让我们看看它里面的几个重要的方法:
① onPreExecute():一般用来在执行后台任务前对UI做一些标记,例如dialog的show。
② doInBackground(Params... params) :用于执行较为耗时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
③ publishProgress(Progress... values):用来更新进度
④ onProgressUpdate(Progress... values):在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
⑤.onPostExecute(Result result):当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,将结果回调显示到UI组件上。
这里需要注意几个点:上面这五个方法都不是手动调用的,当你调用ascyntask.execute()方法之后上面的方法会自动调用、doInBackground是运行在子线程,不能进行ui操作。好了,大致的知识点都了解了,我们开始写一个栗子。
先看一下效果:
让我们直接来看一下自定义的MyAsyncTask的代码:
MyAsyncTask.java
public class MyAsyncTask extends AsyncTask<String ,Integer,byte[]> { /**
* 被调用后立即执行,一般用来在执行后台任务前对UI做一些标记
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.i("wangjitao","onPreExecute"+Thread.currentThread().getName());
} /**
* 在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
* @param params
* @return
*/
@Override
protected byte[] doInBackground(String... params) {
Log.i("wangjitao","doInBackground"+Thread.currentThread().getName()); //请求网络数据
InputStream inputStream = null;
HttpURLConnection httpURLConnection = null;
try {
URL url = new URL(params[0]);
if (url != null) {
httpURLConnection = (HttpURLConnection) url.openConnection(); // 设置连接网络的超时时间
httpURLConnection.setRequestMethod("GET");//GET和POST必须全大写
httpURLConnection.setConnectTimeout(10000);//连接的超时时间
httpURLConnection.setReadTimeout(5000);//读数据的超时时间 // 表示设置本次http请求使用GET方式请求
httpURLConnection.setRequestMethod("GET");
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == 200) {
// 从服务器获得一个输入流
InputStream is = httpURLConnection.getInputStream(); long total = httpURLConnection.getContentLength();
int count = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len=0;
while((len=is.read(buffer))!=-1){
baos.write(buffer, 0, len); count += len;
publishProgress((int) ((count / (float) total) * 100)); //为了演示进度,休眠500毫秒
Thread.sleep(500);
}
is.close();
byte[] datas = baos.toByteArray();
baos.close(); return datas; }
}
} catch (Exception e) { } return null;
} /**
* 当前进度的更新
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
Log.i("wangjitao","onProgressUpdate"+Thread.currentThread().getName());
mDialog.setProgress(values[0]);
} /**
* 当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
* @param bytes
*/
@Override
protected void onPostExecute(byte[] bytes) {
Log.i("wangjitao","onPostExecute"+Thread.currentThread().getName());
mDialog.dismiss();
mImageView.setImageBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.length));
} }
这里在点击button之后我调用了new MyAsyncTask().execute(url);,然后再MyAsyncTask的每个方法里面添加上了当前线程的log,想看一下doInBackground是不是像我们网上说的运行在子线程,这里注意一下,我们请求的是网络数据,记得在清单文件加上网络权限,打印结果如下:
08-16 05:36:24.978 19453-19453/com.ysten.anysctasksource I/wangjitao: onPreExecute:main
08-16 05:36:24.979 19453-23587/com.ysten.anysctasksource I/wangjitao: doInBackground:AsyncTask #1
08-16 05:36:28.573 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:29.074 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:29.574 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:30.075 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:30.575 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:31.086 19453-19453/com.ysten.anysctasksource I/wangjitao: onProgressUpdate:main
08-16 05:36:31.588 19453-19453/com.ysten.anysctasksource I/wangjitao: onPostExecute:main
可以看到doInBackground的确运行在子线程中,过一下我们从源码里面来验证一下。
3,源码分析
我们知道启动我们整个任务的就是我们new MyAsyncTask().execute(url);方法,所以我们首先来看一下execute()方法
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
} @MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
} mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params;
exec.execute(mFuture); return this;
}
第3行:我们知道execute方法是调用本类的executeOnExecutor。
第10-20行:用来判断当前AsyncTask的状态,一共存在三种状态 :PENDING(就绪)、RUNNING(运行)、FINDISH(完成),保证每次都是第一次运行此异步任务,不然就抛异常
第22行:将当前的运行状态切换至RUNNING状态
第24行:执行了我们的onPreExecute() ,注意一下,这时候我们的第一个准备操作的方法被执行了(所以一般我们在这个方法做一些ui的准备操作)
第26行:将我们的params复制成员变量mWorker对象的mParams上(有同学说这里我还不知道mWorker,先不要慌,继续往下看)
第27行:exec.execute(mFuture);这行代码我们更加一脸懵逼,mFuture、exec分别代表什么对象呢?exec.execute()是用来干什么的呢?
ok,看完 上述代码我们一定对mWorker、mFuture、exec这些成员变量有所疑惑吧,没事我们来一点点的来看。
首先来看一下我们的mWorker究竟是什么,源码如下:
private final WorkerRunnable<Params, Result> mWorker; private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
} public interface Callable<V> {
V call() throws Exception;
} mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
第4-10行:可以看到我们的mWorker是Callable的子类,包含一个call方法和一个变量mParams。
这里要补充一下,mWorker是在构造函数中初始化的,因为是一个抽象类,在这里new了一个实现类,实现了call方法
第14-27行:首先将mTaskInvoked的value设置成true(这里要留心一下这个操作,后面还有地方对mTaskInvoked进行判断的),然后Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);设置子线程优先执行,然后就执行到了doInBackground(),看到没,这里就到了我们的第二个方法,这里还有一个疑问,我们上面知道doInBackground是执行在子线程中的,那怎么执行在子线程中呢(我们带着这个问题),先继续往下看。
当运行到finally代码块中调用的是 postResult(result);我们接下来看看postResult()的具体代码:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
} @SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
第3-5行:很熟悉没?了解我们的异步消息handle的,都知道这是干什么的而 AsyncTaskResult类就是把当前从网络下请求的结果数据result保存到data中
看到这,我相信大家肯定会想到,在某处肯定存在一个handler,且重写了handleMessage方法等待消息的传入
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
} @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
果然,在handleMessage中我们的MESSAGE_POST_RESULT之后时调用的本身的finish()方法(这里也请大家留心一下MESSAGE_POST_PROGRESS这个消息,后面也会用到),那么让我们再看看finish里面的具体代码吧
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
首先判断当前的asynctask是否被取消,若果没取消则执行onPostExecute(result),这时候数据已经回调了,到这里我们就差不多执行完了几个重要的方法了,然后再将asynctask的执行状态切换到FINISH状态。
到这里我们的mWorker的工作流程全部了解了,继续往下看到我们mFuture这个变量
private final FutureTask<Result> mFuture; mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
mFuture这个变量也是初始化在我们的构造函数里面,首先我们不去看FutureTask这个类中的具体代码包含的方法和变量(由于代码有点多,后面的话单独的给大家分析一下),我们先继续往下看,mFuture的初始化是将将mWorker作为参数,并重写其done()方法。
done方法中具体的时调用postResultIfNotInvoked()方法,而get()方法里面是获取的我们的Result泛型,拿到的是我们mWorker.call的返回值,看一下具体的代码
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
如果mTaskInvoked不为true,则执行postResult;但是在mWorker初始化时就已经将mTaskInvoked为true(我在上面call方法里面提醒过大家的),所以一般这个postResult执行不到。
ok,到这里我们我们基本上都了解都mWorker、mFuture的意思了,接下来看看我们的exec这个变量的含义吧
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
} private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive; public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
} protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
第1-3行:从上面的代码我们可以知道,exec为executeOnExecutor(sDefaultExecutor, params)中的sDefaultExecutor
第5-7行:sDefaultExecutor实际上是一个SerialExecutor对象
第9-26行:这里我们可以看到SerialExecutor里面内部维持一个任务队列,在execute方法中将runnable放入mTasks队尾,然后判断当前mActive是否为空,如果不为空在调用scheduleNext()方法;
第28-33行:取出任务队列中的队首任务,如果不为null则赋值给mActive对象并传入THREAD_POOL_EXECUTOR进行执行。
所以到这里我们再来看看THREAD_POOL_EXECUTOR是一个什么鬼!
public static final Executor THREAD_POOL_EXECUTOR; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
}; private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
可以看到THREAD_POOL_EXECUTOR其实就是一个线程池
第9-12行:是一些线程池的配置,在3.0以前最大线程并发数MAXIMUM_POOL_SIZE是一个固定值128,现在都是根据当前cpu合数来动态设置的,例如现在的床铺是四核的,所以MAXIMUM_POOL_SIZE为9,并且将阻塞线程由以前的10个变成现在的128个(记得前不久面试的时候面试官就问道为什么会设置128这个数值为什么不是129或者140,到现在我还是不知道怎么回答这个问题),那么是不是意味着我们并发的线程数超过137个之后,我们的程序就会抛异常,其实不是这样的,我们之前的SerialExecutor 类中的execute里面的代码
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
} protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
当存在10个任务的时候,第一个任务进来,mActive为空执行scheduleNext,在scheduleNext方法中取出线程头部添加到线程池中,然后复制给mActive,当第二个任务进来时,mActive不为空,也就是说不执行scheduleNext()方法,所以只有等到第一个任务run方法执行完之后调用finally中的scheduleNext()才会执行下一个任务,所以来说其实还是单线程线性执行,一个接一个。
ok,到这里我们基本上完成了对源码的解读,但是我们还是有几个疑问,我们的onProgressUpdate()和publishProgress()怎么没有执行啊,onProgressUpdate方法只有在调用publishProgress()之后才执行,所以让我们来看看publishProgress()的源码
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
很熟悉,又发现了我们的handle,继续找到我们的消息接受的地方
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
} @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
这不是我们刚刚看过的代码,没错,上面我叫大家留意过的MESSAGE_POST_PROGRESS就是这个用于更新我们进度的,在里面直接调用onProgressUpdate()方法。
ok,我们现在还是有一个疑问,就是为什么说doInBackground运行在子线程中
我们进过上面的源码分析指导doInBackground是在mWorker的call方法中调用的,所以我们现在只需要在哪里调用了mWorker.call()代码就行
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
由于我们知道mWorker是以参数传递到mFuture中的,所以我们还是要看FutureTask这个类的源码
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
} public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
这里我只展示除了重要的几个方法,我们知道mFuture是以参数传递到SerialExecutor类中的execute方法中
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
所以这里new了一个runnable创建了一个子线程,并调用mFuture的run方法,而在mFuture的run方法中有一行result = c.call();这里的c就是我们的mWorker,所以这也就解释了我们的doInBackground是运行在子线程中。
ok,到这里我们的问题解决的差不多了,但是有一些公司喜欢问一下3.0之前的asynctask 的缺陷问题,在这里给大家找了一写,就直接贴出来了,够大家应付面试,具体是怎么产生的就大家自己去研究了,篇幅有点长,就不过多介绍了。
如果现在大家去面试,被问到AsyncTask的缺陷,可以分为两个部分说,在3.0以前,最大支持128个线程的并发,10个任务的等待。在3.0以后,无论有多少任务,都会在其内部单线程执行;
这是GitHub代码,有需要的同学可以去下载一下.在写线程池这一块感觉自己不是很熟悉,所以下一篇打算来总结总结线程池。
Android -- AsyncTask源码解析的更多相关文章
- Android AsyncTask 源码解析
1. 官方介绍 public abstract class AsyncTask extends Object java.lang.Object ↳ android.os.AsyncTask&l ...
- 还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar
或许对于许多Android开发者来说,所谓的Android工程师的工作"不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上"就好了,源码什么的,看 ...
- Android EventBus源码解析 带你深入理解EventBus
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...
- Android -- 从源码解析Handle+Looper+MessageQueue机制
1,今天和大家一起从底层看看Handle的工作机制是什么样的,那么在引入之前我们先来了解Handle是用来干什么的 handler通俗一点讲就是用来在各个线程之间发送数据的处理对象.在任何线程中,只要 ...
- Android LayoutInflater源码解析:你真的能正确使用吗?
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...
- Android HandlerThread源码解析
在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环.那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时 ...
- Android——LruCache源码解析
以下针对 Android API 26 版本的源码进行分析. 在了解LruCache之前,最好对LinkedHashMap有初步的了解,LruCache的实现主要借助LinkedHashMap.Lin ...
- Android DiskLruCache 源码解析 硬盘缓存的绝佳方案
一.概述 依旧是整理东西,所以近期的博客涉及的东西可能会比较老一点,会分析一些经典的框架,我觉得可能也是每个优秀的开发者必须掌握的东西:那么对于Disk Cache,DiskLruCache可以算佼佼 ...
- Android EventBus源码解析
项目地址 :https://github.com/greenrobot/EventBus 这个项目个人感觉就是为了解决回调事件过多的,比方说A函数在做完以后 要调用b类的c函数,那我们通常的做法就是 ...
随机推荐
- 3.VBScript基础
1.VBS只有一种数据类型 ->Variant类似于泛类型,其中具体类型会在调用的时候具体化 2.声明变量可以用Dim语句,Public语句,Private语句 声明多个变量用逗号分隔 也可以隐 ...
- 6.javaweb之respose对象
1.respose的生成的outer对象要优先于内置的out对象输出 response.setContentType("text/html;charaset=utf-8");//设 ...
- python迭代器生成器(二)
其他内置类型迭代器 除了文件以及列表这样的实际的序列外,其他类型也有适合的迭代器. 遍历字典的经典方法是明确的获取其键的列表. 在最近的python版本中,字典有一个迭代器,在迭代环境中,会自动一次返 ...
- 【Linux相识相知】bash的基础特性
命令历史 shell进程会记录用户提交执行过的命令 可以是用history查看: [root@localhost dev]# history ss -tnl ifconfig vi /etc/sysc ...
- 通用JSONHelp 的通用的封装
1. 最近项目已经上线了 ,闲暇了几天 想将JSON 的序列化 以及反序列化进行重新的封装一下本人定义为JSONHelp,虽然Microsoft 已经做的很好了.但是我想封装一套为自己开发的项目使用 ...
- 拥抱.NET Core系列:依赖注入(2)
上一篇"拥抱.NET Core系列:依赖注入(1)"大体介绍了服务注册.获取和生命周期,这一篇来做一些补充. 由于内容跨度大(.NET Core.ASP.NET Core),所以文 ...
- 历年NOIP中的搜索题
什么题目都不会做于是开始做搜索题. 然而我搜索题也不会做了. 铁定没戏的蒟蒻. 1.NOIP2004 虫食算 “对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立.输入数据 ...
- (转发)RequestDispatcher的include()方法和forward()方法的区别
forward(): 该方法用于将请求从一个Servlet传递给服务器上的另外的Servlet.JSP页面或者是HTML文件. 在Servlet中,可以对请求做一个初步的处理,然后调用这个方法,将请求 ...
- 部分转载[C#性能优化实践]
全文出处:http://www.infoq.com/cn/articles/C-sharp-performance-optimization 1.性能 主要指两个方面:内存消耗和执行速度.性能优化简而 ...
- Entity Framework Core 生成跟踪列
本文翻译自<Entity Framework Core: Generate tracking columns>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 注意:我使用的是 ...