[
  {
    "path": "Part1/Android/AIDL.md",
    "content": "#AIDL\n---\n\n1. 创建一个接口，再里面定义方法\n\n```\npackage com.example.taidl;  \ninterface ICalcAIDL  \n{  \n    int add(int x , int y);  \n    int min(int x , int y );  \n} \n```\n\nbuild一下gen目录下会生成ICalcAIDL.java文件\n\n```\n/*\n * This file is auto-generated.  DO NOT MODIFY.\n * Original file: /Users/dream/Downloads/android/androidProject/TAIDL/src/com/example/taidl/ICalcAIDL.aidl\n */\npackage com.example.taidl;\npublic interface ICalcAIDL extends android.os.IInterface\n{\n/** Local-side IPC implementation stub class. */\npublic static abstract class Stub extends android.os.Binder implements com.example.taidl.ICalcAIDL\n{\nprivate static final java.lang.String DESCRIPTOR = \"com.example.taidl.ICalcAIDL\";\n/** Construct the stub at attach it to the interface. */\npublic Stub()\n{\nthis.attachInterface(this, DESCRIPTOR);\n}\n/**\n * Cast an IBinder object into an com.example.taidl.ICalcAIDL interface,\n * generating a proxy if needed.\n */\npublic static com.example.taidl.ICalcAIDL asInterface(android.os.IBinder obj)\n{\nif ((obj==null)) {\nreturn null;\n}\nandroid.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);\nif (((iin!=null)&&(iin instanceof com.example.taidl.ICalcAIDL))) {\nreturn ((com.example.taidl.ICalcAIDL)iin);\n}\nreturn new com.example.taidl.ICalcAIDL.Stub.Proxy(obj);\n}\n@Override public android.os.IBinder asBinder()\n{\nreturn this;\n}\n@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException\n{\nswitch (code)\n{\ncase INTERFACE_TRANSACTION:\n{\nreply.writeString(DESCRIPTOR);\nreturn true;\n}\ncase TRANSACTION_add:\n{\ndata.enforceInterface(DESCRIPTOR);\nint _arg0;\n_arg0 = data.readInt();\nint _arg1;\n_arg1 = data.readInt();\nint _result = this.add(_arg0, _arg1);\nreply.writeNoException();\nreply.writeInt(_result);\nreturn true;\n}\ncase TRANSACTION_min:\n{\ndata.enforceInterface(DESCRIPTOR);\nint _arg0;\n_arg0 = data.readInt();\nint _arg1;\n_arg1 = data.readInt();\nint _result = this.min(_arg0, _arg1);\nreply.writeNoException();\nreply.writeInt(_result);\nreturn true;\n}\n}\nreturn super.onTransact(code, data, reply, flags);\n}\nprivate static class Proxy implements com.example.taidl.ICalcAIDL\n{\nprivate android.os.IBinder mRemote;\nProxy(android.os.IBinder remote)\n{\nmRemote = remote;\n}\n@Override public android.os.IBinder asBinder()\n{\nreturn mRemote;\n}\npublic java.lang.String getInterfaceDescriptor()\n{\nreturn DESCRIPTOR;\n}\n@Override public int add(int x, int y) throws android.os.RemoteException\n{\nandroid.os.Parcel _data = android.os.Parcel.obtain();\nandroid.os.Parcel _reply = android.os.Parcel.obtain();\nint _result;\ntry {\n_data.writeInterfaceToken(DESCRIPTOR);\n_data.writeInt(x);\n_data.writeInt(y);\nmRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);\n_reply.readException();\n_result = _reply.readInt();\n}\nfinally {\n_reply.recycle();\n_data.recycle();\n}\nreturn _result;\n}\n@Override public int min(int x, int y) throws android.os.RemoteException\n{\nandroid.os.Parcel _data = android.os.Parcel.obtain();\nandroid.os.Parcel _reply = android.os.Parcel.obtain();\nint _result;\ntry {\n_data.writeInterfaceToken(DESCRIPTOR);\n_data.writeInt(x);\n_data.writeInt(y);\nmRemote.transact(Stub.TRANSACTION_min, _data, _reply, 0);\n_reply.readException();\n_result = _reply.readInt();\n}\nfinally {\n_reply.recycle();\n_data.recycle();\n}\nreturn _result;\n}\n}\nstatic final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);\nstatic final int TRANSACTION_min = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);\n}\npublic int add(int x, int y) throws android.os.RemoteException;\npublic int min(int x, int y) throws android.os.RemoteException;\n}\n\n```\n\n2. 新建一个Service\n\n```\npackage com.example.taidl;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteException;\nimport android.util.Log;\n\npublic class CalcService extends Service{\n\n\tprivate static final String TAG = \"server\";  \n\t\n\tpublic void onCreate()  \n    {  \n        Log.e(TAG, \"onCreate\");  \n    }  \n  \n    public IBinder onBind(Intent t)  \n    {  \n        Log.e(TAG, \"onBind\");  \n        return mBinder;  \n    }  \n  \n    public void onDestroy()  \n    {  \n        Log.e(TAG, \"onDestroy\");  \n        super.onDestroy();  \n    }  \n  \n    public boolean onUnbind(Intent intent)  \n    {  \n        Log.e(TAG, \"onUnbind\");  \n        return super.onUnbind(intent);  \n    }  \n  \n    public void onRebind(Intent intent)  \n    {  \n        Log.e(TAG, \"onRebind\");  \n        super.onRebind(intent);  \n    }  \n\tprivate final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub() {\n\t\t\n\t\t@Override\n\t\tpublic int min(int x, int y) throws RemoteException {\n\t\t\treturn x + y;\n\t\t}\n\t\t\n\t\t@Override\n\t\tpublic int add(int x, int y) throws RemoteException {\n\t\t\t// TODO Auto-generated method stub\n\t\t\treturn x - y;\n\t\t}\n\t};\n\t\n\t\n\n}\n\n```\n\n创建了一个mBinder对象，并在Service的onBind方法中返回\n\n注册：\n\n```\n        <service android:name=\"com.example.taidl.CalcService\">\n            <intent-filter>  \n               <action android:name=\"com.example.taidl.calc\" />  \n  \n               <category android:name=\"android.intent.category.DEFAULT\" />  \n           </intent-filter>  \n        </service>\n```\n\n我们一会会在别的应用程序中通过Intent来查找此Service；这个不需要Activity，所以我也就没写Activity，安装完成也看不到安装图标，悄悄在后台运行着。服务端编写完毕。下面开始编写客户端:\n\n```\npackage com.example.tclient;\n\nimport com.example.taidl.ICalcAIDL;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Bundle;\nimport android.os.IBinder;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Toast;\n\npublic class MainActivity extends Activity {\n\n\tprivate ICalcAIDL mCalcAidl;\n\n\tprivate ServiceConnection mServiceConn = new ServiceConnection()\n\t{\n\t\t@Override\n\t\tpublic void onServiceDisconnected(ComponentName name)\n\t\t{\n\t\t\tLog.e(\"client\", \"onServiceDisconnected\");\n\t\t\tmCalcAidl = null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void onServiceConnected(ComponentName name, IBinder service)\n\t\t{\n\t\t\tLog.e(\"client\", \"onServiceConnected\");\n\t\t\tmCalcAidl = ICalcAIDL.Stub.asInterface(service);\n\t\t}\n\t};\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState)\n\t{\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.activity_main);\n\n\t}\n\t\n\t/**\n\t * 点击BindService按钮时调用\n\t * @param view\n\t */\n\tpublic void bindService(View view)\n\t{\n\t\tIntent intent = new Intent();\n\t\tintent.setAction(\"com.example.taidl.calc\");\n\t\tbindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);\n\t}\n\t/**\n\t * 点击unBindService按钮时调用\n\t * @param view\n\t */\n\tpublic void unbindService(View view)\n\t{\n\t\tunbindService(mServiceConn);\n\t}\n\t/**\n\t * 点击12+12按钮时调用\n\t * @param view\n\t */\n\tpublic void addInvoked(View view) throws Exception\n\t{\n\n\t\tif (mCalcAidl != null)\n\t\t{\n\t\t\tint addRes = mCalcAidl.add(12, 12);\n\t\t\tToast.makeText(this, addRes + \"\", Toast.LENGTH_SHORT).show();\n\t\t} else\n\t\t{\n\t\t\tToast.makeText(this, \"服务器被异常杀死，请重新绑定服务端\", Toast.LENGTH_SHORT)\n\t\t\t\t\t.show();\n\n\t\t}\n\n\t}\n\t/**\n\t * 点击50-12按钮时调用\n\t * @param view\n\t */\n\tpublic void minInvoked(View view) throws Exception\n\t{\n\n\t\tif (mCalcAidl != null)\n\t\t{\n\t\t\tint addRes = mCalcAidl.min(50, 12);\n\t\t\tToast.makeText(this, addRes + \"\", Toast.LENGTH_SHORT).show();\n\t\t} else\n\t\t{\n\t\t\tToast.makeText(this, \"服务器未绑定或被异常杀死，请重新绑定服务端\", Toast.LENGTH_SHORT)\n\t\t\t\t\t.show();\n\n\t\t}\n\n\t}\n}\n\n```\n\n将服务端的aidl文件完整的复制过来，包名一定要一致。\n\n##分析AIDL生成的代码\n\n1. 服务端\n\n```\nprivate final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub()  \n    {  \n  \n        @Override  \n        public int add(int x, int y) throws RemoteException  \n        {  \n            return x + y;  \n        }  \n  \n        @Override  \n        public int min(int x, int y) throws RemoteException  \n        {  \n            return x - y;  \n        }  \n  \n    };  \n\n```\n\nICalcAILD.Stub来执行的，让我们来看看Stub这个类的声明：\n\n```\npublic static abstract class Stub extends android.os.Binder implements com.zhy.calc.aidl.ICalcAIDL  \n```\n\n清楚的看到这个类是Binder的子类，是不是符合我们文章开通所说的服务端其实是一个Binder类的实例\n接下来看它的onTransact()方法：\n\n```\n@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException  \n{  \nswitch (code)  \n{  \ncase INTERFACE_TRANSACTION:  \n{  \nreply.writeString(DESCRIPTOR);  \nreturn true;  \n}  \ncase TRANSACTION_add:  \n{  \ndata.enforceInterface(DESCRIPTOR);  \nint _arg0;  \n_arg0 = data.readInt();  \nint _arg1;  \n_arg1 = data.readInt();  \nint _result = this.add(_arg0, _arg1);  \nreply.writeNoException();  \nreply.writeInt(_result);  \nreturn true;  \n}  \ncase TRANSACTION_min:  \n{  \ndata.enforceInterface(DESCRIPTOR);  \nint _arg0;  \n_arg0 = data.readInt();  \nint _arg1;  \n_arg1 = data.readInt();  \nint _result = this.min(_arg0, _arg1);  \nreply.writeNoException();  \nreply.writeInt(_result);  \nreturn true;  \n}  \n}  \nreturn super.onTransact(code, data, reply, flags);  \n}  \n```\n\n文章开头也说到服务端的Binder实例会根据客户端依靠Binder驱动发来的消息，执行onTransact方法，然后由其参数决定执行服务端的代码。\n可以看到onTransact有四个参数\ncode ， data ，replay ， flags\n\n* code 是一个整形的唯一标识，用于区分执行哪个方法，客户端会传递此参数，告诉服务端执行哪个方法\n* data客户端传递过来的参数\n* replay服务器返回回去的值\n* flags标明是否有返回值，0为有（双向），1为没有（单向）\n\n我们仔细看case TRANSACTION_min中的代码\n\ndata.enforceInterface(DESCRIPTOR);\n\n与客户端的writeInterfaceToken对用，标识远程服务的名称\n\n```\nint _arg0;\n_arg0 = data.readInt();\nint _arg1;\n_arg1 = data.readInt();\n```\n\n接下来分别读取了客户端传入的两个参数\n\n```\nint _result = this.min(_arg0, _arg1);\nreply.writeNoException();\nreply.writeInt(_result);\n```\n\n然后执行this.min，即我们实现的min方法；返回result由reply写回。\n\nadd同理，可以看到服务端通过AIDL生成Stub的类，封装了服务端本来需要写的代码。\n\n###客户端\n\n客户端主要通过ServiceConnected与服务端连接\n\n```\nprivate ServiceConnection mServiceConn = new ServiceConnection()  \n    {  \n        @Override  \n        public void onServiceDisconnected(ComponentName name)  \n        {  \n            Log.e(\"client\", \"onServiceDisconnected\");  \n            mCalcAidl = null;  \n        }  \n  \n        @Override  \n        public void onServiceConnected(ComponentName name, IBinder service)  \n        {  \n            Log.e(\"client\", \"onServiceConnected\");  \n            mCalcAidl = ICalcAIDL.Stub.asInterface(service);  \n        }  \n    };  \n```\n\n如果你比较敏锐，应该会猜到这个onServiceConnected中的IBinder实例，其实就是我们文章开通所说的Binder驱动，也是一个Binder实例\n在ICalcAIDL.Stub.asInterface中最终调用了：\n\n```\nreturn new com.zhy.calc.aidl.ICalcAIDL.Stub.Proxy(obj);  \n```\n这个Proxy实例传入了我们的Binder驱动，并且封装了我们调用服务端的代码，文章开头说，客户端会通过Binder驱动的transact()方法调用服务端代码\n\n直接看Proxy中的add方法\n\n```\n@Override public int add(int x, int y) throws android.os.RemoteException  \n{  \nandroid.os.Parcel _data = android.os.Parcel.obtain();  \nandroid.os.Parcel _reply = android.os.Parcel.obtain();  \nint _result;  \ntry {  \n_data.writeInterfaceToken(DESCRIPTOR);  \n_data.writeInt(x);  \n_data.writeInt(y);  \nmRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);  \n_reply.readException();  \n_result = _reply.readInt();  \n}  \nfinally {  \n_reply.recycle();  \n_data.recycle();  \n}  \nreturn _result;  \n}  \n```\n\n首先声明两个Parcel对象，一个用于传递数据，一个用户接收返回的数据\n\n```\n_data.writeInterfaceToken(DESCRIPTOR);与服务器端的enforceInterfac对应\n_data.writeInt(x);\n_data.writeInt(y);写入需要传递的参数\nmRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);\n```\n\n终于看到了我们的transact方法，第一个对应服务端的code,_data,_repay分别对应服务端的data，reply，0表示是双向的\n\n```\n_reply.readException();\n_result = _reply.readInt();\n```\n\n最后读出我们服务端返回的数据，然后return。可以看到和服务端的onTransact基本是一行一行对应的。\n\n我们已经通过AIDL生成的代码解释了Android Binder框架的工作原理。Service的作用其实就是为我们创建Binder驱动，即服务端与客户端连接的桥梁。\n"
  },
  {
    "path": "Part1/Android/ANR问题.md",
    "content": "#ANR\n---\n\n1、ANR排错一般有三种类型\n\n1. KeyDispatchTimeout(5 seconds) --主要是类型按键或触摸事件在特定时间内无响应\n2. BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成\n3. ServiceTimeout(20 secends) --小概率事件 Service在特定的时间内无法处理完成\n\n2、哪些操作会导致ANR\n在主线程执行以下操作：\n1. 高耗时的操作，如图像变换\n2. 磁盘读写，数据库读写操作\n3. 大量的创建新对象\n\n\n3、如何避免\n\n1. UI线程尽量只做跟UI相关的工作\n2. 耗时的操作(比如数据库操作，I/O，连接网络或者别的有可能阻塞UI线程的操作)把它放在单独的线程处理\n3. 尽量用Handler来处理UIThread和别的Thread之间的交互\n\n4、解决的逻辑\n1. 使用AsyncTask\n\t1. 在doInBackground()方法中执行耗时操作\n\t2. 在onPostExecuted()更新UI\n2. 使用Handler实现异步任务\n\t1. 在子线程中处理耗时操作\n\t2. 处理完成之后，通过handler.sendMessage()传递处理结果\n\t3. 在handler的handleMessage()方法中更新UI\n\t4. 或者使用handler.post()方法将消息放到Looper中\n\t\n\n5、如何排查\n\n1. 首先分析log\n2. 从trace.txt文件查看调用stack，adb pull data/anr/traces.txt ./mytraces.txt\n3. 看代码\n4. 仔细查看ANR的成因(iowait?block?memoryleak?)\n\n6、监测ANR的Watchdog\n\n最近出来一个叫LeakCanary\n\n#FC(Force Close)\n##什么时候会出现\n1. Error\n2. OOM，内存溢出\n3. StackOverFlowError\n4. Runtime,比如说空指针异常\n\n##解决的办法\n1. 注意内存的使用和管理\n2. 使用Thread.UncaughtExceptionHandler接口\n"
  },
  {
    "path": "Part1/Android/APP启动过程.md",
    "content": "#APP启动过程\n---\n\n![](http://7xntdm.com1.z0.glb.clouddn.com/activity_start_flow.png)\n\n* 上图就可以很好的说明App启动的过程\n* ActivityManagerService组织回退栈时以ActivityRecord为基本单位，所有的ActivityRecord放在同一个ArrayList里，可以将mHistory看作一个栈对象，索引0所指的对象位于栈底，索引mHistory.size()-1所指的对象位于栈顶\n* Zygote进程孵化出新的应用进程后，会执行ActivityThread类的main方法.在该方法里会先准备好Looper和消息队列，然后调用attach方法将应用进程绑定到ActivityManagerService，然后进入loop循环，不断地读取消息队列里的消息，并分发消息。\n* ActivityThread的main方法执行后,应用进程接下来通知ActivityManagerService应用进程已启动，ActivityManagerService保存应用进程的一个代理对象，这样ActivityManagerService可以通过这个代理对象控制应用进程，然后ActivityManagerService通知**应用进程**创建入口Activity的实例，并执行它的生命周期方法"
  },
  {
    "path": "Part1/Android/Activity启动过程全解析.md",
    "content": "#Activity启动过程\n---\n\n###一些基本的概念\n\n* ActivityManagerServices，简称AMS，服务端对象，负责系统中所有Activity的生命周期\n* ActivityThread，App的真正入口。当开启App之后，会调用main()开始运行，开启消息循环队列，这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合，一起完成Activity的管理工作\n* ApplicationThread，用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时，通过ApplicationThread的代理对象与ActivityThread通讯。\n* ApplicationThreadProxy，是ApplicationThread在服务器端的代理，负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。\n* Instrumentation，每一个应用程序只有一个Instrumentation对象，每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家，ActivityThread要创建或暂停某个Activity时，都需要通过Instrumentation来进行具体的操作。\n* ActivityStack，Activity在AMS的栈管理，用来记录已经启动的Activity的先后关系，状态信息等。通过ActivityStack决定是否需要启动新的进程。\n* ActivityRecord，ActivityStack的管理对象，每个Activity在AMS对应一个* ActivityRecord，来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。\n* TaskRecord，AMS抽象出来的一个“任务”的概念，是记录ActivityRecord的栈，一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode，那么对这个概念应该不陌生。\n\n###回答一些问题\n\n**zygote是什么？有什么作用？**\n\nzygote意为“受精卵“。Android是基于Linux系统的，而在Linux中，所有的进程都是由init进程直接或者是间接fork出来的，zygote进程也不例外。\n\n在Android系统里面，zygote是一个进程的名字。Android是基于Linux System的，当你的手机开机的时候，Linux的内核加载完成之后就会启动一个叫“init“的进程。在Linux System里面，所有的进程都是由init进程fork出来的，我们的zygote进程也不例外。\n\n我们都知道，每一个App其实都是\n\n* 一个单独的dalvik虚拟机\n* 一个单独的进程\n\n所以当系统里面的第一个zygote进程运行之后，在这之后再开启App，就相当于开启一个新的进程。而为了实现资源共用和更快的启动速度，Android系统开启新进程的方式，是通过fork第一个zygote进程实现的。所以说，除了第一个zygote进程，其他应用所在的进程都是zygote的子进程，这下你明白为什么这个进程叫“受精卵”了吧？因为就像是一个受精卵一样，它能快速的分裂，并且产生遗传物质一样的细胞！\n\n**SystemServer是什么？有什么作用？它与zygote的关系是什么？**\n\n首先我要告诉你的是，SystemServer也是一个进程，而且是由zygote进程fork出来的。\n\n知道了SystemServer的本质，我们对它就不算太陌生了，这个进程是Android Framework里面两大非常重要的进程之一——另外一个进程就是上面的zygote进程。\n\n为什么说SystemServer非常重要呢？因为系统里面重要的服务都是在这个进程里面开启的，比如 \nActivityManagerService、PackageManagerService、WindowManagerService等等，看着是不是都挺眼熟的？\n\n那么这些系统服务是怎么开启起来的呢？\n\n在zygote开启的时候，会调用ZygoteInit.main()进行初始化\n\n```\npublic static void main(String argv[]) {\n\n     ...ignore some code...\n\n    //在加载首个zygote的时候，会传入初始化参数，使得startSystemServer = true\n     boolean startSystemServer = false;\n     for (int i = 1; i < argv.length; i++) {\n                if (\"start-system-server\".equals(argv[i])) {\n                    startSystemServer = true;\n                } else if (argv[i].startsWith(ABI_LIST_ARG)) {\n                    abiList = argv[i].substring(ABI_LIST_ARG.length());\n                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {\n                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());\n                } else {\n                    throw new RuntimeException(\"Unknown command line argument: \" + argv[i]);\n                }\n            }\n\n            ...ignore some code...\n\n         //开始fork我们的SystemServer进程\n     if (startSystemServer) {\n                startSystemServer(abiList, socketName);\n         }\n\n     ...ignore some code...\n\n}\n```\n\n我们看下startSystemServer()做了些什么\n\n```\n    /**留着这个注释，就是为了说明SystemServer确实是被fork出来的\n     * Prepare the arguments and fork for the system server process.\n     */\n    private static boolean startSystemServer(String abiList, String socketName)\n            throws MethodAndArgsCaller, RuntimeException {\n\n         ...ignore some code...\n\n        //留着这段注释，就是为了说明上面ZygoteInit.main(String argv[])里面的argv就是通过这种方式传递进来的\n        /* Hardcoded command line to start the system server */\n        String args[] = {\n            \"--setuid=1000\",\n            \"--setgid=1000\",\n            \"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007\",\n            \"--capabilities=\" + capabilities + \",\" + capabilities,\n            \"--runtime-init\",\n            \"--nice-name=system_server\",\n            \"com.android.server.SystemServer\",\n        };\n\n        int pid;\n        try {\n            parsedArgs = new ZygoteConnection.Arguments(args);\n            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);\n            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);\n\n        //确实是fuck出来的吧，我没骗你吧~不对，是fork出来的 -_-|||\n            /* Request to fork the system server process */\n            pid = Zygote.forkSystemServer(\n                    parsedArgs.uid, parsedArgs.gid,\n                    parsedArgs.gids,\n                    parsedArgs.debugFlags,\n                    null,\n                    parsedArgs.permittedCapabilities,\n                    parsedArgs.effectiveCapabilities);\n        } catch (IllegalArgumentException ex) {\n            throw new RuntimeException(ex);\n        }\n\n        /* For child process */\n        if (pid == 0) {\n            if (hasSecondZygote(abiList)) {\n                waitForSecondaryZygote(socketName);\n            }\n\n            handleSystemServerProcess(parsedArgs);\n        }\n\n        return true;\n    }\n```\n\n**ActivityManagerService是什么？什么时候初始化的？有什么作用？**\n\nActivityManagerService，简称AMS，服务端对象，负责系统中所有Activity的生命周期。\n\nActivityManagerService进行初始化的时机很明确，就是在SystemServer进程开启的时候，就会初始化ActivityManagerService。从下面的代码中可以看到\n\n```\npublic final class SystemServer {\n\n    //zygote的主入口\n    public static void main(String[] args) {\n        new SystemServer().run();\n    }\n\n    public SystemServer() {\n        // Check for factory test mode.\n        mFactoryTestMode = FactoryTest.getMode();\n    }\n\n    private void run() {\n\n        ...ignore some code...\n\n        //加载本地系统服务库，并进行初始化 \n        System.loadLibrary(\"android_servers\");\n        nativeInit();\n\n        // 创建系统上下文\n        createSystemContext();\n\n        //初始化SystemServiceManager对象，下面的系统服务开启都需要调用SystemServiceManager.startService(Class<T>)，这个方法通过反射来启动对应的服务\n        mSystemServiceManager = new SystemServiceManager(mSystemContext);\n\n        //开启服务\n        try {\n            startBootstrapServices();\n            startCoreServices();\n            startOtherServices();\n        } catch (Throwable ex) {\n            Slog.e(\"System\", \"******************************************\");\n            Slog.e(\"System\", \"************ Failure starting system services\", ex);\n            throw ex;\n        }\n\n        ...ignore some code...\n\n    }\n\n    //初始化系统上下文对象mSystemContext，并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候，会调用ActivityThread.attach(true)，而在attach()里面，则创建了Application对象，并调用了Application.onCreate()。\n    private void createSystemContext() {\n        ActivityThread activityThread = ActivityThread.systemMain();\n        mSystemContext = activityThread.getSystemContext();\n        mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);\n    }\n\n    //在这里开启了几个核心的服务，因为这些服务之间相互依赖，所以都放在了这个方法里面。\n    private void startBootstrapServices() {\n\n        ...ignore some code...\n\n        //初始化ActivityManagerService\n        mActivityManagerService = mSystemServiceManager.startService(\n                ActivityManagerService.Lifecycle.class).getService();\n        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);\n\n        //初始化PowerManagerService，因为其他服务需要依赖这个Service，因此需要尽快的初始化\n        mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);\n\n        // 现在电源管理已经开启，ActivityManagerService负责电源管理功能\n        mActivityManagerService.initPowerManagement();\n\n        // 初始化DisplayManagerService\n        mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);\n\n    //初始化PackageManagerService\n    mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,\n       mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);\n\n    ...ignore some code...\n\n    }\n\n}\n```\n\n经过上面这些步骤，我们的ActivityManagerService对象已经创建好了，并且完成了成员变量初始化。而且在这之前，调用createSystemContext()创建系统上下文的时候，也已经完成了mSystemContext和ActivityThread的创建。注意，这是系统进程开启时的流程，在这之后，会开启系统的Launcher程序，完成系统界面的加载与显示。\n\n你是否会好奇，我为什么说AMS是服务端对象？下面我给你介绍下Android系统里面的服务器和客户端的概念。\n\n其实服务器客户端的概念不仅仅存在于Web开发中，在Android的框架设计中，使用的也是这一种模式。服务器端指的就是所有App共用的系统服务，比如我们这里提到的ActivityManagerService，和前面提到的PackageManagerService、WindowManagerService等等，这些基础的系统服务是被所有的App公用的，当某个App想实现某个操作的时候，要告诉这些系统服务，比如你想打开一个App，那么我们知道了包名和MainActivity类名之后就可以打开\n\n```\nIntent intent = new Intent(Intent.ACTION_MAIN);  \nintent.addCategory(Intent.CATEGORY_LAUNCHER);              \nComponentName cn = new ComponentName(packageName, className);              \nintent.setComponent(cn);  \nstartActivity(intent); \n```\n\n但是，我们的App通过调用startActivity()并不能直接打开另外一个App，这个方法会通过一系列的调用，最后还是告诉AMS说：“我要打开这个App，我知道他的住址和名字，你帮我打开吧！”所以是AMS来通知zygote进程来fork一个新进程，来开启我们的目标App的。这就像是浏览器想要打开一个超链接一样，浏览器把网页地址发送给服务器，然后还是服务器把需要的资源文件发送给客户端的。\n\n知道了Android Framework的客户端服务器架构之后，我们还需要了解一件事情，那就是我们的App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程，他们之间如何通信呢？\n\n知道了Android Framework的客户端服务器架构之后，我们还需要了解一件事情，那就是我们的App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程，他们之间如何通信呢？\n\nApp与AMS通过Binder进行IPC通信，AMS(SystemServer进程)与zygote通过Socket进行IPC通信。\n\n那么AMS有什么用呢？在前面我们知道了，如果想打开一个App的话，需要AMS去通知zygote进程，除此之外，其实所有的Activity的开启、暂停、关闭都需要AMS来控制，所以我们说，AMS负责系统中所有Activity的生命周期。\n\n在Android系统中，任何一个Activity的启动都是由AMS和应用程序进程（主要是ActivityThread）相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动，而每个Activity的启动过程则由其所属的进程具体来完成。\n\n这样说你可能还是觉得比较抽象，没关系，下面有一部分是专门来介绍AMS与ActivityThread如何一起合作控制Activity的生命周期的。\n\n**Launcher是什么？什么时候启动的？**\n\n当我们点击手机桌面上的图标的时候，App就由Launcher开始启动了。但是，你有没有思考过Launcher到底是一个什么东西？\n\nLauncher本质上也是一个应用程序，和我们的App一样，也是继承自Activity\n\npackages/apps/Launcher2/src/com/android/launcher2/Launcher.java\n\n```\npublic final class Launcher extends Activity\n        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,\n                   View.OnTouchListener {\n                   }\n```\n\nLauncher实现了点击、长按等回调接口，来接收用户的输入。既然是普通的App，那么我们的开发经验在这里就仍然适用，比如，我们点击图标的时候，是怎么开启的应用呢？如果让你，你怎么做这个功能呢？捕捉图标点击事件，然后startActivity()发送对应的Intent请求呗！是的，Launcher也是这么做的，就是这么easy！\n\n那么到底是处理的哪个对象的点击事件呢？既然Launcher是App，并且有界面，那么肯定有布局文件呀，是的，我找到了布局文件launcher.xml\n\n```\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:launcher=\"http://schemas.android.com/apk/res/com.android.launcher\"\n    android:id=\"@+id/launcher\">\n\n    <com.android.launcher2.DragLayer\n        android:id=\"@+id/drag_layer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <!-- Keep these behind the workspace so that they are not visible when\n             we go into AllApps -->\n        <include\n            android:id=\"@+id/dock_divider\"\n            layout=\"@layout/workspace_divider\"\n            android:layout_marginBottom=\"@dimen/button_bar_height\"\n            android:layout_gravity=\"bottom\" />\n\n        <include\n            android:id=\"@+id/paged_view_indicator\"\n            layout=\"@layout/scroll_indicator\"\n            android:layout_gravity=\"bottom\"\n            android:layout_marginBottom=\"@dimen/button_bar_height\" />\n\n        <!-- The workspace contains 5 screens of cells -->\n        <com.android.launcher2.Workspace\n            android:id=\"@+id/workspace\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:paddingStart=\"@dimen/workspace_left_padding\"\n            android:paddingEnd=\"@dimen/workspace_right_padding\"\n            android:paddingTop=\"@dimen/workspace_top_padding\"\n            android:paddingBottom=\"@dimen/workspace_bottom_padding\"\n            launcher:defaultScreen=\"2\"\n            launcher:cellCountX=\"@integer/cell_count_x\"\n            launcher:cellCountY=\"@integer/cell_count_y\"\n            launcher:pageSpacing=\"@dimen/workspace_page_spacing\"\n            launcher:scrollIndicatorPaddingLeft=\"@dimen/workspace_divider_padding_left\"\n            launcher:scrollIndicatorPaddingRight=\"@dimen/workspace_divider_padding_right\">\n\n            <include android:id=\"@+id/cell1\" layout=\"@layout/workspace_screen\" />\n            <include android:id=\"@+id/cell2\" layout=\"@layout/workspace_screen\" />\n            <include android:id=\"@+id/cell3\" layout=\"@layout/workspace_screen\" />\n            <include android:id=\"@+id/cell4\" layout=\"@layout/workspace_screen\" />\n            <include android:id=\"@+id/cell5\" layout=\"@layout/workspace_screen\" />\n        </com.android.launcher2.Workspace>\n\n    ...ignore some code...\n\n    </com.android.launcher2.DragLayer>\n</FrameLayout>\n```\n\n为了方便查看，我删除了很多代码，从上面这些我们应该可以看出一些东西来：Launcher大量使用标签来实现界面的复用，而且定义了很多的自定义控件实现界面效果，dock_divider从布局的参数声明上可以猜出，是底部操作栏和上面图标布局的分割线，而paged_view_indicator则是页面指示器，和App首次进入的引导页下面的界面引导是一样的道理。当然，我们最关心的是Workspace这个布局，因为注释里面说在这里面包含了5个屏幕的单元格，想必你也猜到了，这个就是在首页存放我们图标的那五个界面(不同的ROM会做不同的DIY，数量不固定)。\n\n接下来，我们应该打开workspace_screen布局，看看里面有什么东东。\n\nworkspace_screen.xml\n\n```\n<com.android.launcher2.CellLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:launcher=\"http://schemas.android.com/apk/res/com.android.launcher\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:paddingStart=\"@dimen/cell_layout_left_padding\"\n    android:paddingEnd=\"@dimen/cell_layout_right_padding\"\n    android:paddingTop=\"@dimen/cell_layout_top_padding\"\n    android:paddingBottom=\"@dimen/cell_layout_bottom_padding\"\n    android:hapticFeedbackEnabled=\"false\"\n    launcher:cellWidth=\"@dimen/workspace_cell_width\"\n    launcher:cellHeight=\"@dimen/workspace_cell_height\"\n    launcher:widthGap=\"@dimen/workspace_width_gap\"\n    launcher:heightGap=\"@dimen/workspace_height_gap\"\n    launcher:maxGap=\"@dimen/workspace_max_gap\" />\n```\n\n里面就一个CellLayout，也是一个自定义布局，那么我们就可以猜到了，既然可以存放图标，那么这个自定义的布局很有可能是继承自ViewGroup或者是其子类，实际上，CellLayout确实是继承自ViewGroup。在CellLayout里面，只放了一个子View，那就是ShortcutAndWidgetContainer。从名字也可以看出来，ShortcutAndWidgetContainer这个类就是用来存放快捷图标和Widget小部件的，那么里面放的是什么对象呢？\n\n在桌面上的图标，使用的是BubbleTextView对象，这个对象在TextView的基础之上，添加了一些特效，比如你长按移动图标的时候，图标位置会出现一个背景(不同版本的效果不同)，所以我们找到BubbleTextView对象的点击事件，就可以找到Launcher如何开启一个App了。\n\n除了在桌面上有图标之外，在程序列表中点击图标，也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象，而是PagedViewIcon对象，我们如果找到它的点击事件，就也可以找到Launcher如何开启一个App。\n\n其实说这么多，和今天的主题隔着十万八千里，上面这些东西，你有兴趣就看，没兴趣就直接跳过，不知道不影响这篇文章阅读。\n\nBubbleTextView的点击事件在哪里呢？我来告诉你：在Launcher.onClick(View v)里面。\n\n```\n/**\n     * Launches the intent referred by the clicked shortcut\n     */\n    public void onClick(View v) {\n\n          ...ignore some code...\n\n         Object tag = v.getTag();\n        if (tag instanceof ShortcutInfo) {\n            // Open shortcut\n            final Intent intent = ((ShortcutInfo) tag).intent;\n            int[] pos = new int[2];\n            v.getLocationOnScreen(pos);\n            intent.setSourceBounds(new Rect(pos[0], pos[1],\n                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));\n        //开始开启Activity咯~\n            boolean success = startActivitySafely(v, intent, tag);\n\n            if (success && v instanceof BubbleTextView) {\n                mWaitingForResume = (BubbleTextView) v;\n                mWaitingForResume.setStayPressed(true);\n            }\n        } else if (tag instanceof FolderInfo) {\n            //如果点击的是图标文件夹，就打开文件夹\n            if (v instanceof FolderIcon) {\n                FolderIcon fi = (FolderIcon) v;\n                handleFolderClick(fi);\n            }\n        } else if (v == mAllAppsButton) {\n        ...ignore some code...\n        }\n    }\n```\n\n从上面的代码我们可以看到，在桌面上点击快捷图标的时候，会调用\n\n```\nstartActivitySafely(v, intent, tag);\n```\n\n那么从程序列表界面，点击图标的时候会发生什么呢？实际上，程序列表界面使用的是AppsCustomizePagedView对象，所以我在这个类里面找到了onClick(View v)。\n\ncom.android.launcher2.AppsCustomizePagedView.java\n\n```\n/**\n * The Apps/Customize page that displays all the applications, widgets, and shortcuts.\n */\npublic class AppsCustomizePagedView extends PagedViewWithDraggableItems implements\n        View.OnClickListener, View.OnKeyListener, DragSource,\n        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,\n        LauncherTransitionable {\n\n     @Override\n    public void onClick(View v) {\n\n         ...ignore some code...\n\n        if (v instanceof PagedViewIcon) {\n            mLauncher.updateWallpaperVisibility(true);\n            mLauncher.startActivitySafely(v, appInfo.intent, appInfo);\n        } else if (v instanceof PagedViewWidget) {\n                 ...ignore some code..\n         }\n     }      \n   }\n```\n\n可以看到，调用的是\n\n```\nmLauncher.startActivitySafely(v, appInfo.intent, appInfo);\n```\n\n殊途同归\n\n不管从哪里点击图标，调用的都是Launcher.startActivitySafely()。\n\n下面我们就可以一步步的来看一下Launcher.startActivitySafely()到底做了什么事情。\n\n```\n boolean startActivitySafely(View v, Intent intent, Object tag) {\n        boolean success = false;\n        try {\n            success = startActivity(v, intent, tag);\n        } catch (ActivityNotFoundException e) {\n            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();\n            Log.e(TAG, \"Unable to launch. tag=\" + tag + \" intent=\" + intent, e);\n        }\n        return success;\n    }\n```\n\n调用了startActivity(v, intent, tag)\n\n```\n boolean startActivity(View v, Intent intent, Object tag) {\n\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        try {\n            boolean useLaunchAnimation = (v != null) &&\n                    !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);\n\n            if (useLaunchAnimation) {\n                if (user == null || user.equals(android.os.Process.myUserHandle())) {\n                    startActivity(intent, opts.toBundle());\n                } else {\n                    launcherApps.startMainActivity(intent.getComponent(), user,\n                            intent.getSourceBounds(),\n                            opts.toBundle());\n                }\n            } else {\n                if (user == null || user.equals(android.os.Process.myUserHandle())) {\n                    startActivity(intent);\n                } else {\n                    launcherApps.startMainActivity(intent.getComponent(), user,\n                            intent.getSourceBounds(), null);\n                }\n            }\n            return true;\n        } catch (SecurityException e) {\n        ...\n        }\n        return false;\n    }\n```\n\n这里会调用Activity.startActivity(intent, opts.toBundle())，这个方法熟悉吗？这就是我们经常用到的Activity.startActivity(Intent)的重载函数。而且由于设置了\n\n```\nintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n```\n\n所以这个Activity会添加到一个新的Task栈中，而且，startActivity()调用的其实是startActivityForResult()这个方法。\n\n```\n@Override\n    public void startActivity(Intent intent, @Nullable Bundle options) {\n        if (options != null) {\n            startActivityForResult(intent, -1, options);\n        } else {\n            // Note we want to go through this call for compatibility with\n            // applications that may have overridden the method.\n            startActivityForResult(intent, -1);\n        }\n    }\n```\n\n所以我们现在明确了，Launcher中开启一个App，其实和我们在Activity中直接startActivity()基本一样，都是调用了Activity.startActivityForResult()。\n\n**Instrumentation是什么？和ActivityThread是什么关系？**\n\n还记得前面说过的Instrumentation对象吗？每个Activity都持有Instrumentation对象的一个引用，但是整个进程只会存在一个Instrumentation对象。当startActivityForResult()调用之后，实际上还是调用了mInstrumentation.execStartActivity()\n\n```\npublic void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {\n        if (mParent == null) {\n            Instrumentation.ActivityResult ar =\n                mInstrumentation.execStartActivity(\n                    this, mMainThread.getApplicationThread(), mToken, this,\n                    intent, requestCode, options);\n            if (ar != null) {\n                mMainThread.sendActivityResult(\n                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),\n                    ar.getResultData());\n            }\n            ...ignore some code...    \n        } else {\n            if (options != null) {\n                 //当现在的Activity有父Activity的时候会调用，但是在startActivityFromChild()内部实际还是调用的mInstrumentation.execStartActivity()\n                mParent.startActivityFromChild(this, intent, requestCode, options);\n            } else {\n                mParent.startActivityFromChild(this, intent, requestCode);\n            }\n        }\n         ...ignore some code...    \n    }\n```\n\n下面是mInstrumentation.execStartActivity()的实现\n\n```\npublic ActivityResult execStartActivity(\n            Context who, IBinder contextThread, IBinder token, Activity target,\n            Intent intent, int requestCode, Bundle options) {\n        IApplicationThread whoThread = (IApplicationThread) contextThread;\n            ...ignore some code...\n      try {\n            intent.migrateExtraStreamToClipData();\n            intent.prepareToLeaveProcess();\n            int result = ActivityManagerNative.getDefault()\n                .startActivity(whoThread, who.getBasePackageName(), intent,\n                        intent.resolveTypeIfNeeded(who.getContentResolver()),\n                        token, target != null ? target.mEmbeddedID : null,\n                        requestCode, 0, null, options);\n            checkStartActivityResult(result, intent);\n        } catch (RemoteException e) {\n        }\n        return null;\n    }\n```\n\n所以当我们在程序中调用startActivity()的 时候，实际上调用的是Instrumentation的相关的方法。\n\nInstrumentation意为“仪器”，我们先看一下这个类里面包含哪些方法吧\n\n![](http://i11.tietuku.com/15eb948d37d9d4b3.png)\n\n我们可以看到，这个类里面的方法大多数和Application和Activity有关，是的，这个类就是完成对Application和Activity初始化和生命周期的工具类。比如说，我单独挑一个callActivityOnCreate()让你看看\n\n```\npublic void callActivityOnCreate(Activity activity, Bundle icicle) {\n        prePerformCreate(activity);\n        activity.performCreate(icicle);\n        postPerformCreate(activity);\n    }\n```\n\n对activity.performCreate(icicle);这一行代码熟悉吗？这一行里面就调用了传说中的Activity的入口函数onCreate()，不信？接着往下看\n\nActivity.performCreate()\n\n```\nfinal void performCreate(Bundle icicle) {\n        onCreate(icicle);\n        mActivityTransitionState.readState(icicle);\n        performCreateCommon();\n    }\n```\n\n没骗你吧，onCreate在这里调用了吧。但是有一件事情必须说清楚，那就是这个Instrumentation类这么重要，为啥我在开发的过程中，没有发现他的踪迹呢？\n\n是的，Instrumentation这个类很重要，对Activity生命周期方法的调用根本就离不开他，他可以说是一个大管家，但是，这个大管家比较害羞，是一个女的，管内不管外，是老板娘~\n\n那么你可能要问了，老板是谁呀？ \n老板当然是大名鼎鼎的ActivityThread了！\n\nActivityThread你都没听说过？那你肯定听说过传说中的UI线程吧？是的，这就是UI线程。我们前面说过，App和AMS是通过Binder传递信息的，那么ActivityThread就是专门与AMS的外交工作的。\n\nAMS说：“ActivityThread，你给我暂停一个Activity！” \nActivityThread就说：“没问题！”然后转身和Instrumentation说：“老婆，AMS让暂停一个Activity，我这里忙着呢，你快去帮我把这事办了把~”\n于是，Instrumentation就去把事儿搞定了。\n\n所以说，AMS是董事会，负责指挥和调度的，ActivityThread是老板，虽然说家里的事自己说了算，但是需要听从AMS的指挥，而Instrumentation则是老板娘，负责家里的大事小事，但是一般不抛头露面，听一家之主ActivityThread的安排。\n\n**如何理解AMS和ActivityThread之间的Binder通信？**\n\n前面我们说到，在调用startActivity()的时候，实际上调用的是\n\n```\nmInstrumentation.execStartActivity()\n```\n\n但是到这里还没完呢！里面又调用了下面的方法\n\n```\nActivityManagerNative.getDefault()\n                .startActivity\n```\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/Android关于oom的解决方案.md",
    "content": "#Android关于OOM的解决方案\n##OOM\n* 内存溢出（Out Of Memory）\n* 也就是说内存占有量超过了VM所分配的最大\n\n\n##出现OOM的原因\n1. 加载对象过大\n2. 相应资源过多，来不及释放\n\n##如何解决\n1. 在内存引用上做些处理，常用的有软引用、强化引用、弱引用\n2. 在内存中加载图片时直接在内存中作处理，如边界压缩\n3. 动态回收内存\n4. 优化Dalvik虚拟机的堆内存分配\n5. 自定义堆内存大小\n\n\n"
  },
  {
    "path": "Part1/Android/Android内存泄漏总结.md",
    "content": "#Android 内存泄漏总结\n\n内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了，简单粗俗的讲，就是该被释放的对象没有释放，一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料，打算做个 总结 沉淀下来跟大家一起分享和学习，也给自己一个警示，以后 coding 时怎么避免这些情况，提高应用的体验和质量。\n\n我会从 java 内存泄漏的基础知识开始，并通过具体例子来说明 Android 引起内存泄漏的各种原因，以及如何利用工具来分析应用内存泄漏，最后再做总结。\n\n\n##Java 内存分配策略\n\nJava 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配，对应的，三种存储策略使用的内存空间主要分别是静态存储区（也称方法区）、栈区和堆区。\n\n* 静态存储区（方法区）：主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好，并且在程序整个运行期间都存在。\n\n* 栈区 ：当方法被执行时，方法体内的局部变量（其中包括基础数据类型、对象的引用）都在栈上创建，并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。\n\n* 堆区 ： 又称动态内存分配，通常就是指在程序运行时直接 new 出来的内存，也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。\n\n##栈与堆的区别：\n\n在方法体内定义的（局部变量）一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时，Java 就会在栈中为该变量分配内存空间，当超过该变量的作用域后，该变量也就无效了，分配给它的内存空间也将被释放掉，该内存空间可以被重新使用。\n\n堆内存用来存放所有由 new 创建的对象（包括该对象其中的所有成员变量）和数组。在堆中分配的内存，将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后，还可以在栈中定义一个特殊的变量，这个变量的取值等于数组或者对象在堆内存中的首地址，这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。\n\n举个例子:\n\n```\npublic class Sample {\n    int s1 = 0;\n    Sample mSample1 = new Sample();\n\n    public void method() {\n        int s2 = 1;\n        Sample mSample2 = new Sample();\n    }\n}\n\nSample mSample3 = new Sample();\n```\n\nSample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中，但 mSample2 指向的对象是存在于堆上的。\nmSample3 指向的对象实体存放在堆上，包括这个对象的所有成员变量 s1 和 mSample1，而它自己存在于栈中。\n\n结论：\n\n局部变量的基本数据类型和引用存储于栈中，引用的对象实体存储于堆中。—— 因为它们属于方法中的变量，生命周期随方法而结束。\n\n成员变量全部存储与堆中（包括基本数据类型，引用和引用的对象实体）—— 因为它们属于类，类对象终究是要被new出来使用的。\n\n了解了 Java 的内存分配之后，我们再来看看 Java 是怎么管理内存的。\n\n##Java是如何管理内存\n\nJava的内存管理就是对象的分配和释放问题。在 Java 中，程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外)，所有的对象都在堆 (Heap)中分配空间。另外，对象的释放是由 GC 决定和执行的。在 Java 中，内存的分配是由程序完成的，而内存的释放是由 GC 完成的，这种收支两条线的方法确实简化了程序员的工作。但同时，它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为，GC 为了能够正确释放对象，GC 必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，GC 都需要进行监控。\n\n监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。\n\n为了更好理解 GC 的工作原理，我们可以将对象考虑为有向图的顶点，将引用关系考虑为图的有向边，有向边从引用者指向被引对象。另外，每个线程对象可以作为一个图的起始顶点，例如大多程序从 main 进程开始执行，那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中，根顶点可达的对象都是有效对象，GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意，该图为有向图)，那么我们认为这个(这些)对象不再被引用，可以被 GC 回收。\n以下，我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻，我们都有一个有向图表示JVM的内存分配情况。以下右图，就是左边程序运行到第6行的示意图。\n\n![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/1.gif)\n\nJava使用有向图的方式进行内存管理，可以消除引用循环的问题，例如有三个对象，相互引用，只要它们和根进程不可达的，那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高，但是效率较低。另外一种常用的内存管理技术是使用计数器，例如COM模型采用计数器方式管理构件，它与有向图相比，精度行低(很难处理循环引用的问题)，但执行效率很高。\n\n##什么是Java中的内存泄露\n\n在Java中，内存泄漏就是存在一些被分配的对象，这些对象有下面两个特点，首先，这些对象是可达的，即在有向图中，存在通路可以与其相连；其次，这些对象是无用的，即程序以后不会再使用这些对象。如果对象满足这两个条件，这些对象就可以判定为Java中的内存泄漏，这些对象不会被GC所回收，然而它却占用内存。\n\n在C++中，内存泄漏的范围更大一些。有些对象被分配了内存空间，然后却不可达，由于C++中没有GC，这些内存将永远收不回来。在Java中，这些不可达的对象都由GC负责回收，因此程序员不需要考虑这部分的内存泄露。\n\n通过分析，我们得知，对于C++，程序员需要自己管理边和顶点，而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式，Java提高了编程的效率。\n\n![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/2.gif)\n\n因此，通过以上分析，我们知道在Java中也有内存泄漏，但范围比C++要小一些。因为Java从语言上保证，任何对象都是可达的，所有的不可达对象都由GC管理。\n\n对于程序员来说，GC基本是透明的，不可见的。虽然，我们只有几个函数可以访问GC，例如运行GC的函数System.gc()，但是根据Java语言规范定义， 该函数不保证JVM的垃圾收集器一定会执行。因为，不同的JVM实现者可能使用不同的算法管理GC。通常，GC的线程的优先级别较低。JVM调用GC的策略也有很多种，有的是内存使用到达一定程度时，GC才开始工作，也有定时执行的，有的是平缓执行GC，有的是中断式执行GC。但通常来说，我们不需要关心这些。除非在一些特定的场合，GC的执行影响应用程序的性能，例如对于基于Web的实时系统，如网络游戏等，用户不希望GC突然中断应用程序执行而进行垃圾回收，那么我们需要调整GC的参数，让GC能够通过平缓的方式释放内存，例如将垃圾回收分解为一系列的小步骤执行，Sun提供的HotSpot JVM就支持这一特性。\n\n同样给出一个 Java 内存泄漏的典型例子，\n\n```\nVector v = new Vector(10);\nfor (int i = 1; i < 100; i++) {\n    Object o = new Object();\n    v.add(o);\n    o = null;   \n}\n```\n\n在这个例子中，我们循环申请Object对象，并将所申请的对象放入一个 Vector 中，如果我们仅仅释放引用本身，那么 Vector 仍然引用该对象，所以这个对象对 GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从 Vector 中删除，最简单的方法就是将 Vector 对象设置为 null。\n\n\n**详细Java中的内存泄漏**\n\n1.Java内存回收机制\n\n不论哪种语言的内存分配方式，都需要返回所分配内存的真实地址，也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的，这些对象的创建都是在堆（Heap）中分配的，所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象，会监控每个对象的运行状况，对他们的申请、引用、被引用、赋值等状况进行监控，Java会使用有向图的方法进行管理内存，实时监控对象是否可以达到，如果不可到达，则就将其回收，这样也可以消除引用循环的问题。在Java语言中，判断一个内存空间是否符合垃圾收集标准有两个：一个是给对象赋予了空值null，以下再没有调用过，另一个是给对象赋予了新值，这样重新分配了内存空间。 \n\n2.Java内存泄漏引起的原因\n\n内存泄漏是指无用对象（不再使用的对象）持续占有内存或无用对象的内存得不到及时释放，从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉，这样开发者就不知道存在内存泄露，但有时也会很严重，会提示你Out of memory。j\n\nJava内存泄漏的根本原因是什么呢？长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏，尽管短生命周期对象已经不再需要，但是因为长生命周期持有它的引用而导致不能被回收，这就是Java中内存泄漏的发生场景。具体主要有如下几大类：\n\n1、静态集合类引起内存泄漏：\n\n像HashMap、Vector等的使用最容易出现内存泄露，这些静态变量的生命周期和应用程序一致，他们所引用的所有的对象Object也不能被释放，因为他们也将一直被Vector等引用着。 \n\n例如\n\n```\nStatic Vector v = new Vector(10);\nfor (int i = 1; i<100; i++)\n{\nObject o = new Object();\nv.add(o);\no = null;\n}\n```\n\n在这个例子中，循环申请Object 对象，并将所申请的对象放入一个Vector 中，如果仅仅释放引用本身（o=null），那么Vector 仍然引用该对象，所以这个对象对GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从Vector 中删除，最简单的方法就是将Vector对象设置为null。\n\n2、当集合里面的对象属性被修改后，再调用remove()方法时不起作用。\n\n例如：\n\n```\npublic static void main(String[] args)\n{\nSet<Person> set = new HashSet<Person>();\nPerson p1 = new Person(\"唐僧\",\"pwd1\",25);\nPerson p2 = new Person(\"孙悟空\",\"pwd2\",26);\nPerson p3 = new Person(\"猪八戒\",\"pwd3\",27);\nset.add(p1);\nset.add(p2);\nset.add(p3);\nSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:3 个元素!\np3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变\n\nset.remove(p3); //此时remove不掉，造成内存泄漏\n\nset.add(p3); //重新添加，居然添加成功\nSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:4 个元素!\nfor (Person person : set)\n{\nSystem.out.println(person);\n}\n}\n```\n\n3、监听器\n\n在java 编程中，我们都需要和监听器打交道，通常一个应用当中会用到很多监听器，我们会调用一个控件的诸如addXXXListener()等方法来增加监听器，但往往在释放对象的时候却没有记住去删除这些监听器，从而增加了内存泄漏的机会。\n\n4、各种连接 \n\n比如数据库连接（dataSourse.getConnection()），网络连接(socket)和io连接，除非其显式的调用了其close（）方法将其连接关闭，否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收，但Connection 一定要显式回收，因为Connection 在任何时候都无法自动回收，而Connection一旦回收，Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池，情况就不一样了，除了要显式地关闭连接，还必须显式地关闭Resultset Statement 对象（关闭其中一个，另外一个也会关闭），否则就会造成大量的Statement 对象无法释放，从而引起内存泄漏。这种情况下一般都会在try里面去的连接，在finally里面释放连接。\n\n5、内部类和外部模块的引用\n\n内部类的引用是比较容易遗忘的一种，而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用，例如程序员A 负责A 模块，调用了B 模块的一个方法如：\npublic void registerMsg(Object b);\n这种调用就要非常小心了，传入了一个对象，很可能模块B就保持了对该对象的引用，这时候就需要注意模块B 是否提供相应的操作去除引用。\n\n6、单例模式 \n\n不正确使用单例模式是引起内存泄漏的一个常见问题，单例对象在初始化后将在JVM的整个生命周期中存在（以静态变量的方式），如果单例对象持有外部的引用，那么这个对象将不能被JVM正常回收，导致内存泄漏，考虑下面的例子：\n\n```\nclass A{\npublic A(){\nB.getInstance().setA(this);\n}\n....\n}\n//B类采用单例模式\nclass B{\nprivate A a;\nprivate static B instance=new B();\npublic B(){}\npublic static B getInstance(){\nreturn instance;\n}\npublic void setA(A a){\nthis.a=a;\n}\n//getter...\n} \n```\n\n\n显然B采用singleton模式，它持有一个A对象的引用，而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况\n\n##Android中常见的内存泄漏汇总\n---\n\n###集合类泄漏\n\n集合类如果仅仅有添加元素的方法，而没有相应的删除机制，导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性，全局性的 map 等即有静态引用或 final 一直指向它)，那么没有相应的删除机制，很可能导致集合所占用的内存只增不减。比如上面的典型例子就是其中一种情况，当然实际上我们在项目中肯定不会写这么 2B 的代码，但稍不注意还是很容易出现这种情况，比如我们都喜欢通过 HashMap 做一些缓存之类的事，这种情况就要多留一些心眼。\n\n###单例造成的内存泄漏\n\n由于单例的静态特性使得其生命周期跟应用的生命周期一样长，所以如果使用不恰当的话，很容易造成内存泄漏。比如下面一个典型的例子，\n\n```\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager(Context context) {\nthis.context = context;\n}\npublic static AppManager getInstance(Context context) {\nif (instance == null) {\ninstance = new AppManager(context);\n}\nreturn instance;\n}\n}\n```\n\n这是一个普通的单例模式，当创建这个单例的时候，由于需要传入一个Context，所以这个Context的生命周期的长短至关重要：\n\n1、如果此时传入的是 Application 的 Context，因为 Application 的生命周期就是整个应用的生命周期，所以这将没有任何问题。\n\n2、如果此时传入的是 Activity 的 Context，当这个 Context 所对应的 Activity 退出时，由于该 Context 的引用被单例对象所持有，其生命周期等于整个应用程序的生命周期，所以当前 Activity 退出时它的内存并不会被回收，这就造成泄漏了。\n\n正确的方式应该改为下面这种方式：\n\n```\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager(Context context) {\nthis.context = context.getApplicationContext();// 使用Application 的context\n}\npublic static AppManager getInstance(Context context) {\nif (instance == null) {\ninstance = new AppManager(context);\n}\nreturn instance;\n}\n}\n```\n\n或者这样写，连 Context 都不用传进来了：\n\n```\n在你的 Application 中添加一个静态方法，getContext() 返回 Application 的 context，\n\n...\n\ncontext = getApplicationContext();\n\n...\n   /**\n     * 获取全局的context\n     * @return 返回全局context对象\n     */\n    public static Context getContext(){\n        return context;\n    }\n\npublic class AppManager {\nprivate static AppManager instance;\nprivate Context context;\nprivate AppManager() {\nthis.context = MyApplication.getContext();// 使用Application 的context\n}\npublic static AppManager getInstance() {\nif (instance == null) {\ninstance = new AppManager();\n}\nreturn instance;\n}\n}\n```\n\n###匿名内部类/非静态内部类和异步线程\n\n非静态内部类创建静态实例造成的内存泄漏\n\n有的时候我们可能会在启动频繁的Activity中，为了避免重复创建相同的数据资源，可能会出现这种写法：\n\n```\n        public class MainActivity extends AppCompatActivity {\n        private static TestResource mResource = null;\n        @Override\n        protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        if(mManager == null){\n        mManager = new TestResource();\n        }\n        //...\n        }\n        class TestResource {\n        //...\n        }\n        }\n```\n这样就在Activity内部创建了一个非静态内部类的单例，每次启动Activity时都会使用该单例的数据，这样虽然避免了资源的重复创建，不过这种写法却会造成内存泄漏，因为非静态内部类默认会持有外部类的引用，而该非静态内部类又创建了一个静态的实例，该实例的生命周期和应用的一样长，这就导致了该静态实例一直会持有该Activity的引用，导致Activity的内存资源不能正常回收。正确的做法为：\n\n将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例，如果需要使用Context，请按照上面推荐的使用Application 的 Context。当然，Application 的 context 不是万能的，所以也不能随便乱用，对于有些地方则必须使用 Activity 的 Context，对于Application，Service，Activity三者的Context的应用场景如下：\n\n![](http://img.blog.csdn.net/20151123144226349?spm=5176.100239.blogcont.9.CtU1c4)\n\n其中： NO1表示 Application 和 Service 可以启动一个 Activity，不过需要创建一个新的 task 任务队列。而对于 Dialog 而言，只有在 Activity 中才能创建\n\n###匿名内部类\n\nandroid开发经常会继承实现Activity/Fragment/View，此时如果你使用了匿名类，并被异步线程持有了，那要小心了，如果没有任何措施这样一定会导致泄露\n\n```\n    public class MainActivity extends Activity {\n    ...\n    Runnable ref1 = new MyRunable();\n    Runnable ref2 = new Runnable() {\n        @Override\n        public void run() {\n\n        }\n    };\n       ...\n    }\n```\n\nref1和ref2的区别是，ref2使用了匿名内部类。我们来看看运行时这两个引用的内存：\n\n![](http://img2.tbcdn.cn/L1/461/1/fb05ff6d2e68f309b94dd84352c81acfe0ae839e?spm=5176.100239.blogcont.10.CtU1c4)\n\n可以看到，ref1没什么特别的。\n\n但ref2这个匿名类的实现对象里面多了一个引用：\n\nthis$0这个引用指向MainActivity.this，也就是说当前的MainActivity实例会被ref2持有，如果将这个引用再传入一个异步线程，此线程和此Acitivity生命周期不一致的时候，就造成了Activity的泄露。\n\n###Handler 造成的内存泄漏\n\nHandler 的使用造成的内存泄漏问题应该说是最为常见了，很多时候我们为了避免 ANR 而不在主线程进行耗时操作，在处理网络任务或者封装一些请求回调等api都借助Handler来处理，但 Handler 不是万能的，对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外，我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的，万一 Handler 发送的 Message 尚未被处理，则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。\n\n由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致，故很容易导致无法正确释放。\n\n举个例子：\n\n```\n    public class SampleActivity extends Activity {\n\n    private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ...\n    }\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mLeakyHandler.postDelayed(new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n    }, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n    }\n    }\n```\n\n在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message，mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时，延迟执行任务的 Message 还会继续存在于主线程中，它持有该 Activity 的 Handler 引用，所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏（因 Handler 为非静态内部类，它会持有外部类的引用，在这里就是指 SampleActivity）。\n\n修复方法：在 Activity 中避免使用非静态内部类，比如上面我们将 Handler 声明为静态的，则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity，避免直接将 Activity 作为 context 传进去，见下面代码：\n\n```\npublic class SampleActivity extends Activity {\n\n  /**\n   * Instances of static inner classes do not hold an implicit\n   * reference to their outer class.\n   */\n  private static class MyHandler extends Handler {\n    private final WeakReference<SampleActivity> mActivity;\n\n    public MyHandler(SampleActivity activity) {\n      mActivity = new WeakReference<SampleActivity>(activity);\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n      SampleActivity activity = mActivity.get();\n      if (activity != null) {\n        // ...\n      }\n    }\n  }\n\n  private final MyHandler mHandler = new MyHandler(this);\n\n  /**\n   * Instances of anonymous classes do not hold an implicit\n   * reference to their outer class when they are \"static\".\n   */\n  private static final Runnable sRunnable = new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n  };\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n综述，即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。\n\n前面提到了 WeakReference，所以这里就简单的说一下 Java 对象的几种引用类型。\n\nJava对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。\n\n![](https://gw.alicdn.com/tps/TB1U6TNLVXXXXchXFXXXXXXXXXX-644-546.jpg)\n\n在Android应用的开发中，为了防止内存溢出，在处理一些占用内存大而且声明周期较长的对象时候，可以尽量应用软引用和弱引用技术。\n\n软/弱引用可以和一个引用队列（ReferenceQueue）联合使用，如果软引用所引用的对象被垃圾回收器回收，Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表，从而为缓冲器清除已失效的软/弱引用。\n\n假设我们的应用会用到大量的默认图片，比如应用中有默认的头像，默认游戏图标等等，这些图片很多地方会用到。如果每次都去读取图片，由于读取文件需要硬件操作，速度较慢，会导致性能较低。所以我们考虑将图片缓存起来，需要的时候直接从内存中读取。但是，由于图片占用内存空间比较大，缓存很多图片需要很多的内存，就可能比较容易发生OutOfMemory异常。这时，我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形：\n\n首先定义一个HashMap，保存软引用对象。\n\n```\nprivate Map <String, SoftReference<Bitmap>> imageCache = new HashMap <String, SoftReference<Bitmap>> ();\n```\n\n再来定义一个方法，保存Bitmap的软引用到HashMap。\n\n![](https://gw.alicdn.com/tps/TB1oW_FLVXXXXXuaXXXXXXXXXXX-679-717.jpg)\n\n使用软引用以后，在OutOfMemory异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免Crash发生。\n\n如果只是想避免OutOfMemory异常的发生，则可以使用软引用。如果对于应用的性能更在意，想尽快回收一些占用内存比较大的对象，则可以使用弱引用。\n\n另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的，就尽量用软引用。如果该对象不被使用的可能性更大些，就可以用弱引用。\n\nok，继续回到主题。前面所说的，创建一个静态Handler内部类，然后对 Handler 持有的对象使用弱引用，这样在回收时也可以回收 Handler 持有的对象，但是这样做虽然避免了 Activity 泄漏，不过 Looper 线程的消息队列中还是可能会有待处理的消息，所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。\n\n下面几个方法都可以移除 Message：\n\n```\npublic final void removeCallbacks(Runnable r);\n\npublic final void removeCallbacks(Runnable r, Object token);\n\npublic final void removeCallbacksAndMessages(Object token);\n\npublic final void removeMessages(int what);\n\npublic final void removeMessages(int what, Object object);\n```\n\n###尽量避免使用 static 成员变量\n\n如果成员变量被声明为 static，那我们都知道其生命周期将与整个app进程生命周期一样。\n\n这会导致一系列问题，如果你的app进程设计上是长驻内存的，那即使app切到后台，这部分内存也不会被释放。按照现在手机app内存管理机制，占内存较大的后台进程将优先回收，yi'wei如果此app做过进程互保保活，那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量，你的app不得不被用户卸载或者静默。\n\n这里修复的方法是：\n\n不要在类初始时初始化静态成员。可以考虑lazy初始化。\n架构设计上要思考是否真的有必要这样做，尽量避免。如果架构需要这么设计，那么此对象的生命周期你有责任管理起来。\n\n###避免 override finalize()\n\n1、finalize 方法被执行的时间不确定，不能依赖与它来释放紧缺的资源。时间不确定的原因是：\n        虚拟机调用GC的时间不确定\n        Finalize daemon线程被调度到的时间不确定\n\n2、finalize 方法只会被执行一次，即使对象被复活，如果已经执行过了 finalize 方法，再次被 GC 时也不会再执行了，原因是：\n\n含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的，而在 finalize 方法执行的时候，该 object 所对应的 finalize Reference 会被释放掉，即使在这个时候把该 object 复活(即用强引用引用住该 object )，再第二次被 GC 的时候由于没有了 finalize reference 与之对应，所以 finalize 方法不会再执行。\n\n3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。\n\n\n###资源未关闭造成的内存泄漏\n\n对于使用了BraodcastReceiver，ContentObserver，File，游标 Cursor，Stream，Bitmap等资源的使用，应该在Activity销毁时及时关闭或者注销，否则这些资源将不会被回收，造成内存泄漏。\n\n###一些不良代码造成的内存压力\n\n有些代码并不造成内存泄露，但是它们，或是对没使用的内存没进行有效及时的释放，或是没有有效的利用已有的对象而是频繁的申请新内存。\n\n比如：\n        Bitmap 没调用 recycle()方法，对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存，然后才它设置为 null. 因为加载 Bitmap 对象的内存空间，一部分是 java 的，一部分 C 的（因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。\n        构造 Adapter 时，没有使用缓存的 convertView ,每次都在创建新的 converView。这里推荐使用 ViewHolder。\n\n##总结\n\n对 Activity 等组件的引用应该控制在 Activity 的生命周期之内； 如果不能就考虑使用 getApplicationContext 或者 getApplication，以避免 Activity 被外部长生命周期的对象引用而泄露。\n\n尽量不要在静态变量或者静态内部类中使用非静态外部成员变量（包括context )，即使要使用，也要考虑适时把外部成员变量置空；也可以在内部类中使用弱引用来引用外部类的变量。\n\n对于生命周期比Activity长的内部类对象，并且内部类中使用了外部类的成员变量，可以这样做避免内存泄漏：\n\n        将内部类改为静态内部类\n        静态内部类中使用弱引用来引用外部类的成员变量\n\nHandler 的持有的引用对象最好使用弱引用，资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候，取消掉该 Handler 对象的 Message和 Runnable.\n\n在 Java 的实现过程中，也要考虑其对象释放，最好的方法是在不使用某对象时，显式地将此对象赋值为 null，比如使用完Bitmap 后先调用 recycle()，再赋为null,清空对图片等资源有直接引用或者间接引用的数组（使用 array.clear() ; array = null）等，最好遵循谁创建谁释放的原则。\n\n正确关闭资源，对于使用了BraodcastReceiver，ContentObserver，File，游标 Cursor，Stream，Bitmap等资源的使用，应该在Activity销毁时及时关闭或者注销。\n\n保持对对象生命周期的敏感，特别注意单例、静态对象、全局性集合等的生命周期。\n\n\n\n"
  },
  {
    "path": "Part1/Android/Android几种进程.md",
    "content": "#Android几种进程\n---\n\n1. 前台进程：即与用户正在交互的Activity或者Activity用到的Service等，如果系统内存不足时前台进程是最后被杀死的\n2. 可见进程：可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service，即被用户可见，但由于失去了焦点而不能与用户交互\n3. 服务进程：其中运行着使用startService方法启动的Service，虽然不被用户可见，但是却是用户关心的，例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等；当系统要空间运行前两者进程时才会被终止\n4. 后台进程：其中运行着执行onStop方法而停止的程序，但是却不是用户当前关心的，例如后台挂着的QQ，这样的进程系统一旦没了有内存就首先被杀死\n5. 空进程：不包含任何应用程序的程序组件的进程，这样的进程系统是一般不会让他存在的\n\n如何避免后台进程被杀死：\n\n1. 调用startForegound，让你的Service所在的线程成为前台进程\n2. Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT\n3. Service的onDestroy里面重新启动自己"
  },
  {
    "path": "Part1/Android/Android图片中的三级缓存.md",
    "content": "#Android图片中的三级缓存\n---\n\n##为什么要使用三级缓存\n\n* 如今的 Android App 经常会需要网络交互，通过网络获取图片是再正常不过的事了\n* 假如每次启动的时候都从网络拉取图片的话，势必会消耗很多流量。在当前的状况下，对于非wifi用户来说，流量还是很贵的，一个很耗流量的应用，其用户数量级肯定要受到影响\n* 特别是，当我们想要重复浏览一些图片时，如果每一次浏览都需要通过网络获取，流量的浪费可想而知\n* 所以提出三级缓存策略，通过网络、本地、内存三级缓存图片，来减少不必要的网络交互，避免浪费流量\n\n##什么是三级缓存\n\n* 网络加载，不优先加载，速度慢，浪费流量\n* 本地缓存，次优先加载，速度快\n* 内存缓存，优先加载，速度最快\n\n\n##三级缓存原理\n\n* 首次加载 Android App 时，肯定要通过网络交互来获取图片，之后我们可以将图片保存至本地SD卡和内存中\n* 之后运行 App 时，优先访问内存中的图片缓存，若内存中没有，则加载本地SD卡中的图片\n* 总之，只在初次访问新内容时，才通过网络获取图片资源\r\n\n\n参考链接\n\n[http://www.jianshu.com/p/2cd59a79ed4a](http://www.jianshu.com/p/2cd59a79ed4a)"
  },
  {
    "path": "Part1/Android/Android基础知识.md",
    "content": "#Android：\n---\n**五种布局： FrameLayout 、 LinearLayout 、 AbsoluteLayout 、 RelativeLayout 、 TableLayout 全都继承自ViewGroup，各自特点及绘制效率对比。**\n\n* FrameLayout(框架布局)\n\n    此布局是五种布局中最简单的布局，Android中并没有对child view的摆布进行控制，这个布局中所有的控件都会默认出现在视图的左上角，我们可以使用``android:layout_margin``，``android:layout_gravity``等属性去控制子控件相对布局的位置。\n\n* LinearLayout(线性布局)\n\n    一行（或一列）只控制一个控件的线性布局，所以当有很多控件需要在一个界面中列出时，可以用LinearLayout布局。\n    此布局有一个需要格外注意的属性:``android:orientation=“horizontal|vertical``。\n    \n    * 当`android:orientation=\"horizontal`时，*说明你希望将水平方向的布局交给**LinearLayout** *，其子元素的`android:layout_gravity=\"right|left\"` 等控制水平方向的gravity值都是被忽略的，*此时**LinearLayout**中的子元素都是默认的按照水平从左向右来排*，我们可以用`android:layout_gravity=\"top|bottom\"`等gravity值来控制垂直展示。\n    * 反之，可以知道 当`android:orientation=\"vertical`时，**LinearLayout**对其子元素展示上的的处理方式。\n\n* AbsoluteLayout(绝对布局)\n\n    可以放置多个控件，并且可以自己定义控件的x,y位置\n\n* RelativeLayout(相对布局)\n\n    这个布局也是相对自由的布局，Android 对该布局的child view的 水平layout& 垂直layout做了解析，由此我们可以FrameLayout的基础上使用标签或者Java代码对垂直方向 以及 水平方向 布局中的views进行任意的控制.\n    \n    * 相关属性：\n    \n    ```\n    \n    　　android:layout_centerInParent=\"true|false\"\n\t　　android:layout_centerHorizontal=\"true|false\"\n\t　　android:layout_alignParentRight=\"true|false\"\n\t　　\n\t```\n\n* TableLayout(表格布局)\n\n    将子元素的位置分配到行或列中，一个TableLayout由许多的TableRow组成\n    \n\n---\n\n\n**Activity生命周期。**\n\n* 启动Activity:\n  onCreate()—>onStart()—>onResume()，Activity进入运行状态。\n* Activity退居后台:\n  当前Activity转到新的Activity界面或按Home键回到主屏：\n  onPause()—>onStop()，进入停滞状态。\n* Activity返回前台:\n  onRestart()—>**onStart()**—>onResume()，再次回到运行状态。\n* Activity退居后台，且系统内存不足，\n  系统会杀死这个后台状态的Activity（此时这个Activity引用仍然处在任务栈中，只是这个时候引用指向的对象已经为null），若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期)\n* 锁屏：`onPause()->onStop()`\n* 解锁：`onStart()->onResume()`\n  \n* 更多流程分支，请参照以下生命周期流程图\n\t![](http://img.blog.csdn.net/20130828141902812)\n\n\n---\n\n\n**通过Acitivty的xml标签来改变任务栈的默认行为**\n\n* 使用``android:launchMode=\"standard|singleInstance|singleTask|singleTop\"``来控制Acivity任务栈。\n\n    **任务栈**是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其``onDestory()``方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.\n\n    * standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(*onCreate()->onStart()->onResume()*)都会执行。\n    - singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的``onNewIntent()``方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与*standard模式*一样.\n    - singleTask: 栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,``onNewIntent()``,并且singleTask会清理在当前Activity上面的所有Activity.(clear top)\n    - singleInstance : 加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了\n\nActivity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈\n\n---\n\n**Activity缓存方法。**\n\n有a、b两个Activity，当从a进入b之后一段时间，可能系统会把a回收，这时候按back，执行的不是a的onRestart而是onCreate方法，a被重新创建一次，这是a中的临时数据和状态可能就丢失了。\n\n可以用Activity中的onSaveInstanceState()回调方法保存临时数据和状态，这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数，putString()、putInt()等方法需要传入两个参数，一个键一个值。数据保存之后会在onCreate中恢复，onCreate也有一个Bundle类型的参数。\n\n示例代码：\n\n```\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        //这里，当Acivity第一次被创建的时候为空\n        //所以我们需要判断一下\n        if( savedInstanceState != null ){\n            savedInstanceState.getString(\"anAnt\");\n        }\n    }\n\n    @Override\n    protected void onSaveInstanceState(Bundle outState) {\n        super.onSaveInstanceState(outState);\n\n        outState.putString(\"anAnt\",\"Android\");\n\n    }\n\n```\n\n一、onSaveInstanceState (Bundle outState)\n\n当某个activity变得“容易”被系统销毁时，该activity的onSaveInstanceState就会被执行，除非该activity是被用户主动销毁的，例如当用户按BACK键的时候。\n\n注意上面的双引号，何为“容易”？言下之意就是该activity还没有被销毁，而仅仅是一种可能性。这种可能性有哪些？通过重写一个activity的所有生命周期的onXXX方法，包括onSaveInstanceState和onRestoreInstanceState方法，我们可以清楚地知道当某个activity（假定为activity A）显示在当前task的最上层时，其onSaveInstanceState方法会在什么时候被执行，有这么几种情况：\n\n1、当用户按下HOME键时。\n\n这是显而易见的，系统不知道你按下HOME后要运行多少其他的程序，自然也不知道activity A是否会被销毁，故系统会调用onSaveInstanceState，让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则\n\n2、长按HOME键，选择运行其他的程序时。\n\n3、按下电源按键（关闭屏幕显示）时。\n\n4、从activity A中启动一个新的activity时。\n\n5、屏幕方向切换时，例如从竖屏切换到横屏时。（如果不指定configchange属性）\n在屏幕切换之前，系统会销毁activity A，在屏幕切换之后系统又会自动地创建activity A，所以onSaveInstanceState一定会被执行\n \n总而言之，onSaveInstanceState的调用遵循一个重要原则，即当系统“未经你许可”时销毁了你的activity，则onSaveInstanceState会被系统调用，这是系统的责任，因为它必须要提供一个机会让你保存你的数据（当然你不保存那就随便你了）。另外，需要注意的几点：\n \n1.布局中的每一个View默认实现了onSaveInstanceState()方法，这样的话，这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用，如果没有提供ID，app将不会存储它的状态。\n \n2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态，所以如果你需要覆盖这个方法去存储额外的状态信息，你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法（super.onSaveInstanceState()）。\n既然有现成的可用，那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了，如果你自己的派生类中有变量影响到UI，或你程序的行为，当然就要把这个变量也保存了，那么就需要自己实现，否则就不需要。\n\n3.由于onSaveInstanceState()方法调用的不确定性，你应该只使用这个方法去记录activity的瞬间状态（UI的状态）。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据（例如应该被存储到数据库中的数据）。\n \n4.onSaveInstanceState()如果被调用，这个方法会在onStop()前被触发，但系统并不保证是否在onPause()之前或者之后触发。\n \n \n二、onRestoreInstanceState (Bundle outState)\n\n至于onRestoreInstanceState方法，需要注意的是，onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的，（本人注：我昨晚调试时就发现原来不一定成对被调用的！）\n \nonRestoreInstanceState被调用的前提是，activity A“确实”被系统销毁了，而如果仅仅是停留在有这种可能性的情况下，则该方法不会被调用，例如，当正在显示activity A的时候，用户按下HOME键回到主界面，然后用户紧接着又返回到activity A，这种情况下activity A一般不会因为内存的原因被系统销毁，故activity A的onRestoreInstanceState方法不会被执行\n\n另外，onRestoreInstanceState的bundle参数也会传递到onCreate方法中，你也可以选择在onCreate方法中做数据还原。\n还有onRestoreInstanceState在onstart之后执行。\n至于这两个函数的使用，给出示范代码（留意自定义代码在调用super的前或后）：\n\n```\n@Override\npublic void onSaveInstanceState(Bundle savedInstanceState) {\n        savedInstanceState.putBoolean(\"MyBoolean\", true);\n        savedInstanceState.putDouble(\"myDouble\", 1.9);\n        savedInstanceState.putInt(\"MyInt\", 1);\n        savedInstanceState.putString(\"MyString\", \"Welcome back to Android\");\n        // etc.\n        super.onSaveInstanceState(savedInstanceState);\n}\n\n@Override\npublic void onRestoreInstanceState(Bundle savedInstanceState) {\n        super.onRestoreInstanceState(savedInstanceState);\n\n        boolean myBoolean = savedInstanceState.getBoolean(\"MyBoolean\");\n        double myDouble = savedInstanceState.getDouble(\"myDouble\");\n        int myInt = savedInstanceState.getInt(\"MyInt\");\n        String myString = savedInstanceState.getString(\"MyString\");\n}\n\n```\n\n---\n\n\n\n**Fragment的生命周期和activity如何的一个关系**\n\n这我们引用本知识库里的一张图片：\n![Mou icon](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/FlowchartDiagram.jpg?raw=true)\n\n\n**为什么在Service中创建子线程而不是Activity中**\n\n这是因为Activity很难对Thread进行控制，当Activity被销毁之后，就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程，另一个Activity无法对其进行操作。但是Service就不同了，所有的Activity都可以与Service进行关联，然后可以很方便地操作其中的方法，即使Activity被销毁了，之后只要重新与Service建立关联，就又能够获取到原有的Service中Binder的实例。因此，使用Service来处理后台任务，Activity就可以放心地finish，完全不需要担心无法对后台任务进行控制的情况。\n\n\n**Intent的使用方法，可以传递哪些数据类型。**\n\n通过查询Intent/Bundle的API文档，我们可以获知，Intent/Bundle支持传递基本类型的数据和基本类型的数组数据，以及String/CharSequence类型的数据和String/CharSequence类型的数组数据。而对于其它类型的数据貌似无能为力，其实不然，我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parcelable（包裹化，邮包）和Serializable（序列化）类型的数据，以及它们的数组/列表数据。\n\n所以要让非基本类型和非String/CharSequence类型的数据通过Intent/Bundle来进行传输，我们就需要在数据类型中实现Parcelable接口或是Serializable接口。\n\n[http://blog.csdn.net/kkk0526/article/details/7214247](http://blog.csdn.net/kkk0526/article/details/7214247)\n\n**Fragment生命周期**\n\n![](http://7xntdm.com1.z0.glb.clouddn.com/fragment_lifecycle.png)\n\n注意和Activity的相比的区别,按照执行顺序\n\n* onAttach(),onDetach()\n* onCreateView(),onDestroyView()\n\n---\n\n**Service的两种启动方法，有什么区别**\n1.在Context中通过``public boolean bindService(Intent service,ServiceConnection conn,int flags)`` 方法来进行Service与Context的关联并启动，并且Service的生命周期依附于Context(**不求同时同分同秒生！但求同时同分同秒屎！！**)。\n\n2.通过`` public ComponentName startService(Intent service)``方法去启动一个Service，此时Service的生命周期与启动它的Context无关。\n\n3.要注意的是，whatever，**都需要在xml里注册你的Service**，就像这样:\n```\n<service\n        android:name=\".packnameName.youServiceName\"\n        android:enabled=\"true\" />\n```\n\n**广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。**\n\n* 静态注册：在AndroidManifest.xml文件中进行注册，当App退出后，Receiver仍然可以接收到广播并且进行相应的处理\n* 动态注册：在代码中动态注册，当App退出后，也就没办法再接受广播了\n\n---\n\n\n**ContentProvider使用方法**\n\n[http://blog.csdn.net/juetion/article/details/17481039](http://blog.csdn.net/juetion/article/details/17481039)\n\n---\n\n**目前能否保证service不被杀死**\n\n**Service设置成START_STICKY**\n\n* kill 后会被重启（等待5秒左右），重传Intent，保持与重启前一样\n\n**提升service优先级**\n * 在AndroidManifest.xml文件中对于intent-filter可以通过``android:priority = \"1000\"``这个属性设置最高优先级，1000是最高值，如果数字越小则优先级越低，**同时适用于广播**。\n * 【结论】目前看来，priority这个属性貌似只适用于broadcast，对于Service来说可能无效\n\n**提升service进程优先级**\n * Android中的进程是托管的，当系统进程空间紧张的时候，会依照优先级自动进行进程的回收\n * 当service运行在低内存的环境时，将会kill掉一些存在的进程。因此进程的优先级将会很重要，可以在startForeground()使用startForeground()将service放到前台状态。这样在低内存时被kill的几率会低一些。\n * 【结论】如果在极度极度低内存的压力下，该service还是会被kill掉，并且不一定会restart()\n\n**onDestroy方法里重启service**\n * service +broadcast  方式，就是当service走onDestory()的时候，发送一个自定义的广播，当收到广播的时候，重新启动service\n * 也可以直接在onDestroy()里startService\n * 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时，APP进程可能就直接被干掉了，onDestroy方法都进不来，所以还是无法保证\n\n**监听系统广播判断Service状态**\n * 通过系统的一些广播，比如：手机重启、界面唤醒、应用状态改变等等监听并捕获到，然后判断我们的Service是否还存活，别忘记加权限\n * 【结论】这也能算是一种措施，不过感觉监听多了会导致Service很混乱，带来诸多不便\n\n**在JNI层,用C代码fork一个进程出来**\n\n* 这样产生的进程,会被系统认为是两个不同的进程.但是Android5.0之后可能不行\n\n**root之后放到system/app变成系统级应用**\n\n**大招: 放一个像素在前台(手机QQ)**\n\n---\n\n\n**动画有哪两类，各有什么特点？三种动画的区别**\n\n* tween 补间动画。通过指定View的初末状态和变化时间、方式，对View的内容完成一系列的图形变换来实现动画效果。\nAlpha\nScale\nTranslate\nRotate。\n\n* frame 帧动画\nAnimationDrawable 控制\nanimation-list xml布局\n\n* PropertyAnimation 属性动画\n \n---\n\n**Android的数据存储形式。**\n\n\n\n* SQLite：SQLite是一个轻量级的数据库，支持基本的SQL语法，是常被采用的一种数据存储方式。\nAndroid为此数据库提供了一个名为SQLiteDatabase的类，封装了一些操作数据库的api\n\n* SharedPreference： 除SQLite数据库外，另一种常用的数据存储方式，其本质就是一个xml文件，常用于存储较简单的参数设置。\n\n* File： 即常说的文件（I/O）存储方法，常用语存储大数量的数据，但是缺点是更新数据将是一件困难的事情。\n\n* ContentProvider: Android系统中能实现所有应用程序共享的一种数据存储方式，由于数据通常在各应用间的是互相私密的，所以此存储方式较少使用，但是其又是必不可少的一种存储方式。例如音频，视频，图片和通讯录，一般都可以采用此种方式进行存储。每个Content Provider都会对外提供一个公共的URI（包装成Uri对象），如果应用程序有数据需要共享时，就需要使用Content Provider为这些数据定义一个URI，然后其他的应用程序就通过Content Provider传入这个URI来对数据进行操作。\n\n---\n\n**Sqlite的基本操作。**\n\n\n[http://blog.csdn.net/zgljl2012/article/details/44769043](http://blog.csdn.net/zgljl2012/article/details/44769043)\n\n---\n\n**如何判断应用被强杀**\n\n在Application中定义一个static常量，赋值为－1，在欢迎界面改为0，如果被强杀，application重新初始化，在父类Activity判断该常量的值。\n\n**应用被强杀如何解决**\n\n如果在每一个Activity的onCreate里判断是否被强杀，冗余了，封装到Activity的父类中，如果被强杀，跳转回主界面，如果没有被强杀，执行Activity的初始化操作，给主界面传递intent参数，主界面会调用onNewIntent方法，在onNewIntent跳转到欢迎页面，重新来一遍流程。\n\n**Json有什么优劣势。**\n\n**怎样退出终止App**\n\n**Asset目录与res目录的区别。**\nres 目录下面有很多文件，例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外，其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。Asset 也是用来存储资源，但是 asset 文件内容只能通过路径或者 AssetManager 读取。 [官方文档](https://developer.android.com/studio/projects/index.html)\n\n**Android怎么加速启动Activity。**\n分两种情况，启动应用 和 普通Activity\n启动应用 ：Application 的构造方法，onCreate 方法中不要进行耗时操作，数据预读取(例如 init 数据) 放在异步中操作\n启动普通的Activity：A 启动B 时不要在 A 的 onPause 中执行耗时操作。因为 B 的 onResume 方法必须等待 A 的 onPause 执行完成后才能运行\n\n**Android内存优化方法：ListView优化，及时关闭资源，图片缓存等等。**\n\n**Android中弱引用与软引用的应用场景。**\n\n**Bitmap的四种属性，与每种属性队形的大小。**\n\n\n**View与View Group分类。自定义View过程：onMeasure()、onLayout()、onDraw()。**\n\n如何自定义控件：\n\n1. 自定义属性的声明和获取\n\n    * 分析需要的自定义属性\n    * 在res/values/attrs.xml定义声明\n    * 在layout文件中进行使用\n    * 在View的构造方法中进行获取       \n2. 测量onMeasure\n3. 布局onLayout(ViewGroup)\n4. 绘制onDraw\n5. onTouchEvent\n6. onInterceptTouchEvent(ViewGroup)\n7. 状态的恢复与保存\n\n\n**Android长连接，怎么处理心跳机制。**\n\n---\n\n**View树绘制流程**\n\n---\n\n**下拉刷新实现原理**\n\n---\n\n**你用过什么框架，是否看过源码，是否知道底层原理。**\n\nRetrofit\n\nEventBus\n\nglide\n\n---\n\n**Android5.0、6.0新特性。**\n\nAndroid5.0新特性：\n\n* MaterialDesign设计风格\n* 支持多种设备\n* 支持64位ART虚拟机\n\nAndroid6.0新特性\n\n* 大量漂亮流畅的动画\n* 支持快速充电的切换\n* 支持文件夹拖拽应用\n* 相机新增专业模式\n\nAndroid7.0新特性\n\n* 分屏多任务\n* 增强的Java8语言模式\n* 夜间模式\n\n---\n\n\n**Context区别**\n\n* Activity和Service以及Application的Context是不一样的,Activity继承自ContextThemeWraper.其他的继承自ContextWrapper\n* 每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象\n* getApplication()用来获取Application实例的，但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的，但是如果在一些其它的场景，比如BroadcastReceiver中也想获得Application的实例，这时就可以借助getApplicationContext()方法，getApplicationContext()比getApplication()方法的作用域会更广一些，任何一个Context的实例，只要调用getApplicationContext()方法都可以拿到我们的Application对象。\n* Activity在创建的时候会new一个ContextImpl对象并在attach方法中关联它，Application和Service也差不多。ContextWrapper的方法内部都是转调ContextImpl的方法\n* 创建对话框传入Application的Context是不可以的\n* 尽管Application、Activity、Service都有自己的ContextImpl，并且每个ContextImpl都有自己的mResources成员，但是由于它们的mResources成员都来自于唯一的ResourcesManager实例，所以它们看似不同的mResources其实都指向的是同一块内存\n* Context的数量等于Activity的个数 + Service的个数 + 1，这个1为Application\n\n---\n\n**IntentService的使用场景与特点。**\n\n>IntentService是Service的子类，是一个异步的，会自动停止的服务，很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题\n\n优点：\n\n* 一方面不需要自己去new Thread\n* 另一方面不需要考虑在什么时候关闭该Service\n\nonStartCommand中回调了onStart，onStart中通过mServiceHandler发送消息到该handler的handleMessage中去。最后handleMessage中回调onHandleIntent(intent)。\n\n---\n\n\n**图片缓存**\n\n查看每个应用程序最高可用内存：\n\n```\n    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  \n    Log.d(\"TAG\", \"Max memory is \" + maxMemory + \"KB\");  \n```\n\n---\n\n**Gradle**\n\n构建工具、Groovy语法、Java\n\nJar包里面只有代码，aar里面不光有代码还包括代码还包括资源文件，比如 drawable 文件，xml 资源文件。对于一些不常变动的 Android Library，我们可以直接引用 aar，加快编译速度\n\n---\n\n**你是如何自学Android**\n\n首先是看书和看视频敲代码，然后看大牛的博客，做一些项目，向github提交代码，觉得自己API掌握的不错之后，开始看进阶的书，以及看源码，看完源码学习到一些思想，开始自己造轮子，开始想代码的提升，比如设计模式，架构，重构等。\n\n---\n\n\n\n"
  },
  {
    "path": "Part1/Android/Android开机过程.md",
    "content": "# Android开机过程\n\n* BootLoder引导,然后加载Linux内核.\n* 0号进程init启动.加载init.rc配置文件,配置文件有个命令启动了zygote进程\n* zygote开始fork出SystemServer进程\n* SystemServer加载各种JNI库,然后init1,init2方法,init2方法中开启了新线程ServerThread.\n* 在SystemServer中会创建一个socket客户端，后续AMS（ActivityManagerService）会通过此客户端和zygote通信\n* ServerThread的run方法中开启了AMS,还孵化新进程ServiceManager,加载注册了一溜的服务,最后一句话进入loop 死循环\n* run方法的SystemReady调用resumeTopActivityLocked打开锁屏界面"
  },
  {
    "path": "Part1/Android/Android性能优化.md",
    "content": "#Android性能优化\n---\n##合理管理内存\n---\n###节制的使用Service\n如果应用程序需要使用Service来执行后台任务的话，只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时，系统会倾向于将这个Service所依赖的进程进行保留，系统可以在LRUcache当中缓存的进程数量也会减少，导致切换程序的时候耗费更多性能。我们可以使用IntentService，当后台任务执行结束后会自动停止，避免了Service的内存泄漏。\n\n###当界面不可见时释放内存\n当用户打开了另外一个程序，我们的程序界面已经不可见的时候，我们应当将所有和界面相关的资源进行释放。重写Activity的onTrimMemory()方法，然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别，一旦触发说明用户离开了程序，此时就可以进行资源释放操作了。\n\n###当内存紧张时释放内存\nonTrimMemory()方法还有很多种其他类型的回调，可以在手机内存降低的时候及时通知我们，我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。\n\n###避免在Bitmap上浪费内存\n读取一个Bitmap图片的时候，千万不要去加载不需要的分辨率。可以压缩图片等操作。\n\n###是有优化过的数据集合\nAndroid提供了一系列优化过后的数据集合工具类，如SparseArray、SparseBooleanArray、LongSparseArray，使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效，因为它需要为每一个键值对都提供一个对象入口，而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。\n\n###知晓内存的开支情况\n* 使用枚举通常会比使用静态常量消耗两倍以上的内存，尽可能不使用枚举\n* 任何一个Java类，包括匿名类、内部类，都要占用大概500字节的内存空间\n* 任何一个类的实例要消耗12-16字节的内存开支，因此频繁创建实例也是会在一定程序上影响内存的\n* 使用HashMap时，即使你只设置了一个基本数据类型的键，比如说int，但是也会按照对象的大小来分配内存，大概是32字节，而不是4字节，因此最好使用优化后的数据集合\n\n###谨慎使用抽象编程\n在Android使用抽象编程会带来额外的内存开支，因为抽象的编程方法需要编写额外的代码，虽然这些代码根本执行不到，但是也要映射到内存中，不仅占用了更多的内存，在执行效率上也会有所降低。所以需要合理的使用抽象编程。\n\n###尽量避免使用依赖注入框架\n使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了，但是这些框架为了要搜寻代码中的注解，通常都需要经历较长的初始化过程，并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间，可能很久之后才会得到释放，所以可能多敲几行代码是更好的选择。\n\n###使用多个进程\n谨慎使用，多数应用程序不该在多个进程中运行的，一旦使用不当，它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务，和前台是完全可以区分开的场景。比如音乐播放，关闭软件，已经完全由Service来控制音乐播放了，系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程，一个用于UI展示，另一个用于在后台持续的播放音乐。关于实现多进程，只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义，但是之前要加个冒号，表示该进程是一个当前应用程序的私有进程。\n\n##分析内存的使用情况\n---\n系统不可能将所有的内存都分配给我们的应用程序，每个程序都会有可使用的内存上限，被称为堆大小。不同的手机堆大小不同，如下代码可以获得堆大小：\n\n```\nActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);\nint heapSize = manager.getMemoryClass();\n```\n结果以MB为单位进行返回，我们开发时应用程序的内存不能超过这个限制，否则会出现OOM。\n\n###Android的GC操作\nAndroid系统会在适当的时机触发GC操作，一旦进行GC操作，就会将一些不再使用的对象进行回收。GC操作会从一个叫做Roots的对象开始检查，所有它可以访问到的对象就说明还在使用当中，应该进行保留，而其他的对系那个就表示已经不再被使用了。\n\n###Android中内存泄漏\nAndroid中的垃圾回收机制并不能防止内存泄漏的出现导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用，导致垃圾回收器无法去回收掉这些对象，也就是出现内存泄漏了。比如说像Activity这样的系统组件，它又会包含很多的控件甚至是图片，如果它无法被垃圾回收器回收掉的话，那就算是比较严重的内存泄漏情况了。\n举个例子，在MainActivity中定义一个内部类，实例化内部类对象，在内部类新建一个线程执行死循环，会导致内部类资源无法释放，MainActivity的控件和资源无法释放，导致OOM,可借助一系列工具，比如LeakCanary。\n\n##高性能编码优化\n---\n都是一些微优化，在性能方面看不出有什么显著的提升的。使用合适的算法和数据结构是优化程序性能的最主要手段。\n\n###避免创建不必要的对象\n不必要的对象我们应该避免创建：\n\n* 如果有需要拼接的字符串，那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接，而不是加号连接符，因为使用加号连接符会创建多余的对象，拼接的字符串越长，加号连接符的性能越低。\n* 在没有特殊原因的情况下，尽量使用基本数据类型来代替封装数据类型，int比Integer要更加有效，其它数据类型也是一样。\n* 当一个方法的返回值是String的时候，通常需要去判断一下这个String的作用是什么，如果明确知道调用方会将返回的String再进行拼接操作的话，可以考虑返回一个StringBuffer对象来代替，因为这样可以将一个对象的引用进行返回，而返回String的话就是创建了一个短生命周期的临时对象。\n* 基本数据类型的数组也要优于对象数据类型的数组。另外两个平行的数组要比一个封装好的对象数组更加高效，举个例子，Foo[]和Bar[]这样的数组，使用起来要比Custom(Foo,Bar)[]这样的一个数组高效的多。\n\n尽可能地少创建临时对象，越少的对象意味着越少的GC操作。\n\n###静态优于抽象\n如果你并不需要访问一个对系那个中的某些字段，只是想调用它的某些方法来去完成一项通用的功能，那么可以将这个方法设置成静态方法，调用速度提升15%-20%，同时也不用为了调用这个方法去专门创建对象了，也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。\n\n###对常量使用static final修饰符\n```\nstatic int intVal = 42;  \nstatic String strVal = \"Hello, world!\";  \n```\n编译器会为上面的代码生成一个初始方法，称为<clinit>方法，该方法会在定义类第一次被使用的时候调用。这个方法会将42的值赋值到intVal当中，从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后，我们就可以通过字段搜寻的方式去访问具体的值了。\n\nfinal进行优化:\n\n```\nstatic final int intVal = 42;  \nstatic final String strVal = \"Hello, world!\";  \n```\n\n这样，定义类就不需要<clinit>方法了，因为所有的常量都会在dex文件的初始化器当中进行初始化。当我们调用intVal时可以直接指向42的值，而调用strVal会用一种相对轻量级的字符串常量方式，而不是字段搜寻的方式。\n\n这种优化方式只对基本数据类型以及String类型的常量有效，对于其他数据类型的常量是无效的。\n\n###使用增强型for循环语法\n\n```\nstatic class Counter {  \n    int mCount;  \n}  \n  \nCounter[] mArray = ...  \n  \npublic void zero() {  \n    int sum = 0;  \n    for (int i = 0; i < mArray.length; ++i) {  \n        sum += mArray[i].mCount;  \n    }  \n}  \n  \npublic void one() {  \n    int sum = 0;  \n    Counter[] localArray = mArray;  \n    int len = localArray.length;  \n    for (int i = 0; i < len; ++i) {  \n        sum += localArray[i].mCount;  \n    }  \n}  \n  \npublic void two() {  \n    int sum = 0;  \n    for (Counter a : mArray) {  \n        sum += a.mCount;  \n    }  \n}  \n```\n\nzero()最慢，每次都要计算mArray的长度，one()相对快得多，two()fangfa在没有JIT(Just In Time Compiler)的设备上是运行最快的，而在有JIT的设备上运行效率和one()方法不相上下，需要注意这种写法需要JDK1.5之后才支持。\n\nTips:ArrayList手写的循环比增强型for循环更快，其他的集合没有这种情况。因此默认情况下使用增强型for循环，而遍历ArrayList使用传统的循环方式。\n\n###多使用系统封装好的API\n\n系统提供不了的Api完成不了我们需要的功能才应该自己去写，因为使用系统的Api很多时候比我们自己写的代码要快得多，它们的很多功能都是通过底层的汇编模式执行的。\n举个例子，实现数组拷贝的功能，使用循环的方式来对数组中的每一个元素一一进行赋值当然可行，但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。\n\n###避免在内部调用Getters/Setters方法\n\n面向对象中封装的思想是不要把类内部的字段暴露给外部，而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中，字段搜寻比方法调用效率高得多，我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的，我们应该在能优化的地方进行优化，比如避免在内部调用getters/setters方法。\n\n##布局优化技巧\n---\n###重用布局文件\n**<include>**\n\n<include>标签可以允许在一个布局当中引入另一个布局，那么比如说我们程序的所有界面都有一个公共的部分，这个时候最好的做法就是将这个公共的部分提取到一个独立的布局中，然后每个界面的布局文件当中来引用这个公共的布局。\n\nTips:如果我们要在<include>标签中覆写layout属性，必须要将layout_width和layout_height这两个属性也进行覆写，否则覆写xiaoguo将不会生效。\n\n**<merge>**\n\n<merge>标签是作为<include>标签的一种辅助扩展来使用的，它的主要作用是为了防止在引用布局文件时引用文件时产生多余的布局嵌套。布局嵌套越多，解析起来就越耗时，性能就越差。因此编写布局文件时应该让嵌套的层数越少越好。\n\n举例：比如在LinearLayout里边使用一个<include>布局。<include>里边又有一个LinearLayout，那么其实就存在了多余的布局嵌套，使用merge可以解决这个问题。\n\n###仅在需要时才加载布局\n\n某个布局当中的元素不是一起显示出来的，普通情况下只显示部分常用的元素，而那些不常用的元素只有在用户进行特定操作时才会显示出来。\n\n举例：填信息时不是需要全部填的，有一个添加更多字段的选项，当用户需要添加其他信息的时候，才将另外的元素显示到界面上。用VISIBLE性能表现一般，可以用ViewStub。ViewStub也是View的一种，但是没有大小，没有绘制功能，也不参与布局，资源消耗非常低，可以认为完全不影响性能。\n\n```\n<ViewStub   \n        android:id=\"@+id/view_stub\"  \n        android:layout=\"@layout/profile_extra\"  \n        android:layout_width=\"match_parent\"  \n        android:layout_height=\"wrap_content\"  \n        />  \n```\n\n```\npublic void onMoreClick() {  \n    ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);  \n    if (viewStub != null) {  \n        View inflatedView = viewStub.inflate();  \n        editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);  \n        editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);  \n        editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);  \n    }  \n}  \n```\n\ntips：ViewStub所加载的布局是不可以使用<merge>标签的，因此这有可能导致加载出来出来的布局存在着多余的嵌套结构。\n\n"
  },
  {
    "path": "Part1/Android/Android系统机制.md",
    "content": "#Android系统机制\n---\n###APP启动过程\n\n1. Launcher线程捕获onclick的点击事件，调用Launcher.startActivitySafely，进一步调用Launcher.startActivity，最后调用父类Activity的startActivity。\n2. Activity和ActivityManagerService交互，引入Instrumentation，将启动请求交给Instrumentation，调用Instrumentation.execStartActivity\n3. \n\n###Android内核解读-应用的安装过程\n\n[http://blog.csdn.net/singwhatiwanna/article/details/19578947](http://blog.csdn.net/singwhatiwanna/article/details/19578947)\napk的安装过程分为两步：\n\n1. 将apk文件复制到程序目录下(/data/app/)\n2. 为应用创建数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/delvik-cache/)、修改系统包管理信息。\n\n\n###View的事件体系\n\n\n\n###Handler消息机制\n\n\n###AsyncTask\nAyncTask是一个抽象类。\n\n需要重写的方法有四个：\n1. onPreExecute()\n\t这个方法会在后台任务开始之前调用，用于进行一些界面上的初始化操作，比如显示一个进度条对话框等\n2. doInBackGround(Params...)\n\t在子线程中运行，不可更新UI，返回的是执行结果，第三个参数为Void不返回\n3. onProgressUpdate(Progress...)\n\t利用参数可以进行UI操作。\n4. onPostExecute(Result)\n\t返回的数据会作为参数传递到此方法中，可以利用返回的一些数据来进行一些UI操作。\n\n```\nclass DownloadTask extends AsyncTask<Void, Integer, Boolean> {  \n  \n    @Override  \n    protected void onPreExecute() {  \n        progressDialog.show();  \n    }  \n  \n    @Override  \n    protected Boolean doInBackground(Void... params) {  \n        try {  \n            while (true) {  \n                int downloadPercent = doDownload();  \n                publishProgress(downloadPercent);  \n                if (downloadPercent >= 100) {  \n                    break;  \n                }  \n            }  \n        } catch (Exception e) {  \n            return false;  \n        }  \n        return true;  \n    }  \n  \n    @Override  \n    protected void onProgressUpdate(Integer... values) {  \n        progressDialog.setMessage(\"当前下载进度：\" + values[0] + \"%\");  \n    }  \n  \n    @Override  \n    protected void onPostExecute(Boolean result) {  \n        progressDialog.dismiss();  \n        if (result) {  \n            Toast.makeText(context, \"下载成功\", Toast.LENGTH_SHORT).show();  \n        } else {  \n            Toast.makeText(context, \"下载失败\", Toast.LENGTH_SHORT).show();  \n        }  \n    }  \n}  \n```\n\n启动这个任务：\n\n```\nnew DownloadTask().execute();\n```\n\n\n\n\n\n\n\n###图片缓存机制\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/Art和Dalvik区别.md",
    "content": "#ART和Dalvik区别\n---\n\nArt上应用启动快，运行快，但是耗费更多存储空间，安装时间长，总的来说ART的功效就是\"空间换时间\"。\n\nART: Ahead of Time Dalvik: Just in Time\n\n什么是Dalvik：Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一，它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行，.dex格式是专为Dalvik应用设计的一种压缩格式，适合内存和处理器速度有限的系统。Dalvik经过优化，允许在有限的内存中同时运行多个虚拟机的实例，并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。\n\n什么是ART:Android操作系统已经成熟，Google的Android团队开始将注意力转向一些底层组件，其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik，Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行，这一机制并不高效，但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法，在应用安装的时候就预编译字节码到机器语言，这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后，应用程序执行将更有效率，启动更快。\n\nART优点：\n\n1. 系统性能的显著提升\n2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时\n3. 更长的电池续航能力\n4. 支持更低的硬件\n\nART缺点：\n\n1. 更大的存储空间占用，可能会增加10%-20%\n2. 更长的应用安装时间"
  },
  {
    "path": "Part1/Android/Asynctask源码分析.md",
    "content": "#AsyncTask\n---\n\n\n首先从Android3.0开始，系统要求网络访问必须在子线程中进行，否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常，这样做是为了避免主线程由于耗时操作所阻塞从而出现ANR现象。AsyncTask封装了线程池和Handler。AsyncTask有两个线程池：SerialExecutor和THREAD_POOL_EXECUTOR。前者是用于任务的排队，默认是串行的线程池：后者用于真正的执行任务。AsyncTask还有一个Handler，叫InternalHandler，用于将执行环境从线程池切换到主线程。AsyncTask内部就是通过InternalHandler来发送任务执行的进度以及执行结束等消息。\n\nAsyncTask排队执行过程：系统先把参数Params封装为FutureTask对象，它相当于Runnable，接着FutureTask交给SerialExcutor的execute方法，它先把FutureTask插入到任务队列tasks中，如果这个时候没有正在活动的AsyncTask任务，那么就会执行下一个AsyncTask任务，同时当一个AsyncTask任务执行完毕之后，AsyncTask会继续执行其他任务直到所有任务都被执行为止。\n\n\n---\n\n关于线程池，AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的，都是static的，所以是AsyncTask控制着进程范围内所有的子类实例。由于这个限制的存在，当使用默认线程池时，如果线程数超过线程池的最大容量，线程池就会爆掉(3.0默认串行执行，不会出现这个问题)。针对这种情况。可以尝试自定义线程池，配合AsyncTask使用。\n\n"
  },
  {
    "path": "Part1/Android/Binder机制.md",
    "content": "#Binder机制\n---\n\n首先Binder是Android系统进程间通信(IPC)方式之一。\n\nBinder使用Client－Server通信方式。Binder框架定义了四个角色：Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间，驱动运行于内核空间。Binder驱动程序提供设备文件/dev/binder与用户空间交互，Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信。\n\nServer创建了Binder实体，为其取一个字符形式，可读易记的名字，将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager，通知ServiceManager注册一个名字为XX的Binder，它位于Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体结点以及ServiceManager对实体的引用，将名字以及新建的引用打包给ServiceManager。ServiceManager收数据包后，从中取出名字和引用填入一张查找表中。但是一个Server若向ServiceManager注册自己Binder就必须通过0这个引用和ServiceManager的Binder通信。Server向ServiceManager注册了Binder实体及其名字后，Client就可以通过名字获得该Binder的引用了。Clent也利用保留的0号引用向ServiceManager请求访问某个Binder：我申请名字叫XX的Binder的引用。ServiceManager收到这个连接请求，从请求数据包里获得Binder的名字，在查找表里找到该名字对应的条目，从条目中取出Binder引用，将该引用作为回复发送给发起请求的Client。\n\n当然，不是所有的Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client，当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字，所以是匿名Binder。Client将会收到这个匿名Binder的引用，通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道，只要Server没有把匿名Binder发给别的进程，别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用，向该Binder发送请求。\n\n---\n\n\n###为什么Binder只进行了一次数据拷贝？\n\n\nLinux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数，需要先用copy_from_user()拷贝到内核空间，再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝，mmap()分配的内存除了映射进了接收方进程里，还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间，这就是Binder只需一次拷贝的‘秘密’。\n\n\n最底层的是Android的ashmen(Anonymous shared memory)机制，它负责辅助实现内存的分配，以及跨进程所需要的内存共享。AIDL(android interface definition language)对Binder的使用进行了封装，可以让开发者方便的进行方法的远程调用，后面会详细介绍。Intent是最高一层的抽象，方便开发者进行常用的跨进程调用。\n\n从英文字面上意思看，Binder具有粘结剂的意思那么它是把什么东西粘接在一起呢？在Android系统的Binder机制中，由一系统组件组成，分别是Client、Server、Service Manager和Binder驱动，其中Client、Server、Service Manager运行在用户空间，Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘连剂了，其中，核心组件便是Binder驱动程序了，ServiceManager提供了辅助管理的功能，Client和Server正是Binder驱动和ServiceManager提供的基础设施上，进行Client-Server之间的通信。\n\n1. Client、Server和ServiceManager实现在用户空间中，Binder驱动实现在内核空间中\n2. Binder驱动程序和ServiceManager在Android平台中已经实现，开发者只需要在用户空间实现自己的Client和Server\n3. Binder驱动程序提供设备文件/dev/binder与用户空间交互，Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信\n4. Client和Server之间的进程间通信通过Binder驱动程序间接实现\n5. ServiceManager是一个守护进程，用来管理Server，并向Client提供查询Server接口的能力\n\n服务器端：一个Binder服务器就是一个Binder类的对象。当创建一个Binder对象后，内部就会开启一个线程，这个线程用户接收binder驱动发送的消息，收到消息后，会执行相关的服务代码。\n\nBinder驱动：当服务端成功创建一个Binder对象后，Binder驱动也会相应创建一个mRemote对象，该对象的类型也是Binder类，客户就可以借助这个mRemote对象来访问远程服务。\n\n客户端：客户端要想访问Binder的远程服务，就必须获取远程服务的Binder对象在binder驱动层对应的binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后，就可以调用相应Binde对象的服务了。\n\n在这里我们可以看到，客户是通过Binder驱动来调用服务端的相关服务。首先，在服务端创建一个Binder对象，接着客户端通过获取Binder驱动中Binder对象的引用来调用服务端的服务。在Binder机制中正是借着Binder驱动将不同进程间的组件bind(粘连)在一起，实现通信。\n\nmmap将一个文件或者其他对象映射进内存。文件被映射进内存。文件被映射到多个页上，如果文件的大小不是所有页的大小之和，最后一个页不被使用的空间将会凋零。munmap执行相反的操作，删除特定地址区域的对象映射。\n\n当使用mmap映射文件到进程后，就可以直接操作这段虚拟地址进行文件的读写等操作，不必再调用read,write等系统调用。但需注意，直接对该段内存写时不会写入超过当前文件大小的内容。\n\n使用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次内存数据：一次从输入文件到共享内存区，另一次从共享内存到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域，而是保持共享区域，直到通信完成为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除内存映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。\n\naidl主要就帮助了我们完成了包装数据和解包的过程，并调用了transact过程，而用来传递的数据包我们就称为parcel\n\nAIDL:xxx.aidl -> xxx.java ,注册service\n\n1. 用aidl定义需要被调用方法接口\n2. 实现这些方法\n3. 调用这些方法\n\n"
  },
  {
    "path": "Part1/Android/Bitmap的分析与使用.md",
    "content": "## **Bitmap**的分析与使用\n - Bitmap的创建\n    - 创建Bitmap的时候，Java不提供`new Bitmap()`的形式去创建，而是通过`BitmapFactory`中的静态方法去创建,如:`BitmapFactory.decodeStream(is);//通过InputStream去解析生成Bitmap`(这里就不贴`BitmapFactory`中创建`Bitmap`的方法了，大家可以自己去看它的源码)，我们跟进`BitmapFactory`中创建`Bitmap`的源码，最终都可以追溯到这几个native函数\n    ```\n        private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,\n                    Rect padding, Options opts);\n        private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,\n                Rect padding, Options opts);\n        private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);\n        private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,\n                int length, Options opts);\n    ```\n     而`Bitmap`又是Java对象，这个Java对象又是从native，也就是C/C++中产生的，所以，在Android中Bitmap的内存管理涉及到两部分，一部分是*native*，另一部分是*dalvik*，也就是我们常说的java堆(如果对java堆与栈不了解的同学可以戳)，到这里基本就已经了解了创建Bitmap的一些内存中的特性(大家可以使用``adb shell dumpsys meminfo``去查看Bitmap实例化之后的内存使用情况)。\n \n - Bitmap的使用\n    - 我们已经知道了`BitmapFactory`是如何通过各种资源创建`Bitmap`了，那么我们如何合理的使用它呢？以下是几个我们使用`Bitmap`需要关注的点\n        1. **Size**\n            - 这里我们来算一下，在Android中，如果采用`Config.ARGB_8888`的参数去创建一个`Bitmap`，[这是Google推荐的配置色彩参数](https://developer.android.com/reference/android/graphics/Bitmap.Config.html)，也是Android4.4及以上版本默认创建Bitmap的Config参数(``Bitmap.Config.inPreferredConfig``的默认值)，那么每一个像素将会占用4byte，如果一张手机照片的尺寸为1280×720，那么我们可以很容易的计算出这张图片占用的内存大小为 1280x720x4 = 3686400(byte) = 3.5M，一张未经处理的照片就已经3.5M了! 显而易见，在开发当中，这是我们最需要关注的问题，否则分分钟OOM!\n            - *那么，我们一般是如何处理Size这个重要的因素的呢？*，当然是调整`Bitmap`的大小到适合的程度啦！辛亏在`BitmapFactory`中，我们可以很方便的通过`BitmapFactory.Options`中的`options.inSampleSize`去设置`Bitmap`的压缩比，官方给出的说法是 \n            > If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory....For example, inSampleSize == 4 returns\n            an image that is 1/4 the width/height of the original, and 1/16 the\n            number of pixels. Any value <= 1 is treated the same as 1.\n            \n             很简洁明了啊！也就是说，只要按计算方法设置了这个参数，就可以完成我们Bitmap的Size调整了。那么，应该怎么调整姿势才比较舒服呢？下面先介绍其中一种通过``InputStream``的方式去创建``Bitmap``的方法，上一段从Gallery中获取照片并且将图片Size调整到合适手机尺寸的代码：\n        ```\n            static final int PICK_PICS = 9;\n            \n            public void startGallery(){\n                Intent i = new Intent();\n                i.setAction(Intent.ACTION_PICK);\n                i.setType(\"image/*\");\n                startActivityForResult(i,PICK_PICS);\n            }\n            \n             private int[] getScreenWithAndHeight(){\n                WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);\n                DisplayMetrics dm = new DisplayMetrics();\n                wm.getDefaultDisplay().getMetrics(dm);\n                return new int[]{dm.widthPixels,dm.heightPixels};\n            }\n            \n            /**\n             *\n             * @param actualWidth 图片实际的宽度，也就是options.outWidth\n             * @param actualHeight 图片实际的高度，也就是options.outHeight\n             * @param desiredWidth 你希望图片压缩成为的目的宽度\n             * @param desiredHeight 你希望图片压缩成为的目的高度\n             * @return\n             */\n            private int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {\n                double wr = (double) actualWidth / desiredWidth;\n                double hr = (double) actualHeight / desiredHeight;\n                double ratio = Math.min(wr, hr);\n                float n = 1.0f;\n                //这里我们为什么要寻找 与ratio最接近的2的倍数呢？\n                //原因就在于API中对于inSimpleSize的注释：最终的inSimpleSize应该为2的倍数，我们应该向上取与压缩比最接近的2的倍数。\n                while ((n * 2) <= ratio) {\n                    n *= 2;\n                }\n        \n                return (int) n;\n            }\n\n            @Override\n            protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n                if(resultCode == RESULT_OK){\n                    switch (requestCode){\n                        case PICK_PICS:\n                            Uri uri = data.getData();\n                            InputStream is = null;\n                            try {\n                                is = getContentResolver().openInputStream(uri);\n                            } catch (FileNotFoundException e) {\n                                e.printStackTrace();\n                            }\n        \n                            BitmapFactory.Options options = new BitmapFactory.Options();\n                            //当这个参数为true的时候,意味着你可以在解析时候不申请内存的情况下去获取Bitmap的宽和高\n                            //这是调整Bitmap Size一个很重要的参数设置\n                            options.inJustDecodeBounds = true;\n                            BitmapFactory.decodeStream( is,null,options );\n        \n                            int realHeight = options.outHeight;\n                            int realWidth = options.outWidth;\n        \n                            int screenWidth = getScreenWithAndHeight()[0];\n                            \n                            int simpleSize = findBestSampleSize(realWidth,realHeight,screenWidth,300);\n                            options.inSampleSize = simpleSize;\n                            //当你希望得到Bitmap实例的时候，不要忘了将这个参数设置为false\n                            options.inJustDecodeBounds = false;\n        \n                            try {\n                                is = getContentResolver().openInputStream(uri);\n                            } catch (FileNotFoundException e) {\n                                e.printStackTrace();\n                            }\n        \n                            Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);\n        \n                            iv.setImageBitmap(bitmap);\n        \n                            try {\n                                is.close();\n                                is = null;\n                            } catch (IOException e) {\n                                e.printStackTrace();\n                            }\n        \n                            break;\n                    }\n                }\n                super.onActivityResult(requestCode, resultCode, data);\n            }\n        ```\n        \n     我们来看看这段代码的功效：\n     压缩前：![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c)\n     压缩后：![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851)\n     **对比条件为：1080P的魅族Note3拍摄的高清无码照片**\n            \n        2. **Reuse**\n        上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式，以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法，但将单个Bitmap加载到UI是简单的，但是如果我们需要一次性加载大量的图片，事情就会变得复杂起来。`Bitmap`是吃内存大户，我们不希望多次解析相同的`Bitmap`，也不希望可能不会用到的`Bitmap`一直存在于内存中，所以，这个场景下，`Bitmap`的重用变得异常的重要。\n        *在这里只介绍一种``BitmapFactory.Options.inBitmap``的重用方式，下一篇文章会介绍使用三级缓存来实现Bitmap的重用。*\n        \n              根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap)，如果这个值被设置了，decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的，这样可以提升性能, 并且减少了内存的分配与回收。然而，使用inBitmap有一些限制。特别是在Android 4.4 之前，只支持同等大小的位图。\n            我们看来看看这个参数最基本的运用方法。\n            \n            ```\n            new BitmapFactory.Options options = new BitmapFactory.Options();\n            //inBitmap只有当inMutable为true的时候是可用的。\n            options.inMutable = true;\n            Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options);\n            options.inBitmap = reusedBitmap;\n            ```\n            \n              这样，当你在下一次decodeBitmap的时候，将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入，Android就会复用你的Bitmap了，具体实例：\n            \n            ```\n            @Override\n            protected void onCreate(@Nullable Bundle savedInstanceState) {\n                super.onCreate(savedInstanceState);\n                setContentView(reuseBitmap());\n            }\n    \n            private LinearLayout reuseBitmap(){\n                LinearLayout linearLayout = new LinearLayout(this);\n                linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));\n                linearLayout.setOrientation(LinearLayout.VERTICAL);\n        \n                ImageView iv = new ImageView(this);\n                iv.setLayoutParams(new ViewGroup.LayoutParams(500,300));\n        \n                options = new BitmapFactory.Options();\n                options.inJustDecodeBounds = true;\n                //inBitmap只有当inMutable为true的时候是可用的。\n                options.inMutable = true;\n                BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);\n                \n                //压缩Bitmap到我们希望的尺寸\n                //确保不会OOM\n                options.inSampleSize = findBestSampleSize(options.outWidth,options.outHeight,500,300);\n                options.inJustDecodeBounds = false;\n        \n                Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);\n                options.inBitmap = bitmap;\n        \n                iv.setImageBitmap(bitmap);\n        \n                linearLayout.addView(iv);\n        \n                ImageView iv1 = new ImageView(this);\n                iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300));\n                iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));\n                linearLayout.addView(iv1);\n        \n                ImageView iv2 = new ImageView(this);\n                iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300));\n                iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));\n                linearLayout.addView(iv2);\n        \n        \n                return linearLayout;\n            }\n            ```\n            \n             以上代码中，我们在解析了一次一张1080P分辨率的图片，并且设置在`options.inBitmap`中，然后分别decode了同一张图片，并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。\n            \n        3. **Recycle**\n        一定要记得及时回收Bitmap，否则如上分析，你的native以及dalvik的内存都会被一直占用着，最终导致OOM\n        \n        \n        ```\n        // 先判断是否已经回收\n        if(bitmap != null && !bitmap.isRecycled()){\n            // 回收并且置为null\n            bitmap.recycle();\n            bitmap = null;\n        }\n        System.gc();\n        ```\n        \n    - Enjoy Android  :) 如果有误，轻喷，欢迎指正。\n\n"
  },
  {
    "path": "Part1/Android/EventBus用法详解.md",
    "content": "#EventBus\n---\n\n###概述\n\nEventBus是一款针对Android优化的发布/订阅（publish/subscribe）事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment，Activity，Service，线程之间传递消息。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小，代码更优雅。以及将发送者和接收者解耦。比如请求网络，等网络返回时通过Handler或Broadcast通知UI，两个Fragment之间需要通过Listener通信，这些需求都可以通过EventBus实现。\n\n###EventBus作为一个消息总线，有三个主要的元素：\n\n* Event：事件。可以是任意类型的对象\n* Subscriber：事件订阅者，接收特定的事件。在EventBus中，使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数，具体来说，函数的名字是onEvent,onEventMainThread，onEventBackgroundThread，onEventAsync这四个，这个和\nThreadMode（下面讲）有关。\n* Publisher：事件发布者，用于通知 Subscriber 有事件发生。可以在任意线程任意位置发送事件，直接调用eventBus.post(Object) 方法，可以自己实例化 EventBus \n对象，但一般使用默认的单例就好了：EventBus.getDefault()， 根据post函数参数的类型，会自动调用订阅相应类型事件的函数。\n\n###关于ThreadMode\n\n前面说了，Subscriber的函数只能是那4个，因为每个事件订阅函数都是和一个ThreadMode相关联的，ThreadMode指定了会调用的函数。有以下四个ThreadMode：\n\n* PostThread：事件的处理在和事件的发送在相同的进程，所以事件处理时间不应太长，不然影响事件的发送线程，而这个线程可能是UI线程。对应的函数名是onEvent。\n* MainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长，这个不用说的，长了会ANR的，对应的函数名是onEventMainThread。\n* BackgroundThread：事件的处理会在一个后台线程中执行，对应的函数名是onEventBackgroundThread，虽然名字是BackgroundThread，事件处理是在后台线程，但事件处理时间还是不应该太长，因为如果发送事件的线程是后台线程，会直接执行事件，如果当前线程是UI线程，事件会被加到一个队列中，由一个线程依次处理这些事件，如果某个事件处理时间太长，会阻塞后面的事件的派发或处理。\n* Async：事件处理会在单独的线程中执行，主要用于在后台线程中执行耗时操作，每个事件会开启一个线程（有线程池），但最好限制线程的数目。\n\n根据事件订阅都函数名称的不同，会使用不同的ThreadMode，比如果在后台线程加载了数据想在UI线程显示，订阅者只需把函数命名onEventMainThread。\n\n对相应的函数名，进一步解释一下：\n\n**onEvent**:如果使用onEvent作为订阅函数，那么该事件在哪个线程发布出来的，onEvent就会在这个线程中运行，也就是说发布事件和接收事件线程在同一个线程。使用这个方法时，在onEvent方法中不能执行耗时操作，如果执行耗时操作容易导致事件分发延迟。\n\n**onEventMainThread**:如果使用onEventMainThread作为订阅函数，那么不论事件是在哪个线程中发布出来的，onEventMainThread都会在UI线程中执行，接收事件就会在UI线程中运行，这个在Android中是非常有用的，因为在Android中只能在UI线程中跟新UI，所以在onEvnetMainThread方法中是不能执行耗时操作的。\n\n**onEventBackground**:如果使用onEventBackgrond作为订阅函数，那么如果事件是在UI线程中发布出来的，那么onEventBackground就会在子线程中运行，如果事件本来就是子线程中发布出来的，那么onEventBackground函数直接在该子线程中执行。\n\n**onEventAsync**：使用这个函数作为订阅函数，那么无论事件在哪个线程发布，都会创建新的子线程在执行onEventAsync。\n\n##基本用法\n\n###引入EventBus:\n\n```\ncompile 'org.greenrobot:eventbus:3.0.0'\n```\n\n定义事件:\n\n```\npublic class MessageEvent { /* Additional fields if needed */ }\n```\n\n注册事件接收者：\n\n```\neventBus.register(this);\n```\n\n发送事件:\n\n```\neventBus.post(event)\n```\n\n接收消息并处理:\n\n```\n// 3.0后不再要求事件以 onEvent 开头，而是采用注解的方式\n@Subscribe(threadMode = ThreadMode.MAIN)\npublic void receive(MessageEvent event){}\n```\n\n注销事件接收：\n\n```\neventBus.unregister(this);\n```\n\n索引加速：\n\n```\n3.0 后引入了索引加速(默认不开启)的功能，即通过 apt 编译插件的方式，在代码编译的时候对注解进行索引，避免了以往通过反射造成的性能损耗。\n如何使用可以参考[官方文档](http://greenrobot.org/eventbus/documentation/subscriber-index/)\n```\n\n最后，proguard 需要做一些额外处理:\n\n```\n#EventBus\n -keepclassmembers class ** {\n    public void onEvent*(**);\n    void onEvent*(**);\n }\n```\n\n\n\n"
  },
  {
    "path": "Part1/Android/Fragment.md",
    "content": "#Fragment\n\n##为何产生\n* 同时适配手机和平板、UI和逻辑的共享。\n\n##介绍\n* Fragment也会被加入回退栈中。\n* Fragment拥有自己的生命周期和接受、处理用户的事件\n* 可以动态的添加、替换和移除某个Fragment\n\n##生命周期\n* 必须依存于Activity\n\n![Mou icon](http://img.blog.csdn.net/20140719225005356?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG1qNjIzNTY1Nzkx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n* Fragment依附于Activity的生命状态\n* ![Mou icon](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/FlowchartDiagram.jpg?raw=true)\n\n生命周期中那么多方法，懵逼了的话我们就一起来看一下每一个生命周期方法的含义吧。\n\n##Fragment生命周期方法含义：\n* `public void onAttach(Context context)`\n\t* onAttach方法会在Fragment于窗口关联后立刻调用。从该方法开始，就可以通过Fragment.getActivity方法获取与Fragment关联的窗口对象，但因为Fragment的控件未初始化，所以不能够操作控件。\n\t\n* `public void onCreate(Bundle savedInstanceState)`\n\t* 在调用完onAttach执行完之后立刻调用onCreate方法，可以在Bundle对象中获取一些在Activity中传过来的数据。通常会在该方法中读取保存的状态，获取或初始化一些数据。在该方法中不要进行耗时操作，不然窗口不会显示。\n\t\n* `public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)`\n\t* 该方法是Fragment很重要的一个生命周期方法，因为会在该方法中创建在Fragment显示的View，其中inflater是用来装载布局文件的，container是`<fragment>`标签的父标签对应对象，savedInstanceState参数可以获取Fragment保存的状态，如果未保存那么就为null。\n\t\n* `public void onViewCreated(View view,Bundle savedInstanceState)`\n\t* Android在创建完Fragment中的View对象之后，会立刻回调该方法。其种view参数就是onCreateView中返回的view，而bundle对象用于一般用途。\n\t\n* `public void onActivityCreated(Bundle savedInstanceState)`\n\t* 在Activity的onCreate方法执行完之后，Android系统会立刻调用该方法，表示窗口已经初始化完成，从这一个时候开始，就可以在Fragment中使用getActivity().findViewById(Id);来操控Activity中的view了。\n\t\n* `public void onStart()`\n\t* 这个没啥可讲的，但有一个细节需要知道，当系统调用该方法的时候，fragment已经显示在ui上，但还不能进行互动，因为onResume方法还没执行完。\n\t\n* `public void onResume()`\n\t* 该方法为fragment从创建到显示Android系统调用的最后一个生命周期方法，调用完该方法时候，fragment就可以与用户互动了。\n\n* `public void onPause()`\n\t* fragment由活跃状态变成非活跃状态执行的第一个回调方法，通常可以在这个方法中保存一些需要临时暂停的工作。如保存音乐播放进度，然后在onResume中恢复音乐播放进度。\n\n* `public void onStop()`\n\t* 当onStop返回的时候，fragment将从屏幕上消失。\n\n* `public void onDestoryView()`\n\t* 该方法的调用意味着在 `onCreateView` 中创建的视图都将被移除。\n\n* `public void onDestroy()`\n\t* Android在Fragment不再使用时会调用该方法，要注意的是~这时Fragment还和Activity藕断丝连！并且可以获得Fragment对象，但无法对获得的Fragment进行任何操作（呵~呵呵~我已经不听你的了）。\n\n* `public void onDetach()`\n\t* 为Fragment生命周期中的最后一个方法，当该方法执行完后，Fragment与Activity不再有关联(分手！我们分手！！(╯‵□′)╯︵┻━┻)。\n\n##Fragment比Activity多了几个额外的生命周期回调方法：\n* onAttach(Activity):当Fragment和Activity发生关联时使用\n* onCreateView(LayoutInflater, ViewGroup, Bundle):创建该Fragment的视图\n* onActivityCreate(Bundle):当Activity的onCreate方法返回时调用\n* onDestoryView():与onCreateView相对应，当该Fragment的视图被移除时调用\n* onDetach():与onAttach相对应，当Fragment与Activity关联被取消时调用\n\n###注意：除了onCreateView，其他的所有方法如果你重写了，必须调用父类对于该方法的实现\n\n##Fragment与Activity之间的交互\n* Fragment与Activity之间的交互可以通过`Fragment.setArguments(Bundle args)`以及`Fragment.getArguments()`来实现。\n\n##Fragment状态的持久化。\n由于Activity会经常性的发生配置变化，所以依附它的Fragment就有需要将其状态保存起来问题。下面有两个常用的方法去将Fragment的状态持久化。\n\n* 方法一：\n\t* 可以通过`protected void onSaveInstanceState(Bundle outState)`,`protected void onRestoreInstanceState(Bundle savedInstanceState)` 状态保存和恢复的方法将状态持久化。\n* 方法二(更方便,让Android自动帮我们保存Fragment状态)：\n\t* 我们只需要将Fragment在Activity中作为一个变量整个保存，只要保存了Fragment，那么Fragment的状态就得到保存了，所以呢.....\n\t\t* `FragmentManager.putFragment(Bundle bundle, String key, Fragment fragment)` 是在Activity中保存Fragment的方法。\n\t\t* `FragmentManager.getFragment(Bundle bundle, String key)` 是在Activity中获取所保存的Frament的方法。\n\t* 很显然，key就传入Fragment的id，fragment就是你要保存状态的fragment，但，我们注意到上面的两个方法，第一个参数都是Bundle，这就意味着*FragmentManager*是通过Bundle去保存Fragment的。但是，这个方法仅仅能够保存Fragment中的控件状态，比如说EditText中用户已经输入的文字（*注意！在这里，控件需要设置一个id，否则Android将不会为我们保存控件的状态*），而Fragment中需要持久化的变量依然会丢失，但依然有解决办法，就是利用方法一！\n\t* 下面给出状态持久化的事例代码：\n\t\t```\t\t\n\t\t\t /** Activity中的代码 **/\n\t\t\t FragmentB fragmentB;\n\n\t\t    @Override\n\t\t    protected void onCreate(@Nullable Bundle savedInstanceState) {\n\t\t        super.onCreate(savedInstanceState);\n\t\t        setContentView(R.layout.fragment_activity);\n\t\t        if( savedInstanceState != null ){\n\t\t            fragmentB = (FragmentB) getSupportFragmentManager().getFragment(savedInstanceState,\"fragmentB\");\n\t\t        }\n\t\t        init();\n\t\t    }\n\n\t\t    @Override\n\t\t    protected void onSaveInstanceState(Bundle outState) {\n\t\t        if( fragmentB != null ){\n\t\t            getSupportFragmentManager().putFragment(outState,\"fragmentB\",fragmentB);\n\t\t        }\n\n\t\t        super.onSaveInstanceState(outState);\n\t\t    }\n\n\t\t    /** Fragment中保存变量的代码 **/\n\n\t\t    @Nullable\n\t\t    @Override\n\t\t    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n\t\t        AppLog.e(\"onCreateView\");\n\t\t        if ( null != savedInstanceState ){\n\t\t            String savedString = savedInstanceState.getString(\"string\");\n\t\t            //得到保存下来的string\n\t\t        }\n\t\t        View root = inflater.inflate(R.layout.fragment_a,null);\n\t\t        return root;\n\t\t    }\n\n\t\t\t@Override\n\t\t    public void onSaveInstanceState(Bundle outState) {\n\t\t        outState.putString(\"string\",\"anAngryAnt\");\n\t\t        super.onSaveInstanceState(outState);\n\t\t    }\n\n\t\t```\n\n##静态的使用Fragment\n1. 继承Fragment，重写onCreateView决定Fragment的布局\n2. 在Activity中声明此Fragment,就和普通的View一样\n\n##Fragment常用的API\n* android.support.v4.app.Fragment 主要用于定义Fragment\n* android.support.v4.app.FragmentManager 主要用于在Activity中操作Fragment，可以使用FragmentManager.findFragmenById，FragmentManager.findFragmentByTag等方法去找到一个Fragment\n* android.support.v4.app.FragmentTransaction 保证一些列Fragment操作的原子性，熟悉事务这个词\n* 主要的操作都是FragmentTransaction的方法\n(一般我们为了向下兼容，都使用support.v4包里面的Fragment)\n<code>\n\n\tgetFragmentManager() // Fragment若使用的是support.v4包中的，那就使用getSupportFragmentManager代替\n\t\n</code>\n\n* 主要的操作都是FragmentTransaction的方法\n\n```\n\t\n\tFragmentTransaction transaction = fm.benginTransatcion();//开启一个事务\n\ttransaction.add() \n\t//往Activity中添加一个Fragment\n\n\ttransaction.remove() \n\t//从Activity中移除一个Fragment，如果被移除的Fragment没有添加到回退栈（回退栈后面会详细说），这个Fragment实例将会被销毁。\n\n\ttransaction.replace()\n\t//使用另一个Fragment替换当前的，实际上就是remove()然后add()的合体~\n\n\ttransaction.hide()\n\t//隐藏当前的Fragment，仅仅是设为不可见，并不会销毁\n\n\ttransaction.show()\n\t//显示之前隐藏的Fragment\n\n\tdetach()\n\t//当fragment被加入到回退栈的时候，该方法与*remove()*的作用是相同的，\n\t//反之，该方法只是将fragment从视图中移除，\n\t//之后仍然可以通过*attach()*方法重新使用fragment，\n\t//而调用了*remove()*方法之后，\n\t//不仅将Fragment从视图中移除，fragment还将不再可用。\n\n\tattach()\n\t//重建view视图，附加到UI上并显示。\n\n\ttransatcion.commit()\n\t//提交一个事务\n\n```\n\n##管理Fragment回退栈\n* 跟踪回退栈状态\n\t* 我们通过实现*``OnBackStackChangedListener``*接口来实现回退栈状态跟踪，具体如下\n \t```\n \tpublic class XXX implements FragmentManager.OnBackStackChangedListener \n\n \t/** 实现接口所要实现的方法 **/\n\n\t@Override\n    public void onBackStackChanged() {\n        //do whatevery you want\n    }\n\n    /** 设置回退栈监听接口 **／\n    getSupportFragmentManager().addOnBackStackChangedListener(this);\n\n\n \t```\n\t\t\n* 管理回退栈\n\t* ``FragmentTransaction.addToBackStack(String)`` *--将一个刚刚添加的Fragment加入到回退栈中*\n\t* ``getSupportFragmentManager().getBackStackEntryCount()`` *－获取回退栈中实体数量*\n\t* ``getSupportFragmentManager().popBackStack(String name, int flags)`` *－根据name立刻弹出栈顶的fragment*\n\t* ``getSupportFragmentManager().popBackStack(int id, int flags)`` *－根据id立刻弹出栈顶的fragment*\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/Git操作.md",
    "content": "# Git 操作\n\n## git 命令\n\n* 创建本地仓库\n\n```\ngit init\n```\n\n* 获取远程仓库\n\n```\ngit clone [url]\n例：git clone https://github.com/you/yourpro.git\n```\n\n* 创建远程仓库\n\n```\n// 添加一个新的 remote 远程仓库\ngit remote add [remote-name] [url]\n例：git remote add origin https://github.com/you/yourpro.git\norigin：相当于该远程仓库的别名\n\n// 列出所有 remote 的别名\ngit remote\n\n// 列出所有 remote 的 url\ngit remote -v\n\n// 删除一个 renote\ngit remote rm [name]\n\n// 重命名 remote\ngit remote rename [old-name] [new-name]\n```\n\n* 从本地仓库中删除\n\n```\ngit rm file.txt         // 从版本库中移除，删除文件\ngit rm file.txt -cached // 从版本库中移除，不删除原始文件\ngit rm -r xxx           // 从版本库中删除指定文件夹\n```\n\n* 从本地仓库中添加新的文件\n\n```\ngit add .               // 添加所有文件\ngit add file.txt        // 添加指定文件\n```\n\n* 提交，把缓存内容提交到 HEAD 里\n\n```\ngit commit -m \"注释\"\n```\n\n* 撤销\n\n```\n// 撤销最近的一个提交.\ngit revert HEAD\n\n// 取消 commit + add\ngit reset --mixed\n\n// 取消 commit\ngit reset --soft\n\n// 取消 commit + add + local working\ngit reset --hard\n```\n\n* 把本地提交 push 到远程服务器\n\n```\ngit push [remote-name] [loca-branch]:[remote-branch]\n例：git push origin master:master\n```\n\n* 查看状态\n\n```\ngit status\n```\n\n* 从远程库中下载新的改动\n\n```\ngit fetch [remote-name]/[branch]\n```\n\n* 合并下载的改动到分支\n\n```\ngit merge [remote-name]/[branch]\n```\n\n* 从远程库中下载新的改动\n\n```\npull = fetch + merge\n\ngit pull [remote-name] [branch]\n例：git pull origin master\n```\n\n* 分支\n\n```\n// 列出分支\ngit branch\n\n// 创建一个新的分支\ngit branch (branch-name)\n\n// 删除一个分支\ngit branch -d (branch-nam)\n\n// 删除 remote 的分支\ngit push (remote-name) :(remote-branch)\n```\n\n* 切换分支\n\n```\n// 切换到一个分支\ngit checkout [branch-name]\n\n// 创建并切换到该分支\ngit checkout -b [branch-name]\n```\n\n##与github建立ssh通信，让Git操作免去输入密码的繁琐。\n*   首先呢，我们先建立ssh密匙。\n> ssh key must begin with 'ssh-ed25519', 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'.  -- from github\n\n    根据以上文段我们可以知道github所支持的ssh密匙类型，这里我们创建ssh-rsa密匙。\n    在command line 中输入以下指令:``ssh-keygen -t rsa``去创建一个ssh-rsa密匙。如果你并不需要为你的密匙创建密码和修改名字，那么就一路回车就OK，如果你需要，请您自行Google翻译，因为只是英文问题。\n>$ ssh-keygen -t rsa\nGenerating public/private rsa key pair.\n//您可以根据括号中的路径来判断你的.ssh文件放在了什么地方\nEnter file in which to save the key (/c/Users/Liang Guan Quan/.ssh/id_rsa):\n\n* 到 https://github.com/settings/keys 这个地址中去添加一个新的SSH key，然后把你的xx.pub文件下的内容文本都复制到Key文本域中，然后就可以提交了。\n* 添加完成之后 我们用``ssh git@github.com`` 命令来连通一下github，如果你在response里面看到了你github账号名，那么就说明配置成功了。  *let's enjoy github ;)*\n\n\n## gitignore\n---\n\n在本地仓库根目录创建 .gitignore 文件。Win7 下不能直接创建，可以创建 \".gitignore.\" 文件，后面的标点自动被忽略；\n\n```\n/.idea          // 过滤指定文件夹\n/fd/*           // 忽略根目录下的 /fd/ 目录的全部内容；\n*.iml           // 过滤指定的所有文件\n!.gitignore     // 不忽略该文件\n```\n\n"
  },
  {
    "path": "Part1/Android/Handler内存泄漏分析及解决.md",
    "content": "#Handler内存泄漏分析及解决\n---\n\n###一、介绍\n\n首先，请浏览下面这段handler代码：\n\n```\npublic class SampleActivity extends Activity {\n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ... \n    }\n  }\n}\n```\n\n在使用handler时，这是一段很常见的代码。但是，它却会造成严重的内存泄漏问题。在实际编写中，我们往往会得到如下警告：\n\n```\n ⚠ In Android, Handler classes should be static or leaks might occur.\n\n```\n\n###二、分析\n\n1、 Android角度\n\n当Android应用程序启动时，framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue，并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件，例如Activity生命周期方法调用、button点击等，这些消息都会被添加到消息队列中并被逐个处理。\n\n另外，主线程的Looper对象会伴随该应用程序的整个生命周期。\n\n然后，当主线程里，实例化一个Handler对象后，它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用，所以当Looper来处理消息时，会据此回调[Handler#handleMessage(Message)]方法来处理消息。\n\n2、 Java角度\n\n在java里，非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是，静态内部类却不会。\n\n###三、泄漏来源\n\n请浏览下面一段代码：\n\n```\npublic class SampleActivity extends Activity {\n\n  private final Handler mLeakyHandler = new Handler() {\n    @Override\n    public void handleMessage(Message msg) {\n      // ...\n    }\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mLeakyHandler.postDelayed(new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n    }, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n当activity结束(finish)时，里面的延时消息在得到处理前，会一直保存在主线程的消息队列里持续10分钟。而且，由上文可知，这条消息持有对handler的引用，而handler又持有对其外部类（在这里，即SampleActivity）的潜在引用。这条引用关系会一直保持直到消息得到处理，从而，这阻止了SampleActivity被垃圾回收器回收，同时造成应用程序的泄漏。\n\n<<<<<<< HEAD\n注意，上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。\n\n=======\n注意，上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。\n\n>>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b\n###四、泄漏解决方案\n\n首先，上面已经明确了内存泄漏来源：\n\n只要有未处理的消息，那么消息会引用handler，非静态的handler又会引用外部类，即Activity，导致Activity无法被回收，造成泄漏；\n\nRunnable类属于非静态匿名类，同样会引用外部类。\n\n为了解决遇到的问题，我们要明确一点：静态内部类不会持有对外部类的引用。所以，我们可以把handler类放在单独的类文件中，或者使用静态内部类便可以避免泄漏。\n\n另外，如果想要在handler内部去调用所在的外部类Activity，那么可以在handler内部使用弱引用的方式指向所在Activity，这样统一不会导致内存泄漏。\n\n对于匿名类Runnable，同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。\n\n```\npublic class SampleActivity extends Activity {\n\n  /**\n   * Instances of static inner classes do not hold an implicit\n   * reference to their outer class.\n   */\n  private static class MyHandler extends Handler {\n    private final WeakReference<SampleActivity> mActivity;\n\n    public MyHandler(SampleActivity activity) {\n      mActivity = new WeakReference<SampleActivity>(activity);\n    }\n\n    @Override\n    public void handleMessage(Message msg) {\n      SampleActivity activity = mActivity.get();\n      if (activity != null) {\n        // ...\n      }\n    }\n  }\n\n  private final MyHandler mHandler = new MyHandler(this);\n\n  /**\n   * Instances of anonymous classes do not hold an implicit\n   * reference to their outer class when they are \"static\".\n   */\n  private static final Runnable sRunnable = new Runnable() {\n      @Override\n      public void run() { /* ... */ }\n  };\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Post a message and delay its execution for 10 minutes.\n    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);\n\n    // Go back to the previous Activity.\n    finish();\n  }\n}\n```\n\n###五、小结\n\n<<<<<<< HEAD\n虽然静态类与非静态类之间的区别并不大，但是对于Android开发者而言却是必须理解的。至少我们要清楚，如果一个内部类实例的生命周期比Activity更长，那么我们千万不要使用非静态的内部类。最好的做法是，使用静态内部类，然后在该类里使用弱引用来指向所在的Activity。\n\n原文链接：\n\n[http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820)\n=======\n虽然静态类与非静态类之间的区别并不大，但是对于Android开发者而言却是必须理解的。至少我们要清楚，如果一个内部类实例的生命周期比Activity更长，那么我们千万不要使用非静态的内部类。最好的做法是，使用静态内部类，然后在该类里使用弱引用来指向所在的Activity。\n\n原文链接：\n\n[http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820)\n>>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b\n"
  },
  {
    "path": "Part1/Android/Listview详解.md",
    "content": "#ListView详解\n---\n直接继承自AbsListView，AbsListView继承自AdapterView，AdapterView又继承自ViewGroup。\n\nAdpater在ListView和数据源之间起到了一个桥梁的作用\n\n###RecycleBin机制\n\nRecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。RecycleBin是AbsListView的一个内部类。\n\n* RecycleBin当中使用mActiveViews这个数组来存储View，调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews中。\n* mActiveViews当中所存储的View，一旦被获取了之后就会从mActiveViews当中移除，下次获取同样位置的时候将会返回null，所以mActiveViews不能被重复利用。\n* addScrapView()用于将一个废弃的View进行缓存，该方法接收一个View参数，当有某个View确定要废弃掉的时候（比如滚动出了屏幕）就应该调用这个方法来对View进行缓存，RecycleBin当中使用mScrapV\n* iews和mCurrentScrap这两个List来存储废弃View。\n* getScrapView 用于从废弃缓存中取出一个View，这些废弃缓存中的View是没有顺序可言的，因此getScrapView()方法中的算法也非常简单，就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。\n* 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项，而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。\n\nView的流程分三步，onMeasure()用于测量View的大小，onLayout()用于确定View的布局，onDraw()用于将View绘制到界面上。\n"
  },
  {
    "path": "Part1/Android/MVC,MVP,MVVM的区别.md",
    "content": "#MVC,MVP,MVVM的区别\n---\n\n\n#MVC\n软件可以分为三部分\n\n* 视图（View）:用户界面\n* 控制器（Controller）:业务逻辑\n* 模型（Model）:数据保存\n\n各部分之间的通信方式如下：\n\n1. View传送指令到Controller\n2. Controller完成业务逻辑后，要求Model改变状态\n3. Model将新的数据发送到View，用户得到反馈\n\nTips：所有的通信都是单向的。\n\n#互动模式\n接受用户指令时，MVC可以分为两种方式。一种是通过View接受指令，传递给Controller。\n\n另一种是直接通过Controller接受指令\n\n\n#MVP\n\nMVP模式将Controller改名为Presenter，同时改变了通信方向。\n\n1. 各部分之间的通信，都是双向的\n2. View和Model不发生联系，都通过Presenter传递\n3. View非常薄，不部署任何业务逻辑，称为\"被动视图\"(Passive View)，即没有任何主动性，而Presenter非常厚，所有逻辑都部署在那里。\n\n\n#MVVM\n\nMVVM模式将Presenter改名为ViewModel，基本上与MVP模式完全一致。\n\n唯一的区别是，它采用双向绑定(data-binding)：View的变动，自动反映在ViewModel，反之亦然。\n\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/MVP.md",
    "content": "#MVP\n---\n###为什么需要MVP\n\n1. 尽量简单\n\t大部分的安卓应用只使用View-Model结构,程序员现在更多的是和复杂的View打交道而不是解决业务逻辑。当你在应用中只使用Model-View时，到最后，你会发现“所有的事物都被连接到一起”。复杂的任务被分成细小的任务，并且很容易解决。越小的东西，bug越少，越容易debug，更好测试。在MVP模式下的View层将会变得简单，所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。\n2. 后台任务\n\t当你编写一个Actviity、Fragment、自定义View的时候，你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样，你的Task不再和Activity联系在一起，这既不会导致内存泄露，也不依赖于Activity的重建。\n\t\n###优缺点\n\n优点：\n\n1. 降低耦合度，实现了Model和View真正的完全分离，可以修改View而不影响Modle\n2. 模块职责划分明显，层次清晰\n3. 隐藏数据\n4. Presenter可以复用，一个Presenter可以用于多个View，而不需要更改Presenter的逻辑（当然是在View的改动不影响业务逻辑的前提下）\n5. 利于测试驱动开发。以前的Android开发是难以进行单元测试的（虽然很多Android开发者都没有写过测试用例，但是随着项目变得越来越复杂，没有测试是很难保证软件质量的；而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧），在使用MVP的项目中Presenter对View是通过接口进行，在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象，这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中，单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。\n6. View可以进行组件化。在MVP当中，View不依赖Model。这样就可以让View从特定的业务场景中脱离出来，可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。\n7. 代码灵活性\n\n缺点：\n\n1. Presenter中除了应用逻辑以外，还有大量的View->Model，Model->View的手动同步逻辑，造成Presenter比较笨重，维护起来会比较困难。\n2. 由于对视图的渲染放在了Presenter中，所以视图和Presenter的交互会过于频繁。\n3. 如果Presenter过多地渲染了视图，往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更，那么Presenter也需要变更了。\n4. 额外的代码复杂度及学习成本。\n\n**在MVP模式里通常包含4个要素：**\n\n1. View :负责绘制UI元素、与用户进行交互(在Android中体现为Activity);\n2. View interface :需要View实现的接口，View通过View interface与Presenter进行交互，降低耦合，方便进行单元测试;\n3. Model :负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);\n4. Presenter :作为View与Model交互的中间纽带，处理与用户交互的负责逻辑。"
  },
  {
    "path": "Part1/Android/Recyclerview和Listview的异同.md",
    "content": "#RecyclerView和ListView的异同\n---\n\n* ViewHolder是用来保存视图引用的类，无论是ListView亦或是RecyclerView。只不过在ListView中，ViewHolder需要自己来定义，且这只是一种推荐的使用方式，不使用当然也可以，这不是必须的。只不过不使用ViewHolder的话，ListView每次getView的时候都会调用findViewById(int)，这将导致ListView性能展示迟缓。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须，尽管实现起来稍显复杂，但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。\n* 我们知道ListView只能在垂直方向上滚动，Android API没有提供ListView在水平方向上面滚动的支持。或许有多种方式实现水平滑动，但是请想念我，ListView并不是设计来做这件事情的。但是RecyclerView相较于ListView，在滚动上面的功能扩展了许多。它可以支持多种类型列表的展示要求，主要如下：\n\n\t1. LinearLayoutManager，可以支持水平和竖直方向上滚动的列表。\n\t2. StaggeredGridLayoutManager，可以支持交叉网格风格的列表，类似于瀑布流或者Pinterest。\n\t3. GridLayoutManager，支持网格展示，可以水平或者竖直滚动，如展示图片的画廊。\n\n* 列表动画是一个全新的、拥有无限可能的维度。起初的Android API中，删除或添加item时，item是无法产生动画效果的。后面随着Android的进化，Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。\n相比较于ListView，RecyclerView.ItemAnimator则被提供用于在RecyclerView添加、删除或移动item时处理动画效果。同时，如果你比较懒，不想自定义ItemAnimator，你还可以使用DefaultItemAnimator。\n\n* ListView的Adapter中，getView是最重要的方法，它将视图跟position绑定起来，是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。RecyclerView也有这个特性，RecyclerView.AdapterDataObserver就是这个观察者。ListView有三个Adapter的默认实现，分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而，RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现的，我们必须采取措施将数据提供给Adapter，正如BaseAdapter对ListView所做的那样。\n* 在ListView中如果我们想要在item之间添加间隔符，我们只需要在布局文件中对ListView添加如下属性即可：\n\n\t```\n\t android:divider=\"@android:color/transparent\"\n\t android:dividerHeight=\"5dp\"\n\t```\n* ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来探测触摸事件。它虽然增加了实现的难度，但是却给予开发人员拦截触摸事件更多的控制权限。\n* ListView可以设置选择模式，并添加MultiChoiceModeListener，如下所示：\n\n\t```\n\tlistView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);\nlistView.setMultiChoiceModeListener(new MultiChoiceModeListener() {\n    public boolean onCreateActionMode(ActionMode mode, Menu menu) { ... }\n    public void onItemCheckedStateChanged(ActionMode mode, int position,\nlong id, boolean checked) { ... }\n    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_item_delete_crime:\n            CrimeAdapter adapter = (CrimeAdapter)getListAdapter();\n            CrimeLab crimeLab = CrimeLab.get(getActivity());\n            for (int i = adapter.getCount() - 1; i >= 0; i--) {\n                if (getListView().isItemChecked(i)) {\n                    crimeLab.deleteCrime(adapter.getItem(i));\n                }\n          }\n        mode.finish();\n        adapter.notifyDataSetChanged();\n        return true;\n        default:\n            return false;\n}\n    public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... }\n    public void onDestroyActionMode(ActionMode mode) { ... }\n});\n\t```\n\t而RecyclerView则没有此功能。\n\t\n[http://www.cnblogs.com/littlepanpc/p/4497290.html](http://www.cnblogs.com/littlepanpc/p/4497290.html)"
  },
  {
    "path": "Part1/Android/SurfaceView.md",
    "content": "##为什么要使用SurfaceView来实现动画？\n###因为View的绘图存在以下缺陷：\n1. View缺乏双缓冲机制\n2. 当程序需要更新View上的图像时，程序必须重绘View上显示的整张图片\n3. 新线程无法直接更新View组件\n\n\n\n##SurfaceView的绘图机制\n* 一般会与SurfaceView结合使用\n* 调用SurfaceView的getHolder()方法即可获得SurfaceView关联的SurfaceHolder\n\n##SurfaceHolder提供了如下方法来获取Canvas对象\n1. Canvas lockCanvas():锁定整个SurfaceView对象，获取该Surface上的Canvas\n2. Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域，获取该Surface上的Canvas\n3. unlockCanvasAndPost(canvas):释放绘图、提交所绘制的图形，需要注意，当调用SurfaceHolder上的unlockCanvasAndPost方法之后，该方法之前所绘制的图形还处于缓冲之中，下一次lockCanvas()方法锁定的区域可能会“遮挡”它\n\n\n```\n\n\tpublic class SurfaceViewTest extends Activity\n{\n\t// SurfaceHolder负责维护SurfaceView上绘制的内容\n\tprivate SurfaceHolder holder;\n\tprivate Paint paint;\n\n\t@Override\n\tpublic void onCreate(Bundle savedInstanceState)\n\t{\n\t\tsuper.onCreate(savedInstanceState);\n\t\tsetContentView(R.layout.main);\n\t\tpaint = new Paint();\n\t\tSurfaceView surface = (SurfaceView) findViewById(R.id.show);\n\t\t// 初始化SurfaceHolder对象\n\t\tholder = surface.getHolder();\n\t\tholder.addCallback(new Callback()\n\t\t{\n\t\t\t@Override\n\t\t\tpublic void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,\n\t\t\t\t\tint arg3)\n\t\t\t{\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void surfaceCreated(SurfaceHolder holder)\n\t\t\t{\n\t\t\t\t// 锁定整个SurfaceView\n\t\t\t\tCanvas canvas = holder.lockCanvas();\n\t\t\t\t// 绘制背景\n\t\t\t\tBitmap back = BitmapFactory.decodeResource(\n\t\t\t\t\tSurfaceViewTest.this.getResources()\n\t\t\t\t\t, R.drawable.sun);\n\t\t\t\t// 绘制背景\n\t\t\t\tcanvas.drawBitmap(back, 0, 0, null);\n\t\t\t\t// 绘制完成，释放画布，提交修改\n\t\t\t\tholder.unlockCanvasAndPost(canvas);\n\t\t\t\t// 重新锁一次，\"持久化\"上次所绘制的内容\n\t\t\t\tholder.lockCanvas(new Rect(0, 0, 0, 0));\n\t\t\t\tholder.unlockCanvasAndPost(canvas);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void surfaceDestroyed(SurfaceHolder holder)\n\t\t\t{\n\t\t\t}\n\t\t});\n\t\t// 为surface的触摸事件绑定监听器\n\t\tsurface.setOnTouchListener(new OnTouchListener()\n\t\t{\n\t\t\t@Override\n\t\t\tpublic boolean onTouch(View source, MotionEvent event)\n\t\t\t{\n\t\t\t\t// 只处理按下事件\n\t\t\t\tif (event.getAction() == MotionEvent.ACTION_DOWN)\n\t\t\t\t{\n\t\t\t\t\tint cx = (int) event.getX();\n\t\t\t\t\tint cy = (int) event.getY();\n\t\t\t\t\t// 锁定SurfaceView的局部区域，只更新局部内容\n\t\t\t\t\tCanvas canvas = holder.lockCanvas(new Rect(cx - 50,\n\t\t\t\t\t\t\tcy - 50, cx + 50, cy + 50));\n\t\t\t\t\t// 保存canvas的当前状态\n\t\t\t\t\tcanvas.save();\n\t\t\t\t\t// 旋转画布\n\t\t\t\t\tcanvas.rotate(30, cx, cy);\n\t\t\t\t\tpaint.setColor(Color.RED);\n\t\t\t\t\t// 绘制红色方块\n\t\t\t\t\tcanvas.drawRect(cx - 40, cy - 40, cx, cy, paint);\n\t\t\t\t\t// 恢复Canvas之前的保存状态\n\t\t\t\t\tcanvas.restore();\n\t\t\t\t\tpaint.setColor(Color.GREEN);\n\t\t\t\t\t// 绘制绿色方块\n\t\t\t\t\tcanvas.drawRect(cx, cy, cx + 40, cy + 40, paint);\n\t\t\t\t\t// 绘制完成，释放画布，提交修改\n\t\t\t\t\tholder.unlockCanvasAndPost(canvas);\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t}\n\t}\n\t\n ```\n\n上面的程序为SurfaceHolder添加了一个CallBack实例，该Callback中定义了如下三个方法：\n\n* void surfaceChanged(SurfaceHolder holder, int format, int width, int height):当一个surface的格式或大小发生改变时回调该方法。\n* void surfaceCreated(SurfaceHolder holder):当surface被创建时回调该方法\n* void surfaceDestroyed(SurfaceHolder holder):当surface将要被销毁时回调该方法\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/Zygote和System进程的启动过程.md",
    "content": "#Zygote和System进程的启动过程\n---\n\n##init脚本的启动\n---\n\n```\n+------------+    +-------+   +-----------+\n|Linux Kernel+--> |init.rc+-> |app_process|\n+------------+    +-------+   +-----------+\n               create and public          \n                 server socket\n```\n\nlinux内核加载完成后，运行init.rc脚本\n\n```\nservice zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666\n```\n\n* /system/bin/app_process Zygote服务启动的进程名\n* --start-system-server 表明Zygote启动完成之后，要启动System进程。\n* socket zygote stream 666 在Zygote启动时，创建一个权限为666的socket。此socket用来请求Zygote创建新进程。socket的fd保存在名称为“ANDROID_SOCKET_zygote”的环境变量中。\n\n##Zygote进程的启动过程\n---\n\n```\n               create rumtime                            \n  +-----------+            +----------+                  \n  |app_process+----------> |ZygoteInit|                  \n  +-----------+            +-----+----+                  \n                                 |                       \n                                 |                       \n                                 | registerZygoteSocket()\n                                 |                       \n   +------+  startSystemServer() |                       \n   |System| <-------+            |                       \n   +------+   fork               | runSelectLoopMode()   \n                                 |                       \n                                 v         \n```\n\n**app_process进程**\n\n---\n\n/system/bin/app_process 启动时创建了一个AppRuntime对象。通过AppRuntime对象的start方法，通过JNI调用创建了一个虚拟机实例，然后运行com.android.internal.os.ZygoteInit类的静态main方法，传递true(boolean startSystemServer)参数。\n\n**ZygoteInit类**\n\n---\n\nZygoteInit类的main方法运行时，会通过registerZygoteSocket方法创建一个供ActivityManagerService使用的server socket。然后通过调用startSystemServer方法来启动System进程。最后通过runSelectLoopMode来等待AMS的新建进程请求。\n\n1. 在registerZygoteSocket方法中，通过名为ANDROID_SOCKET_zygote的环境获取到zygote启动时创建的socket的fd，然后以此来创建server socket。\n2. 在startSystemServer方法中，通过Zygote.forkSystemServer方法创建了一个子进程，并将其用户和用户组的ID设置为1000。\n3. 在runSelectLoopMode方法中，会将之前建立的server socket保存起来。然后进入一个无限循环，在其中通过selectReadable方法，监听socket是否有数据可读。有数据则说明接收到了一个请求。\nselectReadable方法会返回一个整数值index。如果index为0，则说明这个是AMS发过来的连接请求。这时会与AMS建立一个新的socket连接，并包装成ZygoteConnection对象保存起来。如果index大于0，则说明这是AMS发过来的一个创建新进程的请求。此时会取出之前保存的ZygoteConnection对象，调用其中的runOnce方法创建新进程。调用完成后将connection删除。\n这就是Zygote处理一次AMS请求的过程。\n\n##System进程的启动\n---\n\n```\n +                                                     \n |                                                     \n |                                                     \n v fork()                                              \n +--------------+                                      \n |System Process|                                      \n +------+-------+                                      \n        |                                              \n        | RuntimeInit.zygoteInit() commonInit, zygoteInitNative                                             \n        | init1()  SurfaceFlinger, SensorServic...     \n        |                                              \n        |                                              \n        | init2() +------------+                       \n        +-------> |ServerThread|                       \n        |         +----+-------+                       \n        |              |                               \n        |              | AMS, PMS, WMS...              \n        |              |                               \n        |              |                               \n        |              |                               \n        v              v               \n```\n\nSystem进程是在ZygoteInit的handleSystemServerProcess中开始启动的。\n\n1. 首先，因为System进程是直接fork Zygote进程的，所以要先通过closeServerSocket方法关掉server socket。\n2. 调用RuntimeInit.zygoteInit方法进一步启动System进程。在zygoteInit中，通过commonInit方法设置时区和键盘布局等通用信息，然后通过zygoteInitNative方法启动了一个Binder线程池。最后通过invokeStaticMain方法调用SystemServer类的静态Main方法。\n3. SystemServer类的main通过JNI调用cpp实现的init1方法。在init1方法中，会启动各种以C++开发的系统服务（例如SurfaceFlinger和SensorService）。然后回调ServerServer类的init2方法来启动以Java开发的系统服务。\n4. 在init2方法中，首先会新建名为\"android.server.ServerThread\"的ServerThread线程，并调用其start方法。然后在该线程中启动各种Service（例如AMS，PMS，WMS等）。启动的方式是调用对应Service类的静态main方法。\n5. 首先，AMS会被创建，但未注册到ServerManager中。然后PMS被创建，AMS这时候才注册到ServerManager中。然后到ContentService、WMS等。\n注册到ServerManager中时会制定Service的名字，其后其他进程可以通过这个名字来获取到Binder Proxy对象，以访问Service提供的服务。\n6. 执行到这里，System就将系统的关键服务启动起来了，这时候其他进程便可利用这些Service提供的基础服务了。\n7. 最后会调用ActivityManagerService的systemReady方法，在该方法里会启动系统界面以及Home程序。\n\n##Android进程启动\n\n---\n\n```\n   +----------------------+       +-------+      +----------+   +----------------+   +-----------+                         \n   |ActivityManagerService|       |Process|      |ZygoteInit|   |ZygoteConnection|   |RuntimeInit|                         \n   +--------------+-------+       +---+---+      +-----+----+   +-----------+----+   +------+----+                         \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n startProcessLocked()                 |                |                    |               |                              \n+---------------> |                   |                |                    |               |                              \n                  |  start()          |                |                    |               |                              \n                  |  \"android.app.ActivityThread\"      |                    |               |                              \n                  +-----------------> |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |openZygoteSocketIfNeeded()           |               |                              \n                  |                   +------+         |                    |               |                              \n                  |                   |      |         |                    |               |                              \n                  |                   | <----+         |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |sZygoteWriter.write(arg)             |               |                              \n                  |                   +------+         |                    |               |                              \n                  |                   |      |         |                    |               |                              \n                  |                   |      |         |                    |               |                              \n                  |                   | <----+         |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   +--------------> |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |runSelectLoopMode() |               |                              \n                  |                   |                +-----------------+  |               |                              \n                  |                   |                |                 |  |               |                              \n                  |                   |                | <---------------+  |               |                              \n                  |                   |                |   acceptCommandPeer()              |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |  runOnce()         |               |                              \n                  |                   |                +------------------> |               |                              \n                  |                   |                |                    |forkAndSpecialize()                           \n                  |                   |                |                    +-------------+ |                              \n                  |                   |                |                    |             | |                              \n                  |                   |                |                    | <-----------+ |                              \n                  |                   |                |                    |  handleChildProc()                           \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |                    | zygoteInit()  |                              \n                  |                   |                |                    +-------------> |                              \n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |in^okeStaticMain()            \n                  |                   |                |                    |               +---------------->             \n                  |                   |                |                    |               |(\"android.app.ActivityThread\")\n                  |                   |                |                    |               |                              \n                  |                   |                |                    |               |                              \n                  +                   +                +                    +               +                            \n```\n\n* AMS向Zygote发起请求（通过之前保存的socket），携带各种参数，包括“android.app.ActivityThread”。\n* Zygote进程fork自己，然后在新Zygote进程中调用RuntimeInit.zygoteInit方法进行一系列的初始化（commonInit、Binder线程池初始化等）。\n* 新Zygote进程中调用ActivityThread的main函数，并启动消息循环。"
  },
  {
    "path": "Part1/Android/事件分发机制.md",
    "content": "# 事件分发机制\n---\n\n\n* 对于一个根ViewGroup来说,发生点击事件首先调用dispatchTouchEvent\n* 如果这个ViewGroup的onIterceptTouchEvent返回true就表示它要拦截当前事件,接着这个ViewGroup的onTouchEvent就会被调用.如果onIterceptTouchEvent返回false,那么就会继续向下调用子View的dispatchTouchEvent方法\n* 当一个View需要处理事件的时候,如果它没有设置onTouchListener,那么直接调用onTouchEvent.如果设置了Listenter 那么就要看Listener的onTouch方法返回值.为true就不调,为false就调onTouchEvent\n* View的默认实现会在onTouchEvent里面把touch事件解析成Click之类的事件\n* 点击事件传递顺序 Activity -> Window -> View\n* 一旦一个元素拦截了某事件,那么一个事件序列里面后续的Move,Down事件都会交给它处理.并且它的onInterceptTouchEvent不会再调用\n* View的onTouchEvent默认都会消耗事件,除非它的clickable和longClickable都是false(不可点击),但是enable属性不会影响"
  },
  {
    "path": "Part1/Android/开源框架源码分析.md",
    "content": "#框架源码分析\n---\n\n###Retrofit\n---\n\n\n###EventBus\n---\n\n###Glide\n---\n\n"
  },
  {
    "path": "Part1/Android/插件化技术学习.md",
    "content": "###Android动态加载dex技术初探\n\n[http://blog.csdn.net/u013478336/article/details/50734108](http://blog.csdn.net/u013478336/article/details/50734108)\n\nAndroid使用Dalvik虚拟机加载可执行程序，所以不能直接加载基于class的jar，而是需要将class转化为dex字节码。\n\nAndroid支持动态加载的两种方式是：DexClassLoader和PathClassLoader，DexClassLoader可加载jar/apk/dex，且支持从SD卡加载；PathClassLoader据说只能加载已经安装在Android系统内APK文件。\n\n###Android插件化基础\n\nAndroid简单来说就是如下操作：\n\n* 开发者将插件代码封装成Jar或者APK\n* 宿主下载或者从本地加载Jar或者APK到宿主中\n* 将宿主调用插件中的算法或者Android特定的Class（如Activity）\n\n###插件化开发—动态加载技术加载已安装和未安装的apk\n\n[http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io](http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io)\n为什么引入动态加载技术？\n\n* 一个应用程序dex文件的方法数最大不能超过65536个\n* 可以让应用程序实现插件化、插拔式结构，对后期维护有益\n\n什么是动态加载技术\n\n动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件)，再通过反射获得该apk、dex、jar内部的资源（class、图片、color等等）进而供宿主app使用。\n\n关于动态加载使用的类加载器\n\n* PathClassLoader - 只能加载已经安装的apk，即/data/app目录下的apk。\n* DexClassLoader  - 能加载手机中未安装的apk、jar、dex，只要能在找到对应的路径。\n\n\n\n\n\n\n\n\n\n#插件化技术学习\n---\n原因：\n\n各大厂商都碰到了AndroidNative平台的瓶颈：\n\n1. 从技术上讲，业务逻辑的复杂代码急剧膨胀，各大厂商陆续触到65535方法数的天花板；同时，对模块热更新提出了更高的要求。\n2. 在业务层面上，功能模块的解耦以及维护团队的分离也是大势所趋。\n\n插件化技术主要解决两个问题：\n\n1. 代码加载\n2. 资源加载\n\n###代码加载\n类的加载可以使用Java的ClassLoader机制，还需要组件生命周期管理。\n\n###资源加载\n用AssetManager的隐藏方法addAssetPath。\n\n##Android插件化原理解析——Hook机制之动态代理 \n\n使用代理机制进行API Hook进而达到方法增强。\n\n静态代理\n\n动态代理：可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类。\n\n###代理Hook\n\n如果我们自己创建代理对象，然后把原始对象替换为我们的代理对象，就可以在这个代理对象中为所欲为了；修改参数，替换返回值，称之为Hook。\n\n整个Hook过程简要总结如下：\n\n1. 寻找Hook点，原则是静态变量或者单例对象，尽量Hook public的对象和方法，非public不保证每个版本都一样，需要适配。\n2. 选择合适的代理方式，如果是接口可以用动态代理；如果是类可以手动写代理也可以使用cglib。\n3. 偷梁换柱－用代理对象替换原始对象\n\n##Android插件化原理解析——Hook机制之Binder Hook \n\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/查漏补缺.md",
    "content": "#查漏补缺\n---\n\n请分析一张400*500尺寸的PNG图片加载到程序中占用内存中的大小\n\n[http://m.blog.csdn.net/article/details?id=7856519](http://m.blog.csdn.net/article/details?id=7856519)\n"
  },
  {
    "path": "Part1/Android/热修复技术.md",
    "content": "#热修复技术\n---\n\nAPP提早发出去的包，如果出现客户端的问题，实在是干着急，覆水难收。因此线上修复方案迫在眉睫。\n\n###概述\n\n基于Xposed中的思想，通过修改c层的Method实例描述，来实现更改与之对应的java方法的行为，从而达到修复的目的。\n\n###Xposed\n\n诞生于XDA论坛，类似一个应用平台，不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed需要以越狱为前提，像是iOS中的cydia。\n\nXposed可以修改任何程序的任何java方法（需root），github上提供了XposedInstaller，是一个android app。提供很多framework层，应用层级的程序。开发者可以为其开发一些系统或应用方面的插件，自定义android系统，它甚至可以做动态权限管理（XposedMods）。\n\n###Android系统启动与应用启动\n\nZygote进程是Android手机系统启动后，常驻的一个名为‘受精卵’的进程。\n\n* zygote的启动实现脚本在/init.rc文件中\n* 启动过程中执行的二进制文件在/system/bin/app_process\n\n任何应用程序启动时，会从zygote进程fork出一个新的进程。并装载一些必要的class，invoke一些初始化方法。这其中包括像：\n\n* ActivityThread\n* ServiceThread\n* ApplicationPackageManager\n\n等应用启动中必要的类，触发必要的方法，比如：handleBindApplication，将此进程与对应的应用绑定的初始化方法；同时，会将zygote进程中的dalvik虚拟机实例复制一份，因此每个应用程序进程都有自己的dalvik虚拟机实例；会将已有Java运行时加载到进程中；会注册一些android核心类的jni方法到虚拟机中，支撑从c到java的启动过程。\n\n###Xposed做了手脚\n\nXposed在这个过程改写了app_process(源码在Xposed : a modified app_process binary)，替换/system/bin/app_process这个二进制文件。然后做了两个事：\n\n1. 通过Xposed的hook技术，在上述过程中，对上面提到的那些加载的类的方法hook。\n2. 加载XposedBridge.jar\n\n这时hook必要的方法是为了方便开发者为它开发插件，加载XposedBridge.jar是为动态hook提供了基础。在这个时候加载它意味着，所有的程序在启动时，都可以加载这个jar（因为上面提到的fork过程）。结合hook技术，从而达到了控制所有程序的所有方法。\n\n为获得/system/bin/目录的读写权限，因而需要以root为前提。\n\n###Xposed的hook思想\n\n那么Xposed是怎么hook java方法的呢？要从XposedBridge看起，重点在\nXposedBridge.hookmethod(原方法的Member对象，含有新方法的XC_MethodHook对象)；，这里会调到\n\n```\nprivate native synchronized static void hookMethodNative(Member method, Class<?> declaringClass, int slot, Object additionalInfo);\n```\n\n这个native的方法，通过这个方法，可以让所hook的方法，转向native层的一个c方法。如何做到？\n\n```\nWhen a transmit from java to native occurs, dvm sets up a native stack.\nIn dvmCallJNIMethod(), dvmPlatformInvoke is used to call the native method(signature in Method.insns).\n```\n\n在jni这个中间世界里，类型数据由jni表来沟通java和c的世界；方法由c++指针结合DVM*系(如dvmSlotToMethod,dvmDecodeIndirectRef等方法)的api方法，操作虚拟机，从而实现java方法与c方法的世界。\n\n那么hook的过程是这样：首先通过dexclassload来load所要hook的方法，分析类后，进c层，见代码XposedBridge_hookMethodNative方法，拿到要hook的Method类，然后通过dvmslotTomethod方法获取Method*指针，\n\n```\nMethod* method = dvmSlotToMethod(declaredClass, slot);\n```\n\ndeclaredClass就是所hook方法所在的类，对应的jobject。slot是Method类中，描述此java对象在vm中的索引；那么通过这个方法，我们就获取了c层的Method指针,通过\n\n```\nSET_METHOD_FLAG(method, ACC_NATIVE);\n```\n\n将该方法标记为一个native方法，然后通过\n\n```\nmethod->nativeFunc = &hookedMethodCallback;\n```\n\n定向c层方法到hookedMethodCallback，这样当被hook的java方法执行时，就会调到c层的hookedMethodCallback方法。\n\n通过meth->nativeFunc重定向MethodCallBridge到hookedMethodCallback这个方法上，控制这个c++指针是无视java的private的。\n\n另外，在method结构体中有\n\n```\nmethod->insns = (const u2*) hookInfo;\n```\n\n用insns指向替换成为的方法，以便hookedMethodCallback可以获取真正期望执行的java方法。\n\n现在所有被hook的方法，都指向了hookedMethodCallbackc方法中，然后在此方法中实现调用替换成为的java方法。\n\n###从Xposed提炼精髓\n\n回顾Xposed，以root为必要条件，在app_process加载XposedBidge.jar，从而实现有hook所有应用的所有方法的能力；而后续动态hook应用内的方法，其实只是load了从zypote进程复制出来的运行时的这个XposedBidge.jar，然后hook而已。因此，若在一个应用范围内的hook，root不是必须的，只是单纯的加载hook的实现方法，即可修改本应用的方法。\n\n\n\n业界内也不乏通过「修改BaseDexClassLoader中的pathList，来动态加载dex」方式实现热修复。后者纯java实现，但需要hack类的优化流程，将打CLASS_ISPREVERIFIED标签的类，去除此标签，以解决类与类引用不在一个dex中的异常问题。这会放弃dex optimize对启动运行速度的优化。原则上，这对于方法数没有大到需要multidex的应用，损失更明显。而前者不触犯原有的优化流程，只点杀需要hook的方法，更为纯粹、有效。\n\n\n\n\n"
  },
  {
    "path": "Part1/Android/线程通信基础流程分析.md",
    "content": "> 老司机们都知道，Android的线程间通信就靠Handler、Looper、Message、MessageQueue这四个麻瓜兄弟了，那么，他们是怎么运作的呢？下面做一个基于主要源代码的大学生水平的分析。 [原文链接](http://anangryant.leanote.com/post/Handler%E3%80%81Looper%E3%80%81Message%E3%80%81MessageQueue%E5%88%86%E6%9E%90)\n\n##Looper(先分析这个是因为能够引出四者的关系)\n在Looper中，维持一个`Thread`对象以及`MessageQueue`，通过Looper的构造函数我们可以知道:\n```\n    private Looper(boolean quitAllowed) {\n        mQueue = new MessageQueue(quitAllowed);//传入的参数代表这个Queue是否能够被退出\n        mThread = Thread.currentThread();\n    }\n```\n`Looper`在构造函数里干了两件事情：\n1. 将线程对象指向了创建`Looper`的线程\n2. 创建了一个新的`MessageQueue`\n\n分析完构造函数之后，接下来我们主要分析两个方法:\n1. `looper.loop()`\n2. `looper.prepare()`\n\n###looper.loop()（在当前线程启动一个Message loop机制，此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系）\n```\n public static void loop() {\n        final Looper me = myLooper();//获得当前线程绑定的Looper\n        if (me == null) {\n            throw new RuntimeException(\"No Looper; Looper.prepare() wasn't called on this thread.\");\n        }\n        final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue\n\n        // Make sure the identity of this thread is that of the local process,\n        // and keep track of what that identity token actually is.\n        Binder.clearCallingIdentity();\n        final long ident = Binder.clearCallingIdentity();\n        \n        //进入死循环，不断地去取对象，分发对象到Handler中消费\n        for (;;) {\n            Message msg = queue.next(); // 不断的取下一个Message对象，在这里可能会造成堵塞。\n            if (msg == null) {\n                // No message indicates that the message queue is quitting.\n                return;\n            }\n\n            // This must be in a local variable, in case a UI event sets the logger\n            Printer logging = me.mLogging;\n            if (logging != null) {\n                logging.println(\">>>>> Dispatching to \" + msg.target + \" \" +\n                        msg.callback + \": \" + msg.what);\n            }\n            \n            //在这里，开始分发Message了\n            //至于这个target是神马？什么时候被赋值的？ \n            //我们一会分析Handler的时候就会讲到\n            msg.target.dispatchMessage(msg);\n\n            if (logging != null) {\n                logging.println(\"<<<<< Finished to \" + msg.target + \" \" + msg.callback);\n            }\n\n            // Make sure that during the course of dispatching the\n            // identity of the thread wasn't corrupted.\n            final long newIdent = Binder.clearCallingIdentity();\n            if (ident != newIdent) {\n                Log.wtf(TAG, \"Thread identity changed from 0x\"\n                        + Long.toHexString(ident) + \" to 0x\"\n                        + Long.toHexString(newIdent) + \" while dispatching to \"\n                        + msg.target.getClass().getName() + \" \"\n                        + msg.callback + \" what=\" + msg.what);\n            }\n            \n            //当分发完Message之后，当然要标记将该Message标记为 *正在使用* 啦\n            msg.recycleUnchecked();\n        }\n    }\n```\n*分析了上面的源代码，我们可以意识到，最重要的方法是：*\n1. `queue.next()`\n2. `msg.target.dispatchMessage(msg)`\n3. `msg.recycleUnchecked()`\n\n其实Looper中最重要的部分都是由`Message`、`MessageQueue`组成的有木有！这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下：\n1. `MessageQueue`：装食物的容器\n2. `Message`：被装的食物\n3. `Handler`（msg.target实际上就是`Handler`）：食物的消费者\n4. `Looper`：负责分发食物的人\n\n\n###looper.prepare()（在当前线程关联一个Looper对象）\n```\n private static void prepare(boolean quitAllowed) {\n        if (sThreadLocal.get() != null) {\n            throw new RuntimeException(\"Only one Looper may be created per thread\");\n        }\n        //在当前线程绑定一个Looper\n        sThreadLocal.set(new Looper(quitAllowed));\n    }\n```\n以上代码只做了两件事情：\n1. 判断当前线程有木有`Looper`，如果有则抛出异常（在这里我们就可以知道，Android规定一个线程只能够拥有一个与自己关联的`Looper`）。\n2. 如果没有的话，那么就设置一个新的`Looper`到当前线程。\n\n--------------\n##Handler\n由于我们使用Handler的通常性的第一步是:\n```\n Handler handler = new Handler(){\n        //你们有没有很好奇这个方法是在哪里被回调的？\n        //我也是！所以接下来会分析到哟！\n        @Override\n        public void handleMessage(Message msg) {\n            //Handler your Message\n        }\n    };\n```\n所以我们先来分析`Handler`的构造方法\n```\n//空参数的构造方法与之对应，这里只给出主要的代码，具体大家可以到源码中查看\npublic Handler(Callback callback, boolean async) {\n        //打印内存泄露提醒log\n        ....\n        \n        //获取与创建Handler线程绑定的Looper\n        mLooper = Looper.myLooper();\n        if (mLooper == null) {\n            throw new RuntimeException(\n                \"Can't create handler inside thread that has not called Looper.prepare()\");\n        }\n        //获取与Looper绑定的MessageQueue\n        //因为一个Looper就只有一个MessageQueue，也就是与当前线程绑定的MessageQueue\n        mQueue = mLooper.mQueue;\n        mCallback = callback;\n        mAsynchronous = async;\n        \n    }\n```\n*带上问题：*\n1. `Looper.loop()`死循环中的`msg.target`是什么时候被赋值的？\n2. `handler.handleMessage(msg)`在什么时候被回调的？\n\n###A1：`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的？\n要分析这个问题，很自然的我们想到从发送消息开始，无论是`handler.sendMessage(msg)`还是`handler.sendEmptyMessage(what)`，我们最终都可以追溯到以下方法\n```\npublic boolean sendMessageAtTime(Message msg, long uptimeMillis) {\n        //引用Handler中的MessageQueue\n        //这个MessageQueue就是创建Looper时被创建的MessageQueue\n        MessageQueue queue = mQueue;\n        \n        if (queue == null) {\n            RuntimeException e = new RuntimeException(\n                    this + \" sendMessageAtTime() called with no mQueue\");\n            Log.w(\"Looper\", e.getMessage(), e);\n            return false;\n        }\n        //将新来的Message加入到MessageQueue中\n        return enqueueMessage(queue, msg, uptimeMillis);\n    }\n```\n\n我们接下来分析`enqueueMessage(queue, msg, uptimeMillis)`:\n```\nprivate boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {\n        //显而易见，大写加粗的赋值啊！\n        **msg.target = this;**\n        if (mAsynchronous) {\n            msg.setAsynchronous(true);\n        }\n        return queue.enqueueMessage(msg, uptimeMillis);\n    }\n```\n\n\n###A2：`handler.handleMessage(msg)`在什么时候被回调的？\n通过以上的分析，我们很明确的知道`Message`中的`target`是在什么时候被赋值的了，我们先来分析在`Looper.loop()`中出现过的过的`dispatchMessage(msg)`方法\n\n```\npublic void dispatchMessage(Message msg) {\n        if (msg.callback != null) {\n            handleCallback(msg);\n        } else {\n            if (mCallback != null) {\n                if (mCallback.handleMessage(msg)) {\n                    return;\n                }\n            }\n            //看到这个大写加粗的方法调用没！\n            **handleMessage(msg);**\n        }\n    }\n```\n\n加上以上分析，我们将之前分析结果串起来，就可以知道了某些东西：\n`Looper.loop()`不断地获取`MessageQueue`中的`Message`，然后调用与`Message`绑定的`Handler`对象的`dispatchMessage`方法，最后，我们看到了`handleMessage`就在`dispatchMessage`方法里被调用的。\n\n------------------\n通过以上的分析，我们可以很清晰的知道Handler、Looper、Message、MessageQueue这四者的关系以及如何合作的了。\n\n#总结：\n当我们调用`handler.sendMessage(msg)`方法发送一个`Message`时，实际上这个`Message`是发送到**与当前线程绑定**的一个`MessageQueue`中，然后**与当前线程绑定**的`Looper`将会不断的从`MessageQueue`中取出新的`Message`，调用`msg.target.dispathMessage(msg)`方法将消息分发到与`Message`绑定的`handler.handleMessage()`方法中。\n\n一个`Thread`对应多个`Handler`\n一个`Thread`对应一个`Looper`和`MessageQueue`，`Handler`与`Thread`共享`Looper`和`MessageQueue`。\n`Message`只是消息的载体，将会被发送到**与线程绑定的唯一的**`MessageQueue`中，并且被**与线程绑定的唯一的**`Looper`分发，被与其自身绑定的`Handler`消费。\n\n------\n- Enjoy Android :) 如果有误，轻喷，欢迎指正。\n\n\n\n"
  },
  {
    "path": "Part1/Android/自定义控件.md",
    "content": "#自定义控件\n---\n自定义View的步骤：\n\n- 自定义View的属性\n- 在View的构造方法中获得我们自定义View的步骤\n- ［3.重写onMeasure］(不必须)\n- 重写onDraw"
  },
  {
    "path": "Part1/DesignPattern/Builder模式.md",
    "content": "#Builder模式\n---\n\n##模式介绍\n---\n\n###模式的定义\n\n将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。\n\n###模式的使用场景\n\n1. 相同的方法，不同的执行顺序，产生不同的事件结果时；\n2. 多个部件或零件，都可以装配到一个对象中，但是产生的运行结果又不相同时；\n3. 产品类非常复杂，或者产品类中的调用顺序不同产生了不同的效能，这个时候使用建造者模式非常合适；\n\n\n###Android源码中的模式实现\n\n在Android源码中，我们最常用到的Builder模式就是AlertDialog.Builder， 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 :\n\n```\n//显示基本的AlertDialog  \n    private void showDialog(Context context) {  \n        AlertDialog.Builder builder = new AlertDialog.Builder(context);  \n        builder.setIcon(R.drawable.icon);  \n        builder.setTitle(\"Title\");  \n        builder.setMessage(\"Message\");  \n        builder.setPositiveButton(\"Button1\",  \n                new DialogInterface.OnClickListener() {  \n                    public void onClick(DialogInterface dialog, int whichButton) {  \n                        setTitle(\"点击了对话框上的Button1\");  \n                    }  \n                });  \n        builder.setNeutralButton(\"Button2\",  \n                new DialogInterface.OnClickListener() {  \n                    public void onClick(DialogInterface dialog, int whichButton) {  \n                        setTitle(\"点击了对话框上的Button2\");  \n                    }  \n                });  \n        builder.setNegativeButton(\"Button3\",  \n                new DialogInterface.OnClickListener() {  \n                    public void onClick(DialogInterface dialog, int whichButton) {  \n                        setTitle(\"点击了对话框上的Button3\");  \n                    }  \n                });  \n        builder.create().show();  // 构建AlertDialog， 并且显示\n    } \n```\n\n##优点与缺点\n---\n\n###优点\n\n* 良好的封装性， 使用建造者模式可以使客户端不必知道产品内部组成的细节；\n* 建造者独立，容易扩展；\n* 在对象创建过程中会使用到系统中的一些其它对象，这些对象在产品对象的创建过程中不易得到。\n\n###缺点\n\n* 会产生多余的Builder对象以及Director对象，消耗内存；\n* 对象的构建过程暴露。"
  },
  {
    "path": "Part1/DesignPattern/代理模式.md",
    "content": "#代理模式\n---\n\n##模式介绍\n\n代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象，并由代理对象控制对原对象的引用。\n\n##模式的使用场景\n\n就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下，一个客户不想或者不能够直接引用一个对象，而代理对象可以在客户端和目标对象之间起到中介的作用。\n\n##角色介绍\n\n* 抽象对象角色：声明了目标对象和代理对象的共同接口，这样一来在任何可以使用目标对象的地方都可以使用代理对象。\n* 目标对象角色：定义了代理对象所代表的目标对象。\n* 代理对象角色：代理对象内部含有目标对象的引用，从而可以在任何时候操作目标对象；代理对象提供一个与目标对象相同的接口，以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后，执行某个操作，而不是单纯地将调用传递给目标对象。\n\n##优点与缺点\n---\n\n优点\n\n给对象增加了本地化的扩展性，增加了存取操作控制\n\n缺点\n\n会产生多余的代理类"
  },
  {
    "path": "Part1/DesignPattern/单例模式.md",
    "content": "#单例模式\n---\n\n###定义\n>保证一个类仅有一个实例，并提供一个访问它的全局访问点。\n\n>Singleton:负责创建Singleton类自己的唯一实例，并提供一个getInstance的方法，让外部来访问这个类的唯一实例。\n\n\n* 饿汉式：\n\t```\n\tprivate static Singleton uniqueInstance = new Singleton();\n\t```\n* 懒汉式\n\t```\n\tprivate static Singleton uniqueInstance = null;\n\t```\n\n###功能\n单例模式是用来保证这个类在运行期间只会被创建一个类实例，另外，单例模式还提供了一个全局唯一访问这个类实例的访问点，就是getInstance方法。\n\n###范围\nJava里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的，所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。\n\n懒汉式单例有延迟加载和缓存的思想\n\n###优缺点\n* 懒汉式是典型的时间换空间\n* 饿汉式是典型的空间换时间\n\n---\n* 不加同步的懒汉式是线程不安全的。比如，有两个线程，一个是线程A，一个是线程B，它们同时调用getInstance方法，就可能导致并发问题。\n\n* 饿汉式是线程安全的，因为虚拟机保证只会装载一次，在装载类的时候是不会发生并发的。\n\n---\n\n如何实现懒汉式的线程安全？\n\n加上synchronized即可\n\n```\npublic static synchronized Singleton getInstance(){}\n```\n\n但这样会降低整个访问的速度，而且每次都要判断。可以用双重检查加锁。\n\n双重加锁机制，指的是：并不是每次进入getInstance方法都需要同步，而是先不同步，进入方法过后，先检查实例是否存在，如果不存在才进入下面的同步块，这是第一重检查。进入同步块后，再次检查实例是否存在，如果不存在，就在同步的情况下创建一个实例。这是第二重检查。\n\n双重加锁机制的实现会使用一个关键字volatile，它的意思是：被volatile修饰的变量的值，将不会被本地线程缓存，所有对该变量的读写都是直接操作共享内存，从而确保多个线程能正确的处理该变量。\n\n```\n\n/**\n * 双重检查加锁的单例模式\n * @author dream\n *\n */\npublic class Singleton {\n\n\t/**\n\t * 对保存实例的变量添加volitile的修饰\n\t */\n\tprivate volatile static Singleton instance = null;\n\tprivate Singleton(){\n\t\t\n\t}\n\t\n\tpublic static Singleton getInstance(){\n\t\t//先检查实例是否存在，如果不存在才进入下面的同步块\n\t\tif(instance == null){\n\t\t\t//同步块，线程安全的创建实例\n\t\t\tsynchronized (Singleton.class) {\n\t\t\t\t//再次检查实例是否存在，如果不存在才真正的创建实例\n\t\t\t\tinstance = new Singleton();\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n\t\n}\n\n```\n\n###一种更好的单例实现方式\n\n```\npublic class Singleton {\n\n\t/**\n\t * 类级的内部类，也就是静态类的成员式内部类，该内部类的实例与外部类的实例\n\t * 没有绑定关系，而且只有被调用时才会装载，从而实现了延迟加载\n\t * @author dream\n\t *\n\t */\n\tprivate static class SingletonHolder{\n\t\t/**\n\t\t * 静态初始化器，由JVM来保证线程安全\n\t\t */\n\t\tprivate static final Singleton instance = new Singleton();\n\t}\n\t\n\t/**\n\t * 私有化构造方法\n\t */\n\tprivate Singleton(){\n\t\t\n\t}\n\t\n\tpublic static Singleton getInstance(){\n\t\treturn SingletonHolder.instance;\n\t}\n}\n\n```\n\n根据《高效Java第二版》中的说法，单元素的枚举类型已经成为实现Singleton的最佳方法。\n\n```\npackage example6;\n\n/**\n * 使用枚举来实现单例模式的示例\n * @author dream\n *\n */\npublic class Singleton {\n\n\t/**\n\t * 定义一个枚举的元素，它就代表了Singleton的一个实例\n\t */\n\tuniqueInstance;\n\t\n\t/**\n\t * 示意方法，单例可以有自己的操作\n\t */\n\tpublic void singletonOperation(){\n\t\t//功能树立\n\t}\n}\n\n```\n---\n\n###本质\n控制实例数量\n\n###何时选用单例模式\n当需要控制一个类的实例只能有一个，而且客户只能从一个全局访问点访问它时，可以选用单例模式，这些功能恰好是单例模式要解决的问题。\n\n"
  },
  {
    "path": "Part1/DesignPattern/原型模式.md",
    "content": "#原型模式\n---\n\n##模式介绍  \n \n###模式的定义\n\n用原型实例指定创建对象的种类，并通过拷贝这些原型创建新的对象。\n\n### 模式的使用场景\n\n1. 类初始化需要消化非常多的资源，这个资源包括数据、硬件资源等，通过原型拷贝避免这些消耗；\n2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限，则可以使用原型模式；\n3. 一个对象需要提供给其他对象访问，而且各个调用者可能都需要修改其值时，可以考虑使用原型模式拷贝多个对象供调用者使用，即保护性拷贝。\n\n##Android源码中的模式实现\n\nIntent中使用了原型模式\n\n```\nUri uri = Uri.parse(\"smsto:0800000123\");    \n    Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri);    \n    shareIntent.putExtra(\"sms_body\", \"The SMS text\");    \n\n    Intent intent = (Intent)shareIntent.clone() ;\n    startActivity(intent);\n``` \n\n##优点与缺点\n\n###优点\n原型模式是在内存二进制流的拷贝，要比直接 new 一个对象性能好很多，特别是要在一个循环体内产生大量的对象时，原型模式可以更好地体现其优点。\n\n\n###缺点\n这既是它的优点也是缺点，直接在内存中拷贝，构造函数是不会执行的，在实际开发当中应该注意这个潜在的问题。优点就是减少了约束，缺点也是减少了约束，需要大家在实际应用时考虑。"
  },
  {
    "path": "Part1/DesignPattern/外观模式.md",
    "content": "#外观模式\n---\n\n###定义\n为子系统中的一组接口提供一个一致的界面，Facade模式定义了一个高层接口，这个接口使得这一子系统更加容易使用。\n\n###外观模式的目的\n\n不是给子系统添加新的功能接口，而是为了让外部减少与子系统内多个模块的交互，松散耦合，从而让外部能够更简单的使用子系统。\n\n###优缺点\n1. 优点\n\t* 松散耦合\n\t* 简单易用\n\t* 更好的划分访问的层次\n2. 缺点\n\t* 过多的或者是不太合理的Facade也容易让人迷惑。到底是调用Facade好还是直接调用模块好。\n\n###本质\n封装交互，简化调用\n\n###何时选用外观模式\n* 如果你希望为复杂的子系统提供一个简单接口的时候，可以考虑使用外观模式。使用外观对象对实现大部分客户需要的功能，从而简化客户的使用。\n* 如果想要让客户程序和抽象类的实现部分松散耦合，可以考虑使用外观模式，使用外观对象来将这个子系统与它的客户分离开来，从而提高子系统的独立性和可移植性。\n* 如果构建多层结构的系统，可以考虑使用外观模式，使用外观对象作为每层的入口，这样就可以简化层间调用，也可以松散层次之间的依赖关系。\n"
  },
  {
    "path": "Part1/DesignPattern/常见的面向对象设计原则.md",
    "content": "#常见的面向对象设计原则\n\n1. 单一职责原则 SRP\n一个类应该仅有一个引起它变化的原因。\n2. 开放关闭原则 OCP\n一个类应该对外扩展开放，对修改关闭。\n3. 里氏替换原则 LSP\n子类型能够替换掉它们的父类型。\n4. 依赖倒置原则 DIP\n要依赖于抽象，不要依赖于具体类，要做到依赖倒置，应该做到：\n\t* 高层模块不应该依赖底层模块，二者都应该依赖于抽象。\n\t* 抽象不应该依赖于具体实现，具体实现应该依赖于抽象。\n5. 接口隔离原则 ISP\n不应该强迫客户依赖于他们不用的方法。\n6. 最少知识原则 LKP\n只和你的朋友谈话。\n7. 其他原则\n\t* 面向接口编程\n\t* 优先使用组合，而非继承\n\t* 一个类需要的数据应该隐藏在类的内部\n\t* 类之间应该零耦合，或者只有传导耦合，换句话说，类之间要么没关系，要么只使用另一个类的接口提供的操作\n\t* 在水平方向上尽可能统一地分布系统功能\n\n\n"
  },
  {
    "path": "Part1/DesignPattern/策略模式.md",
    "content": "#策略模式\n---\n\n##模式的定义\n\n策略模式定义了一系列的算法，并将每一个算法封装起来，而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。\n\n注：针对同一类型操作，将复杂多样的处理方式分别开来，有选择的实现各自特有的操作。\n\n##模式的使用场景\n\n* 针对同一类型问题的多种处理方式，仅仅是具体行为有差别时。\n* 需要安全的封装多种同一类型的操作时。\n* 出现同一抽象多个子类，而又需要使用if-else 或者 switch-case来选择时。\n\n##Android源码中的模式实现\n\n\n\n\n策略模式主要用来分离算法，根据相同的行为抽象来做不同的具体策略实现。\n\n##优缺点\n\n###优点：\n\n* 结构清晰明了、使用简单直观。\n* 耦合度相对而言较低，扩展方便。\n* 操作封装也更为彻底，数据更为安全。\n\n###缺点：\n\n* 随着策略的增加，子类也会变得繁多。"
  },
  {
    "path": "Part1/DesignPattern/简单工厂.md",
    "content": "#简单工厂\n---\n###接口\n接口是一种特殊的抽象类，跟一般的抽象类相比，接口里的所有方法都是抽象方法，接口里的所有属性都是常量。也就是说接口里面只有方法定义没有任何方法实现。\n\n接口的思想是\"封装隔离\"\n\n###简单工厂\n示例代码：\n[https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory](https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory)\n\n客户端在调用的时候，不但知道了接口，同时还知道了具体的实现。接口的思想是\"封装隔离\"，而实现类Impl应该是被接口Api封装并同客户端隔离开来的，客户端不应该知道具体的实现类是Impl。\n\n###简单工厂的功能\n不仅可以利用简单工厂来创建接口，也可以用简单工厂来创造抽象类，甚至是一个具体的实例。\n\n###静态工厂\n没有创建工厂实例的必要，把简单工厂实现成一个工具类，直接使用静态方法。\n\n###万能工厂\n 一个简单哪工厂可以包含很多用来构造东西的方法，这些方法可以创建不同的接口、抽象类或者是类实例。\n\n###简单工厂的优缺点\n1. 优点\n* 帮助封装\n* 解耦\n2. 缺点\n* 可能增加客户端的复杂度\n* 不方便扩展子工厂\n\n##思考\n\n简单工厂的本质是选择实现。\n\n"
  },
  {
    "path": "Part1/DesignPattern/观察者模式.md",
    "content": "#观察者模式\n---\n\n首先在Android中，我们往ListView添加数据后，都会调用Adapter的notifyDataChanged()方法，其中使用了观察者模式。\n\n当ListView的数据发生变化时，调用Adapter的notifyDataSetChanged函数，这个函数又会调用DataSetObservable的notifyChanged函数，这个函数会调用所有观察者(AdapterDataSetObserver)的onChanged方法，在onChanged函数中又会调用ListView重新布局的函数使得ListView刷新界面。\n\nAndroid中应用程序发送广播的过程：\n\n- 通过sendBroadcast把一个广播通过Binder发送给ActivityManagerService，ActivityManagerService根据这个广播的Action类型找到相应的广播接收器，然后把这个广播放进自己的消息队列中，就完成第一阶段对这个广播的异步分发。\n- ActivityManagerService在消息循环中处理这个广播，并通过Binder机制把这个广播分发给注册的ReceiverDispatcher，ReceiverDispatcher把这个广播放进MainActivity所在线程的消息队列中，就完成第二阶段对这个广播的异步分发：\n- ReceiverDispatcher的内部类Args在MainActivity所在的线程消息循环中处理这个广播，最终是将这个广播分发给所注册的BroadcastReceiver实例的onReceive函数进行处理："
  },
  {
    "path": "Part1/DesignPattern/责任链模式.md",
    "content": "#责任链模式\n---\n\n##模式介绍\n\n###模式的定义\n\n一个请求沿着一条“链”传递，直到该“链”上的某个处理者处理它为止。\n\n###模式的使用场景\n\n一个请求可以被多个处理者处理或处理者未明确指定时。"
  },
  {
    "path": "Part1/DesignPattern/适配器模式.md",
    "content": "#适配器模式\n---\n定义：\n>将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。\n\n功能：\n\n>进行转换匹配，目的是复用已有的功能，而不是来实现新的接口。在适配器里实现功能，这种适配器称为智能适配器。\n\n优点：\n\n* 更好的复用性\n* 更好的扩展性\n\n缺点：\n\n* 过多的使用适配器，会让系统非常零乱，不容易整体进行把握。\n\n本质：\n\n转换匹配，复用功能。\n\n何时选用适配器模式：\n\n* 如果你想要使用一个已经存在的类，但是它的接口不符合你的需求，这种情况可以使用适配器模式，来把已有的实现转换成你需要的接口。\n* 如果你想创建一个可以复用的类，这个类可能和一些不兼容的类一起工作，这种情况可以使用适配器模式，到时候需要什么就适配什么。\n* 如果你想使用一些已经存在的子类，但是不可能对每一个子类都进行适配，这种情况可以选用对象适配器，直接适配这些子类的父类就可以了。\n"
  },
  {
    "path": "Part2/JVM/JVM.md",
    "content": "#JVM\n---\n\n**内存模型以及分区，需要详细到每个区放什么。**\n\n[http://blog.csdn.net/ns_code/article/details/17565503](http://blog.csdn.net/ns_code/article/details/17565503)\n\nJVM所管理的内存分为以下几个运行时数据区：程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。\n\n![](http://img.blog.csdn.net/20131226151744250)\n\n程序计数器(Program Counter Register)\n\n一块较小的内存空间，它是当前线程所执行的字节码的行号指示器，字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令，分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器，各线程间的计数器互不影响，因此该区域是线程私有的。\n\n当线程在执行一个Java方法时，该计数器记录的是正在执行的虚拟机字节码指令的地址，当线程在执行的是Native方法（调用本地操作系统方法）时，该计数器的值为空。另外，该内存区域是唯一一个在Java虚拟机规范中么有规定任何OOM（内存溢出：OutOfMemoryError）情况的区域。\n\nJava虚拟机栈（Java Virtual Machine Stacks）\n\n该区域也是线程私有的，它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型：每个方法被执行的时候都会同时创建一个栈帧，栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲，活动线程中，只有栈顶的栈帧是有效的，称为当前栈帧，这个栈帧所关联的方法称为当前方法，执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时，栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了，并且写入了方法表的Code属性之中。因此，一个栈帧需要分配多少内存，不会受到程序运行期变量数据的影响，而仅仅取决于具体的虚拟机实现。\n\n本地方法栈（Native Method Stacks）\n\n该区域与虚拟机栈所发挥的作用非常相似，只是虚拟机栈为虚拟机执行Java方法服务，而本地方法栈则为使用到的本地操作系统（Native）方法服务。\n\nJava堆（Java Heap）\n\nJava Heap是Java虚拟机所管理的内存中最大的一块，它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域，因此很多时候也被称为“GC堆”。\n\n根据Java虚拟机规范的规定，Java堆可以处在物理上不连续的内存空间中，只要逻辑上是连续的即可。如果在堆中没有内存可分配时，并且堆也无法扩展时，将会抛出OutOfMemoryError异常。   \n\n方法区（Method Area）\n\n方法区也是各个线程共享的内存区域，它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为“永久代”，但这仅仅对于Sun HotSpot来讲，JRockit和IBM J9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分，而且它和Java Heap一样不需要连续的内存，可以选择固定大小或可扩展，另外，虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言，垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分，Class文件中除了有类的版本、字段、方法、接口等描述信息外，还有一项信息是常量池（Class文件常量池），用于存放编译器生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性，Java语言并不要求常量一定只能在编译期产生，也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池，运行期间也可能将新的常量放入池中，这种特性被开发人员利用比较多的是String类的intern（）方法。\n\n根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。\n\n**内存泄漏和内存溢出的差别**\n\n内存泄露是指分配出去的内存没有被回收回来，由于失去了对该内存区域的控制，因而造成了资源的浪费。Java中一般不会产生内存泄露，因为有垃圾回收器自动回收垃圾，但这也不绝对，当我们new了对象，并保存了其引用，但是后面一直没用它，而垃圾回收器又不会去回收它，这边会造成内存泄露，\n\n内存溢出是指程序所需要的内存超出了系统所能分配的内存（包括动态扩展）的上限。\n\n**类型擦除**\n\n[http://blog.csdn.net/ns_code/article/details/18011009](http://blog.csdn.net/ns_code/article/details/18011009)\n\nJava语言在JDK1.5之后引入的泛型实际上只在程序源码中存在，在编译后的字节码文件中，就已经被替换为了原来的原生类型，并且在相应的地方插入了强制转型代码，因此对于运行期的Java语言来说，`ArrayList<String>`和`ArrayList<Integer>`就是同一个类。所以泛型技术实际上是Java语言的一颗语法糖，Java语言中的泛型实现方法称为类型擦除，基于这种方法实现的泛型被称为伪泛型。\n\n下面是一段简单的Java泛型代码：\n\n```\nMap<Integer,String> map = new HashMap<Integer,String>();  \nmap.put(1,\"No.1\");  \nmap.put(2,\"No.2\");  \nSystem.out.println(map.get(1));  \nSystem.out.println(map.get(2));  \n````\n\n将这段Java代码编译成Class文件，然后再用字节码反编译工具进行反编译后，将会发现泛型都变回了原生类型，如下面的代码所示：\n\n```\nMap map = new HashMap();  \nmap.put(1,\"No.1\");  \nmap.put(2,\"No.2\");  \nSystem.out.println((String)map.get(1));  \nSystem.out.println((String)map.get(2));  \n```\n\n为了更详细地说明类型擦除，再看如下代码：\n\n```\nimport java.util.List;  \npublic class FanxingTest{  \n    public void method(List<String> list){  \n        System.out.println(\"List String\");  \n    }  \n    public void method(List<Integer> list){  \n        System.out.println(\"List Int\");  \n    }  \n}  \n```\n\n当我用Javac编译器编译这段代码时，报出了如下错误：\n\n\n```\nFanxingTest.java:3: 名称冲突：method(java.util.List<java.lang.String>) 和 method\n\n(java.util.List<java.lang.Integer>) 具有相同疑符\n\npublic void method(List<String> list){\n\n^\n\nFanxingTest.java:6: 名称冲突：method(java.util.List<java.lang.Integer>) 和 metho\n\nd(java.util.List<java.lang.String>) 具有相同疑符\n\npublic void method(List<Integer> list){\n\n^\n```\n\n2 错误\n\n\n这是因为泛型List<String>和List<Integer>编译后都被擦除了，变成了一样的原生类型List，擦除动作导致这两个方法的特征签名变得一模一样，在Class类文件结构一文中讲过，Class文件中不能存在特征签名相同的方法。\n\n把以上代码修改如下：\n\n```\nimport java.util.List;  \npublic class FanxingTest{  \n    public int method(List<String> list){  \n        System.out.println(\"List String\");  \n        return 1;  \n    }  \n    public boolean method(List<Integer> list){  \n        System.out.println(\"List Int\");  \n        return true;  \n    }  \n}  \n```\n\n发现这时编译可以通过了（注意：Java语言中true和1没有关联，二者属于不同的类型，不能相互转换，不存在C语言中整数值非零即真的情况）。两个不同类型的返回值的加入，使得方法的重载成功了。这是为什么呢？\n\n    我们知道，Java代码中的方法特征签名只包括了方法名称、参数顺序和参数类型，并不包括方法的返回值，因此方法的返回值并不参与重载方法的选择，这样看来为重载方法加入返回值貌似是多余的。对于重载方法的选择来说，这确实是多余的，但我们现在要解决的问题是让上述代码能通过编译，让两个重载方法能够合理地共存于同一个Class文件之中，这就要看字节码的方法特征签名，它不仅包括了Java代码中方法特征签名中所包含的那些信息，还包括方法返回值及受查异常表。为两个重载方法加入不同的返回值后，因为有了不同的字节码特征签名，它们便可以共存于一个Class文件之中。\n\n**堆里面的分区：Eden，survival from to，老年代，各自的特点。**\n\n\n\n**对象创建方法，对象的内存分配，对象的访问定位。**\n\n对内存分配情况分析最常见的示例便是对象实例化:\n\n```\nObject obj = new Object();\n```\n\n这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中，及时对JVM虚拟机不了解的Java使用这，应该也知道obj会作为引用类型（reference）的数据保存在Java栈的本地变量表中，而会在Java堆中保存该引用的实例化对象，但可能并不知道，Java堆中还必须包含能查找到此对象类型数据的地址信息（如对象类型、父类、实现的接口、方法等），这些类型数据则保存在方法区中。\n\n另外，由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用，并没有定义这个引用应该通过哪种方式去定位，以及访问到Java堆中的对象的具体位置，因此不同虚拟机实现的对象访问方式会有所不同，主流的访问方式有两种：使用句柄池和直接使用指针。\n\n\n\n**GC的两种判定方法：引用计数与引用链。**\n\n引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起，该计数器记录着该对象当前被引用的次数，每当创建一个新的引用指向该对象时其计数器就加1，每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。\n\nJava的内存回收机制可以形象地理解为在堆空间中引入了重力场，已经加载的类的静态变量和处于活动线程的堆栈空间的变量是这个空间的牵引对象。这里牵引对象是指按照Java语言规范，即便没有其它对象保持对它的引用也不能够被回收的对象，即Java内存空间中的本原对象。当然类可能被去加载，活动线程的堆栈也是不断变化的，牵引对象的集合也是不断变化的。对于堆空间中的任何一个对象，如果存在一条或者多条从某个或者某几个牵引对象到该对象的引用链，则就是可达对象，可以形象地理解为从牵引对象伸出的引用链将其拉住，避免掉到回收池中。\n\n**GC的三种收集方法：标记清除、标记整理、复制算法的原理与特点，分别用在什么地方，如果让你优化收集方法，有什么思路？**\n\n标记清除算法是最基础的收集算法，其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段：首先标记出需要回收的对象，标记完成之后统一清除对象。它的主要缺点：①.标记和清除过程效率不高 。②.标记清除之后会产生大量不连续的内存碎片。\n\n标记整理，标记操作和“标记-清除”算法一致，后续操作不只是直接清理对象，而是在清理无用对象完成后让所有存活的对象都向一端移动，并更新引用其对象的指针。主要缺点：在标记-清除的基础上还需进行对象的移动，成本相对较高，好处则是不会产生内存碎片。\n\n复制算法，它将可用内存容量划分为大小相等的两块，每次只使用其中的一块。当这一块用完之后，就将还存活的对象复制到另外一块上面，然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收，不会产生碎片等情况，只要移动堆订的指针，按顺序分配内存即可，实现简单，运行高效。主要缺点：内存缩小为原来的一半。\n\n\n**Minor GC与Full GC分别在什么时候发生？**\n\nMinor GC：通常是指对新生代的回收。指发生在新生代的垃圾收集动作，因为 Java 对象大多都具备朝生夕灭的特性，所以 Minor GC 非常频繁，一般回收速度也比较快\n\nMajor GC：通常是指对年老代的回收。\n\nFull GC：Major GC除并发gc外均需对整个堆进行扫描和回收。指发生在老年代的 GC，出现了 Major GC，经常会伴随至少一次的 Minor GC（但非绝对的，在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程） 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。\n\n**几种常用的内存调试工具：jmap、jstack、jconsole。**\n\njmap（linux下特有，也是很常用的一个命令）观察运行中的jvm物理内存的占用情况。\n参数如下：\n-heap：打印jvm heap的情况\n-histo：打印jvm heap的直方图。其输出信息包括类名，对象数量，对象占用大小。\n-histo：live ：同上，但是只答应存活对象的情况\n-permstat：打印permanent generation heap情况\njstack（linux下特有）可以观察到jvm中当前所有线程的运行情况和线程当前状态\njconsole一个图形化界面，可以观察到java进程的gc，class，内存等信息\njstat最后要重点介绍下这个命令。这是jdk命令中比较重要，也是相当实用的一个命令，可以观察到classloader，compiler，gc相关信息\n具体参数如下：\n-class：统计class loader行为信息\n-compile：统计编译行为信息\n-gc：统计jdk gc时heap信息\n-gccapacity：统计不同的generations（不知道怎么翻译好，包括新生区，老年区，permanent区）相应的heap容量情况\n-gccause：统计gc的情况，（同-gcutil）和引起gc的事件\n-gcnew：统计gc时，新生代的情况\n-gcnewcapacity：统计gc时，新生代heap容量\n-gcold：统计gc时，老年区的情况\n-gcoldcapacity：统计gc时，老年区heap容量\n-gcpermcapacity：统计gc时，permanent区heap容量\n-gcutil：统计gc时，heap情况\n-printcompilation：不知道干什么的，一直没用过。\n\n**类加载的五个过程：加载、验证、准备、解析、初始化。**\n\n类加载过程\n\n类从被加载到虚拟机内存中开始，到卸载出内存为止，它的整个生命周期包括加载、验证、准备、解析、初始化、使用、卸载。\n\n其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中，加载、验证、准备和初始化这四个阶段发生的顺序是确定的，而解析阶段则不一定，它在某些情况下可以在初始化阶段之后开始，这是为了支持Java语言的运行时绑定（也成为动态绑定或晚期绑定）。另外注意这里的几个阶段是按顺序开始，而不是按顺序进行或完成，因为这些阶段通常都是互相交叉地混合进行的，通常在一个阶段执行的过程中调用或激活另一个阶段。\n\n这里简要说明下Java中的绑定：绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来，对java来说，绑定分为静态绑定和动态绑定：\n\n* 静态绑定：即前期绑定。在程序执行前方法已经被绑定，此时由编译器或其它连接程序实现。针对java，简单的可以理解为程序编译期的绑定。java当中的方法只有final，static，private和构造方法是前期绑定的。\n* 动态绑定：即晚期绑定，也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中，几乎所有的方法都是后期绑定的。\n\n“加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段，在此阶段，虚拟机需要完成以下三件事情：\n\n1. 通过一个类的全限定名来获取定义此类的二进制字节流。\n2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。\n3. 在Java堆中生成一个代表这个类的java.lang.Class对象，作为方法区这些数据的访问入口。\n\n验证是连接阶段的第一步，这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。\n\n准备阶段是为类的静态变量分配内存并将其初始化为默认值，这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存，实例变量将会在对象实例化时随着对象一起分配在Java堆中。\n\n解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。\n\n类初始化是类加载过程的最后一步，前面的类加载过程，除了在加载阶段用户应用程序可以通过自定义类加载器参与之外，其余动作完全由虚拟机主导和控制。到了初始化阶段，才真正开始执行类中定义的Java程序代码。\n\n\n\n**双亲委派模型：Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。**\n\n1. 启动类加载器，负责将存放在<JAVA_HOME>\\lib目录中的，或者被-Xbootclasspath参数所指定的路径中，并且是虚拟机识别的（仅按照文件名识别，如rt.jar，名字不符合的类库即时放在lib目录中也不会被加载）类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用。\n2. 扩展类加载器：负责加载<JAVA_HOME>\\lib\\ext目录中的，或者被java.ext.dirs系统变量所指定的路径中的所有类库，开发者可以直接使用该类加载器。\n3. 应用程序类加载器：负责加载用户路径上所指定的类库，开发者可以直接使用这个类加载器，也是默认的类加载器。\n三种加载器的关系：启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器。\n\n这种关系即为类加载器的双亲委派模型。其要求除启动类加载器外，其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不以继承关系实现，而是用组合的方式来复用父类的代码。\n\n双亲委派模型的工作过程：如果一个类加载器接收到了类加载的请求，它首先把这个请求委托给他的父类加载器去完成，每个层次的类加载器都是如此，因此所有的加载请求都应该传送到顶层的启动类加载器中，只有当父加载器反馈自己无法完成这个加载请求（它在搜索范围中没有找到所需的类）时，子加载器才会尝试自己去加载。\n\n好处：java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object，它存放在rt.jar中，无论哪个类加载器要加载这个类，最终都会委派给启动类加载器进行加载，因此Object类在程序的各种类加载器环境中都是同一个类。相反，如果用户自己写了一个名为java.lang.Object的类，并放在程序的Classpath中，那系统中将会出现多个不同的Object类，java类型体系中最基础的行为也无法保证，应用程序也会变得一片混乱。\n\n实现：在java.lang.ClassLoader的loadClass()方法中，先检查是否已经被加载过，若没有加载则调用父类加载器的loadClass()方法，若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败，则抛出ClassNotFoundException异常后，再调用自己的findClass()方法进行加载。\n\n\n**分派：静态分派与动态分派。**\n\n静态分派与重载有关，虚拟机在重载时是通过参数的静态类型，而不是运行时的实际类型作为判定依据的；静态类型在编译期是可知的；\n动态分派与重写（Override）相关，invokevirtual(调用实例方法)指令执行的第一步就是在运行期确定接收者的实际类型，根据实际类型进行方法调用；\n\n**GC收集器有哪些？CMS收集器与G1收集器的特点。**\n\n\n\n**自动内存管理机制，GC算法，运行时数据区结构，可达性分析工作原理，如何分配对象内存**\n\n**反射机制，双亲委派机制，类加载器的种类**\n\n**Jvm内存模型，先行发生原则，violate关键字作用**"
  },
  {
    "path": "Part2/JVM/JVM类加载机制.md",
    "content": "#虚拟机类加载机制\n---\n**虚拟机把描述类的数据从Class文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可以被Java虚拟机直接使用的Java类型，这就是虚拟机的类加载机制。**\n\n类从被加载到虚拟内存中开始，到卸载内存为止，它的整个生命周期包括了：加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中，验证，准备和解析三个部分统称为连接(Linking)。\n\n###类加载的过程\n类加载的全过程，加载，验证，准备，解析和初始化这五个阶段。\n\n---\n\n####加载\n在加载阶段，虚拟机需要完成以下三件事情：\n\n* 通过一个类的全限定名来获取定义此类的二进制字节流\n* 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构\n* 在Java堆中生成一个代表这个类的java.lang.Class对象，作为方法区这些数据的访问入口\n\n####验证\n这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能有所不同，但大致上都会完成下面四个阶段的检验过程：文件格式验证、元数据验证、字节码验证和符号引用验证。\n\n**文件格式验证**\n\n第一阶段要验证字节流是否符合Class文件格式的规范，并且能被当前版本的虚拟机处理。\n\n**元数据验证**\n\n第二阶段是对字节码描述的信息进行语义分析，以保证其描述的信息符合Java语言规范的要求。\n\n**字节码验证**\n\n第三阶段时整个验证过程中最复杂的一个阶段，主要工作是数据流和控制流的分析。在第二阶段对元数据信息中的数据类型做完校验后，这阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。\n\n**符号引用验证**\n\n最后一个阶段的校验发生在虚拟机将符号引用直接转化为直接引用的时候，这个转化动作将在连接的第三个阶段－解析阶段产生。符号引用验证可以看作是对类自身以外（常量池中的各种符号引用）的信息进行匹配性的校验。\n\n####准备\n准备阶段是正式为类变量分配内存并设置类变量初始值的阶段，这些内存都将在方法区进行分配。\n\n####解析\n解析阶段是虚拟机将常量池的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。\n\n* 类或接口的解析\n* 字段解析\n* 类方法解析\n* 接口方法解析\n\n####初始化\n前面的类加载过程中，除了在加载阶段用户应用程序可以通过自定义类加载器参与之外，其余动作完全由Java虚拟机主导和控制。到了初始化阶段，才真正开始执行类中定义的Java程序代码（或者说是字节码）。在准备阶段，变量已经赋过一次系统要求的初始值，而在初始化阶段，则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源，或者说初始化阶段是执行类构造器<clinit>()方法的过程。\n\n###类加载器\n---\n####类与类加载器\n虚拟机设计团队把类加载阶段中的\"通过一个类的全限定名来获取描述此类的二进制字节流\"这个动作放到Java虚拟机外部去实现，以便让程序自己决定如何去获取所需的类。实现这个动作的代码模块被称为\"类加载器\"。\n\n####双亲委派模型\n站在Java虚拟机的角度讲，只存在两种不同的类加载器：一种是启动类加载器(Bootstrap ClassLoader)，这个类加载器使用C++语言实现，是虚拟机自身的一部分；另外一种就是所有其他的类加载器，这些类加载器都由Java语言实现，独立于虚拟机外部，并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看，类加载器还可以分得更细致一些，绝大部分Java程序都会使用到以下三种系统提供的类加载器：\n\n* 启动类加载器\n* 扩展类加载器\n* 应用程序类加载器\n\n\n\n"
  },
  {
    "path": "Part2/JVM/Java内存区域与内存溢出.md",
    "content": "##内存区域\n\nJava虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区：程序计数器，Java虚拟机栈，本地方法栈，Java堆，方法区。下面详细阐述各数据区所存储的数据类型。\n\n![这里写图片描述](http://img.blog.csdn.net/20160401142029373)\n\n**程序计数器（Program Counter Register）**\n\n一块较小的内存空间，它是当前线程所执行的子节码的行号指示器，字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的子节码指令，分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的程序计数器，各线程间的计数器互不影响，因此该区域是线程私有的。\n\n当线程在执行一个Java方法时，该计数器纪录的是正在执行的虚拟机字节吗指令的地址，当线程在执行的是Native方法(调用本地操作系统方法)时，该计数器的值为空。另外，该内存区域是唯一一个在Java虚拟机规范中没有任何OOM（内存溢出：OutOfMemoryError）情况的区域。\n\n**Java虚拟机栈（Java Virtual Machine Stacks）**\n\n该区域也是线程私有的，它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型：每个方法被执行的时候都会创建一个帧栈，栈它是用于支持虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲，活动线程中，只有栈顶的栈帧是有效的，称为当前栈，这个栈帧所关联的方法称为当前方法，执行引擎所运行的所有字节码都只针对当前的栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时，栈帧中需要分配多少内存，不会受到程序运行期变量数据的影响，而仅仅取决于具体的虚拟机实现。\n在Java虚拟机规范中，对这个区域规定了两种异常情况：\n\n1.  如果线程请求的栈深度大于虚拟机所允许的深度，将抛出StackOverflowError异常。\n2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间，则抛出OutOfMemory异常。\n\n这两种情况存在着一些互相重叠的部分：当栈空间无法继续分配时，到底是内存太小，还是已使用的栈空间太大，其本质只是对同一件事情的两种描述而已。其本质上只是对一件事情的两种描述而已。在单线程的操作中，无论是由于栈帧太大，还是虚拟机栈空间太小，当栈空间无法分配时，虚拟机抛出的都是StackOverflowError异常，而不会得到OutOfMemoryError异常。而在多线程环境下，则会抛出OutOfMemory异常。\n\n下面详细说明栈帧中所存放的各部分信息的作用和数据结构。\n\n局部变量表是一组变量值存储空间，用于存放方法参数和方法内部定义的局部变量，其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用（reference）和returnAddress类型（它指向了一条字节码指令的地址）。局部变量表所需的内存空间在编译期间完成分配，即在Java程序被编译成Class文件时，就确定了所需分配的最大局部变量表的容量。当进入一个方法时，这个方法需要在栈中分配多大的局部变量空间是完全确定的，在方法运行期间不会改变局部变量表的大小。\n下面详细说明栈帧中所存放的各部分信息的作用和数据结构。 \n\n1、局部变量表\n局部变量表的容量以变量槽（Slot）为最小单位。在虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小（允许其随着处理器、操作系统或虚拟机的不同而发生变化），一个Slot可以存放一个32位以内的数据类型：boolean、byte、char、short、int、float、reference和returnAddresss。reference是对象的引用类型，returnAddress是为字节指令服务的，它执行了一条字节码指令的地址。对于64位的数据类型（long和double），虚拟机会以高位在前的方式为其分配两个连续的Slot空间。\n\n虚拟机通过索引定位的方式使用局部变量表，索引值的范围是从0开始到局部变量表最大的Slot数量，对于32位数据类型的变量，索引n代表第n个Slot，对于64位的，索引n代表第n和第n+1两个Slot。\n\n在方法执行时，虚拟机是使用局部变量表来完成参数值到参数变量列表的传递过程的，如果是实例方法（非static），则局部变量表中的第0位索引的Slot默认是用于传递方法所属对象实例的引用，在方法中可以通过关键字“this”来访问这个隐含的参数。其余参数则按照参数表的顺序来排列，占用从1开始的局部变量Slot，参数表分配完毕后，再根据方法体内部定义的变量顺序和作用域分配其余的Slot。\n\n局部变量表中的Slot是可重用的，方法体中定义的变量，作用域并不一定会覆盖整个方法体，如果当前字节码PC计数器的值已经超过了某个变量的作用域，那么这个变量对应的Slot就可以交给其他变量使用。这样的设计不仅仅是为了节省空间，在某些情况下Slot的复用会直接影响到系统的而垃圾收集行为。\n\n2、操作数栈\n\n操作数栈又常被称为操作栈，操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1,64为数据类型所占的栈容量为2。当一个方法开始执行时，它的操作栈是空的，在方法的执行过程中，会有各种字节码指令（比如：加操作、赋值元算等）向操作栈中写入和提取内容，也就是入栈和出栈操作。\n\nJava虚拟机的解释执行引擎称为“基于栈的执行引擎”，其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的，这点不同于Android虚拟机，Android虚拟机是基于寄存器的。\n\n基于栈的指令集最主要的优点是可移植性强，主要的缺点是执行速度相对会慢些；而由于寄存器由硬件直接提供，所以基于寄存器指令集最主要的优点是执行速度快，主要的缺点是可移植性差。\n\n3、动态连接\n\n每个栈帧都包含一个指向运行时常量池（在方法区中，后面介绍）中该栈帧所属方法的引用，持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用，字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用，一部分会在类加载阶段或第一次使用的时候转化为直接引用（如final、static域等），称为静态解析，另一部分将在每一次的运行期间转化为直接引用，这部分称为动态连接。\n\n4、方法返回地址\n\n当一个方法被执行后，有两种方式退出该方法：执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常，并且该异常没有在方法体内得到处理。无论采用何种退出方式，在方法退出之后，都需要返回到方法被调用的位置，程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息，用来帮助恢复它的上层方法的执行状态。一般来说，方法正常退出时，调用者的PC计数器的值就可以作为返回地址，栈帧中很可能保存了这个计数器值，而方法异常退出时，返回地址是要通过异常处理器来确定的，栈帧中一般不会保存这部分信息。\n\n方法退出的过程实际上等同于把当前栈帧出站，因此退出时可能执行的操作有：恢复上层方法的局部变量表和操作数栈，如果有返回值，则把它压入调用者栈帧的操作数栈中，调整PC计数器的值以指向方法调用指令后面的一条指令。\n\n**本地方法栈（Native Method Stacks）**\n\n该区域与虚拟机栈所发挥的作用非常相似，只是虚拟机栈为虚拟机执行Java方法服务，而本地方法栈则为使用到的本地操作系统（Native）方法服务。\n\n**Java堆（Java Heap）**\n\nJava Heap是Java虚拟机所管理的内存中的最大的一块，它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域，因此很多时候也被称为\"GC堆\"。\n\n根据Java虚拟机的规定，Java堆可以处在物理上不连续的内存空间中，只要逻辑上是连续的即可。如果在堆中没有内存可分配时，并且堆也无法扩展时，将会抛出OutOfMemory。\n\n**方法区（Method Area）**\n\n方法区也是各个线程共享的内存区域，它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为\"永久代\"。但着这仅仅对于Sun HotSpot来讲，JRocket和IBMJ9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分，而且它和Java Heap一样不需要连续的内存，可以选择固定大小或可扩展，另外，虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言，垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分，Class文件中除了有类的版本、字段、方法、接口等描述信息外，还有一项信息是常量池（Class文件常量池），用于存放编译器生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性，Java语言并不要求常量一定只能在编译期产生，也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池，运行期间也可能将新的常量放入池中，这种特性被开发人员利用比较多的是String类的intern（）方法。\n\n根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。\n\n直接内存（Direct Memory）\n\n直接内存并不是虚拟机运行内存时数据区的一部分，也不是Java虚拟机规范中定义的内存区域，它直接从操作系统中分配内存，因此不受Java堆的大小的限制，但是会受到本机总内存的大小及处理器寻址空间的限制，因此它也可能导致OutOfMemoryError异常出现。在Java1.4中新引入了NIO机制，它是一种基于通道与缓冲区的新I/O方式，可以直接从操作系统中分配直接内存，可以直接从操作系统中分配直接内存，即在堆外分配内存，这样能在一些场景中提高性能，因为避免了在Java堆和Native堆中来回复制数据。\n\n内存溢出\n\n下面给出个内存区域内存溢出的简单测试方法\n\n![这里写图片描述](http://img.blog.csdn.net/20160401173849014)\n\n这里有一点要重点说明，在多线程情况下，给每个线程的栈分配的内存越大，反而越容易产生内存产生内存溢出一场。操作系统为每个进程分配的内存是有限制的，虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值，忽略掉程序计数器消耗的内存（很小），以及进程本身消耗的内存，剩下的内存便给了虚拟机栈和本地方法栈，每个线程分配到的栈容量越大，可以建立的线程数量自然就越少。因此，如果是建立过多的线程导致的内存溢出，在不能减少线程数的情况下，就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。\n另外，由于Java堆内也可能发生内存泄露（Memory Leak），这里简要说明一下内存泄露和内存溢出的区别：\n\n内存泄漏是指分配出去的内存没有被回收回来，由于失去了对该内存区域的控制，因而造成了资源的浪费。Java中一般不会产生内存泄漏，因为有垃圾回收器自动回收垃圾，但这也不绝对，当我们new了对象，并保存了其引用，但是后面一直没用它，而垃圾回收器又不会去回收它，这就会造成内存泄漏。\n\n内存溢出是指程序所需要的内存超过了系统所能分配的内存（包括动态扩展）的上限。\n\n对象实例化分析\n\n对内存分配情况分析最常见的示例便是对象实例化：\n\n```\nObject obj = new Object();\n```\n\n这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中，及时对JVM虚拟机不了解的Java使用这，应该也知道obj会作为引用类型（reference）的数据保存在Java栈的本地变量表中，而会在Java堆中保存该引用的实例化对象，但可能并不知道，Java堆中还必须包含能查找到此对象类型数据的地址信息（如对象类型、父类、实现的接口、方法等），这些类型数据则保存在方法区中。\n\n另外，由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用，并没有定义这个引用应该通过哪种方式去定位，以及访问到Java堆中的对象的具体位置，因此不同虚拟机实现的对象访问方式会有所不同，主流的访问方式有两种：使用句柄池和直接使用指针。\n\n通过句柄池访问的方式如下：\n\n![这里写图片描述](http://img.blog.csdn.net/20160401175131207)\n\n通过直接指针访问的方式如下：\n\n![这里写图片描述](http://img.blog.csdn.net/20160401175203926)\n\n这两种对象的访问方式各有优势，使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址，在对象被移动（垃圾收集时移动对象是非常普遍的行为）时只会改变句柄中的实例数据指针，而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快，它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的。"
  },
  {
    "path": "Part2/JVM/垃圾回收算法.md",
    "content": "#垃圾回收算法\n---\n\n1. 引用计数法：缺点是无法处理循环引用问题\n2. 标记-清除法：标记所有从根结点开始的可达对象，缺点是会造成内存空间不连续，不连续的内存空间的工作效率低于连续的内存空间，不容易分配内存\n3. 复制算法：将内存空间分成两块，每次将正在使用的内存中存活对象复制到未使用的内存块中，之后清除正在使用的内存块。算法效率高，但是代价是系统内存折半。适用于新生代(存活对象少，垃圾对象多)\n4. 标记－压缩算法：标记－清除的改进，清除未标记的对象时还将所有的存活对象压缩到内存的一端，之后，清理边界所有空间既避免碎片产生，又不需要两块同样大小的内存快，性价比高。适用于老年代。\n5. 分代\n\n"
  },
  {
    "path": "Part2/JavaConcurrent/Java并发基础知识.md",
    "content": "#Java并发\n---\n\n（Executor框架和多线程基础）\n\n**Thread与Runable如何实现多线程**\n\nJava 5以前实现多线程有两种实现方法：一种是继承Thread类；另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为，推荐使用后者，因为Java中的继承是单继承，一个类有一个父类，如果继承了Thread类就无法再继承其他类了，显然使用Runnable接口更为灵活。\n\n实现Runnable接口相比继承Thread类有如下优势：\n\n1. 可以避免由于Java的单继承特性而带来的局限\n2. 增强程序的健壮性，代码能够被多个程序共享，代码与数据是独立的\n3. 适合多个相同程序代码的线程区处理同一资源的情况\n\n补充：Java 5以后创建线程还有第三种方式：实现Callable接口，该接口中的call方法可以在线程执行结束时产生一个返回值，代码如下所示：\n\n```\nclass MyTask implements Callable<Integer> {  \n    private int upperBounds;  \n      \n    public MyTask(int upperBounds) {  \n        this.upperBounds = upperBounds;  \n    }  \n      \n    @Override  \n    public Integer call() throws Exception {  \n        int sum = 0;   \n        for(int i = 1; i <= upperBounds; i++) {  \n            sum += i;  \n        }  \n        return sum;  \n    }  \n      \n}  \n  \npublic class Test {  \n  \n    public static void main(String[] args) throws Exception {  \n        List<Future<Integer>> list = new ArrayList<>();  \n        ExecutorService service = Executors.newFixedThreadPool(10);  \n        for(int i = 0; i < 10; i++) {  \n            list.add(service.submit(new MyTask((int) (Math.random() * 100))));  \n        }  \n          \n        int sum = 0;  \n        for(Future<Integer> future : list) {  \n            while(!future.isDone()) ;  \n            sum += future.get();  \n        }  \n          \n        System.out.println(sum);  \n    }  \n}  \n```\n\n**线程同步的方法有什么；锁，synchronized块，信号量等**\n\n\n\n**锁的等级：方法锁、对象锁、类锁**\n\n**生产者消费者模式的几种实现，阻塞队列实现，sync关键字实现，lock实现,reentrantLock等**\n\n**ThreadLocal的设计理念与作用，ThreadPool用法与优势（这里在Android SDK原生的AsyncTask底层也有使用）**\n\n**线程池的底层实现和工作原理（建议写一个雏形简版源码实现）**\n\n**几个重要的线程api，interrupt，wait，sleep，stop等等**\n\n**写出生产者消费者模式。**\n\n**ThreadPool用法与优势。**\n\n**Concurrent包里的其他东西：ArrayBlockingQueue、CountDownLatch等等。**\n\n**wait()和sleep()的区别。**\n\nsleep()方法是线程类（Thread）的静态方法，导致此线程暂停执行指定时间，将执行机会给其他线程，但是监控状态依然保持，到时后会自动恢复（线程回到就绪（ready）状态），因为调用sleep 不会释放对象锁。wait()是Object 类的方法，对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行)，进入等待此对象的等待锁定池，只有针对此对象发出notify 方法（或notifyAll）后本线程才进入对象锁定池准备获得对象锁进入就绪状态。\n\n\n\n###3、IO（IO,NIO，目前okio已经被集成Android包）\n\n**IO框架主要用到什么设计模式**\n\nJDK的I/O包中就主要使用到了两种设计模式：Adatper模式和Decorator模式。\n\n\n**NIO包有哪些结构？分别起到的作用？**\n\n\n**NIO针对什么情景会比IO有更好的优化？**\n\n**OKIO底层实现**\n\n"
  },
  {
    "path": "Part2/JavaConcurrent/NIO.md",
    "content": "#NIO\n\n---\n\nJava NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java1.4开始)，Java NIO提供了与标准IO不同的IO工作方式。\n\n###Java NIO: Channels and Buffers（通道和缓冲区）\n\n标准的俄IO基于字节流和字符流进行操作的，而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入通道也类似。\n\n###Java NIO: Non-blocking IO（非阻塞IO）\n\nJava NIO可以让你非阻塞的使用IO，例如：当线程从通道读取数据到缓冲区时，线程还是进行其他事情。当数据被写入到缓冲区时，线程可以继续处理它。从缓冲区写入通道也类似。\n\n###Java NIO: Selectors(选择器)\n\nJava NIO引入了选择器的概念，选择器用于监听多个通道的事件(比如：连接打开，数据到达)。因此，单个的线程可以监听多个数据通道。\n\nNIO由以下核心部分组成：\n\n* Channels\n* Buffers\n* Selectors\n\n**Channel和Buffer**\n\n基本上，所有的IO和NIO都从一个Channel开始。Channel有点像流。数据可以从Channel读到Buffer中，也可以从Buffer写到Channel中\n\n![](http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers1.png)\n\nChannel的实现\n\n* FileChannel\n* DatagramChannel\n* SocketChannel\n* ServerSocketChannel\n\n这些通道涵盖了UDP和TCP网络IO，以及文件IO。\n\n以下是Java NIO里关键的Buffer实现\n\n* ByteBuffer\n* CharBuffer\n* DoubleBuffer\n* FloatBuffer\n* IntBuffer\n* LongBuffer\n* ShortBuffer\n\n这些Buffer覆盖了你能通过IO发送的基本数据类型：byte,short,int,long,float,double和char\n\nJava NIO还有个MappedByteBuffer，用于表示内存映射文件。\n\n**Selextor**\n\nSelector允许单线程处理多个Channel。如果你的应用打开了多个连接(通道)，但每一个连接的流量都很低，使用Selector就会很方便。\n\n例如，在一个聊天服务器中\n\n这是在一个单线程中使用一个Selector处理3个Channel的图示：\n\n![](http://ifeve.com/wp-content/uploads/2013/06/overview-selectors.png)\n\n要使用Selector，得向Selector注册Channel，然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子有如新连接进来，数据接送等。\n\n\n##Channel\n---\n\nJava NIO的通道类似流，但又有些不同：\n\n* 既可以从通道中读取数据，又可以写数据到通道。但流的读写通常是单向的。\n* 通道可以异步的读写。\n* 通道的数据总是要先读到一个Buffer，或者总要从一个Buffer中写入。\n\n正如上面所说，从通道读取数据到缓冲区，从缓冲区写入数据到通道。\n\n**Channel的实现**\n\n* FileChannel  从文件中读取数据\n* DataChannel  能通过UDP读写网络中的数据\n* SocketChannel   能通过TCP读写网络中的数据\n* ServerSocketChannel   可以监听新进来的TCP连接，像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel\n\n\n基本的Channel示例\n\n下面是一个使用FileChannel读取数据到Buffer中的示例\n\n```\nRandomAccessFile aFile = new RandomAccessFile(\"data/nio-data.txt\", \"rw\");\nFileChannel inChannel = aFile.getChannel();\nByteBuffer buf = ByteBuffer.allocate(48);\nint bytesRead = inChannel.read(buf);\nwhile (bytesRead != -1) {\nSystem.out.println(\"Read \" + bytesRead);\nbuf.flip();\nwhile(buf.hasRemaining()){\nSystem.out.print((char) buf.get());\n}\nbuf.clear();\nbytesRead = inChannel.read(buf);\n}\naFile.close();\n\n```\n注意 buf.flip() 的调用，首先读取数据到Buffer，然后反转Buffer,接着再从Buffer中读取数据。\n\n\n##Buffer\n\nJava NIO中的Buffer用于和NIO通道进行交互。如你所知，数据是从通道读入缓冲区，从缓冲区写入到通道中的。\n\n缓冲区本质是一块可以写入数据，然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象，并提供了一组方法，用来方便的访问这块内存。\n\n**Buffer的基本用法**\n\n使用Buffer读写数据一般遵循以下四个步骤：\n\n1. 写入数据到Buffer\n2. 调用flip()方法\n3. 从Buffer中读取数据\n4. 调用clear()方法或者compact()方法\n\n\n\n\n\n"
  },
  {
    "path": "Part2/JavaConcurrent/Synchronized.md",
    "content": "#synchronized\n---\n\n在并发编程中，多线程同时并发访问的资源叫做临界资源，当多个线程同时访问对象并要求操作相同资源时，分割了原子操作就有可能出现数据的不一致或数据不完整的情况，为避免这种情况的发生，我们会采取同步机制，以确保在某一时刻，方法内只允许有一个线程。\n\n采用synchronized修饰符实现的同步机制叫做互斥锁机制，它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记)，当线程拥有这个锁标记时才能访问这个资源，没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁，这个锁是为了分配给线程的，防止打断原子操作。每个对象的锁只能分配给一个线程，因此叫做互斥锁。\n\n这里就使用同步机制获取互斥锁的情况，进行几点说明：\n\n1. 如果同一个方法内同时有两个或更多线程，则每个线程有自己的局部变量拷贝。\n2. 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时，该线程便获取了该实例的对象级别锁，其他线程这时如果要访问synchronized同步代码块或同步方法，便需要阻塞等待，直到前面的线程从同步代码块或方法中退出，释放掉了该对象级别锁。\n3. 访问同一个类的不同实例对象中的同步代码块，不存在阻塞等待获取对象锁的问题，因为它们获取的是各自实例的对象级别锁，相互之间没有影响。\n4. 持有一个对象级别锁不会阻止该线程被交换出来，也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁（即进入了synchronized修饰的代码块或方法中）时，线程也有可能被交换出去，此时线程B有可能获取执行该对象中代码的时间，但它只能执行非同步代码（没有用synchronized修饰），当执行到同步代码时，便会被阻塞，此时可能线程规划器又让A线程运行，A线程继续持有对象级别锁，当A线程退出同步代码时（即释放了对象级别锁），如果B线程此时再运行，便会获得该对象级别锁，从而执行synchronized中的代码。\n5. 持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如，在一个类中有三个synchronized方法a，b，c，当线程A正在执行一个实例对象M中的方法a时，它便获得了该对象级别锁，那么其他的线程在执行同一实例对象（即对象M）中的代码时，便会在所有的synchronized方法处阻塞，即在方法a，b，c处都要被阻塞，等线程A释放掉对象级别锁时，其他的线程才可以去执行方法a，b或者c中的代码，从而获得该对象级别锁。\n6. 使用synchronized（obj）同步语句块，可以获取指定对象上的对象级别锁。obj为对象的引用，如果获取了obj对象上的对象级别锁，在并发访问obj对象时时，便会在其synchronized代码处阻塞等待，直到获取到该obj对象的对象级别锁。当obj为this时，便是获取当前对象的对象级别锁。\n7. 类级别锁被特定类的所有示例共享，它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。\n8. 互斥是实现同步的一种手段，临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后，会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求，在执行monitorenter指令时，首先要尝试获取对象的锁，如果获得了锁，把锁的计数器加1，相应地，在执行monitorexit指令时会将锁计数器减1，当计数器为0时，锁便被释放了。由于synchronized同步块对同一个线程是可重入的，因此一个线程可以多次获得同一个对象的互斥锁，同样，要释放相应次数的该互斥锁，才能最终释放掉该锁。\n\n##内存可见性\n\n加锁（synchronized同步）的功能不仅仅局限于互斥行为，同时还存在另外一个重要的方面：内存可见性。我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态，而且还希望确保当一个线程修改了对象状态后，其他线程能够看到该变化。而线程的同步恰恰也能够实现这一点。\n\n内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。为了确保所有的线程都能看到共享变量的最新值，可以在所有执行读操作或写操作的线程上加上同一把锁。下图示例了同步的可见性保证。\n\n![](http://img.blog.csdn.net/20131212211029125)\n\n当线程A执行某个同步代码块时，线程B随后进入由同一个锁保护的同步代码块，这种情况下可以保证，当锁被释放前，A看到的所有变量值（锁释放前，A看到的变量包括y和x）在B获得同一个锁后同样可以由B看到。换句话说，当线程B执行由锁保护的同步代码块时，可以看到线程A之前在同一个锁保护的同步代码块中的所有操作结果。如果在线程A unlock M之后，线程B才进入lock M，那么线程B都可以看到线程A unlock M之前的操作，可以得到i=1，j=1。如果在线程B unlock M之后，线程A才进入lock M，那么线程B就不一定能看到线程A中的操作，因此j的值就不一定是1。\n\n现在考虑如下代码：\n\n```\npublic class  MutableInteger  \n{  \n    private int value;  \n  \n    public int get(){  \n        return value;  \n    }  \n    public void set(int value){  \n        this.value = value;  \n    }  \n}  \n```\n\n以上代码中，get和set方法都在没有同步的情况下访问value。如果value被多个线程共享，假如某个线程调用了set，那么另一个正在调用get的线程可能会看到更新后的value值，也可能看不到。\n\n通过对set和get方法进行同步，可以使MutableInteger成为一个线程安全的类，如下：\n\n```\npublic class  SynchronizedInteger  \n{  \n    private int value;  \n  \n    public synchronized int get(){  \n        return value;  \n    }  \n    public synchronized void set(int value){  \n        this.value = value;  \n    }  \n}  \n```\n\n对set和get方法进行了同步，加上了同一把对象锁，这样get方法可以看到set方法中value值的变化，从而每次通过get方法取得的value的值都是最新的value值。     \n     "
  },
  {
    "path": "Part2/JavaConcurrent/Thread和Runnable实现多线程的区别.md",
    "content": "#Thread和Runnable实现多线程的区别\n---\n\nJava中实现多线程有两种方法：继承Thread、实现Runnable接口，在程序开发中只要是多线程，肯定永远以实现Runnable接口为主，因为实现Runnable接口相比继承Thread类有如下优势：\n\n1. 可以避免由于Java的单继承特性而带来的局限\n2. 增强程序的健壮性，代码能够被多个线程共享，代码与数据是独立的\n3. 适合多个相同程序的线程区处理同一资源的情况\n\n首先通过Thread类实现\n\n```\nclass MyThread extends Thread{  \n    private int ticket = 5;  \n    public void run(){  \n        for (int i=0;i<10;i++)  \n        {  \n            if(ticket > 0){  \n                System.out.println(\"ticket = \" + ticket--);  \n            }  \n        }  \n    }  \n}  \n  \npublic class ThreadDemo{  \n    public static void main(String[] args){  \n        new MyThread().start();  \n        new MyThread().start();  \n        new MyThread().start();  \n    }  \n} \n```\n\n运行结果：\n\n```\nticket = 5\nticket = 4\nticket = 5\nticket = 5\nticket = 4\nticket = 3\nticket = 2\nticket = 1\nticket = 4\nticket = 3\nticket = 3\nticket = 2\nticket = 1\nticket = 2\nticket = 1\n```\n\n每个线程单独卖了5张票，即独立的完成了买票的任务，但实际应用中，比如火车站售票，需要多个线程去共同完成任务，在本例中，即多个线程共同买5张票。\n\n通过实现Runnable借口实现的多线程程序\n\n```\nclass MyThread implements Runnable{  \n    private int ticket = 5;  \n    public void run(){  \n        for (int i=0;i<10;i++)  \n        {  \n            if(ticket > 0){  \n                System.out.println(\"ticket = \" + ticket--);  \n            }  \n        }  \n    }  \n}  \n  \npublic class RunnableDemo{  \n    public static void main(String[] args){  \n        MyThread my = new MyThread();  \n        new Thread(my).start();  \n        new Thread(my).start();  \n        new Thread(my).start();  \n    }  \n} \n```\n\n运行结果\n\n```\nticket = 5\nticket = 2\nticket = 1\nticket = 3\nticket = 4\n```\n\n* 在第二种方法(Runnable)中，ticket输出的顺序并不是54321，这是因为线程执行的时机难以预测。ticket并不是原子操作。\n* 在第一种方法中，我们new了3个Thread对象，即三个线程分别执行三个对象中的代码，因此便是三个线程去独立地完成卖票的任务；而在第二种方法中，我们同样也new了3个Thread对象，但只有一个Runnable对象，3个Thread对象共享这个Runnable对象中的代码，因此，便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象，作为参数分别传入3个Thread对象中，那么3个线程便会独立执行各自Runnable对象中的代码，即3个线程各自卖5张票。\n* 在第二种方法中，由于3个Thread对象共同执行一个Runnable对象中的代码，因此可能会造成线程的不安全，比如可能ticket会输出-1（如果我们System.out....语句前加上线程休眠操作，该情况将很有可能出现），这种情况的出现是由于，一个线程在判断ticket为1>0后，还没有来得及减1，另一个线程已经将ticket减1，变为了0，那么接下来之前的线程再将ticket减1，便得到了-1。这就需要加入同步操作（即互斥锁），确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中，并不需要加入同步操作，因为每个线程执行自己Thread对象中的代码，不存在多个线程共同执行同一个方法的情况。\n"
  },
  {
    "path": "Part2/JavaConcurrent/thread与runable如何实现多线程.md",
    "content": "##Thread与Runable如何实现多线程?\n\nJava实现多线程有两种途径：继承Thread类或者实现Runnable接口。Runnable是接口\n\nRunnable是接口，建议用接口的方式生成线程，因为接口可以实现多继承，况且Runnable只有一个run方法，很适合继承。\n\n在使用Thread方法的时候只需继承Thread，并且new一个实例出来，调用start()方法既可以启动一个线程\n\n[ java多线程 Thread 和Runnable](http://blog.csdn.net/luyysea/article/details/7995351)"
  },
  {
    "path": "Part2/JavaConcurrent/volatile变量修饰符.md",
    "content": "#volatile变量修饰符\n---\n\n##volatile用处说明\n\n在JDK1.2之前，Java的类型模型实现总是从主存(即共享内存)读取变量，是不需要进行特别的注意的。而随着JVM的成熟和优化，现在在多线程环境下volatile关键字的使用变的非常重要。\n\n在当前的Java内存模型下，线程可以把变量保存在本地内存(比如机器的寄存器)中，而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值，而另一个线程还在继续使用它在寄存器中的变量值的拷贝，造成数据的不一致。\n\n要解决这个问题，就需要把变量声明为volatile，这就指示JVM，这个变量是不稳定的，每次使用它都到主存中进行读取。一般来说，多任务环境下，各任务间共享的变量都应该加volatile修饰符。\n\nvolatile修饰de成员变量在每次被线程访问时，都强迫从共享内存中重读该成员变量的值。而且，当成员变量发生变化时，强迫线程将变化值回写到共享内存。这样在任何时刻，两个不同的线程总是看到某个成员变量的同一个值。\n\nJava语言规范中指出：为了获得最佳速度，允许线程保存成员变量的私有拷贝，而且只当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。\n\n这样当多个线程同时与某个对象交互时，就必须注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM：对于这个成员变量，不能保存它的私有拷贝，而应直接与共享成员变量交互。\n\nvolatile是一种稍弱的同步机制，在访问volatile变量时不会执行加锁操作，也就不会执行线程阻塞，因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。\n\n使用建议：在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中，或者为常量时，或者为常量时，没必要使用volatile。\n\n由于使用volatile屏蔽掉了JVM中必要的代码优化，所以在效率上比较低，因此一定在必要时才使用此关键字。\n\n下面给出一段代码，通过其运行结果来说明使用关键字volatile产生的差异，但实际上遇到了意料之外的问题：\n\n```\n\n```"
  },
  {
    "path": "Part2/JavaConcurrent/使用wait notify notifyall实现线程间通信.md",
    "content": "#使用wait/notify/notifyAll实现线程间通信\n---\n\n在Java中，可以通过配合调用Object对象的wait（）方法和notify（）方法或notifyAll（）方法来实现线程间的通信。在线程中调用wait（）方法，将阻塞等待其他线程的通知（其他线程调用notify（）方法或notifyAll（）方法），在线程中调用notify（）方法或notifyAll（）方法，将通知其他线程从wait（）方法处返回。\n\nObject是所有类的超类，它有5个方法组成了等待/通知机制的核心：notify（）、notifyAll（）、wait（）、wait（long）和wait（long，int）。在Java中，所有的类都从Object继承而来，因此，所有的类都拥有这些共有方法可供使用。而且，由于他们都被声明为final，因此在子类中不能覆写任何一个方法。\n\n这里详细说明一下各个方法在使用中需要注意的几点：\n\n1、wait（）\n\n```\npublic final void wait()  throws InterruptedException,IllegalMonitorStateException\n```\n\n该方法用来将当前线程置入休眠状态，直到接到通知或被中断为止。在调用wait（）之前，线程必须要获得该对象的对象级别锁，即只能在同步方法或同步块中调用wait（）方法。进入wait（）方法后，当前线程释放锁。在从wait（）返回前，线程与其他线程竞争重新获得锁。如果调用wait（）时，没有持有适当的锁，则抛出IllegalMonitorStateException，它是RuntimeException的一个子类，因此，不需要try-catch结构。\n\n2、notify（）\n\n```\npublic final native void notify() throws IllegalMonitorStateException\n```\n\n该方法也要在同步方法或同步块中调用，即在调用前，线程也必须要获得该对象的对象级别锁，的如果调用notify（）时没有持有适当的锁，也会抛出IllegalMonitorStateException。\n\n该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待，则线程规划器任意挑选出其中一个wait（）状态的线程来发出通知，并使它等待获取该对象的对象锁（notify后，当前线程不会马上释放该对象锁，wait所在的线程并不能马上获取该对象锁，要等到程序退出synchronized代码块后，当前线程才会释放锁，wait所在的线程也才可以获取该对象锁），但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后，它会释放掉该对象锁，此时如果该对象没有再次使用notify语句，则即便该对象已经空闲，其他wait状态等待的线程由于没有得到该对象的通知，会继续阻塞在wait状态，直到这个对象发出一个notify或notifyAll。这里需要注意：它们等待的是被notify或notifyAll，而不是锁。这与下面的notifyAll（）方法执行后的情况不同。 \n\n3、notifyAll（）\n\n```\npublic final native void notifyAll() throws IllegalMonitorStateException\n```\n\n该方法与notify（）方法的工作方式相同，重要的一点差异是：\n\nnotifyAll使所有原来在该对象上wait的线程统统退出wait的状态（即全部被唤醒，不再等待notify或notifyAll，但由于此时还没有获取到该对象锁，因此还不能继续往下执行），变成等待获取该对象上的锁，一旦该对象锁被释放（notifyAll线程退出调用了notifyAll的synchronized代码块的时候），他们就会去竞争。如果其中一个线程获得了该对象锁，它就会继续往下执行，在它退出synchronized代码块，释放锁后，其他的已经被唤醒的线程将会继续竞争获取该锁，一直进行下去，直到所有被唤醒的线程都执行完毕。\n\n4、wait（long）和wait（long,int）\n\n显然，这两个方法是设置等待超时时间的，后者在超值时间上加上ns，精度也难以达到，因此，该方法很少使用。对于前者，如果在等待线程接到通知或被中断之前，已经超过了指定的毫秒数，则它通过竞争重新获得锁，并从wait（long）返回。另外，需要知道，如果设置了超时时间，当wait（）返回时，我们不能确定它是因为接到了通知还是因为超时而返回的，因为wait（）方法不会返回任何相关的信息。但一般可以通过设置标志位来判断，在notify之前改变标志位的值，在wait（）方法后读取该标志位的值来判断，当然为了保证notify不被遗漏，我们还需要另外一个标志位来循环判断是否调用wait（）方法。\n\n\n深入理解：\n\n* 如果线程调用了对象的wait（）方法，那么线程便会处于该对象的等待池中，等待池中的线程不会去竞争该对象的锁。\n* 当有线程调用了对象的notifyAll（）方法（唤醒所有wait线程）或notify（）方法（只随机唤醒一个wait线程），被唤醒的的线程便会进入该对象的锁池中，锁池中的线程会去竞争该对象锁。\n* 优先级高的线程竞争到对象锁的概率大，假若某线程没有竞争到该对象锁，它还会留在锁池中，唯有线程再次调用wait（）方法，它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行，直到执行完了synchronized代码块，它会释放掉该对象锁，这时锁池中的线程会继续竞争该对象锁。\n"
  },
  {
    "path": "Part2/JavaConcurrent/可重入内置锁.md",
    "content": "#可重入内置锁\n---\n\n每个Java对象都可以用做一个实现同步的锁，这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获取锁，并且在退出同步代码块时会自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。\n\n当某个线程请求一个由其他线程持有的锁时，发出请求的线程就会阻塞。然而，由于内置锁是可重入的，因此如果摸个线程试图获得一个已经由它自己持有的锁，那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”，而不是调用。重入的一种实现方法是，为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时，这个锁就被认为是没有被任何线程所持有，当线程请求一个未被持有的锁时，JVM将记下锁的持有者，并且将获取计数值置为1，如果同一个线程再次获取这个锁，计数值将递增，而当线程退出同步代码块时，计数器会相应地递减。当计数值为0时，这个锁将被释放。\n\n重入进一步提升了加锁行为的封装性，因此简化了面向对象并发代码的开发。分析如下程序：\n\n```\npublic class Father  \n{  \n    public synchronized void doSomething(){  \n        ......  \n    }  \n}  \n  \npublic class Child extends Father  \n{  \n    public synchronized void doSomething(){  \n        ......  \n        super.doSomething();  \n    }  \n}  \n```\n\n子类覆写了父类的同步方法，然后调用父类中的方法，此时如果没有可重入的锁，那么这段代码件产生死锁。\n\n由于Fither和Child中的doSomething方法都是synchronized方法，因此每个doSomething方法在执行前都会获取Child对象实例上的锁。如果内置锁不是可重入的，那么在调用super.doSomething时将无法获得该Child对象上的互斥锁，因为这个锁已经被持有，从而线程会永远阻塞下去，一直在等待一个永远也无法获取的锁。重入则避免了这种死锁情况的发生。\n\n同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时，都不会阻碍该线程地执行，因为互斥锁时可重入的。"
  },
  {
    "path": "Part2/JavaConcurrent/多线程环境中安全使用集合API.md",
    "content": "#多线程环境中安全使用集合API\n---\n\n在集合API中，最初设计的Vector和Hashtable是多线程安全的。例如：对于Vector来说，用来添加和删除元素的方法是同步的。如果只有一个线程与Vector的实例交互，那么，要求获取和释放对象锁便是一种浪费，另外在不必要的时候如果滥用同步化，也有可能会带来死锁。因此，对于更改集合内容的方法，没有一个是同步化的。集合本质上是非多线程安全的，当多个线程与集合交互时，为了使它多线程安全，必须采取额外的措施。\n\n在Collections类 中有多个静态方法，它们可以获取通过同步方法封装非同步集合而得到的集合：\n\n* public static Collection synchronizedCollention(Collection c)\n* public static List synchronizedList(list l)\n* public static Map synchronizedMap(Map m)\n* public static Set synchronizedSet(Set s)\n* public static SortedMap synchronizedSortedMap(SortedMap sm)\n* public static SortedSet synchronizedSortedSet(SortedSet ss)\n\n\n这些方法基本上返回具有同步集合方法版本的新类。比如，为了创建多线程安全且由ArrayList支持的List，可以使用如下代码：\n\n```\nList list = Collection.synchronizedList(new ArrayList());\n```\n\n注意，ArrayList实例马上封装起来，不存在对未同步化ArrayList的直接引用（即直接封装匿名实例）。这是一种最安全的途径。如果另一个线程要直接引用ArrayList实例，它可以执行非同步修改。\n\n下面给出一段多线程中安全遍历集合元素的示例。我们使用Iterator逐个扫描List中的元素，在多线程环境中，当遍历当前集合中的元素时，一般希望阻止其他线程添加或删除元素。安全遍历的实现方法如下：\n\n```\nimport java.util.*;  \n  \npublic class SafeCollectionIteration extends Object {  \n    public static void main(String[] args) {  \n        //为了安全起见，仅使用同步列表的一个引用，这样可以确保控制了所有访问  \n        //集合必须同步化，这里是一个List  \n        List wordList = Collections.synchronizedList(new ArrayList());  \n  \n        //wordList中的add方法是同步方法，会获取wordList实例的对象锁  \n        wordList.add(\"Iterators\");  \n        wordList.add(\"require\");  \n        wordList.add(\"special\");  \n        wordList.add(\"handling\");  \n  \n        //获取wordList实例的对象锁，  \n        //迭代时，阻塞其他线程调用add或remove等方法修改元素  \n        synchronized ( wordList ) {  \n            Iterator iter = wordList.iterator();  \n            while ( iter.hasNext() ) {  \n                String s = (String) iter.next();  \n                System.out.println(\"found string: \" + s + \", length=\" + s.length());  \n            }  \n        }  \n    }  \n}  \n```\n\n这里需要注意的是：在Java语言中，大部分的线程安全类都是相对线程安全的，它能保证对这个对象单独的操作时线程安全的，我们在调用的时候不需要额外的保障措施，但是对于一些特定的连续调用，就可能需要在调用端使用额外的同步手段来保证调用的正确性。例如Vector、HashTable、Collections的synchronizedXxxx（）方法包装的集合等。"
  },
  {
    "path": "Part2/JavaConcurrent/守护线程与阻塞线程.md",
    "content": "#守护线程与阻塞线程的四种情况\n---\n\nJava中有两类线程：User Thread(用户线程)、Daemon Thread(守护线程)\n\n用户线程即运行在前台的线程，而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务，而且仅在普通、非守护线程仍然运行时才需要，比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程，而用户线程都已经退出运行时，VM就会退出，因为如果没有了守护者，也就没有继续运行程序的必要了。如果有非守护线程仍然活着，VM就不会退出。\n\n守护线程并非只有虚拟机内部提供，用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。\n\n虽然守护线程可能非常有用，但必须小心确保其它所有非守护线程消亡时，不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前，守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了，虚拟机也就退出运行了。因此，不要再守护线程中执行业务逻辑操作(比如对数据的读写等)。\n\n还有几点：\n\n1. setDaemon(true)必须在调用线程的start()方法之前设置，否则会跑出IllegalThreadStateException异常。\n2. 在守护线程中产生的新线程也是守护线程\n3. 不要认为所有的应用都可以分配给守护线程来进行服务，比如读写操作或者计算逻辑。\n\n\n##线程阻塞\n\n线程可以阻塞于四种状态：\n\n1. 当线程执行Thread.sleep()时，它一直阻塞到指定的毫秒时间之后，或者阻塞被另一个线程打断\n2. 当线程碰到一条wait()语句时，它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒 时间为止(若指定了超时值的话)\n3. 线程阻塞与不同的I/O的方式有多种。常见的一种方式是InputStream的read()方法，该方法一直阻塞到从流中读取一个字节的数据为止，它可以无限阻塞，因此不能指定超时时间\n4. 线程也可以阻塞等待获取某个对象锁的排它性访问权限(即等待获得synchronized语句必须的锁时阻塞)\n\n并非所有的阻塞状态都是可中断的，以上阻塞状态的前两种可以被中断，后两种不会对中断做出反应。\n"
  },
  {
    "path": "Part2/JavaConcurrent/实现内存可见的两种方法比较：加锁和volatile变量.md",
    "content": "#并发编程中实现内存可见的两种方法比较：加锁和volatile变量\n---\n\n 1. volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作，因此也就不会使执行线程阻塞，因此volatile变量是一种比synchronized关键字更轻量级的同步机制。\n\n2. 从内存可见性的角度看，写入volatile变量相当于退出同步代码块，而读取volatile变量相当于进入同步代码块。\n\n3. 在代码中如果过度依赖volatile变量来控制状态的可见性，通常会比使用锁的代码更脆弱，也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时，才应该使用它。一般来说，用同步机制会更安全些。\n\n4. 加锁机制（即同步机制）既可以确保可见性又可以确保原子性，而volatile变量只能确保可见性，原因是声明为volatile的简单变量如果当前值与该变量以前的值相关，那么volatile关键字不起作用，也就是说如下的表达式都不是原子操作：“count++”、“count = count+1”。\n\n当且仅当满足以下所有条件时，才应该使用volatile变量：\n\n1. 对变量的写入操作不依赖变量的当前值，或者你能确保只有单个线程更新变量的值。\n2. 该变量没有包含在具有其他变量的不变式中。\n\n\n总结：在需要同步的时候，第一选择应该是synchronized关键字，这是最安全的方式，尝试其他任何方式都是有风险的。尤其在、jdK1.5之后，对synchronized同步机制做了很多优化，如：自适应的自旋锁、锁粗化、锁消除、轻量级锁等，使得它的性能明显有了很大的提升。"
  },
  {
    "path": "Part2/JavaConcurrent/死锁.md",
    "content": "#死锁\n---\n\n当线程需要同时持有多个锁时，有可能产生死锁。考虑如下情形：\n\n线程A当前持有互斥所锁lock1，线程B当前持有互斥锁lock2。接下来，当线程A仍然持有lock1时，它试图获取lock2，因为线程B正持有lock2，因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候，也在试图获取lock1，因为线程A正持有lock1，因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁的释放，而二者却又都没释放自己所持有的锁，这时二者便会一直阻塞下去。这种情形称为死锁。\n\n下面给出一个两个线程间产生死锁的示例，如下：\n\n```\n\npublic class Deadlock {\n\n\tprivate String objID;\n\n\tpublic Deadlock(String id) {\n\t\tobjID = id;\n\t}\n\n\tpublic synchronized void checkOther(Deadlock other) {\n\t\tprint(\"entering checkOther()\");  \n        try { Thread.sleep(2000); }   \n        catch ( InterruptedException x ) { }  \n        print(\"in checkOther() - about to \" + \"invoke 'other.action()'\");  \n      //调用other对象的action方法，由于该方法是同步方法，因此会试图获取other对象的对象锁  \n        other.action();  \n        print(\"leaving checkOther()\");  \n\t}\n\t\n\tpublic synchronized void action() {  \n        print(\"entering action()\");  \n        try { Thread.sleep(500); }   \n        catch ( InterruptedException x ) { }  \n        print(\"leaving action()\");  \n    }  \n\n\tpublic void print(String msg) {\n\t\tthreadPrint(\"objID=\" + objID + \" - \" + msg);\n\t}\n\t\n\tpublic static void threadPrint(String msg) {  \n        String threadName = Thread.currentThread().getName();  \n        System.out.println(threadName + \": \" + msg);  \n    }  \n\t\n\tpublic static void main(String[] args) {\n\t\tfinal Deadlock obj1 = new Deadlock(\"obj1\");  \n        final Deadlock obj2 = new Deadlock(\"obj2\");  \n        \n        Runnable runA = new Runnable() {  \n            public void run() {  \n                obj1.checkOther(obj2);  \n            }  \n        };  \n        \n        Thread threadA = new Thread(runA, \"threadA\");  \n        threadA.start();  \n  \n        try { Thread.sleep(200); }   \n        catch ( InterruptedException x ) { }  \n        \n        Runnable runB = new Runnable() {  \n            public void run() {  \n                obj2.checkOther(obj1);  \n            }  \n        };  \n        \n        Thread threadB = new Thread(runB, \"threadB\");  \n        threadB.start();  \n  \n        try { Thread.sleep(5000); }   \n        catch ( InterruptedException x ) { }  \n  \n        threadPrint(\"finished sleeping\");  \n  \n        threadPrint(\"about to interrupt() threadA\"); \n        \n        threadA.interrupt();  \n        \n        try { Thread.sleep(1000); }   \n        catch ( InterruptedException x ) { }  \n  \n        threadPrint(\"about to interrupt() threadB\");  \n        threadB.interrupt();  \n  \n        try { Thread.sleep(1000); }   \n        catch ( InterruptedException x ) { }  \n  \n        threadPrint(\"did that break the deadlock?\");  \n\t}\n}\n\n```\n\n运行结果：\n\n```\nthreadA: objID=obj1 - entering checkOther()\nthreadB: objID=obj2 - entering checkOther()\nthreadA: objID=obj1 - in checkOther() - about to invoke 'other.action()'\nthreadB: objID=obj2 - in checkOther() - about to invoke 'other.action()'\nmain: finished sleeping\nmain: about to interrupt() threadA\nmain: about to interrupt() threadB\nmain: did that break the deadlock?\n```\n\n从结果中可以看出，在执行到other.action（）时，由于两个线程都在试图获取对方的锁，但对方都没有释放自己的锁，因而便产生了死锁，在主线程中试图中断两个线程，但都无果。\n\n大部分代码并不容易产生死锁，死锁可能在代码中隐藏相当长的时间，等待不常见的条件地发生，但即使是很小的概率，一旦发生，便可能造成毁灭性的破坏。避免死锁是一件困难的事，遵循以下原则有助于规避死锁： \n\n1. 只在必要的最短时间内持有锁，考虑使用同步语句块代替整个同步方法；\n2. 尽量编写不在同一时刻需要持有多个锁的代码，如果不可避免，则确保线程持有第二个锁的时间尽量短暂；\n3. 创建和使用一个大锁来代替若干小锁，并把这个锁用于互斥，而不是用作单个对象的对象级别锁；"
  },
  {
    "path": "Part2/JavaConcurrent/生产者和消费者问题.md",
    "content": "#生产者和消费者问题\n\n\n```\npackage 生产者消费者;\n\npublic class ProducerConsumerTest {\n\n\tpublic static void main(String[] args) {\n\t\tPublicResource resource = new PublicResource();\n\t\tnew Thread(new ProducerThread(resource)).start();\n        new Thread(new ConsumerThread(resource)).start();\n        new Thread(new ProducerThread(resource)).start();\n        new Thread(new ConsumerThread(resource)).start();\n        new Thread(new ProducerThread(resource)).start();\n        new Thread(new ConsumerThread(resource)).start();\n\t}\n}\n```\n```\npackage 生产者消费者;\n/**\n * 生产者线程，负责生产公共资源\n * @author dream\n *\n */\npublic class ProducerThread implements Runnable{\n\n\tprivate PublicResource resource;\n\n\t\n\tpublic ProducerThread(PublicResource resource) {\n\t\tthis.resource = resource;\n\t}\n\n\n\t@Override\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tThread.sleep((long) (Math.random() * 1000));\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t\tresource.increase();\n\t\t}\n\t}\n\t\n\t\n}\n```\n\n```\npackage 生产者消费者;\n\n/**\n * 消费者线程，负责消费公共资源\n * @author dream\n *\n */\npublic class ConsumerThread implements Runnable{\n\n\tprivate PublicResource resource;\n\t\n\t\n\tpublic ConsumerThread(PublicResource resource) {\n\t\tthis.resource = resource;\n\t}\n\n\n\t@Override\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tThread.sleep((long) (Math.random() * 1000));\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t\tresource.decrease();\n\t\t}\n\t\t\n\t}\n\t\n\n}\n```\n\n```\npackage 生产者消费者;\n\n/**\n * 公共资源类\n * @author dream\n *\n */\npublic class PublicResource {\n\n\tprivate int number = 0;\n\tprivate int size = 10;\n\t\n\t/**\n\t * 增加公共资源\n\t */\n\tpublic synchronized void increase()\n\t{\n\t\twhile (number >= size) {\n\t\t\ttry {\n\t\t\t\twait();\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\tnumber++;\n\t\tSystem.out.println(\"生产了1个，总共有\" + number);\n\t\tnotifyAll();\n\t}\n\t\n\t\n\t/**\n\t * 减少公共资源\n\t */\n\tpublic synchronized void decrease()\n\t{\n\t\twhile (number <= 0) {\n\t\t\ttry {\n\t\t\t\twait();\n\t\t\t} catch (InterruptedException e) {\n\t\t\t\t// TODO Auto-generated catch block\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t\tnumber--;\n\t\tSystem.out.println(\"消费了1个，总共有\" + number);\n\t\tnotifyAll();\n\t}\n}\n\n```\n"
  },
  {
    "path": "Part2/JavaConcurrent/线程中断.md",
    "content": "#线程中断\n---\n\n##使用interrupt()中断线程\n\n当一个线程运行时，另一个线程可以调用对应的Thread对象的interrupt（）方法来中断它，该方法只是在目标线程中设置一个标志，表示它已经被中断，并立即返回。这里需要注意的是，如果只是单纯的调用interrupt（）方法，线程并没有实际被中断，会继续往下执行。\n\n演示休眠线程的中断\n\n```\n\npublic class SleepInterrupt extends Object implements Runnable{\n\n\t@Override\n\tpublic void run() {\n\t\t\n\t\ttry {\n\t\t\tSystem.out.println(\"in run() - about to sleep for 20 seconds\");\n\t\t\tThread.sleep(20000);\n\t\t\tSystem.out.println(\"in run() - woke up\");\n\t\t} catch (InterruptedException e) {\n\t\t\tSystem.out.println(\"in run() - interrupted while sleeping\");\n\t\t\t//处理完中断异常后，返回到run()方法入口\n\t\t\t//如果没有return,线程不会实际被中断，它会继续打印下面的信息\n\t\t\treturn;\n\t\t}\n\t\tSystem.out.println(\"in run() - leaving normally\");\n\t}\n\t\n\tpublic static void main(String[] args) {\n\t\tSleepInterrupt si = new SleepInterrupt();\n\t\tThread t = new Thread(si);\n\t\tt.start();\n\t\t//住线程休眠2秒，从而确保刚才启动的线程有机会执行一段时间\n\t\ttry {\n\t\t\tThread.sleep(2000);\n\t\t} catch (InterruptedException e) {\n\t\t\te.printStackTrace();\n\t\t}\n\t\tSystem.out.println(\"in main() - interrupting other thread\"); \n\t\t//中断线程t\n\t\tt.interrupt();\n\t\tSystem.out.println(\"in main() - leaving\");\n\t}\n\n}\n\n```\n\n运行结果如下：\n\n```\nin run() - about to sleep for 20 seconds\nin main() - interrupting other thread\nin main() - leaving\nin run() - interrupted while sleeping\n```\n\n主线程启动新线程后，自身休眠2秒钟，允许新线程获得运行时间。新线程打印信息“about to sleep for 20 seconds”后，继而休眠20秒钟，大约2秒钟后，main线程通知新线程中断，那么新线程的20秒的休眠将被打断，从而抛出InterruptException异常，执行跳转到catch块，打印出“interrupted while sleeping”信息，并立即从run（）方法返回，然后消亡，而不会打印出catch块后面的“leaving normally”信息。\n\n请注意：由于不确定的线程规划，上图运行结果的后两行可能顺序相反，这取决于主线程和新线程哪个先消亡。但前两行信息的顺序必定如上图所示。\n\n另外，如果将catch块中的return语句注释掉，则线程在抛出异常后，会继续往下执行，而不会被中断，从而会打印出”leaving normally“信息。\n\n##待决中断\n---\n在上面的例子中，sleep（）方法的实现检查到休眠线程被中断，它会相当友好地终止线程，并抛出InterruptedException异常。另外一种情况，如果线程在调用sleep（）方法前被中断，那么该中断称为待决中断，它会在刚调用sleep（）方法时，立即抛出InterruptedException异常。\n\n```\n\npublic class PendingInterrupt extends Object{\n\n\tpublic static void main(String[] args) {\n\t\t//如果输入了参数，则在main线程中中断当前线程（即main线程）\n\t\tif(args.length > 0){\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\t\t//获取当前时间\n\t\tlong startTime = System.currentTimeMillis();\n\t\ttry {\n\t\t\tThread.sleep(2000);\n\t\t\tSystem.out.println(\"was NOT interrupted\");\n\t\t} catch (InterruptedException e) {\n\t\t\tSystem.out.println(\"was interrupted\");\n\t\t}\n\t\t//计算中间代码执行的时间\n\t\tSystem.out.println(\"elapsedTime=\" + (System.currentTimeMillis() - startTime));\n\t}\n}\n\n```\n\n如果PendingInterrupt不带任何命令行参数，那么线程不会被中断，最终输出的时间差距应该在2000附近（具体时间由系统决定，不精确），如果PendingInterrupt带有命令行参数，则调用中断当前线程的代码，但main线程仍然运行，最终输出的时间差距应该远小于2000，因为线程尚未休眠，便被中断，因此，一旦调用sleep（）方法，会立即打印出catch块中的信息。执行结果如下:\n\n```\nwas NOT interrupted\nelapsedTime=2001\n\n```\n\n这种模式下，main线程中断它自身。除了将中断标志（它是Thread的内部标志）设置为true外，没有其他任何影响。线程被中断了，但main线程仍然运行，main线程继续监视实时时钟，并进入try块，一旦调用sleep（）方法，它就会注意到待决中断的存在，并抛出InterruptException。于是执行跳转到catch块，并打印出线程被中断的信息。最后，计算并打印出时间差。\n\n##使用isInterrupted（）方法判断中断状态\n---\n\n可以在Thread对象上调用isInterrupted（）方法来检查任何线程的中断状态。这里需要注意：线程一旦被中断，isInterrupted（）方法便会返回true，而一旦sleep（）方法抛出异常，它将清空中断标志，此时isInterrupted（）方法将返回false。\n\n下面的代码演示了isInterrupted（）方法的使用：\n\n```\n\npublic class InterruptCheck extends Object{\n\t\n\tpublic static void main(String[] args) {\n\t\tThread t = Thread.currentThread();\n\t\tSystem.out.println(\"Point A: t.isInterrupted()=\" + t.isInterrupted());  \n        //待决中断，中断自身  \n        t.interrupt();  \n        System.out.println(\"Point B: t.isInterrupted()=\" + t.isInterrupted());  \n        System.out.println(\"Point C: t.isInterrupted()=\" + t.isInterrupted());  \n\t\n        try {\n\t\t\tThread.sleep(2000);\n\t\t\tSystem.out.println(\"was NOT interrupted\");  \n\t\t} catch (InterruptedException e) {\n\t\t\tSystem.out.println(\"was interrupted\");  \n\t\t}\n        //跑出异常后，会清除中断标志，这里会返回false\n        System.out.println(\"Point D: t.isInterrupted()=\" + t.isInterrupted());\n\t}\n\n}\n\n```\n\n运行结果如下：\n\n```\nPoint A: t.isInterrupted()=false\nPoint B: t.isInterrupted()=true\nPoint C: t.isInterrupted()=true\nwas interrupted\nPoint D: t.isInterrupted()=false\n\n```\n\n##使用Thread.interrupted（）方法判断中断状态\n---\n\n可以使用Thread.interrupted（）方法来检查当前线程的中断状态（并隐式重置为false）。又由于它是静态方法，因此不能在特定的线程上使用，而只能报告调用它的线程的中断状态，如果线程被中断，而且中断状态尚不清楚，那么，这个方法返回true。与isInterrupted（）不同，它将自动重置中断状态为false，第二次调用Thread.interrupted（）方法，总是返回false，除非中断了线程。\n\n如下代码演示了Thread.interrupted（）方法的使用：\n\n```\n\npublic class InterruptReset extends Object{\n\t\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(  \n\t            \"Point X: Thread.interrupted()=\" + Thread.interrupted());  \n\t        Thread.currentThread().interrupt();  \n\t        System.out.println(  \n\t            \"Point Y: Thread.interrupted()=\" + Thread.interrupted());  \n\t        System.out.println(  \n\t            \"Point Z: Thread.interrupted()=\" + Thread.interrupted());  \n\t}\n\n}\n\n```\n\n运行结果\n\n\n```\nPoint X: Thread.interrupted()=false\nPoint Y: Thread.interrupted()=true\nPoint Z: Thread.interrupted()=false\n\n```\n\n从结果中可以看出，当前线程中断自身后，在Y点，中断状态为true，并由Thread.interrupted（）自动重置为false，那么下次调用该方法得到的结果便是false。\n\n##补充\n---\n\nyield和join方法的使用\n\n* join方法用线程对象调用，如果在一个线程A中调用另一个线程B的join方法，线程A将会等待线程B执行完毕后再执行。\n* yield可以直接用Thread类调用，yield让出CPU执行权给同等级的线程，如果没有相同级别的线程在等待CPU的执行权，则该线程继续执行。"
  },
  {
    "path": "Part2/JavaConcurrent/线程挂起、恢复与终止的正确方法.md",
    "content": "#线程挂起、恢复与终止的正确方法（含代码）\n---\n\n##挂起和恢复线程\n\nThread 的API中包含两个被淘汰的方法，它们用于临时挂起和重启某个线程，这些方法已经被淘汰，因为它们是不安全的，不稳定的。如果在不合适的时候挂起线程（比如，锁定共享资源时），此时便可能会发生死锁条件——其他线程在等待该线程释放锁，但该线程却被挂起了，便会发生死锁。另外，在长时间计算期间挂起线程也可能导致问题。\n\n下面的代码演示了通过休眠来延缓运行，模拟长时间运行的情况，使线程更可能在不适当的时候被挂起：\n\n```\n\n```"
  },
  {
    "path": "Part2/JavaSE/ArrayList 、 LinkedList 、 Vector 的底层实现和区别.md",
    "content": "#Java基础之集合List-ArrayList、LinkedList、Vector的底层实现和区别\n\n\n\n1. ArrayList底层实际是采用数组实现的（并且该数组的类型是Object类型的）\n2. 如果jdk6,采用Array.copyOf()方法来生成一个新的数组，如果是jdk5，采用的是System.arraycopy()方法（当添加的数据量大于数组的长度的时候）\n3. List list = new ArrayList()时，底层会生成一个长度为10的数组来存放对象\n4. ArrayList、Vector底部都是采用数组实现的\n5. 对于ArrayList，方法都不是同步的，对于Vector，大部分public方法都是同步的\n6. LinkedList采用双向循环列表\n7. 对于ArrayList，查询速度很快，增加和删除（非最后一个节点）操作非常慢（本质上由数组的特性决定的）\n8. 对于LinkedList，查询速度非常慢，增加和删除操作非常快（本质上是由双向循环列表决定的）\n\n参考博客：\n\n[http://blog.csdn.net/sundenskyqq/article/details/27630179](http://blog.csdn.net/sundenskyqq/article/details/27630179)\n"
  },
  {
    "path": "Part2/JavaSE/ArrayList源码剖析.md",
    "content": "##ArrayList简介\n\nArrayList是基于数组实现的，是一个动态数组，其容量能自动增长，类似于C语言中的动态申请内存，动态增长内存。\n\nArrayList不是线程安全的，只能在单线程环境下，多线程环境下可以考虑用collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类，也可以使用concurrent并发包下的CopyOnWriteArrayList类。\n\n ArrayList实现了Serializable接口，因此它支持序列化，能够通过序列化传输，实现了RandomAccess接口，支持快速随机访问，实际上就是通过下标序号进行快速访问，实现了Cloneable接口，能被克隆。\n\nArrayList源码剖析\n\nArrayList的源码如下（加入了比较详细的注释）：\n\n```\npackage java.util;    \n   \npublic class ArrayList<E> extends AbstractList<E>    \n        implements List<E>, RandomAccess, Cloneable, java.io.Serializable    \n{    \n    // 序列版本号    \n    private static final long serialVersionUID = 8683452581122892189L;    \n   \n    // ArrayList基于该数组实现，用该数组保存数据   \n    private transient Object[] elementData;    \n   \n    // ArrayList中实际数据的数量    \n    private int size;    \n   \n    // ArrayList带容量大小的构造函数。    \n    public ArrayList(int initialCapacity) {    \n        super();    \n        if (initialCapacity < 0)    \n            throw new IllegalArgumentException(\"Illegal Capacity: \"+    \n                                               initialCapacity);    \n        // 新建一个数组    \n        this.elementData = new Object[initialCapacity];    \n    }    \n   \n    // ArrayList无参构造函数。默认容量是10。    \n    public ArrayList() {    \n        this(10);    \n    }    \n   \n    // 创建一个包含collection的ArrayList    \n    public ArrayList(Collection<? extends E> c) {    \n        elementData = c.toArray();    \n        size = elementData.length;    \n        if (elementData.getClass() != Object[].class)    \n            elementData = Arrays.copyOf(elementData, size, Object[].class);    \n    }    \n   \n   \n    // 将当前容量值设为实际元素个数    \n    public void trimToSize() {    \n        modCount++;    \n        int oldCapacity = elementData.length;    \n        if (size < oldCapacity) {    \n            elementData = Arrays.copyOf(elementData, size);    \n        }    \n    }    \n   \n   \n    // 确定ArrarList的容量。    \n    // 若ArrayList的容量不足以容纳当前的全部元素，设置 新的容量=“(原始容量x3)/2 + 1”    \n    public void ensureCapacity(int minCapacity) {    \n        // 将“修改统计数”+1，该变量主要是用来实现fail-fast机制的    \n        modCount++;    \n        int oldCapacity = elementData.length;    \n        // 若当前容量不足以容纳当前的元素个数，设置 新的容量=“(原始容量x3)/2 + 1”    \n        if (minCapacity > oldCapacity) {    \n            Object oldData[] = elementData;    \n            int newCapacity = (oldCapacity * 3)/2 + 1;    \n            //如果还不够，则直接将minCapacity设置为当前容量  \n            if (newCapacity < minCapacity)    \n                newCapacity = minCapacity;    \n            elementData = Arrays.copyOf(elementData, newCapacity);    \n        }    \n    }    \n   \n    // 添加元素e    \n    public boolean add(E e) {    \n        // 确定ArrayList的容量大小    \n        ensureCapacity(size + 1);  // Increments modCount!!    \n        // 添加e到ArrayList中    \n        elementData[size++] = e;    \n        return true;    \n    }    \n   \n    // 返回ArrayList的实际大小    \n    public int size() {    \n        return size;    \n    }    \n   \n    // ArrayList是否包含Object(o)    \n    public boolean contains(Object o) {    \n        return indexOf(o) >= 0;    \n    }    \n   \n    //返回ArrayList是否为空    \n    public boolean isEmpty() {    \n        return size == 0;    \n    }    \n   \n    // 正向查找，返回元素的索引值    \n    public int indexOf(Object o) {    \n        if (o == null) {    \n            for (int i = 0; i < size; i++)    \n            if (elementData[i]==null)    \n                return i;    \n            } else {    \n                for (int i = 0; i < size; i++)    \n                if (o.equals(elementData[i]))    \n                    return i;    \n            }    \n            return -1;    \n        }    \n   \n        // 反向查找，返回元素的索引值    \n        public int lastIndexOf(Object o) {    \n        if (o == null) {    \n            for (int i = size-1; i >= 0; i--)    \n            if (elementData[i]==null)    \n                return i;    \n        } else {    \n            for (int i = size-1; i >= 0; i--)    \n            if (o.equals(elementData[i]))    \n                return i;    \n        }    \n        return -1;    \n    }    \n   \n    // 反向查找(从数组末尾向开始查找)，返回元素(o)的索引值    \n    public int lastIndexOf(Object o) {    \n        if (o == null) {    \n            for (int i = size-1; i >= 0; i--)    \n            if (elementData[i]==null)    \n                return i;    \n        } else {    \n            for (int i = size-1; i >= 0; i--)    \n            if (o.equals(elementData[i]))    \n                return i;    \n        }    \n        return -1;    \n    }    \n     \n   \n    // 返回ArrayList的Object数组    \n    public Object[] toArray() {    \n        return Arrays.copyOf(elementData, size);    \n    }    \n   \n    // 返回ArrayList元素组成的数组  \n    public <T> T[] toArray(T[] a) {    \n        // 若数组a的大小 < ArrayList的元素个数；    \n        // 则新建一个T[]数组，数组大小是“ArrayList的元素个数”，并将“ArrayList”全部拷贝到新数组中    \n        if (a.length < size)    \n            return (T[]) Arrays.copyOf(elementData, size, a.getClass());    \n   \n        // 若数组a的大小 >= ArrayList的元素个数；    \n        // 则将ArrayList的全部元素都拷贝到数组a中。    \n        System.arraycopy(elementData, 0, a, 0, size);    \n        if (a.length > size)    \n            a[size] = null;    \n        return a;    \n    }    \n   \n    // 获取index位置的元素值    \n    public E get(int index) {    \n        RangeCheck(index);    \n   \n        return (E) elementData[index];    \n    }    \n   \n    // 设置index位置的值为element    \n    public E set(int index, E element) {    \n        RangeCheck(index);    \n   \n        E oldValue = (E) elementData[index];    \n        elementData[index] = element;    \n        return oldValue;    \n    }    \n   \n    // 将e添加到ArrayList中    \n    public boolean add(E e) {    \n        ensureCapacity(size + 1);  // Increments modCount!!    \n        elementData[size++] = e;    \n        return true;    \n    }    \n   \n    // 将e添加到ArrayList的指定位置    \n    public void add(int index, E element) {    \n        if (index > size || index < 0)    \n            throw new IndexOutOfBoundsException(    \n            \"Index: \"+index+\", Size: \"+size);    \n   \n        ensureCapacity(size+1);  // Increments modCount!!    \n        System.arraycopy(elementData, index, elementData, index + 1,    \n             size - index);    \n        elementData[index] = element;    \n        size++;    \n    }    \n   \n    // 删除ArrayList指定位置的元素    \n    public E remove(int index) {    \n        RangeCheck(index);    \n   \n        modCount++;    \n        E oldValue = (E) elementData[index];    \n   \n        int numMoved = size - index - 1;    \n        if (numMoved > 0)    \n            System.arraycopy(elementData, index+1, elementData, index,    \n                 numMoved);    \n        elementData[--size] = null; // Let gc do its work    \n   \n        return oldValue;    \n    }    \n   \n    // 删除ArrayList的指定元素    \n    public boolean remove(Object o) {    \n        if (o == null) {    \n                for (int index = 0; index < size; index++)    \n            if (elementData[index] == null) {    \n                fastRemove(index);    \n                return true;    \n            }    \n        } else {    \n            for (int index = 0; index < size; index++)    \n            if (o.equals(elementData[index])) {    \n                fastRemove(index);    \n                return true;    \n            }    \n        }    \n        return false;    \n    }    \n   \n   \n    // 快速删除第index个元素    \n    private void fastRemove(int index) {    \n        modCount++;    \n        int numMoved = size - index - 1;    \n        // 从\"index+1\"开始，用后面的元素替换前面的元素。    \n        if (numMoved > 0)    \n            System.arraycopy(elementData, index+1, elementData, index,    \n                             numMoved);    \n        // 将最后一个元素设为null    \n        elementData[--size] = null; // Let gc do its work    \n    }    \n   \n    // 删除元素    \n    public boolean remove(Object o) {    \n        if (o == null) {    \n            for (int index = 0; index < size; index++)    \n            if (elementData[index] == null) {    \n                fastRemove(index);    \n            return true;    \n            }    \n        } else {    \n            // 便利ArrayList，找到“元素o”，则删除，并返回true。    \n            for (int index = 0; index < size; index++)    \n            if (o.equals(elementData[index])) {    \n                fastRemove(index);    \n            return true;    \n            }    \n        }    \n        return false;    \n    }    \n   \n    // 清空ArrayList，将全部的元素设为null    \n    public void clear() {    \n        modCount++;    \n   \n        for (int i = 0; i < size; i++)    \n            elementData[i] = null;    \n   \n        size = 0;    \n    }    \n   \n    // 将集合c追加到ArrayList中    \n    public boolean addAll(Collection<? extends E> c) {    \n        Object[] a = c.toArray();    \n        int numNew = a.length;    \n        ensureCapacity(size + numNew);  // Increments modCount    \n        System.arraycopy(a, 0, elementData, size, numNew);    \n        size += numNew;    \n        return numNew != 0;    \n    }    \n   \n    // 从index位置开始，将集合c添加到ArrayList    \n    public boolean addAll(int index, Collection<? extends E> c) {    \n        if (index > size || index < 0)    \n            throw new IndexOutOfBoundsException(    \n            \"Index: \" + index + \", Size: \" + size);    \n   \n        Object[] a = c.toArray();    \n        int numNew = a.length;    \n        ensureCapacity(size + numNew);  // Increments modCount    \n   \n        int numMoved = size - index;    \n        if (numMoved > 0)    \n            System.arraycopy(elementData, index, elementData, index + numNew,    \n                 numMoved);    \n   \n        System.arraycopy(a, 0, elementData, index, numNew);    \n        size += numNew;    \n        return numNew != 0;    \n    }    \n   \n    // 删除fromIndex到toIndex之间的全部元素。    \n    protected void removeRange(int fromIndex, int toIndex) {    \n    modCount++;    \n    int numMoved = size - toIndex;    \n        System.arraycopy(elementData, toIndex, elementData, fromIndex,    \n                         numMoved);    \n   \n    // Let gc do its work    \n    int newSize = size - (toIndex-fromIndex);    \n    while (size != newSize)    \n        elementData[--size] = null;    \n    }    \n   \n    private void RangeCheck(int index) {    \n    if (index >= size)    \n        throw new IndexOutOfBoundsException(    \n        \"Index: \"+index+\", Size: \"+size);    \n    }    \n   \n   \n    // 克隆函数    \n    public Object clone() {    \n        try {    \n            ArrayList<E> v = (ArrayList<E>) super.clone();    \n            // 将当前ArrayList的全部元素拷贝到v中    \n            v.elementData = Arrays.copyOf(elementData, size);    \n            v.modCount = 0;    \n            return v;    \n        } catch (CloneNotSupportedException e) {    \n            // this shouldn't happen, since we are Cloneable    \n            throw new InternalError();    \n        }    \n    }    \n   \n   \n    // java.io.Serializable的写入函数    \n    // 将ArrayList的“容量，所有的元素值”都写入到输出流中    \n    private void writeObject(java.io.ObjectOutputStream s)    \n        throws java.io.IOException{    \n    // Write out element count, and any hidden stuff    \n    int expectedModCount = modCount;    \n    s.defaultWriteObject();    \n   \n        // 写入“数组的容量”    \n        s.writeInt(elementData.length);    \n   \n    // 写入“数组的每一个元素”    \n    for (int i=0; i<size; i++)    \n            s.writeObject(elementData[i]);    \n   \n    if (modCount != expectedModCount) {    \n            throw new ConcurrentModificationException();    \n        }    \n   \n    }    \n   \n   \n    // java.io.Serializable的读取函数：根据写入方式读出    \n    // 先将ArrayList的“容量”读出，然后将“所有的元素值”读出    \n    private void readObject(java.io.ObjectInputStream s)    \n        throws java.io.IOException, ClassNotFoundException {    \n        // Read in size, and any hidden stuff    \n        s.defaultReadObject();    \n   \n        // 从输入流中读取ArrayList的“容量”    \n        int arrayLength = s.readInt();    \n        Object[] a = elementData = new Object[arrayLength];    \n   \n        // 从输入流中将“所有的元素值”读出    \n        for (int i=0; i<size; i++)    \n            a[i] = s.readObject();    \n    }    \n}  \n```\n\n##几点总结\n\n关于ArrayList的源码，给出几点比较重要的总结：\n\n1. 注意其三个不同的构造方法。无参构造方法构造的ArrayList的容量默认为10，带有Collection参数的构造方法，将Collection转化为数组赋给ArrayList的实现数组elementData。\n2. 注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素（可能是1个，也可能是一组）时，都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时，就设置新的容量为旧的容量的1.5倍加1，如果设置后的新容量还不够，则直接新容量设置为传入的参数（也就是所需的容量），而后用Arrays.copyof()方法将元素拷贝到新的数组（详见下面的第3点）。从中可以看出，当容量不够时，每次增加元素，都要将原来的元素拷贝到一个新的数组中，非常之耗时，也因此建议在事先能确定元素数量的情况下，才使用ArrayList，否则建议使用LinkedList。\n3. ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法。我们有必要对这两个方法的实现做下深入的了解。\n\n首先来看Arrays.copyof()方法。它有很多个重载的方法，但实现思路都是一样的，我们来看泛型版本的源码：\n\n```\npublic static <T> T[] copyOf(T[] original, int newLength) {  \n    return (T[]) copyOf(original, newLength, original.getClass());  \n}  \n```\n\n很明显调用了另一个copyof方法，该方法有三个参数，最后一个参数指明要转换的数据的类型，其源码如下：\n\n```\npublic static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  \n    T[] copy = ((Object)newType == (Object)Object[].class)  \n        ? (T[]) new Object[newLength]  \n        : (T[]) Array.newInstance(newType.getComponentType(), newLength);  \n    System.arraycopy(original, 0, copy, 0,  \n                     Math.min(original.length, newLength));  \n    return copy;  \n}  \n```\n\n这里可以很明显地看出，该方法实际上是在其内部又创建了一个长度为newlength的数组，调用System.arraycopy()方法，将原来数组中的元素复制到了新的数组中。\n\n下面来看System.arraycopy()方法。该方法被标记了native，调用了系统的C/C++代码，在JDK中是看不到的，但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数，因此它可以保证同一个数组内元素的正确复制和移动，比一般的复制方法的实现效率要高很多，很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法，以取得更高的效率。\n4. 注意ArrayList的两个转化为静态数组的toArray方法。\n\n第一个，Object[] toArray()方法。该方法有可能会抛出java.lang.ClassCastException异常，如果直接用向下转型的方法，将整个ArrayList集合转变为指定类型的Array数组，便会抛出该异常，而如果转化为Array数组时不向下转型，而是将每个元素向下转型，则不会抛出该异常，显然对数组中的元素一个个进行向下转型，效率不高，且不太方便。\n\n第二个，<T> T[] toArray(T[] a)方法。该方法可以直接将ArrayList转换得到的Array进行整体向下转型（转型其实是在该方法的源码中实现的），且从该方法的源码中可以看出，参数a的大小不足时，内部会调用Arrays.copyOf方法，该方法内部创建一个新的数组返回，因此对该方法的常用形式如下：\n\n```\npublic static Integer[] vectorToArray2(ArrayList<Integer> v) {    \n    Integer[] newText = (Integer[])v.toArray(new Integer[0]);    \n    return newText;    \n}    \n```\n\n\n5.ArrayList基于数组实现，可以通过下标索引直接查找到指定位置的元素，因此查找效率高，但每次插入或删除元素，就要大量地移动元素，插入删除元素的效率低。\n\n6.在查找给定元素索引值等的方法中，源码都将该元素的值分为null和不为null两种情况处理，ArrayList中允许元素为null。"
  },
  {
    "path": "Part2/JavaSE/Arraylist.md",
    "content": "# ArrarList<E\\><A NAME=\"Collection\"> </a>\n---\n* 是一个类\n* 实现的接口：List、Collectin、Iterable、Serializable、Cloneable、RandomAccess\n* 子类：AttributeList、RoleList、RoleUnresolvedList\n\n##简介\n---\n* 它是顺序表\n* 大小可变\n* 允许null元素\n* 可LinkedList一样，不具备线程同步的安全性、但速度较快\n* 第一次定义的时候没有指定数组的长度则长度是0，在第一次添加的时候判断如果是空则追加10。\n\n##容量是如何变化的\n---\n在源码中：\n\n```\nprivate transient Object[] elementData;\n```\n用于保存对象。它会随着元素的添加而改变大小。在下面的叙述中，容量指的是elementData.length，而不是size。\n\n>size不是容量，ArrayList对象占用的内存不是由size决定的。\n>size的大小在每次调用add(E e)方法时加1。\n>如果是调用ArrayList(Collection<? extends E> c)构造方法，则size的初始值为c\n\n* 如果在初始化时，没有指定大小，则容量大小为0。\n* 当大小为0时，第一次添加元素容量大小变为10。\n* 之后每次添加时，会先确保容量是否够用\n* 如果不够用，每次增加的容量为 newCapacity = oldCapacity + (oldCapacity >> 1)；即，每次增加为原来的1.5倍。\n\n##和Vector的区别\n---\n* Vector线程安全\n* ArrayList线程不安全，速度快\n\n##和LinkedList的区别\n---\n* ArrayList是顺序表，LinkedList是链表\n* ArrayList查找，修改方便，LinkedList添加、删除方便\n\n##方法\n---\n**void ensureCapacity(int minCapacity)**\n设置容量能容纳下minCapacity个元素\n\n* 如果"
  },
  {
    "path": "Part2/JavaSE/Arraylist和Hashmap如何扩容等.md",
    "content": "#Arraylist和HashMap如何扩容？\n\n\n\n#负载因子有什么作用？\n\n\n\n\n#如何保证读写进程安全？\n"
  },
  {
    "path": "Part2/JavaSE/Collection.md",
    "content": "\n####线性表，链表，哈希表是常用的数据结构，在进行Java开发时，JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述，向读者阐述各个类的作用以及如何正确使用这些类。 \n\n# Collection<E\\><A NAME=\"Collection\"> </a>\n***\n* 是最基本的集合接口\n* 继承的接口：Iterable\n* 子接口：List、Set、Queue等\n\n##简介\n* 一个Collection代表一组Object，即Collection的元素（Elements），一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。\n* Java SDK不提供直接继承自Collection的类，Java SDK提供的类都是继承自Collection的“子接口”如List和Set。\n\n##如何遍历Collection中的每一个元素？\n* 不论Collection的实际类型如何，它都支持一个iterator()的方法，该方法返回一个迭代子，使用该迭代子即可逐一访问Collection中每一个元素，代码如下：\n\n\n\n```\n\n\tIterator it = collection.iterator(); // 获得一个迭代子\n    while(it.hasNext())　　\n    {\n        Object obj = it.next(); // 得到下一个元素\n    }\n```\n\n## 方法\n\r\n**retainAll(Collection<? extends E\\> c)**：保留，交运算  \r\n**addAll(Collection<? extends E\\> c)**：添加，并运算  \r\n**removeAll(Collection<? extends E\\> c)**：移除，减运算  \n\n\n\n\n"
  },
  {
    "path": "Part2/JavaSE/HashMap源码剖析.md",
    "content": "###HashMap简介\n   HashMap是基于哈希表实现的，每一个元素都是一个key-value对，其内部通过单链表解决冲突问题，容量不足（超过了阈值）时，同样会自动增长。\n\nHashMap是非线程安全的，只是用于单线程环境下，多线程环境下可以采用concurrent并发包下的concurrentHashMap。\n\nHashMap实现了Serializable接口，因此它支持序列化，实现了Cloneable接口，能被克隆。\n\n###HashMap源码剖析\n\nHashMap的源码如下（加入了比较详细的注释）：\n\n```\npackage java.util;    \nimport java.io.*;    \n   \npublic class HashMap<K,V>    \n    extends AbstractMap<K,V>    \n    implements Map<K,V>, Cloneable, Serializable    \n{    \n   \n    // 默认的初始容量（容量为HashMap中槽的数目）是16，且实际容量必须是2的整数次幂。    \n    static final int DEFAULT_INITIAL_CAPACITY = 16;    \n   \n    // 最大容量（必须是2的幂且小于2的30次方，传入容量过大将被这个值替换）    \n    static final int MAXIMUM_CAPACITY = 1 << 30;    \n   \n    // 默认加载因子为0.75   \n    static final float DEFAULT_LOAD_FACTOR = 0.75f;    \n   \n    // 存储数据的Entry数组，长度是2的幂。    \n    // HashMap采用链表法解决冲突，每一个Entry本质上是一个单向链表    \n    transient Entry[] table;    \n   \n    // HashMap的底层数组中已用槽的数量    \n    transient int size;    \n   \n    // HashMap的阈值，用于判断是否需要调整HashMap的容量（threshold = 容量*加载因子）    \n    int threshold;    \n   \n    // 加载因子实际大小    \n    final float loadFactor;    \n   \n    // HashMap被改变的次数    \n    transient volatile int modCount;    \n   \n    // 指定“容量大小”和“加载因子”的构造函数    \n    public HashMap(int initialCapacity, float loadFactor) {    \n        if (initialCapacity < 0)    \n            throw new IllegalArgumentException(\"Illegal initial capacity: \" +    \n                                               initialCapacity);    \n        // HashMap的最大容量只能是MAXIMUM_CAPACITY    \n        if (initialCapacity > MAXIMUM_CAPACITY)    \n            initialCapacity = MAXIMUM_CAPACITY;    \n        //加载因此不能小于0  \n        if (loadFactor <= 0 || Float.isNaN(loadFactor))    \n            throw new IllegalArgumentException(\"Illegal load factor: \" +    \n                                               loadFactor);    \n   \n        // 找出“大于initialCapacity”的最小的2的幂    \n        int capacity = 1;    \n        while (capacity < initialCapacity)    \n            capacity <<= 1;    \n   \n        // 设置“加载因子”    \n        this.loadFactor = loadFactor;    \n        // 设置“HashMap阈值”，当HashMap中存储数据的数量达到threshold时，就需要将HashMap的容量加倍。    \n        threshold = (int)(capacity * loadFactor);    \n        // 创建Entry数组，用来保存数据    \n        table = new Entry[capacity];    \n        init();    \n    }    \n   \n   \n    // 指定“容量大小”的构造函数    \n    public HashMap(int initialCapacity) {    \n        this(initialCapacity, DEFAULT_LOAD_FACTOR);    \n    }    \n   \n    // 默认构造函数。    \n    public HashMap() {    \n        // 设置“加载因子”为默认加载因子0.75    \n        this.loadFactor = DEFAULT_LOAD_FACTOR;    \n        // 设置“HashMap阈值”，当HashMap中存储数据的数量达到threshold时，就需要将HashMap的容量加倍。    \n        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);    \n        // 创建Entry数组，用来保存数据    \n        table = new Entry[DEFAULT_INITIAL_CAPACITY];    \n        init();    \n    }    \n   \n    // 包含“子Map”的构造函数    \n    public HashMap(Map<? extends K, ? extends V> m) {    \n        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,    \n                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);    \n        // 将m中的全部元素逐个添加到HashMap中    \n        putAllForCreate(m);    \n    }    \n   \n    //求hash值的方法，重新计算hash值  \n    static int hash(int h) {    \n        h ^= (h >>> 20) ^ (h >>> 12);    \n        return h ^ (h >>> 7) ^ (h >>> 4);    \n    }    \n   \n    // 返回h在数组中的索引值，这里用&代替取模，旨在提升效率   \n    // h & (length-1)保证返回值的小于length    \n    static int indexFor(int h, int length) {    \n        return h & (length-1);    \n    }    \n   \n    public int size() {    \n        return size;    \n    }    \n   \n    public boolean isEmpty() {    \n        return size == 0;    \n    }    \n   \n    // 获取key对应的value    \n    public V get(Object key) {    \n        if (key == null)    \n            return getForNullKey();    \n        // 获取key的hash值    \n        int hash = hash(key.hashCode());    \n        // 在“该hash值对应的链表”上查找“键值等于key”的元素    \n        for (Entry<K,V> e = table[indexFor(hash, table.length)];    \n             e != null;    \n             e = e.next) {    \n            Object k;    \n            //判断key是否相同  \n            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    \n                return e.value;    \n        }  \n        //没找到则返回null  \n        return null;    \n    }    \n   \n    // 获取“key为null”的元素的值    \n    // HashMap将“key为null”的元素存储在table[0]位置，但不一定是该链表的第一个位置！    \n    private V getForNullKey() {    \n        for (Entry<K,V> e = table[0]; e != null; e = e.next) {    \n            if (e.key == null)    \n                return e.value;    \n        }    \n        return null;    \n    }    \n   \n    // HashMap是否包含key    \n    public boolean containsKey(Object key) {    \n        return getEntry(key) != null;    \n    }    \n   \n    // 返回“键为key”的键值对    \n    final Entry<K,V> getEntry(Object key) {    \n        // 获取哈希值    \n        // HashMap将“key为null”的元素存储在table[0]位置，“key不为null”的则调用hash()计算哈希值    \n        int hash = (key == null) ? 0 : hash(key.hashCode());    \n        // 在“该hash值对应的链表”上查找“键值等于key”的元素    \n        for (Entry<K,V> e = table[indexFor(hash, table.length)];    \n             e != null;    \n             e = e.next) {    \n            Object k;    \n            if (e.hash == hash &&    \n                ((k = e.key) == key || (key != null && key.equals(k))))    \n                return e;    \n        }    \n        return null;    \n    }    \n   \n    // 将“key-value”添加到HashMap中    \n    public V put(K key, V value) {    \n        // 若“key为null”，则将该键值对添加到table[0]中。    \n        if (key == null)    \n            return putForNullKey(value);    \n        // 若“key不为null”，则计算该key的哈希值，然后将其添加到该哈希值对应的链表中。    \n        int hash = hash(key.hashCode());    \n        int i = indexFor(hash, table.length);    \n        for (Entry<K,V> e = table[i]; e != null; e = e.next) {    \n            Object k;    \n            // 若“该key”对应的键值对已经存在，则用新的value取代旧的value。然后退出！    \n            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {    \n                V oldValue = e.value;    \n                e.value = value;    \n                e.recordAccess(this);    \n                return oldValue;    \n            }    \n        }    \n   \n        // 若“该key”对应的键值对不存在，则将“key-value”添加到table中    \n        modCount++;  \n        //将key-value添加到table[i]处  \n        addEntry(hash, key, value, i);    \n        return null;    \n    }    \n   \n    // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置    \n    private V putForNullKey(V value) {    \n        for (Entry<K,V> e = table[0]; e != null; e = e.next) {    \n            if (e.key == null) {    \n                V oldValue = e.value;    \n                e.value = value;    \n                e.recordAccess(this);    \n                return oldValue;    \n            }    \n        }    \n        // 如果没有存在key为null的键值对，则直接题阿见到table[0]处!    \n        modCount++;    \n        addEntry(0, null, value, 0);    \n        return null;    \n    }    \n   \n    // 创建HashMap对应的“添加方法”，    \n    // 它和put()不同。putForCreate()是内部方法，它被构造函数等调用，用来创建HashMap    \n    // 而put()是对外提供的往HashMap中添加元素的方法。    \n    private void putForCreate(K key, V value) {    \n        int hash = (key == null) ? 0 : hash(key.hashCode());    \n        int i = indexFor(hash, table.length);    \n   \n        // 若该HashMap表中存在“键值等于key”的元素，则替换该元素的value值    \n        for (Entry<K,V> e = table[i]; e != null; e = e.next) {    \n            Object k;    \n            if (e.hash == hash &&    \n                ((k = e.key) == key || (key != null && key.equals(k)))) {    \n                e.value = value;    \n                return;    \n            }    \n        }    \n   \n        // 若该HashMap表中不存在“键值等于key”的元素，则将该key-value添加到HashMap中    \n        createEntry(hash, key, value, i);    \n    }    \n   \n    // 将“m”中的全部元素都添加到HashMap中。    \n    // 该方法被内部的构造HashMap的方法所调用。    \n    private void putAllForCreate(Map<? extends K, ? extends V> m) {    \n        // 利用迭代器将元素逐个添加到HashMap中    \n        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {    \n            Map.Entry<? extends K, ? extends V> e = i.next();    \n            putForCreate(e.getKey(), e.getValue());    \n        }    \n    }    \n   \n    // 重新调整HashMap的大小，newCapacity是调整后的容量    \n    void resize(int newCapacity) {    \n        Entry[] oldTable = table;    \n        int oldCapacity = oldTable.length;   \n        //如果就容量已经达到了最大值，则不能再扩容，直接返回  \n        if (oldCapacity == MAXIMUM_CAPACITY) {    \n            threshold = Integer.MAX_VALUE;    \n            return;    \n        }    \n   \n        // 新建一个HashMap，将“旧HashMap”的全部元素添加到“新HashMap”中，    \n        // 然后，将“新HashMap”赋值给“旧HashMap”。    \n        Entry[] newTable = new Entry[newCapacity];    \n        transfer(newTable);    \n        table = newTable;    \n        threshold = (int)(newCapacity * loadFactor);    \n    }    \n   \n    // 将HashMap中的全部元素都添加到newTable中    \n    void transfer(Entry[] newTable) {    \n        Entry[] src = table;    \n        int newCapacity = newTable.length;    \n        for (int j = 0; j < src.length; j++) {    \n            Entry<K,V> e = src[j];    \n            if (e != null) {    \n                src[j] = null;    \n                do {    \n                    Entry<K,V> next = e.next;    \n                    int i = indexFor(e.hash, newCapacity);    \n                    e.next = newTable[i];    \n                    newTable[i] = e;    \n                    e = next;    \n                } while (e != null);    \n            }    \n        }    \n    }    \n   \n    // 将\"m\"的全部元素都添加到HashMap中    \n    public void putAll(Map<? extends K, ? extends V> m) {    \n        // 有效性判断    \n        int numKeysToBeAdded = m.size();    \n        if (numKeysToBeAdded == 0)    \n            return;    \n   \n        // 计算容量是否足够，    \n        // 若“当前阀值容量 < 需要的容量”，则将容量x2。    \n        if (numKeysToBeAdded > threshold) {    \n            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);    \n            if (targetCapacity > MAXIMUM_CAPACITY)    \n                targetCapacity = MAXIMUM_CAPACITY;    \n            int newCapacity = table.length;    \n            while (newCapacity < targetCapacity)    \n                newCapacity <<= 1;    \n            if (newCapacity > table.length)    \n                resize(newCapacity);    \n        }    \n   \n        // 通过迭代器，将“m”中的元素逐个添加到HashMap中。    \n        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {    \n            Map.Entry<? extends K, ? extends V> e = i.next();    \n            put(e.getKey(), e.getValue());    \n        }    \n    }    \n   \n    // 删除“键为key”元素    \n    public V remove(Object key) {    \n        Entry<K,V> e = removeEntryForKey(key);    \n        return (e == null ? null : e.value);    \n    }    \n   \n    // 删除“键为key”的元素    \n    final Entry<K,V> removeEntryForKey(Object key) {    \n        // 获取哈希值。若key为null，则哈希值为0；否则调用hash()进行计算    \n        int hash = (key == null) ? 0 : hash(key.hashCode());    \n        int i = indexFor(hash, table.length);    \n        Entry<K,V> prev = table[i];    \n        Entry<K,V> e = prev;    \n   \n        // 删除链表中“键为key”的元素    \n        // 本质是“删除单向链表中的节点”    \n        while (e != null) {    \n            Entry<K,V> next = e.next;    \n            Object k;    \n            if (e.hash == hash &&    \n                ((k = e.key) == key || (key != null && key.equals(k)))) {    \n                modCount++;    \n                size--;    \n                if (prev == e)    \n                    table[i] = next;    \n                else   \n                    prev.next = next;    \n                e.recordRemoval(this);    \n                return e;    \n            }    \n            prev = e;    \n            e = next;    \n        }    \n   \n        return e;    \n    }    \n   \n    // 删除“键值对”    \n    final Entry<K,V> removeMapping(Object o) {    \n        if (!(o instanceof Map.Entry))    \n            return null;    \n   \n        Map.Entry<K,V> entry = (Map.Entry<K,V>) o;    \n        Object key = entry.getKey();    \n        int hash = (key == null) ? 0 : hash(key.hashCode());    \n        int i = indexFor(hash, table.length);    \n        Entry<K,V> prev = table[i];    \n        Entry<K,V> e = prev;    \n   \n        // 删除链表中的“键值对e”    \n        // 本质是“删除单向链表中的节点”    \n        while (e != null) {    \n            Entry<K,V> next = e.next;    \n            if (e.hash == hash && e.equals(entry)) {    \n                modCount++;    \n                size--;    \n                if (prev == e)    \n                    table[i] = next;    \n                else   \n                    prev.next = next;    \n                e.recordRemoval(this);    \n                return e;    \n            }    \n            prev = e;    \n            e = next;    \n        }    \n   \n        return e;    \n    }    \n   \n    // 清空HashMap，将所有的元素设为null    \n    public void clear() {    \n        modCount++;    \n        Entry[] tab = table;    \n        for (int i = 0; i < tab.length; i++)    \n            tab[i] = null;    \n        size = 0;    \n    }    \n   \n    // 是否包含“值为value”的元素    \n    public boolean containsValue(Object value) {    \n    // 若“value为null”，则调用containsNullValue()查找    \n    if (value == null)    \n            return containsNullValue();    \n   \n    // 若“value不为null”，则查找HashMap中是否有值为value的节点。    \n    Entry[] tab = table;    \n        for (int i = 0; i < tab.length ; i++)    \n            for (Entry e = tab[i] ; e != null ; e = e.next)    \n                if (value.equals(e.value))    \n                    return true;    \n    return false;    \n    }    \n   \n    // 是否包含null值    \n    private boolean containsNullValue() {    \n    Entry[] tab = table;    \n        for (int i = 0; i < tab.length ; i++)    \n            for (Entry e = tab[i] ; e != null ; e = e.next)    \n                if (e.value == null)    \n                    return true;    \n    return false;    \n    }    \n   \n    // 克隆一个HashMap，并返回Object对象    \n    public Object clone() {    \n        HashMap<K,V> result = null;    \n        try {    \n            result = (HashMap<K,V>)super.clone();    \n        } catch (CloneNotSupportedException e) {    \n            // assert false;    \n        }    \n        result.table = new Entry[table.length];    \n        result.entrySet = null;    \n        result.modCount = 0;    \n        result.size = 0;    \n        result.init();    \n        // 调用putAllForCreate()将全部元素添加到HashMap中    \n        result.putAllForCreate(this);    \n   \n        return result;    \n    }    \n   \n    // Entry是单向链表。    \n    // 它是 “HashMap链式存储法”对应的链表。    \n    // 它实现了Map.Entry 接口，即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数    \n    static class Entry<K,V> implements Map.Entry<K,V> {    \n        final K key;    \n        V value;    \n        // 指向下一个节点    \n        Entry<K,V> next;    \n        final int hash;    \n   \n        // 构造函数。    \n        // 输入参数包括\"哈希值(h)\", \"键(k)\", \"值(v)\", \"下一节点(n)\"    \n        Entry(int h, K k, V v, Entry<K,V> n) {    \n            value = v;    \n            next = n;    \n            key = k;    \n            hash = h;    \n        }    \n   \n        public final K getKey() {    \n            return key;    \n        }    \n   \n        public final V getValue() {    \n            return value;    \n        }    \n   \n        public final V setValue(V newValue) {    \n            V oldValue = value;    \n            value = newValue;    \n            return oldValue;    \n        }    \n   \n        // 判断两个Entry是否相等    \n        // 若两个Entry的“key”和“value”都相等，则返回true。    \n        // 否则，返回false    \n        public final boolean equals(Object o) {    \n            if (!(o instanceof Map.Entry))    \n                return false;    \n            Map.Entry e = (Map.Entry)o;    \n            Object k1 = getKey();    \n            Object k2 = e.getKey();    \n            if (k1 == k2 || (k1 != null && k1.equals(k2))) {    \n                Object v1 = getValue();    \n                Object v2 = e.getValue();    \n                if (v1 == v2 || (v1 != null && v1.equals(v2)))    \n                    return true;    \n            }    \n            return false;    \n        }    \n   \n        // 实现hashCode()    \n        public final int hashCode() {    \n            return (key==null   ? 0 : key.hashCode()) ^    \n                   (value==null ? 0 : value.hashCode());    \n        }    \n   \n        public final String toString() {    \n            return getKey() + \"=\" + getValue();    \n        }    \n   \n        // 当向HashMap中添加元素时，绘调用recordAccess()。    \n        // 这里不做任何处理    \n        void recordAccess(HashMap<K,V> m) {    \n        }    \n   \n        // 当从HashMap中删除元素时，绘调用recordRemoval()。    \n        // 这里不做任何处理    \n        void recordRemoval(HashMap<K,V> m) {    \n        }    \n    }    \n   \n    // 新增Entry。将“key-value”插入指定位置，bucketIndex是位置索引。    \n    void addEntry(int hash, K key, V value, int bucketIndex) {    \n        // 保存“bucketIndex”位置的值到“e”中    \n        Entry<K,V> e = table[bucketIndex];    \n        // 设置“bucketIndex”位置的元素为“新Entry”，    \n        // 设置“e”为“新Entry的下一个节点”    \n        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    \n        // 若HashMap的实际大小 不小于 “阈值”，则调整HashMap的大小    \n        if (size++ >= threshold)    \n            resize(2 * table.length);    \n    }    \n   \n    // 创建Entry。将“key-value”插入指定位置。    \n    void createEntry(int hash, K key, V value, int bucketIndex) {    \n        // 保存“bucketIndex”位置的值到“e”中    \n        Entry<K,V> e = table[bucketIndex];    \n        // 设置“bucketIndex”位置的元素为“新Entry”，    \n        // 设置“e”为“新Entry的下一个节点”    \n        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    \n        size++;    \n    }    \n   \n    // HashIterator是HashMap迭代器的抽象出来的父类，实现了公共了函数。    \n    // 它包含“key迭代器(KeyIterator)”、“Value迭代器(ValueIterator)”和“Entry迭代器(EntryIterator)”3个子类。    \n    private abstract class HashIterator<E> implements Iterator<E> {    \n        // 下一个元素    \n        Entry<K,V> next;    \n        // expectedModCount用于实现fast-fail机制。    \n        int expectedModCount;    \n        // 当前索引    \n        int index;    \n        // 当前元素    \n        Entry<K,V> current;    \n   \n        HashIterator() {    \n            expectedModCount = modCount;    \n            if (size > 0) { // advance to first entry    \n                Entry[] t = table;    \n                // 将next指向table中第一个不为null的元素。    \n                // 这里利用了index的初始值为0，从0开始依次向后遍历，直到找到不为null的元素就退出循环。    \n                while (index < t.length && (next = t[index++]) == null)    \n                    ;    \n            }    \n        }    \n   \n        public final boolean hasNext() {    \n            return next != null;    \n        }    \n   \n        // 获取下一个元素    \n        final Entry<K,V> nextEntry() {    \n            if (modCount != expectedModCount)    \n                throw new ConcurrentModificationException();    \n            Entry<K,V> e = next;    \n            if (e == null)    \n                throw new NoSuchElementException();    \n   \n            // 注意！！！    \n            // 一个Entry就是一个单向链表    \n            // 若该Entry的下一个节点不为空，就将next指向下一个节点;    \n            // 否则，将next指向下一个链表(也是下一个Entry)的不为null的节点。    \n            if ((next = e.next) == null) {    \n                Entry[] t = table;    \n                while (index < t.length && (next = t[index++]) == null)    \n                    ;    \n            }    \n            current = e;    \n            return e;    \n        }    \n   \n        // 删除当前元素    \n        public void remove() {    \n            if (current == null)    \n                throw new IllegalStateException();    \n            if (modCount != expectedModCount)    \n                throw new ConcurrentModificationException();    \n            Object k = current.key;    \n            current = null;    \n            HashMap.this.removeEntryForKey(k);    \n            expectedModCount = modCount;    \n        }    \n   \n    }    \n   \n    // value的迭代器    \n    private final class ValueIterator extends HashIterator<V> {    \n        public V next() {    \n            return nextEntry().value;    \n        }    \n    }    \n   \n    // key的迭代器    \n    private final class KeyIterator extends HashIterator<K> {    \n        public K next() {    \n            return nextEntry().getKey();    \n        }    \n    }    \n   \n    // Entry的迭代器    \n    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {    \n        public Map.Entry<K,V> next() {    \n            return nextEntry();    \n        }    \n    }    \n   \n    // 返回一个“key迭代器”    \n    Iterator<K> newKeyIterator()   {    \n        return new KeyIterator();    \n    }    \n    // 返回一个“value迭代器”    \n    Iterator<V> newValueIterator()   {    \n        return new ValueIterator();    \n    }    \n    // 返回一个“entry迭代器”    \n    Iterator<Map.Entry<K,V>> newEntryIterator()   {    \n        return new EntryIterator();    \n    }    \n   \n    // HashMap的Entry对应的集合    \n    private transient Set<Map.Entry<K,V>> entrySet = null;    \n   \n    // 返回“key的集合”，实际上返回一个“KeySet对象”    \n    public Set<K> keySet() {    \n        Set<K> ks = keySet;    \n        return (ks != null ? ks : (keySet = new KeySet()));    \n    }    \n   \n    // Key对应的集合    \n    // KeySet继承于AbstractSet，说明该集合中没有重复的Key。    \n    private final class KeySet extends AbstractSet<K> {    \n        public Iterator<K> iterator() {    \n            return newKeyIterator();    \n        }    \n        public int size() {    \n            return size;    \n        }    \n        public boolean contains(Object o) {    \n            return containsKey(o);    \n        }    \n        public boolean remove(Object o) {    \n            return HashMap.this.removeEntryForKey(o) != null;    \n        }    \n        public void clear() {    \n            HashMap.this.clear();    \n        }    \n    }    \n   \n    // 返回“value集合”，实际上返回的是一个Values对象    \n    public Collection<V> values() {    \n        Collection<V> vs = values;    \n        return (vs != null ? vs : (values = new Values()));    \n    }    \n   \n    // “value集合”    \n    // Values继承于AbstractCollection，不同于“KeySet继承于AbstractSet”，    \n    // Values中的元素能够重复。因为不同的key可以指向相同的value。    \n    private final class Values extends AbstractCollection<V> {    \n        public Iterator<V> iterator() {    \n            return newValueIterator();    \n        }    \n        public int size() {    \n            return size;    \n        }    \n        public boolean contains(Object o) {    \n            return containsValue(o);    \n        }    \n        public void clear() {    \n            HashMap.this.clear();    \n        }    \n    }    \n   \n    // 返回“HashMap的Entry集合”    \n    public Set<Map.Entry<K,V>> entrySet() {    \n        return entrySet0();    \n    }    \n   \n    // 返回“HashMap的Entry集合”，它实际是返回一个EntrySet对象    \n    private Set<Map.Entry<K,V>> entrySet0() {    \n        Set<Map.Entry<K,V>> es = entrySet;    \n        return es != null ? es : (entrySet = new EntrySet());    \n    }    \n   \n    // EntrySet对应的集合    \n    // EntrySet继承于AbstractSet，说明该集合中没有重复的EntrySet。    \n    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {    \n        public Iterator<Map.Entry<K,V>> iterator() {    \n            return newEntryIterator();    \n        }    \n        public boolean contains(Object o) {    \n            if (!(o instanceof Map.Entry))    \n                return false;    \n            Map.Entry<K,V> e = (Map.Entry<K,V>) o;    \n            Entry<K,V> candidate = getEntry(e.getKey());    \n            return candidate != null && candidate.equals(e);    \n        }    \n        public boolean remove(Object o) {    \n            return removeMapping(o) != null;    \n        }    \n        public int size() {    \n            return size;    \n        }    \n        public void clear() {    \n            HashMap.this.clear();    \n        }    \n    }    \n   \n    // java.io.Serializable的写入函数    \n    // 将HashMap的“总的容量，实际容量，所有的Entry”都写入到输出流中    \n    private void writeObject(java.io.ObjectOutputStream s)    \n        throws IOException    \n    {    \n        Iterator<Map.Entry<K,V>> i =    \n            (size > 0) ? entrySet0().iterator() : null;    \n   \n        // Write out the threshold, loadfactor, and any hidden stuff    \n        s.defaultWriteObject();    \n   \n        // Write out number of buckets    \n        s.writeInt(table.length);    \n   \n        // Write out size (number of Mappings)    \n        s.writeInt(size);    \n   \n        // Write out keys and values (alternating)    \n        if (i != null) {    \n            while (i.hasNext()) {    \n            Map.Entry<K,V> e = i.next();    \n            s.writeObject(e.getKey());    \n            s.writeObject(e.getValue());    \n            }    \n        }    \n    }    \n   \n   \n    private static final long serialVersionUID = 362498820763181265L;    \n   \n    // java.io.Serializable的读取函数：根据写入方式读出    \n    // 将HashMap的“总的容量，实际容量，所有的Entry”依次读出    \n    private void readObject(java.io.ObjectInputStream s)    \n         throws IOException, ClassNotFoundException    \n    {    \n        // Read in the threshold, loadfactor, and any hidden stuff    \n        s.defaultReadObject();    \n   \n        // Read in number of buckets and allocate the bucket array;    \n        int numBuckets = s.readInt();    \n        table = new Entry[numBuckets];    \n   \n        init();  // Give subclass a chance to do its thing.    \n   \n        // Read in size (number of Mappings)    \n        int size = s.readInt();    \n   \n        // Read the keys and values, and put the mappings in the HashMap    \n        for (int i=0; i<size; i++) {    \n            K key = (K) s.readObject();    \n            V value = (V) s.readObject();    \n            putForCreate(key, value);    \n        }    \n    }    \n   \n    // 返回“HashMap总的容量”    \n    int   capacity()     { return table.length; }    \n    // 返回“HashMap的加载因子”    \n    float loadFactor()   { return loadFactor;   }    \n}   \n```\n\n###几点总结\n1、首先要清楚HashMap的存储结构，如下图所示：\n\n![](http://img.blog.csdn.net/20140701191403764?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbnNfY29kZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n图中，紫色部分即代表哈希表，也称为哈希数组，数组的每个元素都是一个单链表的头节点，链表是用来解决冲突的，如果不同的key映射到了数组的同一位置处，就将其放入单链表中。\n\n2、首先看链表中节点的数据结构：\n\n```\n// Entry是单向链表。    \n// 它是 “HashMap链式存储法”对应的链表。    \n// 它实现了Map.Entry 接口，即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数    \nstatic class Entry<K,V> implements Map.Entry<K,V> {    \n    final K key;    \n    V value;    \n    // 指向下一个节点    \n    Entry<K,V> next;    \n    final int hash;    \n  \n    // 构造函数。    \n    // 输入参数包括\"哈希值(h)\", \"键(k)\", \"值(v)\", \"下一节点(n)\"    \n    Entry(int h, K k, V v, Entry<K,V> n) {    \n        value = v;    \n        next = n;    \n        key = k;    \n        hash = h;    \n    }    \n  \n    public final K getKey() {    \n        return key;    \n    }    \n  \n    public final V getValue() {    \n        return value;    \n    }    \n  \n    public final V setValue(V newValue) {    \n        V oldValue = value;    \n        value = newValue;    \n        return oldValue;    \n    }    \n  \n    // 判断两个Entry是否相等    \n    // 若两个Entry的“key”和“value”都相等，则返回true。    \n    // 否则，返回false    \n    public final boolean equals(Object o) {    \n        if (!(o instanceof Map.Entry))    \n            return false;    \n        Map.Entry e = (Map.Entry)o;    \n        Object k1 = getKey();    \n        Object k2 = e.getKey();    \n        if (k1 == k2 || (k1 != null && k1.equals(k2))) {    \n            Object v1 = getValue();    \n            Object v2 = e.getValue();    \n            if (v1 == v2 || (v1 != null && v1.equals(v2)))    \n                return true;    \n        }    \n        return false;    \n    }    \n  \n    // 实现hashCode()    \n    public final int hashCode() {    \n        return (key==null   ? 0 : key.hashCode()) ^    \n               (value==null ? 0 : value.hashCode());    \n    }    \n  \n    public final String toString() {    \n        return getKey() + \"=\" + getValue();    \n    }    \n  \n    // 当向HashMap中添加元素时，绘调用recordAccess()。    \n    // 这里不做任何处理    \n    void recordAccess(HashMap<K,V> m) {    \n    }    \n  \n    // 当从HashMap中删除元素时，绘调用recordRemoval()。    \n    // 这里不做任何处理    \n    void recordRemoval(HashMap<K,V> m) {    \n    }    \n}    \n```\n\n它的结构元素除了key、value、hash外，还有next，next指向下一个节点。另外，这里覆写了equals和hashCode方法来保证键值对的独一无二。\n\n3、HashMap共有四个构造方法。构造方法中提到了两个很重要的参数：初始容量和加载因子。这两个参数是影响HashMap性能的重要参数，其中容量表示哈希表中槽的数量（即哈希数组的长度），初始容量是创建哈希表时的容量（从构造函数中可以看出，如果不指明，则默认为16），加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度，当哈希表中的条目数超出了加载因子与当前容量的乘积时，则要对该哈希表进行 resize 操作（即扩容）。\n\n下面说下加载因子，如果加载因子越大，对空间的利用更充分，但是查找效率会降低（链表长度会越来越长）；如果加载因子太小，那么表中的数据将过于稀疏（很多空间还没用，就开始扩容了），对空间造成严重浪费。如果我们在构造方法中不指定，则系统默认加载因子为0.75，这是一个比较理想的值，一般情况下我们是无需修改的。\n\n另外，无论我们指定的容量为多少，构造方法都会将实际容量设为不小于指定容量的2的次方的一个数，且最大值不能超过2的30次方\n\n4、HashMap中key和value都允许为null。\n\n5、要重点分析下HashMap中用的最多的两个方法put和get。先从比较简单的get方法着手，源码如下：\n\n```\n// 获取key对应的value    \npublic V get(Object key) {    \n    if (key == null)    \n        return getForNullKey();    \n    // 获取key的hash值    \n    int hash = hash(key.hashCode());    \n    // 在“该hash值对应的链表”上查找“键值等于key”的元素    \n    for (Entry<K,V> e = table[indexFor(hash, table.length)];    \n         e != null;    \n         e = e.next) {    \n        Object k;    \n/判断key是否相同  \n        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    \n            return e.value;    \n    }  \n没找到则返回null  \n    return null;    \n}    \n  \n// 获取“key为null”的元素的值    \n// HashMap将“key为null”的元素存储在table[0]位置，但不一定是该链表的第一个位置！    \nprivate V getForNullKey() {    \n    for (Entry<K,V> e = table[0]; e != null; e = e.next) {    \n        if (e.key == null)    \n            return e.value;    \n    }    \n    return null;    \n}    \n```\n\n首先，如果key为null，则直接从哈希表的第一个位置table[0]对应的链表上查找。记住，key为null的键值对永远都放在以table[0]为头结点的链表中，当然不一定是存放在头结点table[0]中。\n\n如果key不为null，则先求的key的hash值，根据hash值找到在table中的索引，在该索引对应的单链表中查找是否有键值对的key与目标key相等，有就返回对应的value，没有则返回null。\n\nput方法稍微复杂些，代码如下：\n\n```\n  // 将“key-value”添加到HashMap中    \n  public V put(K key, V value) {    \n      // 若“key为null”，则将该键值对添加到table[0]中。    \n      if (key == null)    \n          return putForNullKey(value);    \n      // 若“key不为null”，则计算该key的哈希值，然后将其添加到该哈希值对应的链表中。    \n      int hash = hash(key.hashCode());    \n      int i = indexFor(hash, table.length);    \n      for (Entry<K,V> e = table[i]; e != null; e = e.next) {    \n          Object k;    \n          // 若“该key”对应的键值对已经存在，则用新的value取代旧的value。然后退出！    \n          if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {    \n              V oldValue = e.value;    \n              e.value = value;    \n              e.recordAccess(this);    \n              return oldValue;    \n          }    \n      }    \n  \n      // 若“该key”对应的键值对不存在，则将“key-value”添加到table中    \n      modCount++;  \n//将key-value添加到table[i]处  \n      addEntry(hash, key, value, i);    \n      return null;    \n  }   \n```\n\n 如果key为null，则将其添加到table[0]对应的链表中，putForNullKey的源码如下：\n\n```\n// putForNullKey()的作用是将“key为null”键值对添加到table[0]位置    \nprivate V putForNullKey(V value) {    \n    for (Entry<K,V> e = table[0]; e != null; e = e.next) {    \n        if (e.key == null) {    \n            V oldValue = e.value;    \n            e.value = value;    \n            e.recordAccess(this);    \n            return oldValue;    \n        }    \n    }    \n    // 如果没有存在key为null的键值对，则直接题阿见到table[0]处!    \n    modCount++;    \n    addEntry(0, null, value, 0);    \n    return null;    \n}   \n```\n\n如果key不为null，则同样先求出key的hash值，根据hash值得出在table中的索引，而后遍历对应的单链表，如果单链表中存在与目标key相等的键值对，则将新的value覆盖旧的value，比将旧的value返回，如果找不到与目标key相等的键值对，或者该单链表为空，则将该键值对插入到改单链表的头结点位置（每次新插入的节点都是放在头结点的位置），该操作是有addEntry方法实现的，它的源码如下：\n\n```\n// 新增Entry。将“key-value”插入指定位置，bucketIndex是位置索引。    \nvoid addEntry(int hash, K key, V value, int bucketIndex) {    \n    // 保存“bucketIndex”位置的值到“e”中    \n    Entry<K,V> e = table[bucketIndex];    \n    // 设置“bucketIndex”位置的元素为“新Entry”，    \n    // 设置“e”为“新Entry的下一个节点”    \n    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    \n    // 若HashMap的实际大小 不小于 “阈值”，则调整HashMap的大小    \n    if (size++ >= threshold)    \n        resize(2 * table.length);    \n}    \n```\n\n注意这里倒数第三行的构造方法，将key-value键值对赋给table[bucketIndex]，并将其next指向元素e，这便将key-value放到了头结点中，并将之前的头结点接在了它的后面。该方法也说明，每次put键值对的时候，总是将新的该键值对放在table[bucketIndex]处（即头结点处）。\n\n两外注意最后两行代码，每次加入键值对时，都要判断当前已用的槽的数目是否大于等于阀值（容量*加载因子），如果大于等于，则进行扩容，将容量扩为原来容量的2倍。\n\n6、关于扩容。上面我们看到了扩容的方法，resize方法，它的源码如下：\n\n```\n// 重新调整HashMap的大小，newCapacity是调整后的单位    \nvoid resize(int newCapacity) {    \n    Entry[] oldTable = table;    \n    int oldCapacity = oldTable.length;    \n    if (oldCapacity == MAXIMUM_CAPACITY) {    \n        threshold = Integer.MAX_VALUE;    \n        return;    \n    }    \n  \n    // 新建一个HashMap，将“旧HashMap”的全部元素添加到“新HashMap”中，    \n    // 然后，将“新HashMap”赋值给“旧HashMap”。    \n    Entry[] newTable = new Entry[newCapacity];    \n    transfer(newTable);    \n    table = newTable;    \n    threshold = (int)(newCapacity * loadFactor);    \n}    \n```\n\n 很明显，是新建了一个HashMap的底层数组，而后调用transfer方法，将就HashMap的全部元素添加到新的HashMap中（要重新计算元素在新的数组中的索引位置）。transfer方法的源码如下：\n \n```\n// 将HashMap中的全部元素都添加到newTable中    \nvoid transfer(Entry[] newTable) {    \n    Entry[] src = table;    \n    int newCapacity = newTable.length;    \n    for (int j = 0; j < src.length; j++) {    \n        Entry<K,V> e = src[j];    \n        if (e != null) {    \n            src[j] = null;    \n            do {    \n                Entry<K,V> next = e.next;    \n                int i = indexFor(e.hash, newCapacity);    \n                e.next = newTable[i];    \n                newTable[i] = e;    \n                e = next;    \n            } while (e != null);    \n        }    \n    }    \n}    \n```\n\n很明显，扩容是一个相当耗时的操作，因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此，我们在用HashMap的时，最好能提前预估下HashMap中元素的个数，这样有助于提高HashMap的性能。\n\n7、注意containsKey方法和containsValue方法。前者直接可以通过key的哈希值将搜索范围定位到指定索引对应的链表，而后者要对哈希数组的每个链表进行搜索。\n\n8、我们重点来分析下求hash值和索引值的方法，这两个方法便是HashMap设计的最为核心的部分，二者结合能保证哈希表中的元素尽可能均匀地散列。\n\n计算哈希值的方法如下：\n\n```\nstatic int hash(int h) {  \n        h ^= (h >>> 20) ^ (h >>> 12);  \n        return h ^ (h >>> 7) ^ (h >>> 4);  \n    }  \n```\n\n它只是一个数学公式，IDK这样设计对hash值的计算，自然有它的好处，至于为什么这样设计，我们这里不去追究，只要明白一点，用的位的操作使hash值的计算效率很高。\n\n由hash值找到对应索引的方法如下：\n```\nstatic int indexFor(int h, int length) {  \n        return h & (length-1);  \n    }  \n```\n\n这个我们要重点说下，我们一般对哈希表的散列很自然地会想到用hash值对length取模（即除法散列法），Hashtable中也是这样实现的，这种方法基本能保证元素在哈希表中散列的比较均匀，但取模会用到除法运算，效率很低，HashMap中则通过h&(length-1)的方法来代替取模，同样实现了均匀的散列，但效率要高很多，这也是HashMap对Hashtable的一个改进。\n\n接下来，我们分析下为什么哈希表的容量一定要是2的整数次幂。首先，length为2的整数次幂的话，h&(length-1)就相当于对length取模，这样便保证了散列的均匀，同时也提升了效率；其次，length为2的整数次幂的话，为偶数，这样length-1为奇数，奇数的最后一位是1，这样便保证了h&(length-1)的最后一位可能为0，也可能为1（这取决于h的值），即与后的结果可能为偶数，也可能为奇数，这样便可以保证散列的均匀性，而如果length为奇数的话，很明显length-1为偶数，它的最后一位是0，这样h&(length-1)的最后一位肯定为0，即只能为偶数，这样任何hash值都只会被散列到数组的偶数下标位置上，这便浪费了近一半的空间，因此，length取2的整数次幂，是为了使不同hash值发生碰撞的概率较小，这样就能使元素在哈希表中均匀地散列。"
  },
  {
    "path": "Part2/JavaSE/HashTable源码剖析.md",
    "content": "##Hashtable简介\n\nHashTable同样是基于哈希表实现的，同样每个元素都是key-value对，其内部也是通过单链表解决冲突问题，容量不足（超过了阈值）时，同样会自动增长。\n\nHashtable也是JDK1.0引入的类，是线程安全的，能用于多线程环境中。\n\nHashtable同样实现了Serializable接口，它支持序列化，实现了Cloneable接口，能被克隆。\n\n##Hashtable源码剖析\nHashtable的源码的很多实现都和HashMap差不多，源码如下（加入了比较详细的注释）：\n\n```\npackage java.util;    \nimport java.io.*;    \n   \npublic class Hashtable<K,V>    \n    extends Dictionary<K,V>    \n    implements Map<K,V>, Cloneable, java.io.Serializable {    \n   \n    // 保存key-value的数组。    \n    // Hashtable同样采用单链表解决冲突，每一个Entry本质上是一个单向链表    \n    private transient Entry[] table;    \n   \n    // Hashtable中键值对的数量    \n    private transient int count;    \n   \n    // 阈值，用于判断是否需要调整Hashtable的容量（threshold = 容量*加载因子）    \n    private int threshold;    \n   \n    // 加载因子    \n    private float loadFactor;    \n   \n    // Hashtable被改变的次数，用于fail-fast机制的实现    \n    private transient int modCount = 0;    \n   \n    // 序列版本号    \n    private static final long serialVersionUID = 1421746759512286392L;    \n   \n    // 指定“容量大小”和“加载因子”的构造函数    \n    public Hashtable(int initialCapacity, float loadFactor) {    \n        if (initialCapacity < 0)    \n            throw new IllegalArgumentException(\"Illegal Capacity: \"+    \n                                               initialCapacity);    \n        if (loadFactor <= 0 || Float.isNaN(loadFactor))    \n            throw new IllegalArgumentException(\"Illegal Load: \"+loadFactor);    \n   \n        if (initialCapacity==0)    \n            initialCapacity = 1;    \n        this.loadFactor = loadFactor;    \n        table = new Entry[initialCapacity];    \n        threshold = (int)(initialCapacity * loadFactor);    \n    }    \n   \n    // 指定“容量大小”的构造函数    \n    public Hashtable(int initialCapacity) {    \n        this(initialCapacity, 0.75f);    \n    }    \n   \n    // 默认构造函数。    \n    public Hashtable() {    \n        // 默认构造函数，指定的容量大小是11；加载因子是0.75    \n        this(11, 0.75f);    \n    }    \n   \n    // 包含“子Map”的构造函数    \n    public Hashtable(Map<? extends K, ? extends V> t) {    \n        this(Math.max(2*t.size(), 11), 0.75f);    \n        // 将“子Map”的全部元素都添加到Hashtable中    \n        putAll(t);    \n    }    \n    \n    private int hash(Object k) {\n        if (useAltHashing) {\n            if (k.getClass() == String.class) {\n                return sun.misc.Hashing.stringHash32((String) k);\n            } else {\n                int h = hashSeed ^ k.hashCode();\n\n                // This function ensures that hashCodes that differ only by\n                // constant multiples at each bit position have a bounded\n                // number of collisions (approximately 8 at default load factor).\n                h ^= (h >>> 20) ^ (h >>> 12);\n                return h ^ (h >>> 7) ^ (h >>> 4);\n             }\n        } else  {\n            return k.hashCode();\n        }\n    }\n   \n    public synchronized int size() {    \n        return count;    \n    }    \n   \n    public synchronized boolean isEmpty() {    \n        return count == 0;    \n    }    \n   \n    // 返回“所有key”的枚举对象    \n    public synchronized Enumeration<K> keys() {    \n        return this.<K>getEnumeration(KEYS);    \n    }    \n   \n    // 返回“所有value”的枚举对象    \n    public synchronized Enumeration<V> elements() {    \n        return this.<V>getEnumeration(VALUES);    \n    }    \n   \n    // 判断Hashtable是否包含“值(value)”    \n    public synchronized boolean contains(Object value) {    \n        //注意，Hashtable中的value不能是null，    \n        // 若是null的话，抛出异常!    \n        if (value == null) {    \n            throw new NullPointerException();    \n        }    \n   \n        // 从后向前遍历table数组中的元素(Entry)    \n        // 对于每个Entry(单向链表)，逐个遍历，判断节点的值是否等于value    \n        Entry tab[] = table;    \n        for (int i = tab.length ; i-- > 0 ;) {    \n            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {    \n                if (e.value.equals(value)) {    \n                    return true;    \n                }    \n            }    \n        }    \n        return false;    \n    }    \n   \n    public boolean containsValue(Object value) {    \n        return contains(value);    \n    }    \n   \n    // 判断Hashtable是否包含key    \n    public synchronized boolean containsKey(Object key) {    \n        Entry tab[] = table;    \n        //计算hash值，直接用key的hashCode代替  \n        int hash = key.hashCode();      \n        // 计算在数组中的索引值   \n        int index = (hash & 0x7FFFFFFF) % tab.length;    \n        // 找到“key对应的Entry(链表)”，然后在链表中找出“哈希值”和“键值”与key都相等的元素    \n        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {    \n            if ((e.hash == hash) && e.key.equals(key)) {    \n                return true;    \n            }    \n        }    \n        return false;    \n    }    \n   \n    // 返回key对应的value，没有的话返回null    \n    public synchronized V get(Object key) {    \n        Entry tab[] = table;    \n        int hash = hash(key);\n        // 计算索引值，    \n        int index = (hash & 0x7FFFFFFF) % tab.length;    \n        // 找到“key对应的Entry(链表)”，然后在链表中找出“哈希值”和“键值”与key都相等的元素    \n        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {    \n            if ((e.hash == hash) && e.key.equals(key)) {    \n                return e.value;    \n            }    \n        }    \n        return null;    \n    }    \n   \n    // 调整Hashtable的长度，将长度变成原来的2倍+1   \n    protected void rehash() {    \n        int oldCapacity = table.length;    \n        Entry[] oldMap = table;    \n   \n        //创建新容量大小的Entry数组  \n        int newCapacity = oldCapacity * 2 + 1;    \n        Entry[] newMap = new Entry[newCapacity];    \n   \n        modCount++;    \n        threshold = (int)(newCapacity * loadFactor);    \n        table = newMap;    \n          \n        //将“旧的Hashtable”中的元素复制到“新的Hashtable”中  \n        for (int i = oldCapacity ; i-- > 0 ;) {    \n            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {    \n                Entry<K,V> e = old;    \n                old = old.next;    \n                //重新计算index  \n                int index = (e.hash & 0x7FFFFFFF) % newCapacity;    \n                e.next = newMap[index];    \n                newMap[index] = e;    \n            }    \n        }    \n    }    \n   \n    // 将“key-value”添加到Hashtable中    \n    public synchronized V put(K key, V value) {    \n        // Hashtable中不能插入value为null的元素！！！    \n        if (value == null) {    \n            throw new NullPointerException();    \n        }    \n   \n        // 若“Hashtable中已存在键为key的键值对”，    \n        // 则用“新的value”替换“旧的value”    \n        Entry tab[] = table;    \n        int hash = hash(key);\n        int index = (hash & 0x7FFFFFFF) % tab.length;    \n        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {    \n            if ((e.hash == hash) && e.key.equals(key)) {    \n                V old = e.value;    \n                e.value = value;    \n                return old;    \n                }    \n        }    \n   \n        // 若“Hashtable中不存在键为key的键值对”，  \n        // 将“修改统计数”+1    \n        modCount++;    \n        //  若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子)    \n        //  则调整Hashtable的大小    \n        if (count >= threshold) {  \n            rehash();    \n   \n            tab = table;    \n            index = (hash & 0x7FFFFFFF) % tab.length;    \n        }    \n   \n        //将新的key-value对插入到tab[index]处（即链表的头结点）  \n        Entry<K,V> e = tab[index];           \n        tab[index] = new Entry<K,V>(hash, key, value, e);    \n        count++;    \n        return null;    \n    }    \n   \n    // 删除Hashtable中键为key的元素    \n    public synchronized V remove(Object key) {    \n        Entry tab[] = table;    \n        int hash = hash(key);\n        int index = (hash & 0x7FFFFFFF) % tab.length;    \n          \n        //从table[index]链表中找出要删除的节点，并删除该节点。  \n        //因为是单链表，因此要保留带删节点的前一个节点，才能有效地删除节点  \n        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {    \n            if ((e.hash == hash) && e.key.equals(key)) {    \n                modCount++;    \n                if (prev != null) {    \n                    prev.next = e.next;    \n                } else {    \n                    tab[index] = e.next;    \n                }    \n                count--;    \n                V oldValue = e.value;    \n                e.value = null;    \n                return oldValue;    \n            }    \n        }    \n        return null;    \n    }    \n   \n    // 将“Map(t)”的中全部元素逐一添加到Hashtable中    \n    public synchronized void putAll(Map<? extends K, ? extends V> t) {    \n        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())    \n            put(e.getKey(), e.getValue());    \n    }    \n   \n    // 清空Hashtable    \n    // 将Hashtable的table数组的值全部设为null    \n    public synchronized void clear() {    \n        Entry tab[] = table;    \n        modCount++;    \n        for (int index = tab.length; --index >= 0; )    \n            tab[index] = null;    \n        count = 0;    \n    }    \n   \n    // 克隆一个Hashtable，并以Object的形式返回。    \n    public synchronized Object clone() {    \n        try {    \n            Hashtable<K,V> t = (Hashtable<K,V>) super.clone();    \n            t.table = new Entry[table.length];    \n            for (int i = table.length ; i-- > 0 ; ) {    \n                t.table[i] = (table[i] != null)    \n                ? (Entry<K,V>) table[i].clone() : null;    \n            }    \n            t.keySet = null;    \n            t.entrySet = null;    \n            t.values = null;    \n            t.modCount = 0;    \n            return t;    \n        } catch (CloneNotSupportedException e) {     \n            throw new InternalError();    \n        }    \n    }    \n   \n    public synchronized String toString() {    \n        int max = size() - 1;    \n        if (max == -1)    \n            return \"{}\";    \n   \n        StringBuilder sb = new StringBuilder();    \n        Iterator<Map.Entry<K,V>> it = entrySet().iterator();    \n   \n        sb.append('{');    \n        for (int i = 0; ; i++) {    \n            Map.Entry<K,V> e = it.next();    \n            K key = e.getKey();    \n            V value = e.getValue();    \n            sb.append(key   == this ? \"(this Map)\" : key.toString());    \n            sb.append('=');    \n            sb.append(value == this ? \"(this Map)\" : value.toString());    \n   \n            if (i == max)    \n                return sb.append('}').toString();    \n            sb.append(\", \");    \n        }    \n    }    \n   \n    // 获取Hashtable的枚举类对象    \n    // 若Hashtable的实际大小为0,则返回“空枚举类”对象；    \n    // 否则，返回正常的Enumerator的对象。   \n    private <T> Enumeration<T> getEnumeration(int type) {    \n    if (count == 0) {    \n        return (Enumeration<T>)emptyEnumerator;    \n    } else {    \n        return new Enumerator<T>(type, false);    \n    }    \n    }    \n   \n    // 获取Hashtable的迭代器    \n    // 若Hashtable的实际大小为0,则返回“空迭代器”对象；    \n    // 否则，返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口)    \n    private <T> Iterator<T> getIterator(int type) {    \n        if (count == 0) {    \n            return (Iterator<T>) emptyIterator;    \n        } else {    \n            return new Enumerator<T>(type, true);    \n        }    \n    }    \n   \n    // Hashtable的“key的集合”。它是一个Set，没有重复元素    \n    private transient volatile Set<K> keySet = null;    \n    // Hashtable的“key-value的集合”。它是一个Set，没有重复元素    \n    private transient volatile Set<Map.Entry<K,V>> entrySet = null;    \n    // Hashtable的“key-value的集合”。它是一个Collection，可以有重复元素    \n    private transient volatile Collection<V> values = null;    \n   \n    // 返回一个被synchronizedSet封装后的KeySet对象    \n    // synchronizedSet封装的目的是对KeySet的所有方法都添加synchronized，实现多线程同步    \n    public Set<K> keySet() {    \n        if (keySet == null)    \n            keySet = Collections.synchronizedSet(new KeySet(), this);    \n        return keySet;    \n    }    \n   \n    // Hashtable的Key的Set集合。    \n    // KeySet继承于AbstractSet，所以，KeySet中的元素没有重复的。    \n    private class KeySet extends AbstractSet<K> {    \n        public Iterator<K> iterator() {    \n            return getIterator(KEYS);    \n        }    \n        public int size() {    \n            return count;    \n        }    \n        public boolean contains(Object o) {    \n            return containsKey(o);    \n        }    \n        public boolean remove(Object o) {    \n            return Hashtable.this.remove(o) != null;    \n        }    \n        public void clear() {    \n            Hashtable.this.clear();    \n        }    \n    }    \n   \n    // 返回一个被synchronizedSet封装后的EntrySet对象    \n    // synchronizedSet封装的目的是对EntrySet的所有方法都添加synchronized，实现多线程同步    \n    public Set<Map.Entry<K,V>> entrySet() {    \n        if (entrySet==null)    \n            entrySet = Collections.synchronizedSet(new EntrySet(), this);    \n        return entrySet;    \n    }    \n   \n    // Hashtable的Entry的Set集合。    \n    // EntrySet继承于AbstractSet，所以，EntrySet中的元素没有重复的。    \n    private class EntrySet extends AbstractSet<Map.Entry<K,V>> {    \n        public Iterator<Map.Entry<K,V>> iterator() {    \n            return getIterator(ENTRIES);    \n        }    \n   \n        public boolean add(Map.Entry<K,V> o) {    \n            return super.add(o);    \n        }    \n   \n        // 查找EntrySet中是否包含Object(0)    \n        // 首先，在table中找到o对应的Entry链表    \n        // 然后，查找Entry链表中是否存在Object    \n        public boolean contains(Object o) {    \n            if (!(o instanceof Map.Entry))    \n                return false;    \n            Map.Entry entry = (Map.Entry)o;    \n            Object key = entry.getKey();    \n            Entry[] tab = table;    \n            int hash = hash(key);\n            int index = (hash & 0x7FFFFFFF) % tab.length;    \n   \n            for (Entry e = tab[index]; e != null; e = e.next)    \n                if (e.hash==hash && e.equals(entry))    \n                    return true;    \n            return false;    \n        }    \n   \n        // 删除元素Object(0)    \n        // 首先，在table中找到o对应的Entry链表  \n        // 然后，删除链表中的元素Object    \n        public boolean remove(Object o) {    \n            if (!(o instanceof Map.Entry))    \n                return false;    \n            Map.Entry<K,V> entry = (Map.Entry<K,V>) o;    \n            K key = entry.getKey();    \n            Entry[] tab = table;    \n            int hash = hash(key);\n            int index = (hash & 0x7FFFFFFF) % tab.length;    \n   \n            for (Entry<K,V> e = tab[index], prev = null; e != null;    \n                 prev = e, e = e.next) {    \n                if (e.hash==hash && e.equals(entry)) {    \n                    modCount++;    \n                    if (prev != null)    \n                        prev.next = e.next;    \n                    else   \n                        tab[index] = e.next;    \n   \n                    count--;    \n                    e.value = null;    \n                    return true;    \n                }    \n            }    \n            return false;    \n        }    \n   \n        public int size() {    \n            return count;    \n        }    \n   \n        public void clear() {    \n            Hashtable.this.clear();    \n        }    \n    }    \n   \n    // 返回一个被synchronizedCollection封装后的ValueCollection对象    \n    // synchronizedCollection封装的目的是对ValueCollection的所有方法都添加synchronized，实现多线程同步    \n    public Collection<V> values() {    \n    if (values==null)    \n        values = Collections.synchronizedCollection(new ValueCollection(),    \n                                                        this);    \n        return values;    \n    }    \n   \n    // Hashtable的value的Collection集合。    \n    // ValueCollection继承于AbstractCollection，所以，ValueCollection中的元素可以重复的。    \n    private class ValueCollection extends AbstractCollection<V> {    \n        public Iterator<V> iterator() {    \n        return getIterator(VALUES);    \n        }    \n        public int size() {    \n            return count;    \n        }    \n        public boolean contains(Object o) {    \n            return containsValue(o);    \n        }    \n        public void clear() {    \n            Hashtable.this.clear();    \n        }    \n    }    \n   \n    // 重新equals()函数    \n    // 若两个Hashtable的所有key-value键值对都相等，则判断它们两个相等    \n    public synchronized boolean equals(Object o) {    \n        if (o == this)    \n            return true;    \n   \n        if (!(o instanceof Map))    \n            return false;    \n        Map<K,V> t = (Map<K,V>) o;    \n        if (t.size() != size())    \n            return false;    \n   \n        try {    \n            // 通过迭代器依次取出当前Hashtable的key-value键值对    \n            // 并判断该键值对，存在于Hashtable中。    \n            // 若不存在，则立即返回false；否则，遍历完“当前Hashtable”并返回true。    \n            Iterator<Map.Entry<K,V>> i = entrySet().iterator();    \n            while (i.hasNext()) {    \n                Map.Entry<K,V> e = i.next();    \n                K key = e.getKey();    \n                V value = e.getValue();    \n                if (value == null) {    \n                    if (!(t.get(key)==null && t.containsKey(key)))    \n                        return false;    \n                } else {    \n                    if (!value.equals(t.get(key)))    \n                        return false;    \n                }    \n            }    \n        } catch (ClassCastException unused)   {    \n            return false;    \n        } catch (NullPointerException unused) {    \n            return false;    \n        }    \n   \n        return true;    \n    }    \n   \n    // 计算Entry的hashCode    \n    // 若 Hashtable的实际大小为0 或者 加载因子<0，则返回0。    \n    // 否则，返回“Hashtable中的每个Entry的key和value的异或值 的总和”。    \n    public synchronized int hashCode() {    \n        int h = 0;    \n        if (count == 0 || loadFactor < 0)    \n            return h;  // Returns zero    \n   \n        loadFactor = -loadFactor;  // Mark hashCode computation in progress    \n        Entry[] tab = table;    \n        for (int i = 0; i < tab.length; i++)    \n            for (Entry e = tab[i]; e != null; e = e.next)    \n                h += e.key.hashCode() ^ e.value.hashCode();    \n        loadFactor = -loadFactor;  // Mark hashCode computation complete    \n   \n        return h;    \n    }    \n   \n    // java.io.Serializable的写入函数    \n    // 将Hashtable的“总的容量，实际容量，所有的Entry”都写入到输出流中    \n    private synchronized void writeObject(java.io.ObjectOutputStream s)    \n        throws IOException    \n    {    \n        // Write out the length, threshold, loadfactor    \n        s.defaultWriteObject();    \n   \n        // Write out length, count of elements and then the key/value objects    \n        s.writeInt(table.length);    \n        s.writeInt(count);    \n        for (int index = table.length-1; index >= 0; index--) {    \n            Entry entry = table[index];    \n   \n            while (entry != null) {    \n            s.writeObject(entry.key);    \n            s.writeObject(entry.value);    \n            entry = entry.next;    \n            }    \n        }    \n    }    \n   \n    // java.io.Serializable的读取函数：根据写入方式读出    \n    // 将Hashtable的“总的容量，实际容量，所有的Entry”依次读出    \n    private void readObject(java.io.ObjectInputStream s)    \n         throws IOException, ClassNotFoundException    \n    {    \n        // Read in the length, threshold, and loadfactor    \n        s.defaultReadObject();    \n   \n        // Read the original length of the array and number of elements    \n        int origlength = s.readInt();    \n        int elements = s.readInt();    \n   \n        // Compute new size with a bit of room 5% to grow but    \n        // no larger than the original size.  Make the length    \n        // odd if it's large enough, this helps distribute the entries.    \n        // Guard against the length ending up zero, that's not valid.    \n        int length = (int)(elements * loadFactor) + (elements / 20) + 3;    \n        if (length > elements && (length & 1) == 0)    \n            length--;    \n        if (origlength > 0 && length > origlength)    \n            length = origlength;    \n   \n        Entry[] table = new Entry[length];    \n        count = 0;    \n   \n        // Read the number of elements and then all the key/value objects    \n        for (; elements > 0; elements--) {    \n            K key = (K)s.readObject();    \n            V value = (V)s.readObject();    \n                // synch could be eliminated for performance    \n                reconstitutionPut(table, key, value);    \n        }    \n        this.table = table;    \n    }    \n   \n    private void reconstitutionPut(Entry[] tab, K key, V value)    \n        throws StreamCorruptedException    \n    {    \n        if (value == null) {    \n            throw new java.io.StreamCorruptedException();    \n        }    \n        // Makes sure the key is not already in the hashtable.    \n        // This should not happen in deserialized version.    \n        int hash = key.hashCode();    \n        int index = (hash & 0x7FFFFFFF) % tab.length;    \n        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {    \n            if ((e.hash == hash) && e.key.equals(key)) {    \n                throw new java.io.StreamCorruptedException();    \n            }    \n        }    \n        // Creates the new entry.    \n        Entry<K,V> e = tab[index];    \n        tab[index] = new Entry<K,V>(hash, key, value, e);    \n        count++;    \n    }    \n   \n    // Hashtable的Entry节点，它本质上是一个单向链表。    \n    // 也因此，我们才能推断出Hashtable是由拉链法实现的散列表    \n    private static class Entry<K,V> implements Map.Entry<K,V> {    \n        // 哈希值    \n        int hash;    \n        K key;    \n        V value;    \n        // 指向的下一个Entry，即链表的下一个节点    \n        Entry<K,V> next;    \n   \n        // 构造函数    \n        protected Entry(int hash, K key, V value, Entry<K,V> next) {    \n            this.hash = hash;    \n            this.key = key;    \n            this.value = value;    \n            this.next = next;    \n        }    \n   \n        protected Object clone() {    \n            return new Entry<K,V>(hash, key, value,    \n                  (next==null ? null : (Entry<K,V>) next.clone()));    \n        }    \n   \n        public K getKey() {    \n            return key;    \n        }    \n   \n        public V getValue() {    \n            return value;    \n        }    \n   \n        // 设置value。若value是null，则抛出异常。    \n        public V setValue(V value) {    \n            if (value == null)    \n                throw new NullPointerException();    \n   \n            V oldValue = this.value;    \n            this.value = value;    \n            return oldValue;    \n        }    \n   \n        // 覆盖equals()方法，判断两个Entry是否相等。    \n        // 若两个Entry的key和value都相等，则认为它们相等。    \n        public boolean equals(Object o) {    \n            if (!(o instanceof Map.Entry))    \n                return false;    \n            Map.Entry e = (Map.Entry)o;    \n   \n            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&    \n               (value==null ? e.getValue()==null : value.equals(e.getValue()));    \n        }    \n   \n        public int hashCode() {    \n            return hash ^ (value==null ? 0 : value.hashCode());    \n        }    \n   \n        public String toString() {    \n            return key.toString()+\"=\"+value.toString();    \n        }    \n    }    \n   \n    private static final int KEYS = 0;    \n    private static final int VALUES = 1;    \n    private static final int ENTRIES = 2;    \n   \n    // Enumerator的作用是提供了“通过elements()遍历Hashtable的接口” 和 “通过entrySet()遍历Hashtable的接口”。    \n    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {    \n        // 指向Hashtable的table    \n        Entry[] table = Hashtable.this.table;    \n        // Hashtable的总的大小    \n        int index = table.length;    \n        Entry<K,V> entry = null;    \n        Entry<K,V> lastReturned = null;    \n        int type;    \n   \n        // Enumerator是 “迭代器(Iterator)” 还是 “枚举类(Enumeration)”的标志    \n        // iterator为true，表示它是迭代器；否则，是枚举类。    \n        boolean iterator;    \n   \n        // 在将Enumerator当作迭代器使用时会用到，用来实现fail-fast机制。    \n        protected int expectedModCount = modCount;    \n   \n        Enumerator(int type, boolean iterator) {    \n            this.type = type;    \n            this.iterator = iterator;    \n        }    \n   \n        // 从遍历table的数组的末尾向前查找，直到找到不为null的Entry。    \n        public boolean hasMoreElements() {    \n            Entry<K,V> e = entry;    \n            int i = index;    \n            Entry[] t = table;    \n            /* Use locals for faster loop iteration */   \n            while (e == null && i > 0) {    \n                e = t[--i];    \n            }    \n            entry = e;    \n            index = i;    \n            return e != null;    \n        }    \n   \n        // 获取下一个元素    \n        // 注意：从hasMoreElements() 和nextElement() 可以看出“Hashtable的elements()遍历方式”    \n        // 首先，从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。    \n        // 然后，依次向后遍历单向链表Entry。    \n        public T nextElement() {    \n            Entry<K,V> et = entry;    \n            int i = index;    \n            Entry[] t = table;    \n            /* Use locals for faster loop iteration */   \n            while (et == null && i > 0) {    \n                et = t[--i];    \n            }    \n            entry = et;    \n            index = i;    \n            if (et != null) {    \n                Entry<K,V> e = lastReturned = entry;    \n                entry = e.next;    \n                return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);    \n            }    \n            throw new NoSuchElementException(\"Hashtable Enumerator\");    \n        }    \n   \n        // 迭代器Iterator的判断是否存在下一个元素    \n        // 实际上，它是调用的hasMoreElements()    \n        public boolean hasNext() {    \n            return hasMoreElements();    \n        }    \n   \n        // 迭代器获取下一个元素    \n        // 实际上，它是调用的nextElement()    \n        public T next() {    \n            if (modCount != expectedModCount)    \n                throw new ConcurrentModificationException();    \n            return nextElement();    \n        }    \n   \n        // 迭代器的remove()接口。    \n        // 首先，它在table数组中找出要删除元素所在的Entry，    \n        // 然后，删除单向链表Entry中的元素。    \n        public void remove() {    \n            if (!iterator)    \n                throw new UnsupportedOperationException();    \n            if (lastReturned == null)    \n                throw new IllegalStateException(\"Hashtable Enumerator\");    \n            if (modCount != expectedModCount)    \n                throw new ConcurrentModificationException();    \n   \n            synchronized(Hashtable.this) {    \n                Entry[] tab = Hashtable.this.table;    \n                int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;    \n   \n                for (Entry<K,V> e = tab[index], prev = null; e != null;    \n                     prev = e, e = e.next) {    \n                    if (e == lastReturned) {    \n                        modCount++;    \n                        expectedModCount++;    \n                        if (prev == null)    \n                            tab[index] = e.next;    \n                        else   \n                            prev.next = e.next;    \n                        count--;    \n                        lastReturned = null;    \n                        return;    \n                    }    \n                }    \n                throw new ConcurrentModificationException();    \n            }    \n        }    \n    }    \n   \n   \n    private static Enumeration emptyEnumerator = new EmptyEnumerator();    \n    private static Iterator emptyIterator = new EmptyIterator();    \n   \n    // 空枚举类    \n    // 当Hashtable的实际大小为0；此时，又要通过Enumeration遍历Hashtable时，返回的是“空枚举类”的对象。    \n    private static class EmptyEnumerator implements Enumeration<Object> {    \n   \n        EmptyEnumerator() {    \n        }    \n   \n        // 空枚举类的hasMoreElements() 始终返回false    \n        public boolean hasMoreElements() {    \n            return false;    \n        }    \n   \n        // 空枚举类的nextElement() 抛出异常    \n        public Object nextElement() {    \n            throw new NoSuchElementException(\"Hashtable Enumerator\");    \n        }    \n    }    \n   \n   \n    // 空迭代器    \n    // 当Hashtable的实际大小为0；此时，又要通过迭代器遍历Hashtable时，返回的是“空迭代器”的对象。    \n    private static class EmptyIterator implements Iterator<Object> {    \n   \n        EmptyIterator() {    \n        }    \n   \n        public boolean hasNext() {    \n            return false;    \n        }    \n   \n        public Object next() {    \n            throw new NoSuchElementException(\"Hashtable Iterator\");    \n        }    \n   \n        public void remove() {    \n            throw new IllegalStateException(\"Hashtable Iterator\");    \n        }    \n   \n    }    \n}   \n```\n\n##几点总结\n 针对Hashtable，我们同样给出几点比较重要的总结，但要结合与HashMap的比较来总结。\n\n1. 二者的存储结构和解决冲突的方法都是相同的。\n2. HashTable在不指定容量的情况下的默认容量为11，而HashMap为16，Hashtable不要求底层数组的容量一定要为2的整数次幂，而HashMap则要求一定为2的整数次幂。\n3. Hashtable中key和value都不允许为null，而HashMap中key和value都允许为null（key只能有一个为null，而value则可以有多个为null）。但是如果在Hashtable中有类似put(null,null)的操作，编译同样可以通过，因为key和value都是Object类型，但运行时会抛出NullPointerException异常，这是JDK的规范规定的。我们来看下ContainsKey方法和ContainsValue的源码：\n\n```\n// 判断Hashtable是否包含“值(value)”    \n public synchronized boolean contains(Object value) {    \n     //注意，Hashtable中的value不能是null，    \n     // 若是null的话，抛出异常!    \n     if (value == null) {    \n         throw new NullPointerException();    \n     }    \n  \n     // 从后向前遍历table数组中的元素(Entry)    \n     // 对于每个Entry(单向链表)，逐个遍历，判断节点的值是否等于value    \n     Entry tab[] = table;    \n     for (int i = tab.length ; i-- > 0 ;) {    \n         for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {    \n             if (e.value.equals(value)) {    \n                 return true;    \n             }    \n         }    \n     }    \n     return false;    \n }    \n  \n public boolean containsValue(Object value) {    \n     return contains(value);    \n }    \n  \n // 判断Hashtable是否包含key    \n public synchronized boolean containsKey(Object key) {    \n     Entry tab[] = table;    \n/计算hash值，直接用key的hashCode代替  \n     int hash = hash(key);\n     // 计算在数组中的索引值   \n     int index = (hash & 0x7FFFFFFF) % tab.length;    \n     // 找到“key对应的Entry(链表)”，然后在链表中找出“哈希值”和“键值”与key都相等的元素    \n     for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {    \n         if ((e.hash == hash) && e.key.equals(key)) {    \n             return true;    \n         }    \n     }    \n     return false;    \n }    \n```\n\n 很明显，如果value为null，会直接抛出NullPointerException异常，但源码中并没有对key是否为null判断，有点小不解！不过NullPointerException属于RuntimeException异常，是可以由JVM自动抛出的，也许对key的值在JVM中有所限制吧。\n4. Hashtable扩容时，将容量变为原来的2倍加1，而HashMap扩容时，将容量变为原来的2倍。\n5. Hashtable和HashMap都重新计算了key的hash值，Hashtable在求hash值对应的位置索引时，用取模运算，而HashMap在求位置索引时，则用与运算，且这里一般先用hash&0x7FFFFFFF后，再对length取模，&0x7FFFFFFF的目的是为了将负的hash值转化为正值，因为hash值有可能为负数，而&0x7FFFFFFF后，只有符号外改变，而后面的位都不变。"
  },
  {
    "path": "Part2/JavaSE/Hashmap的hashcode的作用等.md",
    "content": "#HashMap的hashcode的作用？\n1. hashCode的存在主要是用于查找的快捷性，如Hashtable，HashMap等，hashCode是用来在散列存储结构中确定对象的存储地址的；\n2. 如果两个对象相同，就是适用于equals(java.lang.Object) 方法，那么这两个对象的hashCode一定要相同；\n3. 如果对象的equals方法被重写，那么对象的hashCode也尽量重写，并且产生hashCode使用的对象，一定要和equals方法中使用的一致，否则就会违反上面提到的第2点；\n4. 两个对象的hashCode相同，并不一定表示两个对象就相同，也就是不一定适用于equals(java.lang.Object) 方法，只能够说明这两个对象在散列存储结构中，如Hashtable，他们“存放在同一个篮子里”。\n\n#什么时候需要重写？\n\n一般的地方不需要重载hashCode，只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode，那么为什么要重载hashCode呢？就HashMap来说，好比HashMap就是一个大内存块，里面有很多小内存块，小内存块里面是一系列的对象，可以利用hashCode来查找小内存块hashCode%size(小内存块数量)，所以当equal相等时，hashCode必须相等，而且如果是object对象，必须重载hashCode和equal方法。\n\n[对于equal和hashcode的理解，何时需要重写](http://blog.csdn.net/qq352773277/article/details/41675407)\n\n#如何解决哈希冲突？\n\n1. 线性探查法(Linear Probing)\n2. 线性补偿探测法\n3. 随机探测\n\n[解决哈希（HASH）冲突的主要方法](http://blog.csdn.net/lightty/article/details/11191971)\n\n\n\n#查找的时候流程是如何？\n\n1.hashcode是用来查找的，如果你学过数据结构就应该知道，在查找和排序这一章有\n例如内存中有这样的位置\n0  1  2  3  4  5  6  7  \n而我有个类，这个类有个字段叫ID,我要把这个类存放在以上8个位置之一，如果不用hashcode而任意存放，那么当查找时就需要到这八个位置里挨个去找，或者用二分法一类的算法。\n但如果用hashcode那就会使效率提高很多。\n我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID％8，然后把我们的类存放在取得得余数那个位置。比如我们的ID为9，9除8的余数为1，那么我们就把该类存在1这个位置，如果ID是13，求得的余数是5，那么我们就把该类放在5这个位置。这样，以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。\n2.但是如果两个类有相同的hashcode怎么办那（我们假设上面的类的ID不是唯一的），例如9除以8和17除以8的余数都是1，那么这是不是合法的，回答是：可以这样。那么如何判断呢？在这个时候就需要定义 equals了。\n也就是说，我们先通过 hashcode来判断两个类是否存放某个桶里，但这个桶里可能有很多类，那么我们就需要再通过 equals 来在这个桶里找到我们要的类。\n那么。重写了equals()，为什么还要重写hashCode()呢？\n想想，你要在一个桶里找东西，你必须先要找到这个桶啊，你不通过重写hashcode()来找到桶，光重写equals()有什么用啊\n\n\n总的来说，Java中的集合（Collection）有两类，一类是List，再有一类是Set。你知道它们的区别吗？前者集合内的元素是有序的，元素可以重复；后者元素无序，但元素不可重复。那么这里就有一个比较严重的问题了：要想保证元素不重复，可两个元素是否重复应该依据什么来判断呢？这就是 Object.equals方法了。但是，如果每增加一个元素就检查一次，那么当元素很多时，后添加到集合中的元素比较的次数就非常多了。也就是说，如果集合中现在已经有1000个元素，那么第1001个元素加入集合时，它就要调用1000次equals方法。这显然会大大降低效率。   \n\n于是，Java采用了哈希表的原理。哈希算法也称为散列算法，是将数据依特定算法直接指定到一个地址上。关于哈希算法，这里就不详细介绍。可以这样简单理解，hashCode方法实际上返回的就是对象存储位置的映像。   \n\n这样一来，当集合要添加新的元素时，先调用这个元素的hashCode方法，就能定位到它应该放置的存储位置。如果这个位置上没有元素，它就可以直接存储在这个位置上，不用再进行任何比较了；如果这个位置上已经有元素了，就调用它的equals方法与新元素进行比较，相同的话就不存了，不相同就表示发生冲突了，散列表对于冲突有具体的解决办法，但最终还会将新元素保存在适当的位置。这样一来，实际调用equals方法的次数就大大降低了，几乎只需要一两次。   \n\n[深入解析hashcode,hashMap源码](http://blog.csdn.net/zhuanshenweiliu/article/details/39177447)\n"
  },
  {
    "path": "Part2/JavaSE/Java中的内存泄漏.md",
    "content": "#Java中的内存泄漏\n---\n\n1.Java内存回收机制\n\n不论哪种语言的内存分配方式，都需要返回所分配内存的真实地址，也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的，这些对象的创建都是在堆（Heap）中分配的，所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象，会监控每个对象的运行状况，对他们的申请、引用、被引用、赋值等状况进行监控，Java会使用有向图的方法进行管理内存，实时监控对象是否可以达到，如果不可到达，则就将其回收，这样也可以消除引用循环的问题。在Java语言中，判断一个内存空间是否符合垃圾收集标准有两个：一个是给对象赋予了空值null，以下再没有调用过，另一个是给对象赋予了新值，这样重新分配了内存空间。 \n\n2.Java内存泄漏引起的原因\n\n内存泄漏是指无用对象（不再使用的对象）持续占有内存或无用对象的内存得不到及时释放，从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉，这样开发者就不知道存在内存泄露，但有时也会很严重，会提示你Out of memory。\n\nJava内存泄漏的根本原因是什么呢？长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏，尽管短生命周期对象已经不再需要，但是因为长生命周期持有它的引用而导致不能被回收，这就是Java中内存泄漏的发生场景。具体主要有如下几大类：\n\n1、静态集合类引起内存泄漏：\n\n像HashMap、Vector等的使用最容易出现内存泄露，这些静态变量的生命周期和应用程序一致，他们所引用的所有的对象Object也不能被释放，因为他们也将一直被Vector等引用着。 \n\n例如\n\n```\nStatic Vector v = new Vector(10);\nfor (int i = 1; i<100; i++)\n{\nObject o = new Object();\nv.add(o);\no = null;\n}\n```\n\n在这个例子中，循环申请Object 对象，并将所申请的对象放入一个Vector 中，如果仅仅释放引用本身（o=null），那么Vector 仍然引用该对象，所以这个对象对GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从Vector 中删除，最简单的方法就是将Vector对象设置为null。\n\n2、当集合里面的对象属性被修改后，再调用remove()方法时不起作用。\n\n例如：\n\n```\npublic static void main(String[] args)\n{\nSet<Person> set = new HashSet<Person>();\nPerson p1 = new Person(\"唐僧\",\"pwd1\",25);\nPerson p2 = new Person(\"孙悟空\",\"pwd2\",26);\nPerson p3 = new Person(\"猪八戒\",\"pwd3\",27);\nset.add(p1);\nset.add(p2);\nset.add(p3);\nSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:3 个元素!\np3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变\n\nset.remove(p3); //此时remove不掉，造成内存泄漏\n\nset.add(p3); //重新添加，居然添加成功\nSystem.out.println(\"总共有:\"+set.size()+\" 个元素!\"); //结果：总共有:4 个元素!\nfor (Person person : set)\n{\nSystem.out.println(person);\n}\n}\n```\n\n3、监听器\n\n在java 编程中，我们都需要和监听器打交道，通常一个应用当中会用到很多监听器，我们会调用一个控件的诸如addXXXListener()等方法来增加监听器，但往往在释放对象的时候却没有记住去删除这些监听器，从而增加了内存泄漏的机会。\n\n4、各种连接 \n\n比如数据库连接（dataSourse.getConnection()），网络连接(socket)和io连接，除非其显式的调用了其close（）方法将其连接关闭，否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收，但Connection 一定要显式回收，因为Connection 在任何时候都无法自动回收，而Connection一旦回收，Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池，情况就不一样了，除了要显式地关闭连接，还必须显式地关闭Resultset Statement 对象（关闭其中一个，另外一个也会关闭），否则就会造成大量的Statement 对象无法释放，从而引起内存泄漏。这种情况下一般都会在try里面去的连接，在finally里面释放连接。\n\n5、内部类和外部模块的引用\n\n内部类的引用是比较容易遗忘的一种，而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用，例如程序员A 负责A 模块，调用了B 模块的一个方法如：\npublic void registerMsg(Object b);\n这种调用就要非常小心了，传入了一个对象，很可能模块B就保持了对该对象的引用，这时候就需要注意模块B 是否提供相应的操作去除引用。\n\n6、单例模式 \n\n不正确使用单例模式是引起内存泄漏的一个常见问题，单例对象在初始化后将在JVM的整个生命周期中存在（以静态变量的方式），如果单例对象持有外部的引用，那么这个对象将不能被JVM正常回收，导致内存泄漏，考虑下面的例子：\n\n```\nclass A{\npublic A(){\nB.getInstance().setA(this);\n}\n....\n}\n//B类采用单例模式\nclass B{\nprivate A a;\nprivate static B instance=new B();\npublic B(){}\npublic static B getInstance(){\nreturn instance;\n}\npublic void setA(A a){\nthis.a=a;\n}\n//getter...\n} \n```\n\n\n显然B采用singleton模式，它持有一个A对象的引用，而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况\n"
  },
  {
    "path": "Part2/JavaSE/Java基础知识.md",
    "content": "#J2SE\n---\n\n##基础\n---\n**八种基本数据类型的大小，以及他们的封装类。**\n\n八种基本数据类型，int ,double ,long ,float, short,byte,character,boolean\n\n对应的封装类型是：Integer ,Double ,Long ,Float, Short,Byte,Character,Boolean\n\n---\n\n**Switch能否用string做参数？**\n\n在Java 5以前，switch(expr)中，expr只能是byte、short、char、int。从Java 5开始，Java中引入了枚举类型，expr也可以是enum类型，从Java 7开始，expr还可以是字符串（String），但是长整型（long）在目前所有的版本中都是不可以的。\n\n---\n\n**equals与==的区别。**\n\n[http://www.importnew.com/6804.html](http://www.importnew.com/6804.html)\n> ==与equals的主要区别是：==常用于比较原生类型，而equals()方法用于检查对象的相等性。另一个不同的点是：如果==和equals()用于比较对象，当两个引用地址相同，==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子，字符串的比较，不同情况==和equals()返回不同的结果。equals()方法最重要的一点是，能够根据业务要求去重写，按照自定义规则去判断两个对象是否相等。重写equals()方法的时候，要注意一下hashCode是否会因为对象的属性改变而改变，否则在使用散列集合储存该对象的时候会碰到坑！！理解equals()方法的存在是很重要的。\n\n1. 使用==比较有两种情况：\n\n        比较基础数据类型(Java中基础数据类型包括八中：short,int,long,float,double,char,byte,boolen)：这种情况下，==比较的是他们的值是否相等。\n        引用间的比较：在这种情况下，==比较的是他们在内存中的地址，也就是说，除非引用指向的是同一个new出来的对象，此时他们使用`==`去比较得到true，否则，得到false。\n2. 使用equals进行比较：\n    \n        equals追根溯源，是Object类中的一个方法，在该类中，equals的实现也仅仅只是比较两个对象的内存地址是否相等，但在一些子类中，如：String、Integer 等，该方法将被重写。\n\n3. 以`String`类为例子说明`eqauls`与`==`的区别：\n> 在开始这个例子之前，同学们需要知道JVM处理String的一些特性。*Java的虚拟机在内存中开辟出一块单独的区域，用来存储字符串对象，这块内存区域被称为字符串缓冲池。*当使用\n`String a = \"abc\"`这样的语句进行定义一个引用的时候，首先会在*字符串缓冲池*中查找是否已经相同的对象，如果存在，那么就直接将这个对象的引用返回给a，如果不存在，则需要新建一个值为\"abc\"的对象，再将新的引用返回a。`String a = new String(\"abc\");`这样的语句明确告诉JVM想要产生一个新的String对象，并且值为\"abc\"，于是就*在堆内存中的某一个小角落开辟了一个新的String对象*。\n\n    - `==`在比较引用的情况下，会去比较两个引用的内存地址是否相等。\n    ```\n        String str1 = \"abc\";\n        String str2 = \"abc\";\n        \n        System.out.println(str1 == str2);\n        System.out.println(str1.equals(str2));\n        \n        String str2 = new String(\"abc\");\n        System.out.println(str1 == str2);\n        System.out.println(str1.equals(str2));\n        \n    ```\n        以上代码将会输出\n        true\n        true\n        false\n        true\n        **第一个true：**因为在str2赋值之前，str1的赋值操作就已经在内存中创建了一个值为\"abc\"的对象了，然后str2将会与str1指向相同的地址。\n        **第二个true：**因为`String`已经重写了`equals`方法：为了方便大家阅读我贴出来，并且在注释用进行分析：\n        ```\n        public boolean equals(Object anObject) {\n        //如果比较的对象与自身内存地址相等的话\n        //就说明他两指向的是同一个对象\n        //所以此时equals的返回值跟==的结果是一样的。\n        if (this == anObject) {\n            return true;\n        }\n        //当比较的对象与自身的内存地址不相等，并且\n        //比较的对象是String类型的时候\n        //将会执行这个分支\n        if (anObject instanceof String) {\n            String anotherString = (String)anObject;\n            int n = value.length;\n            if (n == anotherString.value.length) {\n                char v1[] = value;\n                char v2[] = anotherString.value;\n                int i = 0;\n                //在这里循环遍历两个String中的char\n                while (n-- != 0) {\n                    //只要有一个不相等，那么就会返回false\n                    if (v1[i] != v2[i])\n                        return false;\n                    i++;\n                }\n                return true;\n            }\n        }\n        return false;\n    }\n        ```\n        进行以上分析之后，就不难理解第一段代码中的实例程序输出了。\n\n\n\n---\n\n**Object有哪些公用方法？**\n\n[http://www.cnblogs.com/yumo/p/4908315.html](http://www.cnblogs.com/yumo/p/4908315.html)\n\n1．clone方法\n\n保护方法，实现对象的浅复制，只有实现了Cloneable接口才可以调用该方法，否则抛出CloneNotSupportedException异常。\n\n主要是JAVA里除了8种基本类型传参数是值传递，其他的类对象传参数都是引用传递，我们有时候不希望在方法里讲参数改变，这是就需要在类中复写clone方法。\n\n2．getClass方法\n\nfinal方法，获得运行时类型。\n\n3．toString方法\n\n该方法用得比较多，一般子类都有覆盖。\n\n4．finalize方法\n\n该方法用于释放资源。因为无法确定该方法什么时候被调用，很少使用。\n\n5．equals方法\n\n该方法是非常重要的一个方法。一般equals和==是不一样的，但是在Object中两者是一样的。子类一般都要重写这个方法。\n\n6．hashCode方法\n\n该方法用于哈希查找，可以减少在查找中使用equals的次数，重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。\n\n一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode()，但是hashCode相等不一定就满足equals。不过为了提高效率，应该尽量使上面两个条件接近等价。\n\n如果不重写hashCode(),在HashSet中添加两个equals的对象，会将两个对象都加入进去。\n\n7．wait方法\n\nwait方法就是使当前线程等待该对象的锁，当前线程必须是该对象的拥有者，也就是具有该对象的锁。wait()方法一直等待，直到获得锁或者被中断。wait(long timeout)设定一个超时间隔，如果在规定时间内没有获得锁就返回。\n\n调用该方法后当前线程进入睡眠状态，直到以下事件发生。\n\n（1）其他线程调用了该对象的notify方法。\n\n（2）其他线程调用了该对象的notifyAll方法。\n\n（3）其他线程调用了interrupt中断该线程。\n\n（4）时间间隔到了。\n\n此时该线程就可以被调度了，如果是被中断的话就抛出一个InterruptedException异常。\n\n8．notify方法\n\n该方法唤醒在该对象上等待的某个线程。\n\n9．notifyAll方法\n\n该方法唤醒在该对象上等待的所有线程。\n\n---\n\n**Java的四种引用，强弱软虚，用到的场景。**\n\nJDK1.2之前只有强引用,其他几种引用都是在JDK1.2之后引入的.\n\n* 强引用（Strong Reference）\n\t最常用的引用类型，如Object obj = new Object(); 。只要强引用存在则GC时则必定不被回收。\n\t\n* 软引用（Soft Reference）\n\t用于描述还有用但非必须的对象，当堆将发生OOM（Out Of Memory）时则会回收软引用所指向的内存空间，若回收后依然空间不足才会抛出OOM。一般用于实现内存敏感的高速缓存。\n当真正对象被标记finalizable以及的finalize()方法调用之后并且内存已经清理, 那么如果SoftReference object还存在就被加入到它的 ReferenceQueue.只有前面几步完成后,Soft Reference和Weak Reference的get方法才会返回null\n\n* 弱引用（Weak Reference）\n\t发生GC时必定回收弱引用指向的内存空间。\n和软引用加入队列的时机相同\n\n* 虚引用（Phantom Reference)\n又称为幽灵引用或幻影引用，虚引用既不会影响对象的生命周期，也无法通过虚引用来获取对象实例，仅用于在发生GC时接收一个系统通知。\n当一个对象的finalize方法已经被调用了之后，这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了.\n虚引用和软引用和弱引用都不同,它会在内存没有清理的时候被加入引用队列.虚引用的建立必须要传入引用队列,其他可以没有\n\n---\n\n**Hashcode的作用。**\n\n[http://c610367182.iteye.com/blog/1930676](http://c610367182.iteye.com/blog/1930676)\n\n以Java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样: \n\n1. new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。 \n\n\n2. 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话，肯定他们不能equal. \n\n---\n\n\n**String、StringBuffer与StringBuilder的区别。**\n\nJava 平台提供了两种类型的字符串：String和StringBuffer / StringBuilder，它们可以储存和操作字符串。其中String是只读字符串，也就意味着String引用的字符串内容是不能被改变的。而StringBuffer和StringBulder类表示的字符串对象可以直接进行修改。StringBuilder是JDK1.5引入的，它和StringBuffer的方法完全相同，区别在于它是单线程环境下使用的，因为它的所有方面都没有被synchronized修饰，因此它的效率也比StringBuffer略高。\n\n---\n\n**try catch finally，try里有return，finally还执行么？**\n\n会执行，在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的，因为如果存在finally代码块，try中的return语句不会立马返回调用者，而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值，然后如果在finally中修改了返回值，这会对程序造成很大的困扰，C#中就从语法规定不能做这样的事。\n\n---\n\n**Excption与Error区别**\n\nError表示系统级的错误和程序不必处理的异常，是恢复不是不可能但很困难的情况下的一种严重问题；比如内存溢出，不可能指望程序能处理这样的状况；Exception表示需要捕捉或者需要程序进行处理的异常，是一种设计或实现问题；也就是说，它表示如果程序运行正常，从不会发生的情况。\n\n---\n\n**Excption与Error包结构。OOM你遇到过哪些情况，SOF你遇到过哪些情况。**\n\n[http://www.cnblogs.com/yumo/p/4909617.html](http://www.cnblogs.com/yumo/p/4909617.html)\n\nJava异常架构图\n\n![](http://images2015.cnblogs.com/blog/679904/201510/679904-20151025210813989-921927916.jpg)\n\n\n1. Throwable \nThrowable是 Java 语言中所有错误或异常的超类。 \nThrowable包含两个子类: Error 和 Exception 。它们通常用于指示发生了异常情况。 \nThrowable包含了其线程创建时线程执行堆栈的快照，它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。\n\n2. Exception \nException及其子类是 Throwable 的一种形式，它指出了合理的应用程序想要捕获的条件。\n\n3. RuntimeException \nRuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 \n编译器不会检查RuntimeException异常。 例如，除数为零时，抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时，倘若既\"没有通过throws声明抛出ArithmeticException异常\"，也\"没有通过try...catch...处理该异常\"，也能通过编译。这就是我们所说的\"编译器不会检查RuntimeException异常\"！ \n如果代码会产生RuntimeException异常，则需要通过修改代码进行避免。 例如，若会发生除数为零的情况，则需要通过代码避免该情况的发生！\n\n4. Error \n和Exception一样， Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题，大多数这样的错误都是异常条件。 \n和RuntimeException一样， 编译器也不会检查Error。\n\nJava将可抛出(Throwable)的结构分为三种类型： 被检查的异常(Checked Exception)，运行时异常(RuntimeException)和错误(Error)。\n\n(01) 运行时异常 \n定义 : RuntimeException及其子类都被称为运行时异常。 \n特点 : Java编译器不会检查它。 也就是说，当程序中可能出现这类异常时，倘若既\"没有通过throws声明抛出它\"，也\"没有用try-catch语句捕获它\"，还是会编译通过。例如，除数为零时产生的ArithmeticException异常，数组越界时产生的IndexOutOfBoundsException异常，fail-fail机制产生的ConcurrentModificationException异常等，都属于运行时异常。 \n虽然Java编译器不会检查运行时异常，但是我们也可以通过throws进行声明抛出，也可以通过try-catch对它进行捕获处理。 \n如果产生运行时异常，则需要通过修改代码来进行避免。 例如，若会发生除数为零的情况，则需要通过代码避免该情况的发生！\n\n(02) 被检查的异常 \n定义 :  Exception类本身，以及Exception的子类中除了\"运行时异常\"之外的其它子类都属于被检查异常。 \n特点 : Java编译器会检查它。 此类异常，要么通过throws进行声明抛出，要么通过try-catch进行捕获处理，否则不能通过编译。例如，CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象，而该对象对应的类没有实现Cloneable接口，就会抛出CloneNotSupportedException异常。 \n被检查异常通常都是可以恢复的。\n\n(03) 错误 \n定义 : Error类及其子类。 \n特点 : 和运行时异常一样，编译器也不会对错误进行检查。 \n当资源不足、约束失败、或是其它程序无法继续运行的条件发生时，就产生错误。程序本身无法修复这些错误的。例如，VirtualMachineError就属于错误。 \n按照Java惯例，我们是不应该是实现任何新的Error子类的！\n\n对于上面的3种结构，我们在抛出异常或错误时，到底该哪一种？《Effective Java》中给出的建议是： 对于可以恢复的条件使用被检查异常，对于程序错误使用运行时异常。\n\n---\n\n**OOM：**\n\n1. OutOfMemoryError异常\n\n\t除了程序计数器外，虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能，\n\n\tJava Heap 溢出\n\n\t一般的异常信息：java.lang.OutOfMemoryError:Java heap spacess\n\n\tjava堆用于存储对象实例，我们只要不断的创建对象，并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象，就会在对象数量达到最大堆容量限制后产生内存溢出异常。\n\n\t出现这种异常，一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析，重点是确认内存中的对象是否是必要的，先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。\n\n\t如果是内存泄漏，可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。\n\n\t如果不存在泄漏，那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。\n\n2. 虚拟机栈和本地方法栈溢出\n\n\t如果线程请求的栈深度大于虚拟机所允许的最大深度，将抛出StackOverflowError异常。\n\n\t如果虚拟机在扩展栈时无法申请到足够的内存空间，则抛出OutOfMemoryError异常\n\n\t这里需要注意当栈的大小越大可分配的线程数就越少。\n\n3. 运行时常量池溢出\n\n\t异常信息：java.lang.OutOfMemoryError:PermGen space\n\n\t如果要向运行时常量池中添加内容，最简单的做法就是使用String.intern()这个Native方法。该方法的作用是：如果池中已经包含一个等于此String的字符串，则返回代表池中这个字符串的String对象；否则，将此String对象包含的字符串添加到常量池中，并且返回此String对象的引用。由于常量池分配在方法区内，我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小，从而间接限制其中常量池的容量。\n\n4. 方法区溢出\n\n\t方法区用于存放Class的相关信息，如类名、访问修饰符、常量池、字段描述、方法描述等。\n\n\t异常信息：java.lang.OutOfMemoryError:PermGen space\n\n\t方法区溢出也是一种常见的内存溢出异常，一个类如果要被垃圾收集器回收，判定条件是很苛刻的。在经常动态生成大量Class的应用中，要特别注意这点。\n\n---\n\n**Java面向对象的三个特征与含义。**\n\n继承：继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类（超类、基类）；得到继承信息的类被称为子类（派生类）。继承让变化中的软件系统有了一定的延续性，同时继承也是封装程序中可变因素的重要手段。\n\n封装：通常认为封装是把数据和操作数据的方法绑定起来，对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装；我们编写一个类就是对数据和数据操作的封装。可以说，封装就是隐藏一切可隐藏的东西，只向外界提供最简单的编程接口（可以想想普通洗衣机和全自动洗衣机的差别，明显全自动洗衣机封装更好因此操作起来更简单；我们现在使用的智能手机也是封装得足够好的，因为几个按键就搞定了所有的事情）。\n\n多态：多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务，那么运行时的多态性可以解释为：当A系统访问B系统提供的服务时，B系统有多种提供服务的方式，但一切对A系统来说都是透明的（就像电动剃须刀是A系统，它的供电系统是B系统，B系统可以使用电池供电或者用交流电，甚至还有可能是太阳能，A系统只会通过B类对象调用供电的方法，但并不知道供电系统的底层实现是什么，究竟通过何种方式获得了动力）。方法重载（overload）实现的是编译时的多态性（也称为前绑定），而方法重写（override）实现的是运行时的多态性（也称为后绑定）。运行时的多态是面向对象最精髓的东西，要实现多态需要做两件事：1. 方法重写（子类继承父类并重写父类中已有的或抽象的方法）；2. 对象造型（用父类型引用引用子类型对象，这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为）。\n \n---\n\n\n**Override和Overload的含义与区别。**\n\nOverload：顾名思义，就是Over(重新)——load（加载），所以中文名称是重载。它可以表现类的多态性，可以是函数里面可以有相同的函数名但是参数名、类型不能相同；或者说可以改变参数、类型但是函数名字依然不变。\n\nOverride：就是ride(重写)的意思，在子类继承父类的时候子类中可以定义某方法与其父类有相同的名称和参数，当子类在调用这一函数时自动调用子类的方法，而父类相当于被覆盖（重写）了。\n\n方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现，重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数，我们说该方法被重写 (Overriding)。子类的对象使用这个方法时，将调用子类中的定义，对它而言，父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法，它们或有不同的参数个数或有不同的参数类型，则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。\n\n---\n\n**Interface与abstract类的区别。**\n\n抽象类和接口都不能够实例化，但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现，否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象，因为抽象类中可以定义构造器，可以有抽象方法和具体方法，而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的，而接口中的成员全都是public的。抽象类中可以定义成员变量，而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类，而抽象类未必要有抽象方法。\n\n---\n\n**Static class 与non static class的区别。**\n\n内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建，一个非静态内部类可以访问外部类的数据和方法，因为他就在外部类里面。\n\n---\n\n**java多态的实现原理。**\n\n[http://blog.csdn.net/zzzhangzhun/article/details/51095075](http://blog.csdn.net/zzzhangzhun/article/details/51095075)\n\n当JVM执行Java字节码时，类型信息会存储在方法区中，为了优化对象的调用方法的速度，方法区的类型信息会增加一个指针，该指针指向一个记录该类方法的方法表，方法表中的每一个项都是对应方法的指针。\n\n方法区：方法区和JAVA堆一样，是各个线程共享的内存区域，用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 \n运行时常量池：它是方法区的一部分，Class文件中除了有类的版本、方法、字段等描述信息外，还有一项信息是常量池，用于存放编译器生成的各种符号引用，这部分信息在类加载时进入方法区的运行时常量池中。 \n方法区的内存回收目标是针对常量池的回收及对类型的卸载。\n\n方法表的构造\n\n由于java的单继承机制，一个类只能继承一个父类，而所有的类又都继承Object类，方法表中最先存放的是Object的方法，接下来是父类的方法，最后是该类本身的方法。如果子类改写了父类的方法，那么子类和父类的那些同名的方法共享一个方法表项。\n\n由于这样的特性，使得方法表的偏移量总是固定的，例如，对于任何类来说，其方法表的equals方法的偏移量总是一个定值，所有继承父类的子类的方法表中，其父类所定义的方法的偏移量也总是一个定值。\n\n实例\n\n假设Class A是Class B的子类，并且A改写了B的方法的method()，那么B来说，method方法的指针指向B的method方法入口；对于A来说，A的方法表的method项指向自身的method而非父类的。\n\n流程：调用方法时，虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口，查询类的方法表 ，根据实例方法的符号引用解析出该方法在方法表的偏移量，子类对象声明为父类类型时，形式上调用的是父类的方法，此时虚拟机会从实际的方法表中找到方法地址，从而定位到实际类的方法。 \n注：所有引用为父类，但方法区的类型信息中存放的是子类的信息，所以调用的是子类的方法表。\n\n---\n\n**foreach与正常for循环效率对比。**\n\n[http://904510742.iteye.com/blog/2118331](http://904510742.iteye.com/blog/2118331)\n\n直接for循环效率最高，其次是迭代器和 ForEach操作。\n作为语法糖，其实 ForEach 编译成 字节码之后，使用的是迭代器实现的，反编译后，testForEach方法如下：\n\n```\npublic static void testForEach(List list) {  \n    for (Iterator iterator = list.iterator(); iterator.hasNext();) {  \n        Object t = iterator.next();  \n        Object obj = t;  \n    }  \n}  \n```\n\n可以看到，只比迭代器遍历多了生成中间变量这一步，因为性能也略微下降了一些。\n\n---\n\n**反射机制**\n\n\n\nJAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.\n\n主要作用有三：\n\n运行时取得类的方法和字段的相关信息。\n\n创建某个类的新实例(.newInstance())\n\n取得字段引用直接获取和设置对象字段，无论访问修饰符是什么。\n\n用处如下：\n\n观察或操作应用程序的运行时行为。\n\n调试或测试程序，因为可以直接访问方法、构造函数和成员字段。\n\n通过名字调用不知道的方法并使用该信息来创建对象和调用方法。\n\n---\n\n**String类内部实现，能否改变String对象内容**\n\n[String源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/String源码分析.md)\n\n[http://blog.csdn.net/zhangjg_blog/article/details/18319521](http://blog.csdn.net/zhangjg_blog/article/details/18319521)\n\n---\n\n**try catch 块，try里有return，finally也有return，如何执行**\n\n[http://qing0991.blog.51cto.com/1640542/1387200](http://qing0991.blog.51cto.com/1640542/1387200)\n\n---\n\n**泛型的优缺点**\n\n优点：\n\n使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。\n\n泛型最常见的用途是创建集合类。\n\n缺点：\n\n在性能上不如数组快。\n\n---\n\n**泛型常用特点，List`<String>`能否转为List`<Object>`**\n\n能，但是利用类都继承自Object，所以使用是每次调用里面的函数都要通过强制转换还原回原来的类，这样既不安全，运行速度也慢。\n\n---\n\n**解析XML的几种方式的原理与特点：DOM、SAX、PULL。**\n\n[http://www.cnblogs.com/HaroldTihan/p/4316397.html](http://www.cnblogs.com/HaroldTihan/p/4316397.html)\n\n---\n\n**Java与C++对比。**\n\n[http://developer.51cto.com/art/201106/270422.htm](http://developer.51cto.com/art/201106/270422.htm)\n\n---\n\n**Java1.7与1.8新特性。**\n\n[http://blog.chinaunix.net/uid-29618857-id-4416835.html](http://blog.chinaunix.net/uid-29618857-id-4416835.html)\n\n---\n\n**JNI的使用。**\n\n[http://landerlyoung.github.io/blog/2014/10/16/java-zhong-jnide-shi-yong/](http://landerlyoung.github.io/blog/2014/10/16/java-zhong-jnide-shi-yong/)\n\n---\n\n###集合\n\n**ArrayList、LinkedList、Vector的底层实现和区别**\n\n* 从同步性来看，ArrayList和LinkedList是不同步的，而Vector是的。所以线程安全的话，可以使用ArrayList或LinkedList，可以节省为同步而耗费的开销。但在多线程下，有时候就不得不使用Vector了。当然，也可以通过一些办法包装ArrayList、LinkedList，使我们也达到同步，但效率可能会有所降低。\n* 从内部实现机制来讲ArrayList和Vector都是使用Object的数组形式来存储的。当你向这两种类型中增加元素的时候，如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度，Vector缺省情况下自动增长原来一倍的数组长度，ArrayList是原来的50%，所以最后你获得的这个集合所占的空间总是比你实际需要的要大。如果你要在集合中保存大量的数据，那么使用Vector有一些优势，因为你可以通过设置集合的初始化大小来避免不必要的资源开销。\n* ArrayList和Vector中，从指定的位置（用index）检索一个对象，或在集合的末尾插入、删除一个对象的时间是一样的，可表示为O(1)。但是，如果在集合的其他位置增加或者删除元素那么花费的时间会呈线性增长O(n-i)，其中n代表集合中元素的个数，i代表元素增加或移除元素的索引位置，因为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。LinkedList底层是由双向循环链表实现的，LinkedList在插入、删除集合中任何位置的元素所花费的时间都是一样的O(1)，但它在索引一个元素的时候比较慢，为O(i)，其中i是索引的位置，如果只是查找特定位置的元素或只在集合的末端增加、移除元素，那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作，最好选择LinkedList。\n\n**HashMap和HashTable的底层实现和区别，两者和ConcurrentHashMap的区别。**\n\n[http://blog.csdn.net/xuefeng0707/article/details/40834595](http://blog.csdn.net/xuefeng0707/article/details/40834595)\n\nHashTable线程安全则是依靠方法简单粗暴的sychronized修饰，HashMap则没有相关的线程安全问题考虑。。\n\n在以前的版本貌似ConcurrentHashMap引入了一个“分段锁”的概念，具体可以理解为把一个大的Map拆分成N个小的HashTable，根据key.hashCode()来决定把key放到哪个HashTable中。在ConcurrentHashMap中，就是把Map分成了N个Segment，put和get的时候，都是现根据key.hashCode()算出放到哪个Segment中。\n\n通过把整个Map分为N个Segment（类似HashTable），可以提供相同的线程安全，但是效率提升N倍。\n\n---\n\n**HashMap的hashcode的作用？什么时候需要重写？如何解决哈希冲突？查找的时候流程是如何？**\n\n[从源码分析HashMap](http://blog.csdn.net/codeemperor/article/details/51351247)\n\n---\n\n**Arraylist和HashMap如何扩容？负载因子有什么作用？如何保证读写进程安全？**\n\n[http://m.blog.csdn.net/article/details?id=48956087](http://m.blog.csdn.net/article/details?id=48956087)\n\n[http://hovertree.com/h/bjaf/2jdr60li.htm](http://hovertree.com/h/bjaf/2jdr60li.htm)\n\nArrayList 本身不是线程安全的。\n所以正确的做法是去用 java.util.concurrent 里的 CopyOnWriteArrayList 或者某个同步的 Queue 类。\n\nHashMap实现不是同步的。如果多个线程同时访问一个哈希映射，而其中至少一个线程从结构上修改了该映射，则它必须 保持外部同步。（结构上的修改是指添加或删除一个或多个映射关系的任何操作；仅改变与实例已经包含的键关联的值不是结构上的修改。）这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象，则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作，以防止对映射进行意外的非同步访问.\n\n---\n\n**TreeMap、HashMap、LinkedHashMap的底层实现区别。**\n\n[http://blog.csdn.net/lolashe/article/details/20806319](http://blog.csdn.net/lolashe/article/details/20806319)\n\n---\n\n**Collection包结构，与Collections的区别。**\n\nCollection是一个接口，它是Set、List等容器的父接口；Collections是一个工具类，提供了一系列的静态方法来辅助容器操作，这些方法包括对容器的搜索、排序、线程安全化等等。\n\n---\n\n**Set、List之间的区别是什么?**\n\n[http://developer.51cto.com/art/201309/410205_all.htm](http://developer.51cto.com/art/201309/410205_all.htm)\n\n---\n\n**Map、Set、List、Queue、Stack的特点与用法。**\n\n[http://www.cnblogs.com/yumo/p/4908718.html](http://www.cnblogs.com/yumo/p/4908718.html)\n\nCollection 是对象集合， Collection 有两个子接口 List 和 Set\n\nList 可以通过下标 (1,2..) 来取得值，值可以重复\n\n而 Set 只能通过游标来取值，并且值是不能重复的\n\nArrayList ， Vector ， LinkedList 是 List 的实现类\n\nArrayList 是线程不安全的， Vector 是线程安全的，这两个类底层都是由数组实现的\n\nLinkedList 是线程不安全的，底层是由链表实现的   \n\n\nMap 是键值对集合\n\nHashTable 和 HashMap 是 Map 的实现类   \nHashTable 是线程安全的，不能存储 null 值   \nHashMap 不是线程安全的，可以存储 null 值  \n\nStack类：继承自Vector，实现一个后进先出的栈。提供了几个基本方法，push、pop、peak、empty、search等。\n\nQueue接口：提供了几个基本方法，offer、poll、peek等。已知实现类有LinkedList、PriorityQueue等。\n\n\n\n"
  },
  {
    "path": "Part2/JavaSE/Java集合框架.md",
    "content": "Java集合工具包位于Java.util包下，包含了很多常用的数据结构，如数组、链表、栈、队列、集合、哈希表等。学习Java集合框架下大致可以分为如下五个部分：List列表、Set集合、Map映射、迭代器（Iterator、Enumeration）、工具类（Arrays、Collections）。\n\nJava集合类的整体框架如下：\n\n![](http://img.blog.csdn.net/20140628144205625)\n\n从上图中可以看出，集合类主要分为两大类：Collection和Map。\n\nCollection是List、Set等集合高度抽象出来的接口，它包含了这些集合的基本操作，它主要又分为两大部分：List和Set。\n\n List接口通常表示一个列表（数组、队列、链表、栈等），其中的元素可以重复，常用实现类为ArrayList和LinkedList，另外还有不常用的Vector。另外，LinkedList还是实现了Queue接口，因此也可以作为队列使用。\n\n Set接口通常表示一个集合，其中的元素不允许重复（通过hashcode和equals函数保证），常用实现类有HashSet和TreeSet，HashSet是通过Map中的HashMap实现的，而TreeSet是通过Map中的TreeMap实现的。另外，TreeSet还实现了SortedSet接口，因此是有序的集合（集合中的元素要实现Comparable接口，并覆写Compartor函数才行）。\n我们看到，抽象类AbstractCollection、AbstractList和AbstractSet分别实现了Collection、List和Set接口，这就是在Java集合框架中用的很多的适配器设计模式，用这些抽象类去实现接口，在抽象类中实现接口中的若干或全部方法，这样下面的一些类只需直接继承该抽象类，并实现自己需要的方法即可，而不用实现接口中的全部抽象方法。\n\nMap是一个映射接口，其中的每个元素都是一个key-value键值对，同样抽象类AbstractMap通过适配器模式实现了Map接口中的大部分函数，TreeMap、HashMap、WeakHashMap等实现类都通过继承AbstractMap来实现，另外，不常用的HashTable直接实现了Map接口，它和Vector都是JDK1.0就引入的集合类。\n\nIterator是遍历集合的迭代器（不能遍历Map，只用来遍历Collection），Collection的实现类都实现了iterator()函数，它返回一个Iterator对象，用来遍历集合，ListIterator则专门用来遍历List。而Enumeration则是JDK1.0时引入的，作用与Iterator相同，但它的功能比Iterator要少，它只能再Hashtable、Vector和Stack中使用。\n\n Arrays和Collections是用来操作数组、集合的两个工具类，例如在ArrayList和Vector中大量调用了Arrays.Copyof()方法，而Collections中有很多静态方法可以返回各集合类的synchronized版本，即线程安全的版本，当然了，如果要用线程安全的结合类，首选Concurrent并发包下的对应的集合类。"
  },
  {
    "path": "Part2/JavaSE/LinkedHashMap源码剖析.md",
    "content": "##LinkedHashMap简介\n\nLinkedHashMap是HashMap的子类，与HashMap有着同样的存储结构，但它加入了一个双向链表的头结点，将所有put到LinkedHashmap的节点一一串成了一个双向循环链表，因此它保留了节点插入的顺序，可以使节点的输出顺序与输入顺序相同。\n\nLinkedHashMap可以用来实现LRU算法（这会在下面的源码中进行分析）。\n\nLinkedHashMap同样是非线程安全的，只在单线程环境下使用。\n\n##LinkedHashMap源码剖析\n\nLinkedHashMap源码如下（加入了详细的注释）：\n\n```\npackage java.util;  \nimport java.io.*;  \n  \n  \npublic class LinkedHashMap<K,V>  \n    extends HashMap<K,V>  \n    implements Map<K,V>  \n{  \n  \n    private static final long serialVersionUID = 3801124242820219131L;  \n  \n    //双向循环链表的头结点，整个LinkedHashMap中只有一个header，  \n    //它将哈希表中所有的Entry贯穿起来，header中不保存key-value对，只保存前后节点的引用  \n    private transient Entry<K,V> header;  \n  \n    //双向链表中元素排序规则的标志位。  \n    //accessOrder为false，表示按插入顺序排序  \n    //accessOrder为true，表示按访问顺序排序  \n    private final boolean accessOrder;  \n  \n    //调用HashMap的构造方法来构造底层的数组  \n    public LinkedHashMap(int initialCapacity, float loadFactor) {  \n        super(initialCapacity, loadFactor);  \n        accessOrder = false;    //链表中的元素默认按照插入顺序排序  \n    }  \n  \n    //加载因子取默认的0.75f  \n    public LinkedHashMap(int initialCapacity) {  \n        super(initialCapacity);  \n        accessOrder = false;  \n    }  \n  \n    //加载因子取默认的0.75f，容量取默认的16  \n    public LinkedHashMap() {  \n        super();  \n        accessOrder = false;  \n    }  \n  \n    //含有子Map的构造方法，同样调用HashMap的对应的构造方法  \n    public LinkedHashMap(Map<? extends K, ? extends V> m) {  \n        super(m);  \n        accessOrder = false;  \n    }  \n  \n    //该构造方法可以指定链表中的元素排序的规则  \n    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {  \n        super(initialCapacity, loadFactor);  \n        this.accessOrder = accessOrder;  \n    }  \n  \n    //覆写父类的init()方法（HashMap中的init方法为空），  \n    //该方法在父类的构造方法和Clone、readObject中在插入元素前被调用，  \n    //初始化一个空的双向循环链表，头结点中不保存数据，头结点的下一个节点才开始保存数据。  \n    void init() {  \n        header = new Entry<K,V>(-1, null, null, null);  \n        header.before = header.after = header;  \n    }  \n  \n  \n    //覆写HashMap中的transfer方法，它在父类的resize方法中被调用，  \n    //扩容后，将key-value对重新映射到新的newTable中  \n    //覆写该方法的目的是为了提高复制的效率，  \n    //这里充分利用双向循环链表的特点进行迭代，不用对底层的数组进行for循环。  \n    void transfer(HashMap.Entry[] newTable) {  \n        int newCapacity = newTable.length;  \n        for (Entry<K,V> e = header.after; e != header; e = e.after) {  \n            int index = indexFor(e.hash, newCapacity);  \n            e.next = newTable[index];  \n            newTable[index] = e;  \n        }  \n    }  \n  \n  \n    //覆写HashMap中的containsValue方法，  \n    //覆写该方法的目的同样是为了提高查询的效率，  \n    //利用双向循环链表的特点进行查询，少了对数组的外层for循环  \n    public boolean containsValue(Object value) {  \n        // Overridden to take advantage of faster iterator  \n        if (value==null) {  \n            for (Entry e = header.after; e != header; e = e.after)  \n                if (e.value==null)  \n                    return true;  \n        } else {  \n            for (Entry e = header.after; e != header; e = e.after)  \n                if (value.equals(e.value))  \n                    return true;  \n        }  \n        return false;  \n    }  \n  \n  \n    //覆写HashMap中的get方法，通过getEntry方法获取Entry对象。  \n    //注意这里的recordAccess方法，  \n    //如果链表中元素的排序规则是按照插入的先后顺序排序的话，该方法什么也不做，  \n    //如果链表中元素的排序规则是按照访问的先后顺序排序的话，则将e移到链表的末尾处。  \n    public V get(Object key) {  \n        Entry<K,V> e = (Entry<K,V>)getEntry(key);  \n        if (e == null)  \n            return null;  \n        e.recordAccess(this);  \n        return e.value;  \n    }  \n  \n    //清空HashMap，并将双向链表还原为只有头结点的空链表  \n    public void clear() {  \n        super.clear();  \n        header.before = header.after = header;  \n    }  \n  \n    //Enty的数据结构，多了两个指向前后节点的引用  \n    private static class Entry<K,V> extends HashMap.Entry<K,V> {  \n        // These fields comprise the doubly linked list used for iteration.  \n        Entry<K,V> before, after;  \n  \n        //调用父类的构造方法  \n        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {  \n            super(hash, key, value, next);  \n        }  \n  \n        //双向循环链表中，删除当前的Entry  \n        private void remove() {  \n            before.after = after;  \n            after.before = before;  \n        }  \n  \n        //双向循环立链表中，将当前的Entry插入到existingEntry的前面  \n        private void addBefore(Entry<K,V> existingEntry) {  \n            after  = existingEntry;  \n            before = existingEntry.before;  \n            before.after = this;  \n            after.before = this;  \n        }  \n  \n  \n        //覆写HashMap中的recordAccess方法（HashMap中该方法为空），  \n        //当调用父类的put方法，在发现插入的key已经存在时，会调用该方法，  \n        //调用LinkedHashmap覆写的get方法时，也会调用到该方法，  \n        //该方法提供了LRU算法的实现，它将最近使用的Entry放到双向循环链表的尾部，  \n        //accessOrder为true时，get方法会调用recordAccess方法  \n        //put方法在覆盖key-value对时也会调用recordAccess方法  \n        //它们导致Entry最近使用，因此将其移到双向链表的末尾  \n        void recordAccess(HashMap<K,V> m) {  \n            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  \n            //如果链表中元素按照访问顺序排序，则将当前访问的Entry移到双向循环链表的尾部，  \n            //如果是按照插入的先后顺序排序，则不做任何事情。  \n            if (lm.accessOrder) {  \n                lm.modCount++;  \n                //移除当前访问的Entry  \n                remove();  \n                //将当前访问的Entry插入到链表的尾部  \n                addBefore(lm.header);  \n            }  \n        }  \n  \n        void recordRemoval(HashMap<K,V> m) {  \n            remove();  \n        }  \n    }  \n  \n    //迭代器  \n    private abstract class LinkedHashIterator<T> implements Iterator<T> {  \n    Entry<K,V> nextEntry    = header.after;  \n    Entry<K,V> lastReturned = null;  \n  \n    /** \n     * The modCount value that the iterator believes that the backing \n     * List should have.  If this expectation is violated, the iterator \n     * has detected concurrent modification. \n     */  \n    int expectedModCount = modCount;  \n  \n    public boolean hasNext() {  \n            return nextEntry != header;  \n    }  \n  \n    public void remove() {  \n        if (lastReturned == null)  \n        throw new IllegalStateException();  \n        if (modCount != expectedModCount)  \n        throw new ConcurrentModificationException();  \n  \n            LinkedHashMap.this.remove(lastReturned.key);  \n            lastReturned = null;  \n            expectedModCount = modCount;  \n    }  \n  \n    //从head的下一个节点开始迭代  \n    Entry<K,V> nextEntry() {  \n        if (modCount != expectedModCount)  \n        throw new ConcurrentModificationException();  \n            if (nextEntry == header)  \n                throw new NoSuchElementException();  \n  \n            Entry<K,V> e = lastReturned = nextEntry;  \n            nextEntry = e.after;  \n            return e;  \n    }  \n    }  \n  \n    //key迭代器  \n    private class KeyIterator extends LinkedHashIterator<K> {  \n    public K next() { return nextEntry().getKey(); }  \n    }  \n  \n    //value迭代器  \n    private class ValueIterator extends LinkedHashIterator<V> {  \n    public V next() { return nextEntry().value; }  \n    }  \n  \n    //Entry迭代器  \n    private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {  \n    public Map.Entry<K,V> next() { return nextEntry(); }  \n    }  \n  \n    // These Overrides alter the behavior of superclass view iterator() methods  \n    Iterator<K> newKeyIterator()   { return new KeyIterator();   }  \n    Iterator<V> newValueIterator() { return new ValueIterator(); }  \n    Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }  \n  \n  \n    //覆写HashMap中的addEntry方法，LinkedHashmap并没有覆写HashMap中的put方法，  \n    //而是覆写了put方法所调用的addEntry方法和recordAccess方法，  \n    //put方法在插入的key已存在的情况下，会调用recordAccess方法，  \n    //在插入的key不存在的情况下，要调用addEntry插入新的Entry  \n    void addEntry(int hash, K key, V value, int bucketIndex) {  \n        //创建新的Entry，并插入到LinkedHashMap中  \n        createEntry(hash, key, value, bucketIndex);  \n  \n        //双向链表的第一个有效节点（header后的那个节点）为近期最少使用的节点  \n        Entry<K,V> eldest = header.after;  \n        //如果有必要，则删除掉该近期最少使用的节点，  \n        //这要看对removeEldestEntry的覆写,由于默认为false，因此默认是不做任何处理的。  \n        if (removeEldestEntry(eldest)) {  \n            removeEntryForKey(eldest.key);  \n        } else {  \n            //扩容到原来的2倍  \n            if (size >= threshold)  \n                resize(2 * table.length);  \n        }  \n    }  \n  \n    void createEntry(int hash, K key, V value, int bucketIndex) {  \n        //创建新的Entry，并将其插入到数组对应槽的单链表的头结点处，这点与HashMap中相同  \n        HashMap.Entry<K,V> old = table[bucketIndex];  \n        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  \n        table[bucketIndex] = e;  \n        //每次插入Entry时，都将其移到双向链表的尾部，  \n        //这便会按照Entry插入LinkedHashMap的先后顺序来迭代元素，  \n        //同时，新put进来的Entry是最近访问的Entry，把其放在链表末尾 ，符合LRU算法的实现  \n        e.addBefore(header);  \n        size++;  \n    }  \n  \n    //该方法是用来被覆写的，一般如果用LinkedHashmap实现LRU算法，就要覆写该方法，  \n    //比如可以将该方法覆写为如果设定的内存已满，则返回true，这样当再次向LinkedHashMap中put  \n    //Entry时，在调用的addEntry方法中便会将近期最少使用的节点删除掉（header后的那个节点）。  \n    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {  \n        return false;  \n    }  \n}  \n```\n##几点总结\n\n关于LinkedHashMap的源码，给出以下几点比较重要的总结：\n\n1、从源码中可以看出，LinkedHashMap中加入了一个head头结点，将所有插入到该LinkedHashMap中的Entry按照插入的先后顺序依次加入到以head为头结点的双向循环链表的尾部。\n\n![](http://img.blog.csdn.net/20140716084631981?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbnNfY29kZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n1、实际上就是HashMap和LinkedList两个集合类的存储结构的结合。在LinkedHashMapMap中，所有put进来的Entry都保存在如第一个图所示的哈希表中，但它又额外定义了一个以head为头结点的空的双向循环链表，每次put进来Entry，除了将其保存到对哈希表中对应的位置上外，还要将其插入到双向循环链表的尾部。\n\n2、LinkedHashMap由于继承自HashMap，因此它具有HashMap的所有特性，同样允许key和value为null。\n\n3、注意源码中的accessOrder标志位，当它false时，表示双向链表中的元素按照Entry插入LinkedHashMap到中的先后顺序排序，即每次put到LinkedHashMap中的Entry都放在双向链表的尾部，这样遍历双向链表时，Entry的输出顺序便和插入的顺序一致，这也是默认的双向链表的存储顺序；当它为true时，表示双向链表中的元素按照访问的先后顺序排列，可以看到，虽然Entry插入链表的顺序依然是按照其put到LinkedHashMap中的顺序，但put和get方法均有调用recordAccess方法（put方法在key相同，覆盖原有的Entry的情况下调用recordAccess方法），该方法判断accessOrder是否为true，如果是，则将当前访问的Entry（put进来的Entry或get出来的Entry）移到双向链表的尾部（key不相同时，put新Entry时，会调用addEntry，它会调用creatEntry，该方法同样将新插入的元素放入到双向链表的尾部，既符合插入的先后顺序，又符合访问的先后顺序，因为这时该Entry也被访问了），否则，什么也不做。\n\n4、注意构造方法，前四个构造方法都将accessOrder设为false，说明默认是按照插入顺序排序的，而第五个构造方法可以自定义传入的accessOrder的值，因此可以指定双向循环链表中元素的排序规则，一般要用LinkedHashMap实现LRU算法，就要用该构造方法，将accessOrder置为true。\n\n5、LinkedHashMap并没有覆写HashMap中的put方法，而是覆写了put方法中调用的addEntry方法和recordAccess方法，我们回过头来再看下HashMap的put方法：\n\n```\n// 将“key-value”添加到HashMap中      \npublic V put(K key, V value) {      \n    // 若“key为null”，则将该键值对添加到table[0]中。      \n    if (key == null)      \n        return putForNullKey(value);      \n    // 若“key不为null”，则计算该key的哈希值，然后将其添加到该哈希值对应的链表中。      \n    int hash = hash(key.hashCode());      \n    int i = indexFor(hash, table.length);      \n    for (Entry<K,V> e = table[i]; e != null; e = e.next) {      \n        Object k;      \n        // 若“该key”对应的键值对已经存在，则用新的value取代旧的value。然后退出！      \n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {      \n            V oldValue = e.value;      \n            e.value = value;      \n            e.recordAccess(this);      \n            return oldValue;      \n        }      \n    }      \n  \n    // 若“该key”对应的键值对不存在，则将“key-value”添加到table中      \n    modCount++;    \n    //将key-value添加到table[i]处    \n    addEntry(hash, key, value, i);      \n    return null;      \n}      \n```\n\n当要put进来的Entry的key在哈希表中已经在存在时，会调用recordAccess方法，当该key不存在时，则会调用addEntry方法将新的Entry插入到对应槽的单链表的头部。\n\n我们先来看recordAccess方法：\n\n```\n//覆写HashMap中的recordAccess方法（HashMap中该方法为空），  \n//当调用父类的put方法，在发现插入的key已经存在时，会调用该方法，  \n//调用LinkedHashmap覆写的get方法时，也会调用到该方法，  \n//该方法提供了LRU算法的实现，它将最近使用的Entry放到双向循环链表的尾部，  \n//accessOrder为true时，get方法会调用recordAccess方法  \n//put方法在覆盖key-value对时也会调用recordAccess方法  \n//它们导致Entry最近使用，因此将其移到双向链表的末尾  \n      void recordAccess(HashMap<K,V> m) {  \n          LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  \n    //如果链表中元素按照访问顺序排序，则将当前访问的Entry移到双向循环链表的尾部，  \n    //如果是按照插入的先后顺序排序，则不做任何事情。  \n          if (lm.accessOrder) {  \n              lm.modCount++;  \n        //移除当前访问的Entry  \n              remove();  \n        //将当前访问的Entry插入到链表的尾部  \n              addBefore(lm.header);  \n          }  \n      }  \n```\n\n该方法会判断accessOrder是否为true，如果为true，它会将当前访问的Entry（在这里指put进来的Entry）移动到双向循环链表的尾部，从而实现双向链表中的元素按照访问顺序来排序（最近访问的Entry放到链表的最后，这样多次下来，前面就是最近没有被访问的元素，在实现、LRU算法时，当双向链表中的节点数达到最大值时，将前面的元素删去即可，因为前面的元素是最近最少使用的），否则什么也不做。\n\n再来看addEntry方法：\n\n```\n//覆写HashMap中的addEntry方法，LinkedHashmap并没有覆写HashMap中的put方法，  \n//而是覆写了put方法所调用的addEntry方法和recordAccess方法，  \n//put方法在插入的key已存在的情况下，会调用recordAccess方法，  \n//在插入的key不存在的情况下，要调用addEntry插入新的Entry  \n   void addEntry(int hash, K key, V value, int bucketIndex) {  \n    //创建新的Entry，并插入到LinkedHashMap中  \n       createEntry(hash, key, value, bucketIndex);  \n  \n       //双向链表的第一个有效节点（header后的那个节点）为近期最少使用的节点  \n       Entry<K,V> eldest = header.after;  \n    //如果有必要，则删除掉该近期最少使用的节点，  \n    //这要看对removeEldestEntry的覆写,由于默认为false，因此默认是不做任何处理的。  \n       if (removeEldestEntry(eldest)) {  \n           removeEntryForKey(eldest.key);  \n       } else {  \n        //扩容到原来的2倍  \n           if (size >= threshold)  \n               resize(2 * table.length);  \n       }  \n   }  \n  \n   void createEntry(int hash, K key, V value, int bucketIndex) {  \n    //创建新的Entry，并将其插入到数组对应槽的单链表的头结点处，这点与HashMap中相同  \n       HashMap.Entry<K,V> old = table[bucketIndex];  \n    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  \n       table[bucketIndex] = e;  \n    //每次插入Entry时，都将其移到双向链表的尾部，  \n    //这便会按照Entry插入LinkedHashMap的先后顺序来迭代元素，  \n    //同时，新put进来的Entry是最近访问的Entry，把其放在链表末尾 ，符合LRU算法的实现  \n       e.addBefore(header);  \n       size++;  \n   }  \n```\n\n同样是将新的Entry插入到table中对应槽所对应单链表的头结点中，但可以看出，在createEntry中，同样把新put进来的Entry插入到了双向链表的尾部，从插入顺序的层面来说，新的Entry插入到双向链表的尾部，可以实现按照插入的先后顺序来迭代Entry，而从访问顺序的层面来说，新put进来的Entry又是最近访问的Entry，也应该将其放在双向链表的尾部。\n\n上面还有个removeEldestEntry方法，该方法如下：\n\n```\n    //该方法是用来被覆写的，一般如果用LinkedHashmap实现LRU算法，就要覆写该方法，  \n    //比如可以将该方法覆写为如果设定的内存已满，则返回true，这样当再次向LinkedHashMap中put  \n    //Entry时，在调用的addEntry方法中便会将近期最少使用的节点删除掉（header后的那个节点）。  \n    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {  \n        return false;  \n    }  \n}  \n```\n\n该方法默认返回false，我们一般在用LinkedHashMap实现LRU算法时，要覆写该方法，一般的实现是，当设定的内存（这里指节点个数）达到最大值时，返回true，这样put新的Entry（该Entry的key在哈希表中没有已经存在）时，就会调用removeEntryForKey方法，将最近最少使用的节点删除（head后面的那个节点，实际上是最近没有使用）。\n\n6、LinkedHashMap覆写了HashMap的get方法：\n\n```\n//覆写HashMap中的get方法，通过getEntry方法获取Entry对象。  \n//注意这里的recordAccess方法，  \n//如果链表中元素的排序规则是按照插入的先后顺序排序的话，该方法什么也不做，  \n//如果链表中元素的排序规则是按照访问的先后顺序排序的话，则将e移到链表的末尾处。  \n   public V get(Object key) {  \n       Entry<K,V> e = (Entry<K,V>)getEntry(key);  \n       if (e == null)  \n           return null;  \n       e.recordAccess(this);  \n       return e.value;  \n   }  \n```\n\n先取得Entry，如果不为null，一样调用recordAccess方法，上面已经说得很清楚，这里不在多解释了。\n\n7、最后说说LinkedHashMap是如何实现LRU的。首先，当accessOrder为true时，才会开启按访问顺序排序的模式，才能用来实现LRU算法。我们可以看到，无论是put方法还是get方法，都会导致目标Entry成为最近访问的Entry，因此便把该Entry加入到了双向链表的末尾（get方法通过调用recordAccess方法来实现，put方法在覆盖已有key的情况下，也是通过调用recordAccess方法来实现，在插入新的Entry时，则是通过createEntry中的addBefore方法来实现），这样便把最近使用了的Entry放入到了双向链表的后面，多次操作后，双向链表前面的Entry便是最近没有使用的，这样当节点个数满的时候，删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。"
  },
  {
    "path": "Part2/JavaSE/LinkedList源码剖析.md",
    "content": "##LinkedList简介\nLinkedList是基于双向循环链表（从源码中可以很容易看出）实现的，除了可以当作链表来操作外，它还可以当作栈，队列和双端队列来使用。\n\nLinkedList同样是非线程安全的，只在单线程下适合使用。\n\nLinkedList实现了Serializable接口，因此它支持序列化，能够通过序列化传输，实现了Cloneable接口，能被克隆。\n\n##LinkedList源码剖析\nLinkedList的源码如下（加入了比较详细的注释）\n\n```\npackage java.util;    \n   \npublic class LinkedList<E>    \n    extends AbstractSequentialList<E>    \n    implements List<E>, Deque<E>, Cloneable, java.io.Serializable    \n{    \n    // 链表的表头，表头不包含任何数据。Entry是个链表类数据结构。    \n    private transient Entry<E> header = new Entry<E>(null, null, null);    \n   \n    // LinkedList中元素个数    \n    private transient int size = 0;    \n   \n    // 默认构造函数：创建一个空的链表    \n    public LinkedList() {    \n        header.next = header.previous = header;    \n    }    \n   \n    // 包含“集合”的构造函数:创建一个包含“集合”的LinkedList    \n    public LinkedList(Collection<? extends E> c) {    \n        this();    \n        addAll(c);    \n    }    \n   \n    // 获取LinkedList的第一个元素    \n    public E getFirst() {    \n        if (size==0)    \n            throw new NoSuchElementException();    \n   \n        // 链表的表头header中不包含数据。    \n        // 这里返回header所指下一个节点所包含的数据。    \n        return header.next.element;    \n    }    \n   \n    // 获取LinkedList的最后一个元素    \n    public E getLast()  {    \n        if (size==0)    \n            throw new NoSuchElementException();    \n   \n        // 由于LinkedList是双向链表；而表头header不包含数据。    \n        // 因而，这里返回表头header的前一个节点所包含的数据。    \n        return header.previous.element;    \n    }    \n   \n    // 删除LinkedList的第一个元素    \n    public E removeFirst() {    \n        return remove(header.next);    \n    }    \n   \n    // 删除LinkedList的最后一个元素    \n    public E removeLast() {    \n        return remove(header.previous);    \n    }    \n   \n    // 将元素添加到LinkedList的起始位置    \n    public void addFirst(E e) {    \n        addBefore(e, header.next);    \n    }    \n   \n    // 将元素添加到LinkedList的结束位置    \n    public void addLast(E e) {    \n        addBefore(e, header);    \n    }    \n   \n    // 判断LinkedList是否包含元素(o)    \n    public boolean contains(Object o) {    \n        return indexOf(o) != -1;    \n    }    \n   \n    // 返回LinkedList的大小    \n    public int size() {    \n        return size;    \n    }    \n   \n    // 将元素(E)添加到LinkedList中    \n    public boolean add(E e) {    \n        // 将节点(节点数据是e)添加到表头(header)之前。    \n        // 即，将节点添加到双向链表的末端。    \n        addBefore(e, header);    \n        return true;    \n    }    \n   \n    // 从LinkedList中删除元素(o)    \n    // 从链表开始查找，如存在元素(o)则删除该元素并返回true；    \n    // 否则，返回false。    \n    public boolean remove(Object o) {    \n        if (o==null) {    \n            // 若o为null的删除情况    \n            for (Entry<E> e = header.next; e != header; e = e.next) {    \n                if (e.element==null) {    \n                    remove(e);    \n                    return true;    \n                }    \n            }    \n        } else {    \n            // 若o不为null的删除情况    \n            for (Entry<E> e = header.next; e != header; e = e.next) {    \n                if (o.equals(e.element)) {    \n                    remove(e);    \n                    return true;    \n                }    \n            }    \n        }    \n        return false;    \n    }    \n   \n    // 将“集合(c)”添加到LinkedList中。    \n    // 实际上，是从双向链表的末尾开始，将“集合(c)”添加到双向链表中。    \n    public boolean addAll(Collection<? extends E> c) {    \n        return addAll(size, c);    \n    }    \n   \n    // 从双向链表的index开始，将“集合(c)”添加到双向链表中。    \n    public boolean addAll(int index, Collection<? extends E> c) {    \n        if (index < 0 || index > size)    \n            throw new IndexOutOfBoundsException(\"Index: \"+index+    \n                                                \", Size: \"+size);    \n        Object[] a = c.toArray();    \n        // 获取集合的长度    \n        int numNew = a.length;    \n        if (numNew==0)    \n            return false;    \n        modCount++;    \n   \n        // 设置“当前要插入节点的后一个节点”    \n        Entry<E> successor = (index==size ? header : entry(index));    \n        // 设置“当前要插入节点的前一个节点”    \n        Entry<E> predecessor = successor.previous;    \n        // 将集合(c)全部插入双向链表中    \n        for (int i=0; i<numNew; i++) {    \n            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);    \n            predecessor.next = e;    \n            predecessor = e;    \n        }    \n        successor.previous = predecessor;    \n   \n        // 调整LinkedList的实际大小    \n        size += numNew;    \n        return true;    \n    }    \n   \n    // 清空双向链表    \n    public void clear() {    \n        Entry<E> e = header.next;    \n        // 从表头开始，逐个向后遍历；对遍历到的节点执行一下操作：    \n        // (01) 设置前一个节点为null     \n        // (02) 设置当前节点的内容为null     \n        // (03) 设置后一个节点为“新的当前节点”    \n        while (e != header) {    \n            Entry<E> next = e.next;    \n            e.next = e.previous = null;    \n            e.element = null;    \n            e = next;    \n        }    \n        header.next = header.previous = header;    \n        // 设置大小为0    \n        size = 0;    \n        modCount++;    \n    }    \n   \n    // 返回LinkedList指定位置的元素    \n    public E get(int index) {    \n        return entry(index).element;    \n    }    \n   \n    // 设置index位置对应的节点的值为element    \n    public E set(int index, E element) {    \n        Entry<E> e = entry(index);    \n        E oldVal = e.element;    \n        e.element = element;    \n        return oldVal;    \n    }    \n     \n    // 在index前添加节点，且节点的值为element    \n    public void add(int index, E element) {    \n        addBefore(element, (index==size ? header : entry(index)));    \n    }    \n   \n    // 删除index位置的节点    \n    public E remove(int index) {    \n        return remove(entry(index));    \n    }    \n   \n    // 获取双向链表中指定位置的节点    \n    private Entry<E> entry(int index) {    \n        if (index < 0 || index >= size)    \n            throw new IndexOutOfBoundsException(\"Index: \"+index+    \n                                                \", Size: \"+size);    \n        Entry<E> e = header;    \n        // 获取index处的节点。    \n        // 若index < 双向链表长度的1/2,则从前先后查找;    \n        // 否则，从后向前查找。    \n        if (index < (size >> 1)) {    \n            for (int i = 0; i <= index; i++)    \n                e = e.next;    \n        } else {    \n            for (int i = size; i > index; i--)    \n                e = e.previous;    \n        }    \n        return e;    \n    }    \n   \n    // 从前向后查找，返回“值为对象(o)的节点对应的索引”    \n    // 不存在就返回-1    \n    public int indexOf(Object o) {    \n        int index = 0;    \n        if (o==null) {    \n            for (Entry e = header.next; e != header; e = e.next) {    \n                if (e.element==null)    \n                    return index;    \n                index++;    \n            }    \n        } else {    \n            for (Entry e = header.next; e != header; e = e.next) {    \n                if (o.equals(e.element))    \n                    return index;    \n                index++;    \n            }    \n        }    \n        return -1;    \n    }    \n   \n    // 从后向前查找，返回“值为对象(o)的节点对应的索引”    \n    // 不存在就返回-1    \n    public int lastIndexOf(Object o) {    \n        int index = size;    \n        if (o==null) {    \n            for (Entry e = header.previous; e != header; e = e.previous) {    \n                index--;    \n                if (e.element==null)    \n                    return index;    \n            }    \n        } else {    \n            for (Entry e = header.previous; e != header; e = e.previous) {    \n                index--;    \n                if (o.equals(e.element))    \n                    return index;    \n            }    \n        }    \n        return -1;    \n    }    \n   \n    // 返回第一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E peek() {    \n        if (size==0)    \n            return null;    \n        return getFirst();    \n    }    \n   \n    // 返回第一个节点    \n    // 若LinkedList的大小为0,则抛出异常    \n    public E element() {    \n        return getFirst();    \n    }    \n   \n    // 删除并返回第一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E poll() {    \n        if (size==0)    \n            return null;    \n        return removeFirst();    \n    }    \n   \n    // 将e添加双向链表末尾    \n    public boolean offer(E e) {    \n        return add(e);    \n    }    \n   \n    // 将e添加双向链表开头    \n    public boolean offerFirst(E e) {    \n        addFirst(e);    \n        return true;    \n    }    \n   \n    // 将e添加双向链表末尾    \n    public boolean offerLast(E e) {    \n        addLast(e);    \n        return true;    \n    }    \n   \n    // 返回第一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E peekFirst() {    \n        if (size==0)    \n            return null;    \n        return getFirst();    \n    }    \n   \n    // 返回最后一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E peekLast() {    \n        if (size==0)    \n            return null;    \n        return getLast();    \n    }    \n   \n    // 删除并返回第一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E pollFirst() {    \n        if (size==0)    \n            return null;    \n        return removeFirst();    \n    }    \n   \n    // 删除并返回最后一个节点    \n    // 若LinkedList的大小为0,则返回null    \n    public E pollLast() {    \n        if (size==0)    \n            return null;    \n        return removeLast();    \n    }    \n   \n    // 将e插入到双向链表开头    \n    public void push(E e) {    \n        addFirst(e);    \n    }    \n   \n    // 删除并返回第一个节点    \n    public E pop() {    \n        return removeFirst();    \n    }    \n   \n    // 从LinkedList开始向后查找，删除第一个值为元素(o)的节点    \n    // 从链表开始查找，如存在节点的值为元素(o)的节点，则删除该节点    \n    public boolean removeFirstOccurrence(Object o) {    \n        return remove(o);    \n    }    \n   \n    // 从LinkedList末尾向前查找，删除第一个值为元素(o)的节点    \n    // 从链表开始查找，如存在节点的值为元素(o)的节点，则删除该节点    \n    public boolean removeLastOccurrence(Object o) {    \n        if (o==null) {    \n            for (Entry<E> e = header.previous; e != header; e = e.previous) {    \n                if (e.element==null) {    \n                    remove(e);    \n                    return true;    \n                }    \n            }    \n        } else {    \n            for (Entry<E> e = header.previous; e != header; e = e.previous) {    \n                if (o.equals(e.element)) {    \n                    remove(e);    \n                    return true;    \n                }    \n            }    \n        }    \n        return false;    \n    }    \n   \n    // 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)    \n    public ListIterator<E> listIterator(int index) {    \n        return new ListItr(index);    \n    }    \n   \n    // List迭代器    \n    private class ListItr implements ListIterator<E> {    \n        // 上一次返回的节点    \n        private Entry<E> lastReturned = header;    \n        // 下一个节点    \n        private Entry<E> next;    \n        // 下一个节点对应的索引值    \n        private int nextIndex;    \n        // 期望的改变计数。用来实现fail-fast机制。    \n        private int expectedModCount = modCount;    \n   \n        // 构造函数。    \n        // 从index位置开始进行迭代    \n        ListItr(int index) {    \n            // index的有效性处理    \n            if (index < 0 || index > size)    \n                throw new IndexOutOfBoundsException(\"Index: \"+index+ \", Size: \"+size);    \n            // 若 “index 小于 ‘双向链表长度的一半’”，则从第一个元素开始往后查找；    \n            // 否则，从最后一个元素往前查找。    \n            if (index < (size >> 1)) {    \n                next = header.next;    \n                for (nextIndex=0; nextIndex<index; nextIndex++)    \n                    next = next.next;    \n            } else {    \n                next = header;    \n                for (nextIndex=size; nextIndex>index; nextIndex--)    \n                    next = next.previous;    \n            }    \n        }    \n   \n        // 是否存在下一个元素    \n        public boolean hasNext() {    \n            // 通过元素索引是否等于“双向链表大小”来判断是否达到最后。    \n            return nextIndex != size;    \n        }    \n   \n        // 获取下一个元素    \n        public E next() {    \n            checkForComodification();    \n            if (nextIndex == size)    \n                throw new NoSuchElementException();    \n   \n            lastReturned = next;    \n            // next指向链表的下一个元素    \n            next = next.next;    \n            nextIndex++;    \n            return lastReturned.element;    \n        }    \n   \n        // 是否存在上一个元素    \n        public boolean hasPrevious() {    \n            // 通过元素索引是否等于0，来判断是否达到开头。    \n            return nextIndex != 0;    \n        }    \n   \n        // 获取上一个元素    \n        public E previous() {    \n            if (nextIndex == 0)    \n            throw new NoSuchElementException();    \n   \n            // next指向链表的上一个元素    \n            lastReturned = next = next.previous;    \n            nextIndex--;    \n            checkForComodification();    \n            return lastReturned.element;    \n        }    \n   \n        // 获取下一个元素的索引    \n        public int nextIndex() {    \n            return nextIndex;    \n        }    \n   \n        // 获取上一个元素的索引    \n        public int previousIndex() {    \n            return nextIndex-1;    \n        }    \n   \n        // 删除当前元素。    \n        // 删除双向链表中的当前节点    \n        public void remove() {    \n            checkForComodification();    \n            Entry<E> lastNext = lastReturned.next;    \n            try {    \n                LinkedList.this.remove(lastReturned);    \n            } catch (NoSuchElementException e) {    \n                throw new IllegalStateException();    \n            }    \n            if (next==lastReturned)    \n                next = lastNext;    \n            else   \n                nextIndex--;    \n            lastReturned = header;    \n            expectedModCount++;    \n        }    \n   \n        // 设置当前节点为e    \n        public void set(E e) {    \n            if (lastReturned == header)    \n                throw new IllegalStateException();    \n            checkForComodification();    \n            lastReturned.element = e;    \n        }    \n   \n        // 将e添加到当前节点的前面    \n        public void add(E e) {    \n            checkForComodification();    \n            lastReturned = header;    \n            addBefore(e, next);    \n            nextIndex++;    \n            expectedModCount++;    \n        }    \n   \n        // 判断 “modCount和expectedModCount是否相等”，依次来实现fail-fast机制。    \n        final void checkForComodification() {    \n            if (modCount != expectedModCount)    \n            throw new ConcurrentModificationException();    \n        }    \n    }    \n   \n    // 双向链表的节点所对应的数据结构。    \n    // 包含3部分：上一节点，下一节点，当前节点值。    \n    private static class Entry<E> {    \n        // 当前节点所包含的值    \n        E element;    \n        // 下一个节点    \n        Entry<E> next;    \n        // 上一个节点    \n        Entry<E> previous;    \n   \n        /**   \n         * 链表节点的构造函数。   \n         * 参数说明：   \n         *   element  —— 节点所包含的数据   \n         *   next      —— 下一个节点   \n         *   previous —— 上一个节点   \n         */   \n        Entry(E element, Entry<E> next, Entry<E> previous) {    \n            this.element = element;    \n            this.next = next;    \n            this.previous = previous;    \n        }    \n    }    \n   \n    // 将节点(节点数据是e)添加到entry节点之前。    \n    private Entry<E> addBefore(E e, Entry<E> entry) {    \n        // 新建节点newEntry，将newEntry插入到节点e之前；并且设置newEntry的数据是e    \n        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);    \n        newEntry.previous.next = newEntry;    \n        newEntry.next.previous = newEntry;    \n        // 修改LinkedList大小    \n        size++;    \n        // 修改LinkedList的修改统计数：用来实现fail-fast机制。    \n        modCount++;    \n        return newEntry;    \n    }    \n   \n    // 将节点从链表中删除    \n    private E remove(Entry<E> e) {    \n        if (e == header)    \n            throw new NoSuchElementException();    \n   \n        E result = e.element;    \n        e.previous.next = e.next;    \n        e.next.previous = e.previous;    \n        e.next = e.previous = null;    \n        e.element = null;    \n        size--;    \n        modCount++;    \n        return result;    \n    }    \n   \n    // 反向迭代器    \n    public Iterator<E> descendingIterator() {    \n        return new DescendingIterator();    \n    }    \n   \n    // 反向迭代器实现类。    \n    private class DescendingIterator implements Iterator {    \n        final ListItr itr = new ListItr(size());    \n        // 反向迭代器是否下一个元素。    \n        // 实际上是判断双向链表的当前节点是否达到开头    \n        public boolean hasNext() {    \n            return itr.hasPrevious();    \n        }    \n        // 反向迭代器获取下一个元素。    \n        // 实际上是获取双向链表的前一个节点    \n        public E next() {    \n            return itr.previous();    \n        }    \n        // 删除当前节点    \n        public void remove() {    \n            itr.remove();    \n        }    \n    }    \n   \n   \n    // 返回LinkedList的Object[]数组    \n    public Object[] toArray() {    \n    // 新建Object[]数组    \n    Object[] result = new Object[size];    \n        int i = 0;    \n        // 将链表中所有节点的数据都添加到Object[]数组中    \n        for (Entry<E> e = header.next; e != header; e = e.next)    \n            result[i++] = e.element;    \n    return result;    \n    }    \n   \n    // 返回LinkedList的模板数组。所谓模板数组，即可以将T设为任意的数据类型    \n    public <T> T[] toArray(T[] a) {    \n        // 若数组a的大小 < LinkedList的元素个数(意味着数组a不能容纳LinkedList中全部元素)    \n        // 则新建一个T[]数组，T[]的大小为LinkedList大小，并将该T[]赋值给a。    \n        if (a.length < size)    \n            a = (T[])java.lang.reflect.Array.newInstance(    \n                                a.getClass().getComponentType(), size);    \n        // 将链表中所有节点的数据都添加到数组a中    \n        int i = 0;    \n        Object[] result = a;    \n        for (Entry<E> e = header.next; e != header; e = e.next)    \n            result[i++] = e.element;    \n   \n        if (a.length > size)    \n            a[size] = null;    \n   \n        return a;    \n    }    \n   \n   \n    // 克隆函数。返回LinkedList的克隆对象。    \n    public Object clone() {    \n        LinkedList<E> clone = null;    \n        // 克隆一个LinkedList克隆对象    \n        try {    \n            clone = (LinkedList<E>) super.clone();    \n        } catch (CloneNotSupportedException e) {    \n            throw new InternalError();    \n        }    \n   \n        // 新建LinkedList表头节点    \n        clone.header = new Entry<E>(null, null, null);    \n        clone.header.next = clone.header.previous = clone.header;    \n        clone.size = 0;    \n        clone.modCount = 0;    \n   \n        // 将链表中所有节点的数据都添加到克隆对象中    \n        for (Entry<E> e = header.next; e != header; e = e.next)    \n            clone.add(e.element);    \n   \n        return clone;    \n    }    \n   \n    // java.io.Serializable的写入函数    \n    // 将LinkedList的“容量，所有的元素值”都写入到输出流中    \n    private void writeObject(java.io.ObjectOutputStream s)    \n        throws java.io.IOException {    \n        // Write out any hidden serialization magic    \n        s.defaultWriteObject();    \n   \n        // 写入“容量”    \n        s.writeInt(size);    \n   \n        // 将链表中所有节点的数据都写入到输出流中    \n        for (Entry e = header.next; e != header; e = e.next)    \n            s.writeObject(e.element);    \n    }    \n   \n    // java.io.Serializable的读取函数：根据写入方式反向读出    \n    // 先将LinkedList的“容量”读出，然后将“所有的元素值”读出    \n    private void readObject(java.io.ObjectInputStream s)    \n        throws java.io.IOException, ClassNotFoundException {    \n        // Read in any hidden serialization magic    \n        s.defaultReadObject();    \n   \n        // 从输入流中读取“容量”    \n        int size = s.readInt();    \n   \n        // 新建链表表头节点    \n        header = new Entry<E>(null, null, null);    \n        header.next = header.previous = header;    \n   \n        // 从输入流中将“所有的元素值”并逐个添加到链表中    \n        for (int i=0; i<size; i++)    \n            addBefore((E)s.readObject(), header);    \n    }    \n   \n}   \n```\n##几点总结\n\n关于LinkedList的源码，给出几点比较重要的总结：\n\n1、从源码中很明显可以看出，LinkedList的实现是基于双向循环链表的，且头结点中不存放数据，如下图;\n\n![](http://img.blog.csdn.net/20140629153056171)\n\n2、注意两个不同的构造方法。无参构造方法直接建立一个仅包含head节点的空链表，包含Collection的构造方法，先调用无参构造方法建立一个空链表，然后将Collection中的数据加入到链表的尾部后面。\n\n3、在查找和删除某元素时，源码中都划分为该元素为null和不为null两种情况来处理，LinkedList中允许元素为null。\n\n4、LinkedList是基于链表实现的，因此不存在容量不足的问题，所以这里没有扩容的方法。\n\n 5、注意源码中的Entry<E> entry(int index)方法。该方法返回双向链表中指定位置处的节点，而链表中是没有下标索引的，要指定位置出的元素，就要遍历该链表，从源码的实现中，我们看到这里有一个加速动作。源码中先将index与长度size的一半比较，如果index<size/2，就只从位置0往后遍历到位置index处，而如果index>size/2，就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历，从而提高一定的效率（实际上效率还是很低）。\n\n 6、注意链表类对应的数据结构Entry。如下;\n\n```\n// 双向链表的节点所对应的数据结构。    \n// 包含3部分：上一节点，下一节点，当前节点值。    \nprivate static class Entry<E> {    \n    // 当前节点所包含的值    \n    E element;    \n    // 下一个节点    \n    Entry<E> next;    \n    // 上一个节点    \n    Entry<E> previous;    \n  \n    /**   \n     * 链表节点的构造函数。   \n     * 参数说明：   \n     *   element  —— 节点所包含的数据   \n     *   next      —— 下一个节点   \n     *   previous —— 上一个节点   \n     */   \n    Entry(E element, Entry<E> next, Entry<E> previous) {    \n        this.element = element;    \n        this.next = next;    \n        this.previous = previous;    \n    }    \n}    \n```\n\n 7、LinkedList是基于链表实现的，因此插入删除效率高，查找效率低（虽然有一个加速动作）。\n\n 8、要注意源码中还实现了栈和队列的操作方法，因此也可以作为栈、队列和双端队列来使用。"
  },
  {
    "path": "Part2/JavaSE/Linkedlist.md",
    "content": "#LinkedList<E\\>\n---\n* 是一个类\n* 实现的接口：List、Collection、Iterable、Serializable、Cloneable、Deque，Queue\n* 子类：没有子类\n\n##简介\n---\n* 它是链表\n* 它还是队列、双端队列\n* 它还可以用作堆栈\n* 和ArrayList一样，不具有线程安全性\n\n##关于添加元素\n---\n**boolean add(E e)**\n添加到链表末尾\n\n**void add(int index, E e)**\n添加到指定位置\n\n**boolean addAll(int index, Collection<? extends E> c)**\n\n**boolean addAll(Collection<? extends E> c)**\n\n"
  },
  {
    "path": "Part2/JavaSE/List.md",
    "content": "# List<E\\>\n***\n* 是一个接口\n* 继承的接口：Collection\n* 间接继承的接口：Iterable\n* 实现类：ArrayList、LinkedList、Vector等\n\n##简介\n* List是有序的**Collection**\n\t- 可以对每个元素的插入位置进行精准控制\n\t- 可以根据索引访问元素\n* 允许重复元素\n* 有自己的迭代器 ListIterator\n* 如果元素包含自身，equals()和hashCode()不再是良定义的\n\n## 方法\r\n**boolean add(E e)**: 添加到末尾  \r\n**void add(int index, E e)**: 添加到指定位置  \r\n\r\n**E set(int index, E e)**: 设置指定位置的元素,返回一个E  \r\n**get(int index)**: 获得指定位置的元素  \r\n\r\n**Iterator iterator()**   \r\n**ListIterator listIterator()**  \r\n**ListIterator listIterator(int index)**  \r\n\r\n**int indexOf(E e)**  \r\n**int lastIndexOf(E e)**\r\n\r\n**List<E> subList(int fromIndex, int toIndex)**\n\n##子类介绍\n1. ArrayList是一个可改变大小的数组，当更多的元素加入到ArrayList中时，其大小将会动态的增长。内部的元素可以直接通过get与set方法进行访问，因为ArrayList本质上就是一个数组\n2. LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义。\nLinkedList还实现了Queue接口，该接口比List提供了更多的方法，包括offer(),peek(),poll等\n3. Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。\nVector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间，而ArrayList每次对size增长50%.\n\n\n\n\n\n\t"
  },
  {
    "path": "Part2/JavaSE/Queue.md",
    "content": "# Queue<E\\><A NAME=\"Collection\"> </a>\n---\n* 是一个泛型接口\n* 父接口：Collection\n* 子接口：Deque\n\n##简介\n---\n* 是个队列\n* 插入、提取、检查操作都存在两种形式，一种在操作失败后返回特殊值，一种抛出异常\n\n##方法\n---\n1. boolean add(E e)和 boolean offer(E e)添加元素\n失败时，add()抛出异常，offer()返回false\n2. E element()和E peek()获取但不移除\n失败时，element抛出异常，peek()返回null\n3. E remove()和E poll()获取并移除\n失败时，remove()抛出异常，poll()返回null\n\n##子类介绍\n---\n###Deque\n* 双端队列\n* 可以实现队列。也可以用作栈\n"
  },
  {
    "path": "Part2/JavaSE/Set.md",
    "content": "#Set\n---\n* 是一个泛型接口\n* 继承了接口Collection\n* 子接口：NavigableSet、SortedSet\n* 子类：EnumSet、HashSet、LinkedHashSet、TreeSet、AbstractSet等\n* 不允许重复元素\n\n两个注意点\n---\n\n```\n1. Set中的元素的类，必须有一个有效的equals方法。\n2. 对Set的构造方法，传入的Collection对象中重复的元素会只留下一个\n```"
  },
  {
    "path": "Part2/JavaSE/String源码分析.md",
    "content": "#String源码分析\n---\n\n从一段代码说起：\n\n```\npublic void stringTest(){\n    String a = \"a\"+\"b\"+1;\n    String b = \"ab1\";\n    System.out.println(a == b);\n}\n```\n\n大家猜一猜结果如何？如果你的结论是true。好吧，再来一段代码：\n\n```\npublic void stringTest(){\n    String a = new String(\"ab1\");\n    String b = \"ab1\";\n    System.out.println(a == b);\n}\n```\n\n结果如何呢？正确答案是false。\n\n让我们看看经过编译器编译后的代码如何\n\n```\n//第一段代码\npublic void stringTest() {\n    String a = \"ab1\";\n    String b = \"ab1\";\n    System.out.println(a == b);\n}\n```\n```\n//第二段代码\npublic void stringTest() {\n    String a1 = new String(\"ab1\");\n    String b = \"ab1\";\n    System.out.println(a1 == b);\n}\n```\n\n也就是说第一段代码经过了编译期优化，原因是编译器发现\"a\"+\"b\"+1和\"ab1\"的效果是一样的，都是不可变量组成。但是为什么他们的内存地址会相同呢？如果你对此还有兴趣，那就一起看看String类的一些重要源码吧。\n\n\n一 String类\n\nString类被final所修饰，也就是说String对象是不可变量，并发程序最喜欢不可变量了。String类实现了Serializable, Comparable<String>, CharSequence接口。\n\nComparable接口有compareTo(String s)方法，CharSequence接口有length()，charAt(int index)，subSequence(int start,int end)方法。\n\n\n二 String属性\n\nString类中包含一个不可变的char数组用来存放字符串，一个int型的变量hash用来存放计算后的哈希值。\n\n```\n/** The value is used for character storage. */\nprivate final char value[];\n\n/** Cache the hash code for the string */\nprivate int hash; // Default to 0\n\n/** use serialVersionUID from JDK 1.0.2 for interoperability */\nprivate static final long serialVersionUID = -6849794470754667710L;\n```\n\n三 String构造函数\n\n```\n//不含参数的构造函数，一般没什么用，因为value是不可变量\npublic String() {\n    this.value = new char[0];\n}\n\n//参数为String类型\npublic String(String original) {\n    this.value = original.value;\n    this.hash = original.hash;\n}\n\n//参数为char数组，使用java.utils包中的Arrays类复制\npublic String(char value[]) {\n    this.value = Arrays.copyOf(value, value.length);\n}\n\n//从bytes数组中的offset位置开始，将长度为length的字节，以charsetName格式编码，拷贝到value\npublic String(byte bytes[], int offset, int length, String charsetName)\n        throws UnsupportedEncodingException {\n    if (charsetName == null)\n        throw new NullPointerException(\"charsetName\");\n    checkBounds(bytes, offset, length);\n    this.value = StringCoding.decode(charsetName, bytes, offset, length);\n}\n\n//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数\npublic String(byte bytes[], String charsetName)\n        throws UnsupportedEncodingException {\n    this(bytes, 0, bytes.length, charsetName);\n}\n```\n\n三 String常用方法\n\n```\nboolean equals(Object anObject)\n\npublic boolean equals(Object anObject) {\n    //如果引用的是同一个对象，返回真\n    if (this == anObject) {\n        return true;\n    }\n    //如果不是String类型的数据，返回假\n    if (anObject instanceof String) {\n        String anotherString = (String) anObject;\n        int n = value.length;\n        //如果char数组长度不相等，返回假\n        if (n == anotherString.value.length) {\n            char v1[] = value;\n            char v2[] = anotherString.value;\n            int i = 0;\n            //从后往前单个字符判断，如果有不相等，返回假\n            while (n-- != 0) {\n                if (v1[i] != v2[i])\n                        return false;\n                i++;\n            }\n            //每个字符都相等，返回真\n            return true;\n        }\n    }\n    return false;\n}\n```\n\nequals方法经常用得到，它用来判断两个对象从实际意义上是否相等，String对象判断规则：\n\n内存地址相同，则为真。\n\n如果对象类型不是String类型，则为假。否则继续判断。\n\n如果对象长度不相等，则为假。否则继续判断。\n\n从后往前，判断String类中char数组value的单个字符是否相等，有不相等则为假。如果一直相等直到第一个数，则返回真。\n\n由此可以看出，如果对两个超长的字符串进行比较还是非常费时间的。\n\n```\nint compareTo(String anotherString)\n\npublic int compareTo(String anotherString) {\n    //自身对象字符串长度len1\n    int len1 = value.length;\n    //被比较对象字符串长度len2\n    int len2 = anotherString.value.length;\n    //取两个字符串长度的最小值lim\n    int lim = Math.min(len1, len2);\n    char v1[] = value;\n    char v2[] = anotherString.value;\n\n    int k = 0;\n    //从value的第一个字符开始到最小长度lim处为止，如果字符不相等，返回自身（对象不相等处字符-被比较对象不相等字符）\n    while (k < lim) {\n        char c1 = v1[k];\n        char c2 = v2[k];\n        if (c1 != c2) {\n            return c1 - c2;\n        }\n        k++;\n    }\n    //如果前面都相等，则返回（自身长度-被比较对象长度）\n    return len1 - len2;\n}\n```\n\n这个方法写的很巧妙，先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等，就直接返回自身长度减被比较对象长度，如果两个字符串长度相等，则返回的是0，巧妙地判断了三种情况。\n\n```\nint hashCode()\n\npublic int hashCode() {\n    int h = hash;\n    //如果hash没有被计算过，并且字符串不为空，则进行hashCode计算\n    if (h == 0 && value.length > 0) {\n        char val[] = value;\n\n        //计算过程\n        //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]\n        for (int i = 0; i < value.length; i++) {\n            h = 31 * h + val[i];\n        }\n        //hash赋值\n        hash = h;\n    }\n    return h;\n}\n```\n\nString类重写了hashCode方法，Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来，我们完全可以通过不相同的字符串得出同样的hash，所以两个String对象的hashCode相同，并不代表两个String是一样的。\n\n```\nboolean startsWith(String prefix,int toffset)\n\npublic boolean startsWith(String prefix, int toffset) {\n    char ta[] = value;\n    int to = toffset;\n    char pa[] = prefix.value;\n    int po = 0;\n    int pc = prefix.value.length;\n    // Note: toffset might be near -1>>>1.\n    //如果起始地址小于0或者（起始地址+所比较对象长度）大于自身对象长度，返回假\n    if ((toffset < 0) || (toffset > value.length - pc)) {\n        return false;\n    }\n    //从所比较对象的末尾开始比较\n    while (--pc >= 0) {\n        if (ta[to++] != pa[po++]) {\n            return false;\n        }\n    }\n    return true;\n}\n\npublic boolean startsWith(String prefix) {\n    return startsWith(prefix, 0);\n}\n\npublic boolean endsWith(String suffix) {\n    return startsWith(suffix, value.length - suffix.value.length);\n}\n```\n\n起始比较和末尾比较都是比较经常用得到的方法，例如在判断一个字符串是不是http协议的，或者初步判断一个文件是不是mp3文件，都可以采用这个方法进行比较。\n\n```\nString concat(String str)\n\npublic String concat(String str) {\n    int otherLen = str.length();\n    //如果被添加的字符串为空，返回对象本身\n    if (otherLen == 0) {\n        return this;\n    }\n    int len = value.length;\n    char buf[] = Arrays.copyOf(value, len + otherLen);\n    str.getChars(buf, len);\n    return new String(buf, true);\n}\n```\n\nconcat方法也是经常用的方法之一，它先判断被添加字符串是否为空来决定要不要创建新的对象。\n\n```\nString replace(char oldChar,char newChar)\n\npublic String replace(char oldChar, char newChar) {\n    //新旧值先对比\n    if (oldChar != newChar) {\n        int len = value.length;\n        int i = -1;\n        char[] val = value; /* avoid getfield opcode */\n\n        //找到旧值最开始出现的位置\n        while (++i < len) {\n            if (val[i] == oldChar) {\n                break;\n            }\n        }\n        //从那个位置开始，直到末尾，用新值代替出现的旧值\n        if (i < len) {\n            char buf[] = new char[len];\n            for (int j = 0; j < i; j++) {\n                buf[j] = val[j];\n            }\n            while (i < len) {\n                char c = val[i];\n                buf[i] = (c == oldChar) ? newChar : c;\n                i++;\n            }\n            return new String(buf, true);\n        }\n    }\n    return this;\n}\n```\n\n这个方法也有讨巧的地方，例如最开始先找出旧值出现的位置，这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。\n\n```\nString trim()\n\npublic String trim() {\n    int len = value.length;\n    int st = 0;\n    char[] val = value;    /* avoid getfield opcode */\n\n    //找到字符串前段没有空格的位置\n    while ((st < len) && (val[st] <= ' ')) {\n        st++;\n    }\n    //找到字符串末尾没有空格的位置\n    while ((st < len) && (val[len - 1] <= ' ')) {\n        len--;\n    }\n    //如果前后都没有出现空格，返回字符串本身\n    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;\n}\n```\n\ntrim方法用起来也6的飞起\n\n```\nString intern()\n\npublic native String intern();\n```\n\nintern方法是Native调用，它的作用是在方法区中的常量池里通过equals方法寻找等值的对象，如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用，否则直接返回常量池中已存在String对象的引用。\n\n将引言中第二段代码\n\n```\n//String a = new String(\"ab1\");\n//改为\nString a = new String(\"ab1\").intern();\n```\n\n则结果为为真，原因在于a所指向的地址来自于常量池，而b所指向的字符串常量默认会调用这个方法，所以a和b都指向了同一个地址空间。\n\n```\nint hash32()\n\nprivate transient int hash32 = 0;\nint hash32() {\n    int h = hash32;\n    if (0 == h) {\n       // harmless data race on hash32 here.\n       h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);\n\n       // ensure result is not zero to avoid recalcing\n       h = (0 != h) ? h : 1;\n\n       hash32 = h;\n    }\n\n    return h;\n}\n```\n\n在JDK1.7中，Hash相关集合类在String类作key的情况下，不再使用hashCode方式离散数据，而是采用hash32方法。这个方法默认使用系统当前时间，String类地址，System类地址等作为因子计算得到hash种子，通过hash种子在经过hash得到32位的int型数值。\n\n```\npublic int length() {\n    return value.length;\n}\npublic String toString() {\n    return this;\n}\npublic boolean isEmpty() {\n    return value.length == 0;\n}\npublic char charAt(int index) {\n    if ((index < 0) || (index >= value.length)) {\n        throw new StringIndexOutOfBoundsException(index);\n    }\n    return value[index];\n}\n```\n\n以上是一些简单的常用方法。\n\n\n总结\n\nString对象是不可变类型，返回类型为String的String方法每次返回的都是新的String对象，除了某些方法的某些特定条件返回自身。\n\nString对象的三种比较方式：\n\n==内存比较：直接对比两个引用所指向的内存值，精确简洁直接明了。\n\nequals字符串值比较：比较两个引用所指对象字面值是否相等。\n\nhashCode字符串数值化比较：将字符串数值化。两个引用的hashCode相同，不保证内存一定相同，不保证字面值一定相同。\n\n\n"
  },
  {
    "path": "Part2/JavaSE/Vector源码剖析.md",
    "content": "##Vector简介\nVector也是基于数组实现的，是一个动态数组，其容量能自动增长。\n\nVector是JDK1.0引入了，它的很多实现方法都加入了同步语句，因此是线程安全的（其实也只是相对安全，有些时候还是要加入同步语句来保证线程的安全），可以用于多线程环境。\n\nVector没有实现Serializable接口，因此它不支持序列化，实现了Cloneable接口，能被克隆，实现了RandomAccess接口，支持快速随机访问。\n\n##Vector源码剖析\nVector的源码如下（加入了比较详细的注释）：\n\n```\npackage java.util;    \n   \npublic class Vector<E>    \n    extends AbstractList<E>    \n    implements List<E>, RandomAccess, Cloneable, java.io.Serializable    \n{    \n       \n    // 保存Vector中数据的数组    \n    protected Object[] elementData;    \n   \n    // 实际数据的数量    \n    protected int elementCount;    \n   \n    // 容量增长系数    \n    protected int capacityIncrement;    \n   \n    // Vector的序列版本号    \n    private static final long serialVersionUID = -2767605614048989439L;    \n   \n    // Vector构造函数。默认容量是10。    \n    public Vector() {    \n        this(10);    \n    }    \n   \n    // 指定Vector容量大小的构造函数    \n    public Vector(int initialCapacity) {    \n        this(initialCapacity, 0);    \n    }    \n   \n    // 指定Vector\"容量大小\"和\"增长系数\"的构造函数    \n    public Vector(int initialCapacity, int capacityIncrement) {    \n        super();    \n        if (initialCapacity < 0)    \n            throw new IllegalArgumentException(\"Illegal Capacity: \"+    \n                                               initialCapacity);    \n        // 新建一个数组，数组容量是initialCapacity    \n        this.elementData = new Object[initialCapacity];    \n        // 设置容量增长系数    \n        this.capacityIncrement = capacityIncrement;    \n    }    \n   \n    // 指定集合的Vector构造函数。    \n    public Vector(Collection<? extends E> c) {    \n        // 获取“集合(c)”的数组，并将其赋值给elementData    \n        elementData = c.toArray();    \n        // 设置数组长度    \n        elementCount = elementData.length;    \n        // c.toArray might (incorrectly) not return Object[] (see 6260652)    \n        if (elementData.getClass() != Object[].class)    \n            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);    \n    }    \n   \n    // 将数组Vector的全部元素都拷贝到数组anArray中    \n    public synchronized void copyInto(Object[] anArray) {    \n        System.arraycopy(elementData, 0, anArray, 0, elementCount);    \n    }    \n   \n    // 将当前容量值设为 =实际元素个数    \n    public synchronized void trimToSize() {    \n        modCount++;    \n        int oldCapacity = elementData.length;    \n        if (elementCount < oldCapacity) {    \n            elementData = Arrays.copyOf(elementData, elementCount);    \n        }    \n    }    \n   \n    // 确认“Vector容量”的帮助函数    \n    private void ensureCapacityHelper(int minCapacity) {    \n        int oldCapacity = elementData.length;    \n        // 当Vector的容量不足以容纳当前的全部元素，增加容量大小。    \n        // 若 容量增量系数>0(即capacityIncrement>0)，则将容量增大当capacityIncrement    \n        // 否则，将容量增大一倍。    \n        if (minCapacity > oldCapacity) {    \n            Object[] oldData = elementData;    \n            int newCapacity = (capacityIncrement > 0) ?    \n                (oldCapacity + capacityIncrement) : (oldCapacity * 2);    \n            if (newCapacity < minCapacity) {    \n                newCapacity = minCapacity;    \n            }    \n            elementData = Arrays.copyOf(elementData, newCapacity);    \n        }    \n    }    \n   \n    // 确定Vector的容量。    \n    public synchronized void ensureCapacity(int minCapacity) {    \n        // 将Vector的改变统计数+1    \n        modCount++;    \n        ensureCapacityHelper(minCapacity);    \n    }    \n   \n    // 设置容量值为 newSize    \n    public synchronized void setSize(int newSize) {    \n        modCount++;    \n        if (newSize > elementCount) {    \n            // 若 \"newSize 大于 Vector容量\"，则调整Vector的大小。    \n            ensureCapacityHelper(newSize);    \n        } else {    \n            // 若 \"newSize 小于/等于 Vector容量\"，则将newSize位置开始的元素都设置为null    \n            for (int i = newSize ; i < elementCount ; i++) {    \n                elementData[i] = null;    \n            }    \n        }    \n        elementCount = newSize;    \n    }    \n   \n    // 返回“Vector的总的容量”    \n    public synchronized int capacity() {    \n        return elementData.length;    \n    }    \n   \n    // 返回“Vector的实际大小”，即Vector中元素个数    \n    public synchronized int size() {    \n        return elementCount;    \n    }    \n   \n    // 判断Vector是否为空    \n    public synchronized boolean isEmpty() {    \n        return elementCount == 0;    \n    }    \n   \n    // 返回“Vector中全部元素对应的Enumeration”    \n    public Enumeration<E> elements() {    \n        // 通过匿名类实现Enumeration    \n        return new Enumeration<E>() {    \n            int count = 0;    \n   \n            // 是否存在下一个元素    \n            public boolean hasMoreElements() {    \n                return count < elementCount;    \n            }    \n   \n            // 获取下一个元素    \n            public E nextElement() {    \n                synchronized (Vector.this) {    \n                    if (count < elementCount) {    \n                        return (E)elementData[count++];    \n                    }    \n                }    \n                throw new NoSuchElementException(\"Vector Enumeration\");    \n            }    \n        };    \n    }    \n   \n    // 返回Vector中是否包含对象(o)    \n    public boolean contains(Object o) {    \n        return indexOf(o, 0) >= 0;    \n    }    \n   \n   \n    // 从index位置开始向后查找元素(o)。    \n    // 若找到，则返回元素的索引值；否则，返回-1    \n    public synchronized int indexOf(Object o, int index) {    \n        if (o == null) {    \n            // 若查找元素为null，则正向找出null元素，并返回它对应的序号    \n            for (int i = index ; i < elementCount ; i++)    \n            if (elementData[i]==null)    \n                return i;    \n        } else {    \n            // 若查找元素不为null，则正向找出该元素，并返回它对应的序号    \n            for (int i = index ; i < elementCount ; i++)    \n            if (o.equals(elementData[i]))    \n                return i;    \n        }    \n        return -1;    \n    }    \n   \n    // 查找并返回元素(o)在Vector中的索引值    \n    public int indexOf(Object o) {    \n        return indexOf(o, 0);    \n    }    \n   \n    // 从后向前查找元素(o)。并返回元素的索引    \n    public synchronized int lastIndexOf(Object o) {    \n        return lastIndexOf(o, elementCount-1);    \n    }    \n   \n    // 从后向前查找元素(o)。开始位置是从前向后的第index个数；    \n    // 若找到，则返回元素的“索引值”；否则，返回-1。    \n    public synchronized int lastIndexOf(Object o, int index) {    \n        if (index >= elementCount)    \n            throw new IndexOutOfBoundsException(index + \" >= \"+ elementCount);    \n   \n        if (o == null) {    \n            // 若查找元素为null，则反向找出null元素，并返回它对应的序号    \n            for (int i = index; i >= 0; i--)    \n            if (elementData[i]==null)    \n                return i;    \n        } else {    \n            // 若查找元素不为null，则反向找出该元素，并返回它对应的序号    \n            for (int i = index; i >= 0; i--)    \n            if (o.equals(elementData[i]))    \n                return i;    \n        }    \n        return -1;    \n    }    \n   \n    // 返回Vector中index位置的元素。    \n    // 若index月结，则抛出异常    \n    public synchronized E elementAt(int index) {    \n        if (index >= elementCount) {    \n            throw new ArrayIndexOutOfBoundsException(index + \" >= \" + elementCount);    \n        }    \n   \n        return (E)elementData[index];    \n    }    \n   \n    // 获取Vector中的第一个元素。    \n    // 若失败，则抛出异常！    \n    public synchronized E firstElement() {    \n        if (elementCount == 0) {    \n            throw new NoSuchElementException();    \n        }    \n        return (E)elementData[0];    \n    }    \n   \n    // 获取Vector中的最后一个元素。    \n    // 若失败，则抛出异常！    \n    public synchronized E lastElement() {    \n        if (elementCount == 0) {    \n            throw new NoSuchElementException();    \n        }    \n        return (E)elementData[elementCount - 1];    \n    }    \n   \n    // 设置index位置的元素值为obj    \n    public synchronized void setElementAt(E obj, int index) {    \n        if (index >= elementCount) {    \n            throw new ArrayIndexOutOfBoundsException(index + \" >= \" +    \n                                 elementCount);    \n        }    \n        elementData[index] = obj;    \n    }    \n   \n    // 删除index位置的元素    \n    public synchronized void removeElementAt(int index) {    \n        modCount++;    \n        if (index >= elementCount) {    \n            throw new ArrayIndexOutOfBoundsException(index + \" >= \" +    \n                                 elementCount);    \n        } else if (index < 0) {    \n            throw new ArrayIndexOutOfBoundsException(index);    \n        }    \n   \n        int j = elementCount - index - 1;    \n        if (j > 0) {    \n            System.arraycopy(elementData, index + 1, elementData, index, j);    \n        }    \n        elementCount--;    \n        elementData[elementCount] = null; /* to let gc do its work */   \n    }    \n   \n    // 在index位置处插入元素(obj)    \n    public synchronized void insertElementAt(E obj, int index) {    \n        modCount++;    \n        if (index > elementCount) {    \n            throw new ArrayIndexOutOfBoundsException(index    \n                                 + \" > \" + elementCount);    \n        }    \n        ensureCapacityHelper(elementCount + 1);    \n        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);    \n        elementData[index] = obj;    \n        elementCount++;    \n    }    \n   \n    // 将“元素obj”添加到Vector末尾    \n    public synchronized void addElement(E obj) {    \n        modCount++;    \n        ensureCapacityHelper(elementCount + 1);    \n        elementData[elementCount++] = obj;    \n    }    \n   \n    // 在Vector中查找并删除元素obj。    \n    // 成功的话，返回true；否则，返回false。    \n    public synchronized boolean removeElement(Object obj) {    \n        modCount++;    \n        int i = indexOf(obj);    \n        if (i >= 0) {    \n            removeElementAt(i);    \n            return true;    \n        }    \n        return false;    \n    }    \n   \n    // 删除Vector中的全部元素    \n    public synchronized void removeAllElements() {    \n        modCount++;    \n        // 将Vector中的全部元素设为null    \n        for (int i = 0; i < elementCount; i++)    \n            elementData[i] = null;    \n   \n        elementCount = 0;    \n    }    \n   \n    // 克隆函数    \n    public synchronized Object clone() {    \n        try {    \n            Vector<E> v = (Vector<E>) super.clone();    \n            // 将当前Vector的全部元素拷贝到v中    \n            v.elementData = Arrays.copyOf(elementData, elementCount);    \n            v.modCount = 0;    \n            return v;    \n        } catch (CloneNotSupportedException e) {    \n            // this shouldn't happen, since we are Cloneable    \n            throw new InternalError();    \n        }    \n    }    \n   \n    // 返回Object数组    \n    public synchronized Object[] toArray() {    \n        return Arrays.copyOf(elementData, elementCount);    \n    }    \n   \n    // 返回Vector的模板数组。所谓模板数组，即可以将T设为任意的数据类型    \n    public synchronized <T> T[] toArray(T[] a) {    \n        // 若数组a的大小 < Vector的元素个数；    \n        // 则新建一个T[]数组，数组大小是“Vector的元素个数”，并将“Vector”全部拷贝到新数组中    \n        if (a.length < elementCount)    \n            return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass());    \n   \n        // 若数组a的大小 >= Vector的元素个数；    \n        // 则将Vector的全部元素都拷贝到数组a中。    \n    System.arraycopy(elementData, 0, a, 0, elementCount);    \n   \n        if (a.length > elementCount)    \n            a[elementCount] = null;    \n   \n        return a;    \n    }    \n   \n    // 获取index位置的元素    \n    public synchronized E get(int index) {    \n        if (index >= elementCount)    \n            throw new ArrayIndexOutOfBoundsException(index);    \n   \n        return (E)elementData[index];    \n    }    \n   \n    // 设置index位置的值为element。并返回index位置的原始值    \n    public synchronized E set(int index, E element) {    \n        if (index >= elementCount)    \n            throw new ArrayIndexOutOfBoundsException(index);    \n   \n        Object oldValue = elementData[index];    \n        elementData[index] = element;    \n        return (E)oldValue;    \n    }    \n   \n    // 将“元素e”添加到Vector最后。    \n    public synchronized boolean add(E e) {    \n        modCount++;    \n        ensureCapacityHelper(elementCount + 1);    \n        elementData[elementCount++] = e;    \n        return true;    \n    }    \n   \n    // 删除Vector中的元素o    \n    public boolean remove(Object o) {    \n        return removeElement(o);    \n    }    \n   \n    // 在index位置添加元素element    \n    public void add(int index, E element) {    \n        insertElementAt(element, index);    \n    }    \n   \n    // 删除index位置的元素，并返回index位置的原始值    \n    public synchronized E remove(int index) {    \n        modCount++;    \n        if (index >= elementCount)    \n            throw new ArrayIndexOutOfBoundsException(index);    \n        Object oldValue = elementData[index];    \n   \n        int numMoved = elementCount - index - 1;    \n        if (numMoved > 0)    \n            System.arraycopy(elementData, index+1, elementData, index,    \n                     numMoved);    \n        elementData[--elementCount] = null; // Let gc do its work    \n   \n        return (E)oldValue;    \n    }    \n   \n    // 清空Vector    \n    public void clear() {    \n        removeAllElements();    \n    }    \n   \n    // 返回Vector是否包含集合c    \n    public synchronized boolean containsAll(Collection<?> c) {    \n        return super.containsAll(c);    \n    }    \n   \n    // 将集合c添加到Vector中    \n    public synchronized boolean addAll(Collection<? extends E> c) {    \n        modCount++;    \n        Object[] a = c.toArray();    \n        int numNew = a.length;    \n        ensureCapacityHelper(elementCount + numNew);    \n        // 将集合c的全部元素拷贝到数组elementData中    \n        System.arraycopy(a, 0, elementData, elementCount, numNew);    \n        elementCount += numNew;    \n        return numNew != 0;    \n    }    \n   \n    // 删除集合c的全部元素    \n    public synchronized boolean removeAll(Collection<?> c) {    \n        return super.removeAll(c);    \n    }    \n   \n    // 删除“非集合c中的元素”    \n    public synchronized boolean retainAll(Collection<?> c)  {    \n        return super.retainAll(c);    \n    }    \n   \n    // 从index位置开始，将集合c添加到Vector中    \n    public synchronized boolean addAll(int index, Collection<? extends E> c) {    \n        modCount++;    \n        if (index < 0 || index > elementCount)    \n            throw new ArrayIndexOutOfBoundsException(index);    \n   \n        Object[] a = c.toArray();    \n        int numNew = a.length;    \n        ensureCapacityHelper(elementCount + numNew);    \n   \n        int numMoved = elementCount - index;    \n        if (numMoved > 0)    \n        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);    \n   \n        System.arraycopy(a, 0, elementData, index, numNew);    \n        elementCount += numNew;    \n        return numNew != 0;    \n    }    \n   \n    // 返回两个对象是否相等    \n    public synchronized boolean equals(Object o) {    \n        return super.equals(o);    \n    }    \n   \n    // 计算哈希值    \n    public synchronized int hashCode() {    \n        return super.hashCode();    \n    }    \n   \n    // 调用父类的toString()    \n    public synchronized String toString() {    \n        return super.toString();    \n    }    \n   \n    // 获取Vector中fromIndex(包括)到toIndex(不包括)的子集    \n    public synchronized List<E> subList(int fromIndex, int toIndex) {    \n        return Collections.synchronizedList(super.subList(fromIndex, toIndex), this);    \n    }    \n   \n    // 删除Vector中fromIndex到toIndex的元素    \n    protected synchronized void removeRange(int fromIndex, int toIndex) {    \n        modCount++;    \n        int numMoved = elementCount - toIndex;    \n        System.arraycopy(elementData, toIndex, elementData, fromIndex,    \n                         numMoved);    \n   \n        // Let gc do its work    \n        int newElementCount = elementCount - (toIndex-fromIndex);    \n        while (elementCount != newElementCount)    \n            elementData[--elementCount] = null;    \n    }    \n   \n    // java.io.Serializable的写入函数    \n    private synchronized void writeObject(java.io.ObjectOutputStream s)    \n        throws java.io.IOException {    \n        s.defaultWriteObject();    \n    }    \n}   \n```\n\n##几点总结\n\nVector的源码实现总体与ArrayList类似，关于Vector的源码，给出如下几点总结：\n\n1、Vector有四个不同的构造方法。无参构造方法的容量为默认值10，仅包含容量的构造方法则将容量增长量（从源码中可以看出容量增长量的作用，第二点也会对容量增长量详细说）明置为0。\n\n2、注意扩充容量的方法ensureCapacityHelper。与ArrayList相同，Vector在每次增加元素（可能是1个，也可能是一组）时，都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时，就先看构造方法中传入的容量增长量参数CapacityIncrement是否为0，如果不为0，就设置新的容量为就容量加上容量增长量，如果为0，就设置新的容量为旧的容量的2倍，如果设置后的新容量还不够，则直接新容量设置为传入的参数（也就是所需的容量），而后同样用Arrays.copyof()方法将元素拷贝到新的数组。\n\n3、很多方法都加入了synchronized同步语句，来保证线程安全。\n\n4、同样在查找给定元素索引值等的方法中，源码都将该元素的值分为null和不为null两种情况处理，Vector中也允许元素为null。\n\n5、其他很多地方都与ArrayList实现大同小异，Vector现在已经基本不再使用。 "
  },
  {
    "path": "Part2/JavaSE/hashmap和hashtable的底层实现和区别，两者和concurrenthashmap的区别。.md",
    "content": "#HashMap 和 HashTable 的底层实现和区别，两者和 ConcurrentHashMap 的区别。\n\nHashMap和HashTable源代码级别的区别\n\n1. 最明显的区别在于Hashtable是同步的（每个方法都是synchronized），而HashMap则不是\n\n\n[](http://www.cnblogs.com/langtianya/archive/2013/03/19/2970273.html)"
  },
  {
    "path": "Part2/JavaSE/从源码分析HashMap.md",
    "content": "#HashMap\n---\n\n\n###HashMap和Hashtable的区别：\n\n1. Hashtable的大部分方法做了同步，HashMap没有，因此，HashMap不是线程安全的。\n2. Hashtable不允许key或者value使用null值，而HashMap可以。\n3. 在内部算法上，它们对key的hash算法和hash值到内存索引的映射算法不同。\n\n###HashMap的实现原理\n\n简单说，HashMap就是将key做hash算法，然后将hash所对应的数据映射到内存地址，直接取得key所对应的数据。在HashMap中。底层数据结构使用的是数组，所谓的内存地址即数组的下标索引。HashMap的高性能需要保证以下几点：\n\n* hash算法必须高效\n* hash值到内存地址(数组索引)的算法是快速的\n* 根据内存地址(数组索引)可以直接取得对应的值\n\n如何保证hash算法高效,hash算法有关的代码如下：\n\n```\nint hash = hash(key.hashCode());\npublic native int hashCode();\nstatic int hash(int h){\n\th ^= (h >>> 20) ^ (h >>> 12);\n\treturn h ^ (h >>> 7) ^ (h >>> 4);\n}\n```\n\n第一行代码是HashMap用于计算key的hash值，它前后调用了Object类的hashCode()方法和HashMap的内部函数hash()。Object类的hashCode()方法默认是native的实现，可以认为不存在性能问题。而hash()函数的实现全部基于位运算，因此，也是高效的。\n\n当取得key的hash值后，需要通过hash值得到内存地址：\n\n```\nint i = indexFor(hash, table.length);\nstatic int indexFor(int h, int length){\n\treturn h & (length - 1);\n}\n```\n\nindexFor()函数通过将hash值和数组长度按位与直接得到数组索引。\n最后由indexFor()函数返回的数组索引直接通过数组下标便可取得对应的值，直接的内存访问速度也是相当的快，因此，可认为HashMap是高性能的。\n\n###Hash冲突\n\n如图3.11所示，需要存放到HashMap中的两个元素1和2，通过hash计算后，发现对应在内存中的同一个地址，如何处理？\n其实HashMap的底层实现使用的是数组，但是数组内的元素并不是简单的值。而是一个Entry类的对象。因此，对HashMap结构贴切描述如图3.12所示。\n\n![这里写图片描述](http://img.blog.csdn.net/20160509103524275)\n\n\n可以看到，HashMap的内部维护着一个Entry数组，每一个Entry表项包括key、value、next和hash几项。next部分指向另外一个Entry。进一步阅读HashMap的put()方法源码，可以看到当put()操作有冲突时，新的Entry依然会被安放在对应的索引下标内，并替换原有的值。同时为了保证旧值不丢失，会将新的Entry的next指向旧值。这便实现了在一个数组索引空间内存放多个值项。因此，如图3.12所示，HashMap实际上是一个链表的数组。\n\n```\npublic V put(K key, V value){\n\tif(key == null)\n\t\treturn putForNullKey(value);\n\tint hash = hash(key.hashCode());\n\tint i = indexFor(hash, table.length);\n\tfor(Entry<K, V> e = table[i]; e != null; e = e.next){\n\t\tObject k;\n\t\t//如果当前的key已经存在于HashMap中\n\t\tif(e.hash == hash && ((k = e.key) == key || key.equals(k)))\n\t\t{\n\t\t\tV oldValue = e.value;    //取得旧值\n\t\t\te.value = value;\n\t\t\te.recordAccess(this);\n\t\t\treturn oldValue;     //返回旧值\n\t\t}\n\t}\n\tmodCount++;\n\taddEntry(hash, key, value, i);    //添加当前的表项到i位置\n\treturn null;\n}\n```\n\naddEntry()方法的实现如下：\n\n```\nvoid addEntry(int hash, K key, V value, int bucketIndex){\n\tEntry<K,V> e = table[bucketIndex];\n\t//将新增元素放到i的位置，并让它的next指向旧的元素\n\ttable[bucketIndex] = new Entry<K,V>(hash, key, value, e);\n\tif(size++ >= threshold){\n\t\tresize(2 * table.length);\n\t}\n}\n```\n\n基于HashMap的这种实现机制，只要hashCode和hash()方法实现的足够好，能够尽可能的减少冲突的产生，那么对HashMap的操作几乎等价于对数组的随机访问操作，具有很好的性能。但是，如果hashCode()或者hash()方法实现较差，在大量冲突产生的情况下，HashMap事实上就退化为几个链表，对HashMap的操作等价于遍历链表，此时性能很差。\n\n###容量参数\n\n除hashCode()的实现外，影响HashMap性能的还有它的容量参数。和ArrayList和Vector一样，这种基于数组的结构，不可避免的需要在数组空间不足时，进行扩展。而数组的重组相对而言较为耗时，因此对其作一定了解有助于优化HashMap的性能。\n\nHashMap提供了两个可以指定初始化大小的构造函数：\n\n```\npublic HashMap(int initialCapacity)\npublic HashMap(int initialCapacity, float loadFactor)\n```\n\n其中initialCapacity指定了HashMap的初始容量，loadFactor指定了其负载因子。初始容量即数组的大小，HashMap会使用大于等于initialCapacity并且是2的指数次幂的最小的整数作为内置数组的大小。负载因子又叫填充比，它是介于0和1之间的浮点数，它决定了HashMap在扩容之前，其内部数组的填充度。默认情况下，HashMap初始大小为16，负载因子为0.75。\n\n**负载因子 ＝ 元素个数/内部数组总大小**\n\n在实际使用中，负载因子也可以设置为大于1的数，但如果这样做，HashMap将必然产生大量冲突，因为这无疑是在尝试往只有10个口袋的包里放15件物品，必然有几只口袋要大于一个物件。因此，通常不会这么使用。\n\n在HashMap内部，还维护了一个threshold变量，它始终被定义为当前数组总容量和负载因子的乘积，它表示HashMap的阈值。当HashMap的实际容量超过阈值时，HashMap便会进行扩容。因此，HashMap的实际容量超过阈值时，HashMap便会进行扩容。因此，HashMap的实际填充率不会超过负载因子。\n\nHashMap扩容的代码如下：\n\n```\nvoid resize(int newCapacity){\n\tEntry[] oldTable = table;\n\tint oldCapacity= oldTable.length;\n\tif(oldCapacity == MAXMUM_CAPACITY){\n\t\tthrehold = Integer.MAX_VALUE;\n\t\treturn;\n\t}\n\t//建立新的数组\n\tEntry[] newTable = new Entry[newCapacity];\n\t//将原有数组转到新的数组中\n\ttransfer(newTable);\n\ttable = newTable;\n\t//重新设置阈值，为新的容量和负载因子的乘积\n\tthreshold = (int)(newCapacity * loadFactory);\n}\n```\n\n其中，数组迁移逻辑主要在transfer()函数中实现，该函数实现和注释如下：\n\n```\nvoid transfer(Entry[] newTable){\n\tEntry[] src = table;\n\tint newCapacity = newTable.length;\n\t//遍历数组内所有表项\n\tfor(int j = 0; j < src.length; j++){\n\t\tEntry<K,V> e = src[j];\n\t\t//当该表项索引有值存在时，则进行迁移\n\t\tif(e != null){\n\t\t\tsrc[j] = null;\n\t\t\tdo{\n\t\t\t\t//进行数据迁移\n\t\t\t\tEntry<K,V> next = e.next;\n\t\t\t\t//计算该表现在新数组内的索引，并放置到新的数组中\n\t\t\t\t//建立新的链表关系\n\t\t\t\tint i = indexFor(e,hash, newCapacity);\n\t\t\t\te.next = newTable[i];\n\t\t\t\tnewTable[i] = e;\n\t\t\t\te = next;\n\t\t\t}while(e != null)\n\t\t}\n\t}\n}\n```\n\n很明显，HashMap的扩容操作会遍历整个HashMap，应该尽量避免该操作发生，设置合理的初始大小和负载因子，可以有效的减少HashMap扩容的次数。\n\n\n**参考书籍：《Java程序性能优化》**"
  },
  {
    "path": "Part2/JavaSE/反射机制.md",
    "content": "#反射机制\n\n反射技术：其实就是动态加载一个指定的类，并获取该类中所有的内容。并将字节码文件中的内容都封装成对象，这样便于操作这些成员。简单说：反射技术可以对一个类进行解剖。\n\n\n反射的好处：大大增强了程序的扩展性。\n\n反射的基本步骤：\n\n1. 获得Class对象，就是获得指定的名称的字节码文件对象\n2. 实例化对象，获得类的属性、方法或者构造函数\n3. 访问属性、调用方法、调用构造函数创建对象\n\n\n"
  },
  {
    "path": "Part2/JavaSE/如何表达出Collection及其子类.md",
    "content": "#如何介绍数据结构\n\n1. 是泛型？接口？类？\n2. 是个什么？\n3. 相比父类的特点？\n4. 相比于其他类型的特点？\n5. 自己的特殊方法？\n6. 子类？\n\n\n#Collection\n---\n1. 是一个泛型的接口\n2. 继承了超级接口Iterable\n3. 每个Collection的对象包含了一组对象\n4. 所有的实现类都有两个构造方法，一个是无参构造方法，第二个是用另外一个Collection对象作为构造方法的参数\n5. 遍历Collection使用Iterator迭代器实现\n6. retainAll(collection),AddAll(),removeAll(c)分别对应了集合的交并差运算\n7. 没有具体的直接实现，但提供了更具体的子接口，如Set、List等\n\n\n#List\n---\n1. 是一个接口，继承了接口Collection\n2. List是有序的Collection，能够精确控制插入、获取的位置\n3. 和Set接口的最大区别是，List允许重复值，Set不能\n4. 它的直接实现类有ArrayList，LinkedList，Vector等\n5. List有自己的迭代器ListIterator，可以通过这个迭代器进行逆序的迭代，以及用迭代器设置元素的值\n\n#ArrayList\n---\n1. ArrayList实现了Collection接口\n2. ArrayList是一个顺序表。大小可变。\n3. ArrayList相比LinkedList在查找和修改元素上比较快，但是在添加和删除上比LinkedList慢\n4. ArrayList相比Vector是线程不安全的\n\n\n#LinkedList\n---\n1. LinkedList泛型接口\n2. 链表\n3. 由于实现了Deque接口，所以它还是一个双端队列"
  },
  {
    "path": "Part3/Algorithm/LeetCode/two-sum.md",
    "content": "#two-sum\n---\n\nQuestion\n\n```\nGiven an array of integers, find two numbers such that they add up to a specific target number.\nThe function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.\nYou may assume that each input would have exactly one solution.\nInput: numbers={2, 7, 11, 15}, target=9\nOutput: index1=1, index2=2\n```\n\n\n题目大意：\n\n```\n给定一个整数数组，找到2个数字，这样他们就可以添加到一个特定的目标号。功能twosum应该返回两个数字，他们总计达目标数，其中index1必须小于index2。请注意，你的答案返回（包括指数和指数）不为零的基础。你可以假设每个输入都有一个解决方案。\n输入数字numbers= { 2，7，11，15 }，目标= 9输出：index1 = 1，index2= 2\n```\n\n解题思路：\n\n```\n可以申请额外空间来存储目标数减去从头遍历的数，记为key，如果hashMap中存在该key，就可以返回两个索引了。\n```\n\n\n代码；\n\n```\nimport java.util.HashMap;\n\npublic class Solution {\n\n\tpublic int[] twoSum(int[] numbers, int target) {\n\t\tHashMap<Integer, Integer> map = new HashMap<>();\n\t\tfor(int i = 0; i < numbers.length; i++){\n\t\t\tif(map.get(numbers[i]) != null){\n\t\t\t\tint[] result = {map.get(numbers[i]) + 1, i+1};\n\t\t\t\treturn result;\n\t\t\t}else {\n\t\t\t\tmap.put(target - numbers[i], i);\n\t\t\t}\n\t\t}\n\t\tint[] result = {};\n\t\treturn result;\n\t}\n}\n```"
  },
  {
    "path": "Part3/Algorithm/LeetCode/zigzag-conversion.md",
    "content": "#zigzag-conversion\n---\n\n题目描述\n\n```\nThe string\"PAYPALISHIRING\"is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)\n\nP   A   H   N\nA P L S I I G\nY   I   R\n\nAnd then read line by line:\"PAHNAPLSIIGYIR\"\n\nWrite the code that will take a string and make this conversion given a number of rows:\n\nstring convert(string text, int nRows);\n\nconvert(\"PAYPALISHIRING\", 3)should return\"PAHNAPLSIIGYIR\".\n\n```\n\n题目大意\n\n```\n\n```"
  },
  {
    "path": "Part3/Algorithm/Lookup/折半查找.md",
    "content": "#折半查找\n---\n\n基本原理：每次查找都对半分，但要求数组是有序的\n\n\n```\npublic class Solution {\n\n\tpublic static int BinarySearch(int[] sz,int key){\n\t\tint low = 0;\n\t\tint high = sz.length - 1;\n\t\t\n\t\twhile (low <= high) {\n\t\t\tint middle = (low + high) / 2;\n\t\t\tif(sz[middle] == key){\n\t\t\t\treturn middle;\n\t\t\t}else if(sz[middle] > key){\n\t\t\t\thigh = middle - 1;\n\t\t\t}else {\n\t\t\t\tlow = middle + 1;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n}\n```"
  },
  {
    "path": "Part3/Algorithm/Lookup/顺序查找.md",
    "content": "#顺序查找\n---\n\n基本原理：依次遍历\n\n```\npublic class Solution {\n\n\tpublic static int SequenceSearch(int[] sz, int key) {\n\t\tfor (int i = 0; i < sz.length; i++) {\n\t\t\tif (sz[i] == key) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n}\n\n```"
  },
  {
    "path": "Part3/Algorithm/Sort/冒泡排序.md",
    "content": "#冒泡排序：\n---\n* 背景介绍： 是一种简单的排序算法。它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换，也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) \n* 算法规则： 由于算法每次都将一个最大的元素往上冒，我们可以将待排序集合(0...n)看成两部分，一部分为(k..n)的待排序unsorted集合，另一部分为(0...k)的已排序sorted集合，每一次都在unsorted集合从前往后遍历，选出一个数，如果这个数比其后面的数大，则进行交换。完成一轮之后，就肯定能将这一轮unsorted集合中最大的数移动到集合的最后，并且将这个数从unsorted中删除，移入sorted中。\n\n* 代码实现（Java版本）\n```\npublic void sort(int[] args) \n        {\n        \t//第一层循环从数组的最后往前遍历\n    \t\tfor (int i = args.length - 1; i > 0 ; --i) {\n                //这里循环的上界是 i - 1，在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除”\n    \t\t\tfor (int j = 0; j < i; j++) {\n                    //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程)\n    \t\t\t\tif (args[j] > args[j+1]) {\n    \t\t\t\t\tint temp = args[j];\n    \t\t\t\t\targs[j] = args[j+1];\n    \t\t\t\t\targs[j+1] = temp;\n    \t\t\t\t}\n    \t\t\t}\n    \t\t}\n\t    }\n```\n"
  },
  {
    "path": "Part3/Algorithm/Sort/归并排序.md",
    "content": "#归并排序：\n---\n* 背景介绍： 是创建在归并操作上的一种有效的排序算法，效率为O(n log n)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法（Divide and Conquer）的一个非常典型的应用，且各层分治递归可以同时进行。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F) \n* **算法规则： 像快速排序一样，由于归并排序也是分治算法，因此可使用分治思想：**<br> 1.申请空间，使其大小为两个已经排序序列之和，该空间用来存放合并后的序列<br> 2.设定两个指针，最初位置分别为两个已经排序序列的起始位置 <br> 3.比较两个指针所指向的元素，选择相对小的元素放入到合并空间，并移动指针到下一位置 <br> 4.重复步骤3直到某一指针到达序列尾  <br> 5.将另一序列剩下的所有元素直接复制到合并序列尾\n\n* 代码实现（Java版本）\n        \n        public void mergeSort(int[] ints, int[] merge, int start, int end) \n        {\n        \tif (start >= end) return;\n    \t\t\n    \t\tint mid = (end + start) >> 1;\n    \t\t\n    \t\tmergeSort(ints, merge, start, mid);\n    \t\tmergeSort(ints, merge, mid + 1, end);\n    \n    \t\tmerge(ints, merge, start, end, mid);\n\n\t    }\n        \n        private void merge(int[] a, int[] merge, int start, int end,int mid) \n        {\n        \tint i = start;\n    \t\tint j = mid+1;\n    \t\tint pos = start;\n    \t\twhile( i <= mid || j <= end ){\n    \t\t\tif( i > mid ){\n    \t\t\t\twhile( j <= end ) merge[pos++] = a[j++];\n    \t\t\t\tbreak;\n    \t\t\t}\n    \t\t\t\n    \t\t\tif( j > end ){\n    \t\t\t\twhile( i <= mid ) merge[pos++] = a[i++];\n    \t\t\t\tbreak;\n    \t\t\t}\n    \t\t\t\n    \t\t\tmerge[pos++] = a[i] >= a[j] ? a[j++] : a[i++];\n    \t\t}\n    \t\t\n    \t\tfor (pos = start; pos <= end; pos++)\n    \t\t\ta[pos] = merge[pos];\n\t\t\n\t    }\n"
  },
  {
    "path": "Part3/Algorithm/Sort/快速排序.md",
    "content": "\n#快速排序：\n---\n* 背景介绍： 又称划分交换排序（partition-exchange sort），一种排序算法，最早由东尼·霍尔提出。在平均状况下，排序n个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较，但这种状况并不常见。事实上，快速排序通常明显比其他Ο(n log n)算法更快，因为它的内部循环（inner loop）可以在大部分的架构上很有效率地被实现出来 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F)  **\n* 算法规则： 本质来说，快速排序的过程就是不断地将无序元素集递归分割，一直到所有的分区只包含一个元素为止。 <br> 由于快速排序是一种分治算法，我们可以用分治思想将快排分为三个步骤：<br> 1.分：设定一个分割值，并根据它将数据分为两部分<br> 2.治：分别在两部分用递归的方式，继续使用快速排序法 <br> 3.合：对分割的部分排序直到完成 \n\n* 代码实现（Java版本）\n```\n        public int dividerAndChange(int[] args, int start, int end) \n        {   \n            //标准值\n        \tint pivot = args[start];\n    \t\twhile (start < end) {\n    \t\t\t// 从右向左寻找，一直找到比参照值还小的数值，进行替换\n    \t\t\t// 这里要注意，循环条件必须是 当后面的数 小于 参照值的时候\n    \t\t\t// 我们才跳出这一层循环\n    \t\t\twhile (start < end && args[end] >= pivot)\n    \t\t\t\tend--;\n    \n    \t\t\tif (start < end) {\n    \t\t\t\tswap(args, start, end);\n    \t\t\t\tstart++;\n    \t\t\t}\n    \n    \t\t\t// 从左向右寻找，一直找到比参照值还大的数组，进行替换\n    \t\t\twhile (start < end && args[start] < pivot)\n    \t\t\t\tstart++;\n    \n    \t\t\tif (start < end) {\n    \t\t\t\tswap(args, end, start);\n    \t\t\t\tend--;\n    \t\t\t}\n    \t\t}\n    \n    \t\targs[start] = pivot;\n    \t\treturn start;\n    \t}\n\n    \tpublic void sort(int[] args, int start, int end) \n        {\n    \t\t//当分治的元素大于1个的时候，才有意义\n    \t\tif ( end - start > 1) {\n                int mid = 0;\n    \t\t\tmid = dividerAndChange(args, start, end);\n    \t\t\t// 对左部分排序\n    \t\t\tsort(args, start, mid);\n    \t\t\t// 对右部分排序\n    \t\t\tsort(args, mid + 1, end);\n    \t\t}\n    \t}\n\n    \tprivate void swap(int[] args, int fromIndex, int toIndex) \n        {\n    \t\targs[fromIndex] = args[toIndex];\n    \t}\n```\n"
  },
  {
    "path": "Part3/Algorithm/Sort/选择排序.md",
    "content": "#选择排序：\n---\n* 背景介绍： 选择排序（Selection sort）是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小（大）元素，存放到排序序列的起始位置，然后，再从剩余未排序元素中继续寻找最小（大）元素，然后放到已排序序列的末尾。以此类推，直到所有元素均排序完毕。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) \n* 算法规则： 将待排序集合(0...n)看成两部分，在起始状态中，一部分为(k..n)的待排序unsorted集合，另一部分为(0...k)的已排序sorted集合,在待排序集合中挑选出最小元素并且记录下标i，若该下标不等于k，那么 unsorted[i] 与 sorted[k]交换 ，一直重复这个过程，直到unsorted集合中元素为空为止。 \n\n* 代码实现（Java版本）\n```\npublic void sort(int[] args) \n{\n        int len = args.length;\n        for (int i = 0,k = 0; i < len; i++,k = i) {\n            // 在这一层循环中找最小\n            for (int j = i + 1; j < len; j++) {\n                // 如果后面的元素比前面的小，那么就交换下标，每一趟都会选择出来一个最小值的下标\n                if (args[k] > args[j]) k = j;\n    \t\t}\n    \n    \t\tif (i != k) {\n    \t\t\tint tmp = args[i];\n    \t\t\targs[i] = args[k];\n    \t\t\targs[k] = tmp;\n    \t\t}\n    \t}\n    }\n```\n"
  },
  {
    "path": "Part3/Algorithm/Sort/面试中的 10 大排序算法总结.md",
    "content": "<h2> <b>本文转载自码农网：http://www.codeceo.com/article/10-sort-algorithm-interview.html#0-tsina-1-10490-397232819ff9a47a7b7e80a40613cfe1\n </h2>\n\n\n<p>查找和排序算法是算法的入门知识，其经典思想可以用于很多算法当中。因为其实现代码较短，应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗，只要熟悉了思想，灵活运用也不是难事。一般在面试中最常考的是快速排序和归并排序，并且经常有面试官要求现场写出这两种排序的代码。对这两种排序的代码一定要信手拈来才行。还有插入排序、冒泡排序、堆排序、基数排序、桶排序等。面试官对于这些排序可能会要求比较各自的优劣、各种算法的思想及其使用场景。还有要会分析算法的时间和空间复杂度。通常查找和排序算法的考察是面试的开始，如果这些问题回答不好，估计面试官都没有继续面试下去的兴趣都没了。所以想开个好头就要把常见的排序算法思想及其特点要熟练掌握，有必要时要熟练写出代码。</p>\n<p>接下来我们就分析一下常见的排序算法及其使用场景。限于篇幅，某些算法的详细演示和图示请自行寻找详细的参考。</p>\n<h2>冒泡排序</h2>\n<p>冒泡排序是最简单的排序之一了，其大体思想就是通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样，因此而得名。举个栗子，对5,3,8,6,4这个无序序列进行冒泡排序。首先从后向前冒泡，4和6比较，把4交换到前面，序列变成5,3,8,4,6。同理4和8交换，变成5,3,4,8,6,3和4无需交换。5和3交换，变成3,5,4,8,6,3.这样一次冒泡就完了，把最小的数3排到最前面了。对剩下的序列依次冒泡就会得到一个有序序列。冒泡排序的时间复杂度为O(n^2)。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;冒泡排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午8:54:27\n */\npublic class BubbleSort {\n\n    public static void bubbleSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n        for(int i=0; i&lt;arr.length-1; i++) {\n            for(int j=arr.length-1; j&gt;i; j--) {\n                if(arr[j] &lt; arr[j-1]) {\n                    swap(arr, j-1, j);\n                }\n            }\n        }\n    }\n\n    public static void swap(int[] arr, int i, int j) {\n        int temp = arr[i];\n        arr[i] = arr[j];\n        arr[j] = temp;\n    }\n}</pre>\n</div>\n<h2>选择排序</h2>\n<p>选择排序的思想其实和冒泡排序有点类似，都是在一次排序后把最小的元素放到最前面。但是过程不同，冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。举个栗子，对5,3,8,6,4这个无序序列进行简单选择排序，首先要选择5以外的最小数来和5交换，也就是选择3和5交换，一次排序后就变成了3,5,8,6,4.对剩下的序列一次进行选择和交换，最终就会得到一个有序序列。其实选择排序可以看成冒泡排序的优化，因为其目的相同，只是选择排序只有在确定了最小数的前提下才进行交换，大大减少了交换的次数。选择排序的时间复杂度为O(n^2)</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;简单选择排序算法的实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午9:13:35\n */\npublic class SelectSort {\n\n    public static void selectSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n        int minIndex = 0;\n        for(int i=0; i&lt;arr.length-1; i++) { //只需要比较n-1次\n            minIndex = i;\n            for(int j=i+1; j&lt;arr.length; j++) { //从i+1开始比较，因为minIndex默认为i了，i就没必要比了。\n                if(arr[j] &lt; arr[minIndex]) {\n                    minIndex = j;\n                }\n            }\n\n            if(minIndex != i) { //如果minIndex不为i，说明找到了更小的值，交换之。\n                swap(arr, i, minIndex);\n            }\n        }\n\n    }\n\n    public static void swap(int[] arr, int i, int j) {\n        int temp = arr[i];\n        arr[i] = arr[j];\n        arr[j] = temp;\n    }\n\n}</pre>\n</div>\n<h2>插入排序</h2>\n<p>插入排序不是通过交换位置而是通过比较找到合适的位置插入元素来达到排序的目的的。相信大家都有过打扑克牌的经历，特别是牌数较大的。在分牌时可能要整理自己的牌，牌多的时候怎么整理呢？就是拿到一张牌，找到一个合适的位置插入。这个原理其实和插入排序是一样的。举个栗子，对5,3,8,6,4这个无序序列进行简单插入排序，首先假设第一个数的位置时正确的，想一下在拿到第一张牌的时候，没必要整理。然后3要插到5前面，把5后移一位，变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动，6插在8前面，8后移一位，4插在5前面，从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。简单插入排序的时间复杂度也是O(n^2)。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;简单插入排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午9:38:55\n */\npublic class InsertSort {\n\n    public static void insertSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n\n        for(int i=1; i&lt;arr.length; i++) { //假设第一个数位置时正确的；要往后移，必须要假设第一个。\n\n            int j = i;\n            int target = arr[i]; //待插入的\n\n            //后移\n            while(j &gt; 0 &amp;&amp; target &lt; arr[j-1]) {\n                arr[j] = arr[j-1];\n                j --;\n            }\n\n            //插入 \n            arr[j] = target;\n        }\n\n    }\n\n}</pre>\n</div>\n<h2>快速排序</h2>\n<p>快速排序一听名字就觉得很高端，在实际应用当中快速排序确实也是表现最好的排序算法。冒泡排序虽然高端，但其实其思想是来自冒泡排序，冒泡排序是通过相邻元素的比较和交换把最小的冒泡到最顶端，而快速排序是比较和交换小数和大数，这样一来不仅把小数冒泡到上面同时也把大数沉到下面。</p>\n<p>举个栗子：对5,3,8,6,4这个无序序列进行快速排序，思路是右指针找比基准数小的，左指针找比基准数大的，交换之。</p>\n<p>5,3,8,6,4 用5作为比较的基准，最终会把5小的移动到5的左边，比5大的移动到5的右边。</p>\n<p>5,3,8,6,4 首先设置i,j两个指针分别指向两端，j指针先扫描（思考一下为什么？）4比5小停止。然后i扫描，8比5大停止。交换i,j位置。</p>\n<p>5,3,4,6,8 然后j指针再扫描，这时j扫描4时两指针相遇。停止。然后交换4和基准数。</p>\n<p>4,3,5,6,8 一次划分后达到了左边比5小，右边比5大的目的。之后对左右子序列递归排序，最终得到有序序列。</p>\n<p>上面留下来了一个问题为什么一定要j指针先动呢？首先这也不是绝对的，这取决于基准数的位置，因为在最后两个指针相遇的时候，要交换基准数到相遇的位置。一般选取第一个数作为基准数，那么就是在左边，所以最后相遇的数要和基准数交换，那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。</p>\n<p>快速排序是不稳定的，其时间平均时间复杂度是O(nlgn)。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;实现快速排序算法&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午5:07:29\n */\npublic class QuickSort {\n    //一次划分\n    public static int partition(int[] arr, int left, int right) {\n        int pivotKey = arr[left];\n        int pivotPointer = left;\n\n        while(left &lt; right) {\n            while(left &lt; right &amp;&amp; arr[right] &gt;= pivotKey)\n                right --;\n            while(left &lt; right &amp;&amp; arr[left] &lt;= pivotKey)\n                left ++;\n            swap(arr, left, right); //把大的交换到右边，把小的交换到左边。\n        }\n        swap(arr, pivotPointer, left); //最后把pivot交换到中间\n        return left;\n    }\n\n    public static void quickSort(int[] arr, int left, int right) {\n        if(left &gt;= right)\n            return ;\n        int pivotPos = partition(arr, left, right);\n        quickSort(arr, left, pivotPos-1);\n        quickSort(arr, pivotPos+1, right);\n    }\n\n    public static void sort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n        quickSort(arr, 0, arr.length-1);\n    }\n\n    public static void swap(int[] arr, int left, int right) {\n        int temp = arr[left];\n        arr[left] = arr[right];\n        arr[right] = temp;\n    }\n\n}</pre>\n</div>\n<p>其实上面的代码还可以再优化，上面代码中基准数已经在pivotKey中保存了，所以不需要每次交换都设置一个temp变量，在交换左右指针的时候只需要先后覆盖就可以了。这样既能减少空间的使用还能降低赋值运算的次数。优化代码如下：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;实现快速排序算法&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午5:07:29\n */\npublic class QuickSort {\n\n    /**\n     * 划分\n     * @param arr\n     * @param left\n     * @param right\n     * @return\n     */\n    public static int partition(int[] arr, int left, int right) {\n        int pivotKey = arr[left];\n\n        while(left &lt; right) {\n            while(left &lt; right &amp;&amp; arr[right] &gt;= pivotKey)\n                right --;\n            arr[left] = arr[right]; //把小的移动到左边\n            while(left &lt; right &amp;&amp; arr[left] &lt;= pivotKey)\n                left ++;\n            arr[right] = arr[left]; //把大的移动到右边\n        }\n        arr[left] = pivotKey; //最后把pivot赋值到中间\n        return left;\n    }\n\n    /**\n     * 递归划分子序列\n     * @param arr\n     * @param left\n     * @param right\n     */\n    public static void quickSort(int[] arr, int left, int right) {\n        if(left &gt;= right)\n            return ;\n        int pivotPos = partition(arr, left, right);\n        quickSort(arr, left, pivotPos-1);\n        quickSort(arr, pivotPos+1, right);\n    }\n\n    public static void sort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n        quickSort(arr, 0, arr.length-1);\n    }\n\n}</pre>\n</div>\n<p>总结快速排序的思想：冒泡+二分+递归分治，慢慢体会。。。</p>\n<h2>堆排序</h2>\n<p>堆排序是借助堆来实现的选择排序，思想同简单的选择排序，以下以大顶堆为例。注意：如果想升序排序就使用大顶堆，反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。</p>\n<p>首先，实现堆排序需要解决两个问题：</p>\n<p>1. 如何由一个无序序列键成一个堆？</p>\n<p>2. 如何在输出堆顶元素之后，调整剩余元素成为一个新的堆？</p>\n<p>第一个问题，可以直接使用线性数组来表示一个堆，由初始的无序序列建成一个堆就需要自底向上从第一个非叶元素开始挨个调整成一个堆。</p>\n<p>第二个问题，怎么调整成堆？首先是将堆顶元素和最后一个元素交换。然后比较当前堆顶元素的左右孩子节点，因为除了当前的堆顶元素，左右孩子堆均满足条件，这时需要选择当前堆顶元素与左右孩子节点的较大者（大顶堆）交换，直至叶子节点。我们称这个自堆顶自叶子的调整成为筛选。</p>\n<p>从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树，则最后一个非终端节点是n/2取底个元素，由此筛选即可。举个栗子：</p>\n<p>49,38,65,97,76,13,27,49序列的堆排序建初始堆和调整的过程如下：</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/2614bce119263edcf9d18b6365b39197.png\" alt=\"\" /></p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/ad373a589182dd1b7e443915c8775fcd.png\" alt=\"\" /></p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;堆排序算法的实现，以大顶堆为例。&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-4 上午9:26:02\n */\npublic class HeapSort {\n\n    /**\n     * 堆筛选，除了start之外，start~end均满足大顶堆的定义。\n     * 调整之后start~end称为一个大顶堆。\n     * @param arr 待调整数组\n     * @param start 起始指针\n     * @param end 结束指针\n     */\n    public static void heapAdjust(int[] arr, int start, int end) {\n        int temp = arr[start];\n\n        for(int i=2*start+1; i&lt;=end; i*=2) {\n            //左右孩子的节点分别为2*i+1,2*i+2\n\n            //选择出左右孩子较小的下标\n            if(i &lt; end &amp;&amp; arr[i] &lt; arr[i+1]) {\n                i ++; \n            }\n            if(temp &gt;= arr[i]) {\n                break; //已经为大顶堆，=保持稳定性。\n            }\n            arr[start] = arr[i]; //将子节点上移\n            start = i; //下一轮筛选\n        }\n\n        arr[start] = temp; //插入正确的位置\n    }\n\n    public static void heapSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n\n        //建立大顶堆\n        for(int i=arr.length/2; i&gt;=0; i--) {\n            heapAdjust(arr, i, arr.length-1);\n        }\n\n        for(int i=arr.length-1; i&gt;=0; i--) {\n            swap(arr, 0, i);\n            heapAdjust(arr, 0, i-1);\n        }\n\n    }\n\n    public static void swap(int[] arr, int i, int j) {\n        int temp = arr[i];\n        arr[i] = arr[j];\n        arr[j] = temp;\n    }\n\n}</pre>\n</div>\n<h2>希尔排序</h2>\n<p>希尔排序是插入排序的一种高效率的实现，也叫缩小增量排序。简单的插入排序中，如果待排序列是正序时，时间复杂度是O(n)，如果序列是基本有序的，使用直接插入排序效率就非常高。希尔排序就利用了这个特点。基本思想是：先将整个待排记录序列分割成为若干子序列分别进行直接插入排序，待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。</p>\n<p>举个栗子：</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/aef03b0d2339be34627bcd81a3809e62.png\" alt=\"\" /></p>\n<p>从上述排序过程可见，希尔排序的特点是，子序列的构成不是简单的逐段分割，而是将某个相隔某个增量的记录组成一个子序列。如上面的例子，第一堂排序时的增量为5，第二趟排序的增量为3。由于前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较，因此关键字较小的记录就不是一步一步地向前挪动，而是跳跃式地往前移，从而使得进行最后一趟排序时，整个序列已经做到基本有序，只要作记录的少量比较和移动即可。因此希尔排序的效率要比直接插入排序高。</p>\n<p>希尔排序的分析是复杂的，时间复杂度是所取增量的函数，这涉及一些数学上的难题。但是在大量实验的基础上推出当n在某个范围内时，时间复杂度可以达到O(n^1.3)。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;希尔排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-3 下午10:53:55\n */\npublic class ShellSort {\n\n    /**\n     * 希尔排序的一趟插入\n     * @param arr 待排数组\n     * @param d 增量\n     */\n    public static void shellInsert(int[] arr, int d) {\n        for(int i=d; i&lt;arr.length; i++) {\n            int j = i - d;\n            int temp = arr[i];    //记录要插入的数据  \n            while (j&gt;=0 &amp;&amp; arr[j]&gt;temp) {  //从后向前，找到比其小的数的位置   \n                arr[j+d] = arr[j];    //向后挪动  \n                j -= d;  \n            }  \n\n            if (j != i - d)    //存在比其小的数 \n                arr[j+d] = temp;\n\n        }\n    }\n\n    public static void shellSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n        int d = arr.length / 2;\n        while(d &gt;= 1) {\n            shellInsert(arr, d);\n            d /= 2;\n        }\n    }\n\n}</pre>\n</div>\n<h2>归并排序</h2>\n<p>归并排序是另一种不同的排序方法，因为归并排序使用了递归分治的思想，所以理解起来比较容易。其基本思想是，先递归划分子问题，然后合并结果。把待排序列看成由两个有序的子序列，然后合并两个子序列，然后把子序列看成由两个有序序列。。。。。倒着来看，其实就是先两两合并，然后四四合并。。。最终形成有序序列。空间复杂度为O(n)，时间复杂度为O(nlogn)。</p>\n<p>举个栗子：</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/df8478362d9b42913e022eff94d43eb4.png\" alt=\"\" /></p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;归并排序算法的实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-4 上午8:14:20\n */\npublic class MergeSort {\n\n    public static void mergeSort(int[] arr) {\n        mSort(arr, 0, arr.length-1);\n    }\n\n    /**\n     * 递归分治\n     * @param arr 待排数组\n     * @param left 左指针\n     * @param right 右指针\n     */\n    public static void mSort(int[] arr, int left, int right) {\n        if(left &gt;= right)\n            return ;\n        int mid = (left + right) / 2;\n\n        mSort(arr, left, mid); //递归排序左边\n        mSort(arr, mid+1, right); //递归排序右边\n        merge(arr, left, mid, right); //合并\n    }\n\n    /**\n     * 合并两个有序数组\n     * @param arr 待合并数组\n     * @param left 左指针\n     * @param mid 中间指针\n     * @param right 右指针\n     */\n    public static void merge(int[] arr, int left, int mid, int right) {\n        //[left, mid] [mid+1, right]\n        int[] temp = new int[right - left + 1]; //中间数组\n\n        int i = left;\n        int j = mid + 1;\n        int k = 0;\n        while(i &lt;= mid &amp;&amp; j &lt;= right) {\n            if(arr[i] &lt;= arr[j]) {\n                temp[k++] = arr[i++];\n            }\n            else {\n                temp[k++] = arr[j++];\n            }\n        }\n\n        while(i &lt;= mid) {\n            temp[k++] = arr[i++];\n        }\n\n        while(j &lt;= right) {\n            temp[k++] = arr[j++];\n        }\n\n        for(int p=0; p&lt;temp.length; p++) {\n            arr[left + p] = temp[p];\n        }\n\n    }\n}</pre>\n</div>\n<h2>计数排序</h2>\n<p>如果在面试中有面试官要求你写一个O(n)时间复杂度的排序算法，你千万不要立刻说：这不可能！虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序，只不过有前提条件，就是待排序的数要满足一定的范围的整数，而且计数排序需要比较多的辅助空间。其基本思想是，用待排序的数作为计数数组的下标，统计每个数字的个数。然后依次输出即可得到有序序列。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;计数排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-4 下午4:52:02\n */\npublic class CountSort {\n\n    public static void countSort(int[] arr) {\n        if(arr == null || arr.length == 0)\n            return ;\n\n        int max = max(arr);\n\n        int[] count = new int[max+1];\n        Arrays.fill(count, 0);\n\n        for(int i=0; i&lt;arr.length; i++) {\n            count[arr[i]] ++;\n        }\n\n        int k = 0;\n        for(int i=0; i&lt;=max; i++) {\n            for(int j=0; j&lt;count[i]; j++) {\n                arr[k++] = i;\n            }\n        }\n\n    }\n\n    public static int max(int[] arr) {\n        int max = Integer.MIN_VALUE;\n        for(int ele : arr) {\n            if(ele &gt; max)\n                max = ele;\n        }\n\n        return max;\n    }\n\n}</pre>\n</div>\n<h2>桶排序</h2>\n<p>桶排序算是计数排序的一种改进和推广，但是网上有许多资料把计数排序和桶排序混为一谈。其实桶排序要比计数排序复杂许多。</p>\n<p>对桶排序的分析和解释借鉴这位兄弟的文章（有改动）：http://hxraid.iteye.com/blog/647759</p>\n<p>桶排序的基本思想：</p>\n<p>假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ，将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ，那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]&#8230;.B[M]中的全部内容即是一个有序序列。bindex=f(key)   其中，bindex 为桶数组B的下标(即第bindex个桶), k为待排序列的关键字。桶排序之所以能够高效，其关键在于这个映射函数，它必须做到：如果关键字k1&lt;k2，那么f(k1)&lt;=f(k2)。也就是说B(i)中的最小数据都要大于B(i-1)中最大数据。很显然，映射函数的确定与数据本身的特点有很大的关系。</p>\n<p>举个栗子：</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/47bcf435950f64c848ce43d5ae5142aa.gif\" alt=\"\" /></p>\n<p>假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶，然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中，并在每个非空的桶中进行快速排序后得到如图所示。只要顺序输出每个B[i]中的数据就可以得到有序序列了。</p>\n<p>桶排序分析：</p>\n<p>桶排序利用函数的映射关系，减少了几乎所有的比较工作。实际上，桶排序的f(k)值的计算，其作用就相当于快排中划分，希尔排序中的子序列，归并排序中的子问题，已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。</p>\n<p>对N个关键字进行桶排序的时间复杂度分为两个部分：</p>\n<p>(1) 循环计算每个关键字的桶映射函数，这个时间复杂度是O(N)。</p>\n<p>(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序，其时间复杂度为  ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。</p>\n<p>很显然，第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此，我们需要尽量做到下面两点：</p>\n<p>(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中，这样每个桶就有[N/M]个数据量。</p>\n<p>(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据，这样就完全避开了桶内数据的“比较”排序操作。当然，做到这一点很不容易，数据量巨大的情况下，f(k)函数会使得桶集合的数量巨大，空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。</p>\n<p>对于N个待排数据，M个桶，平均每个桶[N/M]个数据的桶排序平均时间复杂度为：</p>\n<p>O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)</p>\n<p>当N=M时，即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。</p>\n<p><strong>总结：</strong> 桶排序的平均时间复杂度为线性的O(N+C)，其中C=N*(logN-logM)。如果相对于同样的N，桶数量M越大，其效率越高，最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M)，如果输入数据非常庞大，而桶的数量也非常多，则空间代价无疑是昂贵的。此外，桶排序是稳定的。</p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;桶排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-4 下午7:39:31\n */\npublic class BucketSort {\n\n    public static void bucketSort(int[] arr) {\n        if(arr == null &amp;&amp; arr.length == 0)\n            return ;\n\n        int bucketNums = 10; //这里默认为10，规定待排数[0,100)\n        List&lt;List&lt;Integer&gt;&gt; buckets = new ArrayList&lt;List&lt;Integer&gt;&gt;(); //桶的索引\n\n        for(int i=0; i&lt;10; i++) {\n            buckets.add(new LinkedList&lt;Integer&gt;()); //用链表比较合适\n        }\n\n        //划分桶\n        for(int i=0; i&lt;arr.length; i++) {\n            buckets.get(f(arr[i])).add(arr[i]);\n        }\n\n        //对每个桶进行排序\n        for(int i=0; i&lt;buckets.size(); i++) {\n            if(!buckets.get(i).isEmpty()) {\n                Collections.sort(buckets.get(i)); //对每个桶进行快排\n            }\n        }\n\n        //还原排好序的数组\n        int k = 0;\n        for(List&lt;Integer&gt; bucket : buckets) {\n            for(int ele : bucket) {\n                arr[k++] = ele;\n            }\n        }\n    }\n\n    /**\n     * 映射函数\n     * @param x\n     * @return\n     */\n    public static int f(int x) {\n        return x / 10;\n    }\n\n}</pre>\n</div>\n<h2>基数排序</h2>\n<p>基数排序又是一种和前面排序方式不同的排序方式，基数排序不需要进行记录关键字之间的比较。基数排序是一种借助多关键字排序思想对单逻辑关键字进行排序的方法。所谓的多关键字排序就是有多个优先级不同的关键字。比如说成绩的排序，如果两个人总分相同，则语文高的排在前面，语文成绩也相同则数学高的排在前面。。。如果对数字进行排序，那么个位、十位、百位就是不同优先级的关键字，如果要进行升序排序，那么个位、十位、百位优先级一次增加。基数排序是通过多次的收分配和收集来实现的，关键字优先级低的先进行分配和收集。</p>\n<p>举个栗子：</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/cb56ff40b1ade7e57d5aa9840fc1eba3.png\" alt=\"\" /></p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/75d38cce23336e133dd8eac33eabf5ad.png\" alt=\"\" /></p>\n<p>实现代码：</p>\n<div>\n<pre>/**\n *@Description:&lt;p&gt;基数排序算法实现&lt;/p&gt;\n *@author 王旭\n *@time 2016-3-4 下午8:29:52\n */\npublic class RadixSort {\n\n    public static void radixSort(int[] arr) {\n        if(arr == null &amp;&amp; arr.length == 0)\n            return ;\n\n        int maxBit = getMaxBit(arr);\n\n        for(int i=1; i&lt;=maxBit; i++) {\n\n            List&lt;List&lt;Integer&gt;&gt; buf = distribute(arr, i); //分配\n            collecte(arr, buf); //收集\n        }\n\n    }\n\n    /**\n     * 分配\n     * @param arr 待分配数组\n     * @param iBit 要分配第几位\n     * @return\n     */\n    public static List&lt;List&lt;Integer&gt;&gt; distribute(int[] arr, int iBit) {\n        List&lt;List&lt;Integer&gt;&gt; buf = new ArrayList&lt;List&lt;Integer&gt;&gt;();\n        for(int j=0; j&lt;10; j++) {\n            buf.add(new LinkedList&lt;Integer&gt;());\n        }\n        for(int i=0; i&lt;arr.length; i++) {\n            buf.get(getNBit(arr[i], iBit)).add(arr[i]);\n        }\n        return buf;\n    }\n\n    /**\n     * 收集\n     * @param arr 把分配的数据收集到arr中\n     * @param buf \n     */\n    public static void collecte(int[] arr, List&lt;List&lt;Integer&gt;&gt; buf) {\n        int k = 0;\n        for(List&lt;Integer&gt; bucket : buf) {\n            for(int ele : bucket) {\n                arr[k++] = ele;\n            }\n        }\n\n    }\n\n    /**\n     * 获取最大位数\n     * @param x\n     * @return\n     */\n    public static int getMaxBit(int[] arr) {\n        int max = Integer.MIN_VALUE;\n        for(int ele : arr) {\n            int len = (ele+\"\").length();\n            if(len &gt; max)\n                max = len;\n        }\n        return max;\n    }\n\n    /**\n     * 获取x的第n位，如果没有则为0.\n     * @param x\n     * @param n\n     * @return\n     */\n    public static int getNBit(int x, int n) {\n\n        String sx = x + \"\";\n        if(sx.length() &lt; n)\n            return 0;\n        else\n            return sx.charAt(sx.length()-n) - '0';\n    }\n\n}</pre>\n</div>\n<h2>总结</h2>\n<p>在前面的介绍和分析中我们提到了冒泡排序、选择排序、插入排序三种简单的排序及其变种快速排序、堆排序、希尔排序三种比较高效的排序。后面我们又分析了基于分治递归思想的归并排序还有计数排序、桶排序、基数排序三种线性排序。我们可以知道排序算法要么简单有效，要么是利用简单排序的特点加以改进，要么是以空间换取时间在特定情况下的高效排序。但是这些排序方法都不是固定不变的，需要结合具体的需求和场景来选择甚至组合使用。才能达到高效稳定的目的。没有最好的排序，只有最适合的排序。</p>\n<p>下面就总结一下排序算法的各自的使用场景和适用场合。</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/2f0f5c6b5c7b007b00f0d33427a70db0.png\" alt=\"\" /></p>\n<p>1. 从平均时间来看，快速排序是效率最高的，但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后者相比较的结果是，在n较大时归并排序使用时间较少，但使用辅助空间较多。</p>\n<p>2. 上面说的简单排序包括除希尔排序之外的所有冒泡排序、插入排序、简单选择排序。其中直接插入排序最简单，但序列基本有序或者n较小时，直接插入排序是好的方法，因此常将它和其他的排序方法，如快速排序、归并排序等结合在一起使用。</p>\n<p>3. 基数排序的时间复杂度也可以写成O(d*n)。因此它最使用于n值很大而关键字较小的的序列。若关键字也很大，而序列中大多数记录的最高关键字均不同，则亦可先按最高关键字不同，将序列分成若干小的子序列，而后进行直接插入排序。</p>\n<p>4. 从方法的稳定性来比较，基数排序是稳定的内排方法，所有时间复杂度为O(n^2)的简单排序也是稳定的。但是快速排序、堆排序、希尔排序等时间性能较好的排序方法都是不稳定的。稳定性需要根据具体需求选择。</p>\n<p>5. 上面的算法实现大多数是使用线性存储结构，像插入排序这种算法用链表实现更好，省去了移动元素的时间。具体的存储结构在具体的实现版本中也是不同的。</p>\n<p>附：基于比较排序算法时间下限为O(nlogn)的证明：</p>\n<p>基于比较排序下限的证明是通过决策树证明的，决策树的高度Ω（nlgn），这样就得出了比较排序的下限。</p>\n<p><img src=\"http://static.codeceo.com/images/2016/03/b4d51a192d469b833a46695c0a7668f6.jpg\" alt=\"\" /></p>\n<p>首先要引入决策树。 首先决策树是一颗二叉树，每个节点表示元素之间一组可能的排序，它予以京进行的比较相一致，比较的结果是树的边。 先来说明一些二叉树的性质，令T是深度为d的二叉树，则T最多有2^片树叶。 具有L片树叶的二叉树的深度至少是logL。 所以，对n个元素排序的决策树必然有n!片树叶（因为n个数有n!种不同的大小关系），所以决策树的深度至少是log(n!)，即至少需要log(n!)次比较。 而 log(n!)=logn+log(n-1)+log(n-2)+&#8230;+log2+log1 &gt;=logn+log(n-1)+log(n-2)+&#8230;+log(n/2) &gt;=(n/2)log(n/2) &gt;=(n/2)logn-n/2 =O(nlogn) 所以只用到比较的排序算法最低时间复杂度是O(nlogn)。</p>\n<p><strong>参考资料：</strong></p>\n<ul>\n<li>《数据结构》 严蔚敏 吴伟民 编著</li>\n<li>桶排序分析：http://hxraid.iteye.com/blog/647759</li>\n<li>部分排序算法分析与介绍：http://www.cnblogs.com/weixliu/archive/2012/12/23/2829671.html</li>\n</ul>\n<script type=\"text/javascript\">\nvar strBatchView = 37731</script>\n\n<a id=\"soft-link\" name=\"soft-link\"></a>\n\n<div style=\"width:336px;height:280px;margin:40px auto\">\n<script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n<!-- codeceo-bottom-big -->\n<ins class=\"adsbygoogle\"\n     style=\"display:inline-block;width:336px;height:280px\"\n     data-ad-client=\"ca-pub-3171310320403916\"\n     data-ad-slot=\"5138981789\"></ins>\n<script>\n"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/1.七种方式实现singleton模式.md",
    "content": "#面试题2.七种方式实现Singleton模式\n---\n```\n\npublic class Test {\n\n\t/**\n\t * 单例模式，懒汉式，线程安全\n\t */\n\tpublic static class Singleton {\n\t\tprivate final static Singleton INSTANCE = new Singleton();\n\n\t\tprivate Singleton() {\n\n\t\t}\n\n\t\tpublic static Singleton getInstance() {\n\t\t\treturn INSTANCE;\n\t\t}\n\t}\n\n\t/**\n\t * 单例模式，懒汉式，线程不安全\n\t */\n\tpublic static class Singleton2 {\n\t\tprivate static Singleton2 instance = null;\n\n\t\tprivate Singleton2() {\n\n\t\t}\n\n\t\tpublic static Singleton2 getInstance() {\n\t\t\tif (instance == null) {\n\t\t\t\tinstance = new Singleton2();\n\t\t\t}\n\t\t\treturn instance;\n\t\t}\n\t}\n\n\t/**\n\t * 单例模式，饿汉式，线程安全，多线程环境下效率不高\n\t */\n\tpublic static class Singleton3 {\n\t\tprivate static Singleton3 instance = null;\n\n\t\tprivate Singleton3() {\n\n\t\t}\n\n\t\tpublic static synchronized Singleton3 getInstance() {\n\t\t\tif (instance == null) {\n\t\t\t\tinstance = new Singleton3();\n\t\t\t}\n\t\t\treturn instance;\n\t\t}\n\t}\n\n\t/**\n\t * 单例模式，懒汉式，变种，线程安全\n\t */\n\tpublic static class Singleton4 {\n\t\tprivate static Singleton4 instance = null;\n\n\t\tstatic {\n\t\t\tinstance = new Singleton4();\n\t\t}\n\n\t\tprivate Singleton4() {\n\n\t\t}\n\n\t\tpublic static Singleton4 getInstance() {\n\t\t\treturn instance;\n\t\t}\n\t}\n\n\t/**\n\t * 单例模式，使用静态内部类，线程安全（推荐）\n\t */\n\tpublic static class Singleton5 {\n\t\tprivate final static class SingletonHolder {\n\t\t\tprivate static final Singleton5 INSTANCE = new Singleton5();\n\t\t}\n\n\t\tprivate static Singleton5 getInstance() {\n\t\t\treturn SingletonHolder.INSTANCE;\n\t\t}\n\t}\n\n\t/**\n\t * 静态内部类，使用枚举方式，线程安全（推荐）\n\t */\n\tpublic enum Singleton6 {\n\t\tINSTANCE;\n\t\tpublic void whateverMethod() {\n\n\t\t}\n\t}\n\n\t/**\n\t * 静态内部类，使用双重校验锁，线程安全（推荐）\n\t */\n\tpublic static class Singleton7 {\n\t\tprivate volatile static Singleton7 instance = null;\n\n\t\tprivate Singleton7() {\n\n\t\t}\n\n\t\tpublic static Singleton7 getInstance() {\n\t\t\tif (instance == null) {\n\t\t\t\tsynchronized (Singleton7.class) {\n\t\t\t\t\tif (instance == null) {\n\t\t\t\t\t\tinstance = new Singleton7();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn instance;\n\t\t}\n\t}\n\n}\n\n```\n"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/2.二维数组中的查找.md",
    "content": "#面试题3:二维数组中的查找\n---\n\n这个题我在面试今日头条的时候面试官让我写过这个题目的代码，所以比较重要。\n\n###题目描述\n\n在一个二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。 \n输入描述:\n\n```\narray： 待查找的二维数组\ntarget：查找的数字\n```\n\n\n输出描述:\n\n```\n查找到返回true，查找不到返回false\n```\n\n解题思路：\n\n```\n\n```"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/合并两个排序的链表.md",
    "content": "#合并两个排序的链表\n---\n\n题目：\n\n>输入两个递增排序的链表，合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如输入图中的链表1和链表2，则合并之后的升序链表3所示：\n\n![](http://img.blog.csdn.net/20150801211607173?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center =500x200)\n\n容易犯的错误：\n\n1. 写代码之前没有对合并的过程想清楚，最终合并出来的链表要么中间断开了要么并没有做到递增排序\n2. 代码在鲁棒性方面存在问题，程序一旦有特殊的输入(如空链表)就会崩溃。\n\n"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/旋转数组的最小数字.md",
    "content": "#旋转数组的最小数字\n---\n\n题目\n\n>题目： 把一个数组最开始的若干个元素搬到数组的末尾，我们称之为旋转。 输入一个递增的排序的数组的一个旋转，输出旋转数组的最小元素。例如数组｛3，4，5，1，2｝为{1,2,3,4,5}的一个旋转，该数组的最小元素为1.\n\n\n解法：\n\n>旋转之后的数组实际上可以划分为两个排序的子数组，而且前面的子数组的元素都大于或者等于后面子数组的元素，而且最小的元素刚好是这两个子数组的分界线。\n \n>和二分查找一样，我们用两个指针分别指向数组的第一个元素和最后一个元素。"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/面试题11：数值的整数次方.md",
    "content": "#面试题11 数值的整数次方\n---\n\n题目：\n\n>实现函数double Power(double base,int exponent)，求base的exponent次方，不得使用库函数，同时不需要考虑大数问题。\n\n看到了很多人会这样写：\n\n```\npublic static double powerWithExponent(double base,int exponent){\n\t\tdouble result = 1.0;\n\t\tfor(int i = 1; i <= exponent; i++){\n\t\t\tresult = result * base;\n\t\t}\n\t\treturn result;\n\t}\n```\n\n输入的指数(exponent)小于1即是零和负数时怎么办？\n\n当指数为负数的时候，可以先对指数求绝对值，然后算出次方的结果之后再取倒数，当底数(base)是零且指数是负数的时候，如果不做特殊处理，就会出现对0求倒数从而导致程序运行出错。最后，由于0的0次方在数学上是没有意义的，因此无论是输出0还是1都是可以接受的。\n\n```\npublic double power(double base, int exponent) throws Exception {\n\t\tdouble result = 0.0;\n\t\tif (equal(base, 0.0) && exponent < 0) {\n\t\t\tthrow new Exception(\"0的负数次幂无意义\");\n\t\t}\n\t\tif (equal(exponent, 0)) {\n\t\t\treturn 1.0;\n\t\t}\n\t\tif (exponent < 0) {\n\t\t\tresult = powerWithExponent(1.0 / base, -exponent);\n\t\t} else {\n\t\t\tresult = powerWithExponent(base, exponent);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate double powerWithExponent(double base, int exponent) {\n\t\tdouble result = 1.0;\n\t\tfor (int i = 1; i <= exponent; i++) {\n\t\t\tresult = result * base;\n\t\t}\n\t\treturn result;\n\t}\n\n\t// 判断两个double型数据，计算机有误差\n\tprivate boolean equal(double num1, double num2) {\n\t\tif ((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001)) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n```\n\n一个细节，再判断底数base是不是等于0时，不能直接写base==0，这是因为在计算机内表示小数时(包括float和double型小数)都有误差。判断两个数是否相等，只能判断 它们之间的绝对值是不是在一个很小的范围内。如果两个数相差很小，就可以认为它们相等。\n\n还有更快的方法。\n\n如果我们的目标是求出一个数字的32次方，如果我们已经知道了它的16次方，那么只要在16次方的基础上再平方一次就好了，依此类推，我们求32次方只需要做5次乘法。\n\n我们可以利用如下公式：\n\n![](http://img.blog.csdn.net/20150731084039653)\n\n```\nprivate double powerWithExponent2(double base,int exponent){\n\t\tif(exponent == 0){\n\t\t\treturn 1;\n\t\t}\n\t\tif(exponent == 1){\n\t\t\treturn base;\n\t\t}\n\t\tdouble result = powerWithExponent2(base, exponent >> 1);\n\t\tresult *= result;\n\t\tif((exponent&0x1) == 1){\n\t\t\tresult *= base;\n\t\t}\n\t\treturn result;\n\t}\n```\n\n我们用右移运算代替除2，用位与运算符代替了求余运算符（%)来判断一个数是奇数还是偶数。位运算的效率比乘除法及求余运算的效率要高很多。\n"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/面试题12：打印1到最大的n位数.md",
    "content": "#\t面试题12 打印1到最大的n位数\n---\n\n题目：\n\n>题目：输入数字n，按顺序打印出从1最大的n位十进制数。比如输入3，则打印出1、2、3一直到最大的3位数即999。\n\n我们能写出如下代码：\n\n```\n\n```"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/面试题44：扑克牌的顺子.md",
    "content": "#面试题44:扑克牌的顺子\n---\n\n题目:\n\n```\n从扑克牌中随机抽 5 张牌,判断是不是顺子,即这 5 张牌是不是连续的。 2-10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大小王可以看成任意的 数字。\n```\n\n>解题思路：我们可以把5张牌看成是由5个数字组成的俄数组。大小王是特殊的数字，我们可以把它们都定义为0，这样就可以和其他的牌区分开来。\n\n\n>首先把数组排序，再统计数组中0的个数，最后统计排序之后的数组中相邻数字之间的空缺总数。如果空缺的总数小于或者等于0的个数，那么这个数组就是连续的，反之则不连续。如果数组中的非0数字重复出现，则该数组是不连续的。换成扑克牌的描述方式就是如果一幅牌里含有对子，则不可能是顺子。\n\n详细代码：\n\n```\nimport java.util.Arrays;\n\npublic class Solution {\n\n\tpublic boolean isContinuous(int[] number){\n\t\tif(number == null){\n\t\t\treturn false;\n\t\t}\n\t\tArrays.sort(number);\n\t\tint numberZero = 0;\n\t\tint numberGap = 0;\n\t\t//计算数组中0的个数\n\t\tfor(int i = 0;i < number.length&&number[i] == 0; i++){\n\t\t\tnumberZero++;\n\t\t}\n\t\t//统计数组中的间隔数目\n\t\tint small = numberZero;\n\t\tint big = small + 1;\n\t\twhile(big<number.length){\n\t\t\t//两个数相等，有对子，不可能是顺子\n\t\t\tif(number[small] == number[big]){\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnumberGap+= number[big] - number[small] - 1;\n\t\t\tsmall = big;\n\t\t\tbig++;\n\t\t}\n\t\treturn (numberGap>numberZero)?false:true;\n\t\t\n\t}\n}\n\n```\n"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/面试题45：圆圈中最后剩下的数字.md",
    "content": "#面试题45 圆圈中最后剩下的数字\n---\n\n题目\n\n>0,1,...,n-1这n个数字排成一个圆圈，从数字0开始每次从这个圆圈里删除第m个数字。求这个圆圈里剩下的最后一个数字。\n\n\n解法:\n\n>可以创建一个总共有n个结点的环形链表，然后每次在这个链表中删除第m个结点。我们发现使用环形链表里重复遍历很多遍。重复遍历当然对时间效率有负面的影响。这种方法每删除一个数字需要m步运算，总共有n个数字，因此总的时间复杂度为O(mn)。同时这种思路还需要一个辅助的链表来模拟圆圈，其空间复杂度O(n）。接下来我们试着找到每次被删除的数字有哪些规律，希望能够找到更加高效的算法。\n\n\n>首先我们定义一个关于n和m的方程f(n,m)，表示每次在n个数字0，1，。。。n-1中每次删除第m个数字最后剩下的数字。\n\n>在这n个数字中，第一个被删除的数字是（m-1)%n.为了简单起见，我们把（m-1)%n记为k，那么删除k之后剩下的n-1个数字为0，1，。。。。k-1，k+1,.....n-1。并且下一次删除从数字k+1,......n-1,0,1,....k-1。该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样（最初的序列是从0开始的连续序列），因此该函数不同于前面的函数，即为f'(n-1,m)。最初序列最后剩下的数字f(n,m)一定是删除一个数字之后的序列最后剩下的数字，即f(n,m)=f'(n-1,m).\n\n>接下来我么把剩下的这n-1个数字的序列k+1,....n-1，0，1，,,,,,,k-1做一个映射，映射的结果是形成一个从0到n-2的序列\n\n```\nk+1        ------>     0\nk+2      --------->    1\n。。。。\nn-1      -----    > n-k-2\n0         ------->    n-k-1\n1       --------->    n-k\n.....\nk-1   --------->    n-k\n```\n\n>我们把映射定义为p，则p(x) = (x-k-1)%n。它表示如果映射前的数字是x,那么映射后的数字是（x-k-1)%n.该映射的逆映射是p-1(x)= (x+k+1)%n.\n\n>由于映射之后的序列和最初的序列具有同样的形式，即都是从0开始的连续序列，因此仍然可以用函数f来表示，记为f(n-1,m).根据我们的映射规则，映射之前的序列中最后剩下的数字f'(n-1,m) = p-1[(n-1,m)] = [f(n-1,m)+k+1]%n ,把k= (m-1)%n代入f(n,m) = f'(n-1,m) =[f(n-1,m)+m]%n.\n\n>经过上面的复杂的分析，我们终于找到一个递归的公示。要得到n个数字的序列中最后剩下的数字，只需要得到n-1个数字的序列和最后剩下的数字，并以此类推。当n-1时，也就是序列中开始只有一个数字0，那么很显然最后剩下的数字就是0.我们把这种关系表示为：\n\n![这里写图片描述](http://img.blog.csdn.net/20160705171823279)\n\n代码如下：\n\n```\npublic static int lastRemaining(int n, int m){\n\t\tif(n < 1 || m < 1){\n\t\t\treturn -1;\n\t\t}\n\t\tint last = 0;\n\t\tfor(int i = 2; i <= n; i++){\n\t\t\tlast = (last + m) % i;\n\t\t}\n\t\treturn last;\n\t}\n```"
  },
  {
    "path": "Part3/Algorithm/剑指Offer/面试题6：重建二叉树.md",
    "content": "#面试题6:重建二叉树\n---\n\n题目：\n\n```\n输入二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设前序遍历和中序遍历结果中都不包含重复的数字,例如输入的前序遍历序列 {1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}重建出如图所示的二叉 树。\n```\n\n解题思路：\n\n```\n前序遍历第一个结点是父结点，中序遍历如果遍历到父结点，那么父结点前面的结点是左子树的结点，后边的结点的右子树的结点，这样我们可以找到左、右子树的前序遍历和中序遍历，我们可以用同样的方法去构建左右子树，可以用递归完成。\n```\n\n\n代码：\n\n\n```\npublic class BinaryTreeNode {\n\n\tpublic static int value;\n\tpublic BinaryTreeNode leftNode;\n\tpublic BinaryTreeNode rightNode;\n}\n\n```\n\n```\npublic class Solution {\n\n\tpublic static BinaryTreeNode constructCore(int[] preorder, int[] inorder) throws Exception {\n\t\tif (preorder == null || inorder == null) {\n\t\t\treturn null;\n\t\t}\n\t\tif (preorder.length != inorder.length) {\n\t\t\tthrow new Exception(\"长度不一样，非法的输入\");\n\t\t}\n\t\tBinaryTreeNode root = new BinaryTreeNode();\n\t\tfor (int i = 0; i < inorder.length; i++) {\n\t\t\tif (inorder[i] == preorder[0]) {\n\t\t\t\troot.value = inorder[i];\n\t\t\t\tSystem.out.println(root.value);\n\t\t\t\troot.leftNode = constructCore(Arrays.copyOfRange(preorder, 1, i + 1),\n\t\t\t\t\t\tArrays.copyOfRange(inorder, 0, i));\n\t\t\t\troot.rightNode = constructCore(Arrays.copyOfRange(preorder, i + 1, preorder.length),\n\t\t\t\t\t\tArrays.copyOfRange(inorder, i + 1, inorder.length));\n\t\t\t}\n\t\t}\n\t\treturn root;\n\n\t}\n}\n```"
  },
  {
    "path": "Part3/Algorithm/程序员代码面试指南(左程云)/1.设计一个有getMin功能的栈.md",
    "content": "#设计一个有getMin功能的栈\n---\n\n实现一个特殊的栈，在实现栈的基本功能的基础上，在实现返回栈中最小元素的操作。\n\n要求：\n\n1. pop、push、getMin操作的时间复杂度都是O(1)\n2. 设计的栈类型可以使用现成的栈结构\n\n解题：\n\n\n```\npackage chapter01_stackandqueue;\n\nimport java.util.Stack;\n\n/**\n * \n * 实现一个特殊的栈，在实现栈的基本功能的基础上，在实现返回栈中最小元素的操作。 要求： 1. pop、push、getMin操作的时间复杂度都是O(1)\n * 2. 设计的栈类型可以使用现成的栈结构\n * \n * @author dream\n *\n */\npublic class Problem01_GetMinStack {\n\n\tpublic static class MyStack1 {\n\n\t\t/**\n\t\t * 两个栈，其中stacMin负责将最小值放在栈顶，stackData通过获取stackMin的peek()函数来获取到栈中的最小值\n\t\t */\n\t\tprivate Stack<Integer> stackData;\n\t\tprivate Stack<Integer> stackMin;\n\n\t\t/**\n\t\t * 在构造函数里面初始化两个栈\n\t\t */\n\t\tpublic MyStack1() {\n\t\t\tstackData = new Stack<Integer>();\n\t\t\tstackMin = new Stack<Integer>();\n\t\t}\n\n\t\t/**\n\t\t * 该函数是stackData弹出栈顶数据，如果弹出的数据恰好等于stackMin的数据，那么stackMin也弹出\n\t\t * @return\n\t\t */\n\t\tpublic Integer pop() {\n\t\t\tInteger num = (Integer) stackData.pop();\n\t\t\tif (num == getmin()) {\n\t\t\t\treturn (Integer) stackMin.pop();\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\t/**\n\t\t * 该函数是先判断stackMin是否为空，如果为空，就push新的数据，如果这个数小于stackMin中的栈顶元素，那么stackMin需要push新的数，不管怎么样\n\t\t * stackData都需要push新的数据\n\t\t * @param value\n\t\t */\n\t\tpublic void push(Integer value) {\n\t\t\tif (stackMin.isEmpty()) {\n\t\t\t\tstackMin.push(value);\n\t\t\t}\n\n\t\t\telse if (value < getmin()) {\n\t\t\t\tstackMin.push(value);\n\t\t\t}\n\t\t\tstackData.push(value);\n\t\t}\n\n\t\t/**\n\t\t * 该函数是当stackMin为空的话第一次也得push到stackMin的栈中，返回stackMin的栈顶元素\n\t\t * @return\n\t\t */\n\t\tpublic Integer getmin() {\n\t\t\tif (stackMin == null) {\n\t\t\t\tthrow new RuntimeException(\"stackMin is empty\");\n\t\t\t}\n\t\t\treturn (Integer) stackMin.peek();\n\n\t\t}\n\t}\n\n\tpublic static void main(String[] args) throws Exception {\n\t\t/**\n\t\t * 要注意要将MyStack1声明成静态的，静态内部类不持有外部类的引用\n\t\t */\n\t\tMyStack1 stack1 = new MyStack1();\n\t\tstack1.push(3);\n\t\tSystem.out.println(stack1.getmin());\n\t\tstack1.push(4);\n\t\tSystem.out.println(stack1.getmin());\n\t\tstack1.push(1);\n\t\tSystem.out.println(stack1.getmin());\n\t\tSystem.out.println(stack1.pop());\n\t\tSystem.out.println(stack1.getmin());\n\n\t\tSystem.out.println(\"=============\");\n\t}\n\n}\n\n```"
  },
  {
    "path": "Part3/Algorithm/程序员代码面试指南(左程云)/2.由两个栈组成的队列.md",
    "content": "#2.由两个栈组成的队列\n---\n\n题目：\n\n```\n编写一个类，用两个栈实现队列，支持队列的基本操作(add、poll、peek)。\n```\n\n解题：\n\n```\n/**\n * \n * 编写一个类，用两个栈实现队列，支持队列的基本操作(add、poll、peek)。\n * \n * @author dream\n *\n */\npublic class Problem02_TwoStacksImplementQueue {\n\n\tpublic static class myQueue{\n\t\t\n\t\tStack<Integer> stack1;\n\t\tStack<Integer> stack2;\n\t\t\n\t\tpublic myQueue() {\n\t\t\tstack1 = new Stack<Integer>();\n\t\t\tstack2 = new Stack<Integer>();\n\t\t}\n\t\t/**\n\t\t * add只负责往stack1里面添加数据\n\t\t * @param newNum\n\t\t */\n\t\tpublic void add(Integer newNum){\n\t\t\tstack1.push(newNum);\n\t\t}\n\t\t\n\t\t/**\n\t\t * 这里要注意两点：\n\t\t * 1.stack1要一次性压入stack2\n\t\t * 2.stack2不为空，stack1绝不能向stack2压入数据\n\t\t * @return\n\t\t */\n\t\tpublic Integer poll(){\n\t\t\tif(stack1.isEmpty() && stack2.isEmpty()){\n\t\t\t\tthrow new RuntimeException(\"Queue is Empty\");\n\t\t\t}else if(stack2.isEmpty()){\n\t\t\t\twhile (!stack1.isEmpty()) {\n\t\t\t\t\tstack2.push(stack1.pop());\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn stack2.pop();\n\t\t}\n\t\t\n\t\tpublic Integer peek(){\n\t\t\tif(stack1.isEmpty() && stack2.isEmpty()){\n\t\t\t\tthrow new RuntimeException(\"Queue is Empty\");\n\t\t\t}else if(stack2.isEmpty()){\n\t\t\t\twhile (!stack1.isEmpty()) {\n\t\t\t\t\tstack2.push(stack1.pop());\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn stack2.peek();\n\t\t}\n\t}\n\t\n\tpublic static void main(String[] args) {\n\t\tmyQueue mQueue = new myQueue();\n\t\tmQueue.add(1);\n\t\tmQueue.add(2);\n\t\tmQueue.add(3);\n\t\tSystem.out.println(mQueue.peek());\n\t\tSystem.out.println(mQueue.poll());\n\t\tSystem.out.println(mQueue.peek());\n\t\tSystem.out.println(mQueue.poll());\n\t\tSystem.out.println(mQueue.peek());\n\t\tSystem.out.println(mQueue.poll());\n\t\t\n\t}\n}\n\n```"
  },
  {
    "path": "Part3/Algorithm/程序员代码面试指南(左程云)/3.如何仅用递归函数和栈操作逆序一个栈.md",
    "content": "#3.如何仅用递归函数和栈操作逆序一个栈\n---\n\n题目：\n\n```\n一个栈一次压入了1、2、3、4、5，那么从栈顶到栈底分别为5、4、3、2、1.将这个栈转置后，从栈顶到栈底为1、2、3、4、5，也就是实现栈中元素的逆序，但是只能用递归函数来实现，不能用其他数据结构。\n```\n\n解题：\n\n```\n/**\n * 一个栈一次压入了1、2、3、4、5，那么从栈顶到栈底分别为5、4、3、2、1.将这个栈转置后，\n * 从栈顶到栈底为1、2、3、4、5，\n * 也就是实现栈中元素的逆序，但是只能用递归函数来实现，不能用其他数据结构。\n * @author dream\n *\n */\npublic class Problem03_ReverseStackUsingRecursive {\n\n\tpublic static void reverse(Stack<Integer> stack) {\n\t\tif (stack.isEmpty()) {\n\t\t\treturn;\n\t\t}\n\t\tint i = getAndRemoveLastElement(stack);\n\t\treverse(stack);\t\t\n\t\tstack.push(i);\n\t}\n\n\t/**\n\t * 这个函数就是删除栈底元素并返回这个元素\n\t * @param stack\n\t * @return\n\t */\n\tpublic static int getAndRemoveLastElement(Stack<Integer> stack) {\n\t\tint result = stack.pop();\n\t\tif (stack.isEmpty()) {\n\t\t\treturn result;\n\t\t} else {\n\t\t\tint last = getAndRemoveLastElement(stack);\n\t\t\tstack.push(result);\n\t\t\treturn last;\n\t\t}\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tStack<Integer> test = new Stack<Integer>();\n\t\ttest.push(1);\n\t\ttest.push(2);\n\t\ttest.push(3);\n\t\ttest.push(4);\n\t\ttest.push(5);\n\t\treverse(test);\n\t\twhile (!test.isEmpty()) {\n\t\t\tSystem.out.println(test.pop());\n\t\t}\n\n\t}\n\t\n}\n\n```"
  },
  {
    "path": "Part3/DataStructure/数据结构(Java).md",
    "content": "#数据结构和算法：\n---\n(推荐书籍：剑指offer、编程之美、Cracking、程序员代码面试指南，特别是这四本书上的重复题)\n\n**数组与链表**\n\n####数组：\n\n创建\n\n```\nint c[] = {2,3,6,10,99};\nint[] d = new int[10];\n```\n\n\n\n```\n\t/**\n\t * 数组检索\n\t * @param args\n\t */\n\tpublic static void main(String[] args) {\n\t\tString name[];\n\t\t\n\t\tname = new String[5];\n\t\tname[0] = \"egg\";\n\t\tname[1] = \"erqing\";\n\t\tname[2] = \"baby\";\n\t\t\n\t\tfor(int i = 0; i < name.length; i++){\n\t\t\tSystem.out.println(name[i]);\n\t\t}\n\t}\n```\n```\n/**\n\t * 插入\n\t * \n\t * @param old\n\t * @param value\n\t * @param index\n\t * @return\n\t */\n\tpublic static int[] insert(int[] old, int value, int index) {  \n        for (int k = old.length - 1; k > index; k--)  \n            old[k] = old[k - 1];  \n        old[index] = value;  \n        return old;  \n    }  \n\n\t/**\n\t * 遍历\n\t * \n\t * @param data\n\t */\n\tpublic static void traverse(int data[]) {\n\t\tfor (int j = 0; j < data.length; j++) {\n\t\t\tSystem.out.println(data[j] + \" \");\n\t\t}\n\t}\n\n\t/**\n\t * 删除\n\t * \n\t * @param old\n\t * @param index\n\t * @return\n\t */\n\tpublic static int[] delete(int[] old, int index) {\n\t\tfor (int h = index; h < old.length - 1; h++) {\n\t\t\told[h] = old[h + 1];\n\t\t}\n\t\told[old.length - 1] = 0;\n\t\treturn old;\n\t}\n```\n\nTips:数组中删除和增加元素的原理：增加元素，需要将index后面的依次往后移动，然后将值插入index位置，删除则是将后面的值一次向前移动。\n\n数组表示相同类型的一类数据的集合，下标从0开始。\n\n\n####单链表：\n\n![](http://img.my.csdn.net/uploads/201304/13/1365855052_1221.jpg)\n\n\n\n**队列和栈，出栈与入栈。**\n\n**链表的删除、插入、反向。**\n\n**字符串操作。**\n\n**Hash表的hash函数，冲突解决方法有哪些。**\n\n**各种排序：冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定。**\n\n**快排的partition函数与归并的Merge函数。**\n\n**对冒泡与快排的改进。**\n\n**二分查找，与变种二分查找。**\n\n**二叉树、B+树、AVL树、红黑树、哈夫曼树。**\n\n**二叉树的前中后续遍历：递归与非递归写法，层序遍历算法。**\n\n二叉树的前序遍历：\n\n\n\n**图的BFS与DFS算法，最小生成树prim算法与最短路径Dijkstra算法。**\n\n**KMP算法。**\n\n**排列组合问题。**\n\n**动态规划、贪心算法、分治算法。（一般不会问到）**\n\n**大数据处理：类似10亿条数据找出最大的1000个数.........等等**"
  },
  {
    "path": "Part3/DataStructure/数组.md",
    "content": "#数组\n---\n\n```\n/**\n * 普通数组的Java代码\n * @author dream\n *\n */\npublic class GeneralArray {\n\n\tprivate int[] a;\n\tprivate int size;   //数组的大小\n\tprivate int nElem;   //数组中有多少项\n\t\n\tpublic GeneralArray(int max){\n\t\tthis.a = new int[max];\n\t\tthis.size = max;\n\t\tthis.nElem = 0;\n\t}\n\t\n\tpublic boolean find(int searchNum){   //查找某个值\n\t\tint j;\n\t\tfor(j = 0; j < nElem; j++){\n\t\t\tif(a[j] == searchNum){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif(j == nElem){\n\t\t\treturn false;\n\t\t}else {\n\t\t\treturn true;\n\t\t}\n\t}\n\t\n\t\n\tpublic boolean insert(int value){   //插入某个值\n\t\tif(nElem == size){\n\t\t\tSystem.out.println(\"数组已满\");\n\t\t\treturn false;\n\t\t}\n\t\ta[nElem] = value;\n\t\tnElem++;\n\t\treturn true;\n\t}\n\t\n\tpublic boolean delete(int value){   //删除某个值\n\t\tint j;\n\t\tfor(j = 0; j < nElem; j++){\n\t\t\tif(a[j] == value){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif(j == nElem){\n\t\t\treturn false;\n\t\t}\n\t\tif(nElem == size){\n\t\t\tfor(int k = j; k < nElem - 1; k++){\n\t\t\t\ta[k] = a[k+1];\n\t\t\t}\n\t\t}else {\n\t\t\tfor(int k = j; k < nElem; k++){\n\t\t\t\ta[k] = a[k+1];\n\t\t\t}\n\t\t}\n\t\tnElem--;\n\t\treturn true;\n\t}\n\t\n\tpublic void display(){   //打印整个数组\n\t\tfor(int i = 0; i < nElem; i++){\n\t\t\tSystem.out.println(a[i] + \" \");\n\t\t}\n\t\tSystem.out.println(\"\");\n\t}\n}\n\n```\n\n```\n/**\n * 有序数组的Java代码\n * @author dream\n *\n */\n\n/**\n * 对于数组这种数据结构，\n * 线性查找的话，时间复杂度为O(N)，\n * 二分查找的话时间为O(longN)，\n * 无序数组插入的时间复杂度为O(1)，\n * 有序数组插入的时间复杂度为O(N)，\n * 删除操作的时间复杂度均为O(N)。\n * @author dream\n *\n */\npublic class OrderedArray {\n\n\tprivate long[] a;\n\tprivate int size;   //数组的大小\n\tprivate int nElem;  //数组中有多少项\n\t\n\tpublic OrderedArray(int max){   //初始化数组\n\t\tthis.a = new long[max];\n\t\tthis.size = max;\n\t\tthis.nElem = 0;\n\t}\n\t\n\tpublic int size(){   //返回数组实际有多少值\n\t\treturn this.nElem;\n\t}\n\t\n\t/**\n\t * 二分查找\n\t * @param searchNum\n\t * @return\n\t */\n\tpublic int find(long searchNum){\n\t\tint lower = 0;\n\t\tint upper = nElem - 1;\n\t\tint curr;\n\t\twhile (true) {\n\t\t\tcurr = (lower + upper) / 2;\n\t\t\tif(a[curr] == searchNum){\n\t\t\t\treturn curr;\n\t\t\t}else if(lower > upper){\n\t\t\t\treturn -1;\n\t\t\t}else {\n\t\t\t\tif(a[curr] < searchNum){\n\t\t\t\t\tlower = curr + 1;\n\t\t\t\t}else {\n\t\t\t\t\tupper = curr - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\t\n\tpublic boolean insert(long value){   //插入某个值\n\t\tif(nElem == size){\n\t\t\tSystem.out.println(\"数组已满!\");\n\t\t\treturn false;\n\t\t}\n\t\tint j;\n\t\tfor(j = 0; j < nElem; j++){\n\t\t\tif(a[j] > value){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor(int k = nElem; k > j; k++){\n\t\t\ta[k] = a[k-1];\n\t\t}\n\t\ta[j] = value;\n\t\tnElem++;\n\t\treturn true;\n\t}\n\t\n\t\n\t\n\tpublic boolean delete(long value){   //删除某个值\n\t\tint j = find(value);\n\t\tif(j == -1){\n\t\t\tSystem.out.println(\"没有该元素!\");\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif(nElem == size){\n\t\t\tfor(int k = j; k < nElem - 1; k++){\n\t\t\t\ta[k] = a[k+1];\n\t\t\t}\n\t\t\ta[nElem-1] = 0;\n\t\t}else {\n\t\t\tfor(int k = j; k < nElem; k++){\n\t\t\t\ta[k] = a[k+1];\n\t\t\t}\n\t\t}\n\t\tnElem--;\n\t\treturn true;\n\t}\n\t\n\t\n\tpublic void display(){   //打印整个数组\n\t\tfor(int i = 0; i < nElem; i++){\n\t\t\tSystem.out.println(a[i] + \" \");\n\t\t}\n\t\tSystem.out.println(\"\");\n\t}\n}\n\n```"
  },
  {
    "path": "Part3/DataStructure/栈和队列.md",
    "content": "#栈和队列\n---\n##栈\n---\n\n栈只允许访问一个数据项：即最后插入的数据。溢出这个数据才能访问倒数第二个插入的数据项。它是一种\"后进先出\"的数据结构。\n\n栈最基本的操作是出栈(Pop)、入栈(Push)，还有其他扩展操作，如查看栈顶元素，判断栈是否为空、是否已满，读取栈的大小等。\n\n```\n/**\n * 栈是先进后出\n * 只能访问栈顶的数据\n * @author dream\n *\n */\n\n/**\n * 基于数组来实现栈的基本操作\n * 数据项入栈和出栈的时间复杂度均为O(1)\n * @author dream\n *\n */\npublic class ArrayStack {\n\n\tprivate long[] a;\n\tprivate int size;   //栈数组的大小\n\tprivate int top;   //栈顶\n\t\n\tpublic ArrayStack(int maxSize){\n\t\tthis.size = maxSize;\n\t\tthis.a = new long[size];\n\t\tthis.top = -1;   //表示空栈\n\t}\n\t\n\tpublic void push(long value){   //入栈\n\t\tif(isFull()){\n\t\t\tSystem.out.println(\"栈已满!\");\n\t\t\treturn;\n\t\t}\n\t\ta[++top] = value;\n\t}\n\t\n\tpublic long peek(){   //返回栈顶内容，但不删除\n\t\tif(isEmpty()){\n\t\t\tSystem.out.println(\"栈中没有数据\");\n\t\t\treturn 0;\n\t\t}\n\t\treturn a[top];\n\t}\n\t\n\t\n\tpublic long pop(){   //弹出栈顶内容\n\t\tif(isEmpty()){\n\t\t\tSystem.out.println(\"栈中没有数据!\");\n\t\t\treturn 0;\n\t\t}\n\t\treturn a[top--];\n\t}\n\t\n\tpublic int size(){\n\t\treturn top + 1;\n\t}\n\t\n\t/**\n\t * 判断是否满了\n\t * @return\n\t */\n\tpublic boolean isFull(){\n\t\treturn (top == size - 1);\n\t}\n\t\n\t/**\n\t * 是否为空\n\t * @return\n\t */\n\tpublic boolean isEmpty(){\n\t\treturn (top == -1);\n\t}\n\t\n\t\n\tpublic void display(){\n\t\tfor (int i = top; i >= 0; i--) {\n\t\t\tSystem.out.println(a[i] + \" \");\n\t\t}\n\t\tSystem.out.println(\"\");\n\t}\n\t\n}\n\n```\n\n##队列\n---\n\n依然使用数组作为底层容器来实现一个队列的封装\n\n```\n/**\n * 队列也可以用数组来实现，不过这里有个问题，当数组下标满了后就不能再添加了，\n * 但是数组前面由于已经删除队列头的数据了，导致空。所以队列我们可以用循环数组来实现，\n * @author dream\n *\n */\npublic class RoundQueue {\n\n\tprivate long[] a;\n\tprivate int size;     //数组大小\n\tprivate int nItems;    //实际存储数量\n\tprivate int front;     //头\n\tprivate int rear;       //尾\n\t\n\tpublic RoundQueue(int maxSize){\n\t\tthis.size = maxSize;\n\t\ta = new long[size];\n\t\tfront = 0;\n\t\trear = -1;\n\t\tnItems = 0;\n\t}\n\t\n\tpublic void insert(long value){\n\t\tif(isFull()){\n\t\t\tSystem.out.println(\"队列已满\");\n\t\t\treturn;\n\t\t}\n\t\trear = ++rear % size;\n\t\ta[rear] = value;  //尾指针满了就循环到0处，这句相当于下面注释内容\n\t\tnItems++;\n\t}\n\t\n\tpublic long remove(){\n\t\tif(isEmpty()){\n\t\t\tSystem.out.println(\"队列为空!\");\n\t\t\treturn 0;\n\t\t}\n\t\tnItems--;\n\t\tfront = front % size;\n\t\treturn a[front++];\n\t}\n\t\n\tpublic void display(){\n\t\tif(isEmpty()){\n\t\t\tSystem.out.println(\"队列为空!\");\n\t\t\treturn;\n\t\t}\n\t\tint item = front;\n\t\tfor(int i = 0;i < nItems; i++){\n\t\t\tSystem.out.println(a[item++ % size] + \" \");\n\t\t}\n\t\tSystem.out.println(\"\");\n\t}\n\t\n\tpublic long peek(){\n\t\tif(isEmpty()){\n\t\t\tSystem.out.println(\"队列为空！\");\n\t\t\treturn 0;\n\t\t}\n\t\treturn a[front];\n\t}\n\t\n\tpublic boolean isFull(){\n\t\treturn (nItems == size);\n\t}\n\t\n\tpublic boolean isEmpty(){\n\t\treturn (nItems == 0);\n\t}\n\t\n\tpublic int size(){\n\t\treturn nItems;\n\t}\n\t\n\t\n}\n\n```\n\n和栈一样，队列中插入数据项和删除数据项的时间复杂度均为O(1)\n\n 还有个优先级队列，优先级队列是比栈和队列更专用的数据结构。优先级队列与上面普通的队列相比，主要区别在于队列中的元素是有序的，关键字最小（或者最大）的数据项总在队头。数据项插入的时候会按照顺序插入到合适的位置以确保队列的顺序。优先级队列的内部实现可以用数组或者一种特别的树——堆来实现。这里用数组实现优先级队列。\n \n ```\n \npublic class PriorityQueue {\n\n\tprivate long[] a;\n\tprivate int size;\n\tprivate int nItems;  //元素个数\n\t\n\tpublic PriorityQueue(int maxSize){\n\t\tsize = maxSize;\n\t\tnItems = 0;\n\t\ta = new long[size];\n\t}\n\t\n\tpublic void insert(long value){\n\t\tif(isFull()){\n\t\t\tSystem.out.println(\"队列已满！\");\n\t\t\treturn;\n\t\t}\n\t\tint j;\n\t\tif(nItems == 0){  //空队列直接添加\n\t\t\ta[nItems++] = value;\n\t\t}else {\n\t\t\t//将数组中的数字依照下标按照从大到小排列\n\t\t\tfor(j=nItems-1; j>=0; j--){\n\t\t\t\tif(value > a[j]){  \n                    a[j+1] = a[j];  \n                }  \n                else {  \n                    break;  \n                } \n\t\t\t}\n\t\t\ta[j+1] = value;\n\t\t\tnItems++;\n\t\t}\n\t}\n\t\n\tpublic long remove(){\n\t\tif(isFull()){\n\t\t\tSystem.out.println(\"队列为空!\");\n\t\t\treturn 0;\n\t\t}\n\t\treturn a[--nItems];\n\t}\n\t\n\tpublic long peekMin(){\n\t\treturn a[nItems - 1];\n\t}\n\t\n\tpublic boolean isFull(){\n\t\treturn (nItems == size);\n\t}\n\t\n\tpublic boolean isEmpty(){\n\t\treturn (nItems == 0);\n\t}\n\t\n\tpublic int size(){\n\t\treturn nItems;\n\t}\n\t\n\tpublic void display(){\n\t\tfor(int i = nItems - 1;i >= 0; i--){\n\t\t\tSystem.out.println(a[i] + \" \");\n\t\t}\n\t\tSystem.out.println(\" \");\n\t}\n}\n\n ```\n \n 优先级队列中，插入操作需要O(N)的时间，而删除操作则需要O(1)的时间。\n \n \n \n\n"
  },
  {
    "path": "Part3/DataStructure/递归和非递归方式实现二叉树先、中、后序遍历.md",
    "content": "###递归和非递归方式实现二叉树先、中、后序遍历\n\n先序遍历顺序为根、左、右，中序遍历顺序为左、根、右，后序遍历是左、右、根。\n\n递归实现：\n\n\n```\npublic class Node {\n\n\tpublic int value;\n\tpublic Node left;\n\tpublic Node right;\n\t\n\tpublic Node(int data){\n\t\tthis.value = data;\n\t}\n}\n```\n\n```\n\t/**\n\t * 前序遍历\n\t * @param head\n\t */\n\tpublic void preOrderRecur(Node head){\n\t\tif(head == null){\n\t\t\treturn;\n\t\t}\n\t\tSystem.out.println(head.value + \" \");\n\t\tpreOrderRecur(head.left);\n\t\tpreOrderRecur(head.right);\n\t}\n\t\n\t/**\n\t * 中序遍历\n\t * @param head\n\t */\n\tpublic void inOderRecur(Node head){\n\t\tif(head == null){\n\t\t\treturn;\n\t\t}\n\t\tinOderRecur(head.left);\n\t\tSystem.out.println(head.value + \" \");\n\t\tinOderRecur(head.right);\n\t}\n\t\n\t/**\n\t * 后序遍历\n\t * @param head\n\t */\n\tpublic void posOrderRecur(Node head){\n\t\tif(head == null){\n\t\t\treturn;\n\t\t}\n\t\tposOrderRecur(head.left);\n\t\tposOrderRecur(head.right);\n\t\tSystem.out.println(head.value + \"\");\n\t}\n```\n\n\n\n非递归遍历\n\n"
  },
  {
    "path": "Part4/Network/Http协议.md",
    "content": "#Http协议\n---\n* 默认端口：80\n\n##Http协议的主要特点\n---\n1. 支持客户／服务器模式\n2. 简单快速：客户向服务端请求服务时，只需传送请求方式和路径。\n3. 灵活：允许传输任意类型的数据对象。由Content-Type加以标记。\n4. 无连接：每次响应一个请求，响应完成以后就断开连接。\n5. 无状态：服务器不保存浏览器的任何信息。每次提交的请求之间没有关联。\n\n###非持续性和持续性\n---\n* HTTP1.0默认非持续性；HTTP1.1默认持续性\n\n####持续性\n浏览器和服务器建立TCP连接后，可以请求多个对象\n####非持续性\n浏览器和服务器建立TCP连接后，只能请求一个对象\n\n###非流水线和流水线\n---\n类似于组成里面的流水操作\n\n* 流水线：不必等到收到服务器的回应就发送下一个报文。\n* 非流水线：发出一个报文，等到响应，再发下一个报文。类似TCP。\n\n####POST和GET的区别\n\n| Post一般用于更新或者添加资源信息       | Get一般用于查询操作，而且应该是安全和幂等的           |\n| ------------- |:-------------:|\n| Post更加安全      | Get会把请求的信息放到URL的后面 |\n| Post传输量一般无大小限制     | Get不能大于2KB      |\n| Post执行效率低 | Get执行效率略高      |\n\n\n####为什么POST效率低，Get效率高\n---\n* Get将参数拼成URL,放到header消息头里传递\n* Post直接以键值对的形式放到消息体中传递。\n* 但两者的效率差距很小很小\n\n\n##Https\n---\n* 端口号是443\n* 是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议。\n\n\n\n"
  },
  {
    "path": "Part4/Network/Socket.md",
    "content": "#Socket\n---\n###使用TCP\n---\n客户端\n\n```\nSocket socket = new Socket(\"ip\", 端口);\n\nInputStream is = socket.getInputStream();\nDataInputStream dis = new DataInputStream(is);\n\nOutputStream os = socket.getOutputStream();\nDataInputStream dos = new DataOutputStream(os);\n```\n\n服务器端\n\n```\nServerSocket serverSocket = new ServerSocket(端口);\nSocket socket = serverSocket.accept();\n//获取流的方式与客户端一样\n```\n\n读取输入流\n\n```\nbyte[] buffer = new byte[1024]; \ndo{ \n\tint count = is.read(buffer); \n\tif(count <= 0){ break; }\n\telse{ \n\t// 对buffer保存或者做些其他操作 \n\t\t} \n\t}\nwhile(true);\n\n\n```\n\n\n使用UDP\n---\n客户端和服务器端一样的\n\n```\nDatagramSocket socket = new DatagramSocket(端口);\nInetAddress serverAddress = InetAddress.getbyName(\"ip\");\n//发送\nDatagramPackage packet = new DatagramPacket(buffer, length, host, port);\nsocket.send(packet);\n//接收\nbyte[] buf = new byte[1024];\nDatagramPacket packet = new DatagramPacket(buf, 1024);\nSocket.receive(packet);\n```"
  },
  {
    "path": "Part4/Network/TCP与UDP.md",
    "content": "#TCP与UDP\n---\n\n**面向报文的传输方式**是应用层交给UDP多长的报文，UDP就照样发送，即一次发送一个报文。因此，应用程序必须选择合适大小的报文。若报文太长，则IP层需要分片，降低效率。若太短，会是IP太小。UDP对应用层交下来的报文，既不合并，也不拆分，而是保留这些报文的边界。这也就是说，应用层交给UDP多长的报文，UDP就照样发送，即一次发送一个报文。\n**面向字节流**的话，虽然应用程序和TCP的交互是一次一个数据块（大小不等），但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲，当应用程序传送的数据块太长，TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节，TCP也可以等待积累有足够多的字节后再构成报文段发送出去。\n\nTCP协议\n-----\n\n\n----------\n\n - Transmission Control Protocol，传输控制协议\n - 面向连接的协议\n - 需要三次握手建立连接\n - 需要四次挥手断开连接\n - TCP报头最小长度：20字节\n\n\n三次握手的过程：\n--------\n\n\n----------\n\n 1. 客户端发送：SYN = 1, SEQ = X, 端口号\n 2. 服务器回复：SYN = 1, ACK = X + 1, SEQ = Y\n 3. 客户端发送：ACK = Y + 1, SEQ = X + 1\n\n> 确认应答信号ACK = 收到的SEQ + 1。\n连接建立中，同步信号SYN始终为1。连接建立后，同步信号SYN=0。\n\n四次挥手过程\n------\n\n----------\n\n\n 1. A向B提出停止连接请求，FIN = 1\n 2. B收到，ACK = 1\n 3. B向A提出停止连接请求，FIN = 1\n 4. A收到，ACK = 1\n\n**优点：** \n\n\n----------\n\n - 可靠，稳定\n\t    1、传递数据前，会有三次握手建立连接\n        2、传递数据时，有确认、窗口、重传、拥塞控制\n        3、传递数据后，会断开连接节省系统资源\n\n**缺点：**\n\n----------\n\n - 传输慢，效率低，占用系统资源高\n 1、传递数据前，建立连接需要耗时\n 2、传递数据时，确认、重传、拥塞等会消耗大量时间以及CPU和内存等硬件资源\n \n - 易被攻击\n 1、因为有确认机制，三次握手等机制，容易被人利用，实现DOS 、DDOS攻击\n\n**如何保证接收的顺序性：**\n\n----------\nTCP协议使用SEQ和ACK机制保证了顺序性\nTCP的每个报文都是有序号的。确认应答信号ACK=收到的SEQ+1\n\n\n\n\n\nUDP协议\n-----\n\n\n----------\n\n - User Data Protocol，用户数据包协议\n - 面向无连接的协议\n - UDP报头只有8字节\n\n**简介：**\n\n----------\n\n - 传输数据之前源端和终端不建立连接，当它想传送时就简单地去抓取来自应用程序的数据，并尽可能快的把它扔到网络上\n - 在发送端，UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制\n - 在接收端，UDP把每个消息段放在队列中，应用程序每次从队列中读一个消息段\n - 由于传输数据不建立连接，因此也就不需要维护连接状态，包括收发状态等，因此一台服务机可同时向多个客户机传输相同的消息\n - UDP信息包的标题很短，只有8个字节，相对于TCP的20个字节信息包的额外开销很小\n - 吞吐量不受拥挤控制算法的调节，只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制\n - UDP使用尽最大努力交付，即不保证可靠交付，因此主机不需要维持复杂的链接状态表。\n - UDP是面向报文的。发送方的UDP对应用程序交下来的报文，在添加首部后就向下交付给IP层。既不拆分，也不合并，而是保留这些报文的边界，因此，应用程序需要选择合适的报文大小。\n\n> 使用“ping”命令来测试两台主机之间TCP/IP通信是否正常，其实“ping”命令的原理就是向对方主机发送UDP数据包，然后对方主机确认收到数据包，如果数据包是否到达的消息及时反馈回来，那么网络就是通的。\n\n**优点：**\n\n\n----------\n\n\n - 传输速率快\n 1、传输数据前，不需要像TCP一样建立连接\n 2、传输数据时，没有确认、窗口、重传、拥塞控制等机制\n \n - 较安全\n 1、由于没有了TCP的一些机制，被攻击者利用的漏洞就少了\n \n**缺点：**\n\n\n----------\n\n\n \n - 不可靠，不稳定\n 1、由于没有了TCP的机制，在数据传输时如果网络不好，很可能丢包\n\n\n**用UDP协议通讯时怎样得知目标机是否获得了数据包**\n\n\n----------\n\n\n仿造TCP的做法，每发一个UDP包，都在里面加一个SEQ序号，接收方收到包后，将SEQ序号回复给发送方。如果发送方在指定时间以内没有收到回应，说明丢包了。\n\n\n\nTCP与UDP的区别\n----------\n\n\n----------\n\n<table class=\"table table-bordered table-striped table-condensed\">\n\t<tr>\n\t\t<td>TCP面向<strong>有链接</strong>的通信服务</td>\n\t\t<td>UDP面向<strong>无连接</strong>的通信服务</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP提供可靠的通信传输</td>\n\t\t<td>UDP不可靠,会丢包</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP保证数据顺序</td>\n\t\t<td>UDP不保证</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP数据无边界</td>\n\t\t<td>UDP有边界</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP速度快</td>\n\t\t<td>UDP速度慢</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP面向字节流</td>\n\t\t<td>UDP面向报文</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP一对一</td>\n\t\t<td>UDP可以一对一，一对多</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP报头至少20字节</td>\n\t\t<td>UDP报头8字节</td>\n\t</tr>\n\t<tr>\n\t\t<td>TCP有流量控制，拥塞控制</td>\n\t\t<td>UDP没有</td>\n\t</tr>\n</table>\n\n\n\n**为什么UDP比TCP快**\n\n\n----------\n\n\n 1. TCP需要三次握手\n 2. TCP有拥塞控制，控制流量等机制\n\n\n\n**为什么TCP比UDP可靠**\n\n\n----------\n\n\n 1. TCP是面向有连接的，建立连接之后才发送数据；而UDP则不管对方存不存在都会发送数据。\n 2. TCP有确认机制，接收端每收到一个正确包都会回应给发送端。超时或者数据包不完整的话发送端会重传。UDP没有。因此可能丢包。\n\n\n\n**什么时候使用TCP**\n\n\n----------\n\n\n当对网络通讯质量有要求的时候，比如：整个数据要准确无误的传递给对方，这往往用于一些要求可靠的应用，比如HTTP、HTTPS、FTP等传输文件的协议，POP、SMTP等邮件传输的协议。\n在日常生活中，常见使用TCP协议的应用如下：\n浏览器，用的HTTP\nFlashFXP，用的FTP\nOutlook，用的POP、SMTP\nPutty，用的Telnet、SSH\nQQ文件传输\n\n\n\n**什么时候应该使用UDP：**\n\n\n----------\n\n\n当对网络通讯质量要求不高的时候，要求网络通讯速度能尽量的快，这时就可以使用UDP。\n比如，日常生活中，常见使用UDP协议的应用如下：\nQQ语音\nQQ视频\nTFTP\n\n\n\n**TCP无边界，UDP有边界**\n\n\n----------\n**TCP无边界**\n\n客户端分多次发送数据给服务器，若服务器的缓冲区够大，那么服务器端会在客户端发送完之后一次性接收过来，所以是无边界的；\n\n**UDP有边界**\n\n客户端每发送一次，服务器端就会接收一次，也就是说发送多少次就会接收多少次，因此是有边界的。\n\n"
  },
  {
    "path": "Part4/Network/计算机网络基础汇总.md",
    "content": "#计算机网络体系分类：\n计算机网络整个体系有两个大的分类：一个是国际组织制定的OSI七层模型，一种是实际使用中的TCP/IP四层模型。\n\n|OSI七层模型 |TCP/IP四层模型|\n|----|----|\n|应用层||\n|表示层|应用层|\n|会话层||\n|传输层|传输层|\n|网络层|网络层|\n|数据链路层|物理接入层|\n|物理层||\n\n#物理层：\n物理层主要是实现主机和网络之间的物理连接，规定一些与传输媒体接口有关的一些特性。\n\n##通信方式：\n- **单工通信：**只能由A主机向B主机发送消息，通信是单向的。\n\n- **半双工通信：**同一时间只能是由A主机向B主机或者是B主机向A主机发送信息，不能同时发送消息。\n\n- **全双工通信：** A主机和B主机可以实现在同一时间内既接收消息，又发送消息，极大的提升了通信效率。\n\n##常见引导型传输媒体：\n###双绞线：\n分为屏蔽双绞线（STP）和非屏蔽双绞线（UTP），屏蔽双绞线就是在非屏蔽双绞线外边又加了一层屏蔽层\n\ntips：为什么双绞线要两根线绞起来，两个线绞起来可以有效的减少相互之间的电磁干扰。\n\n###同轴电缆：\n由内导体铜制芯线，绝缘层，网状编织的外导体屏蔽层，以及塑料保护外层组成。有良好的抗干扰性，被广泛用于较高速率的传输。\n\n###光缆：\n由非常透明的石英玻璃拉成细丝，主要由纤芯和包层构成双层通讯柱。可以分为单模光纤和多模光纤。\n\n##信道复用技术：\n\n- **频分复用：**根据传输的波的频率的不同，将不同频段的波用于不同的通信。\n\n- **时分复用：**将时间分割为时间段，规定在某个时间段内发送什么样的信息，根据时间段来进行信息类别的区分。也称为**同步时分复用**\n\n- **统计时分复用：**基于时分复用的一种改进，由于基本的时分复用并没有考虑到每个时间段内是否有信息发出，例如说每个时间段是20，但是发送消息只占用其中的2，那么剩下的18就处于空闲时间。统计时分复用就是基于这种考虑而设计的，他允许用户有数据时就将数据发送到缓存中，然后会依次扫描输入缓存，把缓存中的数据放入到STMD帧中，若没有就跳过，每当一个帧满了就把这个帧发出去。STMD帧不是分配的固定时序，而是根据需要动态的分配时隙，也称之为**异步时分复用**\n\n- 光分复用：就是光的频分复用，根据光谱中频率的不同，用不同频率的光来携带不同的信息。\n\n#数据链路层：\n数据链路层主要是将上层的数据转化为数据帧发送到链路上，以及把接受到的帧中的数据取出并交给网络层。\n\n##通信方式：\n- **点对点通信：**通信方式是点到点的，也就是只能是两个点之间的通信。常见的就是PPP协议\n\n- **广播通信：**广播通讯，也就是可以同时实现一对多的通信。常见的就是CSMA/CD（载波监听多点访问/冲突检测）\n\n##核心要解决的问题：\n\n- **封装成帧：**在一段数据的前后分别添加首部和尾部，这样就构成了一个帧。接收端在接受到后就可以方便准确的确定帧的首尾，也称为帧定界符。同时定义出了最大传输单元（MTU）--表示一次可以传输的最大长度上限。\n\n- **透明传输：** 由于使用特定的字符来表示帧的开始和结束，所以传输的内容中不能出现和帧定界符相同的字符，但这显然是不可能的，为了解决这个办法，就可以在内容中和帧定界符一样的字符前边加上规定好的转义字符，这种方法称为字符填充或者是字节填充。\n\n- **差错检测：** 比特在传输过程中可能产生差错，差错检测就是用于检测出现的差错，以便及时的重传，最常用的差错检测算法就是[CRC（循环冗余检验）](http://baike.sogou.com/v7730112.htm?fromTitle=CRC%E6%A0%A1%E9%AA%8C)\n\n#网络层\n\n网络层主要是向上只提供简单灵活的，无连接的，尽最大努力交付的数据报服务。\n\n##IP协议（网际协议）：\nIP协议是TCP/IP体系中最主要的协议之一，一切在IP之下，一切又在IP之上。我们常说的应该是IP协议族，还包含配套的协议：\n\n-  ARP（地址解析协议）：将网络的IP地址转化为实际的物理地址（MAC地址），并存储在MAC地址表中。\n\n-  ICMP（网际控制报文协议）：用于进行差错情况和异常情况的反馈，分为询问报文和差错报告报文。\n\n- IGMP（网际组管理协议）：用于控制和管理多播和组播的协议。\n\n网络地址（IP地址）主要分为五类：\n\n-  A类：前8位为网络位，后24位为主机位，首位为0\n\n-  B类：前16位为网络位，后16位为主机位，前两位为10\n\n- C类：前24位为网络位，后8位位主机位，前三位为110\n\n- D类：前四位为1110，用于多播地址\n\n- E类：前四位为1111，保留为以后使用\n\n##路由选择协议：\n\n路由选择协议分为**内部网关协议（IGP）**和**外部网关协议（EGP）**\n\n###内部网关协议：\n\n主要是有RIP协议和OSPF协议\n\n-  **RIP协议（路由信息协议）：**基于距离矢量的协议\n\n- **OSPF（开放最短路径优先协议）：**基于链路状态的协议\n\n###外部网关协议：\n\n主要是**边界网关协议（BGP）**，将一个大的网络分为多个小的自治域，每个自治域内有一个网关路由负责和其他的自治域的网关进行通讯。\n\n# 传输层\n\n网络层主要是为主机之间提供逻辑通讯，而传输层为应用程序之间提供端到端的逻辑通讯。主要是两种类型的通讯方式，面向连接的TCP协议和面向无连接的UDP。\n\n##端口号：\n端口号按照使用地方的不同分为两大类：服务端端口号，客户端端口号。\n按照占用时长又可以分为熟知端口号（0~1023），登记端口号（1024~49151），短暂端口号（49152~65535）\n\n###常见端口：\n\n- FTP（文件传输协议）：20，21------其中20端口用于传输数据，21端口用于命令控制\n\n- Telnet（远程登录协议）：23 \n\n- DNS（域名解析服务）：53\n\n- TFTP（简单文件传输协议）：69\n\n- HTTP（超文本传输协议）：80\n\n##两种协议：\n \n- **UDP（用户数据报协议）：** \n\n - UDP是无连接的\n - UDP使用尽最大努力交付，但是不保证可靠交付\n - UDP是面向报文的\n - UDP没有拥塞控制\n - UDP支持一对一，一对多，多对一，多对一的交互通讯\n - UDP首部的开销小\n\n- **TCP（传输控制协议）：**\n\n - TCP是面向连接的\n - 每一条TCP连接只能由两个端点，每一条TCP连接只能是点对点的$TCP连接：：= \\{ socket_1,socket_2 \\} =\\{ (IP_1:port_1),(IP_2:port_2)\\}$\n - TCP提供可靠交付的服务\n - TCP提供全双工通信\n - 面向字节流\n\n###可靠传输的实现机制：\n\n-  **停止等待协议：** 每发完一个分组就停止发送，直到收到上一个分组的确认信息。若超过规定时间没有接收到确认信息，边认为是分组丢失，开始重传。\n\n- **连续ARQ协议：**  发送方采用滑动窗口的形式，处于滑动窗口的分组可以直接发送出去；接收方一般采用累积确认的方式，也就是说接受方不必对接收到的每一个分组都进行确认，而是对按序到达的最后一个分组进行确认，而发送方每接收到一个确认，窗口就向前滑动一定距离。\n\n- **流量控制：**协调发送方和接收方的收发速率，利用滑动窗口来协调，使用探测报文来探测当前应该发送的速率，采用[Nagle算法](http://baike.sogou.com/v10500918.htm?fromTitle=Nagle%E7%AE%97%E6%B3%95)\n\n- **拥塞控制：** \n当网络中某一资源的需求超出了该资源所能提供的可用部分，这时网络的性能就要开始变坏，这种情况就叫做拥塞。而拥塞控制就是为了减少或者避免拥塞对网络性能的影响而做出的一种控制手段。\n\n - **拥塞控制思路：**发送方维持一个叫做**拥塞窗口**的状态变量，拥塞窗口的大小取决于网络的拥塞程度，并且在动态的变化。发送方让自己的发送窗口等于拥塞窗口，如果在考虑接收方的接收能力，一般发送窗口还要小于拥塞窗口。\n\n - **慢开始：**当主机开始发送数据的时候，由小到大的增大发送窗口，也就是由小到大的增大拥塞窗口。接收方接收到一个报文之后就回传一个确认报文，发送方每接收到一个确认报文，就将拥塞窗口加1，这样每经过一个传输轮次之后，拥塞窗口就增大一倍。\n \n - **拥塞避免：**思路是让拥塞窗口缓慢的增大，即每经过一个往返时间RTT就把发送方的拥塞窗口加1，而不是加倍，这样拥塞窗口就是线性缓慢增加，比慢开始的增长速率缓慢的多。\n \n - **慢开始门限：**为了防止拥塞窗口增长过大引起网络拥塞，还需要设置一个慢开始门限\n    \n     - 拥塞窗口<慢开始门限时，使用慢开始算法\n     - 拥塞窗口>慢开始门限时，使用拥塞避免算法\n     - 拥塞窗口=慢开始门限时，两种算法都可以\n \n - **快重传：**要求收到方每收到一个时序的报文段之后就立即发出重复确认，而不要等到自己发送数据时才进行捎带确认。而发送方只要一连收到三个重复确认就应当立即重传对方尚未接受到的报文，而不必等待为报文设置的重传计时器到期。 \n \n - **快回复：**与快重传配合使用，当发送方连续收到三个重复确认的时候，就执行“乘法减小”算法，将慢开始门限减半。将拥塞窗口设置为慢开始门限减半之后的值，并开始指向拥塞避免算法。\n\n###TCP的连接管理：\n\n####连接三次握手：\n\n1.  客户端请求建立连接：SYN=1，seq=x；\n2.  服务器对客户端的请求进行响应：SYN=1，ACK=1，seq=y，ack=x+1\n3. 客户端对服务器端的响应信息进行回应：ACK=1，seq=x+1，ack=y+1\n\n**注：** SYN为同步信息，在建立连接过程中始终为1\n\n####断开连接四次握手：\n\n1.  客户端请求断开连接： FIN=1，seq = u；\n2.  服务端对客户端的请求应答：ACK=1，seq=v，ack=u+1；\n3.  服务端请求断开连接：FIN=1，ACK=1，seq=w，ack=u+1；\n4.  客户端对服务端的请求应答：ACK=1，seq=u+1，ack=w+1；\n\n# 应用层\n应用层有多种协议，常用到的就是HTTP以及HTTPS。\n\n**HTTP协议报文格式**\n\n|请求报文|相应报文|\n|---|---|\n|请求行（用于区分是请求报文还是响应报文，在响应报文中为状态行）|状态行|\n|首部行（用来说明浏览器，服务器或者是报文主体的一些信息）|首部行|\n|空行（用于隔开请求实体和首部行）|空行|\n|实体主体（用于一些请求或者是响应的的参数内容等）|实体主体|\n\n**常见状态码**\n\n> 1xx：表示通知信息，例如表示收到或者是正在处理\n> 2xx：表示成功，例如表示接受或知道了\n> 3xx：表示重定向，例如表示完成请求还需要采取进一步的行动\n> 4xx：表示客户端的差错，例如表示请求中有语法错误或不能完成\n> 5xx：表示服务器端的差错：例如表示服务器失效无法完成请求\n\nHTTPS协议就是对HTTP协议的加密，更加安全可靠，采用HTTP+SSL（安全套接字层）来保证数据的安全性。\n\n"
  },
  {
    "path": "Part4/OperatingSystem/Linux系统的IPC.md",
    "content": "# Linux系统的IPC\n\n**线程之间不存在通信,因为本来就共享同一片内存**\n\n各个线程可以访问进程中的公共变量，资源，所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据，以免破坏数据的完整性。数据之间的相互制约包括\n1、直接制约关系,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着，这种关系可以称之为同步关系\n2、间接制约关系，即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问，某种意义上说互斥是一种制约关系更小的同步\n\n## 线程间同步\n\n* 临界区\n  * 每个线程中访问临界资源的代码,一个线程拿到临界区的所有权后,可以多次重入.只有前一个线程放弃,后一个才可以进来\n\n* 互斥量\n  * 互斥量就是简化版的信号量\n  * 互斥量由于也有线程所有权的概念，故也只能进行线程间的资源互斥访问，不能由于线程同步\n  * 由于互斥量是内核对象，因此其可以进行进程间通信，同时还具有一个很好的特性，就是在进程间通信时完美的解决了\"遗弃\"问题\n\n\n* 信号量\n  * 信号量的用法和互斥的用法很相似，不同的是它可以同一时刻允许多个线程访问同一个资源，PV操作\n  * 信号量也是内核对象,也可以进行进程间通信\n\n## 进程间通信\n\n* 管道\n  * 半双工\n  * 只能在具有父子关系的进程间使用\n  * FIFO的共享内存实现\n* 命名管道\n  * 以linux中的文件的形式存在\n  * 不要求进程有用父子关系,甚至通过网络也是可以的\n  * 半双工\n* 信号量\n  * 有up和down操作\n  * 共享内存实现\n\n\n* 消息队列\n  * 队列数据结构\n  * 共享内存实现\n* 信号\n  * 较为复杂的方式,用于通知进程某事件已经发生.例如kill信号\n\n\n* 共享内存\n  * 将同一个物理内存附属到两个进程的虚拟内存中\n* socket\n  * 可以跨网络通信"
  },
  {
    "path": "Part4/OperatingSystem/操作系统.md",
    "content": "#OS\n\n**进程和线程**\n\n关系：\n\n一个进程可以创建和撤销另一个线程，同一个进程中的线程可以并发执行。\n\n**死锁的必要条件，怎么处理死锁。**\n\n**Window内存管理方式：段存储，页存储，段页存储。**\n\n**进程的几种状态和转换**\n\n**什么是虚拟内存。**\n\n**Linux下的IPC几种通信方式**\n\n1. 管道(pipe):管道可用于具有亲缘关系的进程间的通信，是一种半双工的方式，数据只能单向流动，允许一个进程和另一个与它有公共祖先的进程之间进行通信。\n2. 命名管道(named pipe):命名管道克服了管道没有名字的限制，同时除了具有管道的功能外(也是半双工)，它还允许无亲缘关系进程间的通信。命令管道在文件系统中有对应的文件名。命令管道通过命令mkfifo或系统调用mkfifo来创建。\n3. 信号(signal):信号是比较复杂的通信方式，用于通知接收进程有某种事件发生了，除了进程间通信外，进程还可以发送信号给进程本身。\n4. 消息队列:消息队列是消息的链接表，包括Posix消息队列和system V消息队列。有足够权限的进程可以向队列中添加消息，被赋予读权限的进程可以读走队列中的消息。消息队列克服了信号承载信息少，管道只能承载无格式字节流以及缓冲区大小受限等缺点。\n5. 共享内存:使得多个进程可以访问同一块内存空间，是最快的IPC形式。是针对其他通信机制运行效率低而设计的。往往与其他通信机制，如信号量结合使用，来达到进程间的同步及互斥。\n6. 内存映射:内存映射允许任何多个进程间通信每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。\n7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。\n8. 套接字(Socket):更为一般的进程间通信机制，可用于不同机器之间的进程间通信。\n \n**逻辑地址、物理地址的区别**\n\n**进程调度算法**"
  },
  {
    "path": "Part5/ReadingNotes/《APP研发录》第1章读书笔记.md",
    "content": "#《APP研发录》读书笔记\n---\n##第一章\n###1.1重新规划Android项目结构\n\n重新规划Android项目的目录结构，分两步走：\n\n1. **建立AndroidLab类库，将与业务无关的逻辑转移到AndroidLab，AndroidLab至少包括五大部分：包名+ acticity,cache,net,ui,utils 。**\n\n\t* activity包里面存放的是与业务无关的Activity基类。\n\t* net包里存放的是网络底层封装。\n\t* cacahe包里面存放的是缓存数据的图片和图片的相关处理。\n\t* ui包中存放的是自定义控件\n\t* utils包中存放的是各种与类无关的功用方法。\t\n2. **将主项目的类分门别类的进行划分，放置在各种包中。**\n\n\n\t\n\t* activity:我们按照模块继续划分，将不同模块的Activity划分到不同的包中。\n\t* adapter:所有的适配器都放在一起\n\t* entity:所有的实体都放在一起\n\t* db:SQLite相关逻辑的封装\n\t* engine:将业务相关的类都放在一起\n\t* ui:将自定义控件都放在这个包里\n\t* utils:将所有的公用方法都放在这里\n\t* interfaces:真正意义上的接口，命名以I作为开头\n\t* listener:基于Listener的接口，命名以On作为开头\n\t\n###1.2为Activity定义新的生命周期\n\n可以把onCreate方法拆成三个子方法\n\n* initVariables:初始化变量，包括Intent带的数据和Activity内的变量\n* initViews:加载layout布局文件，初始化控件，为控件挂上事件方法\n* loadData:调用MobileAPI获取数据\n\n###1.3统一事件编程模型\n\n只要在一个团队内部达成了协议，决定使用某种事件编程方式，所有开发人员就要按照同样的方式编写代码。\n\n###1.4实体化编程\n\n####1.4.1在网络请求中使用实体\n\n一些开发人员不使用实体化编程，在获取MobileAPI网络请求返回的JSON数据时，使用JSONObject或者JSONArray来承载数据，然后把返回的数据当作一个字典，根据键取出响应的值。介绍fastJSON和GSON这种实体化编程的方式.\n\n* **使用fastJSON如下**：\n\n```\nWeatherEntity weatherEntity = JSON.parseObject(content,\n\t\t\t\t\t\tWeatherEntity.class);\n\t\t\t\tWeatherInfo weatherInfo = weatherEntity.getWeatherInfo();\n\t\t\t\tif (weatherInfo != null) {\n\t\t\t\t\ttvCity.setText(weatherInfo.getCity());\n\t\t\t\t\ttvCityId.setText(weatherInfo.getCityid());\n\t\t\t\t}\n```\n\n* **使用GSON如下：**：\n\n\n```\nGson gson = new Gson();\n\t\t\t\tWeatherEntity weatherEntity = gson.fromJson(content,\n\t\t\t\t\t\tWeatherEntity.class);\n\t\t\t\tWeatherInfo weatherInfo = weatherEntity.getWeatherInfo();\n\t\t\t\tif (weatherInfo != null) {\n\t\t\t\t\ttvCity.setText(weatherInfo.getCity());\n\t\t\t\t\ttvCityId.setText(weatherInfo.getCityid());\n\t\t\t\t}\n```\n\n####1.4.3在页面跳转中实现实体\n\n在一个页面中，数据的来源有两种：\n1. 调用MobileAPI获取JSON数据\n2. 从上一个页面传递过来\n\nActivity之间的数据传递一个偷懒的办法是设置一个全局变量，在来源页设置全局变量，在目标页接收全局变量。以下是来源页MainActivity的代码：\n\n```\nIntent intent = new Intent(MainActivity.this,\n\t\t\t\t\t\tLoginActivity.class);\n\t\t\t\tintent.putExtra(AppConstants.Email, \"jianqiang.bao@qq.com\");\n\t\t\t\t\t\t\t\t\n\t\t\t\tCinemaBean cinema = new CinemaBean();\n\t\t\t\tcinema.setCinemaId(\"1\");\n\t\t\t\tcinema.setCinemaName(\"星美\");\n\n\t\t\t\t//使用全局变量的方式传递参数\n\t\t\t\tGlobalVariables.Cinema = cinema;\n\t\t\t\tstartActivity(intent);\n```\n\n\n以下是目标页LoginActivity的代码：\n\n```\n// 使用全局变量的方式传值\n\t\tCinemaBean cinema = GlobalVariables.Cinema;\n\t\tif (cinema != null) {\n\t\t\tcinemaName = cinema.getCinemaName();\n\t\t} else {\n\t\t\tcinemaName = \"\";\n\t\t}\n```\n\n**不建议使用全局变量。App一旦被切换到后台，当手机内存不足的时候，就会回收这些全局变量，从而当App再次切换回前台时，再继续使用全局变量，就会因为它们为空而崩溃。如果必须使用全局变量，就一定要把它们序列化到本地。这样即使全局变量为空，也能从本地文件中恢复。**\n\n我们使用Intent在页面间来传递数据实体的机制。\n\n首先，在MainActivity中：\n\n```\nIntent intent = new Intent(MainActivity.this,\n\t\t\t\t\t\tLoginNewActivity.class);\n\t\t\t\tintent.putExtra(AppConstants.Email, \"jianqiang.bao@qq.com\");\n\t\t\t\t\n\t\t\t\tCinemaBean cinema = new CinemaBean();\n\t\t\t\tcinema.setCinemaId(\"1\");\n\t\t\t\tcinema.setCinemaName(\"星美\");\n\t\t\t\t\n\t\t\t\t//使用intent上挂可序列化实体的方式传递参数\n\t\t\t\tintent.putExtra(AppConstants.Cinema, cinema);\n\n\t\t\t\tstartActivity(intent);\n```\n其次，目标页LoginActivity要这样写：\n\n```\nCinemaBean cinema = (CinemaBean)getIntent()\n\t\t\t\t.getSerializableExtra(AppConstants.Cinema);\n\t\tif (cinema != null) {\n\t\t\tcinemaName = cinema.getCinemaName();\n\t\t} else {\n\t\t\tcinemaName = \"\";\n\t\t}\n```\n\n这里的CinemaBean要实现Serializable接口，支持序列化：\n\n```\npublic class CinemaBean implements Serializable {\n\tprivate static final long serialVersionUID = 1L;\n\n\tprivate String cinemaId;\n\tprivate String cinemaName;\n\n\tpublic CinemaBean() {\n\n\t}\n\n\tpublic String getCinemaId() {\n\t\treturn cinemaId;\n\t}\n\n\tpublic void setCinemaId(String cinemaId) {\n\t\tthis.cinemaId = cinemaId;\n\t}\n\n\tpublic String getCinemaName() {\n\t\treturn cinemaName;\n\t}\n\n\tpublic void setCinemaName(String cinemaName) {\n\t\tthis.cinemaName = cinemaName;\n\t}\n\n}\n\n```\n\n###1.5Adapter模版\n不对Adapter的写法进行规范，就会写出以下的Adapter\n\n* 很多开发人员都喜欢将Adapter内嵌在Activity中，一般会使用SimpleAdapter\n* 由于没有使用实体，所以一般会把一个字典作为构造函数的参数注入到Adapter中\n\n希望Adapter只有一个编码风格，这样发现了问题也很容易排查。于是要求所有的Adapter都继承自BaseAdapter，从构造函数注入List<自定义实体>这样的数据集合，从而完成ListView的填充工作：\n\n```\npublic class CinemaAdapter extends BaseAdapter {\n\tprivate final ArrayList<CinemaBean> cinemaList;\n\tprivate final AppBaseActivity context;\n\n\tpublic CinemaAdapter(ArrayList<CinemaBean> cinemaList,\n\t\t\tAppBaseActivity context) {\n\t\tthis.cinemaList = cinemaList;\n\t\tthis.context = context;\n\t}\n\n\tpublic int getCount() {\n\t\treturn cinemaList.size();\n\t}\n\n\tpublic CinemaBean getItem(final int position) {\n\t\treturn cinemaList.get(position);\n\t}\n\n\tpublic long getItemId(final int position) {\n\n\t\treturn position;\n\t}\n```\n\n对于每个自定义的Adapter，都要实现以下4个方法：\n\n* getCount()\n* getItem()\n* getItemId()\n* getView()\n\n此外，还要内置一个Holder嵌套类，用于存放ListView中每一行中的控件。ViewHolder的存在，可以避免频繁创建同一个列表项，从而极大的节省内存，如下：\n\n```\nclass Holder {\n\t\tTextView tvCinemaName;\n\t\tTextView tvCinemaId;\n\t}\n```\n\n当有很多列表数据时，快速滑动列表会变的很卡，其实就是因为没有ViewHolder机制导致的，正确的写法如下：\n\n```\npublic View getView(final int position, View convertView,\n\t\t\tfinal ViewGroup parent) {\n\t\tfinal Holder holder;\n\t\tif (convertView == null) {\n\t\t\tholder = new Holder();\n\t\t\tconvertView = context.getLayoutInflater().inflate(\n\t\t\t\t\tR.layout.item_cinemalist, null);\n\t\t\tholder.tvCinemaName = (TextView) convertView\n\t\t\t\t\t.findViewById(R.id.tvCinemaName);\n\t\t\tholder.tvCinemaId = (TextView) convertView\n\t\t\t\t\t.findViewById(R.id.tvCinemaId);\n\t\t\tconvertView.setTag(holder);\n\t\t} else {\n\t\t\tholder = (Holder) convertView.getTag();\n\t\t}\n\n\t\tCinemaBean cinema = cinemaList.get(position);\n\t\tholder.tvCinemaName.setText(cinema.getCinemaName());\n\t\tholder.tvCinemaId.setText(cinema.getCinemaId());\n\t\treturn convertView;\n\t}\n```\n\n在Activity中，在使用Adapter的地方，我们按照下面的方式把列表数据传递过去：\n\n```\n@Override\n\tprotected void initViews(Bundle savedInstanceState) {\n\t\tsetContentView(R.layout.activity_listdemo);\t\t\n\n\t\tlvCinemaList = (ListView) findViewById(R.id.lvCinemalist);\n\t\t\n\t\tCinemaAdapter adapter = new CinemaAdapter(\n\t\t\t\tcinemaList, ListDemoActivity.this);\n\t\tlvCinemaList.setAdapter(adapter);\n\t\tlvCinemaList.setOnItemClickListener(\n\t\t\t\tnew AdapterView.OnItemClickListener() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void onItemClick(AdapterView<?> parent, View view,\n\t\t\t\t\t\t\tint position, long id) {\n\t\t\t\t\t\t//do something\n\t\t\t\t\t}\n\t\t\t\t});\n\t}\n```\n\n###1.6类型安全转换函数\n\n在每天统计线上崩溃的时候，我们发现因为类型转换不正确导致的崩溃占了很大的比例。于是去检查程序中的所有类型转换，发现主要集中在两个地方：Object类型的对象，substring函数。\n\n*  对于一个Object类型的对象，我们对其使用字符串操作函数toString，当其为null时就会崩溃。\n\n\t```\n\tint result = Integer.valueOf(obj.toString());\n\t```\n\t一旦obj这个对象为空，那么上面这行代码就会直接崩溃，这里的obj，一般是从JSON数据中取出来的，对于MobileAPI返回的JSON数据，我们无法保证其永远不为空。\n\t\n\t比较好的做法是编写一个类型安全转换函数convertToInt，实现如下：\n\t\n\t```\n\tpublic final static int convertToInt(Object value, int defaultValue) {\n\t\tif (value == null || \"\".equals(value.toString().trim())) {\n\t\t\treturn defaultValue;\n\t\t}\n\t\ttry {\n\t\t\treturn Integer.valueOf(value.toString());\n\t\t} catch (Exception e) {\n\t\t\ttry {\n\t\t\t\treturn Double.valueOf(value.toString()).intValue();\n\t\t\t} catch (Exception e1) {\n\t\t\t\treturn defaultValue;\n\t\t\t}\n\t\t}\n\t}\n\t```\n\t将这个方法放到Utils类下面，每当要把一个Object对象转化成整型时，都使用该方法，就不会崩溃\n\t\n\t```\n\tint result = Utils.convertToInt(obj, 0);\n\t```\n* 如果长度不够，执行substring函数就会崩溃。Java的substring有两个参数：start和end。\n\n\t```\n\tString cityName = \"T\";\n\tString firstLetter = cityName.substring(1, 2);\n\t```\n对于第一个参数设为0一般没有问题，设置为大于0如上代码就会崩溃，使用substring函数的时候，都要判断start和end两个参数是否越界了，应该这样写：\n\t\n\t```\n\tString cityName = \"T\";\n\tString firstLetter = \"\";\n\tif(cityName.length() > 1)\n\t{\n\t\tcityName.substring(1, 2);\n\t}\n\t```\n\n\n**对于MobileAPI返回的数据**\n\n* 首先，不能让App直接崩溃，应该在解析JSON数据的外面包一层try...catch...语句，讲截获到的异常在catch中进行处理，比如发送错误日志给服务器。\n* 其次，需要分级对待，例如：\n\t* 对于那些不需要加工就能直接展示的数据，我们不担心，即使为空页面也就是不显示而已，不会引起逻辑的问题。\n\t* 对于那些很重要的数据，比如涉及支付的金额不能为空的逻辑，这时候就应该弹出提示框提示用户当前服务不可用，并停止接下来的操作。"
  },
  {
    "path": "Part5/ReadingNotes/《APP研发录》第2章读书笔记.md",
    "content": "#APP研发录第二章笔记\n---\n\n* 抛弃AsyncTask，自定义一套网络底层的封装框架。\n* 设计一套App缓存策略。\n* 设计一套MockService的机制，在没有MobileAPI的时候，也能假装获取到了网络返回的数据。\n* 封装了用户Cookie的逻辑。\n\n##2.1 网络底层封装\n\n很多公司和团队都是用AsyncTask来封装网络底层，因为这个类非常好用，内部封装了很多好用的方法，但缺点是可扩展性不高。\n\n对于网络请求，我们一般定义为GET和POST即可，GET为请求数据，POST为修改数据（增删改）。\n\n###1.Request格式\n\n所有的MobileAPI都可以写作http://www.xxx.com/aaaa.api的形式。\n\n* 对于GET，我们可以写作：http://www.xxx.com/aaaa.api?k1=va&k2=v2的形式，也就是说，把key-value这样的键值对存放在URL上。之所以这样设计，是为了更方便地定义数据缓存。我们尽量使GET的参数都是string、int这样的简单类型。\n* 对于POST，我们将key-value这样的键值对存放在Form表单中，进行提交。POST经常会提交大量数据，所以有些键值对要定义成集合或者复杂的自定义实体，这时我们就需要将这样的值转换为JSON字符串进行提交，由App传递到MobileAPI后，再将JSON字符串转换为对应的实体。\n\n###2.Response格式\n\n我们一般使用JSON作为MobileAPI返回的结果。最规范的JSON数据返回格式如下。\nJSON数据格式1：\n\n```\n{ \n  \"isError\" : true,\n    \"errorType\" : 1,\n      \"errorMessage\" : \"网络异常\",\n        \"result\" : \"\"\n}\n```\n\nJSON数据格式2：\n\n```\n{ \n  \"isError\" : false,\n    \"errorType\" : 0,\n      \"errorMessage\" : \"\",\n        \"result\" : {\n          \"cinemaId\" : 1,\n            \"cinemaName\" : \"星美\"\n        }\n} \n```\n\n这里，isError是调用MobileAPI成功与否，errorType是错误类型（如果成功则为0），errorMessage是错误消息（如果成功则为空），result是成功请求返回的数据结果（如果失败则返回空）。\n\n既然所有的JSON都返回isError、errorType、errorMessage、result这4个字段，我们不妨定义一个Response实体类，作为所有JSON实体的最外层，代码如下所示：\n\n```\npublic class Response\n{\n  private boolean error;\n  private int errorType;       // 1为Cookie失效\n  private String errorMessage;\n  private String result;\n  public boolean hasError() {\n    return error;\n  }\n  public void setError(boolean hasError) {\n    this.error = hasError;\n  }\n  public String getErrorMessage() {\n    return errorMessage;\n  }\n  public void setErrorMessage(String errorMessage) {\n    this.errorMessage = errorMessage;\n  }\n  public String getResult() {\n    return result;\n  }\n  public void setResult(String result) {\n    this.result = result;\n  }\n  public int getErrorType() {\n      return errorType;\n  }\n  public void setErrorType(int errorType) {\n    this.errorType = errorType;\n  }\n} \n```\n\n如果成功返回了数据，数据会存放在result字段中，映射为Response实体的result属性。\n\n上面的JSON数据返回的是一笔影院数据，如果返回的result是很多影院的数据集合，那么就要把result解析为相应的实体集合，如下所示：\n\n```\n{ \n  \"isError\" : false,\n    \"errorType\" : 0,\n      \"errorMessage\" : \"\",\n        \"result\" : [\n          {\"cinemaId\" : 1, \"cinemaName\" : \"星美\"},\n          {\"cinemaId\" : 2, \"cinemaName\" : \"万达\"}\n        ]\n} \n```\n\n####2.1.2 AsyncTask的使用和缺点\n\n对AsyncTask的封装属于网络底层的技术，所以AsyncTask应该封装在AndroidLib类库中，而不是具体的项目里。\n对网络异常的分类，也就是Response类中的errorType字段，分析如下：\n\n* 一种是请求发送到MobileAPI，MobileAPI执行过程中发现的异常，这时候要自定义错误类型，也就是errorType，比如说1是Cookie过期，2是第三方支付平台不能连接，等等，这些已知的错误都是大于0的整数，因接口不同而各自定义不同。\n* 另一种是在App访问MobileAPI接口时发生的异常，有可能App自身网络不稳定，有可能因为网络传输不好导致返回了空值，这些异常情况我们都标记为负数。\n\n基于上述分析，AsyncTask的doInBackground方法复写为：\n\n```\n@Override\nprotected Response doInBackground(String… url) {\n  return getResponseFromURL(url[0]);\n}\nprivate Response getResponseFromURL(String url) {\n  Response response = new Response();\n  HttpGet get = new HttpGet(url);\n  String strResponse = null;\n  try {\n    HttpParams httpParameters = new BasicHttpParams();\n    HttpConnectionParams.setConnectionTimeout(httpParameters, 8000);\n    HttpClient httpClient = new DefaultHttpClient(httpParameters);\n    HttpResponse httpResponse = httpClient.execute(get);\n    if (httpResponse.getStatusLine().getStatusCode() \n        == HttpStatus.SC_OK) {\n      strResponse = EntityUtils.toString(httpResponse.getEntity());\n    }\n  } catch (Exception e) {\n    response.setErrorType(-1);\n    response.setError(true);\n    response.setErrorMessage(e.getMessage());\n  }\n  if (strResponse == null) {\n      response.setErrorType(-1);\n    response.setError(true);\n    response.setErrorMessage(\"网络异常, 返回空值\");\n  } else {\n    strResponse = \"{'isError':false,'errorType':0,'errorMessage':'',\n    'result':{'city':'北京','cityid':'101010100','temp':'17',\n      'WD':'西南风','WS':'2级','SD':'54%','WSE':'2','time':'23:15',\n        'isRadar':'1','Radar':'JC_RADAR_AZ9010_JB',\n          'njd':'暂无实况','qy':'1016'}}\";\n          response = JSON.parseObject(strResponse, Response.class);\n}\nreturn response;\n} \n```\n\n相应的，在AsyncTask的onPostExecute方法中，我们要对错误类型进行分类，从而进一步回调：\n\n```\npublic abstract class RequestAsyncTask \n  extends AsyncTask<String, Void, Response> {\n    public abstract void onSuccess(String content);\n    public abstract void onFail(String errorMessage);\n    @Override\n      protected void onPreExecute() {\n    }\n    @Override\n      protected void onPostExecute(Response response) {\n      if(response.hasError()) {\n        onFail(response.getErrorMessage());\n      } else {\n        onSuccess(response.getResult());\n      }\n    } \n```\n\n\n目前我们只定义了onSuccess和onFail两个回调函数，将网络返回值简单地分为成功与失败两种情况。\n\n在相应的Activity页面，调用AysncTask如下所示：\n\n```\nprotected void loadData() {\n  String url = \"http://www.weather.com.cn/data/sk/101010100.html\";\n  RequestAsyncTask task = new RequestAsyncTask() {\n    @Override\n    public void onSuccess(String content) {\n      // 第2种写法, 基于fastJSON\n      WeatherEntity weatherEntity = JSON.parseObject(content,\n                                                     WeatherEntity.class);\n      WeatherInfo weatherInfo = weatherEntity.getWeatherInfo();\n      if (weatherInfo != null) {\n        tvCity.setText(weatherInfo.getCity());\n        tvCityId.setText(weatherInfo.getCityid());\n      }\n    }\n     @Override\n     public void onFail(String errorMessage) {\n       new AlertDialog.Builder(WeatherByFastJsonActivity.this)\n         .setTitle(\"出错啦\").setMessage(errorMessage)\n         .setPositiveButton(\"确定\", null).show();\n     }\n  };\n  task.execute(url);\n} \n```\n\n网上关于如何使用AsyncTask的文章不胜枚举，大家都在欣赏它的优点，却忽略了它的致命缺点，那就是不能灵活控制其内部的线程池。\n\n线程池里面的每个线程存放的都是MobileAPI的调用请求，而AsyncTask中又没有暴露出取消这些请求的方法，也就是我们熟知的CancelRequest方法，所以，一旦从A页面跳转到B页面，那么在A页面发起的MobileAPI请求，如果还没有返回，并不会被取消。\n\n对于一款频繁调用MobileAPI的应用类App而言，最严重的情况发生在首页到二级页面的跳转，因为在首页会调用十几个MobileAPI接口，视网络情况而定，如果是WiFi，应该很快就能请求到数据，不会产生积压，但如果是3G或者2G，那么请求就会花费很长时间，而我们在这期间就跳转到二级页面，而这个二级页面也会调用MobileAPI接口，那么将得不到任何结果，因为首页的请求还在排队处理中，之前的那十几个MobileAPI接口的数据还都遥遥无期在线程池里排队呢，就更不要说当前页面这个请求了。\n\n如果你不信，我们可以做个试验。记录每次MobileAPI请求发起和接收数据的时间点，你会看到，在迅速进入二级页面后，首页的十几个MobileAPI请求只有发起时间并没有返回时间，说明它们还在处理过程中，都被堵塞了。\n\n####2.1.3 使用原生的ThreadPoolExcutor+Runnable+Handler\n\n既然AsyncTask有诸多问题，那么退而求其次，使用ThreadPoolExecutor+Runnable+Handler的原生方式，对网络底层进行封装。\n\n建议大家下载源码感受一些，源码下载地址：\n\n[《App研发录》 源码](http://www.cnblogs.com/Jax/p/4656789.html)\n\n####2.1.4　网络底层的一些优化工作\n\n接下来将完善这个框架，修复其中的一些瑕疵，如onFail的统一处理机制、UrlConfigManager的优化、ProgressBar的处理等。\n\n**1.onFail的统一处理机制**\n\n如果访问MobileAPI请求失败，我们一般希望只是在App上简单地弹出一个提示框，告诉用户网络有异常。\n\n也就是说，对于每个在Activity中声明的RequestCallback实例而言，尽管每个onSuccess方法的处理逻辑各不相同，但每个onFail方法都是一样的逻辑和代码，如下所示：\n\n```\nweatherCallback = new RequestCallback() {\n  @Override\n  public void onSuccess(String content) {\n    WeatherInfo weatherInfo = JSON.parseObject(content,\n                                               WeatherInfo.class);\n    if (weatherInfo != null) {\n      tvCity.setText(weatherInfo.getCity());\n      tvCityId.setText(weatherInfo.getCityid());\n    }\n  }\n   @Override\n   public void onFail(String errorMessage) {\n     new AlertDialog.Builder(WeatherByFastJsonActivity.this)\n       .setTitle(\"出错啦\").setMessage(errorMessage)\n       .setPositiveButton(\"确定\", null).show();\n   }\n}; \n```\n\n我不希望每次都编写同样的onFail方法，这会使程序很臃肿。于是在AppBaseActivity中写一个自定义类AbstractRequestCallback，如下所示：\n\n```\npublic abstract class AppBaseActivity extends BaseActivity {\n  public abstract class AbstractRequestCallback \n    implements RequestCallback {\n    public abstract void onSuccess(String content);\n    public void onFail(String errorMessage) {\n      new AlertDialog.Builder(AppBaseActivity.this)\n        .setTitle(\"出错啦\").setMessage(errorMessage)\n        .setPositiveButton(\"确定\", null).show();\n    }\n      }\n} \n```\n\n那么我们的weatherRequestCallback的实例化就可以改写如下，可以看到，不再需要重写onFail方法：\n\n```\nweatherCallback = new AbstractRequestCallback() {\n  @Override\n  public void onSuccess(String content) {\n    WeatherInfo weatherInfo = JSON.parseObject(content,\n                                               WeatherInfo.class);\n    if (weatherInfo != null) {\n      tvCity.setText(weatherInfo.getCity());\n      tvCityId.setText(weatherInfo.getCityid());\n    }\n  }\n  }; \n```\n\n当然，如果有些MobileAPI接口在返回错误时需要App特殊处理，比如重启App或者啥都不做，我们只需要在实例化AbstractRequestCallback时，重写onFail方法即可，如下所示。重写的onFail方法是一个空方法，表示出错时啥都不做：\n\n```\nweatherCallback = new AbstractRequestCallback() {\n  @Override\n  public void onSuccess(String content) {\n    WeatherInfo weatherInfo = JSON.parseObject(content,\n                                               WeatherInfo.class);\n    if (weatherInfo != null) {\n      tvCity.setText(weatherInfo.getCity());\n      tvCityId.setText(weatherInfo.getCityid());\n    }\n      }\n   @Override\n   public void onFail(String errorMessage) {\n     // 重启App或者啥都不做\n   }\n}; \n```\n\n**2.UrlConfigManager的优化**\n\n在UrlConfigManager的实现上，我们采取的策略是每发起一次MobileAPI请求，都会读取url.xml文件，把符合这次MobileAPI接口调用的参数取出来。\n\n在一个大量调用MobileAPI的App中，这样的设计会造成频繁读xml文件，性能很差。于是我们对其进行改造，在App启动时，一次性将url.xml文件都读取到内存，把所有的UrlData实体保存在一个集合中，然后每次调用MobileAPI接口，直接从内存的这个集合中查找。考虑到内存中的数据会被回收，所以上述这个集合一旦为空，我们要从url.xml中再次读取。\n\n基于上述方案，我们对UrlConfigManager的findUrl方法进行改造：\n\n```\npublic static URLData findURL(final Activity activity, \n                              final String findKey) {\n  // 如果urlList还没有数据(第一次)\n  // 或者被回收了, 那么(重新)加载xml\n  if (urlList == null || urlList.isEmpty())\n    fetchUrlDataFromXml(activity);\n  for (URLData data : urlList) {\n    if (findKey.equals(data.getKey())) {\n      return data;\n    }\n  }\n  return null;\n} \n```\n\n其中，fetchUrlDataFromXml方法就不多说了，它的工作就是把xml的数据都搬到内存集合urlList中。\n\n**3.不是每个请求都需要回调的**\n\n有些时候，我们调用一个MobileAPI接口，并不需要知道调用成功与否以及返回结果是什么，比如向MobileAPI发送打点统计数据。那就是说，我们不需要回调函数了，那么代码可以写为：\n\n```\nvoid loadAPIData3() {\n  ArrayList<RequestParameter> params \n    = new ArrayList<RequestParameter>();\n  RequestParameter rp1 = \n    new RequestParameter(\"cityId\", \"111\");\n  RequestParameter rp2 = \n    new RequestParameter(\"cityName\", \"Beijing\");\n  params.add(rp1);\n  params.add(rp2);\n  RemoteService.getInstance()\n    .invoke(this, \"getWeatherInfo\", params, null);\n} \n```\n\n我们将空的RequestCallback传给HttpRequest，那么在HttpRequest处理请求返回的结果时，就需要添加HttpRequest是否为空的判断，不为空，才会处理返回结果；否则，发起MobileAPI请求后什么都不做。\n\n有以下两个地方需要修改：\n\n1）处理请求时：\n\n```\nresponse = httpClient.execute(request);\nif ((requestCallback != null)) {\n  // 获取状态\n  final int statusCode = \n    response.getStatusLine().getStatusCode();\n  if (statusCode == HttpStatus.SC_OK) {\n    final ByteArrayOutputStream content = \n      new ByteArrayOutputStream(); \n```\n\n2）遇到异常，是否要回调onFail方法：\n\n```\npublic void handleNetworkError(final String errorMsg) {\n  if ((requestCallback != null)) {\n    handler.post(new Runnable() {\n      @Override\n        public void run() {\n        HttpRequest.this.requestCallback\n          .onFail(errorMsg);\n      }\n    });\n  }\n} \n```\n\n**4.ProgressBar的处理**\n\n在调用MobileAPI的时候，会显示进度条ProgressBar，直到返回结果到onSuccess或onFail回调方法，ProgressBar才会消失。\n\n由于App要保持风格统一，所以所有页面的ProgressBar应该长得一样。那么我们就可以将其定义在AppBaseActivity中，如下所示：\n\n```\npublic abstract class AppBaseActivity extends BaseActivity {\n  protected ProgressDialog dlg;\n  public abstract class AbstractRequestCallback \n    implements RequestCallback {\n    public abstract void onSuccess(String content);\n    public void onFail(String errorMessage) {\n      dlg.dismiss();\n          public void onFail(String errorMessage) {\n      dlg.dismiss();\n      new AlertDialog.Builder(AppBaseActivity.this)\n        .setTitle(\"出错啦\").setMessage(errorMessage)\n        .setPositiveButton(\"确定\", null).show();\n    }\n  }\n} \n```\n\n在使用的时候，在开始调用MobileAPI的地方，执行show方法；在onSuccess和onFail方法的开始，执行dismiss方法：\n\n```\n@Override\nprotected void loadData() {\n  dlg = Utils.createProgressDialog(this,\n                                   this.getString(R.string.str_loading));\n  dlg.show();\n  loadAPIData1();\n}\nvoid loadAPIData1() {\n  weatherCallback = new AbstractRequestCallback() {\n    @Override\n    public void onSuccess(String content) {\n      dlg.dismiss();\n      WeatherInfo weatherInfo = JSON.parseObject(content,\n                                                 WeatherInfo.class);\n      if (weatherInfo != null) { \n```\n\n不要把Dialog的show方法和dismiss方法封装到网络底层。网络底层的调用经常是在子线程执行的，子线程是不能操作Dialog、Toast和控件的。\n\n###2.2　App数据缓存设计\n\n如果以为上一节内容就是网络底层框架的全部，那就错了。那只是网络底层框架的最核心的功能，我们还有很多高级功能没有介绍。在接下来的几节中，我将陆续介绍到这些高级功能。本节先介绍App本地的缓存策略。\n\n####2.2.1　数据缓存策略\n\n对于任何一款应用类App，如果访问MobileAPI的速度和牛车一样慢，那么就是失败之作。不要在WiFi下测试速度，那是自欺欺人，要把App放在2G或3G网络环境下进行测试，才能得到大部分用户的真实数据。\n\n访问MobileAPI，主要慢在一来一回的传输速度上，对于服务器的处理速度，不需要担心太多，大多数服务器逻辑原本就是支持网站端的，现在只是在外面包了一层，返回给App而已。\n\n既然时间主要花在了数据传输上，那么我们就要想一些应对的措施。比如说，减少MobileAPI的调用次数。对于一个App页面，它一次性可能需要3部分数据，分别从3个MobileAPI接口获取，那么我们就可以做一个新的MobileAPI接口，将这3部分数据都获取到，然后一次性返回。\n\n减少调用次数只是若干解决方案中的一种，更极端的做法是，App调用一次MobileAPI接口后，在一个时间段内不再调用，仍然使用上次调用接口获取到的数据，这些数据保存在App上，我们称为App缓存，这个时间段我们称为App缓存时间。\n\nApp缓存只能针对于MobileAPI中GET类型的接口，对于POST不适用。因为GET是获取数据，而POST是修改数据。\n\n此外，即使是GET类型的接口，对于那些即时性很低的、不怎么改变的数据，比如获取商品的描述，缓存时间可以设置得比较长，比如5~10分钟，对于那些即时性比较高、频繁变动的数据，比如商品价格，缓存时间就会比较短，甚至不能进行缓存。\n\n即使对于同一个需要做App缓存的MobileAPI，参数不同，缓存也是不同的。比如GetWeather.api这个MobileAPI接口，它有一个参数也就是时间date，对于date=2014-9-8和date=2014-9-9，它们就分别对应两个缓存，不能存在一起。\n\n接下来要说的是，App缓存存在哪里，以及以什么方式进行存放。由于缓存数据比较大，所以我们将其存在SD卡上，而不是内存中。这样的话，App缓存策略就仅限于那些有SD卡的手机用户了。\n\n我们可以将xxx.apik1=va&k2=v2这样的URL格式作为key，存放App缓存数据。需要注意的是，我们要对k1、k2这些key进行排序，这样才能唯一，否则对于如下URL：\n\n```\nxxxx.apik1=v1&k2=v2\nxxxx.apik2=v2&k1=v1 \n```\n\n就会被认为是两个不同的key存放在缓存中，但其实它们是一样的。\n对上面的介绍总结如下：\n\n1）对于App而言，它是感受不到取的是缓存数据还是调用MobileAPI。具体工作由网络底层完成。\n\n2）在url.xml中为每一个MobileAPI接口配置缓存时间Expired。对于post，一律设置为0，因为post不需要缓存。\n\n3）在HttpRequest类中的run方法中，改动3个地方：\n\na）写一个排序算法sortKeys，对URL中的key进行排序。\n\nb）将newUrl作为key，检查缓存中是否有数据，有则直接返回；否则，继续调用MobileAPI接口。\n\n```\n// 如果这个get的API有缓存时间(大于0)\nif (urlData.getExpires() > 0) {\n  final String content = CacheManager.getInstance()\n    .getFileCache(newUrl);\n  if (content != null) {\n    handler.post(new Runnable() {\n      @Override\n        public void run() {\n        requestCallback.onSuccess(content);\n      }\n    });\n    return;\n  }\n} \n```\n\nc）MobileAPI接口返回数据后，将数据存入缓存。\n\n```\nfinal Response responseInJson = JSON.parseObject(\n  strResponse, Response.class);\nif (responseInJson.hasError()) {\n  handleNetworkError(responseInJson.getErrorMessage());\n} else {\n  // 把成功获取到的数据记录到缓存\n  if (urlData.getNetType().equals(REQUEST_GET)\n      && urlData.getExpires() > 0) {\n    CacheManager.getInstance().putFileCache(newUrl,\n                                            responseInJson.getResult(),\n                                            urlData.getExpires());\n  }\n  handler.post(new Runnable() {\n    @Override\n      public void run() {\n      requestCallback.onSuccess(responseInJson\n                                .getResult());\n    }\n  });\n} \n```\n\n4）CacheManager用于操作读写缓存数据，并判断缓存数据是否过期。缓存中存放的实体就是CacheItem。\n\n5）在App项目中，创建YoungHeartApplication这个Application级别的类，在程序启动时，初始化缓存的目录，如果不存在则创建之。\n\n```\npublic class YoungHeartApplication extends Application {\n  @Override\n    public void onCreate() {\n    super.onCreate(); \n    CacheManager.getInstance().initCacheDir();\n  }\n} \n```\n\n####2.2.2　强制更新\n\n不光是App端需要记录缓存数据，在MobileAPI的很多接口，其实也需要一样的设计。\n\n如果对于某个接口的数据，MobileAPI缓存了5分钟，App缓存了3分钟，那么最极端的情况是，用户在8分钟内是看不到数据更新的。因此，我们需要在页面上提供一个强制更新的按钮。\n\n我们可以让RemoteService多暴露一个boolean类型的参数，用于判断是否要遵守App端缓存策略，如果是，则在从url.xml中取出UrlData实体后，将其expired强制设置为0，这样就不会执行缓存策略了。\n\nRemoteService的改动如下：\n\n```\npublic void invoke(final BaseActivity activity,\n                   final String apiKey,\n                   final List<RequestParameter> params,\n                   final RequestCallback callBack) {\n  invoke(activity, apiKey, params, callBack, false);\n}\npublic void invoke(final BaseActivity activity,\n                   final String apiKey,\n                   final List<RequestParameter> params,\n                   final RequestCallback callBack,\n                   final boolean forceUpdate) {\n  final URLData urlData = \n    UrlConfigManager.findURL(activity, apiKey);\n  if(forceUpdate) {\n    // 如果强制更新, 那么就把过期时间强制设置为0\n    urlData.setExpires(0); \n  }\n  HttpRequest request = \n    activity.getRequestManager().createRequest(\n    urlData, params, callBack);\n  DefaultThreadPool.getInstance().execute(request);\n} \n```\n\n那么在调用的时候，只需要加一个参数就好：\n\n```\nRemoteService.getInstance().invoke(\n  this, \"getWeatherInfo\", params,\n  weatherCallback); \n```\n\n###2.3　MockService\n\n设计App端MockService包括如下几个关键点：\n\n1）对需要Mock数据的MobileAPI接口，通过在url.xml中配置Node节点MockClass属性，来指定要使用那个Mock子类生成的数据：\n\n```\n<Node\n    Key=\"getWeatherInfo\"\n    Expires=\"300\"\n    NetType=\"get\"\n    MockClass=\"com.youngheart.mockdata.MockWeatherInfo\"\n    Url=\"http://www.weather.com.cn/data/sk/101010100.html\" /> \n```\n\n这里将使用com.mockdata.mockdata包下的MockWeatherInfo子类来解析。\n\n2）我使用了反射工厂来设计MockService。MockService类是基类，它有一个抽象方法getJsonData，用于返回手动生成的Mock数据。\n\n```\npublic abstract class MockService {\n  public abstract String getJsonData();\n} \n```\n\n每个要Mock数据的MobileAPI接口，都对应一个继承自MockService的子类，都要实现各自的getJsonData方法，返回不同的JSON数据。\n\n比如在上述url.xml中声明的MockWeatherInfo，它对应的类实现如下：\n\n```\npublic class MockWeatherInfo extends MockService {\n  @Override\n  public String getJsonData() {\n    WeatherInfo weather = new WeatherInfo();\n    weather.setCity(\"Beijing\");\n    weather.setCityid(\"10000\");\n    Response response = getSuccessResponse();\n    response.setResult(JSON.toJSONString(weather));\n    return JSON.toJSONString(response);\n  }\n  } \n```\n\n3）接下来介绍如何实现反射机制。\n\n主要的改造工作在RemoteService类的invoke方法中，根据是否在url.xml中指定了MockClass值来决定，是调用线上MobileAPI还是从本地MockService直接取假数据。\n\n如果MockClass有值，就把这个值反射为一个具体的类，比如MockWeatherInfo，然后调用它的getJsonData方法。\n\n```\npublic void invoke(final BaseActivity activity, \n                   final String apiKey,\n                   final List<RequestParameter> params, \n                   final RequestCallback callBack) {\n  final URLData urlData = UrlConfigManager.findURL(activity, apiKey);\n  if (urlData.getMockClass() != null) {\n    try {\n      MockService mockService = (MockService) Class.forName(\n        urlData.getMockClass()).newInstance();\n      String strResponse = mockService.getJsonData();\n      final Response responseInJson = \n        JSON.parseObject(strResponse, Response.class);\n      if (callBack != null) {\n        if (responseInJson.hasError()) {\n          callBack.onFail(responseInJson.getErrorMessage());\n        } else {\n          callBack.onSuccess(responseInJson.getResult());\n        }\n      }\n    } catch (ClassNotFoundException e) {\n      e.printStackTrace();\n    } catch (InstantiationException e) {\n      e.printStackTrace();\n    } catch (IllegalAccessException e) {\n      e.printStackTrace();\n    }\n  } else {\n    HttpRequest request = \n      activity.getRequestManager().createRequest(\n      urlData, params, callBack);\n    DefaultThreadPool.getInstance().execute(request);\n  }\n} \n```\n\n###2.4　用户登录\n\n####2.4.1　登录成功后的各种场景\n\n首先，贯穿App的，应该有一个User全局变量，在每次登录成功后，会将其isLogin属性设置为true，在退出登录后，则将该属性设置为false。这个User全局变量要支持序列化到本地的功能，这样数据才不会因内存回收而丢失。\n\n其次，登录分为3种情形：\n\n情形1：点击登录按钮，进入登录页面LoginActivity，登录成功后，直接进入个人中心PersonCenterActivity。这种情况最直截了当，一路执行startActivity(intent)就能达到目的。\n\n情形2：在页面A，想要跳转到页面B，并携带一些参数，却发现没有登录，于是先跳转到登录页，登录成功后，再跳转到B页面，同时仍然带着那些参数。\n\n这就主要是setResult(intent,resultCode)发挥作用的时候了，Activity的回调机制这时候派上了用场，如下所示：\n\n```\nbtnLogin2.setOnClickListener(new OnClickListener(){\n  @Override\n  public void onClick(View v) {\n  if(User.getInstance().isLogin()) {\n      gotoNewsActivity();\n    } else {\n      Intent intent = new Intent(LoginMainActivity.this, \n                                 LoginActivity.class);\n      intent.putExtra(AppConstants.NeedCallback, true);\n      startActivityForResult(intent, \n                             LOGIN_REDIRECT_OUTSIDE);\n    }\n  }\n}); \n```\n\n情形3：在页面A，执行某个操作，却发现没有登录，于是跳转到登录页，登录成功后，再回到页面A，继续执行该操作。\n\n处理方式同于情形2，也是使用setResult来完成回调。\n\n```\nbtnLogin3.setOnClickListener(new OnClickListener(){\n  @Override\n  public void onClick(View v) {\n    if(User.getInstance().isLogin()) {\n      changeText();\n    } else {\n      Intent intent = new Intent(LoginMainActivity.this, \n                                 LoginActivity.class);\n      intent.putExtra(AppConstants.NeedCallback, true);\n      startActivityForResult(intent, \n                             LOGIN_REDIRECT_INSIDE);\n    }\n  }\n}); \n```\n\n无论是上述哪种情形，登录页面LoginActivity只有一个，所以要把上面的三个逻辑整合在一起，如下所示：\n\n```\nRequestCallback loginCallback = new AbstractRequestCallback() {\n@Override\n  public void onSuccess(String content) {\n    UserInfo userInfo = JSON.parseObject(content,\n                                         UserInfo.class);\n    if (userInfo != null) {\n      User.getInstance().reset();\n      User.getInstance().setLoginName(userInfo.getLoginName());\n      User.getInstance().setScore(userInfo.getScore());\n      User.getInstance().setUserName(userInfo.getUserName());\n      User.getInstance().setLoginStatus(true);\n      User.getInstance().save();\n    }\n    if(needCallback) {\n      setResult(Activity.RESULT_OK);\n      finish();\n    } else {\n      Intent intent = new Intent(LoginActivity.this, \n                                 PersonCenterActivity.class);\n      startActivity(intent);\n    }\n  }\n  }; \n```\n\n整合的关键在于从上个页面传过来needCallback变量，它决定了是否要回到上个页面。\n\n另一方面，我们看到，在登录成功后，我们会把用户信息存储到User这个全局变量并序列化到本地，这是因为各个模块都有可能使用到用户的信息。其中LoginStatus是关键，接下来的篇幅将着重谈论这个属性。\n\n最后在LoginMainActivity中的onActivityResult回调函数，它负责处理登录后的事情，如下所示：\n\n```\n@Override\n  protected void onActivityResult(int requestCode, \n                                  int resultCode, Intent data) {\n  if (resultCode != Activity.RESULT_OK) {\n    return;\n  }\n  switch (requestCode) {\n    case LOGIN_REDIRECT_OUTSIDE:\n    gotoNewsActivity();\n    break;\n    case LOGIN_REDIRECT_INSIDE:\n    changeText();\n    break;\n    default:\n    break;\n  }\n} \n```\n\n我们看到，对于情形2，当用户在LoginMainActivity点击按钮想跳转到NewsActivity，如果已经登录，就直接跳转过去；否则，先到LoginActivity登录，然后回调LoginMain-Activity的onActivityResult，仍然跳转到NewsActivity。\n\n####2.4.2　自动登录\n\n所谓自动登录，就是登录成功后，重启App后用户仍然是登录状态。\n\n最直接的方法是，登录成功后，本地保存用户名和密码。重启App后，检查本地是否有保存用户名和密码，如果有，则将用户名和密码传入到登录接口，模拟用户登录的行为。\n\n但这样就有安全风险了，分析如下：\n\n* 本地保存用户密码，这种的敏感信息容易被人窃取。要么是在本地文件中看到这些信息，要么是侦听App的网络请求，获取到请求的数据。\n* 所以本地保存密码时，一定要进行加密。对称加密是不可靠的，因为很难确保App的源代码不外泄，所以别有用心的人还是可以根据源码中的对称加密算法，反向把密码推算出来。只有不对称加密才是安全的。\n* 那么登录之后呢？市面上大多数App的逻辑都有问题，它们会在本地保存一个isLogin的全局变量，登录成功后设置为true。接下来涉及用户相关的MobileAPI，只有在这个值为true时才能调用，它们会把UserId传递给服务器。\n* 服务器的解决方案通常也很简陋，它没有任何安全机制，包括用户信息相关的MobileAPI接口，只要接口调用参数中有UserId，它就会去把相关的数据取出并返回。\n* 一种补救措施是，每次调用用户相关的MobileAPI接口时，都需要把UserId和加密后的密码一起传递。而服务器需要对那些用户相关的MobileAPI接口加上安全验证机制，每次请求都检查用户名和密码是否正确。我们要求密码是经过哈希散列算法不对称加密过的，是无法还原的。服务器的验证工作是根据传过来的UserId从数据库中取出相应的密码，然后进行比对。注意，数据库中存放的密码是在注册的时候经过哈希散列算法加密过的。\n* 本地保存用户名和密码的另一个问题是，每次用户启动App，登录页都会一闪而过，因为它要模拟用户登录的行为：假装输入用户名和密码，然后假装点击登录按钮。这样做用户体验很不好倒是其次，关键是这种做法有个无法自圆其说的硬伤——出于安全考虑，我们要修改登录接口，使其除了接收用户名和密码这两个参数外，还必须接收验证码，也就是动态口令.\n\n我们知道，验证码必须是手动输入的，否则就失去了它存在的意义。但是当前这种自动登录的做法，我们只知道用户名和密码，而不知道每次生成的验证码是什么，所以就不能自动登录了。是时候该抛弃这种每次启动就进行一次登录的机制了，其实Web在这一点已经做得很成熟了，那就是Cookie机制。\n\n也有的人管Cookie叫Token，这是用户身份的唯一性标志。\n\n首先，App在登录成功后，会从服务器获取到一个Cookie，这个Cookie存放在Http-Response的header中，如下所示：\n\n```\nSet-Cookie: customer=huangxp; path=/foo; domain=.ibm.com; \nexpires= Wednesday, 19-OCT-05 23:12:40 GMT; [secure] \n```\n\n我们将其取出来，不用关心它是什么，只要把它存放在本地文件中即可。\n\n我们需要修改App的网络底层，也就是HttpRequest类，分以下几步：\n\n1）每次发起MobileAPI请求时，都要把本地保存的Cookie取出来，放到HttpRequest的header中。还是那句话，不用管Cookie是什么，也不管Cookie是否有值，都应如下操作：\n\n```\n// 添加Cookie到请求头中\naddCookie();\n// 发送请求\nresponse = httpClient.execute(request); \n```\n\n2）每次接收MobileAPI的相应结果时，都把HttpResponse的header里面的Cookie取出来，覆盖本地保存的Cookie。不用管Cookie有值与否，如下所示：\n\n```\nif (urlData.getNetType().equals(REQUEST_GET)\n    && urlData.getExpires() > 0) {\n  CacheManager.getInstance().putFileCache(newUrl,\n                                          responseInJson.getResult(),\n                                                                                    urlData.getExpires());\n}\nhandler.post(new Runnable() {\n  @Override\n  public void run() {\n    requestCallback.onSuccess(responseInJson\n                              .getResult());\n  }\n});\n// 保存Cookie\nsaveCookie(); \n```\n\n以下是addCookie和saveCookie方法的实现：\n\n```\npublic void addCookie() {\n  List<SerializableCookie> cookieList = null;\n  Object cookieObj = BaseUtils.restoreObject(cookiePath);\n  if (cookieObj != null) {\n    cookieList = (ArrayList<SerializableCookie>) cookieObj;\n  }\n  if ((cookieList != null) && (cookieList.size() > 0)) {\n    final BasicCookieStore cs = new BasicCookieStore();\n    cs.addCookies(cookieList.toArray(new Cookie[] {}));\n    httpClient.setCookieStore(cs);\n  } else {\n    httpClient.setCookieStore(null);\n  }\n}\npublic synchronized void saveCookie() {\n  // 获取本次访问的cookie\n  final List<Cookie> cookies = \n    httpClient.getCookieStore().getCookies();\n  // 将普通cookie转换为可序列化的cookie\n  List<SerializableCookie> serializableCookies = null;\n  if ((cookies != null) && (cookies.size() > 0)) {\n    serializableCookies = new ArrayList<SerializableCookie>();\n    for (final Cookie c : cookies) {\n      serializableCookies.add(new SerializableCookie(c));\n    }\n        }\n  }\n  BaseUtils.saveObject(cookiePath, serializableCookies);\n} \n```\n\n而服务器的相应操作，对于来自App的请求：\n\n3）如果是用户信息相关的，则判断HttpRequest中Cookie是否有效，如果有效，就去执行后续的逻辑并返回结果；否则，返回Cookie过期失效的错误信息。\n\n4）如果是用户无关的，则不需要检查HttpRequest中Cookie，直接执行下面的逻辑即可。\n\n此外，还需要注意几个地方，都是些琐碎的工作：\n\n* 用户注销功能，要把本地保存的Cookie清空。App判断用户是否登录的标志，就是Cookie是否为空。\n* 用户注册功能，一般在注册成功后，都会拿着用户名和密码再调用一次登录接口，这就又和验证码功能冲突了，解决方案是注册成功后直接跳转到登录页面，让用户手动再输入一次。这是从产品层面来解决问题。另一种解决方案是，注册成功后进入个人中心页面，不需要再登录一次，而是把注册和登录接口绑在一起。\n* 对于Cookie过期，App应该跳转到登录页面，让用户手动进行登录。这里有一个比较有挑战性的工作，就是登录成功后，应该返回手动登录之前的那个页面。我们在下一节再细说这个技术。\n\n####2.4.3　Cookie过期的统一处理\n\nCookie不是一直有效的，到了一定时间就会失效。\n\nCookie过期的表现是，当访问MobileAPI某个接口的时候，就不会返回数据了，代之以Cookie过期的错误消息，这时要统一处理。\n\n我们要求MobileAPI在遇到这种情况时，直接返回以下内容的JSON，其中，errorType固定为1：\n\n```\n{\n  \"isError\" : true,\n    \"errorType\" : 1,\n      \"errorMessage\" : \"Cookie失效, 请重新登录\",\n        \"result\" : \"\"\n} \n```\n\n为此我们修改AndroidLib，使之支持Cookie失效的场景。\n\n1）在RequestCallback中增加一种onCookieExpired回调方法，如下所示：\n\n```\npublic interface RequestCallback\n{\n  public void onSuccess(String content);\n  public void onFail(String errorMessage);\n  public void onCookieExpired();\n} \n```\n\n2）在网络底层对JSON返回结果进行解析，如果发现是属于Cookie过期的错误类型，就直接回调onCookieExpired方法，如下所示：\n\n```\nfinal Response responseInJson = JSON.parseObject(\n  strResponse, Response.class);\nif (responseInJson.hasError()) {\n  if(responseInJson.getErrorType() == 1) {\n    handler.post(new Runnable() {\n      @Override\n        public void run() {\n        requestCallback.onCookieExpired();\n      }\n    });\n  } else {\n    handleNetworkError(responseInJson.getErrorMessage());\n  } \n```\n\n我们模拟一种场景，在CookieExpiredActivity页面，访问天气预报这个MobileAPI接口，如果Cookie失效，则弹出对话框，通知用户“Cookie过期，请重新登录”，点击确定按钮，将跳转到登录页。登录成功后，将回到上一个页面，即CookieExpiredActivity。\n\n由于所有页面处理Cookie过期的逻辑都是相同的，所以我们将其封装到基类AppBaseActivity中，放在和onFail方法平级的位置：\n\n```\npublic void onCookieExpired() {\n  dlg.dismiss();\n  new AlertDialog.Builder(AppBaseActivity.this)\n  .setTitle(\"出错啦\")\n  .setMessage(\"Cookie过期, 请重新登录\")\n  .setPositiveButton(\"确定\",\n                     new DialogInterface.OnClickListener() {\n                       @Override\n                       public void onClick(DialogInterface dialog,\n                                                                  int which) {\n                         Intent intent = new Intent(\n                           AppBaseActivity.this,\n                           LoginActivity.class);\n                         intent.putExtra(AppConstants.NeedCallback,\n                                         true);\n                         startActivity(intent);\n                       }\n                     }).show();\n} \n```\n\n####2.4.4　防止黑客刷库\n\n\n* MobileAPI在发现有同一IP短时间内频繁访问某一个MobileAPI接口时，就直接返回一段HTML5，要求用户输入验证码。\n* App在接收到这段代码时，就在页面上显示一个浮层，里面一个WebView，显示这个要求用户输入验证码的HTML5。\n\n\n###2.5　HTTP头中的奥妙\n\n####2.5.1　HTTP请求\n\nHTTP请求分为HTTPRequest和HTTPResponse两种。但无论哪种请求，都由header和body两部分组成。\n\n**1.HTTP Body**\n\nBody部分就是存放数据的地方，回顾一下我们在HTTPRequest类中封装的网络请求：\n\n1）对于get形式的HTTPRequest，要发送的数据都以键值对的形式存放在URL上，比如aaa.apik1=va&k2=va。它的Body是空的，如下所示：\n\n```\nif (urlData.getNetType().equals(REQUEST_GET)) {\n  // 添加参数\n  final StringBuffer paramBuffer = new StringBuffer();\n  if ((parameter != null) && (parameter.size() > 0)) {\n    // 这里要对key进行排序\n    sortKeys();\n    for (final RequestParameter p : parameter) {\n      if (paramBuffer.length() == 0) {\n        paramBuffer.append(p.getName() + \"=\"\n                           + BaseUtils.UrlEncodeUnicode(p.getValue()));\n      } else {\n        paramBuffer.append(\"&\" + p.getName() + \"=\"\n                           + BaseUtils.UrlEncodeUnicode(p.getValue()));\n      }\n    }\n    newUrl = url + \"?\" + paramBuffer.toString();\n  } else {\n    newUrl = url;\n  }\n  request = new HttpGet(newUrl);\n} \n```\n2）对于post形式的HTTPRequest，要发送的数据都存在Body里面，也是以键值对的形式，所以代码编写与get情形完全不同，如下所示：\n\n```\nelse if (urlData.getNetType().equals(REQUEST_POST)) {\n  request = new HttpPost(url);\n  // 添加参数\n  if ((parameter != null) && (parameter.size() > 0)) {\n    final List<BasicNameValuePair> list = \n      new ArrayList<BasicNameValuePair>();\n    for (final RequestParameter p : parameter) {\n      list.add(new BasicNameValuePair(\n        p.getName(), p.getValue()));\n    }\n    ((HttpPost) request).setEntity(\n      new UrlEncodedFormEntity(list, HTTP.UTF_8));\n  }\n} \n```\n\n**2.HTTP Header**\n\n与Body相比，HTTP header就丰富的多了。它由很多键值对（key-value）组成，其中有些key是标准的，兼容于各大浏览器，比如：\n\n* accept\n* accept-language\n* referrer\n* user-agent\n* accept-encoding\n\n此外，我们还可以在MobileAPI端自定义一些键值对，然后要求App在调用MobileAPI时把这些信息传递过来。比如MobileAPI可以定义一个check-value这样的key，然后要求App将AppId（同一公司的不同App编号）、ClientType（Android还是iPhone、iPad）这些值拼接在一起经过MD5加密后，作为这个key的值传递给MobileAPI，然后由MobileAPI再去分析这些数据。\n对于App开发人员而言，只要按照MobileAPI的要求，把这些key所需要的值拼接成HTTPRequest头正确传递过去即可。如下所示：\n\n```\nvoid setHttpHeaders(final HttpUriRequest httpMessage)\n{\n  headers.clear();\n  headers.put(FrameConstants.ACCEPT_CHARSET, \"UTF-8,*\");\n  headers.put(FrameConstants.USER_AGENT, \n              \"Young Heart Android App \");\n  if ((httpMessage != null) && (headers != null))\n  {\n    for (final Entry<String, String> entry : headers.entrySet())\n    {\n      if (entry.getKey()!=null)\n      {\n        httpMessage.addHeader(entry.getKey(), entry.getValue());\n      }\n    }\n  }\n} \n```\n\n我们在组装Cookie之前调用setHttpHeaders方法：\n\n```\n// 添加必要的头信息\nsetHttpHeaders(request);\n// 添加Cookie到请求头中\naddCookie();\n// 发送请求\nresponse = httpClient.execute(request); \n```\n\n而在返回数据时，也可以从HTTP Response头中把所需要的数据解析出来。Android SDK将其封装成了若干方法以供调用。我们在下面的章节将会看到。\n\n前面我们介绍过Cookie，其实也是HTTP头的一部分。它的作用我们已经见识过了。下面将讨论HTTP头中的另几个重要字段。\n\n####2.5.2　时间校准\n\n接下来要介绍的是HTTP Response头中另一重要属性：Date，这个属性中记录了MobileAPI当时的服务器时间。\n\n为什么说这个属性很重要呢？App开发人员经常遇到的一个bug就是，App显示的时间不准，经常会因为时区问题前后差几个小时，而接到用户的投诉。\n\n为了解决这个问题，要从MobileAPI和App同时做一些工作。MobileAPI永远使用UTC时间。包括入参和返回值，都不要使用Date格式，而是减去UTC时间1970年1月1日的差值，这是一个long类型的长整数。\n\n在App端比较麻烦。这里我们只讨论中国，比如国内航班时间、电影上映时间等等，那么我们把MobileAPI返回的long型时间转换为GMT8时区的时间就万事大吉了——只需要额外加8个小时。无论使用的人身在哪个时区，他们看到的都应该是一个时间，也就是GMT8的时间。\n\n由于App本地时间会不准，比如前后差十几分钟，又比如设置了GMT9的时区，这样在取本地时间的时候，就会差一个小时。遇到这种情况，就要依赖于HTTP Response头的Date属性了。\n\n每调用一次MobileAPI，就取出HTTP Response头的Date值，转换为GMT时间后，再减去本地取出的时间，得到一个差值delta。这个值可能是因为手机时间不准而差出来的那十几分钟，也可能是因为时区不同导致的1个小时差值。我们将这个delta值保存下来。那么每当取本地当前时间的时候，再额外加上这个delta差值，就得到了服务器GMT8的时间，就做到了任何人看到的时间是一样的。\n\n因为App会频繁调用MobileAPI，所以这个delta值也会频繁更新，不用担心长期不调用MobileAPI而导致的这个delta值不太准的问题。\n\n接下来我们修改AndroidLib框架，以支持上述的这些功能。\n\n1）首先，在HTTPRequest类提供一个用于更新本地时间和服务器时间差值的方法updateDeltaBetweenServerAndClientTime，如下所示，由于我们在这里补上了UTC和GMT8相差的那8个小时，所以App其他地方不再需要考虑时差的问题，如下所示：\n\n```\nvoid updateDeltaBetweenServerAndClientTime() {\n  if (response != null) {\n    final Header header = response.getLastHeader(\"Date\");\n    if (header != null) {\n      final String strServerDate = header.getValue();\n      try {\n        if ((strServerDate != null) && !strServerDate.equals(\"\")) {\n          final SimpleDateFormat sdf = new SimpleDateFormat(\n            \"EEE, d MMM yyyy HH:mm:ss z\", Locale.ENGLISH);\n          TimeZone.setDefault(TimeZone.getTimeZone(\"GMT+8\"));\n          Date serverDateUAT = sdf.parse(strServerDate);\n          deltaBetweenServerAndClientTime = serverDateUAT\n            .getTime()\n            + 8 * 60 * 60 * 1000\n            - System.currentTimeMillis();\n        }\n      } catch (java.text.ParseException e) {\n        e.printStackTrace();\n      }\n    }\n  }\n} \n```\n\n我们会在发起MobileAPI网络请求得到响应结果后，执行该方法，更新这个差值：\n\n```\n// 发送请求\nresponse = httpClient.execute(request);\n// 获取状态\nfinal int statusCode = response.getStatusLine().getStatusCode();\n// 设置回调函数, 但如果requestCallback, 说明不需要回调, 不需要知道返回结果\nif ((requestCallback != null)) {\n  if (statusCode == HttpStatus.SC_OK) {\n    // 更新服务器时间和本地时间的差值\n    updateDeltaBetweenServerAndClientTime(); \n```\n\n因为我们的App会频繁的调用MobileAPI，所以为了避免频繁读写文件，我们没有将deltaBetweenServerAndClientTime存到本地文件，而是放在了内存中，当作一个全局变量来使用。\n\n2）我们把这个deltaBetweenServerAndClientTime方法暴露出来，供外界调用：\n\n```\npublic static Date getServerTime() {\n  return new Date(System.currentTimeMillis()\n                  + deltaBetweenServerAndClientTime);\n} \n```\n\n现在我们就可以模拟一个场景了。我把手机的时间改成任意一个值，然后再进入到WeatherByFastJsonActivity页面，因为页面加载的时候会调用MobileAPI获取天气的接口，所以本地会保存一个deltaBetweenServerAndClientTime差值。点击WeatherByFastJsonActivity页面上的“获取服务器时间”按钮，会因为我调用了AndroidLib中封装好的getServerTime方法，而弹出GMT8的当前时间：\n\n```\nbtnShowTime.setOnClickListener(new View.OnClickListener() {\n                               @Override\n                               public void onClick(View v) {\n  String strCurrentTime = Utils.getServerTime().toString();\n  new AlertDialog.Builder(WeatherByFastJsonActivity.this)\n    .setTitle(\"当前时间是：\").setMessage(strCurrentTime)\n    .setPositiveButton(\"确定\", null).show();\n}\n}); \n```\n\n####2.5.3　开启gzip压缩\n\nHTTP协议上的gzip编码是一种用来改进Web应用程序性能的技术。大流量的Web站点常常使用gzip压缩技术来减少传输量的大小，减少传输量大小有两个明显的好处，一是可以减少存储空间，二是通过网络传输时，可以减少传输的时间。\n\n使用gzip的流程如下：\n\n1）在App发起请求时，在HTTPRequest头中，添加要求支持gzip的key-value，这里的key是Accept-Encoding，value是gzip。如下所示，我们需要修改setHttpHeaders方法：\n\n```\nvoid setHttpHeaders(final HttpUriRequest httpMessage) {\n  headers.clear();\n  headers.put(FrameConstants.ACCEPT_CHARSET, \"UTF-8,*\");\n  headers.put(FrameConstants.USER_AGENT, \"Young Heart Android App \");\n  headers.put(FrameConstants.ACCEPT_ENCODING, \"gzip\");\n  if ((httpMessage != null) && (headers != null)) {\n    for (final Entry<String, String> entry : headers.entrySet()) {\n      if (entry.getKey() != null) {\n        httpMessage.addHeader(entry.getKey(), entry.getValue());\n      }\n    }\n  }\n} \n```\n\n2）MobileAPI的逻辑是，检查HTTP请求头中的Accept-Encoding是否有gzip值，如果有，就会执行gzip压缩。\n\n如果执行了gzip压缩，那么在返回值也就是HTTPResponse的头中，有一个content-encoding字段，会带有gzip的值；否则，就没有这个值。\n\n3）App检查HTTPResponse头中的content-encoding字段是否包含gzip值，这个值的有无，导致了App解析HTTPResponse的姿势不同，如下所示值，这个值的有无，导致了App解析HTTPResponse的姿势不同，如下所示（以下代码参见HTTPRequest这个类）：\n\n```\nString strResponse = \"\";\nif ((response.getEntity().getContentEncoding() != null)\n    && (response.getEntity().getContentEncoding()\n        .getValue() != null)) {\n  if (response.getEntity().getContentEncoding()\n      .getValue().contains(\"gzip\")) {\n    final InputStream in = response.getEntity()\n      .getContent();\n    final InputStream is = new GZIPInputStream(in);\n                                               strResponse = HttpRequest.inputStreamToString(is);\n    is.close();\n  } else {\n    response.getEntity().writeTo(content);\n    strResponse = new String(content.toByteArray()).trim();\n  }\n} else {\n  response.getEntity().writeTo(content);\n  strResponse = new String(content.toByteArray()).trim();\n} \n```"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第一章笔记.md",
    "content": "#《Android开发艺术探索》第一章笔记\n---\n\n注：此篇笔记只记录重难点，对于基础和详细内容请自行学习《Android开发艺术探索》。\n\n\n(1) onStart和onResume的区别是onStart可见，还没有出现在前台，无法和用户进行交互。onResume获取到焦点可以和用户交互。\n\n(2) 新Activity是透明主题时，旧Activity不会走onStop；\n\n(3)Activity切换时，旧Activity的onPause会先执行，然后才会启动新的Activity；\n\n(4)Activity在异常情况下被回收时，onSaveInstanceState方法会被回调，回调时机是在onStop之前，当Activity被重新创建的时候，onRestoreInstanceState方法会被回调，时序在onStart之后；\n\n(5)Activity的LaunchMode\n\na.\n standard 系统默认。每次启动会重新创建新的实例，谁启动了这个Activity，这个Activity就在谁的栈里。\n\nb.\n singleTop 栈顶复用模式。该Activity的onNewIntent方法会被回调，onCreate和onStart并不会被调用。\n\nc.\n singleTask 栈内复用模式。只要该Activity在一个栈中存在，都不会重新创建，onNewIntent会被回调。如果不存在，系统会先寻找是否存在需要的栈，如果不存在该栈，就创建一个任务栈，然后把这个Activity放进去；如果存在，就会创建到已经存在的这个栈中。\n\nd.\n singleInstance。具有此种模式的Activity只能单独存在于一个任务栈。\n\n(5) 标识Activity任务栈名称的属性：TaskAffinity，默认为应用包名。\n\n(6) IntentFilter匹配规则。\n\na.\n action匹配规则：要求intent中的action存在且必须和过滤规则中的其中一个相同 区分大小写；\n\nb.\n category匹配规则：系统会默认加上一个android.intent.category.DEAFAULT，所以intent中可以不存在category，但如果存在就必须匹配其中一个；\n\nc.\n data匹配规则：data由两部分组成，mimeType和URI，要求和action相似。如果没有指定URI，URI但默认值为content和file（schema）"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第三章笔记.md",
    "content": "#《Android开发艺术探索》第三章笔记\n---\n##View的基础知识\n\n* 什么是View\n\nView是Android中所有控件的基类，View是一种界面层的控件的一种抽象，它代表了一个控件，在Android设计中，ViewGroup也继承了View，这就意味着View本身就可以是单个控件也可以是多个控件组成的一组控件，通过这种关系就形成了View树的结构。\n\n* View的位置参数\n\nview的位置主要由它的四个顶点来决定，分别对应于View的四个属性：top、left、right、bottom，其中top是左上角纵坐标，left是左上角横坐标，right是右下角横坐标，bottom是右下角纵坐标\nView的宽高和坐标的关系：\n\nwidth = right - left;\n\nheight = bottom - top;\n\n如何得到这四个参数：\n\nLeft = getLeft();\n\nRight = getRight();\n\nTop = getTop();\n\nBottom = getBottom();\n\n从Android 3.0开始，view增加了x、y、translationX、translationY四个参数，这几个参数也是相对于父容器的坐标。x和y是左上角的坐标，而translationX和translationY是view左上角相对于父容器的偏移量，默认值都是0。\n\nx = left + translationX\n\ny = top + translationY\n\n* MotionEvent和TouchSlop\n\nMotionEvent:\n\n在手指触摸屏幕后所产生的一系列事件中，典型的时间类型有：\n\n1、ACTION_DOWN-手指刚接触屏幕\n\n2、ACTION_MOVE-手指在屏幕上移动\n\n3、ACTION_UP-手机从屏幕上松开的一瞬间\n\n正常情况下，一次手指触摸屏幕的行为会触发一系列点击事件，考虑如下几种情况：\n\n1、点击屏幕后离开松开，事件序列为 DOWN -> UP\n\n2、点击屏幕滑动一会再松开，事件序列为DOWN->MOVE->...->UP\n\n通过MotionEvent对象我们可以得到点击事件发生的x和y坐标，getX/getY返回的是相对于当前View左上角的x和y坐标，getRawX和getRawY是相对于手机屏幕左上角的x和y坐标。\n\nTouchSlop:\n\nTouchSlope是系统所能识别出的可以被认为是滑动的最小距离，获取方式是ViewConfiguration.get(getContext()).getScaledTouchSlope()。\n\n* VelocityTracker、GestureDetector和Scroller\n\n1、 VelocityTracker：用于追踪手指在滑动过程中的速度，包括水平和垂直方向上的速度。\n\nVelocityTracker的使用方式：\n\n\n\n\t//初始化\n\tVelocityTracker mVelocityTracker = VelocityTracker.obtain();\n\n\t//在onTouchEvent方法中\n\tmVelocityTracker.addMovement(event);\n\n\t//获取速度\n\tmVelocityTracker.computeCurrentVelocity(1000);\n\n\tfloat xVelocity = mVelocityTracker.getXVelocity();\n\t//重置和回收\n\n\tmVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用\n\n\tmVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用\n\t\n\t\n\n\n\n\n\n速度的计算公式：\n\n速度 = （终点位置 - 起点位置） / 时间段\n\n速度可能为负值，例如当手指从屏幕右边往左边滑动的时候。此外，速度是单位时间内移动的像素数，单位时间不一定是1秒钟，可以使用方法computeCurrentVelocity(xxx)指定单位时间是多少，单位是ms。例如通过computeCurrentVelocity(1000)来获取速度，手指在1s中滑动了100个像素，那么速度是100，即100(像素/1000ms)。如果computeCurrentVelocity(100)来获取速度，在100ms内手指只是滑动了10个像素，那么速度是10，即10(像素/100ms)。\n\n当不需要的时候，需要调用clear方法来重置并回收内存\n\n\n\n\tvelocityTracker.clear();\n\tvelocityTracker.recycler();\n\t\n\t\n\n\n2、GestureDetector\n\n手势检测，用于辅助检测用户的点击、滑动、长按、双击等行为。\n \n在日常开发中，比较常用的有:onSingleTapUp(单击)、onFling(快速滑动)、onScroll（拖动）、onLongPress(长按)、onDoubleTap(双击)，建议：如果只是监听滑动相关的事件在onTouchEvent中实现；如果要监听双击这种行为的话，那么就使用GestureDetector。\n\n3、Scroller\n\n弹性滑动对象，用于实现View的弹性滑动。Scroller本身无法让View弹性滑动，它需要和View的computeScroll方法配合使用才能共同完成这个功能。\n\n\n##View的滑动\n\n通过三种方式可以实现View的滑动\n\n* 第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动\n* 第二种是通过动画给View施加平移效果来实现滑动\n* 通过改变View的LayoutParams使得View重新布局从而实现滑动\n\n1、使用scrollTo/scrollBy\nscrollTo和scrollBy方法只能改变view内容的位置而不能改变view在布局中的位置。 scrollBy是基于当前位置的相对滑动，而scrollTo是基于所传参数的绝对滑动。通过View的getScrollX和getScrollY方法可以得到滑动的距离。\n\n2、使用动画\n使用动画来移动view主要是操作view的translationX和translationY属性，既可以使用传统的view动画，也可以使用属性动画，使用后者需要考虑兼容性问题，如果要兼容Android3.0一下版本系统的话推荐使用nineoldandroids。使用动画还存在一个交互问题：在android3.0以前的系统上，view动画和属性动画，新位置均无法触发点击事件，同时，老位置仍然可以触发单击事件。从3.0开始，属性动画的单击事件触发位置为移动后的位置，view动画仍然在原位置。\n\n3、改变布局参数\n通过改变LayoutParams的方式去实现View的滑动是一种灵活的方法。\n\n4、各种滑动方式的对比\n\n* scrollTo/scrollBy:操作简单，适合对View内容的滑动\n* 动画：操作简单，主要适用于没有交互的View和实现复杂的动画效果\n* 改变布局参数：操作稍微复杂，适用于有交互的View\n\n动画兼容库nineoldandroids中的ViewHelper类提供了很多的get/set方法来为属性动画服务，例如setTranslationX和setTranslationY方法，这些方法是没有版本要求的。\n\n##弹性滑动\n\n1、使用Scroller\nScroller的工作原理：Scroller本身并不能实现view的滑动，它需要配合view的computeScroll方法才能完成弹性滑动的效果，它不断地让view重绘，而每一次重绘距滑动起始时间会有一个时间间隔，通过这个时间间隔Scroller就可以得出view的当前的滑动位置，知道了滑动位置就可以通过scrollTo方法来完成view的滑动。就这样，view的每一次重绘都会导致view进行小幅度的滑动，而多次的小幅度滑动就组成了弹性滑动，这就是Scroller的工作原理。\n\n2、通过动画\n采用这种方法除了能完成弹性滑动以外，还可以实现其他动画效果，我们完全可以在onAnimationUpdate方法中加上我们想要的其他操作。\n\n3、使用延时策略\n使用延时策略来实现弹性滑动，它的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果，具体来说可以使用Handler的sendEmptyMessageDelayed(xxx)或view的postDelayed方法，也可以使用线程的sleep方法。\n\n##View的事件分发机制\n\n1、事件分发机制的三个重要方法\n\n* public boolean dispatchTouchEvent(MotionEvent ev)\n\n用来进行事件的分发。如果事件能够传递给当前的View，那么此方法一定会被调用，返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响，表示是否消耗当前事件。\n\n* public boolean onInterceptTouchEvent(MotionEvent event)\n\n在上述方法内部调用，用来判断是否拦截某个事件，如果当前View拦截了某个事件，那么在同一个事件序列当中，此方法不会被再次调用，返回结果表示是否拦截当前事件。\n\n* public boolean onTouchEvent(MotionEvent event)\n\n在dispatchTouchEvent方法中调用，用来处理点击事件，返回结果表示是否消耗当前的事件，如果不消耗，则在同一个事件序列中，当前View无法再次接受到事件。\n\n这三个方法的关系可以用如下伪代码表示：\n\n\n\tpublic boolean dispatchTouchEvent(MotionEvent event)\n\t{\n\t\tboolean consume = false;\n\t\tif(onInterceptTouchEvent(ev))\n\t\t{\n\t\t\tconsume = onTouchEvent(ev);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tconsume = child.dispatchTouchEvent(ev);\n\t\t}\n\t\treturn consume;\n\t}\n\t\n\n\n我们可以大致了解点击事件的传递规则：对于一个根ViewGroup来说，点击事件产生后，首先会传递给它，这时它的dispatchTouchEvent会被调用，如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件，接着事件就会交给这个ViewGroup处理，即它的onTouchEvent方法就会被调用；如果这个ViewGroup的onInterceptTouchEvent方法返回false就表示它不拦截当前事件，这时当前事件就会继续传递给它的子元素，接着子元素的dispatchTouchEvent方法就会被调用，如此反复直到事件被最终处理。\n\n####OnTouchListener的优先级比onTouchEvent要高\n\n如果给一个view设置了OnTouchListener，那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值，如果返回false，那么当前view的onTouchEvent方法会被调用；如果返回true，那么onTouchEvent方法将不会被调用。\n在onTouchEvent方法中，如果当前view设置了OnClickListener，那么它的onClick方法会被调用，所以OnClickListener的优先级最低。\n\n当点击一个事件产生后，它的传递过程遵循如顺序，Activity->Window->View\n\n如果一个View的onTouchEvent方法返回false，那么它的父容器的onTouchEvent方法将会被调用，依次类推，如果所有的元素都不处理这个事件，那么这个事件将会最终传递给Activity处理（调用Activity的onTouchEvent方法）\n\n关于事件传递的机制，给出一些结论：\n\n* 同一个事件序列是以down事件开始，中间含有数量不定的move事件，最终以up事件结束\n* 正常情况下，一个事件序列只能被一个View拦截且消耗。一旦一个元素拦截了某次事件，那么同一个事件序列内的所有事件都会直接交给它处理，因此同一个事件序列中的事件不能分别由两个View同时处理，但是通过特殊手段可以做到，比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理\n* 某个View一旦开始处理事件，如果它不消耗ACTION_DOWN事件，那么同一事件序列的其他事情都不会再交给它来处理，并且事件将重新交给它的父容器去处理（调用父容器的onTouchEvent方法）；如果它消耗ACTION_DOWN事件，但是不消耗其他类型事件，那么这个点击事件会消失，父容器的onTouchEvent方法不会被调用，当前view依然可以收到后续的事件，但是这些事件最后都会传递给Activity处理。\n* ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false，View没有onInterceptTouchEvent方法，一旦有点击事件传递给它，那么它的onTouchEvent方法就会调用。\n* View的onTouchEvent默认都会消耗事件（返回true），除非它是不可点击的（clickable和longClickable同时为false）。View的longClickable属性默认都为false，clickable要分情况，比如Button的clickable属性默认为true，而TextView的clickable属性默认为false。\n* View的enable属性不影响onTouchEvent的默认返回值，哪怕一个View是disable状态的，只要它的clickable或者longClickable有一个为true，那么它的onTouchEvent就返回true\n* 事件传递过程总是先传递给父元素，然后再由父元素分发给子view，通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程，但是ACTION_DOWN事件除外，即当面对ACTION_DOWN事件时，ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件。\n\n##View的滑动冲突\n\n1、常见的滑动冲突场景\n\n* 外部滑动方向与内部滑动方向不一致，比如ViewPager中包含ListView\n* 外部滑动方向与内部滑动方向一致\n* 上面两种情况的嵌套\n\n2、滑动冲突的处理规则\n\n可以根据滑动距离和水平方向形成的夹角；或者根据水平和竖直方向滑动的距离差；或者两个方向上的速度差等。\n\n3、滑动冲突的解决方式\n\n* 外部拦截法\n\n点击事件都经过父容器的拦截处理，如果父容器需要此事件就拦截，如果不需要此事件就不拦截，该方法需要重写父容器的onInterceptTouchEvent方法，在内部做相应的拦截即可，伪代码如下：\n\n\tpublic boolean onInterceptTouchEvent(MotionEvent event) {\n    \tboolean intercepted = false;\n    \tint x = (int) event.getX();\n   \t\tint y = (int) event.getY();\n\n    \tswitch (event.getAction()) {\n    \tcase MotionEvent.ACTION_DOWN: {\n    \t    intercepted = false;\n    \t    break;\n    \t}\n    \tcase MotionEvent.ACTION_MOVE: {\n     \t   int deltaX = x - mLastXIntercept;\n     \t   int deltaY = y - mLastYIntercept;\n     \t   if (父容器需要拦截当前点击事件的条件，例如：Math.abs(deltaX) > Math.abs(deltaY)) {\n       \t     intercepted = true;\n       \t } else {\n        \t    intercepted = false;\n      \t  }\n       \t break;\n    \t}\n    \tcase MotionEvent.ACTION_UP: {\n     \t   intercepted = false;\n        \tbreak;\n    \t}\t\n    \tdefault:\n        \tbreak;\n    \t}\n\n    \tmLastXIntercept = x;\n    \tmLastYIntercept = y;\n\n    \treturn intercepted;\n\t}\n\n* 内部拦截法\n\n父容器不拦截任何事件，所有的事件都传递给子元素，如果子元素需要此事件就直接消耗掉，否则就由父容器进行处理，这种方法和Android中的事件分发机制不一样，需要配合requestDisallowInterceptTouchEvent方法才能正常工作。\n\n\tpublic boolean dispatchTouchEvent(MotionEvent event) {\n    \tint x = (int) event.getX();\n    \tint y = (int) event.getY();\n\n    \tswitch (event.getAction()) {\n    \tcase MotionEvent.ACTION_DOWN: {]\n    \t    getParent().requestDisallowInterceptTouchEvent(true);\n        \tbreak;\n    \t}\n    \tcase MotionEvent.ACTION_MOVE: {\n        \tint deltaX = x - mLastX;\n        \tint deltaY = y - mLastY;\n        \tif (当前view需要拦截当前点击事件的条件，例如：\tMath.abs(deltaX) > Math.abs(deltaY)) {\n            \t\tgetParent().requestDisallowInterceptTouchEvent(false);\n        \t}\n        \tbreak;\n    \t}\n    \tcase MotionEvent.ACTION_UP: {\n        \tbreak;\n    \t}\n    \tdefault:\n        \tbreak;\n    \t}\n\n    \tmLastX = x;\n    \tmLastY = y;\n    \treturn super.dispatchTouchEvent(event);\n\t}\n\t\n\t\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第二章笔记.md",
    "content": "##《Android开发艺术探索》第二章笔记\n---\n\n##IPC\n* Inter-Process Communication的缩写。含义为进程间通信或跨进程通信，是指两个进程之间进行数据交换的过程。\n\n##进程和线程的区别\n* 按照操作系统的描述，线程是CPU调度的最小单元，同时线程是一种有限的系统资源。\n* 进程一般指一个执行单元，在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程，因此进程和线程是包含与被包含的关系。\n\n##多进程分为两种\n* 第一种情况是一个应用因为某些原因自身需要采用多线程模式来实现。\n* 另一种情况是当前应用需要向其他应用获取数据\n\n##Android中的多进程模式\n通过给四大组件指定android:process属性，我们可以开启多线程模式\n\n* 进程名以\":\"开头的进程属于当前应用的私有进程，其他应用的组件不可以和它跑在同一进程，而进程名不以\":\"开头的进程属于全局进程，其他应用通过ShareUID方式可以和它跑在同一个进程中。\n* Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据，两个应用通过ShareUID跑在同一个进程中是有要求的，需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下，它们可以互相访问对方的私有数据，比如data目录、组件信息等，不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中，那么除了能共享data目录、组件信息，还可以共享内存数据，或者说它们看起来就像是一个应用的两个部分。\n\n##多进程模式的运行机制\n*  Android为每一个应用分配了一个独立的虚拟机，或者说为每个进程都分配了一个独立的虚拟机，不同的虚拟机在不同的内存分配上有不同的地址空间，这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。\n* 所有运行在不同进程中的四大组件，只要它们之间需要通过内存来共享数据，都会共享失败。\n\n一般来说，使用多进程会造成如下几个方面的问题：\n\n* 静态成员和单例模式完全失效\n* 线程同步机制完全失效\n\n\n不管是锁对象还是锁全局类都无法保证线程同步，因为不同进程锁的不是同一个对象\n\n* SharedPreference的可靠性下降\n\nSharedPreferences不支持两个进程同时去执行写操作，否则会导致一定几率的数据丢失，这时因为SharedPreferences底层是通过读写XML文件来实现的，并发写显然是可能出问题的，甚至并发读写都有可能发生问题\n\n* Application会多次创建\n\n运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的。同理，运行在不同进程中的组件是属于两个不同的虚拟机和Application的。\n\n##IPC基础概念介绍\n* Serializable\n\n是Java所提供的一个序列化接口，它是一个空接口，为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单，只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。\n\n```\n\nprivate static final long serialVersionUID = 8711368828010083044L\n\n```\n通过Serializable方来实现对象的序列化，如下代码：\n```\n\n//序列化过程\nUser user = new User(0, \"jake\", true);\nObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(\"cache.txt\"));\nout.writeObject(user);\nout.close();\n\n//反序列化过程\nObjectInputStream in = new ObjectInputStream(new FileInputStream(\"cache.txt\"));\nUser newUser = (User)in.readObject();\nin.close();\n\n```\n\n原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同时才能够正常的被反序列化。serialVersionUID的详细工作机制是这样的：序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中（也可能是其他的中介），当反序列化的时候系统会去检测文件中的serialVersionUID，看它是否和当前类的serialVersionUID一致，如果一致就说明序列化的类的版本和当前类的版本是相同的，这个时候可以成功反序列化，否则就说明当前类和序列化的类相比发生了某些变换\n\n给serialVersionUID制定为1L或者采用Eclipse根据当前类结构去生成的hash值，这两者并没有本质区别。\n\n*  静态成员变量属于类不属于对象，所以不会参与序列化过程\n*  其次用transient关键字标记的成员变量不参与序列化过程\n\n\n##Parcelable接口\nParcelable也是一个接口，只要实现这个接口，一个类的对象就可以实现序列化并可以通过Intent的Binder传递\nParcelable的方法说明：\n\n| 方法        | 功能           | 标记位  |\n| ------------- |:-------------:| -----:|\n| createFromParcel(Parcel in)      | 从序列化的对象中创建原始对象 |  |\n| newArray[int size]      | 创建指定长度的原始对象数组      |    |\n| User(Parcel in) |  从序列化的对象中创建原始对象      |     |\n| write ToParcel(Parcel out, int flags) | 将当前对象写入序列化结构中，其中flags标识有两种值0或1（参见右侧标记位）。为1时标识当前对象需要作为返回值返回，不能立即释放资源，几乎所有情况都为0      |    PARCELABLE_WRITE_RETURN_VALUE |\n| describeContents | 返回当前对象的内容描述。如果含有文件描述符，返回1（参见右侧标记位），否则返回0，几乎所有的情况都返回0      |    CONTENTS_FILE_DESCRIPTOR |\n\n\n* 系统已经为我们提供了许多实现了Parcelable接口的类，它们都是可以直接序列化的，比如Intent、Bundle、Bitmap等，同时List和Map也可以序列化，前提是它们里面的每个元素都是可序列化的。\n\n##如何选取\nSerializable是Java中的序列化接口，其使用起来简单但是开销很大，序列化和反序列化需要大量I/O操作。而Parceleble是Android中的序列化方式，因此更适合在Android平台上，缺点是麻烦，但是效率高，这是Android推荐的序列化方式，所以我们要首选Parcelable。Parcelable主要用在内存序列化上，通过Parcelable将对象序列化到存储设备中或者将对象序列化之后通过网络传输，但是过程稍显复杂，因此在这两种情况下建议大家使用Serializable。\n\n\n##Binder\n* 继承了IBinder接口\n* Binder是一种跨进程通信方式\n* 是ServiceManager连接各种Manager（ActivityManager,WindowManager等）和相应ManagerService的桥梁\n* 从Android应用层来说，Binder是客户端和服务端进行通信的媒介，当bindService的时候，服务器会返回一个包含了服务器端业务调用的Binder对象，通过这个Binder对象，客户端就可以获取服务端提供的服务或者是数据，这里的服务包含了普通服务和基于AIDL的服务\n\naidl工具根据aidl文件自动生成的java接口的解析：首先，它声明了几个接口方法，同时还声明了几个整型的id用于标识这些方法，id用于标识在transact过程中客户端所请求的到底是哪个方法；接着，它声明了一个内部类Stub，这个Stub就是一个Binder类，当客户端和服务端都位于同一个进程时，方法调用不会走跨进程的transact过程，而当两者位于不同进程时，方法调用需要走transact过程，这个逻辑由Stub内部的代理类Proxy来完成。\n所以，这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。 下面分析其中的方法：\n\n* asInterface(android.os.IBinder obj):用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象，这种转换过程是区分进程的，如果客户端和服务端是在同一进程中，那么这个方法返回的是服务端的Stub对象本身，否则返回的是系统封装的Stub.Proxy对象。\n* asBinder:返回当前Binder对象\n* onTransact：这个方法运行在服务端中的Binder线程池中，当客户端发起跨进程请求时，远程请求会通过系统底层封装后交由此方法来处理。\n这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)\n服务端通过code可以知道客户端请求的目标方法，接着从data中取出所需的参数，然后执行目标方法，执行完毕之后，将结果写入到reply中。如果此方法返回false，说明客户端的请求失败，利用这个特性可以做权限验证(即验证是否有权限调用该服务)。\n* Proxy#[Method]：代理类中的接口方法，这些方法运行在客户端，当客户端远程调用此方法时，它的内部实现是：首先创建该方法所需要的参数，然后把方法的参数信息写入到_data中，接着调用transact方法来发起RPC请求，同时当前线程挂起；然后服务端的onTransact方法会被调用，直到RPC过程返回后，当前线程继续执行，并从_reply中取出RPC过程的返回结果，最后返回_reply中的数据。\n\n######首先，当客户端发起远程请求时，由于当前线程会被挂起直至服务端进程返回数据，所以如果一个远程方法是很耗时的，那么不能在UI线程发起此远程请求；其次，由于服务端的Binder方法运行在Binder的线程池中，所以不管Binder是否耗时都应该采用同步的方式去实现，因为它已经运行在一个线程中了。\n\n###Binder两种重要的方法\n1. linkToDeath\n2. unlinkToDeath\nBinder运行在服务端，如果由于某种服务端异常终止了的话会导致客户端的远程调用失败、所以Binder提供了两个配对的方法linkToDeath和unlinkToDeath，通过linkToDeath方法可以给Binder设置一个死亡代理，当Binder死亡的时候客户端就会收到通知，然后就可以重新发起连接从而恢复连接了。\n###如何给Binder设置死亡代理\n1、声明一个DeathRecipient对象、DeathRecipient是一个接口，其内部只有一个方法bindDied，实现这个方法就可以在Binder死亡的时候收到通知了。\n\n```\n\nprivate IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {\n    @Override\n    public void binderDied() {\n        if (mRemoteBookManager == null) return;\n        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);\n        mRemoteBookManager = null;\n        // TODO:这里重新绑定远程Service\n    }\n};\n\n```\n\n2、在客户端绑定远程服务成功之后，给binder设置死亡代理\n\n```\n\nmRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);\n\n```\n\n##Android的IPC方式\n\n1、 使用Bundle\n\nBundle实现了Parcelable接口，Activity、Service和Receiver都支持在Intent中传递Bundle数据\n\n2、 使用文件共享\n\n这种方式简单，适合在对数据同步要求不高的进程之间进行通信，并且要妥善处理并发读写的问题，SharedPreferences是一个特例，虽然它也是文件的一种，但是由于系统对它的读写有一定的缓存策略，即在内存中会有一份SharedPreferences文件的缓存，因此在多进程模式下、系统对它的读写就变的不可靠，当面对高并发读写访问的时候，有很大几率会丢失，因此，不建议在进程间通信中使用SharedPreferences。\n\n3、 使用Messenger\n\nMessenger是一种轻量级的IPC方案，它的底层实现就是AIDL。Messenger是以串行的方式处理请求的，即服务端只能一个个处理，不存在并发执行的情形。\n\n4、 使用AIDL\n\n大致流程：首先建一个Service和一个AIDL接口，接着创建一个类继承自AIDL接口中的Stub类中的抽象方法，在Service的onBind方法中返回这个类的对象，然后客户端就可以绑定服务端Service，建立连接后就可以访问远程服务端的方法了。\n1.AIDL支持的数据类型：基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL；\n2.某些类即使和AIDL文件在同一个包中也要显式import进来；\n3.AIDL中除了基本数据类，其他类型的参数都要标上方向：in、out或者inout；\n4.AIDL接口中支持方法，不支持声明静态变量；\n5.为了方便AIDL的开发，建议把所有和AIDL相关的类和文件全部放入同一个包中，这样做的好处是，当客户端是另一个应用的时候，可以直接把整个包复制到客户端工程中。\n6.RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型，支持管理任意的AIDL接口，因为所有的AIDL接口都继承自IInterface接口。\n\n5、使用ContentProvider\n\n1.ContentProvider主要以表格的形式来组织数据，并且可以包含多个表；\n2.ContentProvider还支持文件数据，比如图片、视频等，系统提供的MediaStore就是文件类型的ContentProvider；\n3.ContentProvider对底层的数据存储方式没有任何要求，可以是SQLite、文件，甚至是内存中的一个对象都行；\n4.要观察ContentProvider中的数据变化情况，可以通过ContentResolver的registerContentObserver方法来注册观察者；\n\n6、使用Socket\n\n套接字，分为流式套接字和用户数据报套接字两种，分别对应于网络的传输控制层中TCP和UDP协议。\n\n* TCP协议是面向连接的协议，提供稳定的双向通信功能，TCP连接的建立需要经过\"三次握手\"才能完成，为了提供稳定的数据传输功能，其本身提供了超时重传功能，因此具有很高的稳定性\n* UDP是无连接的，提供不稳定的单向通信功能，当然UDP也可以实现双向通信功能，在性能上，UDP具有更好的效率，其缺点是不保证数据能够正确传输，尤其是在网络拥塞的情况下。\n\n##Binder连接池\n* 当项目规模很大的时候，创建很多个Service是不对的做法，因为service是系统资源，太多的service会使得应用看起来很重，所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是：每个业务模块创建自己的AIDL接口并实现此接口，这个时候不同业务模块之间是不能有耦合的，所有实现细节我们要单独开来，然后向服务端提供自己的唯一标识和其对应的Binder对象；对于服务端来说，只需要一个Service，服务端提供一个queryBinder接口，这个接口能够根据业务模块的特征来返回相应的Binder对象给它们，不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。\nBinder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行，从而避免了重复创建Service的过程。\n* 建议在AIDL开发工作中引入BinderPool机制。\n\n##选用合适的IPC方式\n![Mou icon](http://hujiaweibujidao.github.io/images/androidart_ipc.png)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第八章笔记.md",
    "content": "##理解Window和WindowManager\n\nWindow是一个抽象类，它的具体实现是PhoneWindow。WindowManager是外界访问Window的入口，Window的具体实现位于WindowManagerService中，WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现的，不管是Activity、Dialog还是Toast，它们的视图实际上都是附加在Window上的，因此Window实际是View的直接管理者。\n\n###8.1 Window和WindowManager\n\n为了分析Window的工作机制，先通过代码了解如何使用WindowManager添加一个Window，下面一段代码将一个Button添加到屏幕坐标为(100, 300)的位置上\n\n```\nmFloatingButton = new Button(this);\nmFloatingButton.setText(\"test button\");\nmLayoutParams = new WindowManager.LayoutParams(\n        LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,\n        PixelFormat.TRANSPARENT);//0,0 分别是type和flags参数，在后面分别配置了\nmLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL\n        | LayoutParams.FLAG_NOT_FOCUSABLE\n        | LayoutParams.FLAG_SHOW_WHEN_LOCKED;\nmLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;\nmLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;\nmLayoutParams.x = 100;\nmLayoutParams.y = 300;\nmFloatingButton.setOnTouchListener(this);\nmWindowManager.addView(mFloatingButton, mLayoutParams);\n```\nFlags参数表示Window的属性，以下列举常用的选项：\n\n* FLAG_NOT_FOCUSABLE：表示Window不需要获取焦点，也不需要接收各种输入事件，此标记会同时启动FLAG_NOT_TOUCH_MODEL，最终事件会传递给下层的具有焦点的Window\n* FLAG_NOT_TOUCH_MODAL：在此模式下，系统会将当前Window区域以外的单击事件传递给底层的Window，当前Window区域以内的单击事件则自己处理。这个标记很重要，一般来说都需要开启此标记，否则其他Window将无法收到单击事件。\n* FLAG_SHOW_WHEN_LOCKED：开启此模式可以让显示在锁屏的界面\n\n\n\n\nType参数表示Window的类型，Window有三种类型，分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在，它需要附属在特定的父Window之中，比如常见的一些Dialog就是一个子Window。系统Window是需要声明权限才能创建的Window，比如Toast和系统状态栏这些都是系统Window。\n\nWindow是分层的，每个Window都有对应的z-ordered，层级最大的会覆盖在层级小的Window上面，这和HTML中的z-index的概念是完全一致的。在三类Window中，应用Window的层级范围是1~99，子Window的层级范围是1000~1999，系统Window的层级范围是2000~2999，这些层级属性范围对应着WindowManager.LayoutParams的type参数。\n\n如果采用TYPE_SYSTEM_ERROR，只需要为type参数指定这个层级即可：\n\nmLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR\n\n同时声明权限：\n\n<uses-permissionandroid:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n\nWindowManager所提供的功能很简单，常用的只有三个方法，即添加View、更新View和删除View，这三个方法定义在ViewManager中，而WindowManager继承了ViewManager。\n\n###8.2 Window的内部机制\n\nWindow是一个抽象的概念，并不是实际存在的，它是以View的形式存在，每一个Window都对应着一个View和一个ViewRootImpl，Window和View通过ViewRootImpl来建立联系。在实际使用中无法直接访问Window，对Window的访问必须通过WindowManager。\n\n####8.2.1 Window的添加过程\n\nWindow的添加过程需要通过WindowManager的addView来实现，WindowManager是一个接口，它的真正实现是WindowManagerImpl类。WindowManager的实现类对于addView、updateView和removeView方法都是委托给WindowManagerGlobal类。\n\nWindowManagerGlobal的addView方法分为如下几步：\n\n1. 检查参数是否合法，如果是子Window那么还需要调整一些布局参数\n2. 创建ViewRootImpl并将View添加到列表中\n3. 通过ViewRootImpl来更新界面并完成Window的添加过程\n\n####8.2.2 Window的删除过程\n\n和添加过程一样，都是先通过WindowManagerImpl后，再进一步通过WindowManagerGlobal来实现的。\n真正删除View的逻辑在dispatchDetachedFromWindow方法的内部实现。dispatchDetachedFromWindow方法主要做四件事：\n\n1. 垃圾回收的工作，比如清除数据和消息，移除回调。\n2. 通过Session的remove方法删除Window，mWindowSession.remove(mWindow)，这同样是一个IP C过程，最终会调用WindowManagerService的removeWindow方法\n3. 调用View的dispatchDetachedFromWindow方法，在内部调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。\n4. 调用WindowManagerGlobal的doRemoveView方法刷新数据，包括mRoots、mParams以及mDyingViews，需要将当前Window所关联的这三类对象从列表中删除。\n\n####8.2.3 Window的更新过程\n\n首先需要更新View的LayoutParams并替换掉老的LayoutParams，接着再更新ViewRootImpl中的LayoutParams，这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTrversals方法来对View重新布局，包括测量、布局、重绘三个过程。除了View本身的重绘以外，ViewRootImpl还会通过WindowSession来更新Window的视图，这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的，同样是一个IPC过程。\n\n##Window的创建过程\n\n###8.3.1 Activity的Window创建过程\n\n1、Activity的启动过程很复杂，最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程，在这个方法内部会通过类加载器创建Activity的实例对象，并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量。\n\n2、Activity实现了Window的Callback接口，当Window接收到外界的状态变化时就会调用Activity的方法，例如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等。\n\n3、Activity的Window是由PolicyManager来创建的，它的真正实现是Policy类，它会新建一个PhoneWindow对象，Activity的setContentView的实现是由PhoneWindow来实现的。\nPhoneWindow方法大致遵循如下几个步骤：\n\n1. 如果没有DecorView，那么就创建它\n2. 将View添加到DecorView的mContentParent中\n3. 回调Activity的onCreateChanged方法通知Activity视图已经发生改变\n\n###8.3.2 Dialog的Window创建过程\n\nDialog的Window的创建过程和Activity类似，有如下步骤：\n\n1. 创建Window:Diolog中Window的创建同样是通过PolicyManager的makeNewWindow方法来完成的，创建后的对象实际上就是PhoneWindow。\n2. 初始化DecorView并将Dialog的视图添加到DecorView中\n3. 将DecorView添加到Window中并显示：普通的Dialog有一个特殊之处，就是必须采用Activity的Context，如果采用Application的Context，那么就会报错。应用token只有Activity拥有，所以这里只需要Activity作为Context来显示对话框即可。\n\n###8.3.3 Toast的Window创建过程\n\n在Toast的内部有两类IPC过程，第一类是Toast访问NotificationManagerService，第二类是NotificationManagerService回调Toast里的TN接口。\n\nToast属于系统Window，它内部的视图由两种方式指定：一种是系统默认的演示，另一种是通过setView方法来指定一个自定义的View\n\nToast具有定时取消功能，所以系统采用了Handler。Toast的显示和隐藏是IPC过程，都需要NotificationManagerService（NMS）来实现，在Toast和NMS进行IPC过程时，NMS会跨进程回调Toast中的TN类中的方法，TN类是一个Binder类，运行在Binder线程池中，所以需要通过Handler将其切换到当前发送Toast请求所在的线程，所以Toast无法在没有Looper的线程中弹出。\n\n对于非系统应用来说，mToastQueue最多能同时存在50个ToastRecord，这样做是为了防止DOS(Denial of Service，拒绝服务)。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第十五章笔记.md",
    "content": "#Android性能优化\n---\nAndroid不可能无限制的使用内存和CPU资源，过多的使用内存会导致内存溢出，即OOM。而过多的使用CPU资源，一般是指做大量的耗时任务，会导致手机变的卡顿甚至出现程序无法响应的情况，即ANR。\n\n###15.1.1布局优化\n\n1、如何进行布局优化？\n\n* 首先删除布局中无用的控件和层级\n* 其次有选择的使用性能较低的ViewGroup。\n* 布局优化的另一种手段是采用<include>标签、<merge>标签、ViewStub。<include>标签主要用于布局重用，<merge>标签一般和<include>配合使用，它可以降低减少布局的层级，而ViewStub则提供了按需加载的功能，当需要时才会将ViewStub中的布局加载到内存，提高了程序的初始化效率。\n\n2、<include>标签只支持android:layout_开头的属性，android:id属性例外。\n\n3、ViewStub继承了View，它非常轻量级且宽/高都是0，因此它本身并不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件，在实际开发中，有很多布局文件在正常情况下不会显示，比如网络异常时的界面，这个时候就没有必要在整个界面初始化的时候将其加载起来，通过ViewStub就可以做到在使用的时候再加载，提高了程序初始化的性能。\n\n如下所示，android:id是ViewStub的id，而android:inflatedId是布局的根元素的id。\n\n```\n<ViewStub android:id=\"@+id/xxx\"\n  android:inflatedId=\"@+id/yyy\"\n  android:layout=\"@layout/zzz\"\n  ...\n</ViewStub>\n```\n\n需要加载ViewStub中的布局的时候，可以按照如下两种方式进行：\n\n```\n((ViewStub)findViewById(R.id.xx)).setVisibility(View.VISIBLE);\n```\n\n或者\n\n```\nView importPanel = ((ViewStub)findViewById(R.id.stub_import)).inflate();\n```\n\n###15.1.1绘制优化\n\n绘制优化是指View的onDraw方法要避免执行大量的操作：\n\n* 在onDraw中不要创建新的局部对象，这是因为onDraw方法可能会被频繁调用，这样就会在一瞬间产生大量的临时对象，这不仅占用了过多的内存而且还会导致系统更加频繁的gc，降低了程序的执行效率。\n* onDraw方法中不要指定耗时任务，也不能执行成千上万次的循环操作，View的绘制帧率保证60fps是最佳的，这就要求每帧的绘制时间不超过16ms，虽然程序很难保证16ms这个时间，但是尽量降低onDraw方法的复杂度总是切实有效的。\n\n\n###15.1.3内存泄漏优化\n\n可能导致内存泄漏的场景很多，例如静态变量、单例模式、属性动画、AsyncTask、Handler等等\n\n\n###15.1.4响应速度优化和ANR日志分析\n\n1. ANR出现的情况：Activity如果5秒内没有响应屏幕触摸事件或者键盘输入事件就会ANR。而BroadcastReceiver如果10s没有执行完操作也会出现ANR。\n2. 当一个进程发生了ANR之后，系统会在/data/anr目录下创建一个文件traces.txt，通过分析这个文件就能定位ANR的原因。\n\n###15.1.5ListView和Bitmap优化\n\n1. ListView优化：采用ViewHolder并避免在getView方法中执行耗时操作；根据列表的滑动状态来绘制任务的执行效率；可以尝试开启硬件加速期来使ListView的滑动更加流畅。\n2. Bitmap优化：根据需要对图片进行采样，主要是通过BitmapFactory.Options来根据需要对图片进行采样，采样主要用到了BitmapFactory.Options的inSampleSize参数。\n\n###15.1.6线程优化\n\n1. 采用线程池，避免程序中存在大量的Thread。线程池可以重用内部的线程，从而避免了线程的创建和销毁所带来的性能开销，同时线程池还能有效的控制线程池的最大并发数，避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。\n\n###15.1.7一些性能优化建议\n\n* 避免 创建过多的对象\n* 不要过多的使用枚举，枚举占用的内存空间要比整形大\n* 常量请用static final来修饰\n* 使用一些Android特有的数据结构，比如SparseArray和Pair等，它们都具有更好的性能\n* 适当使用软引用和弱引用\n* 采用内存缓存和磁盘缓存\n* 尽量采用静态内部类，这样可以避免潜在的由于内部类而导致的内存泄漏\n\n\n###15.2内存泄漏分析之MAT工具\n\nMAT是功能强大的内存分析工具，主要有Histograms和Dominator Tree等功能\n\n\n###15.3提高程序的可维护性\n\n1. 命名要规范，要能正确地传达出变量或者方法的含义，少用缩写，关于变量的前缀可以参考Android源码的命名方式，比如私有成员以m开头，静态成员以s开头，常量则全部用大写字母表示，等等。\n2. 代码的排版上需要留出合理的空白来区分不同的代码块，其中同类变量的声明要放在一起，两类变量之间要留出一行空白作为区分。\n3. 合理的命名风格，仅在非常关键的代码添加注释。\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Android开发艺术探索》第四章笔记.md",
    "content": "##View的工作原理\n---\n\n###4.1 初识ViewRoot和DecorView\n\n1、ViewRoot对应于ViewRootImpl类，它是连接WindowManager和DecorView的纽带，View的三大流程均是通过ViewRoot来完成的。在ActivityThread中，当Activity对象被创建完毕后，会将DecorView添加到Window中，同时会创建ViewRootImpl对象，并将ViewRootImpl对象和DecorView建立连接。\n\n2、View的绘制流程是从ViewRoot的performTraversals方法开始的，它经过measure、layout、draw三个过程才能最终将一个View绘制出来，其中measure用来测量View的宽和高，layout用来确定View在父容器的放置位置，而draw则负责将View绘制在屏幕上。\n\n3、performTraversals会依次调用performMeasure、performLayout、performDraw三个方法，这三个方法分别完成顶级View的measure、layout和draw这三大流程，其中performMeasure会调用measure方法，在measure方法中又会调用onMeasure方法，在onMeasure方法中对所有的子元素进行measure过程，这个时候measure流程就会从父容器传递到子元素中了，这样就完成了一次measure过程。接着子元素就会重复父容器的measure过程，如此反复就完成了整个View树的遍历。\n\n4、measure过程中决定了View的宽/高，Measure完成以后，可以通过getMeasureWidth和getMeasureHeight方法来获取到View测量后的宽/高，在几乎所有的情况下它都等同于View最终的宽高。layout决定了View的四个顶点的坐标和View的实际的宽高，通过getWidth和getHeight方法可以获得最终的宽高。draw过程决定了View的显示。\n\n5、DecorView其实是一个FrameLayout，其中包含了一个竖直方向的LinearLayout，上面是标题栏，下面是内容栏（id为android.R.id.content）。View层的事件都先经过DecorView，然后才传给我们的View。\n\n###理解MeasureSpec\n\n1、MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的内存分配，为了方便操作，其提供了打包和解包方法。SpecMode和SpecSize也是一个int值，一组SpecMode和SpecSize可以打包为一个MeasureSpec，而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。\n\nSpecMode有三类，每一类都表示特殊的含义：\n\n1. UNSPECIFIED   父容器不对View有任何的限制，要多大给多大，这种情况下一般用于系统内部，表示一种测量的状态。\n2. EXACTLY   父容器已经检测出View所需要的精确大小，这个时候View的最终大小就是SpecSize所指定的值，它对应于LayoutParams中的match_parent和具体的数值这两种模式\n3. AT_MOST   父容器指定了一个可用大小即SpecSize，View的大小不能大于这个值，具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content\n\n2、MeasureSpec和LayoutParams的对应关系\n\n在View测量的时候系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec，然后再根据这个MeasureSpec来确定View测量后的宽高。\n\nMeasureSpec不是唯一由LayoutParams决定的，LayoutParams需要和父容器一起才能决定View的MeasureSpec，从而进一步确定View的宽高。对于DecorView，它的MeasureSpec由窗口的尺寸和其自身的LayoutParams来决定；对于普通View，它的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定\n\n3、当view采用固定宽高时，不管父容器的MeasureSpec是什么，view的MeasureSpec都是精确模式，并且大小是LayoutParams中的大小。\n当view的宽高是match_parent时，如果父容器的模式是精确模式，那么view也是精确模式，并且大小是父容器的剩余空间；如果父容器是最大模式，那么view也是最大模式，并且大小是不会超过父容器的剩余空间。\n当view的宽高是wrap_content时，不管父容器的模式是精确模式还是最大模式，view的模式总是最大模式，并且大小不超过父容器的剩余空间。\n\n###4.3 View的工作流程\n\n1、View的measure过程和Activity的生命周期方法不是同步执行的，因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕了。如果View还没有测量完毕，那么获得的宽和高都是0。下面是四种解决该问题的方法：\n\n* Activity/View#onWindowsChanged方法\n\nonWindowFocusChanged方法表示View已经初始化完毕了，宽高已经准备好了，这个时候去获取是没问题的。这个方法会被调用多次，当Activity继续执行或者暂停执行的时候，这个方法都会被调用。\n\n* View.post(runnable)\n\n通过post将一个Runnable投递到消息队列的尾部，然后等待Looper调用此runnable的时候，View也已经初始化好了。\n\n* ViewTreeObsever\n\n使用ViewTreeObserver的众多回调方法可以完成这个功能，比如使用onGlobalLayoutListener接口，当View树的状态发生改变或者View树内部的View的可见性发生改变时，onGlobalLayout方法将被回调。伴随着View树的变化，这个方法也会被多次调用。\n\n* view.measure(int widthMeasureSpec, int heightMeasureSpec)\n\n通过手动对View进行measure来得到View的宽高，这个要根据View的LayoutParams来处理：\n\nmatch_parent:无法measure出具体的宽高\n\nwrap_content:如下measure，设置最大值\n\n```\nint widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);\n\nint heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);\n\nview.measure(widthMeasureSpec, heightMeasureSpec);\n```\n\n\n\n精确值：例如100px\n\n```\n\nint widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);\n\nint heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);\n\nview.measure(widthMeasureSpec, heightMeasureSpec);\n```\n\n\n2、在View的默认实现中，View的测量宽高和最终宽高时相等的，只不过测量宽高形成于measure过程，而最终宽高形成于layout过程。\n\n3、draw过程大概有下面几步：\n\n1. 绘制背景：background.draw(canvas);\n2. 绘制自己：onDraw();\n3. 绘制children：dispatchDraw;\n4. 绘制装饰：onDrawScrollBars\n\n###4.4自定义View\n\n作者将自定义View分为以下4类：\n\n1. 继承view重写onDraw方法\n2. 继承ViewGroup派生特殊的Layout\n3. 继承特定的View(比如TextView)\n4. 继承特殊的ViewGroup(比如LinearLayout)\n\n自定义View须知：\n\n1. 让View支持wrap_content\n2. 如果有必要，让你的View支持padding\n3. 尽量不要在View中使用Handler，没必要\n4. View中如果有线程或者动画，需要及时停止，参考View#onDetachedFromWindow\n5. View带有滑动嵌套情形时，需要处理好滑动冲突\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Java编程思想》第一章读书笔记.md",
    "content": "#《Java编程思想》第一章\n---\n##对象导论\n\n封装\n\n* 被隐藏（也即封装）的部分通常代表对象内部脆弱的部分，它们很容易被程序员所毁坏，因此将实现隐藏起来可以减少程序的bug。\n* 隐藏是通过访问控制修饰符（public、protected、包访问、private）来实现的。\n* 访问控制的第一个存在原因就是让调用者无法直接触及他们不应该触及的部分，但从另一方面来看，其实这不失为一项服务，因为他们可以很容易地看出哪些东西对他们来说很重要，而哪些东西可能不关心；访问控制的第二个存在原因就是允许库设计者可以改变类的内部的工作方式而不用担心会影响到调用者。\n\n继承\n\n* 代码复用：复用是面向对象程序设计所提供最了不起的优点之一。\n* 最简单的代码复用就是直接调用类的方法，此外，我们还可以将该类置于某个新类中，使它成为新类的属性成员。新的类也可由任意数量、任意类型的其他对象以任意可以实现新的类中想要功能的方式所组成，这种使用现有的类合成新的类方式称为组合复用。\n* 组合复用带来了极大的灵活性，使得它能在运行时动态的修改其实现行为，但继承并不具备这样的灵活性，因为继承是在编译时期就已经确定其行为，在运行时期是不能修改的。\n* 继承两种实现方式，第一种方式非常直接：直接在子类中添加新的方法，即扩展父类接口。第二种方式就是子类覆写父类方法，但不新增父类没有接口。\n* “is-a是一个”与“is-like-a像是一个”。继承时，我们使用第一种还是第二种方式呢？这可能引起争论：继承应该只覆盖基类的方法，而并不添加在基类中没有的新方法吗？如果这样做，就意味着子类与基类是完全相同的类型，因为它们具有完全相同的接口，结果可以用一个子类对象来完全替代一个基类对象，这可被认为是纯粹替代，通常称之为替代原则，这也是一种理想的方式，我们经常称这种情况下的子类与基类的关系是“is-a是一个”；有时必须在子类型中添加新的接口，这样也就扩展了接口，这个新的类型仍可以替代基类，但是这种替代并不完美，因为基类无法访问新添加的方法，这种情况下我们可以描述为“is-like-a像是一个”关系。\n\n多态\n\n* 一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定，而向对象程序设计语言使用了后期绑定的概念。\n* 方法的调用就是编译器将产生对一个具体函数名字的调用，前期绑定是在运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中，程序直到运行时才能够确定代码的地址，为了执行后期绑定，Java编译器使用了一小段特殊代码来替代绝对地址调用，这段代码使用对象中存储的信息来计算方法体的地址，根据这段特殊代码，每个对象都可以具有不同的行为表现。\n* 在某些语言中，必须明确地声明某个方法具备后期绑定属性所带来的灵活性，如C++使用virtual关键字来实现，在默认情况下，C++不是动态绑定的，而在Java中，动态绑定是默认行为，不需要添加额外的关键字来实现多态。\n\nJava语言支持四种类型\n\n接口（interface）、类（class）、数组（array）和基本类型（primitive）。前三种类型通常被称为引用类型（reference type），类实例和数组是对象（object），而基本类型的值则不是对象。类的成员（member）由它的域（field）、方法（method）、成员类（member class）和成员接口（member interface）组成。方法签名（signature）由它的名称和所有参数类型组成；签名不包括它的返回类型。\n\n类与类之间的关系\n\n类和类、类和接口、接口和接口之间有如下几种关系：泛化关系、实现关系、关联关系（聚合、合成）、依赖关系。\n\n* 泛化：表示类与类之间的继承关系，使用extends关键字来表示。在图形上使用虚线三角形箭头表示。\n\n* 实现：表示类与接口之间的实现关系，使用implements关键字来表示。在图形上使用实线三角形箭头表示。\n\n* 关联：类与类之间的联接。关联可以是双向的，也可以是单向的，双向的关联可以有两个箭头或都没有箭头。单向的关联有一个箭头，表示关联的方向。在Java里，关联关系是使用实例变量实现的。在每一个关联的端点，还可以有一个基数，表时这一端的类可以有几个实例。常见的基数有：0..1（零个或者一个实例）、0..*或者*（没限制，可以是零）、1（只有一个实例）、1..*（至少有一个实例）。一个关联关系可以进一步确定为聚合与合成关系。在图形上使用实线的箭头表示。\n\n* 聚合：是关联关系的一种，是强的关联关系，聚合是整体和个体之间的关系。关联与聚合仅仅从Java语法是上是分辨不出的，需要考察所涉及的类之间的逻辑关系。如果不确定是聚合关系，可以将之设置为关联关系。图形是在实线的箭头的尾部多一个空心菱形。\n\n* 合成：是关联关系的一种，是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。整体消亡，则部分与消亡。图形是在实线的箭头的尾部多一个黑心菱形。\n\n* 依赖：类与类之间的连接，依赖总是单向的。表示一个类依赖于另一个类的定义。一般而言，依赖关系在Java里体现为局部变量、方法的参数、以及对静态方法的调用。但如果对方出现在实例变量中，那关系就超过了依赖关系，而成了关联关系了。在图形上使用虚线的箭头表示。\n\n\n\n\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《Java编程思想》第二章读书笔记.md",
    "content": "#《Java编程思想第二章》\n---\n\n##一切都是对象\n\n###对象存放位置与生命周期\n\nC++创建的对象可以存放在栈、静态存储区与堆（heap）中，放在栈中的对象用完后不需手动释放，会自动销毁，但放在堆中的对象需手动释放，栈中的对象所需空间与生命周期都是确定的，堆中的对象内存分配是动态的，在运行时才知道需要多少内存以及生命周期，如果说在堆上创建对象，编译器就会对它的生命周期一无所知，C++就需要以编程的方式来确定何时销毁对象，这可能因不正确处理而导致内存泄漏，而Java则提供了自动垃圾回收机制。\n\nJava对象的创建采用了动态内存分配策略，即创建的堆都是放在堆中的。\n\n###数据内存分配\n\n寄存器——位于处理器内部，这是最快的存储区，大小极其有限，一般不能直接控制，但C和C++允许你向编译器建议寄存器分配方式。\n\n堆栈——位于通用RAM（随机访问存储器）中，堆栈指针向下移动，则分配新的内存；若向上移动，则释放内存。这是一种快速有效的分配方法，速度仅次于寄存器。创建程序时，Java系统必须知道存储在堆栈内所有项的确切生命周期，以便上下移动堆栈指针。这一约束限制了程序的灵活性，所以虽然某些Java数据存储于堆栈中（如对象引用），但是Java对象并不存储于其中。\n\n堆——一种通用的内存池（也位于RAM区），用于存放所有的Java对象，堆不同于堆栈的好处是，编译器不需要知道存储的数据在堆里存活多长时间。因此，在堆栈分配存储有很大的灵活性。当然，这种灵活性导致了分配需要更多的时间，时间效率上不如堆栈。\n\n常量存储：常量值通常直接存放到程序代码内部，这样做是安全的，因为它们永远不会被改变。\n\n非RAM存储：数据可完全存活于程序之外，在没有运行机制时也可以存在，如持久化对象的存放。\n\nJVM有两类存储区：常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。\n\nJava字节码的执行有两种方式：\n\n1. 即时编译方式：解释器先将字节码编译成机器码，然后再执行该机器码。\n2. 解释执行方式：解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。\n\n通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性，这使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序，解释器可将Java字节码即时编译为机器码，从而很好地保证了Java代码的可移植性和高性能。\n\n###基本类型\n\nvoid属于基本类型，但只能用来修饰方法，不能用来修饰变量。\n\n* 只要两个操作数中有一个是double类型的，另一个将会被转换成double类型，并且结果也是double类型；\n\n* 否则，只要两个操作数中有一个是float类型的，另一个将会被转换成float类型，并且结果也是float类型；\n\n* 否则，只要两个操作数中有一个是long类型的，另一个将会被转换成long类型，并且结果也是long类型；\n\n* 否则，两个操作数（包括byte、short、int、char）都将会被转换成int类型，并且结果也是int类型。\n\nJava提供了两个用于高精度计算类：BigInteger和BigDecimal，大体属于“包装器类”范畴，但都没有对应的基本类型。\n\nBigInteger支持任意精度的整数，可表示任何大小的整数值。\n\nBigDecimal支持任意精度的定点数，例如，可以用它进行精确的货币计算。\n\n###引用与对象生存周期\n\n```\n{\n\n       String s = new String(\"a string\");\n\n}\n```\n\n引用s在作用域终点就消失了，然而，s指向的String对象继续占据内存，最后由垃圾回收器回收。\n\n###方法签名\n\n方法名和参数列表（合起来被称为“方法签名”）唯一地标识出某个方法。\n\n###static修饰字段与方法的区别\n\n一个static字段对每个对象来说都只有一份空间，而非static字段则是对每个对象都有一个存储空间，但是如果static作用于某个方法时，差别却没有那么大，static方法的一个重要的用法就是在不创建任何对象的前提下就可以调用它，这一点对定义main()方法很重要，该方法是运行时程序的一个入口。\n\n\n"
  },
  {
    "path": "Part5/ReadingNotes/《深入理解java虚拟机》第12章.md",
    "content": "#深入理解Java虚拟机读书笔记\n---\n###第12章\n\n####主内存和工作内存\njava内存模型的主要目标是定义程序中各个变量的访问规则，即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。为了获得较好的执行效能，Java内存模型咩有并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互，也没有限制即时编译器调整代码执行顺序这类权利。\n\nJava内存模型规定了所有的变量都存储在主内存中(Main Memory)中（此处的主内存和介绍物理硬件时的主内存名字一样，两者也可以互相类比，但此处仅是虚拟机内存的一部分）。每条线程还有自己的工作内存（Working Memory，可与前面所讲的处理器高速缓存类比），线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝，线程对变量的所有操作（读取，赋值等）都必须在工作内存中进行，而不能直接读写主内存中的变量。不同的线程也无法直接访问对方工作内存中的变量，线程间变量的传递需要通过主内存来完成，线程、主内存、工作内存的交互关系如图：\n\n![这里写图片描述](http://img.blog.csdn.net/20160321091659271)\n\n####内存间交互操作\n\n关于主内存和工作内存之间具体的交互协议，即一个遍历那个如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节，Java内存模型中定义了一下八种操作来完成：\n\n* lock(锁定)：作用于主内存的变量，它把一个变量标识为一条线程独占的状态。\n* unlock(解锁)：作用于主内存的变量，它把一个处于锁定状态的变量释放出来，释放后的变量才可以被其他线程锁定。\n* read(读取)：作用于主内存的变量，它把一个变量的值从主内存传输到线程的工作内存中，以便随后的load动作使用。\n* load(载入)：作用于工作内存的变量，它把read操作从主内存中得到的变量值放入工作内存的变量副本中。\n* use(使用)：作用于工作内存的变量，它把工作内存中一个变量的值传递给执行引擎，每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。\n* assign(赋值)：作用于工作内存的变量，它把一个从执行引擎收到的值赋值给工作内存的变量，每当虚拟机遇到一个给变量赋值的字节码时执行这个指令。\n* store(存储)：作用于工作内存的变量，它把工作内存中一个变量的值传送到主内存中，以便随后的write操作使用。\n* write(写入)：作用于主内存的变量，它把store操作从工作内存中得到的变量的值放入主内存的变量中。\n\n如果要把一个变量从主内存复制到工作内存，那就要按顺序执行read和load操作，如果要把变量从工作内存同步回主内存，就要按照顺序地执行store和write操作。注意，Java内存模型只要求上述两个操作必须按顺序执行，而没有保证必须是连续执行。Java内存模型规定了在执行上述八种基本操作时必须满足时必须满足如下规则：\n\n* 不允许read和load、store和write操作之一单独出现\n* 不允许一个线程丢弃它的最近的assign操作\n* 不允许一个线程无原因的（没有发生过任何assign操作）把数据从线程的工作内存同步到主内存中\n* 一个新的变量只能在主内存中诞生，不允许在工作内存中直接使用一个未被初始化（load或assign）的变量\n* 一个变量在同一时刻只允许一条线程对其进行lock操作，但lock操作可以被同一条线程重复执行多次，多次执行lock后，只有执行相同次数的unlock操作，变量才会被解锁\n* 如果对一个变量执行lock操作，将会清空工作内存中此变量的值，在执行引擎使用这个变量前，需要重新执行load或assign操作初始化变量的值。\n* 如果一个变量事先没有被lock锁定，则不允许对它执行unlock操作；也不允许去unlock一个被其他线程锁定住的变量。\n* 对一个变量执行unlock操作之前，必须先把此变量同步回主内存中（执行store和write）\n\n####对于volatile型变量的特殊规则\n当一个变量被定义成volatile之后，它将具备两种特性，第一是保证此变量对所有线程的可见性，这里的可见性是指当一条线程修改了这个变量的值，新值对于其他的线程是可以立即得知的。\n\n由于volatile变量只能保证可见性，在不符合以下两条规则的运算场景中，我们仍然要通过加锁（使用synchronized或java.util.concurrent中的原子类）来保证原子性\n\n* 运算结果并不依赖于变量的当前值，或者能够确保只有单一的线程修改变量的值\n* 变量不需要与其他的状态变量共同参与不变约束\n\nvolatile变量读操作的性能消耗与普通变量几乎没有什么差别，但是写操作则可能会慢上一些，因为它需要在本地代码中插入许多内存屏障（Memory Barrier或Memory Fence）指令来保证处理器不发生乱序执行。不过即便如此，大多数场景volatile的总开销仍然要比锁来的低。\n\n####对于long和double型变量的特殊规则\nJava内存模型要求对于lock、unlock、read、load、assign、use、store和write这八个操作都具有原子性，但是对于64位的数据类型（long和double），允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行，即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这四个操作的原子性。\n\n####原子性、可见性与有序性\nJava语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性，volatile关键字本生就包含了禁止指令重排序的语义，而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的，这个规则决定了持有同一个锁的两个同步代码块只能串行的进入。\n\n####先行发生原则\n先行发生是Java内存模型中定义的两项操作之间的偏序关系，如果说操作A先行发生于操作B，其实就是说在发生操作B之前，操作A产生的影响能被操作B观察到，“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。\n\n以下是Java内存模型下一些天然的先行发生关系，这些关系无需任何同步器协助就已经存在。\n\n* 程序次序规则（Program Order Rule）:在一个线程内，按照程序代码顺序，书写在前面的操作先行发生于书写在后面的操作。准确的说是控制流顺序而不是程序代码顺序，因为要考虑分支、循环等结构。\n* 管程锁定规则（Monitor Lock Rule）:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁，而后面是指事件上的先后顺序。\n* volatile变量规则（Volatile Variable Rule）:对一个volatile变量的写操作先行发生于后面对这个变量的读操作，这里的后面同样是指时间上的先后顺序。\n* 线程启动规则（Thread Start Rule）:Thread对象的start()方法先行发生于此线程的每一个动作。\n* 线程终止规则（Thread Termination Rule）:对线程所有操作都先行发生于对此线程的终止检测，我们可以通过Thread.join() 方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。\n* 线程中断规则（Thread Interruption Rule）:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测到是否有中断发生。\n* 对象终结规则（Finalizer Rule）:一个对象的初始化完成（构造函数执行结束）先行发生于它的finalize()方法的开始。\n* 传递性（Transitivity）:如果操作A先行发生于操作B,操作B先行发生于操作C，那就可以得出操作A先行发生于操作C的结论。\n\n####线程的实现\n线程是比进程更轻量级的调度执行单位，线程的引入，可以把一个进程的资源分配和执行调度分开，各个线程既可以共享进程资源（内存地址、文件I/O等），又可以独立调度（线程是CPU调度的最基本单位）。\n\n实现线程主要有三种方式：\n\n1. 使用内核线程实现\n2. 使用用户线程实现\n3. 混合实现\n4. Java线程的实现\n\n####Java线程调度\n线程调度是指系统为线程分配处理器使用权的过程，主要调度方式有两种，分别是协同式（Cooperative Threads-Scheduling）线程调度和抢占式（Preemptive Threads-Scheduling）线程调度。\n\n使用协同式调度的多线程系统，线程的执行时间由线程本身来控制，线程把自己的工作执行完了之后，要主动通知系统切换到另外一个线程上去。协同式多线程的最大好处是实现简单，而且由于线程要把自己的事情干完后才会进行线程切换，切换操作对自己是可知的，所以没有什么线程同步的问题。坏处是线程执行时间不可控制，甚至如果一个线程编写有问题，一直不告知系统进行线程切换，那么程序就会一直阻塞在那里。\n\n抢占式调度的多线程系统，那么每个线程将由系统来分配执行时间，线程的切换不由线程本身来决定（在Java中，Thread.yield()可以让出执行时间，但是要获取执行时间的话，线程本身是没有什么办法的）。在这种实现线程调度的方式下，线程的执行时间是系统可控的，也不会有一个线程导致整个进程阻塞的问题，Java使用的线程调度方式就是抢占式调度。\n\n\n####状态转换\nJava定义了5种进程状态\n\n* 新建（New）:创建后尚未启动的线程处于这种状态。\n* 运行(Runnable)：Runnable包括了操作系统状态中的Running和Ready，也就是处于此状态的线程有可能正在执行，也有可能正在等待CPU为它分配执行时间。\n* 无限期等待（Waiting）:处于这种状态的进程不会被分配CPU执行时间，它们要等待被其他线程显示的唤醒。以下方法会让线程陷入无限期的等待状态：\n\t* 没有设置Timeout参数的Object.wait()方法\n\t* 没有设置Timeout参数的Thread.join()方法\n\t* LockSupport.park()方法\n* 限期等待（Timed Waiting）:处于这种状态的进程不会被分配CPU执行时间，不过无需等待被其他线程显示的唤醒，在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态：\n\t* Thread.sleep()方法\n\t* 设置了Timeout参数的Object.wait()方法\n\t* 设置了Timeout参数的Thread.join()方法\n\t* LockSupport.parkNanos()方法\n\t* LockSupport.parkUnitil()方法\n* 阻塞（Blocked）:进程被阻塞了，阻塞状态和等待状态的区别是：阻塞状态在等待着获取到一个排它锁，这个事件将在另一个线程放弃这个锁的时候发生；而等待状态则是在等待一段时间，或者唤醒动作的发生。在程序等待进入同步区域的时候，线程将进入这种状态。\n* 结束（Terminated）:已终止线程的线程状态，线程已经结束执行。"
  },
  {
    "path": "Part6/InterviewExperience/Alibaba.md",
    "content": "#Alibaba\n---\n\n一面\n\n* 说一下你怎么学习安卓的？\n* 项目中遇到哪些问题，如何解决的？\n* Android事件分发机制？\n* 三级缓存底层实现？\n* HashMap底层实现，hashCode如何对应bucket?\n* Java的垃圾回收机制，引用计数法两个对象互相引用如何解决？\n* 用过的开源框架的源码分析\n* Acticity的生命周期，Activity异常退出该如何处理？\n* tcp和udp的区别，tcp如何保证可靠的，丢包如何处理？\n\n二面：\n\n* 标号1-n的n个人首尾相接，1到3报数，报到3的退出，求最后一个人的标号\n* 给定一个字符串，求第一个不重复的字符 abbcad -> c\n\n\n面试者：陶程"
  },
  {
    "path": "Part6/InterviewExperience/新浪微博.md",
    "content": "#新浪微博\n---\n一面\n---\n\n静态内部类、内部类、匿名内部类，为什么内部类会持有外部类的引用？持有的引用是this？还是其它？\n\n```\n静态内部类：使用static修饰的内部类\n匿名内部类：使用new生成的内部类\n因为内部类的产生依赖于外部类，持有的引用是类名.this。\n```\n\nArrayList和Vector的主要区别是什么？\n\n```\nArrayList在Java1.2引入，用于替换Vector\n\nVector:\n\n线程同步\n当Vector中的元素超过它的初始大小时，Vector会将它的容量翻倍\n\nArrayList:\n\n线程不同步，但性能很好\n当ArrayList中的元素超过它的初始大小时，ArrayList只增加50%的大小\n```\n\n[Java集合类框架](http://yuweiguocn.github.io/2016/01/06/java-collection/)\n\n\nJava中try catch finally的执行顺序\n\n```\n先执行try中代码发生异常执行catch中代码，最后一定会执行finally中代码\n```\n\nswitch是否能作用在byte上，是否能作用在long上，是否能作用在String上？\n\n```\nswitch支持使用byte类型，不支持long类型，String支持在java1.7引入\n```\n\nActivity和Fragment生命周期有哪些？\n\n```\nActivity——onCreate->onStart->onResume->onPause->onStop->onDestroy\n\nFragment——onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach\n```\n\n\nonInterceptTouchEvent()和onTouchEvent()的区别？\n\n```\nonInterceptTouchEvent()用于拦截触摸事件\nonTouchEvent()用于处理触摸事件\n```\n\nRemoteView在哪些功能中使用\n\n```\nAPPwidget和Notification中\n```\n\nSurfaceView和View的区别是什么？\n\n```\nSurfaceView中采用了双缓存技术，在单独的线程中更新界面\nView在UI线程中更新界面\n```\n\n讲一下android中进程的优先级？\n\n```\n前台进程\n可见进程\n服务进程\n后台进程\n空进程\n```\n\ntips：静态类持有Activity引用会导致内存泄露\n\n\n##二面\n\n* service生命周期，可以执行耗时操作吗？\n* JNI开发流程\n* Java线程池，线程同步\n* 自己设计一个图片加载框架\n* 自定义View相关方法\n* http ResponseCode\n* 插件化，动态加载\n* 性能优化，MAT\n* AsyncTask原理\n* 65k限制\n* Serializable和Parcelable\n* 文件和数据库哪个效率高\n* 断点续传\n* WebView和JS\n* 所使用的开源框架的实现原理，源码\n\n[codekk：开源框架源码解析](http://codekk.com/open-source-project-analysis)\n\n\n[Android基础——Service](http://yuweiguocn.github.io/2016/03/28/android-basic-service/)\n\n[Android基础——IntentService](http://yuweiguocn.github.io/2016/03/31/android-basic-intentservice/)\n\n[Android开发指导——Service](http://yuweiguocn.github.io/2016/04/02/android-guide-service/)\n\n[Android开发指导——绑定Service](http://yuweiguocn.github.io/2016/03/31/android-guide-bound-service/)\n\n[Android开发指导——进程间通信AIDL](http://yuweiguocn.github.io/2016/03/31/android-guide-aidl/)\n\n[Android面试基础知识总结（一）](http://yuweiguocn.github.io/2016/03/26/android-interview-basic-1/)\n\n[Android面试——APP性能优化](http://yuweiguocn.github.io/2016/04/10/android-interview-peformance/)\n\n[Android中Java和JavaScript交互](http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/)\n\n[WebView 远程代码执行漏洞浅析](http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.oMsDAl&id=48)\n\n[WebView中的Java与JavaScript提供【安全可靠】的多样互通方案](https://github.com/pedant/safe-java-js-webview-bridge)\n\n\n\n\n"
  },
  {
    "path": "Part6/InterviewExperience/网易杭研.md",
    "content": "#网易杭研\n---\n\n###一面：\n\n* 自我介绍\n* Android中ClassLoader和java中有什么关系和区别？\n* 熟不熟jvm，说一下Jvm的自动内存管理？\n* 语言基础，String类可以被继承吗？为什么？\n* Final能修饰什么？（当时我说class、field、method，他说还有吗？然后又叫我不要在意，后来回想起，应该是问到我在参数里面要不要用final，接下来是因为匿名内部类）\n* Java中有内存泄露吗？（先说本质，再结合handler+匿名内部类）当时如何分析的？\n* 描述下Aidl？觉得aidl有什么缺陷（这里在这个问题上回答有欠缺）\n* **评价一下我，如果顺利进网易，需要往技术栈加什么点尽快投入业务？**\n\n\n\n###二面：\n\n* 用过什么开源，举一个例子？（volley）\n* Activity生命周期？情景：现在在一张act1点了新的act2，周期如何？\n* Act的launchMode，有没有结合项目用过（自己的程序锁和微信的PC端登陆对比，不过我现在又发现，应该大约估计可能是动态加载的一个缺陷，如果有找到相关信息，请务必跟我说。具体问题就是，当在PC端登录时，Android终端的微信会跳出，即使wechat的task不是在fore，当按下确认，返回的是wechat，而不是自己先前的app）\n* View的绘制原理，有没有用canvas自己画过ui？\n* 以后想做Android什么方向？（中间件+SDK）\n* 怎么看待前端和后端？\n* 如果学前端会如何学？\n* 优缺点？兴趣？\n* 想不想来杭州？\n* **评价一下我？往技术栈加什么？**\n\n\n###三面HR：\n\n* 为什么想来网易？\n* 有投其他公司吗？\n* 网易最吸引你的是什么？\n* 想来杭州吗？\n* **评价一下我？**"
  },
  {
    "path": "Part6/InterviewExperience/美团.md",
    "content": "#美团\n---\n\n一面\n\n- 自我介绍\n- 面向对象三大特性\n- Java虚拟机，垃圾回收\n- GSON\n- RxJava+Retrofit\n- 图片缓存，三级缓存\n- Android启动模式\n- 四大组件\n- Fragment生命周期，嵌套\n- AsyncTask机制\n- Handler机制\n\n二面\n\n- 面试官写程序，看错误。\n- 面试官写程序让判断GC引用计数法循环引用会发生什么情况\n- Android进程间通信，Binder机制\n- Handler消息机制，postDelayed会造成线程阻塞吗？对内存有什么影响？\n- Debug和Release状态的不同\n- 实现stack 的pop和push接口 要求：\n\t- 1.用基本的数组实现\n\t- 2.考虑范型\n\t- 3.考虑下同步问题\n\t- 4.考虑扩容问题\n\n面试者:陶程"
  },
  {
    "path": "Part6/InterviewExperience/蜻蜓FM.md",
    "content": "#蜻蜓FM\n---\n\n一面\n\n- Toolbar的使用\n- 如何判断本地缓存的时候数据需要从网络端获取\n- 跨进程间通信\n- Handler消息机制\n- SharedPreference实现\n- 快速排序\n- 项目难点\n\n\n之后让去现场面，没去...\n\n面试人：陶程"
  },
  {
    "path": "Part6/InterviewExperience/豌豆荚.md",
    "content": "#豌豆荚三面\n---\n豌豆荚一面\n\n* 介绍一下你的项目\n* 网络框架的搭建\n* 图片加载框架的实现\n* 写个图片浏览器，说出你的思路\n* 上网站写代码，如下： \n有一个容器类 ArrayList，保存整数类型的元素，现在要求编写一个帮助类，类内提供一个帮助函数，帮助函数的功能是删除 容器中<10的元素。\n\n豌豆荚二面\n\n* Activity的启动模式 \n* 事件分发机制\n\n* 写代码，LeetCode上股票利益最大化问题 \n* 写代码，剑指offer上第一次只出现一次的字符\n\n豌豆荚三面\n\n* 聊项目，聊大学做过的事\n* 写代码，反转字符串\n* 写代码，字符串中出现最多的字符。\n\n\n面试者：陶程"
  },
  {
    "path": "README.md",
    "content": "# LearningNotes\n  ---\n\n## 第一部分：\n\n* [Android(安卓)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part1/Android)\n\n    * [Android基础知识](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android基础知识.md)\n    * [Android内存泄漏总结](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android内存泄漏总结.md)\n    * [Handler内存泄漏分析及解决](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Handler内存泄漏分析及解决.md)\n    * [Handler、Looper、Message、MessageQueue基础流程分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/线程通信基础流程分析.md)\n    * [Android性能优化](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android性能优化.md)\n    * [ListView详解](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Listview详解.md)\n    * [RecyclerView和ListView的异同](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Recyclerview和Listview的异同.md)\n    * [AsyncTask源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Asynctask源码分析.md)\n    * [插件化技术](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/插件化技术学习.md)\n    * [自定义控件](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/自定义控件.md)\n    * [事件分发机制](http://www.jianshu.com/p/e99b5e8bd67b)\n    * [ANR问题](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/ANR问题.md)\n    * [Art和Dalvik的区别](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Art和Dalvik区别.md)\n    * [Android关于OOM的解决方案](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android关于oom的解决方案.md)\n    * [Fragment](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Fragment.md)\n    * [Activity&Fragment](https://github.com/xxv/android-lifecycle)\n    * [SurfaceView](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/SurfaceView.md)\n    * [Android几种进程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android几种进程.md)\n    * [APP启动过程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/APP启动过程.md)\n    * Activity启动流程以及界面展示过程\n    * [图片三级缓存](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android图片中的三级缓存.md)\n    * [Bitmap的分析与使用](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Bitmap的分析与使用.md)\n    * [热修复的原理](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/热修复技术.md)\n    * [AIDL](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/AIDL.md)\n    * [Binder机制](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Binder机制.md)\n    * [Zygote和System进程的启动过程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Zygote和System进程的启动过程.md)\n    * [Android中的MVC，MVP和MVVM](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/MVC%2CMVP%2CMVVM的区别.md)\n    * [MVP](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/MVP.md)\n    * [Android开机过程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android开机过程.md)\n    * [Retrofit源码分析](http://www.jianshu.com/p/c1a3a881a144)\n    * [Glide源码分析](http://frodoking.github.io/2015/10/10/android-glide/)\n    * [EventBus用法详解](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/EventBus用法详解.md)\n    * [EventBus源码分析](http://p.codekk.com/blogs/detail/54cfab086c4761e5001b2538)\n    * [Android ORM 框架之 greenDAO 使用心得](http://www.open-open.com/lib/view/open1438065400878.html)\n    * [Data Binding（数据绑定）用户指南](http://www.jcodecraeer.com/a/anzhuokaifa/developer/2015/0606/3005.html)\n    * [RxJava](http://gank.io/post/560e15be2dca930e00da1083)\n    * 设计一套图片异步加载缓存方案\n    * Android UI适配\n    * [Gradle](http://wuxiaolong.me/categories/Gradle/)\n    * [查漏补缺](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/查漏补缺.md)\n    * [Git操作](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Git操作.md)\n\n\n\n---\n\n* [DesignPattern(设计模式)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part1/DesignPattern)\n\n\n     * [面向对象六大原则](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/常见的面向对象设计原则.md)\n     * [单例模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/单例模式.md)\n     * [Builder模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/Builder模式.md)\n     * [原型模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/原型模式.md)\n     * [简单工厂](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/简单工厂.md)\n     * 工厂方法模式\n     * 抽象工厂模式\n     * [策略模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/策略模式.md)\n     * 状态模式\n     * [责任链模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/责任链模式.md)\n     * 解释器模式\n     * 命令模式\n     * [观察者模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/观察者模式.md)\n     * 备忘录模式\n     * 迭代器模式\n     * 模板方法模式\n     * 访问者模式\n     * 中介者模式\n     * [代理模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/代理模式.md)\n     * 组合模式\n     * [适配器模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/适配器模式.md)\n     * 装饰模式\n     * 享元模式\n     * [外观模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/DesignPattern/外观模式.md)\n     * 桥接模式\n\n\n---\n\n## 第二部分\n\n* [JavaSE(Java基础)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part2/JavaSE)\n  * [Java基础知识](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/Java基础知识.md)\n  * [Java中的内存泄漏](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/Java中的内存泄漏.md)\n  * [String源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/String源码分析.md)\n  * [Java集合框架](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/Java集合框架.md)\n  * [ArrayList源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/ArrayList源码剖析.md)\n  * [LinkedList源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/LinkedList源码剖析.md)\n  * [Vector源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/Vector源码剖析.md)\n  * [HashMap源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/HashMap源码剖析.md)\n  * [HashTable源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/HashTable源码剖析.md)\n  * [LinkedHashMap源码剖析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaSE/LinkedHashMap源码剖析.md)\n\n* [JVM(Java虚拟机)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part2/JVM)\n  * [JVM基础知识](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JVM/JVM.md)\n  * [JVM类加载机制](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JVM/JVM类加载机制.md)\n  * [Java内存区域与内存溢出](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JVM/Java内存区域与内存溢出.md)\n  * [垃圾回收算法](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JVM/垃圾回收算法.md)\n* [JavaConcurrent(Java并发)](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/Java并发.md)\n  * [Java并发基础知识](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/Java并发基础知识.md)\n  * [生产者和消费者问题](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/生产者和消费者问题.md)\n    * [Thread和Runnable实现多线程的区别](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/Thread和Runnable实现多线程的区别.md) \n  * [线程中断](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/线程中断.md)\n  * [守护线程与阻塞线程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/守护线程与阻塞线程.md)\n  * [synchronized](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/Synchronized.md)\n  * [多线程环境中安全使用集合API](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/多线程环境中安全使用集合API.md)\n  * [实现内存可见的两种方法比较：加锁和volatile变量](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/实现内存可见的两种方法比较：加锁和volatile变量.md)\n  * [死锁](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/死锁.md)\n  * [可重入内置锁](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/可重入内置锁.md)\n  * [使用wait/notify/notifyAll实现线程间通信](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/使用wait:notify:notifyall实现线程间通信.md)\n  * [NIO](https://github.com/GeniusVJR/LearningNotes/blob/master/Part2/JavaConcurrent/NIO.md)\n\n ---\n\n\n## 第三部分\n\n* [DataStructure(数据结构)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part3/DataStructure)\n    * [数组](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/DataStructure/数组.md)\n    * 链表\n    * [栈和队列](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/DataStructure/栈和队列.md)\n    * 字符串\n    * 树\n    * 图\n* [Algorithm(算法)]()\n    * [排序](https://github.com/anAngryAnt/LearningNotes/tree/master/Part3/Algorithm/Sort)\n        * [选择排序](https://github.com/anAngryAnt/LearningNotes/tree/master/Part3/Algorithm/Sort/选择排序.md)\n        * [冒泡排序](https://github.com/anAngryAnt/LearningNotes/tree/master/Part3/Algorithm/Sort/冒泡排序.md)\n        * [快速排序](https://github.com/anAngryAnt/LearningNotes/tree/master/Part3/Algorithm/Sort/快速排序.md)\n        * [归并排序](https://github.com/anAngryAnt/LearningNotes/tree/master/Part3/Algorithm/Sort/归并排序.md)\n        * [面试中的十大排序算法总结](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/Sort/%E9%9D%A2%E8%AF%95%E4%B8%AD%E7%9A%84%2010%20%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93.md)\n    * 查找\n        * [顺序查找](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/Lookup/顺序查找.md)\n        * [折半查找](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/Lookup/折半查找.md)\n    * 《剑指Offer》\n        * [面试题2:实现Singleton模式](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/剑指Offer/1.七种方式实现singleton模式.md)\n        * [面试题6：重建二叉树](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/剑指Offer/面试题6：重建二叉树.md)\n        * [面试题11：数值的整数次方](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/剑指Offer/面试题11：数值的整数次方.md)\n        * [面试题44：扑克牌的顺子](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/剑指Offer/面试题44：扑克牌的顺子.md)\n        * [面试题45：圆圈中最后剩下的数字](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/剑指Offer/面试题45：圆圈中最后剩下的数字.md)\n    * 《程序员面试金典》\n    * 《LeetCode》\n        * [two-sum](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/LeetCode/two-sum.md)\n    * 《程序员代码面试指南(左程云)》\n        * [1.设计一个有getMin功能的栈](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/程序员代码面试指南(左程云)/1.设计一个有getMin功能的栈.md)\n        * [2.由两个栈组成的队列](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/程序员代码面试指南(左程云)/2.由两个栈组成的队列.md)\n        * [3.如何仅用递归函数和栈操作逆序一个栈](https://github.com/GeniusVJR/LearningNotes/blob/master/Part3/Algorithm/程序员代码面试指南(左程云)/3.如何仅用递归函数和栈操作逆序一个栈.md)\n\n ---\n\n## 第四部分\n\n* [Network(网络)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part4/Network)\n    * [TCP/UDP](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/Network/TCP与UDP.md)\n    * [HTTP](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/Network/Http协议.md)\n    * [Socket](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/Network/Socket.md)\n    * [计算机网络基础汇总](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/Network/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80%E6%B1%87%E6%80%BB.md)\n* [OperatingSystem(操作系统)](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/OperatingSystem/操作系统.md)\n    * [Linux系统的IPC](https://github.com/GeniusVJR/LearningNotes/blob/master/Part4/OperatingSystem/Linux系统的IPC.md)\n\n\n ---\n\n\n## 第五部分\n\n* [ReadingNotes(读书笔记)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part5/ReadingNotes)\n   * [《APP研发录》第1章读书笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《APP研发录》第1章读书笔记.md)\n   * [《APP研发录》第2章读书笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《APP研发录》第2章读书笔记.md)\n   * [《Android开发艺术探索》第一章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第一章笔记.md)\n   * [《Android开发艺术探索》第二章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第二章笔记.md)\n   * [《Android开发艺术探索》第三章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第三章笔记.md)\n   * [《Android开发艺术探索》第四章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第四章笔记.md)\n   * [《Android开发艺术探索》第八章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第八章笔记.md)\n   * [《Android开发艺术探索》第十五章笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Android开发艺术探索》第十五章笔记.md)\n   * [《深入理解Java虚拟机》第12章](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《深入理解java虚拟机》第12章.md)\n   * [《Java编程思想》第一章读书笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Java编程思想》第一章读书笔记.md)\n   * [《Java编程思想》第二章读书笔记](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/ReadingNotes/《Java编程思想》第二章读书笔记.md)\n\n* [Project(项目)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part5/Project)\n   * [项目难点](https://github.com/GeniusVJR/LearningNotes/blob/master/Part5/Project/项目.md)\n\n## 第六部分\n\n* [InterviewExperience(面试经验)](https://github.com/GeniusVJR/LearningNotes/tree/master/Part6/InterviewExperience)\n    * [Alibaba](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/Alibaba.md)\n    * [美团](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/美团.md)\n    * [豌豆荚](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/豌豆荚.md)\n    * [蜻蜓FM](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/蜻蜓FM.md)\n    * [新浪微博](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/新浪微博.md)\n    * [网易杭研](https://github.com/GeniusVJR/LearningNotes/blob/master/Part6/InterviewExperience/网易杭研.md)\n\n---\n\n* [Resume(简历)](https://zhuanlan.zhihu.com/p/20672941)\n\n\n---\n\n有任何问题欢迎联系：\ngeniusvjr@gmail.com\n\n欢迎关注我的知乎账号：\n\n[Francis的知乎](https://www.zhihu.com/people/FrancisTao)\n\n微信公众号：\n\n陶程\n\n微博账号：\n\n[Francis的微博](http://weibo.com/3627982543/profile?rightmod=1&wvr=6&mod=personinfo)\n\n已同步到 [http://www.diycode.cc/wiki/androidinterview](http://www.diycode.cc/wiki/androidinterview) \n\n拒绝任何形式的商业用途。\n\n"
  }
]