From 786f5ac626f4822223cfde0e1ae3f352e0e280ea Mon Sep 17 00:00:00 2001 From: geniusvjr Date: Tue, 19 Jul 2016 00:50:49 +0800 Subject: [PATCH 01/40] update --- "Part5/Project/\351\241\271\347\233\256.md" | 52 --------------------- 1 file changed, 52 deletions(-) delete mode 100644 "Part5/Project/\351\241\271\347\233\256.md" diff --git "a/Part5/Project/\351\241\271\347\233\256.md" "b/Part5/Project/\351\241\271\347\233\256.md" deleted file mode 100644 index b85607a..0000000 --- "a/Part5/Project/\351\241\271\347\233\256.md" +++ /dev/null @@ -1,52 +0,0 @@ -#项目 ---- - -##可以说的点 ---- - -###《密码助手》 - -* MVP架构 - - - -* Realm+DataBinding+EventBus+RxBinding框架如何使用及源码研究 -* 图形加锁如何实现 -* 自定义控件,如类浏览器,水波纹效果 - - -###《NBAFans》 - -* RxJava+Retrofit,及内部实现,RxJava使用的设计模式 -* EventBus和GreenDao源码研究 -* 实现动态Blur效果 -* 不同的ItemType实现 - -###《萌记》 - -* 富文本 -* 右滑返回的实现 - -###《Http框架》 - -* Http协议的研究 -* 一系列的封装和优化 -* Retrofit使用的设计模式 - - -###《新闻客户端》 - -* 图片的三级缓存 -* ViewPager预加载机制源码研究 -* 解决View的事件滑动冲突 - - - - - - - - - - - From 8f649a9013fe8836ca801d7c4c4fd9576519aaca Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Tue, 19 Jul 2016 00:50:54 +0800 Subject: [PATCH 02/40] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Bitmap?= =?UTF-8?q?=E7=9A=84=E5=88=86=E6=9E=90=E4=B8=8E=E4=BC=98=E5=8C=96.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\230\346\263\204\346\274\217\346\200\273\347\273\223.md" | 6 +++--- ...\277\347\224\250\344\270\216\344\274\230\345\214\226.md" | 0 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 "Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" diff --git "a/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" "b/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" index aebd000..bfb6f80 100644 --- "a/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" +++ "b/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" @@ -11,9 +11,9 @@ Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式 * 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。 -* 栈区 :当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 +* 栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 -* 堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。 +* 堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。 ##栈与堆的区别: @@ -97,7 +97,7 @@ for (int i = 1; i < 100; i++) { 2.Java内存泄漏引起的原因 -内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。 +内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。j Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类: diff --git "a/Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" "b/Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" new file mode 100644 index 0000000..e69de29 From 92b9353b4e284e1174f1b465ef5b021c2f92d35b Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Tue, 19 Jul 2016 13:16:42 +0800 Subject: [PATCH 03/40] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86Bitmap?= =?UTF-8?q?=E7=9A=84=E5=88=86=E4=BA=AB=E4=B8=8E=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...45\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" => "Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" (100%) diff --git "a/Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" similarity index 100% rename from "Part1/Android/Bitmap\347\232\204\344\275\277\347\224\250\344\270\216\344\274\230\345\214\226.md" rename to "Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" From eb0564ceeeecfac556288850d56faafacfa9f32a Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Tue, 19 Jul 2016 13:19:17 +0800 Subject: [PATCH 04/40] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86Bitmap?= =?UTF-8?q?=E7=9A=84=E5=88=86=E4=BA=AB=E4=B8=8E=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c82010e..6b72057 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ * [APP启动过程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/APP启动过程.md) * Activity启动流程以及界面展示过程 * [图片三级缓存](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android图片中的三级缓存.md) + * [Bitmap的分析与使用](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Bitmap的分析与使用.md)* * [热修复的原理](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/热修复技术.md) * [AIDL](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/AIDL.md) * [Binder机制](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Binder机制.md) From 0ba427059cf3cdcb36887574864d6f540a0a50b4 Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Tue, 19 Jul 2016 13:20:10 +0800 Subject: [PATCH 05/40] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86Bitmap?= =?UTF-8?q?=E7=9A=84=E5=88=86=E4=BA=AB=E4=B8=8E=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...20\344\270\216\344\275\277\347\224\250.md" | 195 ++++++++++++++++++ README.md | 2 +- 2 files changed, 196 insertions(+), 1 deletion(-) diff --git "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" index e69de29..b148a06 100644 --- "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" +++ "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" @@ -0,0 +1,195 @@ +## **Bitmap**的分析与使用 + - Bitmap的创建 + - 创建Bitmap的时候,Java不提供`new Bitmap()`的形式去创建,而是通过`BitmapFactory`中的静态方法去创建,如:`BitmapFactory.decodeStream(is);//通过InputStream去解析生成Bitmap`(这里就不贴`BitmapFactory`中创建`Bitmap`的方法了,大家可以自己去看它的源码),我们跟进`BitmapFactory`中创建`Bitmap`的源码,最终都可以追溯到这几个native函数 + ``` + private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, + Rect padding, Options opts); + private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, + Rect padding, Options opts); + private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts); + private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, + int length, Options opts); + ``` + 而`Bitmap`又是Java对象,这个Java对象又是从native,也就是C/C++中产生的,所以,在Android中Bitmap的内存管理涉及到两部分,一部分是*native*,另一部分是*dalvik*,也就是我们常说的java堆(如果对java堆与栈不了解的同学可以戳),到这里基本就已经了解了创建Bitmap的一些内存中的特性(大家可以使用``adb shell dumpsys meminfo``去查看Bitmap实例化之后的内存使用情况)。 + + - Bitmap的使用 + - 我们已经知道了`BitmapFactory`是如何通过各种资源创建`Bitmap`了,那么我们如何合理的使用它呢?以下是几个我们使用`Bitmap`需要关注的点 + 1. **Size** + - 这里我们来算一下,在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! + - *那么,我们一般是如何处理Size这个重要的因素的呢?*,当然是调整`Bitmap`的大小到适合的程度啦!辛亏在`BitmapFactory`中,我们可以很方便的通过`BitmapFactory.Options`中的`options.inSampleSize`去设置`Bitmap`的压缩比,官方给出的说法是 + > 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 + an image that is 1/4 the width/height of the original, and 1/16 the + number of pixels. Any value <= 1 is treated the same as 1. + + 很简洁明了啊!也就是说,只要按计算方法设置了这个参数,就可以完成我们Bitmap的Size调整了。那么,应该怎么调整姿势才比较舒服呢?下面先介绍其中一种通过``InputStream``的方式去创建``Bitmap``的方法,上一段从Gallery中获取照片并且将图片Size调整到合适手机尺寸的代码: + ``` + static final int PICK_PICS = 9; + + public void startGallery(){ + Intent i = new Intent(); + i.setAction(Intent.ACTION_PICK); + i.setType("image/*"); + startActivityForResult(i,PICK_PICS); + } + + private int[] getScreenWithAndHeight(){ + WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(dm); + return new int[]{dm.widthPixels,dm.heightPixels}; + } + + /** + * + * @param actualWidth 图片实际的宽度,也就是options.outWidth + * @param actualHeight 图片实际的高度,也就是options.outHeight + * @param desiredWidth 你希望图片压缩成为的目的宽度 + * @param desiredHeight 你希望图片压缩成为的目的高度 + * @return + */ + private int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { + double wr = (double) actualWidth / desiredWidth; + double hr = (double) actualHeight / desiredHeight; + double ratio = Math.min(wr, hr); + float n = 1.0f; + //这里我们为什么要寻找 与ratio最接近的2的倍数呢? + //原因就在于API中对于inSimpleSize的注释:最终的inSimpleSize应该为2的倍数,我们应该向上取与压缩比最接近的2的倍数。 + while ((n * 2) <= ratio) { + n *= 2; + } + + return (int) n; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(resultCode == RESULT_OK){ + switch (requestCode){ + case PICK_PICS: + Uri uri = data.getData(); + InputStream is = null; + try { + is = getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + //当这个参数为true的时候,意味着你可以在解析时候不申请内存的情况下去获取Bitmap的宽和高 + //这是调整Bitmap Size一个很重要的参数设置 + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream( is,null,options ); + + int realHeight = options.outHeight; + int realWidth = options.outWidth; + + int screenWidth = getScreenWithAndHeight()[0]; + + int simpleSize = findBestSampleSize(realWidth,realHeight,screenWidth,300); + options.inSampleSize = simpleSize; + //当你希望得到Bitmap实例的时候,不要忘了将这个参数设置为false + options.inJustDecodeBounds = false; + + try { + is = getContentResolver().openInputStream(uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + Bitmap bitmap = BitmapFactory.decodeStream(is,null,options); + + iv.setImageBitmap(bitmap); + + try { + is.close(); + is = null; + } catch (IOException e) { + e.printStackTrace(); + } + + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + ``` + 我们来看看这段代码的功效: + 压缩前:![压缩前](leanote://file/getImage?fileId=578d9edec615a067c0000000) + 压缩后:![压缩后](leanote://file/getImage?fileId=578d9f1ec615a067c0000001) + 对比条件为:1080P的魅族Note3拍摄的高清无码照片 + 2. **Reuse** + 上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。`Bitmap`是吃内存大户,我们不希望多次解析相同的`Bitmap`,也不希望可能不会用到的`Bitmap`一直存在于内存中,所以,这个场景下,`Bitmap`的重用变得异常的重要。 + *在这里只介绍一种``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章会介绍使用三级缓存来实现Bitmap的重用。* + + 根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。 + 我们看来看看这个参数最基本的运用方法。 + ``` + new BitmapFactory.Options options = new BitmapFactory.Options(); + //inBitmap只有当inMutable为true的时候是可用的。 + options.inMutable = true; + Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options); + options.inBitmap = reusedBitmap; + ``` + + 这样,当你在下一次decodeBitmap的时候,将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入,Android就会复用你的Bitmap了,具体实例: + ``` + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(reuseBitmap()); + } + + private LinearLayout reuseBitmap(){ + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + ImageView iv = new ImageView(this); + iv.setLayoutParams(new ViewGroup.LayoutParams(500,300)); + + options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + //inBitmap只有当inMutable为true的时候是可用的。 + options.inMutable = true; + BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options); + + //压缩Bitmap到我们希望的尺寸 + //确保不会OOM + options.inSampleSize = findBestSampleSize(options.outWidth,options.outHeight,500,300); + options.inJustDecodeBounds = false; + + Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options); + options.inBitmap = bitmap; + + iv.setImageBitmap(bitmap); + + linearLayout.addView(iv); + + ImageView iv1 = new ImageView(this); + iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300)); + iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options)); + linearLayout.addView(iv1); + + ImageView iv2 = new ImageView(this); + iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300)); + iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options)); + linearLayout.addView(iv2); + + + return linearLayout; + } + ``` + 以上代码中,我们在解析了一次一张1080P分辨率的图片,并且设置在`options.inBitmap`中,然后分别decode了同一张图片,并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。 + 3. **Recycle** + 一定要记得及时回收Bitmap,否则如上分析,你的native以及dalvik的内存都会被一直占用着,最终导致OOM + ``` + // 先判断是否已经回收 + if(bitmap != null && !bitmap.isRecycled()){ + // 回收并且置为null + bitmap.recycle(); + bitmap = null; + } + System.gc(); + ``` + - Enjoy Android :) 如果有误,轻喷,欢迎指正。 + diff --git a/README.md b/README.md index 6b72057..754e200 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ * [APP启动过程](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/APP启动过程.md) * Activity启动流程以及界面展示过程 * [图片三级缓存](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android图片中的三级缓存.md) - * [Bitmap的分析与使用](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Bitmap的分析与使用.md)* + * [Bitmap的分析与使用](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Bitmap的分析与使用.md) * [热修复的原理](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/热修复技术.md) * [AIDL](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/AIDL.md) * [Binder机制](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Binder机制.md) From 1b7752eba1ae3a5151d211520b17b029525bdf0f Mon Sep 17 00:00:00 2001 From: anAngryAnt <695181966@qq.com> Date: Tue, 19 Jul 2016 13:27:31 +0800 Subject: [PATCH 06/40] =?UTF-8?q?Update=20Bitmap=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E4=B8=8E=E4=BD=BF=E7=94=A8.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新Bitmap的分析与使用 --- ...10\206\346\236\220\344\270\216\344\275\277\347\224\250.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" index b148a06..7242345 100644 --- "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" +++ "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" @@ -114,8 +114,8 @@ } ``` 我们来看看这段代码的功效: - 压缩前:![压缩前](leanote://file/getImage?fileId=578d9edec615a067c0000000) - 压缩后:![压缩后](leanote://file/getImage?fileId=578d9f1ec615a067c0000001) + 压缩前:![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c) + 压缩后:![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851) 对比条件为:1080P的魅族Note3拍摄的高清无码照片 2. **Reuse** 上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。`Bitmap`是吃内存大户,我们不希望多次解析相同的`Bitmap`,也不希望可能不会用到的`Bitmap`一直存在于内存中,所以,这个场景下,`Bitmap`的重用变得异常的重要。 From d82fc247ab957726980062cd7bb8e7d1929817e6 Mon Sep 17 00:00:00 2001 From: anAngryAnt <695181966@qq.com> Date: Tue, 19 Jul 2016 13:32:36 +0800 Subject: [PATCH 07/40] =?UTF-8?q?Update=20Bitmap=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E4=B8=8E=E4=BD=BF=E7=94=A8.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新Bitmap的分析与使用 --- ...346\236\220\344\270\216\344\275\277\347\224\250.md" | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" index 7242345..03d2c3b 100644 --- "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" +++ "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" @@ -113,10 +113,12 @@ super.onActivityResult(requestCode, resultCode, data); } ``` - 我们来看看这段代码的功效: - 压缩前:![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c) - 压缩后:![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851) - 对比条件为:1080P的魅族Note3拍摄的高清无码照片 + + 我们来看看这段代码的功效: + 压缩前:![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c) + 压缩后:![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851) + 对比条件为:1080P的魅族Note3拍摄的高清无码照片 + 2. **Reuse** 上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。`Bitmap`是吃内存大户,我们不希望多次解析相同的`Bitmap`,也不希望可能不会用到的`Bitmap`一直存在于内存中,所以,这个场景下,`Bitmap`的重用变得异常的重要。 *在这里只介绍一种``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章会介绍使用三级缓存来实现Bitmap的重用。* From d57d6d2b1c4f2453401bb3b1920afb9b22e4da95 Mon Sep 17 00:00:00 2001 From: anAngryAnt <695181966@qq.com> Date: Tue, 19 Jul 2016 13:38:59 +0800 Subject: [PATCH 08/40] =?UTF-8?q?Update=20Bitmap=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E4=B8=8E=E4=BD=BF=E7=94=A8.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新Bitmap的分析与使用 --- ...36\220\344\270\216\344\275\277\347\224\250.md" | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" index 03d2c3b..32344ed 100644 --- "a/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" +++ "b/Part1/Android/Bitmap\347\232\204\345\210\206\346\236\220\344\270\216\344\275\277\347\224\250.md" @@ -117,14 +117,15 @@ 我们来看看这段代码的功效: 压缩前:![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c) 压缩后:![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851) - 对比条件为:1080P的魅族Note3拍摄的高清无码照片 + **对比条件为:1080P的魅族Note3拍摄的高清无码照片** 2. **Reuse** 上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。`Bitmap`是吃内存大户,我们不希望多次解析相同的`Bitmap`,也不希望可能不会用到的`Bitmap`一直存在于内存中,所以,这个场景下,`Bitmap`的重用变得异常的重要。 *在这里只介绍一种``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章会介绍使用三级缓存来实现Bitmap的重用。* - 根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。 + 根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。 我们看来看看这个参数最基本的运用方法。 + ``` new BitmapFactory.Options options = new BitmapFactory.Options(); //inBitmap只有当inMutable为true的时候是可用的。 @@ -133,7 +134,8 @@ options.inBitmap = reusedBitmap; ``` - 这样,当你在下一次decodeBitmap的时候,将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入,Android就会复用你的Bitmap了,具体实例: + 这样,当你在下一次decodeBitmap的时候,将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入,Android就会复用你的Bitmap了,具体实例: + ``` @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -181,9 +183,13 @@ return linearLayout; } ``` - 以上代码中,我们在解析了一次一张1080P分辨率的图片,并且设置在`options.inBitmap`中,然后分别decode了同一张图片,并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。 + + 以上代码中,我们在解析了一次一张1080P分辨率的图片,并且设置在`options.inBitmap`中,然后分别decode了同一张图片,并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。 + 3. **Recycle** 一定要记得及时回收Bitmap,否则如上分析,你的native以及dalvik的内存都会被一直占用着,最终导致OOM + + ``` // 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){ @@ -193,5 +199,6 @@ } System.gc(); ``` + - Enjoy Android :) 如果有误,轻喷,欢迎指正。 From 0b544b2530ad42a9ceac0ecb16725a51041c995b Mon Sep 17 00:00:00 2001 From: geniusvjr Date: Tue, 19 Jul 2016 22:22:52 +0800 Subject: [PATCH 09/40] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Data=20Binding?= =?UTF-8?q?=EF=BC=88=E6=95=B0=E6=8D=AE=E7=BB=91=E5=AE=9A=EF=BC=89=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8C=87=E5=8D=97=20=E7=9A=84=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 754e200..c0a9e64 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ * [EventBus用法详解](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/EventBus用法详解.md) * [EventBus源码分析](http://p.codekk.com/blogs/detail/54cfab086c4761e5001b2538) * [Android ORM 框架之 greenDAO 使用心得](http://www.open-open.com/lib/view/open1438065400878.html) + * [Data Binding(数据绑定)用户指南](http://www.jcodecraeer.com/a/anzhuokaifa/developer/2015/0606/3005.html) * [RxJava](http://gank.io/post/560e15be2dca930e00da1083) * 设计一套图片异步加载缓存方案 * Android UI适配 @@ -45,6 +46,7 @@ * [查漏补缺](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/查漏补缺.md) * [Git操作](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Git操作.md) + ======= --- From 4695819a31edaa49f78da53c1608a36d36f88eb6 Mon Sep 17 00:00:00 2001 From: geniusvjr Date: Fri, 22 Jul 2016 10:19:38 +0800 Subject: [PATCH 10/40] UPDATE README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c0a9e64..11a1076 100644 --- a/README.md +++ b/README.md @@ -208,5 +208,7 @@ 有任何问题欢迎联系: geniusvjr@gmail.com +已同步到 [http://www.diycode.cc/wiki/androidinterview](http://www.diycode.cc/wiki/androidinterview) + 拒绝任何形式的商业用途。 From 58e6fdba681f71eb5793c55a0ec28c58f73ace46 Mon Sep 17 00:00:00 2001 From: Hat_Cloud Date: Tue, 26 Jul 2016 11:26:12 +0800 Subject: [PATCH 11/40] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=B1=BB=20=E4=B8=AD=E7=9A=84=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` number++; System.out.println("生产了1个,总共有" + number); notifyAll(); ``` 这几行代码应该放在 while 循环之外 --- ...\210\350\264\271\350\200\205\351\227\256\351\242\230.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" "b/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" index 104bbe0..d3e0b1d 100755 --- "a/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" +++ "b/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" @@ -111,10 +111,10 @@ public class PublicResource { // TODO Auto-generated catch block e.printStackTrace(); } - number++; - System.out.println("生产了1个,总共有" + number); - notifyAll(); } + number++; + System.out.println("生产了1个,总共有" + number); + notifyAll(); } From 92fcb7d941fd9acb5d522fc2eff37e4847b7179a Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Wed, 27 Jul 2016 11:30:01 +0800 Subject: [PATCH 12/40] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E9=80=9A=E4=BF=A1=E5=9F=BA=E7=A1=80=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=88=86=E6=9E=90.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...01\347\250\213\345\210\206\346\236\220.md" | 205 ++++++++++++++++++ README.md | 3 +- 2 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 "Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" diff --git "a/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" new file mode 100644 index 0000000..d052a44 --- /dev/null +++ "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" @@ -0,0 +1,205 @@ +> 老司机们都知道,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) + +##Looper(先分析这个是因为能够引出四者的关系) +在Looper中,维持一个`Thread`对象以及`MessageQueue`,通过Looper的构造函数我们可以知道: +``` + private Looper(boolean quitAllowed) { + mQueue = new MessageQueue(quitAllowed);//传入的参数代表这个Queue是否能够被退出 + mThread = Thread.currentThread(); + } +``` +`Looper`在构造函数里干了两件事情: +1. 将线程对象指向了创建`Looper`的线程 +2. 创建了一个新的`MessageQueue` + +分析完构造函数之后,接下来我们主要分析两个方法: +1. `looper.loop()` +2. `looper.prepare()` + +###looper.loop()(此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系) +``` + public static void loop() { + final Looper me = myLooper();//获得当前线程绑定的Looper + if (me == null) { + throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); + } + final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue + + // Make sure the identity of this thread is that of the local process, + // and keep track of what that identity token actually is. + Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); + + //进入死循环,不断地去取对象,分发对象到Handler中消费 + for (;;) { + Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。 + if (msg == null) { + // No message indicates that the message queue is quitting. + return; + } + + // This must be in a local variable, in case a UI event sets the logger + Printer logging = me.mLogging; + if (logging != null) { + logging.println(">>>>> Dispatching to " + msg.target + " " + + msg.callback + ": " + msg.what); + } + + //在这里,开始分发Message了 + //至于这个target是神马?什么时候被赋值的? + //我们一会分析Handler的时候就会讲到 + msg.target.dispatchMessage(msg); + + if (logging != null) { + logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); + } + + // Make sure that during the course of dispatching the + // identity of the thread wasn't corrupted. + final long newIdent = Binder.clearCallingIdentity(); + if (ident != newIdent) { + Log.wtf(TAG, "Thread identity changed from 0x" + + Long.toHexString(ident) + " to 0x" + + Long.toHexString(newIdent) + " while dispatching to " + + msg.target.getClass().getName() + " " + + msg.callback + " what=" + msg.what); + } + + //当分发完Message之后,当然要标记将该Message标记为 *正在使用* 啦 + msg.recycleUnchecked(); + } + } +``` +*分析了上面的源代码,我们可以意识到,最重要的方法是:* +1. `queue.next()` +2. `msg.target.dispatchMessage(msg)` +3. `msg.recycleUnchecked()` + +其实Looper中最重要的部分都是由`Message`、`MessageQueue`组成的有木有!这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下: +1. `MessageQueue`:装食物的容器 +2. `Message`:被装的食物 +3. `Handler`(msg.target实际上就是`Handler`):食物的消费者 +4. `Looper`:负责分发食物的人 + + +###looper.prepare()(要调用该方法在当前线程启动一个Message loop机制) +``` + private static void prepare(boolean quitAllowed) { + if (sThreadLocal.get() != null) { + throw new RuntimeException("Only one Looper may be created per thread"); + } + //在当前线程绑定一个Looper + sThreadLocal.set(new Looper(quitAllowed)); + } +``` +以上代码只做了两件事情: +1. 判断当前线程有木有`Looper`,如果有则抛出异常(在这里我们就可以知道,Android规定一个线程只能够拥有一个与自己关联的`Looper`)。 +2. 如果没有的话,那么就设置一个新的`Looper`到当前线程。 + +-------------- +##Handler +由于我们使用Handler的通常性的第一步是: +``` + Handler handler = new Handler(){ + //你们有没有很好奇这个方法是在哪里被回调的? + //我也是!所以接下来会分析到哟! + @Override + public void handleMessage(Message msg) { + //Handler your Message + } + }; +``` +所以我们先来分析`Handler`的构造方法 +``` +//空参数的构造方法与之对应,这里只给出主要的代码,具体大家可以到源码中查看 +public Handler(Callback callback, boolean async) { + //打印内存泄露提醒log + .... + + //获取与创建Handler线程绑定的Looper + mLooper = Looper.myLooper(); + if (mLooper == null) { + throw new RuntimeException( + "Can't create handler inside thread that has not called Looper.prepare()"); + } + //获取与Looper绑定的MessageQueue + //因为一个Looper就只有一个MessageQueue,也就是与当前线程绑定的MessageQueue + mQueue = mLooper.mQueue; + mCallback = callback; + mAsynchronous = async; + + } +``` +*带上问题:* +1. `Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? +2. `handler.handleMessage(msg)`在什么时候被回调的? + +###A2:`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? +要分析这个问题,很自然的我们想到从发送消息开始,无论是`handler.sendMessage(msg)`还是`handler.sendEmptyMessage(what)`,我们最终都可以追溯到以下方法 +``` +public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + //引用Handler中的MessageQueue + //这个MessageQueue就是创建Looper时被创建的MessageQueue + MessageQueue queue = mQueue; + + if (queue == null) { + RuntimeException e = new RuntimeException( + this + " sendMessageAtTime() called with no mQueue"); + Log.w("Looper", e.getMessage(), e); + return false; + } + //将新来的Message加入到MessageQueue中 + return enqueueMessage(queue, msg, uptimeMillis); + } +``` + +我们接下来分析`enqueueMessage(queue, msg, uptimeMillis)`: +``` +private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { + //显而易见,大写加粗的赋值啊! + **msg.target = this;** + if (mAsynchronous) { + msg.setAsynchronous(true); + } + return queue.enqueueMessage(msg, uptimeMillis); + } +``` + + +###A2:`handler.handleMessage(msg)`在什么时候被回调的? +通过以上的分析,我们很明确的知道`Message`中的`target`是在什么时候被赋值的了,我们先来分析在`Looper.loop()`中出现过的过的`dispatchMessage(msg)`方法 + +``` +public void dispatchMessage(Message msg) { + if (msg.callback != null) { + handleCallback(msg); + } else { + if (mCallback != null) { + if (mCallback.handleMessage(msg)) { + return; + } + } + //看到这个大写加粗的方法调用没! + **handleMessage(msg);** + } + } +``` + +加上以上分析,我们将之前分析结果串起来,就可以知道了某些东西: +`Looper.loop()`不断地获取`MessageQueue`中的`Message`,然后调用与`Message`绑定的`Handler`对象的`dispatchMessage`方法,最后,我们看到了`handleMessage`就在`dispatchMessage`方法里被调用的。 + +------------------ +通过以上的分析,我们可以很清晰的知道Handler、Looper、Message、MessageQueue这四者的关系以及如何合作的了。 + +#总结: +当我们调用`handler.sendMessage(msg)`方法发送一个`Message`时,实际上这个`Message`是发送到**与当前线程绑定**的一个`MessageQueue`中,然后**与当前线程绑定**的`Looper`将会不断的从`MessageQueue`中取出新的`Message`,调用`msg.target.dispathMessage(msg)`方法将消息分发到与`Message`绑定的`handler.handleMessage()`方法中。 + +一个`Thread`对应多个`Handler` +一个`Thread`对应一个`Looper`和`MessageQueue`,`Handler`与`Thread`共享`Looper`和`MessageQueue`。 +`Message`只是消息的载体,将会被发送到**与线程绑定的唯一的**`MessageQueue`中,并且被**与线程绑定的唯一的**`Looper`分发,被与其自身绑定的`Handler`消费。 + +------ +- Enjoy Android :) 如果有误,轻喷,欢迎指正。 + + + diff --git a/README.md b/README.md index 11a1076..fa51418 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ * [Android基础知识](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android基础知识.md) * [Android内存泄漏总结](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android内存泄漏总结.md) * [Handler内存泄漏分析及解决](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Handler内存泄漏分析及解决.md) - * [Android性能优化](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android性能优化.md) + * [Handler、Looper、Message、MessageQueue基础流程分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/线程通信基础流程分析.md) + * [Android性能优化](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android性能优化.md)* * [ListView详解](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Listview详解.md) * [RecyclerView和ListView的异同](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Recyclerview和Listview的异同.md) * [AsyncTask源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Asynctask源码分析.md) From cb4cfc27fc3ba078898ade459655682c5f844eed Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Wed, 27 Jul 2016 11:36:51 +0800 Subject: [PATCH 13/40] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E9=80=9A=E4=BF=A1=E5=9F=BA=E7=A1=80=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=88=86=E6=9E=90.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...41\200\346\265\201\347\250\213\345\210\206\346\236\220.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" index d052a44..27f8fb5 100644 --- "a/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" +++ "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" @@ -16,7 +16,7 @@ 1. `looper.loop()` 2. `looper.prepare()` -###looper.loop()(此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系) +###looper.loop()(在当前线程启动一个Message loop机制,此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系) ``` public static void loop() { final Looper me = myLooper();//获得当前线程绑定的Looper @@ -82,7 +82,7 @@ 4. `Looper`:负责分发食物的人 -###looper.prepare()(要调用该方法在当前线程启动一个Message loop机制) +###looper.prepare()(在当前线程关联一个Looper对象) ``` private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { From c3f7378d6c6cd01151c70d4f6333aef0f9c637a9 Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Wed, 27 Jul 2016 11:37:06 +0800 Subject: [PATCH 14/40] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E9=80=9A=E4=BF=A1=E5=9F=BA=E7=A1=80=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=88=86=E6=9E=90.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa51418..71f4980 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ * [Android内存泄漏总结](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android内存泄漏总结.md) * [Handler内存泄漏分析及解决](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Handler内存泄漏分析及解决.md) * [Handler、Looper、Message、MessageQueue基础流程分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/线程通信基础流程分析.md) - * [Android性能优化](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android性能优化.md)* + * [Android性能优化](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Android性能优化.md) * [ListView详解](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Listview详解.md) * [RecyclerView和ListView的异同](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Recyclerview和Listview的异同.md) * [AsyncTask源码分析](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/Asynctask源码分析.md) From f65d4bed5f70fa8cf34d72d8713a15e9c01b84d2 Mon Sep 17 00:00:00 2001 From: anAngryAnt <695181966@qq.com> Date: Wed, 27 Jul 2016 11:42:24 +0800 Subject: [PATCH 15/40] =?UTF-8?q?Update=20=E7=BA=BF=E7=A8=8B=E9=80=9A?= =?UTF-8?q?=E4=BF=A1=E5=9F=BA=E7=A1=80=E6=B5=81=E7=A8=8B=E5=88=86=E6=9E=90?= =?UTF-8?q?.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" index 27f8fb5..defe0ca 100644 --- "a/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" +++ "b/Part1/Android/\347\272\277\347\250\213\351\200\232\344\277\241\345\237\272\347\241\200\346\265\201\347\250\213\345\210\206\346\236\220.md" @@ -134,7 +134,7 @@ public Handler(Callback callback, boolean async) { 1. `Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 2. `handler.handleMessage(msg)`在什么时候被回调的? -###A2:`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? +###A1:`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 要分析这个问题,很自然的我们想到从发送消息开始,无论是`handler.sendMessage(msg)`还是`handler.sendEmptyMessage(what)`,我们最终都可以追溯到以下方法 ``` public boolean sendMessageAtTime(Message msg, long uptimeMillis) { From c93422b8810f12e213a9762402fffca7ac4f0873 Mon Sep 17 00:00:00 2001 From: raomengyang Date: Thu, 28 Jul 2016 23:49:25 +0800 Subject: [PATCH 16/40] Fixed static Singleton pattern not be final --- .../\345\215\225\344\276\213\346\250\241\345\274\217.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Part1/DesignPattern/\345\215\225\344\276\213\346\250\241\345\274\217.md" "b/Part1/DesignPattern/\345\215\225\344\276\213\346\250\241\345\274\217.md" index 9a925fd..23f759d 100644 --- "a/Part1/DesignPattern/\345\215\225\344\276\213\346\250\241\345\274\217.md" +++ "b/Part1/DesignPattern/\345\215\225\344\276\213\346\250\241\345\274\217.md" @@ -97,7 +97,7 @@ public class Singleton { /** * 静态初始化器,由JVM来保证线程安全 */ - private static Singleton instance = new Singleton(); + private static final Singleton instance = new Singleton(); } /** From bb24f1fb4a4cee97b65abe62aab107e7377a0652 Mon Sep 17 00:00:00 2001 From: AlwaysBeMySelf Date: Sat, 30 Jul 2016 13:37:42 +0800 Subject: [PATCH 17/40] =?UTF-8?q?Update=20Listview=E8=AF=A6=E8=A7=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除多余语句 --- "Part1/Android/Listview\350\257\246\350\247\243.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Part1/Android/Listview\350\257\246\350\247\243.md" "b/Part1/Android/Listview\350\257\246\350\247\243.md" index 1b943b8..ee533a7 100644 --- "a/Part1/Android/Listview\350\257\246\350\247\243.md" +++ "b/Part1/Android/Listview\350\257\246\350\247\243.md" @@ -1,6 +1,6 @@ #ListView详解 --- -直接继承自AbsListView,AbsListView继承自AdapterView,AdapterView继承自AdapterView,AdapterView又继承自ViewGroup。 +直接继承自AbsListView,AbsListView继承自AdapterView,AdapterView又继承自ViewGroup。 Adpater在ListView和数据源之间起到了一个桥梁的作用 @@ -15,4 +15,4 @@ RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重 * getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。 * 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。 -View的流程分三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。 \ No newline at end of file +View的流程分三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。 From 942953da2e1cf3df5e6755803c613c0e64acc0aa Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Sat, 30 Jul 2016 13:54:58 +0800 Subject: [PATCH 18/40] =?UTF-8?q?=E4=BF=AE=E6=94=B9issue#14=20#10=E6=8C=87?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...55\230\346\263\204\346\274\217\346\200\273\347\273\223.md" | 2 +- ...ndroid\345\237\272\347\241\200\347\237\245\350\257\206.md" | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git "a/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" "b/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" index bfb6f80..445f397 100644 --- "a/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" +++ "b/Part1/Android/Android\345\206\205\345\255\230\346\263\204\346\274\217\346\200\273\347\273\223.md" @@ -24,7 +24,7 @@ Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式 举个例子: ``` -public class Sample() { +public class Sample { int s1 = 0; Sample mSample1 = new Sample(); diff --git "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" index 95f04a8..c7f37e6 100644 --- "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -51,8 +51,8 @@ onRestart()—>**onStart()**—>onResume(),再次回到运行状态。 * Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期) -* 锁定屏与解锁屏幕 - **只会调用onPause(),而不会调用onStop()方法,开屏后则调用onResume()** +* 锁屏:`onPause()->onStop()` +* 解锁:`onStart()->onResume()` * 更多流程分支,请参照以下生命周期流程图 ![](http://img.blog.csdn.net/20130828141902812) From 9b60050b5bf5c1d0050a93f1e2ab60bec9f89e50 Mon Sep 17 00:00:00 2001 From: geniusvjr Date: Sat, 30 Jul 2016 16:21:20 +0800 Subject: [PATCH 19/40] UPDATE README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 11a1076..0138301 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,14 @@ 有任何问题欢迎联系: geniusvjr@gmail.com +欢迎关注我的知乎账号: + +[Francis的知乎](https://www.zhihu.com/people/tao-cheng-1) + +微博账号: + +[Francis的微博](http://weibo.com/3627982543/profile?rightmod=1&wvr=6&mod=personinfo) + 已同步到 [http://www.diycode.cc/wiki/androidinterview](http://www.diycode.cc/wiki/androidinterview) 拒绝任何形式的商业用途。 From 4ad6ffd345468155116c21349b7f8dd8536b0c85 Mon Sep 17 00:00:00 2001 From: Liang Sam <695181966@qq.com> Date: Wed, 3 Aug 2016 12:46:47 +0800 Subject: [PATCH 20/40] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=3D=3D?= =?UTF-8?q?=E4=B8=8Eequals=E7=9A=84=E5=8C=BA=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 46ab7fa..a906f4d 100644 --- "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -20,10 +20,74 @@ **equals与==的区别。** [http://www.importnew.com/6804.html](http://www.importnew.com/6804.html) +> ==与equals的主要区别是:==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。 + +1. 使用==比较有两种情况: + + 比较基础数据类型(Java中基础数据类型包括八中:short,int,long,float,double,char,byte,boolen):这种情况下,==比较的是他们的值是否相等。 + 引用间的比较:在这种情况下,==比较的是他们在内存中的地址,也就是说,除非引用指向的是同一个new出来的对象,此时他们使用`==`去比较得到true,否则,得到false。 +2. 使用equals进行比较: + + equals追根溯源,是Object类中的一个方法,在该类中,equals的实现也仅仅只是比较两个对象的内存地址是否相等,但在一些子类中,如:String、Integer 等,该方法将被重写。 + +3. 以`String`类为例子说明`eqauls`与`==`的区别: +> 在开始这个例子之前,同学们需要知道JVM处理String的一些特性。*Java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。*当使用 +`String a = "abc"`这样的语句进行定义一个引用的时候,首先会在*字符串缓冲池*中查找是否已经相同的对象,如果存在,那么就直接将这个对象的引用返回给a,如果不存在,则需要新建一个值为"abc"的对象,再将新的引用返回a。`String a = new String("abc");`这样的语句明确告诉JVM想要产生一个新的String对象,并且值为"abc",于是就*在堆内存中的某一个小角落开辟了一个新的String对象*。 + + - `==`在比较引用的情况下,会去比较两个引用的内存地址是否相等。 + ``` + String str1 = "abc"; + String str2 = "abc"; + + System.out.println(str1 == str2); + System.out.println(str1.equals(str2)); + + String str2 = new String("abc"); + System.out.println(str1 == str2); + System.out.println(str1.equals(str2)); + + ``` + 以上代码将会输出 + true + true + false + true + **第一个true:**因为在str2赋值之前,str1的赋值操作就已经在内存中创建了一个值为"abc"的对象了,然后str2将会与str1指向相同的地址。 + **第二个true:**因为`String`已经重写了`equals`方法:为了方便大家阅读我贴出来,并且在注释用进行分析: + ``` + public boolean equals(Object anObject) { + //如果比较的对象与自身内存地址相等的话 + //就说明他两指向的是同一个对象 + //所以此时equals的返回值跟==的结果是一样的。 + if (this == anObject) { + return true; + } + //当比较的对象与自身的内存地址不相等,并且 + //比较的对象是String类型的时候 + //将会执行这个分支 + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = value.length; + if (n == anotherString.value.length) { + char v1[] = value; + char v2[] = anotherString.value; + int i = 0; + //在这里循环遍历两个String中的char + while (n-- != 0) { + //只要有一个不相等,那么就会返回false + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + } + return false; + } + ``` + 进行以上分析之后,就不难理解第一段代码中的实例程序输出了。 -主要区别在于前者是方法后者是操作符。“==”的行为对于每个对象来说与equals()是完全相同的,但是equals()可以基于业务规则的不同而重写(overridden )。“==”习惯用于原生(primitive)类型之间的比较,而equals()仅用于对象之间的比较。 -==与equals的主要区别是:==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。 --- From 02b07f61815770493d5eaa8006ed8c9cfc95030f Mon Sep 17 00:00:00 2001 From: anAngryAnt <695181966@qq.com> Date: Wed, 3 Aug 2016 12:56:33 +0800 Subject: [PATCH 21/40] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80=E7=9F=A5?= =?UTF-8?q?=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index a906f4d..25a2d24 100644 --- "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -20,7 +20,7 @@ **equals与==的区别。** [http://www.importnew.com/6804.html](http://www.importnew.com/6804.html) -> ==与equals的主要区别是:==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。 +> ==与equals的主要区别是:==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。equals()方法最重要的一点是,能够根据业务要求去重写,按照自定义规则去判断两个对象是否相等。重写equals()方法的时候,要注意一下hashCode是否会因为对象的属性改变而改变,否则在使用散列集合储存该对象的时候会碰到坑!!理解equals()方法的存在是很重要的。 1. 使用==比较有两种情况: From 610767babbbf3abdf839967e624a06685fd071df Mon Sep 17 00:00:00 2001 From: XuYushi Date: Mon, 15 Aug 2016 16:14:24 +0800 Subject: [PATCH 22/40] =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\210\350\264\271\350\200\205\351\227\256\351\242\230.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" "b/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" index 104bbe0..d3e0b1d 100755 --- "a/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" +++ "b/Part2/JavaConcurrent/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\351\227\256\351\242\230.md" @@ -111,10 +111,10 @@ public class PublicResource { // TODO Auto-generated catch block e.printStackTrace(); } - number++; - System.out.println("生产了1个,总共有" + number); - notifyAll(); } + number++; + System.out.println("生产了1个,总共有" + number); + notifyAll(); } From 9483807592b34711d240fe860e976c924c848c82 Mon Sep 17 00:00:00 2001 From: ZubinXiong Date: Wed, 31 Aug 2016 10:21:07 +0800 Subject: [PATCH 23/40] =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=9C=AA=E5=9B=9E=E7=AD=94=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...roid\345\237\272\347\241\200\347\237\245\350\257\206.md" | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" index c7f37e6..5ec1c87 100644 --- "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -317,8 +317,12 @@ Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一 **怎样退出终止App** **Asset目录与res目录的区别。** +res 目录下面有很多文件,例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外,其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。Asset 也是用来存储资源,但是 asset 文件内容只能通过路径或者 AssetManager 读取。 [官方文档](https://developer.android.com/studio/projects/index.html) **Android怎么加速启动Activity。** +分两种情况,启动应用 和 普通Activity +启动应用 :Application 的构造方法,onCreate 方法中不要进行耗时操作,数据预读取(例如 init 数据) 放在异步中操作 +启动普通的Activity:A 启动B 时不要在 A 的 onPause 中执行耗时操作。因为 B 的 onResume 方法必须等待 A 的 onPause 执行完成后才能运行 **Android内存优化方法:ListView优化,及时关闭资源,图片缓存等等。** @@ -432,7 +436,7 @@ onStartCommand中回调了onStart,onStart中通过mServiceHandler发送消息 构建工具、Groovy语法、Java -Jar包里面只有代码,aar里面不光有代码还包括 +Jar包里面只有代码,aar里面不光有代码还包括代码还包括资源文件,比如 drawable 文件,xml 资源文件。对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度 --- From 8a2d6e3bdbda88ff936076ccccde31cbb7b44a46 Mon Sep 17 00:00:00 2001 From: ZubinXiong Date: Tue, 13 Sep 2016 22:12:29 +0800 Subject: [PATCH 24/40] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=20eventbus=20?= =?UTF-8?q?3.0=20=E7=9A=84=E4=BD=BF=E7=94=A8=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=A1=A5=E5=85=85=E4=BA=86=E5=85=B3=E4=BA=8E3.0?= =?UTF-8?q?=E5=BC=95=E8=BF=9B=E7=9A=84=E7=B4=A2=E5=BC=95=E5=8A=A0=E9=80=9F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...47\224\250\346\263\225\350\257\246\350\247\243.md" | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git "a/Part1/Android/EventBus\347\224\250\346\263\225\350\257\246\350\247\243.md" "b/Part1/Android/EventBus\347\224\250\346\263\225\350\257\246\350\247\243.md" index 3c26b70..28c7ce0 100644 --- "a/Part1/Android/EventBus\347\224\250\346\263\225\350\257\246\350\247\243.md" +++ "b/Part1/Android/EventBus\347\224\250\346\263\225\350\257\246\350\247\243.md" @@ -63,7 +63,9 @@ eventBus.post(event) 接收消息并处理: ``` -public void onEvent(MessageEvent event) {} +// 3.0后不再要求事件以 onEvent 开头,而是采用注解的方式 +@Subscribe(threadMode = ThreadMode.MAIN) +public void receive(MessageEvent event){} ``` 注销事件接收: @@ -72,6 +74,13 @@ public void onEvent(MessageEvent event) {} eventBus.unregister(this); ``` +索引加速: + +``` +3.0 后引入了索引加速(默认不开启)的功能,即通过 apt 编译插件的方式,在代码编译的时候对注解进行索引,避免了以往通过反射造成的性能损耗。 +如何使用可以参考[官方文档](http://greenrobot.org/eventbus/documentation/subscriber-index/) +``` + 最后,proguard 需要做一些额外处理: ``` From 1d673cebb5031e4174c6a0d8a80ab32c20766b4f Mon Sep 17 00:00:00 2001 From: wangkai Date: Mon, 19 Sep 2016 15:41:52 +0800 Subject: [PATCH 25/40] modified: "Part2/JavaSE/HashTable\346\272\220\347\240\201\345\211\226\346\236\220.md" --- ...20\347\240\201\345\211\226\346\236\220.md" | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git "a/Part2/JavaSE/HashTable\346\272\220\347\240\201\345\211\226\346\236\220.md" "b/Part2/JavaSE/HashTable\346\272\220\347\240\201\345\211\226\346\236\220.md" index 032cc20..99adfce 100644 --- "a/Part2/JavaSE/HashTable\346\272\220\347\240\201\345\211\226\346\236\220.md" +++ "b/Part2/JavaSE/HashTable\346\272\220\347\240\201\345\211\226\346\236\220.md" @@ -68,6 +68,24 @@ public class Hashtable // 将“子Map”的全部元素都添加到Hashtable中 putAll(t); } + + private int hash(Object k) { + if (useAltHashing) { + if (k.getClass() == String.class) { + return sun.misc.Hashing.stringHash32((String) k); + } else { + int h = hashSeed ^ k.hashCode(); + + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + } else { + return k.hashCode(); + } + } public synchronized int size() { return count; @@ -131,7 +149,7 @@ public class Hashtable // 返回key对应的value,没有的话返回null public synchronized V get(Object key) { Entry tab[] = table; - int hash = key.hashCode(); + int hash = hash(key); // 计算索引值, int index = (hash & 0x7FFFFFFF) % tab.length; // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 @@ -179,7 +197,7 @@ public class Hashtable // 若“Hashtable中已存在键为key的键值对”, // 则用“新的value”替换“旧的value” Entry tab[] = table; - int hash = key.hashCode(); + int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { @@ -211,7 +229,7 @@ public class Hashtable // 删除Hashtable中键为key的元素 public synchronized V remove(Object key) { Entry tab[] = table; - int hash = key.hashCode(); + int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; //从table[index]链表中找出要删除的节点,并删除该节点。 @@ -376,7 +394,7 @@ public class Hashtable Map.Entry entry = (Map.Entry)o; Object key = entry.getKey(); Entry[] tab = table; - int hash = key.hashCode(); + int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next) @@ -394,7 +412,7 @@ public class Hashtable Map.Entry entry = (Map.Entry) o; K key = entry.getKey(); Entry[] tab = table; - int hash = key.hashCode(); + int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null; e != null; @@ -837,7 +855,7 @@ public class Hashtable public synchronized boolean containsKey(Object key) { Entry tab[] = table; /计算hash值,直接用key的hashCode代替 - int hash = key.hashCode(); + int hash = hash(key); // 计算在数组中的索引值 int index = (hash & 0x7FFFFFFF) % tab.length; // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 @@ -852,4 +870,4 @@ public class Hashtable 很明显,如果value为null,会直接抛出NullPointerException异常,但源码中并没有对key是否为null判断,有点小不解!不过NullPointerException属于RuntimeException异常,是可以由JVM自动抛出的,也许对key的值在JVM中有所限制吧。 4. Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。 -5. Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变。 \ No newline at end of file +5. Hashtable和HashMap都重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号外改变,而后面的位都不变。 \ No newline at end of file From ebb6e865785cde22b97b553c40a4dc7e14c4e278 Mon Sep 17 00:00:00 2001 From: Akuma Date: Fri, 21 Oct 2016 23:05:43 +0800 Subject: [PATCH 26/40] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80=E7=9F=A5?= =?UTF-8?q?=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 25a2d24..782ade9 100644 --- "a/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part2/JavaSE/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -121,9 +121,9 @@ final方法,获得运行时类型。 该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。 -一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。 +一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。 -如果不重写hashcode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。 +如果不重写hashCode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。 7.wait方法 @@ -195,7 +195,7 @@ Java 平台提供了两种类型的字符串:String和StringBuffer / StringBui **try catch finally,try里有return,finally还执行么?** -会执行,在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中国就从语法规定不能做这样的事。 +会执行,在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中就从语法规定不能做这样的事。 --- From 50a0528677a0ba4c6c0ec9ee948ddf528fea824e Mon Sep 17 00:00:00 2001 From: tanglie Date: Fri, 18 Nov 2016 17:01:53 +0800 Subject: [PATCH 27/40] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97=E5=92=8C=E7=97=85=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...272\347\241\200\347\237\245\350\257\206.md" | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" index 5ec1c87..359b62f 100644 --- "a/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Part1/Android/Android\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -4,11 +4,11 @@ * FrameLayout(框架布局) - 此布局是五中布局中最简单的布局,Android中并没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角,我们可以使用``android:layout_margin``,``android:layout_gravity``等属性去控制子控件相对布局的位置。 + 此布局是五种布局中最简单的布局,Android中并没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角,我们可以使用``android:layout_margin``,``android:layout_gravity``等属性去控制子控件相对布局的位置。 * LinearLayout(线性布局) - 一行只控制一个控件的线性布局,所以当有很多控件需要在一个界面中列出时,可以用LinearLayout布局。 +    一行(或一列)只控制一个控件的线性布局,所以当有很多控件需要在一个界面中列出时,可以用LinearLayout布局。 此布局有一个需要格外注意的属性:``android:orientation=“horizontal|vertical``。 * 当`android:orientation="horizontal`时,*说明你希望将水平方向的布局交给**LinearLayout** *,其子元素的`android:layout_gravity="right|left"` 等控制水平方向的gravity值都是被忽略的,*此时**LinearLayout**中的子元素都是默认的按照水平从左向右来排*,我们可以用`android:layout_gravity="top|bottom"`等gravity值来控制垂直展示。 @@ -20,7 +20,7 @@ * RelativeLayout(相对布局) - 这个布局也是相对自由的布局,Android 对该布局的child view的 水平layout& 垂直layout做了解析,由此我们可以FrameLayout的基础上使用标签或者Java代码对垂直方向 以及 水平方向 布局中的views任意的控制. + 这个布局也是相对自由的布局,Android 对该布局的child view的 水平layout& 垂直layout做了解析,由此我们可以FrameLayout的基础上使用标签或者Java代码对垂直方向 以及 水平方向 布局中的views进行任意的控制. * 相关属性: @@ -68,7 +68,7 @@ **任务栈**是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其``onDestory()``方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名. * standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(*onCreate()->onStart()->onResume()*)都会执行。 - - singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的``onNewIntent()``方法会被回调.如果Activity已经存在但是不在栈顶,那么作用于*standard模式*一样. + - singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的``onNewIntent()``方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与*standard模式*一样. - singleTask: 栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,``onNewIntent()``,并且singleTask会清理在当前Activity上面的所有Activity.(clear top) - singleInstance : 加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了 @@ -128,9 +128,9 @@ Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放 总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,需要注意的几点: -1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动的存储和在activity重新创建的时候自动的恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,将不会存储它的状态。 +1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。 -2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息时,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 +2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。 3.由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。 @@ -217,7 +217,7 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { android:enabled="true" /> ``` -**广播(Boardcast Receiver)的两种动态注册和静态注册有什么区别。** +**广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。** * 静态注册:在AndroidManifest.xml文件中进行注册,当App退出后,Receiver仍然可以接收到广播并且进行相应的处理 * 动态注册:在代码中动态注册,当App退出后,也就没办法再接受广播了 @@ -247,7 +247,7 @@ public void onRestoreInstanceState(Bundle savedInstanceState) { * 【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart() **onDestroy方法里重启service** - * service +broadcast 方式,就是当service走ondestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service + * service +broadcast 方式,就是当service走onDestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service * 也可以直接在onDestroy()里startService * 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证 @@ -306,7 +306,7 @@ Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一 **如何判断应用被强杀** -在Applicatio中定义一个static常量,赋值为-1,在欢迎界面改为0,如果被强杀,application重新初始化,在父类Activity判断该常量的值。 +在Application中定义一个static常量,赋值为-1,在欢迎界面改为0,如果被强杀,application重新初始化,在父类Activity判断该常量的值。 **应用被强杀如何解决** From 241c4cb642819da10bca2edd0d74be7b0ac22a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B6=E7=A8=8B?= Date: Tue, 22 Nov 2016 23:14:11 +0800 Subject: [PATCH 28/40] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9324ca..7d7946f 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ geniusvjr@gmail.com 欢迎关注我的知乎账号: -[Francis的知乎](https://www.zhihu.com/people/tao-cheng-1) +[Francis的知乎](https://www.zhihu.com/people/FrancisTao) 微博账号: From 487ae3f07ed1c735f8eb5fc8cb93ef2e092faf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=AB=E6=B8=85=E6=89=8D?= Date: Tue, 10 Jan 2017 21:18:53 +0800 Subject: [PATCH 29/40] NetWork MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增了对计算机网络的总体概述 --- ...72\347\241\200\346\261\207\346\200\273.md" | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 "Part4/Network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\346\261\207\346\200\273.md" diff --git "a/Part4/Network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\346\261\207\346\200\273.md" "b/Part4/Network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\346\261\207\346\200\273.md" new file mode 100644 index 0000000..e4797a3 --- /dev/null +++ "b/Part4/Network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\237\272\347\241\200\346\261\207\346\200\273.md" @@ -0,0 +1,207 @@ +#计算机网络体系分类: +计算机网络整个体系有两个大的分类:一个是国际组织制定的OSI七层模型,一种是实际使用中的TCP/IP四层模型。 + +|OSI七层模型 |TCP/IP四层模型| +|----|----| +|应用层|| +|表示层|应用层| +|会话层|| +|传输层|传输层| +|网络层|网络层| +|数据链路层|物理接入层| +|物理层|| + +#物理层: +物理层主要是实现主机和网络之间的物理连接,规定一些与传输媒体接口有关的一些特性。 + +##通信方式: +- **单工通信:**只能由A主机向B主机发送消息,通信是单向的。 + +- **半双工通信:**同一时间只能是由A主机向B主机或者是B主机向A主机发送信息,不能同时发送消息。 + +- **全双工通信:** A主机和B主机可以实现在同一时间内既接收消息,又发送消息,极大的提升了通信效率。 + +##常见引导型传输媒体: +###双绞线: +分为屏蔽双绞线(STP)和非屏蔽双绞线(UTP),屏蔽双绞线就是在非屏蔽双绞线外边又加了一层屏蔽层 + +tips:为什么双绞线要两根线绞起来,两个线绞起来可以有效的减少相互之间的电磁干扰。 + +###同轴电缆: +由内导体铜制芯线,绝缘层,网状编织的外导体屏蔽层,以及塑料保护外层组成。有良好的抗干扰性,被广泛用于较高速率的传输。 + +###光缆: +由非常透明的石英玻璃拉成细丝,主要由纤芯和包层构成双层通讯柱。可以分为单模光纤和多模光纤。 + +##信道复用技术: + +- **频分复用:**根据传输的波的频率的不同,将不同频段的波用于不同的通信。 + +- **时分复用:**将时间分割为时间段,规定在某个时间段内发送什么样的信息,根据时间段来进行信息类别的区分。也称为**同步时分复用** + +- **统计时分复用:**基于时分复用的一种改进,由于基本的时分复用并没有考虑到每个时间段内是否有信息发出,例如说每个时间段是20,但是发送消息只占用其中的2,那么剩下的18就处于空闲时间。统计时分复用就是基于这种考虑而设计的,他允许用户有数据时就将数据发送到缓存中,然后会依次扫描输入缓存,把缓存中的数据放入到STMD帧中,若没有就跳过,每当一个帧满了就把这个帧发出去。STMD帧不是分配的固定时序,而是根据需要动态的分配时隙,也称之为**异步时分复用** + +- 光分复用:就是光的频分复用,根据光谱中频率的不同,用不同频率的光来携带不同的信息。 + +#数据链路层: +数据链路层主要是将上层的数据转化为数据帧发送到链路上,以及把接受到的帧中的数据取出并交给网络层。 + +##通信方式: +- **点对点通信:**通信方式是点到点的,也就是只能是两个点之间的通信。常见的就是PPP协议 + +- **广播通信:**广播通讯,也就是可以同时实现一对多的通信。常见的就是CSMA/CD(载波监听多点访问/冲突检测) + +##核心要解决的问题: + +- **封装成帧:**在一段数据的前后分别添加首部和尾部,这样就构成了一个帧。接收端在接受到后就可以方便准确的确定帧的首尾,也称为帧定界符。同时定义出了最大传输单元(MTU)--表示一次可以传输的最大长度上限。 + +- **透明传输:** 由于使用特定的字符来表示帧的开始和结束,所以传输的内容中不能出现和帧定界符相同的字符,但这显然是不可能的,为了解决这个办法,就可以在内容中和帧定界符一样的字符前边加上规定好的转义字符,这种方法称为字符填充或者是字节填充。 + +- **差错检测:** 比特在传输过程中可能产生差错,差错检测就是用于检测出现的差错,以便及时的重传,最常用的差错检测算法就是[CRC(循环冗余检验)](http://baike.sogou.com/v7730112.htm?fromTitle=CRC%E6%A0%A1%E9%AA%8C) + +#网络层 + +网络层主要是向上只提供简单灵活的,无连接的,尽最大努力交付的数据报服务。 + +##IP协议(网际协议): +IP协议是TCP/IP体系中最主要的协议之一,一切在IP之下,一切又在IP之上。我们常说的应该是IP协议族,还包含配套的协议: + +- ARP(地址解析协议):将网络的IP地址转化为实际的物理地址(MAC地址),并存储在MAC地址表中。 + +- ICMP(网际控制报文协议):用于进行差错情况和异常情况的反馈,分为询问报文和差错报告报文。 + +- IGMP(网际组管理协议):用于控制和管理多播和组播的协议。 + +网络地址(IP地址)主要分为五类: + +- A类:前8位为网络位,后24位为主机位,首位为0 + +- B类:前16位为网络位,后16位为主机位,前两位为10 + +- C类:前24位为网络位,后8位位主机位,前三位为110 + +- D类:前四位为1110,用于多播地址 + +- E类:前四位为1111,保留为以后使用 + +##路由选择协议: + +路由选择协议分为**内部网关协议(IGP)**和**外部网关协议(EGP)** + +###内部网关协议: + +主要是有RIP协议和OSPF协议 + +- **RIP协议(路由信息协议):**基于距离矢量的协议 + +- **OSPF(开放最短路径优先协议):**基于链路状态的协议 + +###外部网关协议: + +主要是**边界网关协议(BGP)**,将一个大的网络分为多个小的自治域,每个自治域内有一个网关路由负责和其他的自治域的网关进行通讯。 + +# 传输层 + +网络层主要是为主机之间提供逻辑通讯,而传输层为应用程序之间提供端到端的逻辑通讯。主要是两种类型的通讯方式,面向连接的TCP协议和面向无连接的UDP。 + +##端口号: +端口号按照使用地方的不同分为两大类:服务端端口号,客户端端口号。 +按照占用时长又可以分为熟知端口号(0~1023),登记端口号(1024~49151),短暂端口号(49152~65535) + +###常见端口: + +- FTP(文件传输协议):20,21------其中20端口用于传输数据,21端口用于命令控制 + +- Telnet(远程登录协议):23 + +- DNS(域名解析服务):53 + +- TFTP(简单文件传输协议):69 + +- HTTP(超文本传输协议):80 + +##两种协议: + +- **UDP(用户数据报协议):** + + - UDP是无连接的 + - UDP使用尽最大努力交付,但是不保证可靠交付 + - UDP是面向报文的 + - UDP没有拥塞控制 + - UDP支持一对一,一对多,多对一,多对一的交互通讯 + - UDP首部的开销小 + +- **TCP(传输控制协议):** + + - TCP是面向连接的 + - 每一条TCP连接只能由两个端点,每一条TCP连接只能是点对点的$TCP连接::= \{ socket_1,socket_2 \} =\{ (IP_1:port_1),(IP_2:port_2)\}$ + - TCP提供可靠交付的服务 + - TCP提供全双工通信 + - 面向字节流 + +###可靠传输的实现机制: + +- **停止等待协议:** 每发完一个分组就停止发送,直到收到上一个分组的确认信息。若超过规定时间没有接收到确认信息,边认为是分组丢失,开始重传。 + +- **连续ARQ协议:** 发送方采用滑动窗口的形式,处于滑动窗口的分组可以直接发送出去;接收方一般采用累积确认的方式,也就是说接受方不必对接收到的每一个分组都进行确认,而是对按序到达的最后一个分组进行确认,而发送方每接收到一个确认,窗口就向前滑动一定距离。 + +- **流量控制:**协调发送方和接收方的收发速率,利用滑动窗口来协调,使用探测报文来探测当前应该发送的速率,采用[Nagle算法](http://baike.sogou.com/v10500918.htm?fromTitle=Nagle%E7%AE%97%E6%B3%95) + +- **拥塞控制:** +当网络中某一资源的需求超出了该资源所能提供的可用部分,这时网络的性能就要开始变坏,这种情况就叫做拥塞。而拥塞控制就是为了减少或者避免拥塞对网络性能的影响而做出的一种控制手段。 + + - **拥塞控制思路:**发送方维持一个叫做**拥塞窗口**的状态变量,拥塞窗口的大小取决于网络的拥塞程度,并且在动态的变化。发送方让自己的发送窗口等于拥塞窗口,如果在考虑接收方的接收能力,一般发送窗口还要小于拥塞窗口。 + + - **慢开始:**当主机开始发送数据的时候,由小到大的增大发送窗口,也就是由小到大的增大拥塞窗口。接收方接收到一个报文之后就回传一个确认报文,发送方每接收到一个确认报文,就将拥塞窗口加1,这样每经过一个传输轮次之后,拥塞窗口就增大一倍。 + + - **拥塞避免:**思路是让拥塞窗口缓慢的增大,即每经过一个往返时间RTT就把发送方的拥塞窗口加1,而不是加倍,这样拥塞窗口就是线性缓慢增加,比慢开始的增长速率缓慢的多。 + + - **慢开始门限:**为了防止拥塞窗口增长过大引起网络拥塞,还需要设置一个慢开始门限 + + - 拥塞窗口<慢开始门限时,使用慢开始算法 + - 拥塞窗口>慢开始门限时,使用拥塞避免算法 + - 拥塞窗口=慢开始门限时,两种算法都可以 + + - **快重传:**要求收到方每收到一个时序的报文段之后就立即发出重复确认,而不要等到自己发送数据时才进行捎带确认。而发送方只要一连收到三个重复确认就应当立即重传对方尚未接受到的报文,而不必等待为报文设置的重传计时器到期。 + + - **快回复:**与快重传配合使用,当发送方连续收到三个重复确认的时候,就执行“乘法减小”算法,将慢开始门限减半。将拥塞窗口设置为慢开始门限减半之后的值,并开始指向拥塞避免算法。 + +###TCP的连接管理: + +####连接三次握手: + +1. 客户端请求建立连接:SYN=1,seq=x; +2. 服务器对客户端的请求进行响应:SYN=1,ACK=1,seq=y,ack=x+1 +3. 客户端对服务器端的响应信息进行回应:ACK=1,seq=x+1,ack=y+1 + +**注:** SYN为同步信息,在建立连接过程中始终为1 + +####断开连接四次握手: + +1. 客户端请求断开连接: FIN=1,seq = u; +2. 服务端对客户端的请求应答:ACK=1,seq=v,ack=u+1; +3. 服务端请求断开连接:FIN=1,ACK=1,seq=w,ack=u+1; +4. 客户端对服务端的请求应答:ACK=1,seq=u+1,ack=w+1; + +# 应用层 +应用层有多种协议,常用到的就是HTTP以及HTTPS。 + +**HTTP协议报文格式** + +|请求报文|相应报文| +|---|---| +|请求行(用于区分是请求报文还是响应报文,在响应报文中为状态行)|状态行| +|首部行(用来说明浏览器,服务器或者是报文主体的一些信息)|首部行| +|空行(用于隔开请求实体和首部行)|空行| +|实体主体(用于一些请求或者是响应的的参数内容等)|实体主体| + +**常见状态码** + +> 1xx:表示通知信息,例如表示收到或者是正在处理 +> 2xx:表示成功,例如表示接受或知道了 +> 3xx:表示重定向,例如表示完成请求还需要采取进一步的行动 +> 4xx:表示客户端的差错,例如表示请求中有语法错误或不能完成 +> 5xx:表示服务器端的差错:例如表示服务器失效无法完成请求 + +HTTPS协议就是对HTTP协议的加密,更加安全可靠,采用HTTP+SSL(安全套接字层)来保证数据的安全性。 + From 6cd334543938f0bcaab30acbe6806945fdee6215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=AB=E6=B8=85=E6=89=8D?= Date: Tue, 10 Jan 2017 21:29:54 +0800 Subject: [PATCH 30/40] =?UTF-8?q?=E7=BB=8F=E5=85=B8=E7=9A=84=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E7=AE=97=E6=B3=95=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\346\263\225\346\200\273\347\273\223.md" | 647 ++++++++++++++++++ 1 file changed, 647 insertions(+) create mode 100644 "Part3/Algorithm/Sort/\351\235\242\350\257\225\344\270\255\347\232\204 10 \345\244\247\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223.md" diff --git "a/Part3/Algorithm/Sort/\351\235\242\350\257\225\344\270\255\347\232\204 10 \345\244\247\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223.md" "b/Part3/Algorithm/Sort/\351\235\242\350\257\225\344\270\255\347\232\204 10 \345\244\247\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223.md" new file mode 100644 index 0000000..626f256 --- /dev/null +++ "b/Part3/Algorithm/Sort/\351\235\242\350\257\225\344\270\255\347\232\204 10 \345\244\247\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223.md" @@ -0,0 +1,647 @@ +

本文转载自码农网:http://www.codeceo.com/article/10-sort-algorithm-interview.html#0-tsina-1-10490-397232819ff9a47a7b7e80a40613cfe1 +

+ + +

查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中。因为其实现代码较短,应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗,只要熟悉了思想,灵活运用也不是难事。一般在面试中最常考的是快速排序和归并排序,并且经常有面试官要求现场写出这两种排序的代码。对这两种排序的代码一定要信手拈来才行。还有插入排序、冒泡排序、堆排序、基数排序、桶排序等。面试官对于这些排序可能会要求比较各自的优劣、各种算法的思想及其使用场景。还有要会分析算法的时间和空间复杂度。通常查找和排序算法的考察是面试的开始,如果这些问题回答不好,估计面试官都没有继续面试下去的兴趣都没了。所以想开个好头就要把常见的排序算法思想及其特点要熟练掌握,有必要时要熟练写出代码。

+

接下来我们就分析一下常见的排序算法及其使用场景。限于篇幅,某些算法的详细演示和图示请自行寻找详细的参考。

+

冒泡排序

+

冒泡排序是最简单的排序之一了,其大体思想就是通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样,因此而得名。举个栗子,对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)。

+

实现代码:

+
+
/**
+ *@Description:<p>冒泡排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-3 下午8:54:27
+ */
+public class BubbleSort {
+
+    public static void bubbleSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+        for(int i=0; i<arr.length-1; i++) {
+            for(int j=arr.length-1; j>i; j--) {
+                if(arr[j] < arr[j-1]) {
+                    swap(arr, j-1, j);
+                }
+            }
+        }
+    }
+
+    public static void swap(int[] arr, int i, int j) {
+        int temp = arr[i];
+        arr[i] = arr[j];
+        arr[j] = temp;
+    }
+}
+
+

选择排序

+

选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面。但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。举个栗子,对5,3,8,6,4这个无序序列进行简单选择排序,首先要选择5以外的最小数来和5交换,也就是选择3和5交换,一次排序后就变成了3,5,8,6,4.对剩下的序列一次进行选择和交换,最终就会得到一个有序序列。其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。选择排序的时间复杂度为O(n^2)

+

实现代码:

+
+
/**
+ *@Description:<p>简单选择排序算法的实现</p>
+ *@author 王旭
+ *@time 2016-3-3 下午9:13:35
+ */
+public class SelectSort {
+
+    public static void selectSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+        int minIndex = 0;
+        for(int i=0; i<arr.length-1; i++) { //只需要比较n-1次
+            minIndex = i;
+            for(int j=i+1; j<arr.length; j++) { //从i+1开始比较,因为minIndex默认为i了,i就没必要比了。
+                if(arr[j] < arr[minIndex]) {
+                    minIndex = j;
+                }
+            }
+
+            if(minIndex != i) { //如果minIndex不为i,说明找到了更小的值,交换之。
+                swap(arr, i, minIndex);
+            }
+        }
+
+    }
+
+    public static void swap(int[] arr, int i, int j) {
+        int temp = arr[i];
+        arr[i] = arr[j];
+        arr[j] = temp;
+    }
+
+}
+
+

插入排序

+

插入排序不是通过交换位置而是通过比较找到合适的位置插入元素来达到排序的目的的。相信大家都有过打扑克牌的经历,特别是牌数较大的。在分牌时可能要整理自己的牌,牌多的时候怎么整理呢?就是拿到一张牌,找到一个合适的位置插入。这个原理其实和插入排序是一样的。举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一位,变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动,6插在8前面,8后移一位,4插在5前面,从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。简单插入排序的时间复杂度也是O(n^2)。

+

实现代码:

+
+
/**
+ *@Description:<p>简单插入排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-3 下午9:38:55
+ */
+public class InsertSort {
+
+    public static void insertSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+
+        for(int i=1; i<arr.length; i++) { //假设第一个数位置时正确的;要往后移,必须要假设第一个。
+
+            int j = i;
+            int target = arr[i]; //待插入的
+
+            //后移
+            while(j > 0 && target < arr[j-1]) {
+                arr[j] = arr[j-1];
+                j --;
+            }
+
+            //插入 
+            arr[j] = target;
+        }
+
+    }
+
+}
+
+

快速排序

+

快速排序一听名字就觉得很高端,在实际应用当中快速排序确实也是表现最好的排序算法。冒泡排序虽然高端,但其实其思想是来自冒泡排序,冒泡排序是通过相邻元素的比较和交换把最小的冒泡到最顶端,而快速排序是比较和交换小数和大数,这样一来不仅把小数冒泡到上面同时也把大数沉到下面。

+

举个栗子:对5,3,8,6,4这个无序序列进行快速排序,思路是右指针找比基准数小的,左指针找比基准数大的,交换之。

+

5,3,8,6,4 用5作为比较的基准,最终会把5小的移动到5的左边,比5大的移动到5的右边。

+

5,3,8,6,4 首先设置i,j两个指针分别指向两端,j指针先扫描(思考一下为什么?)4比5小停止。然后i扫描,8比5大停止。交换i,j位置。

+

5,3,4,6,8 然后j指针再扫描,这时j扫描4时两指针相遇。停止。然后交换4和基准数。

+

4,3,5,6,8 一次划分后达到了左边比5小,右边比5大的目的。之后对左右子序列递归排序,最终得到有序序列。

+

上面留下来了一个问题为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,要交换基准数到相遇的位置。一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。

+

快速排序是不稳定的,其时间平均时间复杂度是O(nlgn)。

+

实现代码:

+
+
/**
+ *@Description:<p>实现快速排序算法</p>
+ *@author 王旭
+ *@time 2016-3-3 下午5:07:29
+ */
+public class QuickSort {
+    //一次划分
+    public static int partition(int[] arr, int left, int right) {
+        int pivotKey = arr[left];
+        int pivotPointer = left;
+
+        while(left < right) {
+            while(left < right && arr[right] >= pivotKey)
+                right --;
+            while(left < right && arr[left] <= pivotKey)
+                left ++;
+            swap(arr, left, right); //把大的交换到右边,把小的交换到左边。
+        }
+        swap(arr, pivotPointer, left); //最后把pivot交换到中间
+        return left;
+    }
+
+    public static void quickSort(int[] arr, int left, int right) {
+        if(left >= right)
+            return ;
+        int pivotPos = partition(arr, left, right);
+        quickSort(arr, left, pivotPos-1);
+        quickSort(arr, pivotPos+1, right);
+    }
+
+    public static void sort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+        quickSort(arr, 0, arr.length-1);
+    }
+
+    public static void swap(int[] arr, int left, int right) {
+        int temp = arr[left];
+        arr[left] = arr[right];
+        arr[right] = temp;
+    }
+
+}
+
+

其实上面的代码还可以再优化,上面代码中基准数已经在pivotKey中保存了,所以不需要每次交换都设置一个temp变量,在交换左右指针的时候只需要先后覆盖就可以了。这样既能减少空间的使用还能降低赋值运算的次数。优化代码如下:

+
+
/**
+ *@Description:<p>实现快速排序算法</p>
+ *@author 王旭
+ *@time 2016-3-3 下午5:07:29
+ */
+public class QuickSort {
+
+    /**
+     * 划分
+     * @param arr
+     * @param left
+     * @param right
+     * @return
+     */
+    public static int partition(int[] arr, int left, int right) {
+        int pivotKey = arr[left];
+
+        while(left < right) {
+            while(left < right && arr[right] >= pivotKey)
+                right --;
+            arr[left] = arr[right]; //把小的移动到左边
+            while(left < right && arr[left] <= pivotKey)
+                left ++;
+            arr[right] = arr[left]; //把大的移动到右边
+        }
+        arr[left] = pivotKey; //最后把pivot赋值到中间
+        return left;
+    }
+
+    /**
+     * 递归划分子序列
+     * @param arr
+     * @param left
+     * @param right
+     */
+    public static void quickSort(int[] arr, int left, int right) {
+        if(left >= right)
+            return ;
+        int pivotPos = partition(arr, left, right);
+        quickSort(arr, left, pivotPos-1);
+        quickSort(arr, pivotPos+1, right);
+    }
+
+    public static void sort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+        quickSort(arr, 0, arr.length-1);
+    }
+
+}
+
+

总结快速排序的思想:冒泡+二分+递归分治,慢慢体会。。。

+

堆排序

+

堆排序是借助堆来实现的选择排序,思想同简单的选择排序,以下以大顶堆为例。注意:如果想升序排序就使用大顶堆,反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。

+

首先,实现堆排序需要解决两个问题:

+

1. 如何由一个无序序列键成一个堆?

+

2. 如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?

+

第一个问题,可以直接使用线性数组来表示一个堆,由初始的无序序列建成一个堆就需要自底向上从第一个非叶元素开始挨个调整成一个堆。

+

第二个问题,怎么调整成堆?首先是将堆顶元素和最后一个元素交换。然后比较当前堆顶元素的左右孩子节点,因为除了当前的堆顶元素,左右孩子堆均满足条件,这时需要选择当前堆顶元素与左右孩子节点的较大者(大顶堆)交换,直至叶子节点。我们称这个自堆顶自叶子的调整成为筛选。

+

从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树,则最后一个非终端节点是n/2取底个元素,由此筛选即可。举个栗子:

+

49,38,65,97,76,13,27,49序列的堆排序建初始堆和调整的过程如下:

+

+

+

实现代码:

+
+
/**
+ *@Description:<p>堆排序算法的实现,以大顶堆为例。</p>
+ *@author 王旭
+ *@time 2016-3-4 上午9:26:02
+ */
+public class HeapSort {
+
+    /**
+     * 堆筛选,除了start之外,start~end均满足大顶堆的定义。
+     * 调整之后start~end称为一个大顶堆。
+     * @param arr 待调整数组
+     * @param start 起始指针
+     * @param end 结束指针
+     */
+    public static void heapAdjust(int[] arr, int start, int end) {
+        int temp = arr[start];
+
+        for(int i=2*start+1; i<=end; i*=2) {
+            //左右孩子的节点分别为2*i+1,2*i+2
+
+            //选择出左右孩子较小的下标
+            if(i < end && arr[i] < arr[i+1]) {
+                i ++; 
+            }
+            if(temp >= arr[i]) {
+                break; //已经为大顶堆,=保持稳定性。
+            }
+            arr[start] = arr[i]; //将子节点上移
+            start = i; //下一轮筛选
+        }
+
+        arr[start] = temp; //插入正确的位置
+    }
+
+    public static void heapSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+
+        //建立大顶堆
+        for(int i=arr.length/2; i>=0; i--) {
+            heapAdjust(arr, i, arr.length-1);
+        }
+
+        for(int i=arr.length-1; i>=0; i--) {
+            swap(arr, 0, i);
+            heapAdjust(arr, 0, i-1);
+        }
+
+    }
+
+    public static void swap(int[] arr, int i, int j) {
+        int temp = arr[i];
+        arr[i] = arr[j];
+        arr[j] = temp;
+    }
+
+}
+
+

希尔排序

+

希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。简单的插入排序中,如果待排序列是正序时,时间复杂度是O(n),如果序列是基本有序的,使用直接插入排序效率就非常高。希尔排序就利用了这个特点。基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。

+

举个栗子:

+

+

从上述排序过程可见,希尔排序的特点是,子序列的构成不是简单的逐段分割,而是将某个相隔某个增量的记录组成一个子序列。如上面的例子,第一堂排序时的增量为5,第二趟排序的增量为3。由于前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较,因此关键字较小的记录就不是一步一步地向前挪动,而是跳跃式地往前移,从而使得进行最后一趟排序时,整个序列已经做到基本有序,只要作记录的少量比较和移动即可。因此希尔排序的效率要比直接插入排序高。

+

希尔排序的分析是复杂的,时间复杂度是所取增量的函数,这涉及一些数学上的难题。但是在大量实验的基础上推出当n在某个范围内时,时间复杂度可以达到O(n^1.3)。

+

实现代码:

+
+
/**
+ *@Description:<p>希尔排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-3 下午10:53:55
+ */
+public class ShellSort {
+
+    /**
+     * 希尔排序的一趟插入
+     * @param arr 待排数组
+     * @param d 增量
+     */
+    public static void shellInsert(int[] arr, int d) {
+        for(int i=d; i<arr.length; i++) {
+            int j = i - d;
+            int temp = arr[i];    //记录要插入的数据  
+            while (j>=0 && arr[j]>temp) {  //从后向前,找到比其小的数的位置   
+                arr[j+d] = arr[j];    //向后挪动  
+                j -= d;  
+            }  
+
+            if (j != i - d)    //存在比其小的数 
+                arr[j+d] = temp;
+
+        }
+    }
+
+    public static void shellSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+        int d = arr.length / 2;
+        while(d >= 1) {
+            shellInsert(arr, d);
+            d /= 2;
+        }
+    }
+
+}
+
+

归并排序

+

归并排序是另一种不同的排序方法,因为归并排序使用了递归分治的思想,所以理解起来比较容易。其基本思想是,先递归划分子问题,然后合并结果。把待排序列看成由两个有序的子序列,然后合并两个子序列,然后把子序列看成由两个有序序列。。。。。倒着来看,其实就是先两两合并,然后四四合并。。。最终形成有序序列。空间复杂度为O(n),时间复杂度为O(nlogn)。

+

举个栗子:

+

+

实现代码:

+
+
/**
+ *@Description:<p>归并排序算法的实现</p>
+ *@author 王旭
+ *@time 2016-3-4 上午8:14:20
+ */
+public class MergeSort {
+
+    public static void mergeSort(int[] arr) {
+        mSort(arr, 0, arr.length-1);
+    }
+
+    /**
+     * 递归分治
+     * @param arr 待排数组
+     * @param left 左指针
+     * @param right 右指针
+     */
+    public static void mSort(int[] arr, int left, int right) {
+        if(left >= right)
+            return ;
+        int mid = (left + right) / 2;
+
+        mSort(arr, left, mid); //递归排序左边
+        mSort(arr, mid+1, right); //递归排序右边
+        merge(arr, left, mid, right); //合并
+    }
+
+    /**
+     * 合并两个有序数组
+     * @param arr 待合并数组
+     * @param left 左指针
+     * @param mid 中间指针
+     * @param right 右指针
+     */
+    public static void merge(int[] arr, int left, int mid, int right) {
+        //[left, mid] [mid+1, right]
+        int[] temp = new int[right - left + 1]; //中间数组
+
+        int i = left;
+        int j = mid + 1;
+        int k = 0;
+        while(i <= mid && j <= right) {
+            if(arr[i] <= arr[j]) {
+                temp[k++] = arr[i++];
+            }
+            else {
+                temp[k++] = arr[j++];
+            }
+        }
+
+        while(i <= mid) {
+            temp[k++] = arr[i++];
+        }
+
+        while(j <= right) {
+            temp[k++] = arr[j++];
+        }
+
+        for(int p=0; p<temp.length; p++) {
+            arr[left + p] = temp[p];
+        }
+
+    }
+}
+
+

计数排序

+

如果在面试中有面试官要求你写一个O(n)时间复杂度的排序算法,你千万不要立刻说:这不可能!虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序,只不过有前提条件,就是待排序的数要满足一定的范围的整数,而且计数排序需要比较多的辅助空间。其基本思想是,用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。

+

实现代码:

+
+
/**
+ *@Description:<p>计数排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-4 下午4:52:02
+ */
+public class CountSort {
+
+    public static void countSort(int[] arr) {
+        if(arr == null || arr.length == 0)
+            return ;
+
+        int max = max(arr);
+
+        int[] count = new int[max+1];
+        Arrays.fill(count, 0);
+
+        for(int i=0; i<arr.length; i++) {
+            count[arr[i]] ++;
+        }
+
+        int k = 0;
+        for(int i=0; i<=max; i++) {
+            for(int j=0; j<count[i]; j++) {
+                arr[k++] = i;
+            }
+        }
+
+    }
+
+    public static int max(int[] arr) {
+        int max = Integer.MIN_VALUE;
+        for(int ele : arr) {
+            if(ele > max)
+                max = ele;
+        }
+
+        return max;
+    }
+
+}
+
+

桶排序

+

桶排序算是计数排序的一种改进和推广,但是网上有许多资料把计数排序和桶排序混为一谈。其实桶排序要比计数排序复杂许多。

+

对桶排序的分析和解释借鉴这位兄弟的文章(有改动):http://hxraid.iteye.com/blog/647759

+

桶排序的基本思想:

+

假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]….B[M]中的全部内容即是一个有序序列。bindex=f(key) 其中,bindex 为桶数组B的下标(即第bindex个桶), k为待排序列的关键字。桶排序之所以能够高效,其关键在于这个映射函数,它必须做到:如果关键字k1<k2,那么f(k1)<=f(k2)。也就是说B(i)中的最小数据都要大于B(i-1)中最大数据。很显然,映射函数的确定与数据本身的特点有很大的关系。

+

举个栗子:

+

+

假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序后得到如图所示。只要顺序输出每个B[i]中的数据就可以得到有序序列了。

+

桶排序分析:

+

桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,希尔排序中的子序列,归并排序中的子问题,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。

+

对N个关键字进行桶排序的时间复杂度分为两个部分:

+

(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。

+

(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。

+

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:

+

(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。

+

(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

+

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:

+

O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)

+

当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。

+

总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

+

实现代码:

+
+
/**
+ *@Description:<p>桶排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-4 下午7:39:31
+ */
+public class BucketSort {
+
+    public static void bucketSort(int[] arr) {
+        if(arr == null && arr.length == 0)
+            return ;
+
+        int bucketNums = 10; //这里默认为10,规定待排数[0,100)
+        List<List<Integer>> buckets = new ArrayList<List<Integer>>(); //桶的索引
+
+        for(int i=0; i<10; i++) {
+            buckets.add(new LinkedList<Integer>()); //用链表比较合适
+        }
+
+        //划分桶
+        for(int i=0; i<arr.length; i++) {
+            buckets.get(f(arr[i])).add(arr[i]);
+        }
+
+        //对每个桶进行排序
+        for(int i=0; i<buckets.size(); i++) {
+            if(!buckets.get(i).isEmpty()) {
+                Collections.sort(buckets.get(i)); //对每个桶进行快排
+            }
+        }
+
+        //还原排好序的数组
+        int k = 0;
+        for(List<Integer> bucket : buckets) {
+            for(int ele : bucket) {
+                arr[k++] = ele;
+            }
+        }
+    }
+
+    /**
+     * 映射函数
+     * @param x
+     * @return
+     */
+    public static int f(int x) {
+        return x / 10;
+    }
+
+}
+
+

基数排序

+

基数排序又是一种和前面排序方式不同的排序方式,基数排序不需要进行记录关键字之间的比较。基数排序是一种借助多关键字排序思想对单逻辑关键字进行排序的方法。所谓的多关键字排序就是有多个优先级不同的关键字。比如说成绩的排序,如果两个人总分相同,则语文高的排在前面,语文成绩也相同则数学高的排在前面。。。如果对数字进行排序,那么个位、十位、百位就是不同优先级的关键字,如果要进行升序排序,那么个位、十位、百位优先级一次增加。基数排序是通过多次的收分配和收集来实现的,关键字优先级低的先进行分配和收集。

+

举个栗子:

+

+

+

实现代码:

+
+
/**
+ *@Description:<p>基数排序算法实现</p>
+ *@author 王旭
+ *@time 2016-3-4 下午8:29:52
+ */
+public class RadixSort {
+
+    public static void radixSort(int[] arr) {
+        if(arr == null && arr.length == 0)
+            return ;
+
+        int maxBit = getMaxBit(arr);
+
+        for(int i=1; i<=maxBit; i++) {
+
+            List<List<Integer>> buf = distribute(arr, i); //分配
+            collecte(arr, buf); //收集
+        }
+
+    }
+
+    /**
+     * 分配
+     * @param arr 待分配数组
+     * @param iBit 要分配第几位
+     * @return
+     */
+    public static List<List<Integer>> distribute(int[] arr, int iBit) {
+        List<List<Integer>> buf = new ArrayList<List<Integer>>();
+        for(int j=0; j<10; j++) {
+            buf.add(new LinkedList<Integer>());
+        }
+        for(int i=0; i<arr.length; i++) {
+            buf.get(getNBit(arr[i], iBit)).add(arr[i]);
+        }
+        return buf;
+    }
+
+    /**
+     * 收集
+     * @param arr 把分配的数据收集到arr中
+     * @param buf 
+     */
+    public static void collecte(int[] arr, List<List<Integer>> buf) {
+        int k = 0;
+        for(List<Integer> bucket : buf) {
+            for(int ele : bucket) {
+                arr[k++] = ele;
+            }
+        }
+
+    }
+
+    /**
+     * 获取最大位数
+     * @param x
+     * @return
+     */
+    public static int getMaxBit(int[] arr) {
+        int max = Integer.MIN_VALUE;
+        for(int ele : arr) {
+            int len = (ele+"").length();
+            if(len > max)
+                max = len;
+        }
+        return max;
+    }
+
+    /**
+     * 获取x的第n位,如果没有则为0.
+     * @param x
+     * @param n
+     * @return
+     */
+    public static int getNBit(int x, int n) {
+
+        String sx = x + "";
+        if(sx.length() < n)
+            return 0;
+        else
+            return sx.charAt(sx.length()-n) - '0';
+    }
+
+}
+
+

总结

+

在前面的介绍和分析中我们提到了冒泡排序、选择排序、插入排序三种简单的排序及其变种快速排序、堆排序、希尔排序三种比较高效的排序。后面我们又分析了基于分治递归思想的归并排序还有计数排序、桶排序、基数排序三种线性排序。我们可以知道排序算法要么简单有效,要么是利用简单排序的特点加以改进,要么是以空间换取时间在特定情况下的高效排序。但是这些排序方法都不是固定不变的,需要结合具体的需求和场景来选择甚至组合使用。才能达到高效稳定的目的。没有最好的排序,只有最适合的排序。

+

下面就总结一下排序算法的各自的使用场景和适用场合。

+

+

1. 从平均时间来看,快速排序是效率最高的,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后者相比较的结果是,在n较大时归并排序使用时间较少,但使用辅助空间较多。

+

2. 上面说的简单排序包括除希尔排序之外的所有冒泡排序、插入排序、简单选择排序。其中直接插入排序最简单,但序列基本有序或者n较小时,直接插入排序是好的方法,因此常将它和其他的排序方法,如快速排序、归并排序等结合在一起使用。

+

3. 基数排序的时间复杂度也可以写成O(d*n)。因此它最使用于n值很大而关键字较小的的序列。若关键字也很大,而序列中大多数记录的最高关键字均不同,则亦可先按最高关键字不同,将序列分成若干小的子序列,而后进行直接插入排序。

+

4. 从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为O(n^2)的简单排序也是稳定的。但是快速排序、堆排序、希尔排序等时间性能较好的排序方法都是不稳定的。稳定性需要根据具体需求选择。

+

5. 上面的算法实现大多数是使用线性存储结构,像插入排序这种算法用链表实现更好,省去了移动元素的时间。具体的存储结构在具体的实现版本中也是不同的。

+

附:基于比较排序算法时间下限为O(nlogn)的证明:

+

基于比较排序下限的证明是通过决策树证明的,决策树的高度Ω(nlgn),这样就得出了比较排序的下限。

+

+

首先要引入决策树。 首先决策树是一颗二叉树,每个节点表示元素之间一组可能的排序,它予以京进行的比较相一致,比较的结果是树的边。 先来说明一些二叉树的性质,令T是深度为d的二叉树,则T最多有2^片树叶。 具有L片树叶的二叉树的深度至少是logL。 所以,对n个元素排序的决策树必然有n!片树叶(因为n个数有n!种不同的大小关系),所以决策树的深度至少是log(n!),即至少需要log(n!)次比较。 而 log(n!)=logn+log(n-1)+log(n-2)+…+log2+log1 >=logn+log(n-1)+log(n-2)+…+log(n/2) >=(n/2)log(n/2) >=(n/2)logn-n/2 =O(nlogn) 所以只用到比较的排序算法最低时间复杂度是O(nlogn)。

+

参考资料:

+
    +
  • 《数据结构》 严蔚敏 吴伟民 编著
  • +
  • 桶排序分析:http://hxraid.iteye.com/blog/647759
  • +
  • 部分排序算法分析与介绍:http://www.cnblogs.com/weixliu/archive/2012/12/23/2829671.html
  • +
+ + + + +
+ + + +