Android性能优化—MAT比Menmery Monitor更强大

Android性能优化第(三)篇—MAT比Menmery Monitor更强大

 

%title插图%num

在Android性能优化第(一)篇—基本概念中讲了JAVA的四大引用,讲了一下GCRoot,第二篇Memory Monitor检测内存泄露仅仅说了Menmery Monitor的使用,这篇博客谈一下MAT来寻找内存泄露,相对来说,Memory Monitor没有MAT强大,但是在开始介绍MAT之前,上两篇没有说清楚的问题先说一下。

  • GC回收对可回收对象的判定
    什么样的对象是可以被回收的?
    当然是GC发现通过任何referencechain(引用链)无法访问某个对象的时候,该对象即被回收。名词GC Roots正是分析这一过程的起点,例如JVM自己确保了对象的可到达性(那么JVM就是GC Roots),所以GCRoots就是这样在内存中保持对象可到达性的,一旦不可到达,即被回收。通常GC Roots是一个在current thread(当前线程)的call stack(调用栈)上的对象(例如方法参数和局部变量),或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。所以GC Roots是分析对象为何还存活于内存中的利器。
%title插图%num

GC Root Tracing算法,对于可回收对象的判定

上面这段话,也写出了检测内存泄露的基本思想以”GC Roots”的对象作为起始点向下搜索,搜索形成的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达的),则该对象被判定为可以被回收的对象,反之不能被回收。也写出了内存泄露的原因对象无用了,但仍然可达(未释放),垃圾回收器无法回收。

好啦,下面开始介绍MAT,MAT工具全称为Memory Analyzer Tool,(MAT下载:http://eclipse.org/mat/downloads.php)是一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件。但是该文件不能直接被MAT使用,需要进行一步转化,可以使用hprof-conv命令来转化,但是AndroidStudio可以直接转化。

%title插图%num

导出标准的hprof文件

然後用MAT打开我们导出的文件,我导出了两个文件,test1.hprof和test2.hprof,其中test1.hprof是内存未泄露时的快照,test2.hprof是内存已经泄露的快照。我们用MAT的Histogram(直方图)和Dominator Tree (支配树)来分析内存情况。Histogram可以列出内存中每个对象的名字、数量以及大小。Dominator Tree会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。

%title插图%num

MAT工具主界面
一、 Histogram(直方图)

可列出每一个类的实例数。支持正则表达式查找,也可以计算出该类所有对象的retained size。默认是通过class(group by class)分类展示的。

%title插图%num

直方图.png

这就是test2.hprof的直方图。现在要说两个名词解释。Shallow Heap/Retained Heap

  • Shallow Heap
    Shallow size就是对象本身占用内存的大小,不包含其引用的对象内存,实际分析中作用不大。在堆上,看起来是一堆原生的byte[], char[], int[],对象本身的内存都很小。所以我们可以看到以Shallow Heap进行排序的Histogram图中,排在*位第二位的是byte,char
  • Retained Heap
    Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。RetainedHeap可以更精确的反映一个对象实际占用的大小(因为如果该对象释放,retained heap都可以被释放)。

Histogram中是可以显示对象的数量的,那么比如说我们现在怀疑MainActivity中有可能存在内存泄漏,就可以在*行的正则表达式框中搜索“MainActivity”,如下所示:

%title插图%num

可以看到MainActivity的数量是2,这不正常,解决这个,就需要查看MainActivity被谁引用了,不能被释放。我们右键选择exclude all phantom/weak/soft etc.references, 意思是查看排除虚引用/弱引用/软引用等的引用链 (这些引用*终都能够被GC干掉,所以排除)

%title插图%num

还有其他菜单供选择

  • List objects with (以Dominator Tree的方式查看)
  • incoming references 引用到该对象的对象
  • outcoming references 被该对象引用的对象
  • Show objects by class (以class的方式查看)
  • incoming references 引用到该对象的对象
  • outcoming references 被该对象引用的对象

当你按上面操作之后,凶手就出现了,原来是UserManger的实例。

%title插图%num

还有可以通过包名来查看Histogram。

%title插图%num

还有更强大的,通过OQL语句查询,有点像写SQL语句。

%title插图%num

是不是分分钟查出MainActivity有两个对象。哈哈!
比如:查找size=0并且未使用过的ArrayList

select * from java.util.ArrayList where size=0 and modCount=0

这个地方可以多研究研究。

二、Dominator Tree(支配树)

Dominator Tree是对象之间dominator关系树。如果从GC Root到达Y的的所有path都经过X,那么我们称X dominates Y,或者X是Y的Dominator Dominator Tree由系统中复杂的对象图计算而来。从MAT的dominator tree中可以看到占用内存*大的对象以及每个对象的dominator。 我们也可以右键选择Immediate Dominator”来查看某个对象的dominator。它可以将所有对象按照Heap大小排序显示, 这样大内存对象就是排在前几名的,我们可以搜索大内存对象通向GC Roots的路径,因为内存占用越高的对象越值得怀疑,使用方法跟Histogram(直方图)差不多,在这里我就不做过多的介绍了。

三、内存快照对比

我们可以将test1.hprof的直方图与test2.hprof的直方图对比来看,其中Object#0是test1.hprof的,Object#1是test2.hprof的,通过比较看哪一些对象的大小相差过大。

%title插图%num

内存快照对比

Android Studio +MAT 分析内存泄漏

对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的。

首先什么是内存泄漏?

内存泄漏就是一些已经不使用的对象还存在于内存之中且垃圾回收机制无法回收它们,导致它们常驻内存,会使内存消耗越来越大,*终导致程序性能变差。
其中在Android虚拟机中采用的是根节点搜索算法枚举根节点判断是否是垃圾,虚拟机会从GC Roots开始遍历,如果一个节点找不到一条到达GC Roots的路线,也就是没和GC Roots 相连,那么就证明该引用无效,可以被回收,内存泄漏就是存在一些不好的调用导致一些无用对象和GC Roots相连,无法被回收。

既然知道了什么是内存泄漏,自然就知道如何去避免了,就是我们在写代码的时候尽量注意产生对无用对象长时间的引用,说起来简单,但是需要足够的经验才能达到,所以内存泄漏还是比较容易出现的,既然不容易完全避免,那么我们就要能发现程序中出现的内存泄漏并修复它,
下面我就说说如何发现内存泄漏的吧。

查找内存泄漏:

比如说下面这个代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String string = new String();

    }

    public void click(View view){
        Intent intent = new Intent();
        intent.setClass(getApplicationContext(),SecondActivity.class);
        startActivity(intent);
    }
}

 

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(8000000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();
    }
}

 

每次跳转到这个Activity中时都会调用一个线程,然后这个线程会执行runnable的run方法 由于Runnable是一个匿名内部对象 所以握有SecondActivity的引用,因此
很简单的两个Activity,可由MainActivity跳转到SecondActivity中,
下面我们从MainActivity跳到SecondActivity 然后从SecondActivity返回MainActivity,连续这样5次 ,*终返回MainActivity,按照常理来说,我们从SecondActivity返回MainActivity之后 SecondActivity就该被销毁回收,可实际可能并不是这样。

这时候要判断发没发生内存溢出就要使用工具了!下面有两种方式

1.利用MAT工具查找

首先打开AS中的Android Device Monitor工具 具体位置如下图:
AS Android Device Monitor位置
打开后会出现如下的界面
ADM界面
先选中你要检测的应用的包名,然后点击下图画圈的地方,会在程序包名后标记一个图标
%title插图%num
接下来要做的就是操作我们的app 来回跳转5次。
之后点击下图的图标 就可导出hprof文件进行分析了
%title插图%num

导出文件如下图所示:
hprof文件
得到了hprof文件 我们就可以利用MAT工具进行分析了,
打开MAT工具
如果没有 可以在下面网址下载
MAT工具下载地址
%title插图%num

界面如下图所示:
%title插图%num

打开我们先前导出的hprof文件 ,不出意外会报下面的错误
%title插图%num

这是因为MAT是用来分析Java程序的hprof文件的 与Android导出的hprof有一定的格式区别,因此我们需要把导出的hprof文件转换一下,sdk中提供给我们转换的工具 hprof-conv.exe 在下图的位置
hprof-conv位置
接下来我们cd到这个路径下执行这个命令转换我们的hprof文件即可,如下图
转换hprof文件
其中 hprof-conv 命令 这样使用
hprof-conv 源文件 输出文件
比如 hprof-conv E:\aaa.hprof E:\output.hprof
就是 把aaa.hprof 转换为output.hprof输出 output.hprof就是我们转换之后的文件,图中 mat2.hprof就是我们转换完的文件。

接下来 我们用MAT工具打开转换之后的mat2.hprof文件即可 ,打开后不报错 如下图所示:
MAT打开hprof文件
之后我们就可以查看当前内存中存在的对象了,由于我们内存泄漏一般发生在Activity中,因此只需要查找Activity即可。
点击下图中标记的QQL图标 输入 select * from instanceof android.app.Activity
类似于 SQL语句 查找 Activity相关的信息 点击 红色叹号执行后 如下图所示:
QQL

接下来 我们就可以看到下面过滤到的Activity信息了
如上图所示, 其中内存中还存在 6个SecondActivity实例,但是我们是想要全部退出的,这表明出现了内存泄漏

其中 有 Shallow size 和 Retained Size两个属性

Shallow Size
对象自身占用的内存大小,不包括它引用的对象。针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。
当然这里面还会包括一些java语言特性的数据存储单元。针对数组类型的对象,它的大小是数组元素对象的大小总和。
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)
不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。

接下来 右击一个SecondActivity
%title插图%num
选择 with all references
打开如下图所示的页面
%title插图%num

查看下图的页面
看到 this0Activitythis0是表示 内部类的意思,也就是一个内部类引用了Activity 而 this$0又被 target引用 target是一个线程,原因找到了,内存泄漏的原因 就是 Activity被 内部类引用 而内部类又被线程使用 因此无法释放,我们转到这个类的代码处查看

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(8000000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();
    }
}

 

确实 在 SecondActivity中 存在Runnable 内部类对象,然后又被线程 使用,而线程要执行8000秒 因此 SecondActivity对象被引用 无法释放,导致了内存溢出。
要解决这种的内存溢出,要及时在Activity退出时结束线程(不过不大好结束。。),或者良好的控制线程执行的时间即可。

这样我们就找出了这个程序中的内存溢出。

2.直接利用Android Studio的 Monitor Memory 查找内存溢出
还是利用上面那个程序,我就简单点说了。

首先 在手机上运行程序,打开AS的 Minotor 界面 查看Memory 图像
%title插图%num

点击 小卡车图标(图中1位置图标) 可以触发一次 GC
%title插图%num
点击 图中2位置图标可以查看hprof文件
%title插图%num

左边是 内存中的对象,在里面找 Activity 看存不存在我们希望已经回收的Activity 如果 出现我们期望已经回收的Activity,单击 就会在右边显示它的总的个数,点击右边的某个,可以显示 它的GC Roots的树关系图 ,查看关系图就可以找出发生内存泄漏的位置(类似于*种方式)

这样就完成了内存泄漏的查找。

其中内存泄漏产生的原因在Android中大致分为以下几种:

1.static变量引起的内存泄漏
因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中 引用了Activity 那么 这个Activity由于被引用,便会随static变量的生命周期一样,一直无法被释放,造成内存泄漏。

解决办法:
在Activity被静态变量引用时,使用 getApplicationContext 因为Application生命周期从程序开始到结束,和static变量的一样。

2.线程造成的内存泄漏
类似于上述例子中的情况,线程执行时间很长,及时Activity跳出还会执行,因为线程或者Runnable是Acticvity内部类,因此握有Activity的实例(因为创建内部类必须依靠外部类),因此造成Activity无法释放。
AsyncTask 有线程池,问题更严重

解决办法:
1.合理安排线程执行的时间,控制线程在Activity结束前结束。
2.将内部类改为静态内部类,并使用弱引用WeakReference来保存Activity实例 因为弱引用 只要GC发现了 就会回收它 ,因此可尽快回收

3.BitMap占用过多内存
bitmap的解析需要占用内存,但是内存只提供8M的空间给BitMap,如果图片过多,并且没有及时 recycle bitmap 那么就会造成内存溢出。

解决办法:
及时recycle 压缩图片之后加载图片

4.资源未被及时关闭造成的内存泄漏
比如一些Cursor 没有及时close 会保存有Activity的引用,导致内存泄漏

解决办法:
在onDestory方法中及时 close即可

5.Handler的使用造成的内存泄漏
由于在Handler的使用中,handler会发送message对象到 MessageQueue中 然后 Looper会轮询MessageQueue 然后取出Message执行,但是如果一个Message长时间没被取出执行,那么由于 Message中有 Handler的引用,而 Handler 一般来说也是内部类对象,Message引用 Handler ,Handler引用 Activity 这样 使得 Activity无法回收。

解决办法:
依旧使用 静态内部类+弱引用的方式 可解决

其中还有一些关于 集合对象没移除,注册的对象没反注册,代码压力的问题也可能产生内存泄漏,但是使用上述的几种解决办法一般都是可以解决的。

使用Eclipse Memory Analyzer进行内存泄漏分析三部曲

一、准备工作
分析较大的dump文件(根据我自己的经验2G以上的dump文件就需要使用以下介绍的方法,不然mat会出现oom)需要调整虚拟机参数
找个64位的系统在MemoryAnalyzer.ini设置-Xmx2g
如果是32位的xp可以使用下面的方法进行尝试:

  • 安装jrockit 6.0的JDK
  • mat使用jrockit的jdk来启动
    Java代码  收藏代码
    1. -vm
    2. D:/Program Files/Java/jrockit-R28.0.0-jre1.6.0_17/bin/jrockit/jvm.dll  
    3. -vmargs
    4. -Xmx1700m

    二、开始使用MAT进行OOM分析
    *步,启动mat ,选择File->Open Heap Dump 选择你的dump文件。下面开始等待,mat解析dump文件需要花一些时间,在解析的同时会在硬盘上写入一些解析结果文件,这样下次打开时速度会快很多。有时候mat在解析过程中可能会出现出错的情况,这个时候可以将那些临时文件删除以后重试*步,如果你的rp够好的话重试也许会解析成功。

    第二步,查看内存泄漏分析报表。mat解析完成以后会出现如下图的提示:
    %title插图%num
    因为我们就是为了查找内存泄漏的问题,所以保持默认选项直接点“Finish”就可以。
    Mat会非常直观的展现内存泄漏的可疑点,类似下面的报表可以直接看到某个线程占用了大量的内存
    %title插图%num
    问题的详细分析信息:
    %title插图%num

    第三步,开始寻找导致内存泄漏的代码点。这时往往需要打开对象依赖关系树形视图,点击如图按钮即可。
    %title插图%num
    这时会看到如下视图
    %title插图%num
    这个视图的左边大区域可以看到对象的依赖关系,选中某个对象以后可以在左边小窗口查看对象的一些属性。如果属性的值是一些内存地址你还可以点击工具栏的搜索按钮来搜索具体的对象信息。在进行具体分析的时候MAT只是起了帮助你进行分析的工具的功能,OOM问题分析没有固定方法和准则。只能发挥你敏锐的洞察力,结合源代码,对内存中的对象进行分析从而找到代码中的BUG.

    使用贴士:
    关于shallow size、retained size
    Shallow size就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。在32位系统上,对象头占用8字节,int占用4字节,不管成员变量(对象或数组)是否引用了其他对象(实例)或者赋值为null它始终占用4字节。故此,对于String对象实例来说,它有三个int成员(3*4=12字节)、一个char[]成员(1*4=4字节)以及一个对象头(8字节),总共3*4 +1*4+8=24字节。根据这一原则,对String a=”rosen jiang”来说,实例a的shallow size也是24字节

    Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。为了更好的理解retained size,不妨看个例子。

    把内存中的对象看成下图中的节点,并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,正解!这就是reference chain的起点。
    %title插图%num%title插图%num
    从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。
    所以对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。obj2的retained size可以通过相同的方式计算。

    如何查看某一个对象占用的内存空间
    1.按以下方式打开新窗口即可
    %title插图%num
    2.输入类名(输入类的全名)
    %title插图%num

 

android 内存泄漏出现的情况

    • 非静态内部类的静态实例
      由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。
    • 类的静态变量持有大数据对象
      静态变量长期维持到大数据对象的引用,阻止垃圾回收。
    • 资源对象未关闭
      资源性对象如Cursor、Stream、Socket,Bitmap,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。
    • 注册对象未反注册
      我们常常写很多的Listener,未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。
    • Handler临时性内存泄露
      Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。

    • 内部线程泄露new Thread(){}.start();Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧 : 我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC Root引用所以不会被回收掉,导致MainActivity也无法被GC回收。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉
    • Context泄露
    • 内部类的创建需要小心,由于内部类会持有外部类对象,不小心就会造成内存泄漏

198. 打家劫舍(JS实现)

198. 打家劫舍(JS实现)
剑指offer
专栏收录该内容
153 篇文章1 订阅
订阅专栏
1 题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的*高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的*高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的*高金额 = 2 + 9 + 1 = 12 。
链接:https://leetcode-cn.com/problems/house-robber
2 思路
这道题思路就是动态规划,状态转移方程 d[i] = Math.max(d[i-2] + nums[i], d[i-1]), d[i]为第i个房屋时*大利润
3代码
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    if (nums.length === 0) return 0;
    if (nums.length === 1) return nums[0];
    let d = [nums[0]];
    for (let i=1; i<nums.length; i++) {
        d[i] = Math.max(d[i-1], i – 2 >= 0 ? d[i-2] + nums[i] : nums[i]);
    }
    return d[nums.length – 1];
};

199. 二叉树的右视图(JS实现)

199. 二叉树的右视图(JS实现)
剑指offer
专栏收录该内容
153 篇文章1 订阅
订阅专栏
1 题目
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例:
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:
1 <—
/
2 3 <—
\
5 4 <—
链接:https://leetcode-cn.com/problems/binary-tree-right-side-view
2 思路
这道题我们可以从对树进行中序遍历,但得先遍历树的右分支,再左分支,在遍历的过程中,记录每个节点的当前深度和当前遍历过程的*大深度,若当前节点的深度大于当前*大深度,说明该节点从右侧可以看见,则将其加入结果中
3代码
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var rightSideView = function(root) {
    let nums = [];
    if (!root) return nums;
    let stack = [];
    let p = root;
    let maxDepth = 0;
    let currentDepth = 0;
    while(p || stack.length > 0) {
        while(p) {      //遍历节点的右分支
            currentDepth++;
            if (currentDepth > maxDepth) {    //推入节点
                maxDepth++;
                nums.push(p.val);
            }
            stack.push([p, currentDepth]);
            p = p.right;
        }
        let node = stack.pop();    //回溯
        p = node[0].left;     //对节点的左分支进行遍历
        currentDepth = node[1];  //当前深度也要回溯
    }
    return nums;
};

200. 岛屿数量(JS实现)

200. 岛屿数量(JS实现)
剑指offer
专栏收录该内容
153 篇文章1 订阅
订阅专栏
1 题目
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
[
[‘1’,‘1’,‘1’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘0’,‘0’]
]
输出: 1
示例 2:
输入:
[
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘1’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘1’,‘1’]
]
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
链接:https://leetcode-cn.com/problems/number-of-islands
2 思路
这道题思路就是图的广度优先遍历,将搜索到的1变为#,然后统计次数就可以了
3代码
/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function(grid) {
    let res = 0;
    let rows = grid.length;
    if (rows === 0) return 0;
    let cols = grid[0].length;
    for (let i=0; i<rows; i++) {
        for (let j=0; j<cols; j++) {
            if (grid[i][j] === ‘1’) {
                res++;
                search(i,j);
            }
        }
    }
    function search(i, j) {
        if (i < 0 || j < 0 || i >= rows || j >= cols || grid[i][j] === ‘0’ || grid[i][j] === ‘#’) return;
        grid[i][j] = ‘#’;
        search(i+1, j);
        search(i-1, j);
        search(i, j+1);
        search(i, j-1);
    }
    return res;
};

201. 数字范围按位与(JS实现)

201. 数字范围按位与(JS实现)
剑指offer
专栏收录该内容
153 篇文章1 订阅
订阅专栏
1 题目
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
示例 1:
输入: [5,7]
输出: 4
示例 2:
输入: [0,1]
输出: 0
链接:https://leetcode-cn.com/problems/bitwise-and-of-numbers-range
2 思路
这道题可以罗列出几个数,观察一下规律,我们可以发现计算结果只与n和m有关,比如101和111计算结果为4,也就是100,其实就是他们相同的*高位的值
3代码
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var rangeBitwiseAnd = function(m, n) {
    if (m === n) return n;
    let res = 0;
    let count = 0;
    let temp = n;
    while(m && n) {
        if ((m & 1) !== (n & 1)) {
            res = count;
        }
        m = m >> 1;
        n = n >> 1;
        count++;
    }
    if (m || n) return 0;
    temp = temp >> (res + 1);
    temp = temp << (res + 1);
    return temp;
};

创建数据库完整代码

完整创建数据库代码:
use master 调用master数据库

go

if exists(select * from sysdatabases where name=’stuDB’) 判断是否已经存在名为stuDB的数据库

dorp database stuDB 如果存在就先删除

create database stuDB 开始创建数据库
on
(
name=’stuDB_data’, 数据库显示名为stuDB_data
filename=’c:/stuDB_data.mdf’, 在物理硬盘上的的文件名为stuDB_data.mdf
size=5mb, 初始大小
maxsize=100mb, 数据库*大容量
filegrowth=15% 数据*小容量每次以*大容量的15%增加
)

log on
(
name=’stuDB_log’, 数据库显示名为stuDB_log
filenam=’stuDB_log.ldf’, 在物理硬盘上的的文件名为stuDB_log.ldf
size=2mb, 初始大小
filegrowth=1mb 数据*小容量每次以1mb增加
)

 

创建数据库以及其属性的sql语句

创建数据库的SQL语句:

create database stuDB
on primary — 默认就属于primary文件组,可省略
(
/*–数据文件的具体描述–*/
name=’stuDB_data’, — 主数据文件的逻辑名称
filename=’D:\stuDB_data.mdf’, — 主数据文件的物理名称
size=5mb, –主数据文件的初始大小
maxsize=100mb, — 主数据文件增长的*大值
filegrowth=15%–主数据文件的增长率
)
log on
(
/*–日志文件的具体描述,各参数含义同上–*/
name=’stuDB_log’,
filename=’D:\stuDB_log.ldf’,
size=2mb,
filegrowth=1mb
)

那么如何删除这个数据库呢,SQL Server将数据库的清单存放在master系统数据库的sysdatabases表中,只需要查看该表是否存在于该数据库中就可以了,语句如下:

use master — 设置当前数据库为master,以便访问sysdatabases表
go
if exists(select * from sysdatabases where name=’stuDB’)
drop database stuDB
go