Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

无域控AlwaysOn实战教学(二)

$
0
0

前言


在之前的文章,无域控AlwaysOn实战教学 (一)我们已经对AlwaysOn的基础知识,并做好了安装部署无域控AlwaysOn的准备工作。今天就介绍详细的如何安装


创建高可用组

1.安装SQL SERVER 2014


2.启用AlwaysOn功能

   


新建可用性组



完整备份

正式搭建之前至少需要做一次完整备份。(为什么要做完整备份呢?这个问题还挺有趣的,有知道的同学可以想一下。




输入可用性组名称


选择加入可用性组的数据库



指定副本

添加需要加入可用性组的副本。我这里是2个节点57 和59.


我这里使用的是SQL Server 2014 ,自动故障转移为2个.2016支持最多为3个自动故障转移.

同步提交个数2014 和2016 都是3个

选择数据同步方式





到这里整个搭建过程就完成了。

故障切换

关闭可用性组的中的服务器,和SQL Server 服务器都能实现自动的故障转移.


注意事项

1.我这里没有创建证书,AlwaysOn的功能还是能正常使用,但是为了安全起见,在生产环境还是加上证书比较好。

2.强烈建议禁用netbios。





作者:z10843087 发表于2017/11/20 10:32:01 原文链接
阅读:26 评论:1 查看评论

Android RecyclerView批量更新notifyItemRangeChanged

$
0
0
Android RecyclerView批量更新notifyItemRangeChanged


附录1的文章,介绍RecyclerView的定点更新,现在介绍RecyclerView的批量更新,RecyclerView的批量更新通过notifyItemRangeChanged实现,notifyItemRangeChanged官方文档:
  /**
         * Notify any registered observers that the <code>itemCount</code> items starting at
         * position <code>positionStart</code> have changed.
         * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>.
         *
         * <p>This is an item change event, not a structural change event. It indicates that
         * any reflection of the data in the given position range is out of date and should
         * be updated. The items in the given range retain the same identity.</p>
         *
         * @param positionStart Position of the first item that has changed
         * @param itemCount Number of items that have changed
         *
         * @see #notifyItemChanged(int)
         */
        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount);
        }



示例代码:
package zhangphil.demo;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Random;


public class MainActivity extends Activity {
    private ArrayList data = new ArrayList();

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

        for (int i = 0; i < 5; i++) {
            data.add(System.currentTimeMillis());
        }

        RecyclerView mRecyclerView = findViewById(R.id.recycler_view);

        LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
        mLayoutManager.setOrientation(LinearLayout.VERTICAL);
        mRecyclerView.setLayoutManager(mLayoutManager);

        final RecyclerView.Adapter mAdapter = new MyAdapter();
        mRecyclerView.setAdapter(mAdapter);

        final Random random = new Random();

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int count = random.nextInt(data.size());
                for (int i = 0; i < count; i++) {
                    data.set(i, System.currentTimeMillis());
                }

                mAdapter.notifyItemRangeChanged(0, count);
            }
        });
    }


    private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
        public MyAdapter() {
            super();
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View view = LayoutInflater.from(getApplicationContext()).inflate(android.R.layout.simple_list_item_2, null);
            ViewHolder holder = new ViewHolder(view);
            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder viewHolder, int i) {
            viewHolder.text1.setText(i + "");
            viewHolder.text2.setText(data.get(i) + "");
        }

        @Override
        public int getItemCount() {
            return data.size();
        }

        public class ViewHolder extends RecyclerView.ViewHolder {
            public TextView text1;
            public TextView text2;

            public ViewHolder(View itemView) {
                super(itemView);
                text1 = itemView.findViewById(android.R.id.text1);
                text1.setTextColor(Color.RED);
                text2 = itemView.findViewById(android.R.id.text2);
                text2.setTextColor(Color.BLUE);
            }
        }
    }
}



附录:
1,《Android RecyclerView更新子项目notifyItemChanged》链接:http://blog.csdn.net/zhangphil/article/details/78565738 

作者:zhangphil 发表于2017/11/20 10:34:04 原文链接
阅读:21 评论:0 查看评论

mysql超时:The last packet successfully received from the server was 172,848,658 milliseconds ago.

$
0
0

今天查询接口报错了。报错内容如下:

The last packet successfully received from the server was 172,848,658 milliseconds ago. The last packet sent successfully to the server was 172,848,673 milliseconds ago. is longer than the server configured value of ‘wait_timeout’. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property ‘autoReconnect=true’ to avoid this problem.

明明上周是好好的,其实以前我出过这个错误,这次决定要记录下来。
查看一下报错的内容大致是:上次服务器成功接收信息包是172,848,658 毫秒以前,上次服务器成功发送数据是172,848,673毫秒以前,已经超出服务器配置的wait_timeout’超时时间。如果想避免这种异常就需要增加wait_timeout’的超时时间。或者在xml中设置autoReconnect=true

第一种方式

在my.ini中mysqld下面增加
wait_timeout=2147483
interactive_timeout=2147483

最大只能是2147483 解析为24.85天。
大家可以根据自己的需求设置。如果连接人数很多,时间可以适当的减少,连接人数少可以适当增加。

第二种方式

有的同学可能没有my.ini 此时可以输入sql命令
这里写图片描述

第三种方式(转)

  • 如果使用的是JDBC,在JDBC URL上添加?autoReconnect=true,如:

jdbc:mysql://10.10.10.10:3306/mydb?autoReconnect=true
- 如果是在Spring中使用DBCP连接池,在定义datasource增加属性validationQuery和testOnBorrow,如:

<bean id="vrsRankDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${countNew.jdbc.url}" />
    <property name="username" value="${countNew.jdbc.user}" />
    <property name="password" value="${countNew.jdbc.pwd}" />
    <property name="validationQuery" value="SELECT 1" />
    <property name="testOnBorrow" value="true"/>
</bean>
  • 如果是在Spring中使用c3p0连接池,则在定义datasource的时候,添加属性testConnectionOnCheckin和testConnectionOnCheckout,如:
<bean name="cacheCloudDB" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${cache.url}"/>
    <property name="user" value="${cache.user}"/>
    <property name="password" value="${cache.password}"/>
    <property name="initialPoolSize" value="10"/>
    <property name="maxPoolSize" value="${cache.maxPoolSize}"/>
    <property name="testConnectionOnCheckin" value="false"/>
    <property name="testConnectionOnCheckout" value="true"/>
    <property name="preferredTestQuery" value="SELECT 1"/>
</bean>
作者:su20145104009 发表于2017/11/20 10:41:14 原文链接
阅读:16 评论:0 查看评论

电脑硬件知识入门之硬盘篇

$
0
0

Hard Disk Drive 简称HDD,即我们俗称的硬盘,它是电脑主要的存储媒介之一,由一个或者多个铝制或者玻璃制的碟片组成。硬盘分为固态硬盘(SSD)、机械硬盘(HDD)和混合硬盘三种,其中固态硬盘是目前读取速度最快的。


机械硬盘(HDD):最传统的硬盘

机械硬盘英文简称为HDD,是早年的大多数电脑多用的是机械硬盘。

机械硬盘内部构造相当复杂,内部不光有我们熟知的磁盘、磁头和马达,还有主轴、传动手臂、传动轴和反力回拉弹簧装置等,当然还有电路板和铝制防护壳。

机械键盘特点:大容量、价格相对便宜,如今热度虽然不如固态硬盘,但大存储依然少不了它。

固态硬盘(SSD):近几年兴起的硬盘

虽然固态硬盘(SSD)是近几年流行起来的,但其实固态硬盘的起源还是比较早的,但是由于技术和成本的制约,直到近几年固态硬盘才在普通消费市场慢慢成熟起来。

固态硬盘的结构相比机械硬盘就要简单许多了,一块电路板上集成了固态硬盘所有的重要元器件,闪存颗粒、主控芯片和缓存等。并且现在有各种接口来适应固态硬盘的速度,比如M.2接口、SATA-3接口、MSATA接口、PCI-E接口、NGFF接口、CFast接口和SFF-8639接口等,目前主流的基本是M.2和SATA3两种。

固态硬盘特点:速度快,是机械硬盘的几倍,性能无瓶颈,并且静音、不怕震动等,缺点是容量小,并且价格贵。

混合硬盘(SSHD):鲜为人知

混合硬盘是一块基于传统机械硬盘诞生出来的新硬盘,除了机械硬盘必备的碟片、马达、磁头等等,还内置了NAND闪存颗粒,这颗颗粒将用户经常访问的数据进行储存,可以达到如SSD(就是固态硬盘)效果的读取性能 。

混合硬盘可以看作是机械硬盘和固态硬盘叠加的产品,主要解决了机械硬盘速度慢和固态硬盘容量小的问题,属于两者结合的产品。目前生产混合硬盘的厂商并不多,希捷和东芝最具代表力。


HDD、SSD、SSHD区别与优缺点对比

下来我们来分析一下机械硬盘、固态硬盘和混合硬盘的优缺点。

一、机械硬盘

优点:容量大、价位低。

缺点:在这三种硬盘中,机械硬盘的读取速度是最慢的。而且由于自身结构的特点,易损性较高,对震动敏感。现在的硬盘在工作时磁头与磁盘盘片之间的距离小于0.01微米,可想而知在磁片高速转动时(电脑百事网PC841.Com),硬盘受到震动后盘片与磁头之间难免会有真正的亲密接触,从而损坏磁盘盘片。

二、固态硬盘

优点:读写速度在三者之中是最快的。而且由于固态硬盘的结构相比机械硬盘要简单许多,只是一块集成度较高的电路板,因此有非常不错的抗震性能。

缺点:容量小、价格昂贵。目前市场上的固态硬盘鱼龙混杂,同样容量的固态硬盘,其价格却相差悬殊。

三、混合硬盘

优点:混合硬盘结合了机械硬盘和固态硬盘的优点,在性能上也是介于二者之间。价格来说也比较合理,目前一款1T容量的混合硬盘,售价约为600元。

缺点:结合了两者优点的混合硬盘也继承两者的缺点,比如抗震性能差、单颗闪存颗粒导致擦写次数收到了严重制约等,好在这颗闪存只记载常用文件,并且只是备份,而不是将文件完全的存储到闪存中。

以上就是机械硬盘、固态硬盘和混合硬盘的区别,大家可以根据各自的需要,各取所需。另外值得一提的是,Intel今年新推出了傲腾内存,可以为大容量机械硬盘加速,一块傲腾内存+1TB机械硬盘,可以让大容量机械硬盘的速度达到接近70%的固态硬盘速度,感兴趣的朋友,也不妨关注下。

自己装电脑选机械硬盘还是固态硬盘?

通过上面各种硬盘的优缺点介绍,我想大家应该有个大概了解,所以装机硬盘是选态硬盘还是机械硬盘好,主要看预算和需求,目前固态硬盘已经成为主流装机标配,值得优秀考虑。如果预算有限,对硬盘容量要求较高,则可以考虑机械硬盘,最好的方案是同时选用固态+机械双硬盘。


安装固态硬盘对电脑性能提升大吗?


电脑加装到好的固态硬盘对性能的提升还是有很大帮助的,一来可以提高开机关机的速度,加快数据的读取,以及加快软件的加载速度。它比起普通的机械硬盘,几乎为零噪音尤其如果是笔记本电脑的话,由于他低功耗的特性,它可以使笔记本待机时间延长36%,这为外出使用留出了足够多的时间。但是固态硬盘并不能直接提升电脑性能,比如加装了固态硬盘,电脑的游戏性能并不能得到提升,它只是提升读取速度,能起到一些辅助作用。固态硬盘和普通机械硬盘相比较,最大的优势就是存取速度更快!而电脑的性能不单单指数据的存取速度,更取决于CPU,以及CPU,主板还有显卡三者的搭配。所以说加装固态硬盘只是提升读取速度,具备更快的开关机速度、流畅度更高,另外游戏载入速度更快,但对于电脑的整体性能影响不大,但是有很大帮助。




作者:shaoyezhangliwei 发表于2017/11/20 10:50:26 原文链接
阅读:11 评论:0 查看评论

第26章 线程

$
0
0

#include <pthread.h>

//线程
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void*), void *arg);
int pthread_join(pthread_t *tid, void **status);
pthread_t pthread_self(void);
int pthread_detach(pthread_t tid);
void pthread_exit(void *status);

//线程特定数据
int pthread_once(pthread_once_t *onceptr/*PTHREAD_ONCE_INIT*/, void (*init)(void));
int pthread_key_create(pthread_key_t *keyptr, void (*destructor)(void *value));
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);

//互斥锁
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);

//条件变量
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
int pthread_cond_broadcast(pthread_cond_t *cptr);
int pthread_cond_timedwait(pthread_cond_t *cptr, pthread_mutex_t *mptr, const struct timespec *abstime);//gettimeofday clock_gettime


作者:gongluck93 发表于2017/11/20 11:05:17 原文链接
阅读:0 评论:0 查看评论

Android 源码分析 —— 从 Toast 出发

$
0
0

本系列文章在 https://github.com/mzlogin/rtfsc-android 持续更新中,欢迎有兴趣的童鞋们关注。
本篇原始链接:https://github.com/mzlogin/rtfsc-android/blob/master/0x003-start-from-toast.md

(图 from Android Developers)

Toast 是 Android 开发里较常用的一个类了,有时候用它给用户弹提示信息和界面反馈,有时候用它来作为辅助调试的手段。用得多了,自然想对其表层之下的运行机制有所了解,所以在此将它选为我的第一个 RTFSC Roots。

本篇采用的记录方式是先对它有个整体的了解,然后提出一些问题,再通过阅读源码,对问题进行一一解读而后得出答案。

本文使用的工具与源码为:Chrome、插件 insight.io、GitHub 项目 aosp-mirror/platform_frameworks_base

目录

Toast 印象

首先我们从 Toast 类的 官方文档API 指南 中可以得出它具备如下特性:

  1. Toast 不是 View,它用于帮助创建并展示包含一条小消息的 View;

  2. 它的设计理念是尽量不惹眼,但又能展示想让用户看到的信息;

  3. 被展示时,浮在应用界面之上;

  4. 永远不会获取到焦点;

  5. 大小取决于消息的长度;

  6. 超时后会自动消失;

  7. 可以自定义显示在屏幕上的位置(默认左右居中显示在靠近屏幕底部的位置);

  8. 可以使用自定义布局,也只有在自定义布局的时候才需要直接调用 Toast 的构造方法,其它时候都是使用 makeText 方法来创建 Toast;

  9. Toast 弹出后当前 Activity 会保持可见性和可交互性;

  10. 使用 cancel 方法可以立即将已显示的 Toast 关闭,让未显示的 Toast 不再显示;

  11. Toast 也算是一个「通知」,如果弹出状态消息后期望得到用户响应,应该使用 Notification。

不知道你看到这个列表,是否学到了新知识或者明确了以前不确定的东西,反正我在整理列表的时候是有的。

提出问题

根据以上特性,再结合平时对 Toast 的使用,提出如下问题来继续本次源码分析之旅(大致由易到难排列,后文用 小 demo 或者源码分析来解答):

  1. Toast 的超时时间具体是多少?

  2. 能不能弹一个时间超长的 Toast?

  3. Toast 能不能在非 UI 线程调用?

  4. 应用在后台时能不能 Toast?

  5. Toast 数量有没有限制?

  6. Toast.makeText(…).show() 具体都做了些什么?

解答问题

Toast 的超时时间

用这样的一个问题开始「Android 源码分析」,真的好怕被打死……大部分人都会嗤之以鼻:Are you kidding me? So easy. 各位大佬们稍安勿躁,阅读大型源码不是个容易的活,让我们从最简单的开始,一点一点建立自信,将这项伟大的事业进行下去。

面对这个问题,我的第一反应是去查 Toast.LENGTH_LONGToast.LENGTH_SHORT 的值,毕竟平时都是用这两个值来控制显示长/短 Toast 的。

文件 platform_frameworks_base/core/java/android/widget/Toast.java 中能看到它们俩的定义是这样的:

/**
 * Show the view or text notification for a short period of time.  This time
 * could be user-definable.  This is the default.
 * @see #setDuration
 */
public static final int LENGTH_SHORT = 0;

/**
 * Show the view or text notification for a long period of time.  This time
 * could be user-definable.
 * @see #setDuration
 */
public static final int LENGTH_LONG = 1;

啊哦~原来它们只是两个 flag,并非确切的时间值。

既然是 flag,那自然就会有根据不同的 flag 来设置不同的具体值的地方,于是使用 insight.io 点击 LENGTH_SHORT 的定义搜索一波 Toast.LENGTH_SHORT 的引用,在 aosp-mirror/platform_frameworks_base 里一共有 50 处引用,但都是调用 Toast.makeText(...) 时出现的。

继续搜索 Toast.LENGTH_LONG 的引用,在 aosp-mirror/platform_frameworks_base 中共出现 42 次,其中有两处长得像是我们想找的:

第一处,文件 platform_frameworks_base/core/java/android/widget/Toast.java

private static class TN extends ITransientNotification.Stub {
    ...
    static final long SHORT_DURATION_TIMEOUT = 4000;
    static final long LONG_DURATION_TIMEOUT = 7000; 
    ...

    public void handleShow(IBinder windowToken) {
        ...
        mParams.hideTimeoutMilliseconds = mDuration ==
            Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
        ...
    }
    ...
}

这个 hideTimeoutMilliseconds 是干嘛的呢?

文件 platform_frameworks_base/core/java/android/view/WindowManager.java 里能看到这个

/**
 * ...
 * ...                                        . Therefore, we do hide
 * such windows to prevent them from overlaying other apps.
 *
 * @hide
 */
public long hideTimeoutMilliseconds = -1;

在 GitHub 用 blame 查看到改动这一行的最近一次提交 aa07653d,它的 commit message 能表明它的用途:

Prevent apps to overlay other apps via toast windows

It was possible for apps to put toast type windows
that overlay other apps which toast winodws aren't
removed after a timeout.

Now for apps targeting SDK greater than N MR1 to add a
toast window one needs to have a special token. The token
is added by the notificatoion manager service only for
the lifetime of the shown toast and is then removed
including all windows associated with this token. This
prevents apps to add arbitrary toast windows.

Since legacy apps may rely on the ability to directly
add toasts we mitigate by allowing these apps to still
add such windows for unlimited duration if this app is
the currently focused one, i.e. the user interacts with
it then it can overlay itself, otherwise we make sure
these toast windows are removed after a timeout like
a toast would be.

We don't allow more that one toast window per UID being
added at a time which prevents 1) legacy apps to put the
same toast after a timeout to go around our new policy
of hiding toasts after a while; 2) modern apps to reuse
the passed token to add more than one window; Note that
the notification manager shows toasts one at a time.

它并不是用来控制 Toast 的显示时间的,只是为了防止有些应用的 toast 类型的窗口长期覆盖在别的应用上面,而超时自动隐藏这些窗口的时间,可以看作是一种防护措施。

第二处,文件 platform_frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java

long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;

在同一文件里能找到 LONG_DELAYSHORT_DELAY 的定义:

static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
static final int SHORT_DELAY = 2000; // 2 seconds

点击查看 PhoneWindowManager.TOAST_WINDOW_TIMEOUT 的定义:

文件 platform_frameworks_base/services/core/java/com/android/server/policy/PhoneWindowManager.java

/** Amount of time (in milliseconds) a toast window can be shown. */
public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds

至此,我们可以得出 结论:Toast 的长/短超时时间分别为 3.5 秒和 2 秒。

Tips: 也可以通过分析代码里的逻辑,一层一层追踪用到 LENGTH_SHORTLENGTH_LONG 的地方,最终得出结论,而这里是根据一些合理推断来简化追踪过程,更快达到目标,这在一些场景下是可取和必要的。

能不能弹一个时间超长的 Toast?

注:这里探讨的是能否直接通过 Toast 提供的公开 API 做到,网络上能搜索到的使用 Timer、反射、自定义等方式达到弹出一个超长时间 Toast 目的的方法不在讨论范围内。

我们在 Toast 类的源码里看一下跟设置时长相关的代码:

文件 platform_frameworks_base/core/java/android/widget/Toast.java

...

    /** @hide */
    @IntDef({LENGTH_SHORT, LENGTH_LONG})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Duration {}

...

    /**
     * Set how long to show the view for.
     * @see #LENGTH_SHORT
     * @see #LENGTH_LONG
     */
    public void setDuration(@Duration int duration) {
        mDuration = duration;
        mTN.mDuration = duration;
    }

...

    /**
     * Make a standard toast that just contains a text view.
     *
     * @param context  The context to use.  Usually your {@link android.app.Application}
     *                 or {@link android.app.Activity} object.
     * @param text     The text to show.  Can be formatted text.
     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
     *                 {@link #LENGTH_LONG}
     *
     */
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }

...

其实从上面 setDurationmakeText 的注释已经可以看出,duration 只能取值 LENGTH_SHORTLENGTH_LONG,除了注释之外,还使用了 @Duration 注解来保证此事。Duration 自身使用了 @IntDef 注解,它用于限制可以取的值。

文件 platform_frameworks_base/core/java/android/annotation/IntDef.java

/**
 * Denotes that the annotated element of integer type, represents
 * a logical type and that its value should be one of the explicitly
 * named constants. If the {@link #flag()} attribute is set to true,
 * multiple constants can be combined.
 * ...
 */

不信邪的我们可以快速在一个 demo Android 工程里写一句这样的代码试试:

Toast.makeText(this, "Hello", 2);

Android Studio 首先就不会同意,警告你 Must be one of: Toast.LENGTH_SHORT, Toast.LENGTH_LONG,但实际这段代码是可以通过编译的,因为 Duration 注解的 RetentionRetentionPolicy.SOURCE,我的理解是该注解主要能用于 IDE 的智能提示警告,编译期就被丢掉了。

但即使 duration 能传入 LENGTH_SHORTLENGTH_LONG 以外的值,也并没有什么卵用,别忘了这里设置的只是一个 flag,真正计算的时候是 long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;,即 duration 为 LENGTH_LONG 时时长为 3.5 秒,其它情况都是 2 秒。

所以我们可以得出 结论:无法通过 Toast 提供的公开 API 直接弹出超长时间的 Toast。(如节首所述,可以通过一些其它方式实现类似的效果)

Toast 能不能在非 UI 线程调用?

这个问题适合用一个 demo 来解答。

我们创建一个最简单的 App 工程,然后在启动 Activity 的 onCreate 方法里添加这样一段代码:

new Thread(new Runnable() {
    @Override
    public void run() {
        Toast.makeText(MainActivity.this, "Call toast on non-UI thread", Toast.LENGTH_SHORT)
                .show();
    }
}).start();

啊哦~很遗憾程序直接挂掉了。

11-07 13:35:33.980 2020-2035/org.mazhuang.androiduidemos E/AndroidRuntime: FATAL EXCEPTION: Thread-77
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:390)
        at android.widget.Toast.<init>(Toast.java:114)
        at android.widget.Toast.makeText(Toast.java:277)
        at android.widget.Toast.makeText(Toast.java:267)
        at org.mazhuang.androiduidemos.MainActivity$1.run(MainActivity.java:27)
        at java.lang.Thread.run(Thread.java:856)

顺着堆栈里显示的方法调用从下往上一路看过去,

文件 platform_frameworks_base/core/java/android/widget/Toast.java

首先是两级 makeText 方法:

// 我们的代码里调用的 makeText 方法
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
    return makeText(context, null, text, duration);
}

// 隐藏的 makeText 方法,不能手动调用
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
        @NonNull CharSequence text, @Duration int duration) {
    Toast result = new Toast(context, looper); // 这里的 looper 为 null
    ...

然后到了 Toast 的构造方法:

public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mTN = new TN(context.getPackageName(), looper); // looper 为 null
    ...
}

到 Toast$TN 的构造方法:

// looper = null
TN(String packageName, @Nullable Looper looper) {
    ...
    if (looper == null) {
        // Use Looper.myLooper() if looper is not specified.
        looper = Looper.myLooper();
        if (looper == null) {
            throw new RuntimeException(
                    "Can't toast on a thread that has not called Looper.prepare()");
        }
    }
    ...
}

至此,我们已经追踪到了我们的崩溃的 RuntimeException,即要避免进入抛出异常的逻辑,要么调用的时候传递一个 Looper 进来(无法直接实现,能传递 Looper 参数的构造方法与 makeText 方法是 hide 的),要么 Looper.myLooper() 返回不为 null,提示信息 Can't create handler inside thread that has not called Looper.prepare() 里给出了方法,那我们在 toast 前面加一句 Looper.prepare() 试试?这次不崩溃了,但依然不弹出 Toast,毕竟,这个线程在调用完 show() 方法后就直接结束了,没有调用 Looper.loop(),至于为什么调用 Toast 的线程结束与否会对 Toast 的显示隐藏等起影响,在本文的后面的章节里会进行分析。

从崩溃提示来看,Android 并没有限制在非 UI 线程里使用 Toast,只是线程得是一个有 Looper 的线程。于是我们尝试构造如下代码,发现可以成功从非 UI 线程弹出 toast 了:

new Thread(new Runnable() {
    @Override
    public void run() {
        final int MSG_TOAST = 101;
        final int MSG_QUIT = 102;

        Looper.prepare();

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {

                switch (msg.what) {
                    case MSG_TOAST:
                        Toast.makeText(MainActivity.this, "Call toast on non-UI thread", Toast.LENGTH_SHORT)
                                .show();
                        sendEmptyMessageDelayed(MSG_QUIT, 4000);
                        return;

                    case MSG_QUIT:
                        Looper.myLooper().quit();
                        return;
                }

                super.handleMessage(msg);
            }
        };

        handler.sendEmptyMessage(MSG_TOAST);

        Looper.loop();
    }
}).start();

至于为什么 sendEmptyMesageDelayed(MSG_QUIT, 4000) 里的 delayMillis 我设成了 4000,这里卖个关子,感兴趣的同学可以把这个值调成 0、1000 等等看一下效果,会有一些意想不到的情况发生。

到此,我们可以得出 结论:可以在非 UI 线程里调用 Toast,但是得是一个有 Looper 的线程。

ps. 上面这一段演示代码让人感觉为了弹出一个 Toast 好麻烦,也可以采用 Activity.runOnUiThread、View.post 等方法从非 UI 线程将逻辑切换到 UI 线程里执行,直接从 UI 线程里弹出,UI 线程是有 Looper 的。

知识点:这里如果对 Looper、Handler 和 MessageQueue 有所了解,就容易理解多了,预计下一篇对这三剑客进行讲解。

应用在后台时能不能 Toast?

这个问题也比较适合用一个简单的 demo 来尝试回答。

在 MainActivity 的 onCreate 里加上这样一段代码:

view.postDelayed(new Runnable() {
    @Override
    public void run() {
        Toast.makeText(MainActivity.this, "background toast", Toast.LENGTH_SHORT).show();
    }
}, 5000);

然后待应用启动后按 HOME 键,等几秒看是否能弹出该 Toast 即可。

结论是:应用在后台时可以弹出 Toast。

Toast 数量有没有限制?

这个问题将在下一节中一并解答。

Toast.makeText(…).show() 具体都做了些什么?

首先看一下 makeText 方法。

文件 platform_frameworks_base/core/java/android/widget/Toast.java

/**
 * Make a standard toast to display using the specified looper.
 * If looper is null, Looper.myLooper() is used.
 * @hide
 */
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
        @NonNull CharSequence text, @Duration int duration) {
    Toast result = new Toast(context, looper);

    LayoutInflater inflate = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
    TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
    tv.setText(text);

    result.mNextView = v;
    result.mDuration = duration;

    return result;
}

这个方法里就是构造了一个 Toast 对象,将需要展示的 View 准备好,设置好超时时长标记,我们可以看一下 com.android.internal.R.layout.transient_notification 这个布局的内容:

文件 platform_frameworks_base/core/res/res/layout/transient_notification.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">

    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_marginHorizontal="24dp"
        android:layout_marginVertical="15dp"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/primary_text_default_material_light"
        />

</LinearLayout>

我们最常见的 Toast 就是从这个布局文件渲染出来的了。

我们继续看一下 makeText 里调用的 Toast 的构造方法里做了哪些事情:

/**
 * Constructs an empty Toast object.  If looper is null, Looper.myLooper() is used.
 * @hide
 */
public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mTN = new TN(context.getPackageName(), looper);
    mTN.mY = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
            com.android.internal.R.integer.config_toastDefaultGravity);
}

主要就是构造了一个 TN 对象,计算了位置。

TN 的构造方法:

TN(String packageName, @Nullable Looper looper) {
    // XXX This should be changed to use a Dialog, with a Theme.Toast
    // defined that sets up the layout params appropriately.
    final WindowManager.LayoutParams params = mParams;
    params.height = WindowManager.LayoutParams.WRAP_CONTENT;
    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
    params.format = PixelFormat.TRANSLUCENT;
    params.windowAnimations = com.android.internal.R.style.Animation_Toast;
    params.type = WindowManager.LayoutParams.TYPE_TOAST;
    params.setTitle("Toast");
    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

    mPackageName = packageName;

    if (looper == null) {
        // Use Looper.myLooper() if looper is not specified.
        looper = Looper.myLooper();
        if (looper == null) {
            throw new RuntimeException(
                    "Can't toast on a thread that has not called Looper.prepare()");
        }
    }
    mHandler = new Handler(looper, null) {
        ...
    };
}

设置了 LayoutParams 的初始值,在后面 show 的时候会用到,设置了包名和 Looper、Handler。

TN 是 App 中用于与 Notification Service 交互的对象,这里涉及到 Binder 和跨进程通信的知识,这块会在后面开新篇来讲解,这里可以简单地理解一下:Notification Service 是系统为了管理各种 App 的 Notification(包括 Toast)的服务,比如 Toast,由这个服务来统一维护一个待展示 Toast 队列,各 App 需要弹 Toast 的时候就将相关信息发送给这个服务,服务会将其加入队列,然后根据队列的情况,依次通知各 App 展示和隐藏 Toast。

接下来看看 show 方法:

/**
 * Show the view for the specified duration.
 */
public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }

    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;

    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}

调用了 INotificationManager 的 enqueueToast 方法,INotificationManager 是一个接口,其实现类在 NotificationManagerService 里,我们来看 enqueueToast 方法的实现:

文件 platform_frameworks_base/services/core/java/com/android/server/notification/NotificationManagerService.java

@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
    ...

    synchronized (mToastQueue) {
        ...
        try {
            ToastRecord record;
            int index = indexOfToastLocked(pkg, callback);
            // If it's already in the queue, we update it in place, we don't
            // move it to the end of the queue.
            if (index >= 0) {
                record = mToastQueue.get(index);
                record.update(duration);
            } else {
                // Limit the number of toasts that any given package except the android
                // package can enqueue.  Prevents DOS attacks and deals with leaks.
                if (!isSystemToast) {
                    int count = 0;
                    final int N = mToastQueue.size();
                    for (int i=0; i<N; i++) {
                         final ToastRecord r = mToastQueue.get(i);
                         if (r.pkg.equals(pkg)) {
                             count++;
                             if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                 Slog.e(TAG, "Package has already posted " + count
                                        + " toasts. Not showing more. Package=" + pkg);
                                 return;
                             }
                         }
                    }
                }

                Binder token = new Binder();
                mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                record = new ToastRecord(callingPid, pkg, callback, duration, token);
                mToastQueue.add(record);
                index = mToastQueue.size() - 1;
                keepProcessAliveIfNeededLocked(callingPid);
            }
            // If it's at index 0, it's the current toast.  It doesn't matter if it's
            // new or just been updated.  Call back and tell it to show itself.
            // If the callback fails, this will remove it from the list, so don't
            // assume that it's valid after this.
            if (index == 0) {
                showNextToastLocked();
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }
}

主要就是使用调用方传来的包名、callback 和 duration 构造一个 ToastRecord,然后添加到 mToastQueue 中。如果在 mToastQueue 中已经存在该包名和 callback 的 Toast,则只更新其 duration。

这段代码里有一段可以回答我们的上一个问题 Toast 数量有没有限制 了:

// Limit the number of toasts that any given package except the android
// package can enqueue.  Prevents DOS attacks and deals with leaks.
if (!isSystemToast) {
    int count = 0;
    final int N = mToastQueue.size();
    for (int i=0; i<N; i++) {
         final ToastRecord r = mToastQueue.get(i);
         if (r.pkg.equals(pkg)) {
             count++;
             if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                 Slog.e(TAG, "Package has already posted " + count
                        + " toasts. Not showing more. Package=" + pkg);
                 return;
             }
         }
    }
}

即会计算 mToastQueue 里该包名的 Toast 数量,如果超过 50,则将当前申请加入队列的 Toast 抛弃掉。所以上一个问题的 结论是:Toast 队列里允许每个应用存在不超过 50 个 Toast。

那么构造 ToastRecord 并加入 mToastQueue 之后是如何调度,控制显示和隐藏的呢?enqueueToast 方法里有个逻辑是如果当前列表里只有一个 ToastRecord,则调用 showNextToastLocked,看一下与该方法相关的代码:

@GuardedBy("mToastQueue")
void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        ...
        try {
            record.callback.show(record.token);
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            ...
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            ...
        }
    }
}

...

@GuardedBy("mToastQueue")
private void scheduleTimeoutLocked(ToastRecord r)
{
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    mHandler.sendMessageDelayed(m, delay);
}

private void handleTimeout(ToastRecord record)
{
    if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
    synchronized (mToastQueue) {
        int index = indexOfToastLocked(record.pkg, record.callback);
        if (index >= 0) {
            cancelToastLocked(index);
        }
    }
}

...

@GuardedBy("mToastQueue")
void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    try {
        record.callback.hide();
    } catch (RemoteException e) {
        ...
    }

    ToastRecord lastToast = mToastQueue.remove(index);
    mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);

    keepProcessAliveIfNeededLocked(record.pid);
    if (mToastQueue.size() > 0) {
        // Show the next one. If the callback fails, this will remove
        // it from the list, so don't assume that the list hasn't changed
        // after this point.
        showNextToastLocked(); // 继续显示队列里的下一个 Toast
    }
}

...

private final class WorkerHandler extends Handler
{
    ...
    @Override
    public void handleMessage(Message msg)
    {
        switch (msg.what)
        {
            case MESSAGE_TIMEOUT:
                handleTimeout((ToastRecord)msg.obj);
                break;
            ...
        }
    }
}

即首先调用 record.callback.show(record.token),通知 App 展示该 Toast,然后根据 duration,延时发送一条超时消息 MESSAGE_TIMEOUT,WorkHandler 收到该消息后,调用 cancelToastLocked 通知应用隐藏该 Toast,并继续调用 showNextToastLocked 显示队列里的下一个 Toast。这样一个机制就保证了只要队列里有 ToastRecord,就能依次显示出来。

机制弄清楚了,再详细看一下应用接到通知 show 和 hide 一个 Toast 后是怎么做的:

文件 platform_frameworks_base/core/java/android/widget/Toast.java

private static class TN extends ITransientNotification.Stub {
    ...
    TN(String packageName, @Nullable Looper looper) {
        ...
        mHandler = new Handler(looper, null) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case SHOW: {
                        IBinder token = (IBinder) msg.obj;
                        handleShow(token);
                        break;
                    }
                    case HIDE: {
                        handleHide();
                        ...
                        break;
                    }
                    ...
                }
            }
        };
    }

    /**
     * schedule handleShow into the right thread
     */
    @Override
    public void show(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }

    /**
     * schedule handleHide into the right thread
     */
    @Override
    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.obtainMessage(HIDE).sendToTarget();
    }

    ...

    public void handleShow(IBinder windowToken) {
        ...
                mWM.addView(mView, mParams);
        ...
    }

    ...

    public void handleHide() {
        ...
                mWM.removeViewImmediate(mView);
        ...
    }
}

显示过程:show 方法被远程调用后,先是发送了一个 SHOW 消息,接收到该消息后调用了 handleShow 方法,然后 mWM.addView 将该 View 添加到窗口。

隐藏过程:hide 方法被远程调用后,先是发送了一个 HIDE 消息,接收到该消息后调用了 handleHide 方法,然后 mWM.removeViewImmediate 将该 View 从窗口移除。

这里插播一条结论,就是前文留下的为什么调用 Toast 的线程线束之后没弹出的 Toast 就无法弹出了的问题,因为 Notification Service 通知应用进程显示或隐藏 Toast 时,使用的是 mHandler.obtainMessage(SHOW).sendToTarget()mHandler.obtainMessage(HIDE).sendToTarget(),这个消息发出去后,Handler 对应线程没有在 Looper.loop() 过程里的话,就没有办法进入到 Handler 的 handleMessage 方法里去,自然也就无法调用显示和隐藏 View 的流程了。Looper.loop() 相关的知识点将在下篇讲解。

总结

补充后的 Toast 知识点列表

  1. Toast 不是 View,它用于帮助创建并展示包含一条小消息的 View;

  2. 它的设计理念是尽量不惹眼,但又能展示想让用户看到的信息;

  3. 被展示时,浮在应用界面之上;

  4. 永远不会获取到焦点;

  5. 大小取决于消息的长度;

  6. 超时后会自动消失;

  7. 可以自定义显示在屏幕上的位置(默认左右居中显示在靠近屏幕底部的位置);

  8. 可以使用自定义布局,也只有在自定义布局的时候才需要直接调用 Toast 的构造方法,其它时候都是使用 makeText 方法来创建 Toast;

  9. Toast 弹出后当前 Activity 会保持可见性和可交互性;

  10. 使用 cancel 方法可以立即将已显示的 Toast 关闭,让未显示的 Toast 不再显示;

  11. Toast 也算是一个「通知」,如果弹出状态消息后期望得到用户响应,应该使用 Notification;

  12. Toast 的超时时间为 LENGTH_SHORT 对应 2 秒,LENGTH_LONG 对应 3.5 秒;

  13. 不能通过 Toast 类的公开方法直接弹一个时间超长的 Toast;

  14. 应用在后台时可以调用 Toast 并正常弹出;

  15. Toast 队列里允许单个应用往里添加 50 个 Toast,超出的将被丢弃。

遗留知识点

本篇涉及到了一些需要进一步了解的知识点,在后续的篇章中会依次解读:

  1. Handler、Looper 和 MessageQueue

  2. WindowManager

  3. Binder 与跨进程通信

本篇用到的源码分析方法

  1. 查找关键变量被引用的地方;

  2. 按方法调用堆栈一层层逻辑跟踪与分析;

  3. 使用 git blame 查看关键代码行的变更日志;

后话

到此,上面提到的几个问题都已经解答完毕,对 Toast 源码的分析也告一段落。

写这篇文章花费的时间比较长,所以并不能按照预计的节奏更新,这里表示抱歉。另外,各位如果有耐心读到这里,觉得本文的思路是否清晰,是否能跟随文章的节奏理解一些东西?因为我也在摸索写这类文章的组织形式,所以也希望能收到反馈和建议,以作改进,先行谢过。


最后,照例要安利一下我的微信公众号「闷骚的程序员」,扫码关注,接收 rtfsc-android 的最近更新。

作者:mzlogin 发表于2017/11/20 11:32:45 原文链接
阅读:55 评论:0 查看评论

Material Design基础

$
0
0

Material Design,中文名:材质设计,是由Google推出的全新设计语言,旨在为手机、平板电脑、台式机和其他平台提供更一致、更广泛的感官体验。Flutter与Material Design的关系密切,Flutter框架中有一个Material Design风格的Widget库,而且这个Widget库也是Flutter的重要组成部分。但是Material Design不仅仅应用在Flutter应用程序中,从2014年开始,从Android到衍生的Android Wear、Auto和TV中,Material Design贯穿其中,成为勾通不同平台、设备的灵魂,让用户在不同平台上也有连贯的体验。

Material就像是理性空间和运动系统的统一理论,Material根植于现实触觉,来源于对纸和油墨的研究,加上技术性的提升,也加入了更多不可思议的想像力。

以上是Google对Material Design的表述,大致可以描述为基于现实世界的纸和油墨,但并不局限于这些元素在现实世界的表现。

在Material Design的设计思想里,纸是所有元素的载体。但是和现实世界的纸不同的是:

  • 纸可以自由伸缩,还可以改变形状。
  • 纸不会突然出现,通常是以动画形式出现,例如大小变换、滑动位移。
  • 两张纸的边缘发生碰撞时会互相推动。
  • 一张纸可以分成两张纸,也可以是多张纸合成一张纸。
  • 纸不能从另一张纸的中间穿过,但是可以从其表面滑过。
  • 纸的x、y轴尺寸是可变的,z轴则是固定为1dp的厚度。

Material Design世界的纸

纸有固定的厚度,并且可以从另一张纸的表面滑过。说明Material Design是三维的,第三维即Z轴,表示纸距离屏幕的远近,这个距离会影响纸的阴影,也决定纸与其他纸在三维空间里的前后关系。

手机屏幕是平面的,没有第三个维度,那么如何在屏幕上实现Z轴呢?使用传统的虚化或遮挡(前面的纸会遮挡住后面的纸)、透视(距离屏幕表面更近的纸显示更大)和阴影(距离屏幕更近的纸会投射出阴影)技术可以实现。

将这些传统的技术结合在一起,你能清晰的看到两张纸之间的距离与大小,而且了解一张纸在另一张纸的前面。

在平面上实现三维空间

阴影是在光源的照射下产生的,Material Design世界中的阴影来自2个光源:主光源和环境光。举个例子,你使用相机给别人拍照,相机的闪光灯就是主光源,周围的其他光源则是环境光。环境光在各个方向制造柔和的阴影,而主光源位于屏幕顶端的中心位置,投射明显的阴影在纸的下方。这种对视觉细节的研究使阴影看上去更加真实。

Material Design世界的阴影

Material来源于对纸和油墨的研究,除了纸,墨水也是Material Design的一个关键要素。Material Design世界的墨水是指纸的颜色和纸上的文本,墨水没有厚度,可以在纸上随意移动、放大或缩小、改变颜色或形状。比如,一张照片可以从略缩图扩展为原图,还可以查看照片细节。

Material Design世界的油墨

交互是应用程序设计中经常被忽略的部分,在用户轻触按钮打开新页面的过程中,按钮会放大、缩小或改变颜色?新页面是滑入还是从旧页面放大?过去,轻触事件常常是以改变背景颜色的形式反馈,在列表中轻触一行时,背景色可能突然从白色变成蓝色。

Material Design的交互要求自然过渡,最典型的轻触反馈就是以触点为中心的涟漪。就像是手指轻轻触碰湖面后,看到水的波纹从指尖向外扩散开来的景色。另外,如果单独的一张纸对轻触作反馈,整张纸会升起来,就像是急切的想和手指相遇一样。

Material Design中动画首先要求流畅,其次是能根据情况对动画加速或减速,就像开车踩油门的时候,速度上升有一个过程。另外,目的也是动画还有一个重点,动画的目的不是让用户惊讶,也不是用来分散注意力,更不是为了使应用更有趣。动画的真正目的是让用户理解前一种状态与后一种状态之间是怎么样过渡的,这样不仅能引导用户注意到关键的元素,还解释了是什么、在哪里发生了变化。

作者:hekaiyou 发表于2017/11/20 11:44:14 原文链接
阅读:1062 评论:0 查看评论

《剑指offer》刷题笔记(分解让复杂问题简单):二叉搜索树与双向链表

$
0
0

《剑指offer》刷题笔记(分解让复杂问题简单):二叉搜索树与双向链表



前言

在计算机领域有一类算法叫分治法,即“分而治之”。采用的就是各个击破的思想,我们把分解后的小问题各个解决,然后把小问题的解决方案结合起来解决大问题。

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题思路

哇偶,中序遍历啊!中序遍历,递归和循环都可以撒~

二叉搜索树是一种排序的数据结构,每个结点都有两个指向子结点的指针。在双向链表中,每个结点也有两个指针,他们分别指向前一个结点和后一个结点。在二叉搜索树中,左子结点的值总是小于父结点的值,右子结点的值总是大于父结点的值。因此我们在转换成排序双向链表时,原先指向左子结点的指针调整为链表中指向前一结点的指针,原先指向右子结点的指针调整为链表中指向后一个结点指针。

中序遍历在二叉搜索树中的特点是按照从小到大的顺序遍历二叉树的每一个结点。下图中,我们可以把树分成三个部分:值为10的结点、根结点为6的左子树、根结点为14的右子树。根绝排序链表的定义,值为10的结点将和它的左子树的最大一个结点链接起来,同时它还将和右子树最小的结点链接起来。

按照中序遍历的顺序,当我们遍历转换到根结点时,它的左子树已经转换成一个排序的链表了,并且处在链表中最后一个的结点是当前值最大的结点。我们把值为8的结点和根结点链接起来,10就成了最后一个借点,接着我们就去遍历转换右子树,并把根结点和右子树中最小的结点链接起来。

下面个分别用C/C++和Python来实现,其中递归1是书中的解法,递归2是更加容易理解的解法,原理都是一样的,只不过递归1中涉及到指针的指针,容易把自己搞蒙。

C++版代码实现

递归1

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        TreeNode *pLastNodeInList = NULL;
        ConvertNode(pRootOfTree, &pLastNodeInList);

        //需要返回头结点,所以需要遍历到头结点(最左子叶)
        TreeNode *pHeadOfList = pLastNodeInList;
        while(pHeadOfList != NULL && pHeadOfList->left != NULL)
            pHeadOfList = pHeadOfList->left;
        return pHeadOfList;
    }
    void ConvertNode(TreeNode* pNode, TreeNode** pLastNodeInList){
        if(pNode == NULL)
            return;

        TreeNode* pCurrent = pNode;
        //递归左子树
        if(pCurrent->left != NULL)
            ConvertNode(pCurrent->left, pLastNodeInList);
        //处理指针
        pCurrent->left = *pLastNodeInList;
        if(*pLastNodeInList != NULL)
            (*pLastNodeInList)->right = pCurrent;
        *pLastNodeInList = pCurrent;
        //递归右子树
        if(pCurrent->right != NULL)
            ConvertNode(pCurrent->right, pLastNodeInList);
    }
};

递归2

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree == NULL)
            return NULL;
        if(pRootOfTree->left == NULL && pRootOfTree->right == NULL)
            return pRootOfTree;
        //遍历左子树
        TreeNode* pLeft = Convert(pRootOfTree->left);
        TreeNode* pCurrent = pLeft;
        //定位至左子树最右的一个结点
        while(pCurrent != NULL && pCurrent->right != NULL)
            pCurrent = pCurrent->right;
        //如果左子树不为空,则将当前pRootOfTree加到左子树链表
        if(pLeft != NULL){
            pCurrent->right = pRootOfTree;
            pRootOfTree->left = pCurrent;
        }
        //遍历右子树
        TreeNode* pRight = Convert(pRootOfTree->right);
        //如果右子树不为空,则将当前pRootOfTree加到右子树链表
        if(pRight != NULL){
            pRight->left = pRootOfTree;
            pRootOfTree->right= pRight;
        }

        return pLeft != NULL?pLeft:pRootOfTree;
    }
};

循环

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        stack<TreeNode*> s;
        TreeNode* pHeadOfList = NULL;
        TreeNode* pLastNodeInList = NULL;
        TreeNode* pCurrent = pRootOfTree;
        while(pCurrent != NULL || !s.empty()){
            while(pCurrent != NULL){
                s.push(pCurrent);
                pCurrent = pCurrent->left;
            }
            if(!s.empty()){
                pCurrent = s.top();
                s.pop();
                if(pLastNodeInList != NULL){
                    pLastNodeInList->right = pCurrent;
                    pCurrent->left = pLastNodeInList;
                }
                //如果为空,则说明是最左边子结点,未来的头结点
                else
                    pHeadOfList = pCurrent;
                pLastNodeInList = pCurrent;
                pCurrent = pCurrent->right;
            }
        }
        return pHeadOfList;
    }
};

Python版代码实现

递归

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Convert(self, pRootOfTree):
        # write code here
        if not pRootOfTree:
            return None
        if not pRootOfTree.left and not pRootOfTree.right:
            return pRootOfTree
        #遍历左子树
        pLeft = self.Convert(pRootOfTree.left)
        pCurrent = pLeft
        #定位至左子树最右的一个结点
        while pCurrent and pCurrent.right:
            pCurrent = pCurrent.right
        #如果左子树不为空,则将当前pRootOfTree加到左子树链表
        if pLeft:
            pCurrent.right = pRootOfTree
            pRootOfTree.left = pCurrent
        #遍历右子树
        pRight = self.Convert(pRootOfTree.right)
        #如果右子树不为空,则将当前pRootOfTree加到右子树链表
        if pRight:
            pRight.left = pRootOfTree
            pRootOfTree.right = pRight

        return pLeft if pLeft else pRootOfTree

循环

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Convert(self, pRootOfTree):
        # write code here
        s = []
        pCurrent = pRootOfTree
        pHeadOfList = None
        pLastNodeInList = None
        while pCurrent or s:
            while pCurrent:
                s.append(pCurrent)
                pCurrent = pCurrent.left
            if s:
                pCurrent = s.pop()
                if pLastNodeInList:
                    pLastNodeInList.right = pCurrent
                    pCurrent.left = pLastNodeInList
                else:
                    pHeadOfList = pCurrent
                pLastNodeInList = pCurrent
                pCurrent = pCurrent.right
        return pHeadOfList

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

作者:u011475210 发表于2017/11/20 11:46:56 原文链接
阅读:46 评论:0 查看评论

Kafka的高级消费者与低级消费者

$
0
0

          在Kafka实战章节,我们写的例子都是Kafka的高级消费实例,可以看到在消息消费者的程序中,我们只需要指定zookeeper、及消费群组的groupId即可实现从消息队列中消费消息,屏蔽了大量的底层细节:如消息的偏移量等信息都不在程序中维护。Kafka的高级消费实例,满足以下几点规则:

(1)同一个消费群组中,如果线程数大于Topic分区数,那么一些线程永远接收不到消息;

(2)同一个消费群组中,如果线程数小于Topic分区数,部分线程将从多个分区接收消息;

(3)对于从多个分区接收消息的线程,消费每个分区内的消息是有序的,但消费多个分区之间的消息是无序的;

        明白了Kafka的高级消费实例的过程之后,如果我们想进一步控制一个消费者消费哪个分区怎么办呢?比如多次读取同一个消息。答案是使用低级消费者实例,即在程序中指定Topic的Partition的Leader broker,并在程序中跟踪消息的偏移量offset值。其步骤大致如下:

(1)指定消费Topic Partition的Leader broker及备份broker;

(2)构造并发送请求数据;

(3)处理leader broker的变更;

        实例如下:

import kafka.api.FetchRequest;
import kafka.api.FetchRequestBuilder;
import kafka.api.PartitionOffsetRequestInfo;
import kafka.common.ErrorMapping;
import kafka.common.TopicAndPartition;
import kafka.javaapi.*;
import kafka.javaapi.consumer.SimpleConsumer;
import kafka.message.MessageAndOffset;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SimpleConsumerDemo {
    private List<String> m_replicaBrokers = new ArrayList<>();

    public SimpleConsumerDemo(){
        m_replicaBrokers = new ArrayList<>();
    }

    public void run(long a_maxReads, String a_topic, int a_partition, List<String> a_seedBrokers, int a_port) throws Exception {
        // find the meta data about the topic and partition we are interested in
        //
        PartitionMetadata metadata = findLeader(a_seedBrokers, a_port, a_topic, a_partition);
        if (metadata == null) {
            System.out.println("Can't find metadata for Topic and Partition. Exiting");
            return;
        }
        if (metadata.leader() == null) {
            System.out.println("Can't find Leader for Topic and Partition. Exiting");
            return;
        }
        String leadBroker = metadata.leader().host();
        String clientName = "Client_" + a_topic + "_" + a_partition;

        SimpleConsumer consumer = new SimpleConsumer(leadBroker, a_port, 100000, 64 * 1024, clientName);
        long readOffset = getLastOffset(consumer,a_topic, a_partition, kafka.api.OffsetRequest.EarliestTime(), clientName);

        int numErrors = 0;
        while (a_maxReads > 0) {
            if (consumer == null) {
                consumer = new SimpleConsumer(leadBroker, a_port, 100000, 64 * 1024, clientName);
            }
            FetchRequest req = new FetchRequestBuilder()
                    .clientId(clientName)
                    .addFetch(a_topic, a_partition, readOffset, 100000) // Note: this fetchSize of 100000 might need to be increased if large batches are written to Kafka
                    .build();
            FetchResponse fetchResponse = consumer.fetch(req);

            if (fetchResponse.hasError()) {
                numErrors++;
                // Something went wrong!
                short code = fetchResponse.errorCode(a_topic, a_partition);
                System.out.println("Error fetching data from the Broker:" + leadBroker + " Reason: " + code);
                if (numErrors > 5) break;
                if (code == ErrorMapping.OffsetOutOfRangeCode())  {
                    // We asked for an invalid offset. For simple case ask for the last element to reset
                    readOffset = getLastOffset(consumer,a_topic, a_partition, kafka.api.OffsetRequest.LatestTime(), clientName);
                    continue;
                }
                consumer.close();
                consumer = null;
                leadBroker = findNewLeader(leadBroker, a_topic, a_partition, a_port);
                continue;
            }
            numErrors = 0;

            long numRead = 0;
            for (MessageAndOffset messageAndOffset : fetchResponse.messageSet(a_topic, a_partition)) {
                long currentOffset = messageAndOffset.offset();
                if (currentOffset < readOffset) {
                    System.out.println("Found an old offset: " + currentOffset + " Expecting: " + readOffset);
                    continue;
                }
                readOffset = messageAndOffset.nextOffset();
                ByteBuffer payload = messageAndOffset.message().payload();

                byte[] bytes = new byte[payload.limit()];
                payload.get(bytes);
                System.out.println(String.valueOf(messageAndOffset.offset()) + ": " + new String(bytes, "UTF-8"));
                numRead++;
                a_maxReads--;
            }

            if (numRead == 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ie) {
                }
            }
        }
        if (consumer != null) consumer.close();
    }

    public static long getLastOffset(SimpleConsumer consumer, String topic, int partition,
                                     long whichTime, String clientName) {
        TopicAndPartition topicAndPartition = new TopicAndPartition(topic, partition);
        Map<TopicAndPartition, PartitionOffsetRequestInfo> requestInfo = new HashMap<TopicAndPartition, PartitionOffsetRequestInfo>();
        requestInfo.put(topicAndPartition, new PartitionOffsetRequestInfo(whichTime, 1));
        kafka.javaapi.OffsetRequest request = new kafka.javaapi.OffsetRequest(
                requestInfo, kafka.api.OffsetRequest.CurrentVersion(), clientName);
        OffsetResponse response = consumer.getOffsetsBefore(request);

        if (response.hasError()) {
            System.out.println("Error fetching data Offset Data the Broker. Reason: " + response.errorCode(topic, partition) );
            return 0;
        }
        long[] offsets = response.offsets(topic, partition);
        return offsets[0];
    }

    private String findNewLeader(String a_oldLeader, String a_topic, int a_partition, int a_port) throws Exception {
        for (int i = 0; i < 3; i++) {
            boolean goToSleep = false;
            PartitionMetadata metadata = findLeader(m_replicaBrokers, a_port, a_topic, a_partition);
            if (metadata == null) {
                goToSleep = true;
            } else if (metadata.leader() == null) {
                goToSleep = true;
            } else if (a_oldLeader.equalsIgnoreCase(metadata.leader().host()) && i == 0) {
                // first time through if the leader hasn't changed give ZooKeeper a second to recover
                // second time, assume the broker did recover before failover, or it was a non-Broker issue
                //
                goToSleep = true;
            } else {
                return metadata.leader().host();
            }
            if (goToSleep) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ie) {
                }
            }
        }
        System.out.println("Unable to find new leader after Broker failure. Exiting");
        throw new Exception("Unable to find new leader after Broker failure. Exiting");
    }

    private PartitionMetadata findLeader(List<String> a_seedBrokers, int a_port, String a_topic, int a_partition) {
        PartitionMetadata returnMetaData = null;
        loop:
        for (String seed : a_seedBrokers) {
            SimpleConsumer consumer = null;
            try {
                consumer = new SimpleConsumer(seed, a_port, 100000, 64 * 1024, "leaderLookup");
                List<String> topics = Collections.singletonList(a_topic);
                TopicMetadataRequest req = new TopicMetadataRequest(topics);
                kafka.javaapi.TopicMetadataResponse resp = consumer.send(req);

                List<TopicMetadata> metaData = resp.topicsMetadata();
                for (TopicMetadata item : metaData) {
                    for (PartitionMetadata part : item.partitionsMetadata()) {
                        if (part.partitionId() == a_partition) {
                            returnMetaData = part;
                            break loop;
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println("Error communicating with Broker [" + seed + "] to find Leader for [" + a_topic
                        + ", " + a_partition + "] Reason: " + e);
            } finally {
                if (consumer != null) consumer.close();
            }
        }
        if (returnMetaData != null) {
            m_replicaBrokers.clear();
            for (kafka.cluster.Broker replica : returnMetaData.replicas()) {
                m_replicaBrokers.add(replica.host());
            }
        }
        return returnMetaData;
    }

    public static void main(String args[]) {
        SimpleConsumerDemo example = new SimpleConsumerDemo();
        long maxReads = Long.parseLong(args[0]);
        String topic = args[1];
        int partition = Integer.parseInt(args[2]);
        List<String> seeds = new ArrayList<>();
        seeds.add(args[3]);
        int port = Integer.parseInt(args[4]);
        try {
            example.run(maxReads, topic, partition, seeds, port);
        } catch (Exception e) {
            System.out.println("Oops:" + e);
            e.printStackTrace();
        }
    }
}

参考资料:

1、https://cwiki.apache.org/confluence/display/KAFKA/Index

2、http://www.nohup.cc/article/195/

3、http://blog.csdn.net/honglei915/article/details/37563647

4、http://orchome.com/11

作者:u012050154 发表于2017/11/20 12:08:03 原文链接
阅读:5 评论:0 查看评论

leetcode: 70. Climbing Stairs

$
0
0

Q

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output:  2
Explanation:  There are two ways to climb to the top.

1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output:  3
Explanation:  There are three ways to climb to the top.

1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

AC

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 1:
            return 1
        a, b = 1, 2
        for i in xrange(2, n):
            tmp = b
            b = a+b
            a = tmp
        return b


# Time:  O(n)
# Space: O(1)
class Solution2(object):
    """
    :type n: int
    :rtype: int
    """
    def climbStairs(self, n):
        prev, current = 0, 1
        for i in xrange(n):
            prev, current = current, prev + current,
        return current


# Time:  O(2^n)
# Space: O(n)
class Solution3(object):
    """
    :type n: int
    :rtype: int
    """
    def climbStairs(self, n):
        if n == 1:
            return 1
        if n == 2:
            return 2
        return self.climbStairs(n - 1) + self.climbStairs(n - 2)


if __name__ == "__main__":
    result = Solution().climbStairs(2)
    assert result == 2


作者:JNingWei 发表于2017/11/20 13:44:22 原文链接
阅读:9 评论:0 查看评论

leetcode: 71. Simplify Path

$
0
0

Q

Given an absolute path for a file (Unix-style), simplify it.

For example,
path = “/home/”, => “/home”
path = “/a/./b/../../c/”, => “/c”

Corner Cases:
Did you consider the case where path = “/../”?
In this case, you should return “/”.
Another corner case is the path might contain multiple slashes ‘/’ together, such as “/home//foo/”.
In this case, you should ignore redundant slashes and return “/home/foo”.

AC

class Solution(object):
    def simplifyPath(self, path):
        """
        :type path: str
        :rtype: str
        """
        # 思路:
        # 1. split / 形成List
        # 2. 如果 .. 就 pop 前面的
        # 3. 还要考虑 '///' '/...'
        places = [p for p in path.split("/") if p!="." and p!=""]
        stack = []
        for p in places:
            if p == "..":
                if len(stack) > 0:
                    stack.pop()
            else:
                stack.append(p)
        return "/" + "/".join(stack)


# Time:  O(n)
# Space: O(n)
class Solution2(object):
    def simplifyPath(self, path):
        stack, tokens = [], path.split("/")
        for token in tokens:
            if token == ".." and stack:
                stack.pop()
            elif token != ".." and token != "." and token:
                stack.append(token)
        return "/" + "/".join(stack)


if __name__ == "__main__":
    assert Solution().simplifyPath("/../") == '/'
    assert Solution().simplifyPath("/home//foo/") == '/home/foo'


作者:JNingWei 发表于2017/11/20 13:46:07 原文链接
阅读:7 评论:0 查看评论

leetcode: 72. Edit Distance

$
0
0

Q

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character
b) Delete a character
c) Replace a character

AC

class Solution(object):
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        distance = [[i] for i in xrange(len(word1) + 1)]
        distance[0] = [j for j in xrange(len(word2) + 1)]

        for i in xrange(1, len(word1) + 1):
            for j in xrange(1, len(word2) + 1):
                insert = distance[i][j - 1] + 1
                delete = distance[i - 1][j] + 1
                replace = distance[i - 1][j - 1]
                if word1[i - 1] != word2[j - 1]:
                    replace += 1
                distance[i].append(min(insert, delete, replace))

        return distance[-1][-1]


# Time:  O(n * m)
# Space: O(n + m)
class Solution2(object):
    # @return an integer
    def minDistance(self, word1, word2):
        if len(word1) < len(word2):
            return self.minDistance(word2, word1)

        distance = [i for i in xrange(len(word2) + 1)]

        for i in xrange(1, len(word1) + 1):
            pre_distance_i_j = distance[0]
            distance[0] = i
            for j in xrange(1, len(word2) + 1):
                insert = distance[j - 1] + 1
                delete = distance[j] + 1
                replace = pre_distance_i_j
                if word1[i - 1] != word2[j - 1]:
                    replace += 1
                pre_distance_i_j = distance[j]
                distance[j] = min(insert, delete, replace)

        return distance[-1]

# Time:  O(n * m)
# Space: O(n * m)
class Solution3(object):
    def minDistance(self, word1, word2):
        distance = [[i] for i in xrange(len(word1) + 1)]
        distance[0] = [j for j in xrange(len(word2) + 1)]

        for i in xrange(1, len(word1) + 1):
            for j in xrange(1, len(word2) + 1):
                insert = distance[i][j - 1] + 1
                delete = distance[i - 1][j] + 1
                replace = distance[i - 1][j - 1]
                if word1[i - 1] != word2[j - 1]:
                    replace += 1
                distance[i].append(min(insert, delete, replace))

        return distance[-1][-1]


if __name__ == "__main__":
    assert Solution().minDistance("Rabbit", "Racket") == 3
    assert Solution().minDistance("Rabbit", "Rabket") == 2
    assert Solution().minDistance("Rabbit", "Rabbitt") == 1


作者:JNingWei 发表于2017/11/20 13:46:57 原文链接
阅读:7 评论:0 查看评论

leetcode: 73. Set Matrix Zeroes

$
0
0

Q

Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place.

Follow up:
Did you use extra space?
A straight forward solution using O(mn) space is probably a bad idea.
A simple improvement uses O(m + n) space, but still not the best solution.
Could you devise a constant space solution?

AC

class Solution(object):
    def setZeroes(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: void Do not return anything, modify matrix in-place instead.
        """

        if not matrix:
            return matrix

        # find all (row, col) value 0 and put to stack
        stack = []
        rows = len(matrix)
        cols = len(matrix[0])
        for row in xrange(rows):
            for col in xrange(cols):
                if matrix[row][col] == 0:
                    stack.append((row, col))

        # calc 0 rows and cols
        rows_zero = set([x[0] for x in stack])
        cols_zero = set([x[1] for x in stack])


        # set rows zero and cols zero
        for i in rows_zero:
            matrix[i] = [0] * cols

        for j in cols_zero:
            for i in xrange(rows):
                matrix[i][j] = 0


# Time:  O(m * n)
# Space: O(1)
class Solution2(object):
    def setZeroes(self, matrix):
        first_col = reduce(lambda acc, i: acc or matrix[i][0] == 0, xrange(len(matrix)), False)
        first_row = reduce(lambda acc, j: acc or matrix[0][j] == 0, xrange(len(matrix[0])), False)

        for i in xrange(1, len(matrix)):
            for j in xrange(1, len(matrix[0])):
                if matrix[i][j] == 0:
                    matrix[i][0], matrix[0][j] = 0, 0

        for i in xrange(1, len(matrix)):
            for j in xrange(1, len(matrix[0])):
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0

        if first_col:
            for i in xrange(len(matrix)):
                matrix[i][0] = 0

        if first_row:
            for j in xrange(len(matrix[0])):
                matrix[0][j] = 0


if __name__ == "__main__":
    matrix = [ [1, 0, 1, 1], [1, 1, 0, 1], [1, 1, 1, 0], [1, 1, 1, 1]]
    Solution().setZeroes(matrix)
    assert matrix == [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0]]


作者:JNingWei 发表于2017/11/20 13:47:30 原文链接
阅读:3 评论:0 查看评论

leetcode: 74. Search a 2D Matrix

$
0
0

Q

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

Integers in each row are sorted from left to right.
The first integer of each row is greater than the last integer of the previous row.
For example,

Consider the following matrix:

[
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]

Given target = 3, return true.

AC

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if not matrix:
            return False

        m, n = len(matrix), len(matrix[0])
        left, right = 0, m * n
        while left < right:
            mid = left + (right - left) / 2
            if matrix[mid / n][mid % n] >= target:
                right = mid
            else:
                left = mid + 1

        return left < m * n and matrix[left / n][left % n] == target


if __name__ == "__main__":
    matrix = [[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 50]]
    assert Solution().searchMatrix(matrix, 3) == True


作者:JNingWei 发表于2017/11/20 13:47:59 原文链接
阅读:9 评论:0 查看评论

leetcode: 75. Sort Colors

$
0
0

Q

Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue.

Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Note:
You are not suppose to use the library’s sort function for this problem.

Follow up:
A rather straight forward solution is a two-pass algorithm using counting sort.
First, iterate the array counting number of 0’s, 1’s, and 2’s, then overwrite array with total number of 0’s, then 1’s and followed by 2’s.

Could you come up with an one-pass algorithm using only constant space?

AC

class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: void Do not return anything, modify nums in-place instead.
        """
        red, white, blue = 0, 0, len(nums)-1

        while white <= blue:
            if nums[white] == 0:
                nums[red], nums[white] = nums[white], nums[red]
                white += 1
                red += 1
            elif nums[white] == 1:
                white += 1
            else:
                nums[white], nums[blue] = nums[blue], nums[white]
                blue -= 1


# Time:  O(n)
# Space: O(1)
class Solution2(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: void Do not return anything, modify nums in-place instead.
        """
        def triPartition(nums, target):
            i, j, n = 0, 0, len(nums) - 1

            while j <= n:
                if nums[j] < target:
                    nums[i], nums[j] = nums[j], nums[i]
                    i += 1
                    j += 1
                elif nums[j] > target:
                    nums[j], nums[n] = nums[n], nums[j]
                    n -= 1
                else:
                    j += 1

        triPartition(nums, 1)


if __name__ == "__main__":
    A = [2, 1, 1, 0, 0, 2]
    Solution().sortColors(A)
    assert A == [0, 0, 1, 1, 2, 2]


作者:JNingWei 发表于2017/11/20 13:49:08 原文链接
阅读:4 评论:0 查看评论

leetcode: 76. Minimum Window Substring

$
0
0

Q

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

For example,
S = “ADOBECODEBANC”
T = “ABC”
Minimum window is “BANC”.

Note:
If there is no such window in S that covers all characters in T, return the empty string “”.

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

AC

# Time:  O(n)
# Space: O(k), k is the number of different characters
class Solution(object):
    def minWindow(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: str
        """
        current_count = [0 for i in xrange(52)]
        expected_count = [0 for i in xrange(52)]

        for char in t:
            expected_count[ord(char) - ord('a')] += 1

        i, count, start, min_width, min_start = 0, 0, 0, float("inf"), 0
        while i < len(s):
            current_count[ord(s[i]) - ord('a')] += 1
            if current_count[ord(s[i]) - ord('a')] <= expected_count[ord(s[i]) - ord('a')]:
                count += 1

            if count == len(t):
                while expected_count[ord(s[start]) - ord('a')] == 0 or \
                      current_count[ord(s[start]) - ord('a')] > expected_count[ord(s[start]) - ord('a')]:
                    current_count[ord(s[start]) - ord('a')] -= 1
                    start += 1

                if min_width > i - start + 1:
                    min_width = i - start + 1
                    min_start = start
            i += 1

        if min_width == float("inf"):
            return ""

        return s[min_start:min_start + min_width]


if __name__ == "__main__":
    assert Solution().minWindow("ADOBECODEBANC", "ABC") == 'BANC'     


作者:JNingWei 发表于2017/11/20 13:49:45 原文链接
阅读:3 评论:0 查看评论

leetcode: 77. Combinations

$
0
0

Q

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

For example,
If n = 4 and k = 2, a solution is:

[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

AC

class Solution(object):
    def combine(self, n, k):
        """
        :type n: int
        :type k: int
        :rtype: List[List[int]]
        """
        if k == 1:
            return [[i] for i in range(1, n+1)]
        if n == k:
            return [[i for i in range(1, n+1)]]
        return [i + [n] for i in self.combine(n-1,k-1)] + [i for i in self.combine(n-1, k)]


# Time:  O(n!)
# Space: O(n)
class Solution2(object):
    def combine(self, n, k):
        result = []
        self.combineRecu(n, result, 0, [], k)
        return result

    def combineRecu(self, n, result, start, intermediate, k):
        if k == 0:
            result.append(intermediate[:])
        for i in xrange(start, n):
            intermediate.append(i + 1)
            self.combineRecu(n, result, i + 1, intermediate, k - 1)
            intermediate.pop()


if __name__ == "__main__":
    assert Solution().combine(4, 2) == [[1, 4], [2, 4], [3, 4], [1, 3], [2, 3], [1, 2]]


作者:JNingWei 发表于2017/11/20 13:50:17 原文链接
阅读:5 评论:0 查看评论

leetcode: 78. Subsets

$
0
0

Q

Given a set of distinct integers, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

For example,
If nums = [1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

AC

class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        if not nums:
            return [[]]
        else:
            last = nums[-1]
            tmp = self.subsets(nums[:-1])
            tmp2 = [i + [last] for i in tmp]
            return tmp+tmp2


# Time:  O(n * 2^n)
# Space: O(1)
class Solution2(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums.sort()
        result = [[]]
        for i in xrange(len(nums)):
            size = len(result)
            for j in xrange(size):
                result.append(list(result[j]))
                result[-1].append(nums[i])
        return result


# Time:  O(n * 2^n)
# Space: O(1)
class Solution3(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = []
        i, count = 0, 1 << len(nums)
        nums.sort()

        while i < count:
            cur = []
            for j in xrange(len(nums)):
                if i & 1 << j:
                    cur.append(nums[j])
            result.append(cur)
            i += 1

        return result


# Time:  O(n * 2^n)
# Space: O(1)
class Solution4(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        return self.subsetsRecu([], sorted(nums))

    def subsetsRecu(self, cur, nums):
        if not nums:
            return [cur]

        return self.subsetsRecu(cur, nums[1:]) + self.subsetsRecu(cur + [nums[0]], nums[1:])


if __name__ == "__main__":
    assert Solution().subsets([1, 2, 3]) == [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]


作者:JNingWei 发表于2017/11/20 13:50:48 原文链接
阅读:3 评论:0 查看评论

leetcode: 79. Word Search

$
0
0

Q

Given a 2D board and a word, find if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cell, where “adjacent” cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.

For example,
Given board =

[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

word = “ABCCED”, -> returns true,
word = “SEE”, -> returns true,
word = “ABCB”, -> returns false.

AC

# Time:  O(m * n * l)
# Space: O(l)
class Solution(object):
    def exist(self, board, word):
        visited = [[False for j in xrange(len(board[0]))] for i in xrange(len(board))]

        for i in xrange(len(board)):
            for j in xrange(len(board[0])):
                if self.existRecu(board, word, 0, i, j, visited):
                    return True

        return False

    def existRecu(self, board, word, cur, i, j, visited):
        if cur == len(word):
            return True

        if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]) or visited[i][j] or board[i][j] != word[cur]:
            return False

        visited[i][j] = True
        result = self.existRecu(board, word, cur + 1, i + 1, j, visited) or \
                 self.existRecu(board, word, cur + 1, i - 1, j, visited) or \
                 self.existRecu(board, word, cur + 1, i, j + 1, visited) or \
                 self.existRecu(board, word, cur + 1, i, j - 1, visited)
        visited[i][j] = False

        return result


class Solution2(object):
    def exist(self, board, word):
        """
        :type board: List[List[str]]
        :type word: str
        :rtype: bool
        """
        if not board:
            return False
        for i in xrange(len(board)):
            for j in xrange(len(board[0])):
                if self.dfs(board, i, j, word):
                    return True
        return False

    # check whether can find word, start at (i,j) position    
    def dfs(self, board, i, j, word):
        if len(word) == 0: # all the characters are checked
            return True
        if i<0 or i>=len(board) or j<0 or j>=len(board[0]) or word[0]!=board[i][j]:
            return False
        tmp = board[i][j]  # first character is found, check the remaining part
        board[i][j] = "#"  # avoid visit agian 
        # check whether can find "word" along one direction
        res = self.dfs(board, i+1, j, word[1:]) or self.dfs(board, i-1, j, word[1:]) \
        or self.dfs(board, i, j+1, word[1:]) or self.dfs(board, i, j-1, word[1:])
        board[i][j] = tmp
        return res


if __name__ == "__main__":
    board = [
        "ABCE",
        "SFCS",
        "ADEE"
    ]
    assert Solution().exist(board, "ABCCED") == True
    assert Solution().exist(board, "SFCS") == True
    assert Solution().exist(board, "ABCB") == False


作者:JNingWei 发表于2017/11/20 13:51:18 原文链接
阅读:1 评论:0 查看评论

Android RecyclerView单点、批量数据元素项目item的增加、删除和移动

$
0
0
Android RecyclerView单点、批量数据元素项目item的增加、删除和移动


前文附录1,2介绍了基本的Android RecyclerView单点、批量元素项目的更新。现在给出其他比较重要的Android RecyclerView数据元素项目的删除和增加,删除和增加包含两种,一种是单点,另外一种是批量的元素。

(一)RecyclerView删除操作。

(a)单点删除:notifyItemRemoved(int position)

 /**
         * Notify any registered observers that the item previously located at <code>position</code>
         * has been removed from the data set. The items previously located at and after
         * <code>position</code> may now be found at <code>oldPosition - 1</code>.
         *
         * <p>This is a structural change event. Representations of other existing items in the
         * data set are still considered up to date and will not be rebound, though their positions
         * may be altered.</p>
         *
         * @param position Position of the item that has now been removed
         *
         * @see #notifyItemRangeRemoved(int, int)
         */
        public final void notifyItemRemoved(int position) {
            mObservable.notifyItemRangeRemoved(position, 1);
        }
在给adapter维持的数据队列删除一个元素后,调用此方法,该方法删除指定位置position的元素并更新RecyclerView。


(b)批量删除:notifyItemRangeRemoved(int positionStart, int itemCount) 

 public void notifyItemRangeRemoved(int positionStart, int itemCount) {
            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
            }
        }
在给adapter维持的数据元素队列批量删除若干元素后,调用该方法,该方法删除从开始位置positionStart起之后的itemCount个数量的子元素,然后更新RecyclerView。


(二)RecyclerView增加操作。

(a)单点增加:notifyItemInserted(int position)

/**
         * Notify any registered observers that the item reflected at <code>position</code>
         * has been newly inserted. The item previously at <code>position</code> is now at
         * position <code>position + 1</code>.
         *
         * <p>This is a structural change event. Representations of other existing items in the
         * data set are still considered up to date and will not be rebound, though their
         * positions may be altered.</p>
         *
         * @param position Position of the newly inserted item in the data set
         *
         * @see #notifyItemRangeInserted(int, int)
         */
        public final void notifyItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }
在给adapter指定位置position增加一个元素后,调用notifyItemInserted方法,更新RecyclerView。


(b)批量增加:notifyItemRangeInserted(int positionStart, int itemCount)

public void notifyItemRangeInserted(int positionStart, int itemCount) {
            // since onItemRangeInserted() is implemented by the app, it could do anything,
            // including removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }
在给adapter适配器指定位置positionStart增加itemCount个数据元素后,调用此方法,更新RecyclerView。

(三)RecyclerView移动操作。

notifyItemMoved(int fromPosition, int toPosition)

 /**
         * Notify any registered observers that the item reflected at <code>fromPosition</code>
         * has been moved to <code>toPosition</code>.
         *
         * <p>This is a structural change event. Representations of other existing items in the
         * data set are still considered up to date and will not be rebound, though their
         * positions may be altered.</p>
         *
         * @param fromPosition Previous position of the item.
         * @param toPosition New position of the item.
         */
        public final void notifyItemMoved(int fromPosition, int toPosition) {
            mObservable.notifyItemMoved(fromPosition, toPosition);
        }

移动操作的对象位置有两个,开始对象位置fromPosition和目的地址位置toPosition。设定这两个位置后,fromPosition元素位置的元素被移动到toPosition。fromPosition和toPosition是元素的集合队列中的下标。

附录:
1,《 Android RecyclerView更新子项目notifyItemChanged》链接:http://blog.csdn.net/zhangphil/article/details/78565738 
2,《Android RecyclerView批量更新notifyItemRangeChanged》链接:http://blog.csdn.net/zhangphil/article/details/78579849 
作者:zhangphil 发表于2017/11/20 14:33:40 原文链接
阅读:75 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>