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

SQL Server数据库置疑后无法删除问题

$
0
0
今日发现SQL Server 2005出现异常,数据库文件丢失,在恢复数据库的过程中,使用鼠标->右键,选择删除失败,提示数据库已经配置为发布状态,可是从发布中却无法查看到数据库的发布状态。。

这个时候只能使用语句了,如下语句可以解决问题:
 

EXEC SP_REMOVEDBREPLICATION 'TestDB'


即可清除发布。对于一般的数据库发布与订阅清除可以参考如下步骤:

下面的列表是手动删除复制的必要步骤的摘要。 本文的在"更多信息"部分提供每个步骤的详细信息。

  1. 删除所有订阅。
  2. 删除所有文章和发布。
  3. 清除任何"非"的事务日志复制交易记录。
  4. 删除数据库的发布和订阅状态。
  5. 删除不需要的远程服务器和远程登录。
  6. 删除服务器的发布服务器、 订阅服务器和分发服务器状态。
  7. 删除任何剩余的复制任务。
  8. 删除.sch 和.tmp 文件 repldata 目录中。
  9. 删除分发数据库。
  10. 重置 MSlast_job_info 表。
  11. 请验证复制注册表项。

您可能会发现更容易使用 SQL Enterprise Manager 可能要卸载复制,而不是使用存储过程和 Transact-SQL 命令。 

SQL Server 6.5 中, 可以使用 SQL 企业管理器删除通过单击复制配置 / 卸载发布服务器菜单上的复制。 有关详细的信息请参阅第 3 部分的新增 SQL Server 6.5 简介册中的"卸载复制"。 

SQL Server 6.0 没有一个复制卸载选项,但您可以执行的步骤 1 至 7 使用 SQL 企业管理器。 对于 SQL Server 6.0,仍然需要手动执行步骤 8 至 11。 有关详细信息,请参阅"停止复制"章 14 中 SQL Server 6.0 管理员的助理。 

请注意您不应使用转储和负载的机制将发布和订阅数据库传输到一个不同的服务器。 (在这种情况下"不同服务器"意味着不在最初发布数据库的一个)。 在很多的情况下本文中的过程不会从已从一台服务器转储并加载到另一台服务器上的数据库中删除复制足够。 

如果您必须将已发布的数据库移到另一台服务器中,应考虑使用传输管理器或大容量复制程序 (BCP)。 您可以安全地使用转储并加载不同服务器传输已发布的数据库仅当删除所有订阅和所有的出版物已发布数据库中之前转储数据库的机制。 

下面是步骤手动删除 (卸载) 复制进行详细说明。 请参阅 SQL Server 联机丛书有关详细信息在命令和下面讨论的存储的过程。

步骤 1-删除所有订阅

您可以运行 sp_helpsubscription 来确定有任何订阅已发布数据库中。 

运行以下命令删除已发布数据库中的所有发布的所有订阅。 已发布并且已订阅的每个数据库中,必须运行此命令。 运行此命令还会删除与断开的订阅关联的该通讯组任务。

   sp_dropsubscription 'all', 'all', 'all'
				


请注意 sp_dropsubscriptions 将只正确才能都满足以下条件:

  • 有是分发数据库安装。 

    和)
  • 发布服务器具有所有的订阅服务器的正确的远程服务器信息。 配置复制时,将自动定义远程服务器的信息。 可以通过运行 sp _ helpserver 验证远程服务器信息。

 

步骤 2-删除所有文章和发布

您可以运行 sp_helppublication 来确定有任何出版物已发布数据库中。 

您可以运行"Select * 从 sysarticles"来确定是否有任何文章在发布数据库中。 

运行以下命令删除所有文章和已发布数据库中的订阅。 已发布并且已发布的每个数据库中,必须运行此命令。 运行此命令将同时删除与断开的发布,同步任务。

   sp_droppublication 'all'
				


步骤 3-清除事务日志的复制任何"非"

交易记录

无法截断事务日志,只要有任何不分发的复制的事务。 一个"非复制的事务为已标记为已发布的数据库事务日志中复制,但已不被"分发"的日志读取器任务的交易记录。 

您可以看到通过运行以下命令是否有任何"非复制的事务,已发布的数据库中:

   dbcc opentran(<published_database_name>) with tableresults
				


如果该数据库了在某个点必须复制的事务,上面的命令将返回最早的分布式的行 ID ("REPL_OLD_DIST_RID") 和最早的非分布式的行 ID ("REPL_OLD_NONDIST_RID")。 如果数据库不具有任何复制的事务,并且有没有打开的事务,上面的命令将返回 0 的行。 

如果上面的命令不会返回最旧分布式和最早非-分布式行 ID,和如果这些行 ID 不相同,您有或多个 undistributed 复制该数据库中的事务。 如果行 ID 相同,您没有任何非复制的事务在数据库中。 详细信息,请参阅 Transact-SQL 参考书中的 DBCC 语句 (英文)。 

如果非复制的事务请运行下面的命令,将标记为"分配"的所有复制的事务,以便可以截断日志:

   sp_repldone 0, 0, null, 0, 0, 1
				


sp _ repldone 命令必须运行在每个具有非复制的事务的数据库中。 

如果数据库被标记为发布只能运行 sp _ repldone 命令。 如有必要,您可以将临时标记数据库为发布的运行以下命令:

   sp_dboption <database_name>, pub, true
				


在运行 sp _ repldone 之后可以通过运行以下命令来标记不是发布的数据库:

   sp_dboption <database_name>, pub, false
				


您应该与您的主要支持提供者联系如果您持续看到完成本文中过程后的事务日志中复制的事务中。

步骤 4-删除数据库的"发布"和"订阅"状态

运行 sp _ helpdb 来验证的数据库有状态为"发布"或"已订阅。 

运行以下命令从数据库中删除"发布"状态。 运行此命令还会删除数据库的日志读取器任务。

   sp_dboption <database_name>, published, false
				


运行以下命令从数据库中删除"已订阅"的状态:

   sp_dboption <database_name>, subscribed, false
				

 

步骤 5-删除不需要的远程服务器和远程登录

安装复制时,复制会自动定义远程服务器和远程登录。 远程服务器和远程登录是运行远程过程调用所必需的。 如果不需要其他 SQL Server 应用程序,可以删除远程服务器和远程登录。 

注意: 您不应删除本地服务器。 

本地服务器是运行 sp _ helpserver 时具有服务器 ID 0。 有关远程服务器和远程用户的详细信息,请参阅第 10 章 SQL Server 管理员助理中。 

您可以运行"sp _ helpserver"若要查看远程服务器的列表。 您可以运行"sp_helpremotelogin"若要查看远程登录的列表。 运行以下命令删除远程登录:

   sp_dropremotelogin remoteserver_name [, loginame [, remotename]]
				


运行以下命令以删除远程服务器: 注意: 不删除本地服务器。 本地服务器具有服务器 ID 0。

   sp_dropserver server_name [, droplogins]
				

 

步骤 6-删除服务器的发布者、 订阅服务器和分发服务器状态

运行 sp _ helpserver 以验证服务器的状态。 然后运行下面的命令,若要删除服务器的发布服务器 ("pub") 状态:

   sp_serveroption <server_name>, pub, false
				


运行以下命令删除服务器的远程发布服务器 ("dpub") 状态:

   sp_serveroption <server_name>, dpub, false
				


运行以下命令删除服务器的订阅服务器 ("Sub") 状态:

   sp_serveroption <server_name>, sub, false
				


运行以下命令删除服务器的分发服务器 ("分发") 状态:

   sp_serveroption <server_name>, dist, false
				

 

步骤 7-删除任何剩余的复制任务

打开在 SQL 企业管理器管理计划任务窗口,双击 SQL Executive 图标。 删除具有类型"日志读取器,"所有任务"通讯组"或"Sync"。 同时删除所有复制清除任务。 清除任务具有名称以下列格式:

   <publisher_server_name>_<subscriber_server_name>_cleanup.
				

 

步骤 8-删除.Sch 和.Tmp 文件,Repldata Directory 中

删除.sch (已发布的表架构脚本) 和 MSSQL/Repldata 目录中分发服务器上的.tmp (同步数据) 文件。

步骤 9-删除分发数据库

运行以下命令删除分发数据库:

   drop database distribution
				

删除分发数据库之后, 需要手动删除数据,然后登录分发数据库的设备文件,; 这要求停止并重新启动该 SQL Server。

步骤 10-重置 MSlast_job_info 表

MSlast_job_info 表将存在于每个订阅数据库中,将跟踪最后任务从每个已复制到该数据库的发布数据库。 您订阅了发布后,将由分配任务创建了 MSlast_job_info 表。 

如果订阅的数据库当前未订阅任何发布数据库,可以每个订阅数据库中删除 MSlast_job_info 表,只需使用下面的命令:

   drop table MSlast_job_info
				


如果订阅数据库仍然订阅到一个或多个发布的数据库中,您应使用下面的命令删除它不再订阅到该数据库与对应的 MSlast_job_info 表中行:

   delete MSlast_job_info
   where publisher = '<publishing_server_name>'
   and publisher_db = '<publishing_database_name>'
				

 

步骤 11-验证复制注册表项

您可以重新安装复制之前,HKEY_LOCAL_MACHINE/Software/Microsoft/MSSQLServer/Repliction 注册表项下的,"DistributionDB 值必须一个空字符串。 

可以从 master 数据库将该字符串为空运行以下命令:

   xp_regwrite 'HKEY_LOCAL_MACHINE',
   'SOFTWARE/Microsoft/MSSQLServer/Replication', 'DistributionDB',
   'REG_SZ', ''
				


警告: 错误地使用注册表编辑器可以导致严重问题,可能需要重新安装 SQL Server。 Microsoft 不能保证可以解决因注册表编辑器使用不当导致的问题。 使用注册表编辑器需要您自担风险。 

也是可以更改此项使用注册表编辑器 (Regedt 32 在 Windows NT 3.50 和 Windows NT 3.51 中) 或 Windows NT 4.0 和 Windows 95 中的注册表编辑器。 如果您使用注册表编辑器,执行不删除"DistributionDB 项本身 ; 而,双击 DistributionDB 条目并删除编辑对话框框中的,字符串。


作者:cashcat2004 发表于2016/8/1 23:10:08 原文链接
阅读:85 评论:0 查看评论

Android View控件的事件派发

$
0
0

Android View控件的事件派发

引:一直想写View,GroupView控件的事件派发流程.终于现在一口气都写出来.一鼓作气!当然考虑了很久应该用怎么样的方式写这些流程性的东西,应该用什么用的语言来描述.后来想就按照聊天的方式把!尽量把整个派发过程都写下来,并且实现一个简单的山寨派发流程.有点只见树不见山的感觉.但是觉得自己写一次view的事件派发胜过再多的理论!

派发流程


首先我们来看看view的事件派发.

关键函数
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);

看看一个简单的”演示代码-1”.
继承view并且在dispatchTouchEvent和onTouchEvent添加打印信息.

MainActivity.java

package com.dsliang.eventdispatch;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

        result = super.dispatchTouchEvent(event);

        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        boolean result;

        Log.d(Tag, "onTouchEvent: " + event.getAction());

        result = super.onTouchEvent(event);

        Log.d(Tag, "onTouchEvent result: " + result);


        return result;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.dsliang.eventdispatch.EventDispatchView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@android:color/darker_gray" />

</LinearLayout>

这里写图片描述

其实在上面的”演示代码-1”里面我们其实什么事情都没处理.看看在模拟器上运行的效果是怎么样把.

结论:当我们点击红色区域的时候.先后打印出dispatchTouchEvent,onTouchEvent.在这里我们可以知道分发过程必定是先调用dispatchTouchEvent函数然后再调用onTouchEvent函数.

如果你的代码这样写,那么对于我这篇文章你就没必要继续看下去了.事实上我的代码不应该这样写!

一切起于零


然后看看接下来的”演示代码-2”.我们仅仅修改文件EventDispatchView.java文件.

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

//        result = super.dispatchTouchEvent(event);
        result = false;

        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        boolean result;

        Log.d(Tag, "onTouchEvent: " + event.getAction());

//        result = super.onTouchEvent(event);
        result = false;

        Log.d(Tag, "onTouchEvent result: " + result);


        return result;
    }
}
我们在dispatchTouchEvent函数里面返回false并且调用父类的dispatchTouchEvent方法.对onTouchEvent函数我们也进行同样的修改.(对于此时,两个函数的返回值是true/false对目前)

这里写图片描述

结论:很明显能看到onTouchEvent函数并没有调用.那么就是说明其实在父类的dispatchTouchEvent里面一定是调用了onTouchEvent函数.

小试牛刀


这个道理你懂了以后我们要达到父类的相同效果,我们现在就针对dispatchTouchEvent进行一番修改.

现在我们也是只修改EventDispatchView.java文件.

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

//        result = super.dispatchTouchEvent(event);
        result = doOnDispatchTouchEvent(event);


        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    private boolean doOnDispatchTouchEvent(MotionEvent event) {
        return onTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        boolean result;

        Log.d(Tag, "onTouchEvent: " + event.getAction());

//        result = super.onTouchEvent(event);
        result = false;

        Log.d(Tag, "onTouchEvent result: " + result);


        return result;
    }
}
添加doOnDispatchTouchEvent函数,在dispatchTouchEvent调用doOnDispatchTouchEvent函数,doOnDispatchTouchEvent函数调用onTouchEvent函数.这样修改效果基本就和父类有一样的行为了.

这里写图片描述

从上面的效果图也可以看出的确现在这样修改以后和"演示代码-1"行为上看起来好像已经是一致了.

但是现在我又注意到一个问题!event.getAction函数返回是0?进去MotionEvent类看看0代表什么.其实0就是ACTION_DOWN.原来是按钮按下事件.但是也是不科学吧?稍稍有android事件的常事我们都会意识到.点击过程至少会有三个事件会触发.分别是按下,移动,抬起.怎么说再不赖也得有抬起事件把?

这里写图片描述

来到这里,首先抛出一个结论.如果dispatchTouchEvent在按下事件返回false说明此控件并没有消耗此次事件.那么系统(在view的角度触发你可以认为是系统,但是准确来说应该是你的包含你空间的布局容器)会认为你对接下来的一系列事件(移动,抬起)都不感兴趣.简单说就是在收到按下事件的时候返回false,接下来的移动,抬起事件都不会传递到次控件.(这个结论在下一篇”Android GroupView控件的事件派发”会具体阐述说明)

稍稍做润色


嗯嗯,那么稍稍把onTouchEvent的会返回值修改一下.这个就是我们的”演示代码-3”了.同样也是只修改EventDispatchView.java文件.

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

//        result = super.dispatchTouchEvent(event);
        result = doOnDispatchTouchEvent(event);


        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    private boolean doOnDispatchTouchEvent(MotionEvent event) {
        return onTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        boolean result;

        Log.d(Tag, "onTouchEvent: " + event.getAction());

//        result = super.onTouchEvent(event);
        result = true;

        Log.d(Tag, "onTouchEvent result: " + result);


        return result;
    }
}

现在这样可以看的按下,移动,抬起都派发给我们的EventDispatchView控件了!

这里写图片描述

现在我们先来一个小总结.
1,dispatchTouchEvent函数会调用onTouchEvent函数
2,只有在按下到来的时候dispatchTouchEvent返回true才会接收到移动,抬起事件.

初试锋芒


看起来这一节应该是结束的节奏了吧?图样图森破了!
你是忘了在使用控件的时候,可以设置点击事件和接听滑动事件么?
接下来看看”演示代码-4”.为了突出点击事件,滑动事件.dispatchTouchEvent和onTouchEvent只调用父类的方法.另外给EventDispatchView设置点击事件和滑动事件的监听函数.

MainActivity.java

package com.dsliang.eventdispatch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends Activity {

    public static String Tag = MainActivity.class.getSimpleName();

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

        View view = findViewById(R.id.viewEventDispatchView);

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(Tag, "onClick");
            }
        });

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.d(Tag, "onTouch");
                return true;
            }
        });
    }
}

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

        result = super.dispatchTouchEvent(event);
//        result = doOnDispatchTouchEvent(event);


        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    private boolean doOnDispatchTouchEvent(MotionEvent event) {
        return onTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        boolean result;

        Log.d(Tag, "onTouchEvent: " + event.getAction());

        result = super.onTouchEvent(event);
//        result = true;

        Log.d(Tag, "onTouchEvent result: " + result);


        return result;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.dsliang.eventdispatch.EventDispatchView
        android:id="@+id/viewEventDispatchView"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@android:color/darker_gray" />

</LinearLayout>

这里有两个地方需要注意:
1,onTouch函数返回false.onTouch函数的返回值会对派发事件有什么影响?
2,dispatchTouchEvent函数和onTouchEvent函数均使用父类方法的返回值作为返回值.意味着我们只能获取到按下事件.(并没有消耗按下事件)

这里写图片描述

仔细看看,当我们设置了点击事件监听函数之后super.onTouchEvent(event)居然返回true了!不科学呀,明显很之前的有矛盾把?

先给出结论:设置了点击事件回调函数后会改变super.onTouchEvent函数的默认行为.

    /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

...

    /**
     * Enables or disables click events for this view. When a view
     * is clickable it will change its state to "pressed" on every click.
     * Subclasses should set the view clickable to visually react to
     * user's clicks.
     *
     * @param clickable true to make the view clickable, false otherwise
     *
     * @see #isClickable()
     * @attr ref android.R.styleable#View_clickable
     */
    public void setClickable(boolean clickable) {
        setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
    }

...

设置点击回调函数以后,setClickable函数将CLICKABLE置1了.(flags的CLICKABLE位).

public boolean onTouchEvent(MotionEvent event) {

       ...

        /*
        当设置了点击事件回调函数,次条件成立.然后无论是
        ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE都返回true
        */
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:

                    ...

                    break;

                case MotionEvent.ACTION_DOWN:

                   ...

                    break;

                case MotionEvent.ACTION_CANCEL:

                   ...

                    break;

                case MotionEvent.ACTION_MOVE:

                    ...

                    break;
            }

            return true;
        }

        return false;
    }


...

    public boolean dispatchTouchEvent(MotionEvent event) {

        ...

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...

    }

...
事实上只有设置了点击回调函数/长按回调函数均会返回ture.所以也解释了为什么设置回调函数后super.onTouchEvent返回值变成true了.

仔细的同学可能发现了onClick回调函数是在ACTION_UP事件里面调用.这很符合我们使用习惯!哪一个软件不是松开手才调用按钮事件呢?具体情况是performClick函数负责调用onClick函数.

当然有更仔细的同学发现另一个问题了!super.dispatchTouchEvent在调用onTouchEvent函数之前会调用onTouch函数并且根据onTouch函数的返回值判断是否返回!!!这意味了什么?意味着如果你同时设置了onClick函数onTouch的情况下,如果onTouch返回false.那么一切都正常你不会发现什么很玄的东西.但是一旦你讲onTouch返回true.那么问题就会来了.onClick函数没有如愿的调用.就下下面展示的图片一样.

这里写图片描述

专属山寨版


到这里我们摸透view派发事件的默认行为了,那么我们模仿来写一个属于我们理解的派发过程把!

“演示代码-5”

MainActivity.java

package com.dsliang.eventdispatch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends Activity {

    public static String Tag = MainActivity.class.getSimpleName();

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

        View view = findViewById(R.id.viewEventDispatchView);

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(Tag, "onClick");
            }
        });

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.d(Tag, "onTouch");
                return true;
            }
        });
    }
}

EventDispatchView.java

package com.dsliang.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by dsliang on 16-7-31.
 */
public class EventDispatchView extends View {

    public static String Tag = EventDispatchView.class.getSimpleName();

    public EventDispatchView(Context context) {
        super(context);
    }

    public EventDispatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EventDispatchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private OnTouchListener mTouchListener = null;

    /*
    onTouch子类无法访问,当设置onTouch的时候保存对象的引用
     */
    @Override
    public void setOnTouchListener(OnTouchListener l) {
        super.setOnTouchListener(l);
        this.mTouchListener = l;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result;
        Log.d(Tag, "dispatchTouchEvent: " + event.getAction());

//        result = super.dispatchTouchEvent(event);

        if (null != this.mTouchListener && this.mTouchListener.onTouch(this, event)) {
            return true;
        }

        result = doOnDispatchTouchEvent(event);


        Log.d(Tag, "dispatchTouchEvent result: " + result);

        return result;
    }

    private boolean doOnDispatchTouchEvent(MotionEvent event) {
        return onTouchEvent(event);
    }

    public void callOnClickListener() {

        /*
        performClick()已经封装了怎么调用onclick函数
         */
        if (isClickable()) {
            performClick();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {


        Log.d(Tag, "onTouchEvent: " + event.getAction());

//        result = super.onTouchEvent(event);


        /*
        可以点击,可以长按
         */
        if (isClickable() || isLongClickable()) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    ;
                    break;
                case MotionEvent.ACTION_UP:
                    callOnClickListener();
                    break;
                case MotionEvent.ACTION_MOVE:
                    ;
                    break;
                case MotionEvent.ACTION_CANCEL:
                    ;
                    break;

            }

            return true;
        }

        return false;

    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.dsliang.eventdispatch.EventDispatchView
        android:id="@+id/viewEventDispatchView"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@android:color/darker_gray" />

</LinearLayout>
理论上现在我们的EventDispatchView控件就拥有了系统默认的行为了.(在事件派发方面)

现在然我们总结一下view在事件派发方面的几个关键地方:

1,事件派发首先调用doOnDispatchTouchEvent函数,然后调用onTouchEvent函数
2,根据ACTION_DOWN事件是否给消耗判,断接下来能否接收其余的一连串事件.
3,onClick回调函数在ACTION_UP事件中才调用
4,系统在没有设置点击回调函数/长按回调函数的情况下view不会消耗事件.(ACTION_DOWN事件)
5,onTouch回调函数的返回值会影响点击回调函数/长按回调函数的调用

作者:zq2114522 发表于2016/8/1 23:18:40 原文链接
阅读:92 评论:0 查看评论

Java实现简单二维码制作

$
0
0

       二维码概述

       我们生活中使用到二维码的场景:

       

       二维码概念

       二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白

相间的图形记录数据符号信息的图形。

      在代码编制上巧妙地利用构成计算机内部逻辑基础的"0"、"1"比特流的概念,使用若干个与二进制相对应的几何

形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些

共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息

自动识别功能、及处理图形旋转变化点。

      二维码发展历史图示:

      

      一维条码

      

      1)一维条码是由一组粗细不同、黑白(或彩色)相同的条、空机器相应的字符(数字字母)组成的标记,即传统条码。

      二维条码

      

      2)二维条码是用某种特定的几何何图形按一定规律在平面(二维方向上)分布的条、空相间的图形来记录数据符号信

息。

       二维码分类

       二维条码也有许多不同的码制,就码制的编码原理而言,通常分为三种类型:

       1)线性堆叠式二维码

       

       编码原理:建立在一维条码基础之上,按需要堆积成两行或多行。

       2)矩阵式二维码

       最为常用的类型。

       

       编码原理:在一个矩形空间通过黑、白像素在矩阵中的不同分布进行编码。

在矩阵相应元素位置上,用点(方点、圆点或者其他形状)的出现表示二进制"1",点的不出现表示二进制的"0"

       3)邮政码

       编码原理:邮政码通过不同长度的条进行编码,主要用于邮件编码

       如:POSTNET、BPO 4-STATE

       二维码优缺点

       优点:

       1)高密度编码,信息容量大

       2)编码范围广

       3)容错能力强

       4)译码可靠性高

       5)可引入加密措施

       6)成本低,易制作,持久耐用

       缺点:

       1)二维码技术成为手机病毒、钓鱼网站传播的新渠道

       2)信息泄露

       OR Code简介

       目前流行的三大国际标准:

       1)PDF417:不支持中文

       2)DM:专利未公开,需支付专利费用

       3)QR Code:专利公开,支持中文

       QR Code比其他二维码相比,具有识读速度快,数据密度大,占用空间小的优势。QR Code是由日本Denso公

司于1994年研制的一种矩阵二维码符号码,全称是Quick Response Code。

        制作QR Code二维码的三种方式:

        借助jar包zxing

        生成二维码的CreateQRCode.java源文件

package com.zxing;


import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;


import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;


/**
 * 生成二维码
 * @author Administrator
 * @date 2016年7月29日
 */
public class CreateQRCode {


	public static void main(String[] args) {
		
		int width = 300;//二维码图片的宽度
		int height = 300;//二维码图片的高度
		String format = "png";//二维码格式
		String content = "http://www.imooc.com";//二维码内容
		
		//定义二维码内容参数
		HashMap hints = new HashMap();
		//设置字符集编码格式
		hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
		//设置容错等级,在这里我们使用M级别
		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
		//设置边框距
		hints.put(EncodeHintType.MARGIN, 2);
		
		//生成二维码
		try {
			//指定二维码内容
			BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height,hints);
			//指定生成图片的保存路径
			Path file = new File("D:/code/imooc.png").toPath();
			//生成二维码
			MatrixToImageWriter.writeToPath(bitMatrix, format, file);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


}

        运行结果:

        

        解析二维码的ReadQRCode.java源文件:

package com.zxing;


import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;


import javax.imageio.ImageIO;


import com.google.zxing.BinaryBitmap;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;


public class ReadQRCode {


	public static void main(String[] args) {
		try {
			MultiFormatReader formatReader = new MultiFormatReader();
			File file = new File("D:/code/t.png");
			BufferedImage image = ImageIO.read(file);
			BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(image)));
			
			//定义二维码的参数
			HashMap hints = new HashMap();
			hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
			Result result = formatReader.decode(binaryBitmap,hints);
			
			System.out.println("解析结果:" + result.toString());
			System.out.println("二维码格式类型:" + result.getBarcodeFormat());
			System.out.println("二维码文本内容:" + result.getText());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}


}

        运行结果:

        

        (2)借助jar包qrcodejar

        生成二维码的CreateQRCode.java源文件:

package com.qrcode;


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;


import javax.imageio.ImageIO;


import com.sun.prism.Image;
import com.swetake.util.Qrcode;


public class CreateQRCode {


	public static void main(String[] args) throws Exception {
		Qrcode x = new Qrcode();
		//N代表数字,A代表a-z,B代表其他字符
		x.setQrcodeEncodeMode('B');
		//设置纠错等级
		x.setQrcodeErrorCorrect('M');
		//设置版本号(1-40)
		x.setQrcodeVersion(7);
		
		String qrDate = "http://www.baidu.com";
		int width = 67+12*(7-1);
		int height = 67+12*(7-1);
		int pixoff = 2;//偏移量
		
		BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics2D gs = bufferedImage.createGraphics();
		gs.setBackground(Color.WHITE);
		gs.setColor(Color.BLACK);
		gs.clearRect(0, 0, width, height);
		
		byte[] d = qrDate.getBytes("UTF-8"); 
		if(d.length>0&&d.length<120){
			boolean[][] s = x.calQrcode(d);
			for(int i=0;i<s.length;i++){
			    for(int j=0;j<s.length;j++){
					if(s[j][i]){
						gs.fillRect(j*3+pixoff, i*3+pixoff, 3, 3);
					}
				}
			}
		}
		
		gs.dispose();
		bufferedImage.flush();
		ImageIO.write(bufferedImage, "png", new File("D:/code/baidu.png"));
	}


}

        辅助类MyQRCodeImage.java源文件:

package com.qrcode;


import java.awt.image.BufferedImage;


import jp.sourceforge.qrcode.data.QRCodeImage;


public class MyQRCodeImage implements QRCodeImage {
	BufferedImage bufferedImagees;


	public MyQRCodeImage(BufferedImage bufferedImage){
		this.bufferedImagees = bufferedImage;
	}
	
	@Override
	public int getHeight() {
		return bufferedImagees.getHeight();
	}


	@Override
	public int getPixel(int arg0, int arg1) {
		return bufferedImagees.getRGB(arg0, arg1);
	}


	@Override
	public int getWidth() {
		return bufferedImagees.getWidth();
	}


}

        运行结果:

        

        解析二维码的ReadQRCode.java源文件:

package com.qrcode;


import java.awt.image.BufferedImage;
import java.io.File;


import javax.imageio.ImageIO;


import jp.sourceforge.qrcode.QRCodeDecoder;


public class ReadQRCode {
	public static void main(String[] args) throws Exception {
		
		File file = new File("D:/code/baidu.png");
		
		BufferedImage bufferedImage = ImageIO.read(file);
		
		QRCodeDecoder codeDecoder = new QRCodeDecoder();
		codeDecoder.decode(new MyQRCodeImage(bufferedImage));
		
		String result = new String(codeDecoder.decode(new  MyQRCodeImage(bufferedImage)), "UTF-8");
		System.out.println("解析二维码的内容:" + result);
		
	}


}

        运行结果:

        

        借助jquert.qrcode.js

        index.jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>二维码制作</title>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/jquery.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/jquery.qrcode.min.js"></script>
</head>
<body>
       <h1>生成二维码如下:</h1>
       <hr/>
       <div id="qrcode"></div>
       
       <script type="text/javascript" >
            jQuery('#qrcode').qrcode("http://www.qq.com");
       </script>
</body>
</html>

        运行结果:

        

        使用上面解析的结果是:

        



作者:erlian1992 发表于2016/8/1 23:21:53 原文链接
阅读:134 评论:0 查看评论

React Native布局详细指南

$
0
0

本文出自《React Native学习笔记》系列文章。

一款好的APP离不了一个漂亮的布局,本文章将向大家分享React Native中的布局方式FlexBox。
在React Native中布局采用的是FleBox(弹性框)进行布局。

FlexBox提供了在不同尺寸设备上都能保持一致的布局方式。FlexBox是CSS3弹性框布局规范,目前还处于最终征求意见稿 (Last Call Working Draft)阶段,并不是所有的浏览器都支持Flexbox。但大家在做React Native开发时大可不必担心FlexBox的兼容性问题,因为既然React Native选择用FlexBox布局,那么React Native对FlexBox的支持自然会做的很好。

宽和高

在学习FlexBox之前首先要清楚一个概念“宽和高”。一个组件的高度和宽度决定了它在屏幕上的尺寸,也就是大小。

像素无关

在React Native中尺寸是没有单位的,它代表了设备独立像素。

<View style={ {width:100,height:100,margin:40,backgroundColor:'gray'}}>
        <Text style={ {fontSize:16,margin:20}}>尺寸</Text>
</View>

上述代码,运行在Android上时,View的长和宽被解释成:100dp 100dp单位是dp,字体被解释成16sp 单位是sp,运行在iOS上时尺寸单位被解释称了pt,这些单位确保了布局在任何不同dpi的手机屏幕上显示不会发生改变;

和而不同

值得一提的是,React Native中的FlexBox 和Web CSSS上FlexBox工作方式是一样的。但有些地方还是有些出入的,如:

React Native中的FlexBox 和Web CSSS上FlexBox的不同之处

  • flexDirection: React Native中默认为flexDirection:'column',在Web CSS中默认为flex-direction:'row'
  • alignItems: React Native中默认为alignItems:'stretch',在Web CSS中默认align-items:'flex-start'
  • flex: 相比Web CSS的flex接受多参数,如:flex: 2 2 10%;,但在 React Native中flex只接受一个参数
  • 不支持属性:align-content,flex-basis,order,flex-basis,flex-flow,flex-grow,flex-shrink

以上是React Native中的FlexBox 和Web CSSS上FlexBox的不同之处,记住这几点,你可以像在Web CSSS上使用FlexBox一样,在React Native中使用FlexBox。

Layout Props

Flex in React Native

以下属性是React Native所支持的Flex属性。

父视图属性(容器属性):

  • flexDirection enum(‘row’, ‘column’,’row-reverse’,’column-reverse’)
  • flexWrap enum(‘wrap’, ‘nowrap’)
  • justifyContent enum(‘flex-start’, ‘flex-end’, ‘center’, ‘space-between’, ‘space-around’)
  • alignItems enum(‘flex-start’, ‘flex-end’, ‘center’, ‘stretch’)

主轴和侧轴(横轴和竖轴)

在学习上述属性之前,让我们先了解一个概念:主轴和侧轴
主轴和侧轴
主轴即水平方向的轴线,可以理解成横轴,侧轴垂直于主轴,可以理解为竖轴。

flexDirection

flexDirection enum('row', 'column','row-reverse','column-reverse')
flexDirection属性定义了父视图中的子元素沿横轴或侧轴方片的排列方式。

  • row: 从左向右依次排列
  • row-reverse: 从右向左依次排列
  • column(default): 默认的排列方式,从上向下排列
  • column-reverse: 从下向上排列

Usage:

<View style={ {flexDirection:'row-reverse',backgroundColor:"darkgray",marginTop:20}}>
    <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>1</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>2</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>3</Text>
  </View>
  <View style={ {width:40,height:40,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>4</Text>
  </View>
  </View>

flexDirection

flexWrap

flexWrap enum('wrap', 'nowrap')
flexWrap属性定义了子元素在父视图内是否允许多行排列,默认为nowrap。

  • nowrap flex的元素只排列在一行上,可能导致溢出。
  • wrap flex的元素在一行排列不下时,就进行多行排列。

Usage:

<View        style={ {flexWrap:'wrap',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

flexWrap

justifyContent

justifyContent enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around')

justifyContent属性定义了浏览器如何分配顺着父容器主轴的弹性(flex)元素之间及其周围的空间,默认为flex-start。

  • flex-start(default) 从行首开始排列。每行第一个弹性元素与行首对齐,同时所有后续的弹性元素与前一个对齐。
  • flex-end 从行尾开始排列。每行最后一个弹性元素与行尾对齐,其他元素将与后一个对齐。
  • center 伸缩元素向每行中点排列。每行第一个元素到行首的距离将与每行最后一个元素到行尾的距离相同。
  • space-between 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素与行首对齐,每行最后一个元素与行尾对齐。
  • space-around 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素到行首的距离和每行最后一个元素到行尾的距离将会是相邻元素之间距离的一半。

Usage:

<View        style={ {justifyContent:'center',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

justifyContent

alignItems

alignItems enum('flex-start', 'flex-end', 'center', 'stretch')
alignItems属性以与justify-content相同的方式在侧轴方向上将当前行上的弹性元素对齐,默认为stretch。

  • flex-start 元素向侧轴起点对齐。
  • flex-end 元素向侧轴终点对齐。
  • center 元素在侧轴居中。如果元素在侧轴上的高度高于其容器,那么在两个方向上溢出距离相同。
  • stretch 弹性元素被在侧轴方向被拉伸到与容器相同的高度或宽度。

Usage:

<View        style={ {justifyContent:'center',flexDirection:'row',backgroundColor:"darkgray",marginTop:20}}>
···
</View>

alignItems

子视图属性

  • alignSelf enum(‘auto’, ‘flex-start’, ‘flex-end’, ‘center’, ‘stretch’)
  • flex number

alignSelf

alignSelf enum('auto', 'flex-start', 'flex-end', 'center', 'stretch')
alignSelf属性以属性定义了flex容器内被选中项目的对齐方式。注意:alignSelf 属性可重写灵活容器的 alignItems 属性。

  • auto(default) 元素继承了它的父容器的 align-items 属性。如果没有父容器则为 “stretch”。
  • stretch 元素被拉伸以适应容器。
  • center 元素位于容器的中心。
  • flex-start 元素位于容器的开头。
  • flex-end 元素位于容器的结尾。

Usage:

<View style={ {alignSelf:'baseline',width:60,height:    20,backgroundColor:"darkcyan",margin:5}}>
   <Text style={ {fontSize:16}}>1</Text>
</View>
...

alignItems

flex

flex number
flex 属性定义了一个可伸缩元素的能力,默认为0。

Usage:

<View style={ {flexDirection:'row',height:40, backgroundColor:"darkgray",marginTop:20}}>
  <View style={ {flex:1,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:1</Text>
  </View>
  <View style={ {flex:2,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:2</Text>
  </View>
  <View style={ {flex:3,backgroundColor:"darkcyan",margin:5}}>
    <Text style={ {fontSize:16}}>flex:3</Text>
  </View>          
</View>

flex

其他布局 in React Native


以下属性是React Native所支持的除Flex以外的其它布局属性。

视图边框

  • borderBottomWidth number 底部边框宽度
  • borderLeftWidth number 左边框宽度
  • borderRightWidth number 右边框宽度
  • borderTopWidth number 顶部边框宽度
  • borderWidth number 边框宽度
  • border

尺寸

  • width number
  • height number

外边距

  • margin number 外边距
  • marginBottom number 下外边距
  • marginHorizontal number 左右外边距
  • marginLeft number 左外边距
  • marginRight number 右外边距
  • marginTop number 上外边距
  • marginVertical number 上下外边距

内边距

  • padding number 内边距
  • paddingBottom number 下内边距
  • paddingHorizontal number 左右内边距
  • paddingLeft number 做内边距
  • paddingRight number 右内边距
  • paddingTop number 上内边距
  • paddingVertical number 上下内边距

边缘

  • left number 属性规定元素的左边缘。该属性定义了定位元素左外边距边界与其包含块左边界之间的偏移。
  • right number 属性规定元素的右边缘。该属性定义了定位元素右外边距边界与其包含块右边界之间的偏移
  • top number 属性规定元素的顶部边缘。该属性定义了一个定位元素的上外边距边界与其包含块上边界之间的偏移。
  • bottom number 属性规定元素的底部边缘。该属性定义了一个定位元素的下外边距边界与其包含块下边界之间的偏移。

定位(position)

position enum(‘absolute’, ‘relative’)属性设置元素的定位方式,为将要定位的元素定义定位规则。

  • absolute:生成绝对定位的元素,元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
  • relative:生成相对定位的元素,相对于其正常位置进行定位。因此,”left:20” 会向元素的 LEFT 位置添加 20 像素。

参考

A Complete Guide to Flexbox
Using CSS flexible boxes
Layout with Flexbox
Layout Props

About

本文出自《React Native学习笔记》系列文章。
了解更多,可以关注我的GitHub
@https://crazycodeboy.github.io/

推荐阅读

作者:fengyuzhengfan 发表于2016/8/1 23:36:56 原文链接
阅读:138 评论:0 查看评论

uIP1.0用户手册中文版第一章

$
0
0

uIP嵌入式TCP/IP协议栈

uIP1.0用户手册

 

翻译:Leo

翻译时间:2016-8-1

说明:只翻译了其中的一部分,不是完整翻译

 

Adam Dunkels
adam@sics.se
Swedish Institute of Computer Science

 


 

第一章     uIP TCP/IP协议栈

1.1     简介

传统的TCP/IP实现要求太多的资源,无论是在代码大小还是内存使用情况在8位或16位的系统中。几十k的内存大小和不到100k的代码空间使几百k的代码大小和几百k内存空间要求的TCP/IP全栈在系统上得以实现。

 

uIP的实现被设计成仅具有绝对最小集合的所需的全部功能YCP/IP协议栈。它只能处理一个网络接口,包含IP,ICMP,UDP和TCP协议。uIP是使用C编程语言编写的。

 

许多其他的在小系统上实现的TCP/IP协议假设嵌入式设备总是和实现全栈的TCP/IP设备进行通信。在此假设下,有可能消除了在这种情况下很少使用一定的TCP / IP的机制。许多这些机制是必要的,但是,如果嵌入式设备是与另一个同样有限的设备,例如,运行的分布式对等网络服务和协议时的通信。uIP的设计要符合RFC为了让嵌入式设备作为一流的网络公民。未对任何特定的应用程序量身定制的uIP的TCP / IP实现。

 

1.2     TCP/IP通信

完整的TCP/ IP协议套件由许多协议,从低级别的协议比如ARP其IP地址转换为MAC地址,到应用程序级协议如SMTP,用于传送电子邮件。uIP大多关注的是TCP/IP协议和将被提及的叫做“应用”的上层协议。低层协议经常在硬件或固件中实现,并会由该网络设备的驱动控制的“网络设备”来表示。

 

TCP提供了可靠的字节流至上层协议。它打破字节流入适当大小的段,每段在其自己的IP包发送。IP分组的网络由网络设备驱动器上发送出去。如果该目的地不是物理连接的网络上,IP包是由位于两个网络之间的路由器转发到另一网络。如果其他网络的最大数据包大小是比IP分组的大小较小,则分组被分成由路由器较小的数据包。如果可能的话,被选择的TCP段的尺寸,使得碎片被最小化。该数据包的最终接收方将有所有零碎的IP数据包才可以被传递到更高的层次。

 

TCP/ IP协议栈的协议的正式要求是在一些由互联网工程任务组,IETF发布的RFC文档的规定。

 

RFC 1122的要求,可分为两大类;那些处理主机到主机的通信和那些处理应用程序和网络堆栈之间的通信处理。第一类的一个例子是“一个TCP必须能够接收任何段TCP选项”和第二类的例子是“必须有报告软TCP错误情况的应用程序的机制。”违反第一种要求的TCP / IP实现可以不能够与其他的TCP/ IP实现通信,并甚至可能导致网络故障。第二类的要求的违反将只影响系统内的通信,并不会影响主机到主机的通信。

 

在uIP里面,影响主机到主机的通信的所有RFC的要求落到实处。然而,为了减少码量,我们已删除的应用程序和栈之间的界面的某些机制,如软错误报告机制和类型的服务动态地配置比特TCP连接。由于只有该利用这些功能很少应用中,它们可以在不丧失一般性的去除。

 

1.3     主控制循环

uIP堆栈可以作为在多任务系统中的任务,或在一个单任务系统主程序。在这两种情况下,主控制循环做两件事情反复:

 

检查是否有一个数据包从网络到达。

 

检查是否发生了周期性暂停。

 

如果一个包到达了,输入处理函数,uip_input(),将被主控制循环调用。输入处理函数不会阻塞,但会立即返回。当它返回,传入数据包的目的是在堆栈或应用程序可能会产生那些应该被发送出去的一个或多个应答报文。如果是这样,该网络设备驱动器应该调用发出这些数据包。

 

周期超时依赖于定时器驱动TCP机制,如延迟确认,重传和往返时间的估计。当主控制循环推断周期定时器应该触发(fire),它需要调用时间处理函数uip_periodic().因为TCP/IP栈处理一个定时器事件时可能执行重传,网络设备驱动器应该被调用去发送已经生成的包。

 

1.4结构具体方法

uIP需要运行起来的时候需要很少的函数去使整个结构实现。这些功能应该是为特定的体系结构手动调整,但是通用的C实现的是作为uIP发行版本的一部分。

 

1.4.1 校验和计算

TCP和IP协议实现覆盖TCP和IP数据包的数据和报头部分的校验和。大多数情况下,这意味着校验和计算必须进行微调,在其上uIP堆栈运行特定的体系结构。

 

虽然uIP包括一个通用的校验功能,它也留下它开放的架构具体实施两个功能uip_ipchksum()和uip_tcpchksum()的。些功能的校验和计算可以写成高度优化汇编程序,而不是一般的C代码。

 

1.4.2 32位运算

TCP协议采用32位序列号,以及TCP实现将不得不做一些32位增补正常的协议处理的一部分。

 

1.5内存管理

在uIP所针对的结构中,RAM是最稀缺的资源。只有很少的RAM可以被TCP/IP协议栈使用,传统的TCP/IP协议不能直接被使用。

 

uIP不使用显示的动态内存分配。相反,它使用一个全局缓存来保存包,和一个固定的表格来保存链接状态。这个全局的包缓存区足够大去储存一个最大尺寸的包。当接收到一个从网络上来的包时,设备驱动程序将其放置在全局缓冲区并调用TCP / IP协议栈。当这个包里面有数据时,TCP/IP协议栈会通知相关的应用程序。因为缓存区里面的数据会被下一个到达的数据覆盖,所以应用程序必须立即处理这些数据或者把数据复制到其他的缓存区等待后面的处理。应用程序处理程序包必须排队,由网络设备或者由设备驱动器。大多数单芯片以太网控制器有大到足以容纳至少4最大尺寸的以太网帧的片上缓存。处理器处理设备,例如RS-232端口,可以在应用程序处理过程中把进来字节拷贝到一个单独的缓冲器。如果缓存区已满,输入数据包将被丢弃。这会导致性能下降,但只有当多个连接并行运行。这是因为uIP只有一个非常小的接收窗口,意味着每个连接中只有一个TCP字段。

 

在uIP中,同一个用来接收包的全局缓存区同样用来储存发送出去的TCP/IP的头部的数据。发送数据时,应用程序把一个指针指向数据以及要发送的长度传递到协议栈。TCP / IP报头被写入到全局缓冲器,一旦头已经产生,设备驱动器发送报头和所述应用程序数据在网络上。数据重发的时候没有排队。

 

内存配置决定两个系统应该能够处理的业务量和并发连接的最大数量。

 

1.6 应用程序接口(API)

uIP提供了两种API:protosockets,一个类似于BSD 的没有多线程的开销;一个“raw”基于事件的API比protosockets低级但是使用更少的内存。

 

16.1 uIP的raw API

“raw”的uIP API使用一种事件驱动接口,应用程序被调用去响应一些事件。uIP调用应用程序的情况有:数据被接收,数据已经成功地被传递到连接的另一端,建立了一个新的连接,数据需要被重传。应用程序也周期性的为了新的数据轮询。该应用程序只提供一个回调函数;它是由应用程序来处理映射不同的网络服务到不同的端口和连接。

 

1.6.1.1 应用程序事件

应用程序必须用C函数来实现,UIP_APPCALL(),uIP在事件发生的时候调用。每个事件有一个相关的测试函数用来区分不同的事件。该函数将以C宏定义的形式来实现,以0或者非0的形式来评估。注意某些事件可以相互结合发生。(新数据到达的同一时间可以有数据被确认)。

 

1.6.1.2 连接指针

当应用程序被uIP调用的时候,全局变量uip_conn被指向uip_conn结构体表示目前正在处理的连接,被称为当前连接。一个典型的用途是检查uip_conn->lport(本地TCP端口)来决定哪个服务当前的连接需要提供。例如:应用程序可能决定提供HTTP服务如果uip_conn->lport的值为80或者TELNET服务如果这个值是23.

 

1.6.1.3 接收数据

如果uIP测试函数uip_newdata()不是0,则远程连接的主机已经发送了新数据。Uip_appdata指针指向了实际的数据。数据的大小由uIP函数uip_datalen()来获得。该数据不被uIP缓存,应用程序返回时将被覆盖,应用程序需要立即处理接收的数据,或者自己把它复制到另外的缓存区等待以后的处理。

 

1.6.1.4 发送数据

缓存区的空间大小是由存储器的配置所决定的。因此,应用程序发送的所有数据没有到达接收机,应用程序可能使用uip_mss()函数来看多少数据将实际地由堆栈发送出去。

 

应用程序发送数据使用uIP函数uip_send().uip_send()函数需要两个参数,一个指针指向要发送的数据,数据的长度。如果应用程序需要内存空间来处理实际的要被发送的数据,包的缓存区(uip_appdata指针指向的地方)可以被用于这个目的。

 

应用程序在同一时间内只能发送一个数据块在一个连接上,而且也不能多次调用uip_send()在每次应用程序调用时,数据只有在下次调用时发送。

 

1.6.1.5 重传数据

重传由周期性TCP定时器驱动。每次周期性定时器被调用时,每次连接的重传定时器递减。如果定时器到达0,将要重传。当uIP决定某个数据段需要重传时,应用程序被调用的uip_rexmit()标志位设置,表明重传是必需的。

 

应用程序必需检查uip_rexmit()的标志位,产生之前发送的相同的数据。从应用程序的观点来看,执行重传和原始数据的发送是不一样的。因此,应用程序可以写成发送数据和重传数据用相同的代码。

 

1.6.1.6 关闭连接

应用程序关闭当前的连接通过在应用程序调用时调用uip_close()函数。这将导致连接完全关闭。为了表明一个致命的错误,应用程序可能想断开连接通过调用uip_abort()函数。

 

如果连接已被远端封闭,测试函数uip_closed()返回值为true。然后,应用程序可以做任何必要的清理。

 

1.6.1.7 报告错误

在连接的时候有两个致命的错误,连接被远程终端关闭或者连接重传上次的数据太多次将被中断。uIP调用应用程序来报告这个错误。应用程序可以使用两个测试函数uip_aborted()和uip_timedout()来测试这些错误情况。

 

1.6.1.8 轮询

当连接空闲时,每次定时器触发时,uIP轮询应用程序。应用程序调用函数uip_poll()去检查是否被uIP轮询。

 

轮询事件有两个目的。第一个是让应用程序定期知道连接是空闲的,允许应用程序关闭已闲置太久的连接。另一个目的是让应用程序发送已经产生的数据。应用程序只能通过被uIP调用来发送数据,轮询事件是闲置的连接发送数据的唯一的方法。

 

1.6.1.9 监听端口

调用uip_listen()函数来打开一个新的端口来监听。当一个连接请求到达监听端口时,uIP创建一个新的连接,调用应用程序。如果应用因为新的连接被创建而调用时测试函数uip_connected()的返回值为true。

 

应用程序可以检查uip_conn结构体的lport字段来判断哪个端口有新的连接建立。

 

1.6.1.10 打开连接

新的连接可以从uIP内部打开,通过调用函数uip_connect().这个函数分配一个新的连接,并设置一个标志,这将打开一个TCP连接到指定的IP地址和端口。这个函数返回一个指针到uip_conn结构体,如果没有空闲的连接槽,函数将返回NULL。

 

函数uip_ipaddr()可被用于打包一个IP地址到两个16位的元素中。下面给出两个打开连接的例子:

 

void connect_example1_app(void)

{

if(uip_connect(uip_conn->ripaddr, HTONS(8080))== NULL)

{

uip_abort();

}

}

 

void connect_example2(void)

{

u16_t ipaddr[2];

uip_ipaddr(ipaddr, 192,168,0,1);

uip_connect(ipaddr, HTONS(8080));

}

 

 

作者:Leo_Luo1 发表于2016/8/1 23:38:16 原文链接
阅读:81 评论:0 查看评论

Shader特效——实现“噪声”【基于ShaderToy】

$
0
0

本文是学习了CandyCat的博客之后写的一个小结,女神的博客理论写得非常详尽,看完有种如沐春风的感觉。


1.若干常见噪声类型

先上个效果图:


从左到右依次为:1.Perlin噪声,2.FBM叠加的分形噪声,3.对FBM绝对值叠加的分形噪声,4.值噪声。


以下代码是基于ShaderToy的作者Inigo Quilez的demo进行二次修改的,并添加了自己的总结注释

// Created by inigo quilez - iq/2013
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// Gradient Noise (http://en.wikipedia.org/wiki/Gradient_noise), not to be confused with
// Value Noise, and neither with Perlin's Noise (which is one form of Gradient Noise)
// is probably the most convenient way to generate noise (a random smooth signal with 
// mostly all its energy in the low frequencies) suitable for procedural texturing/shading,
// modeling and animation.
//
// It produces smoother and higher quality than Value Noise, but it's of course slighty more
// expensive.
//
// The princpiple is to create a virtual grid/latice all over the plane, and assign one
// random vector to every vertex in the grid. When querying/requesting a noise value at
// an arbitrary point in the plane, the grid cell in which the query is performed is
// determined (line 32), the four vertices of the grid are determined and their random
// vectors fetched (lines 37 to 40). Then, the position of the current point under 
// evaluation relative to each vertex is doted (projected) with that vertex' random
// vector, and the result is bilinearly interpolated (lines 37 to 40 again) with a 
// smooth interpolant (line 33 and 35).

// 算法解析:创建一个由若干虚拟晶格组成的平面,接着给每个晶格的顶点赋予一个随机的向量(通过hash函数生成),
// 然后通过fract函数将该点平移到【x:0-1, y:0-1】的空间中,再计算到各个晶格顶点的距离向量,
// 然后将这两个向量进行dot,最后dot的结果利用ease curves(即u)进行双线性插值。

// 注意:Gradient Noise并不是Value Noise,也不是Perlin Noise,而是基于Perlin Noise的一种分形布朗运动
//(Fractal Brownian Motion,FBM)的叠加

vec2 hash22( vec2 p )
{
	p = vec2( dot(p,vec2(127.1,311.7)),
			  dot(p,vec2(269.5,183.3)) );

	return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}

float hash21(vec2 p)
{
	return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
    //vec3 p3  = fract(vec3(p.xyx) * .1931);
    //p3 += dot(p3, p3.yzx + 19.19);
    //return fract((p3.x + p3.y) * p3.z);
}

// =================================================================================

float noise( in vec2 p )
{
    vec2 i = floor( p );
    vec2 f = fract( p );
	
    // Ease Curve
	//vec2 u = f*f*(3.0-2.0*f);
    vec2 u = f*f*f*(6.0*f*f - 15.0*f + 10.0);

    return mix( mix( dot( hash22( i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ), 
                     dot( hash22( i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                mix( dot( hash22( i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ), 
                   dot( hash22( i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
    
    //return dot(hash22(i+vec2(0.0, 0.0)), f-vec2(0.0, 0.0));
    //return dot(hash22(i+vec2(1.0, 0.0)), f-vec2(1.0, 0.0));
    //return mix(dot(hash22(i+vec2(0.0, 0.0)), f-vec2(0.0, 0.0)),
    //           dot(hash22(i+vec2(1.0, 0.0)), f-vec2(1.0, 0.0)), u.x);
    
    //return dot(hash22(i+vec2(0.0, 1.0)), f-vec2(0.0, 1.0));
    //return dot(hash22(i+vec2(1.0, 1.0)), f-vec2(1.0, 1.0));
    //return mix(dot(hash22(i+vec2(0.0, 1.0)), f-vec2(0.0, 1.0)),
    //           dot(hash22(i+vec2(1.0, 1.0)), f-vec2(1.0, 1.0)), u.x);
}

float noise_fractal(in vec2 p)
{
	p *= 5.0;
    mat2 m = mat2( 1.6,  1.2, -1.2,  1.6 );
	float f  = 0.5000*noise(p); p = m*p;
	f += 0.2500*noise(p); p = m*p;
	f += 0.1250*noise(p); p = m*p;
	f += 0.0625*noise(p); p = m*p;
    
    return f;
}


float noise_sum_abs(vec2 p)
{
    float f = 0.0;
    p = p * 7.0;
    f += 1.0000 * abs(noise(p)); p = 2.0 * p;
    f += 0.5000 * abs(noise(p)); p = 2.0 * p;
    f += 0.2500 * abs(noise(p)); p = 2.0 * p;
    f += 0.1250 * abs(noise(p)); p = 2.0 * p;
    f += 0.0625 * abs(noise(p)); p = 2.0 * p;

    return f;
}

float value_noise(vec2 p)
{
    p *= 56.0;
    vec2 pi = floor(p);
    //vec2 pf = p - pi;
    vec2 pf = fract(p);

    vec2 w = pf * pf * (3.0 - 2.0 * pf);

    // 它把原来的梯度替换成了一个简单的伪随机值,我们也不需要进行点乘操作,
    // 而直接把晶格顶点处的随机值按权重相加即可。
    return mix(mix(hash21(pi + vec2(0.0, 0.0)), hash21(pi + vec2(1.0, 0.0)), w.x),
              mix(hash21(pi + vec2(0.0, 1.0)), hash21(pi + vec2(1.0, 1.0)), w.x),
              w.y);
}

// -----------------------------------------------

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = fragCoord.xy / iResolution.xy;

	vec2 uv = p * vec2(iResolution.x/iResolution.y,1.0);
	
	float f = 0.0;
	
    // left: perlin noise	
	if( p.x<0.25 )
	{
		f = noise( 16.0 * uv );
	}
    // right: fractal noise (4 octaves)
    else if(p.x>=0.25 && p.x<0.5)	
	{
		f = noise_fractal(uv);
	}
    else if(p.x>=0.5 && p.x<0.75)
    {
    	f = noise_sum_abs(uv);
    }
    else 
    {
    	f = value_noise(uv);
    }

	f = 0.5 + 0.5*f;
	
    // 分割线:注意如果第三个参数超过了限定范围就不进行插值
    f *= smoothstep(0.0, 0.005, abs(p.x-0.25));
    f *= smoothstep(0.0, 0.005, abs(p.x-0.5));	
	f *= smoothstep(0.0, 0.005, abs(p.x-0.75));
    
	fragColor = vec4( f, f, f, 1.0 );
}

注意:fragCoord的取值范围是[vec2(0.0, 0.0), iResolution.xy]

2.Perlin噪声的应用

最后附上一个据说是基于Perlin噪声的火球效果:



GLSL Fragment代码和我总结的注释如下:

const vec2 iResolution = vec2(640.0, 640.0);
uniform float iGlobalTime;

float snoise(vec3 uv, float res)
{
   // ❤
   const vec3 s = vec3(1e0, 1e2, 1e3);
   //const vec3 s = vec3(1, 1, 1);
   
   uv *= res;
   
   vec3 uv0 = floor(mod(uv, res))*s;
   vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;
   
   vec3 f = fract(uv); 
   // 缓和函数
   f = f*f*(3.0-2.0*f);
   
   //  ❤扭曲图像 
   vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,
               uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);
   //vec4 v = vec4(uv0.x, uv0.y, uv1.x, uv1.y);
   

   // ❤ 影响形状和速度
   vec4 r = fract(sin(v*1e-1)*1e3);
   //vec4 r = fract(sin(v));
   float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
   
   // ❤ 影响形状和速度
   r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3);
   //r = fract(sin(v));
   float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
   
   return mix(r0, r1, f.z)*2.-1.;
}

void main(void) 
{
   // 换算到[(-.5, -.5), (.5, .5)]
   vec2 p = -.5 + gl_FragCoord.xy / iResolution.xy;
   
   // 换算到[(-1., -1.), (1., 1.)]
   //vec2 p = (2.*gl_FragCoord.xy - iResolution.xy) / iResolution.xy;
   //p *= 0.5; // 放大2倍
   
   // 根据屏幕纵横比变换
   p.x *= iResolution.x/iResolution.y;
   
   // 屏幕中心到边界,亮度由高到低
   // 定义火焰的基本形状
   float color = 3.0 - (3.*length(2.*p));
   //float color = 3.0 - (3.*length(2.*p - vec2(-.5, .5)));
   //float color = 3.0 - (3.*length(2.*p - vec2(2*p.x, 0.0)));
   
   // ❤ 控制火焰发散的形式
   vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, 0.5);
   //vec3 coord = vec3(p.y, 0, 0);
   //vec3 coord = vec3(p.x, 0, 0);
   //vec3 coord = vec3(atan(p.x,p.y), 0, 0);
   //vec3 coord = vec3(length(p)*.4, 0, 0);
   
   // 控制颜色的层次
   for(int i = 1; i <= 7; i++)
   {
      float power = pow(2.0, float(i));
      color += (1.5 / power) * 
      snoise(coord + vec3(0.,-iGlobalTime*.05, iGlobalTime*.01), power*16.);
      //snoise(coord + vec3(0., 0.05, 0.01), power*16.);
   }
   gl_FragColor = vec4( color, pow(max(color,0.),2.)*0.4, pow(max(color,0.),3.)*0.15 , 1.0);
}

注:

z = atan(x, y)/6.2832+.5 ; x, y ∈(-1, 1). 



z = length(p)*.4;



z = f*f*(3.0-2.0*f); f∈(0, 1) 相当于GLSL 的 smoothstep



作者:panda1234lee 发表于2016/8/1 23:51:46 原文链接
阅读:81 评论:0 查看评论

阿里巴巴2017实习生笔试题(一)

$
0
0

PS:查看答案请移步BAT直通车

选择题:

1.关于c++的inline关键字,以下说法正确的是()

A. 使用inline关键字的函数会被编译器在调用处展开

B. 头文件中可以包含inline函数的声明

C. 可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数

D. 定义在Class声明内的成员函数默认是inline函数

E. 优先使用Class声明内定义的inline函数

F. 优先使用Class实现的内inline函数的实现

2.对N个数进行排序,在各自最优条件下以下算法复杂度最低的是

A. 快速排序

B. 堆排序

C. 冒泡排序

D. 插入排序

E. 选择排序

F. 归并排序

3.甲乙两个一样大小的杯子各自分别装着小于一半容积的水和酒精.现将甲杯子里的一部分水倒入乙杯子;之后将乙杯子的混合液倒入一些到甲杯子,此时甲杯子的液体恢复到最初的状态.假定水和酒精混合之后的体积等于混合之前的体积之和.那么以下说法正确的是()

A. 甲杯子里的酒精体积等于乙杯子里的水的体积

B. 甲杯子里的酒精的体积等于乙杯子里的酒精的体积

C. 甲杯子里的水的体积等于乙杯子里的酒精的体积

D. 甲杯子里的水的体积等于乙杯子里的水的体积

E. 甲杯子里的液体高于乙杯子里的液位

4.下列程序的执行结果是()

 main()
{
    char*a[]={"work","at","alibaba"};
    char**pa=a;
    pa++;
    printf("%s",*pa);
}
A. at

B. atalibaba

C. ork

D. orkatalibaba

E. 编译错误

F. 运行溢出

5.一个黑盒子里有若干红球和黑球,随机取出一个球是红球的概率是p.现在从黑盒子中拿出等量的红球和黑球后,随机取出一个球是红球的概率是q,如果p

A. 0

B. 1

C. 3

D. 4

E. 5

F. 7

13.在100-999这900个自然数中,若将组成这个数的三个数字认为是三条线段的长度,那么是三条线段组成一个等腰三角形(包括等边)的共有()个.

A. 45

B. 91

C. 121

D. 142

E. 156

F. 165

14.下面哪个不是线性表?(D)

A. 循环链表

B. 队列

C. 栈

D. 关联数组

E. 空字符串数组

F. 双向链表

15.下面的哪种排序算法在算复杂度平均不是O(nlogn)的?()

A. 快速排序

B. 桶排序

C. 合并排序

D. 二叉树排序树排序

E. 堆排序

16.某创业团队的一个很大的办公室(障碍和遮挡可以忽略)里有一个WIFI源,有1个工位由于距离WIFI源太远而接收不到信号.为了提高该工位的联网质量,准备在工位和WIFI信号源之间安装一个无线AP(相当于中继的,可以中转该工位上的计算机和WIFI之间的信号).只考虑从WIFI发出的信号,如果AP离WIFI源太近,就起不到中继的作用,如果AP离工位太远则可能连不上WIFI.因此AP有一个最佳的安装位置,那么关于AP最佳安装位置的说法正确的是()

A. 如果WIFI源功率越大,那么AP最佳安装位置越靠近WIFI源

B. 如果WiFi源功率越大,那么AP最佳的安装位置越靠近工位

C. WIFI源功率和AP最佳安装位置无关.

D. AP最佳安装位置在工位和WIFI信号源连线之外

E. AP最佳安装位置在工位和WIFI信号源连线中点

F. 以上说法都不对

17.有100个金币,分给10个人.第一个金币等概率地分给10个人之一.之后的每一个金币分配给第K个人的概率正比于这个人已经持有的金币数+1.在这样的分配机制下,关于每个人最终的金币个数的分布的说法错误的是()

A. 每个人得到的金币的个数的期望是相等的

B. 每个人的金币个数接近均匀分布

C. 第一个金币给哪个人,哪个人的最终金币个数的期望就会更大

D. 在中间的某个阶段金币个数越多的人,未来获得金币的可能性越大

E. 以上说法都是正确的

F. 以上说法都是不正确的

18.在自由交换的情况下,只考虑偏好,小张用自己的小刀换了小王的橡皮.关于这个交换以下说法错误的是:()

A. 小张觉得橡皮比小刀更好

B. 小王觉得小刀比橡皮更好

C. 小张和小王总的财富里没有发生变化

D. 小张和小王的效用值增加了

E. 如果把小王换成小吴,那么这个交换可能就不会发生

F. 小刀和橡皮等值

19.如下C程序,在64位处理器上运行后sz的值是什么? ()

struct st
{
    int *p;
    int i;
    char a;
};
int sz=sizeof(struct st);
A. 24

B. 20

C. 16

D. 14

E. 13

F. 12

20.下面这个代码输出的是()

#include <vector>
using namespace std;
int main(void)
{
    vector<int>array;
    array.push_back(100);
    array.push_back(300);
    array.push_back(300);
    array.push_back(300);
    array.push_back(300);
    array.push_back(500);
    vector<int>::iterator itor;
    for(itor=array.begin();itor!=array.end();itor++)
    {
        if(*itor==300)
        {
            itor=array.erase(itor);
        }
    }
    for(itor=array.begin();itor!=array.end();itor++)
    {
            cout<<*itor<<"";
    }
  return 0;

A. 100 300 300 300  300 500

B. 100 3OO 300 300 500

C. 100 300 300 500

D. 100 300 500

E. 100 500

F. 程序错误

21.下面关于一个类的静态成员描述中,不正确的是()

A. 静态成员变量可被该类的所有方法访问

B. 该类的静态方法只能访问该类的静态成员函数

C. 该类的静态数据成员变量的值不可修改

D. 子类可以访问父类的静态成员

E. 静态成员无多态特性

22.给定的一个长度为N的字符串str,查找长度为P(P

作者:hitxueliang 发表于2016/8/1 23:57:03 原文链接
阅读:93 评论:0 查看评论

数据结构----划分树

$
0
0

今晚又学了另外一种树----划分树。看了一晚上了,也是大概明白一些而已,对于一些细节还是不太理解。


划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值 。(主席树也可以,早就听过主席树这个词,感觉好高大上,准备学习一下,下面的例题也是主席树入门)


划分树的定义

         划分树定义为,它的每一个节点保存区间[lft,rht]所有元素,元素顺序与原数组(输入)相同,但是,两个子树的元素为该节点所有元素排序后(rht-lft+1)/2个进入左子树,其余的到右子树,同时维护一个num域,num[i]表示lft->i这个点有多少个进入了左子树。

 

划分树的Sample

 

   如果由下而上看这个图,我们就会发现它和归并排序的(归并树)的过程很类似,或者说正好相反。归并树是由下而上的排序,而它确实是由上而下的排序(观察’4’的运动轨迹,我们可以猜到,划分树的排序也是一种稳定的排序方法,这里不是说明的重点,不予证明),但这正是它可以用来解决第k大元素的理由所在。(具体的理由,写完再补)


划分树的原理

划分树和归并树都是用线段树作为辅助的,原理是基于快排 和归并排序 的。

划分树的建树过程基本就是模拟快排过程,取一个已经排过序的区间中值,然后把小于中值的点放左边,大于的放右边。并且记录d层第i个数之前(包括i)小于中值的放在左边的数。具体看下面代码注释。


查找其实是关键,因为再因查找[l,r]需要到某一点的左右孩子时需要把[l,r]更新。具体分如下几种情况讨论:
假设要在区间[l,r]中查找第k大元素,t为当前节点,lch,rch为左右孩子,left,mid为节点t左边界和中间点。
1、sum[r]-sum[l-1]>=k,查找lch[t],区间对应为[ left+sum[l-1] , left+sum[r]-1 ]
2、sum[r]-sum[l-1]<k,查找rch[t],区间对应为[ mid+1+l-left-sum[l-1] , mid+1+r-left-sum[r] ]

上面两个关系在纸上可以推出来,对着上图更容易理解关系式


划分树链接:

1划分树

2划分树

3划分树




例题:poj 2104   K-th Number(划分树模板)

给出一个长度为n的数列,询问m次,每次给出a,b,k,询问[a,b]区间内第k大的数


#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=100005;
int sorted[N];    // 对原来集合中的元素排序后的值。 
int arr[N];
double Sum=0;
struct tree{
	int val[N];       // val 记录第 k 层当前位置的元素的值 
	int num[N];      // num 记录元素所在区间的当前位置之前进入左孩子的个数 
	double sum[N];    // sum 记录比当前元素sorted[mid]小的元素的和
}t[20];
//划分树构建 
void build(int l,int r,int p){
	if(l==r) return ;
	/* same 用来标记和中间值 sorted[mid] 相等的,且分到左孩子的数的个数。 
	初始时,假定当前区间[lft,rht]有 mid-lft+1 个和 sorted[mid] 相等。    
	先踢掉比中间值小的,剩下的就是要插入到左边的
	例如 1 3 3 3 3 5 5 7   same=4,sorted[mid]=3,分到左孩子且值为3的个数为3 
	*/   
	int mid=(l+r)>>1,same=mid-l+1,lp=l,rp=mid+1;
	for(int i=l;i<=r;i++){
		if(t[p].val[i]<sorted[mid]) same--;
	}
	for(int i=l;i<=r;i++){
		if(i==l){                                  // 初始一个子树。 
			t[p].num[i]=t[p].sum[i]=0;
		}
		else{                                     // 初始区间下一个节点。 
			t[p].num[i]=t[p].num[i-1];
			t[p].sum[i]=t[p].sum[i-1];
		}
		/* 如果大于,肯定进入右孩子,否则,判断是否还有相等的应该进入左孩子的,  
		 没有,就直接进入右孩子,否则进入左孩子,同时更新节点的 sum 域和 num 域*/   
		if(t[p].val[i]<sorted[mid]){
			t[p].num[i]++;
			t[p].sum[i]+=t[p].val[i];
			t[p+1].val[lp++]=t[p].val[i];
		}
		else if(t[p].val[i]>sorted[mid]){
			t[p+1].val[rp++]=t[p].val[i];
		}
		else{
			if(same){
				same--;
				t[p].num[i]++;
				t[p].sum[i]+=t[p].val[i];
				t[p+1].val[lp++]=t[p].val[i];
			}
			else
			    t[p+1].val[rp++]=t[p].val[i];
		}
	}
	build(l,mid,p+1);
	build(mid+1,r,p+1);
} 
//划分树查找 
/* 在区间[a, b]上查找第 k 大元素,同时 Sum 返回区间[a, b]中小于第 k 大元素的和。*/ 
int query(int a,int b,int l,int r,int p,int k){
	int s;                      //[l, a)内将被划分到左子树的元素数目
	int ss;                     //[a, b]内将被划分到左子树的元素数目
	double sss;                 // sss 记录区间[a, b]中小于第 k 大元素的值的和。 
	int mid=(l+r)>>1;
	if(l==r) return t[p].val[a];
	//区间端点点重合的情况,要单独考虑 !!!!!!
	if(a==l){
		s=0;
		ss=t[p].num[b];
		sss=t[p].sum[b];
	}
	else{
		s=t[p].num[a-1];
		ss=t[p].num[b]-s;
		sss=t[p].sum[b]-t[p].sum[a-1];
	}
	 // 进入左孩子,同时更新区间端点值 
	if(ss>=k){
		int la=l+s;
		int lb=l+s+ss-1;
		return query(la,lb,l,mid,p+1,k);
	}
	else{
		int la=mid+1+a-l-s;
		int lb=mid+1+b-l-s-ss;   //lb=la+b-a-num[b]=mid+1+a-l-s+b-a-s-ss
		Sum+=sss;
		return query(la,lb,mid+1,r,p+1,k-ss);
	}
}
int main() {

    #ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	#endif
	int n,m,k,a,b;
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=n;i++){
			scanf("%d",&arr[i]);
			t[0].val[i]=sorted[i]=arr[i];
		}
		sort(sorted+1,sorted+n+1);
		build(1,n,0);
		while(m--){
			scanf("%d%d%d",&a,&b,&k);
			printf("%d\n",query(a,b,1,n,0,k));
		}
	}
}                        



作者:Lin_disguiser 发表于2016/8/2 0:34:11 原文链接
阅读:66 评论:0 查看评论

Random Forest和Gradient Tree Boosting如何调参

$
0
0
作者:城东
链接:https://www.zhihu.com/question/34470160/answer/114305935
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

使用sklearn进行集成学习——实践
系列 目录

1 Random Forest和Gradient Tree Boosting参数详解
2 如何调参?
  2.1 调参的目标:偏差和方差的协调
  2.2 参数对整体模型性能的影响
  2.3 一个朴实的方案:贪心的坐标下降法
    2.3.1 Random Forest调参案例:Digit Recognizer
      2.3.1.1 调整过程影响类参数
      2.3.1.2 调整子模型影响类参数
    2.3.2 Gradient Tree Boosting调参案例:Hackathon3.x
      2.3.2.1 调整过程影响类参数
      2.3.2.2 调整子模型影响类参数
      2.3.2.3 杀一记回马枪
  2.4 局部最优解(温馨提示:看到这里有彩蛋!
3 总结
4 参考资料

1 Random Forest和Gradient Tree Boosting参数详解

  在sklearn.ensemble库中,我们可以找到Random Forest分类和回归的实现:RandomForestClassifier和RandomForestRegression,Gradient Tree Boosting分类和回归的实现:GradientBoostingClassifier和GradientBoostingRegression。有了这些模型后,立马上手操练起来?少侠请留步!且听我说一说,使用这些模型时常遇到的问题:

  • 明明模型调教得很好了,可是效果离我的想象总有些偏差?——模型训练的第一步就是要定好目标,往错误的方向走太多也是后退。
  • 凭直觉调了某个参数,可是居然没有任何作用,有时甚至起到反作用?——定好目标后,接下来就是要确定哪些参数是影响目标的,其对目标是正影响还是负影响,影响的大小。
  • 感觉训练结束遥遥无期,sklearn只是个在小数据上的玩具?——虽然sklearn并不是基于分布式计算环境而设计的,但我们还是可以通过某些策略提高训练的效率。
  • 模型开始训练了,但是训练到哪一步了呢?——饱暖思淫欲啊,目标,性能和效率都得了满足后,我们有时还需要有别的追求,例如训练过程的输出,袋外得分计算等等。

  通过总结这些常见的问题,我们可以把模型的参数分为4类:目标类、性能类、效率类和附加类。下表详细地展示了4个模型参数的意义:


# ★:默认值

  不难发现,基于bagging的Random Forest模型和基于boosting的Gradient Tree Boosting模型有不少共同的参数,然而某些参数的默认值又相差甚远。在《使用sklearn进行集成学习——理论》一文中,我们对bagging和boosting两种集成学习技术有了初步的了解。Random Forest的子模型都拥有较低的偏差,整体模型的训练过程旨在降低方差,故其需要较少的子模型(n_estimators默认值为10)且子模型不为弱模型(max_depth的默认值为None),同时,降低子模型间的相关度可以起到减少整体模型的方差的效果(max_features的默认值为auto)。另一方面,Gradient Tree Boosting的子模型都拥有较低的方差,整体模型的训练过程旨在降低偏差,故其需要较多的子模型(n_estimators默认值为100)且子模型为弱模型(max_depth的默认值为3),但是降低子模型间的相关度不能显著减少整体模型的方差(max_features的默认值为None)。

2 如何调参?

  聪明的读者应当要发问了:”博主,就算你列出来每个参数的意义,然并卵啊!我还是不知道无从下手啊!”

  参数分类的目的在于缩小调参的范围,首先我们要明确训练的目标,把目标类的参数定下来。接下来,我们需要根据数据集的大小,考虑是否采用一些提高训练效率的策略,否则一次训练就三天三夜,法国人孩子都生出来了。然后,我们终于进入到了重中之重的环节:调整那些影响整体模型性能的参数。

2.1 调参的目标:偏差和方差的协调

  同样在《使用sklearn进行集成学习——理论》中,我们已讨论过偏差和方差是怎样影响着模型的性能——准确度。调参的目标就是为了达到整体模型的偏差和方差的大和谐!进一步,这些参数又可分为两类:过程影响类及子模型影响类。在子模型不变的前提下,某些参数可以通过改变训练的过程,从而影响模型的性能,诸如:“子模型数”(n_estimators)、“学习率”(learning_rate)等。另外,我们还可以通过改变子模型性能来影响整体模型的性能,诸如:“最大树深度”(max_depth)、“分裂条件”(criterion)等。正由于bagging的训练过程旨在降低方差,而boosting的训练过程旨在降低偏差,过程影响类的参数能够引起整体模型性能的大幅度变化。一般来说,在此前提下,我们继续微调子模型影响类的参数,从而进一步提高模型的性能。

2.2 参数对整体模型性能的影响

  假设模型是一个多元函数F,其输出值为模型的准确度。我们可以固定其他参数,从而对某个参数对整体模型性能的影响进行分析:是正影响还是负影响,影响的单调性?

  对Random Forest来说,增加“子模型数”(n_estimators)可以明显降低整体模型的方差,且不会对子模型的偏差和方差有任何影响。模型的准确度会随着“子模型数”的增加而提高。由于减少的是整体模型方差公式的第二项,故准确度的提高有一个上限。在不同的场景下,“分裂条件”(criterion)对模型的准确度的影响也不一样,该参数需要在实际运用时灵活调整。调整“最大叶节点数”(max_leaf_nodes)以及“最大树深度”(max_depth)之一,可以粗粒度地调整树的结构:叶节点越多或者树越深,意味着子模型的偏差越低,方差越高;同时,调整“分裂所需最小样本数”(min_samples_split)、“叶节点最小样本数”(min_samples_leaf)及“叶节点最小权重总值”(min_weight_fraction_leaf),可以更细粒度地调整树的结构:分裂所需样本数越少或者叶节点所需样本越少,也意味着子模型越复杂。一般来说,我们总采用bootstrap对样本进行子采样来降低子模型之间的关联度,从而降低整体模型的方差。适当地减少“分裂时考虑的最大特征数”(max_features),给子模型注入了另外的随机性,同样也达到了降低子模型之间关联度的效果。但是一味地降低该参数也是不行的,因为分裂时可选特征变少,模型的偏差会越来越大。在下图中,我们可以看到这些参数对Random Forest整体模型性能的影响:

&amp;lt;img src=&quot;https://pic1.zhimg.com/1a914a6cb136d042d6d8efef78800c0c_b.jpg&quot; data-rawwidth=&quot;1781&quot; data-rawheight=&quot;1263&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;1781&quot; data-original=&quot;https://pic1.zhimg.com/1a914a6cb136d042d6d8efef78800c0c_r.jpg&quot;&amp;gt;  对Gradient Tree Boosting来说,“子模型数”(n_estimators)和“学习率”(learning_rate)需要联合调整才能尽可能地提高模型的准确度:想象一下,A方案是走4步,每步走3米,B方案是走5步,每步走2米,哪个方案可以更接近10米远的终点?同理,子模型越复杂,对应整体模型偏差低,方差高,故“最大叶节点数”(max_leaf_nodes)、“最大树深度”(max_depth)等控制子模型结构的参数是与Random Forest一致的。类似“分裂时考虑的最大特征数”(max_features),降低“子采样率”(subsample),也会造成子模型间的关联度降低,整体模型的方差减小,但是当子采样率低到一定程度时,子模型的偏差增大,将引起整体模型的准确度降低。还记得“初始模型”(init)是什么吗?不同的损失函数有不一样的初始模型定义,通常,初始模型是一个更加弱的模型(以“平均”情况来预测),虽说支持自定义,大多数情况下保持默认即可。在下图中,我们可以看到这些参数对Gradient Tree Boosting整体模型性能的影响:  对Gradient Tree Boosting来说,“子模型数”(n_estimators)和“学习率”(learning_rate)需要联合调整才能尽可能地提高模型的准确度:想象一下,A方案是走4步,每步走3米,B方案是走5步,每步走2米,哪个方案可以更接近10米远的终点?同理,子模型越复杂,对应整体模型偏差低,方差高,故“最大叶节点数”(max_leaf_nodes)、“最大树深度”(max_depth)等控制子模型结构的参数是与Random Forest一致的。类似“分裂时考虑的最大特征数”(max_features),降低“子采样率”(subsample),也会造成子模型间的关联度降低,整体模型的方差减小,但是当子采样率低到一定程度时,子模型的偏差增大,将引起整体模型的准确度降低。还记得“初始模型”(init)是什么吗?不同的损失函数有不一样的初始模型定义,通常,初始模型是一个更加弱的模型(以“平均”情况来预测),虽说支持自定义,大多数情况下保持默认即可。在下图中,我们可以看到这些参数对Gradient Tree Boosting整体模型性能的影响:

&amp;lt;img src=&quot;https://pic1.zhimg.com/01aa52e979435ceb8eb9ea889d6ed270_b.jpg&quot; data-rawwidth=&quot;2019&quot; data-rawheight=&quot;1298&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;2019&quot; data-original=&quot;https://pic1.zhimg.com/01aa52e979435ceb8eb9ea889d6ed270_r.jpg&quot;&amp;gt;2.3 一个朴实的方案:贪心的坐标下降法

  到此为止,我们终于知道需要调整哪些参数,对于单个参数,我们也知道怎么调整才能提升性能。然而,表示模型的函数F并不是一元函数,这些参数需要共同调整才能得到全局最优解。也就是说,把这些参数丢给调参算法(诸如Grid Search)咯?对于小数据集,我们还能这么任性,但是参数组合爆炸,在大数据集上,或许我的子子孙孙能够看到训练结果吧。实际上网格搜索也不一定能得到全局最优解,而另一些研究者从解优化问题的角度尝试解决调参问题。

坐标下降法是一类优化算法,其最大的优势在于不用计算待优化的目标函数的梯度。我们最容易想到一种特别朴实的类似于坐标下降法的方法,与坐标下降法不同的是,其不是循环使用各个参数进行调整,而是贪心地选取了对整体模型性能影响最大的参数。参数对整体模型性能的影响力是动态变化的,故每一轮坐标选取的过程中,这种方法在对每个坐标的下降方向进行一次直线搜索(line search)。首先,找到那些能够提升整体模型性能的参数,其次确保提升是单调或近似单调的。这意味着,我们筛选出来的参数是对整体模型性能有正影响的,且这种影响不是偶然性的,要知道,训练过程的随机性也会导致整体模型性能的细微区别,而这种区别是不具有单调性的。最后,在这些筛选出来的参数中,选取影响最大的参数进行调整即可。

  无法对整体模型性能进行量化,也就谈不上去比较参数影响整体模型性能的程度。是的,我们还没有一个准确的方法来量化整体模型性能,只能通过交叉验证来近似计算整体模型性能。然而交叉验证也存在随机性,假设我们以验证集上的平均准确度作为整体模型的准确度,我们还得关心在各个验证集上准确度的变异系数,如果变异系数过大,则平均值作为整体模型的准确度也是不合适的。在接下来的案例分析中,我们所谈及的整体模型性能均是指平均准确度,请各位留心。

2.3.1 Random Forest调参案例:Digit Recognizer

  在这里,我们选取Kaggle上101教学赛中的Digit Recognizer作为案例来演示对RandomForestClassifier调参的过程。当然,我们也不要傻乎乎地手工去设定不同的参数,然后训练模型。借助sklearn.grid_search库中的GridSearchCV类,不仅可以自动化调参,同时还可以对每一种参数组合进行交叉验证计算平均准确度。

2.3.1.1 调整过程影响类参数

  首先,我们需要对过程影响类参数进行调整,而Random Forest的过程影响类参数只有“子模型数”(n_estimators)。“子模型数”的默认值为10,在此基础上,我们以10为单位,考察取值范围在1至201的调参情况:

&amp;lt;img src=&quot;https://pic1.zhimg.com/e6e9835c6728e21be92ded15ccfd0b34_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic1.zhimg.com/e6e9835c6728e21be92ded15ccfd0b34_r.png&quot;&amp;gt;

# 左图为模型在验证集上的平均准确度,右图为准确度的变异系数。横轴为参数的取值。

  通过上图我们可以看到,随着“子模型数”的增加,整体模型的方差减少,其防止过拟合的能力增强,故整体模型的准确度提高。当“子模型数”增加到40以上时,准确度的提升逐渐不明显。考虑到训练的效率,最终我们选择“子模型数”为200。此时,在Kaggle上提交结果,得分为:0.96500,很凑合。

2.3.1.2 调整子模型影响类参数

  在设定“子模型数”(n_estimators)为200的前提下,我们依次对子模型影响类的参数对整体模型性能的影响力进行分析。

  对“分裂条件”(criterion)分别取值gini和entropy,得到调参结果如下:

&amp;lt;img src=&quot;https://pic2.zhimg.com/84d9d54b07be8ac80f57eb3b27d51fa5_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic2.zhimg.com/84d9d54b07be8ac80f57eb3b27d51fa5_r.png&quot;&amp;gt;

  显见,在此问题中,“分裂条件”保持默认值gini更加合适。

  对“分裂时参与判断的最大特征数”(max_feature)以1为单位,设定取值范围为28至47,得到调参结果如下:

&amp;lt;img src=&quot;https://pic1.zhimg.com/010125e1b49204b4df2e68002d74de88_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic1.zhimg.com/010125e1b49204b4df2e68002d74de88_r.png&quot;&amp;gt;

  “分裂时参与判断的最大特征数”的默认值auto,即总特征数(sqrt(784)=28)的开方。通过提升该参数,整体模型的准确度得到了提升。可见,该参数的默认值过小,导致了子模型的偏差过大,从而整体模型的偏差过大。同时,我们还注意到,该参数对整体模型性能的影响是近似单调的:从28到38,模型的准确度逐步抖动提升。所以,我们可考虑将该参数纳入下一步的调参工作。

  对“最大深度”(max_depth)以10为单位,设定取值范围为10到100,得到调参结果如下:

&amp;lt;img src=&quot;https://pic4.zhimg.com/ec90476aea44b38f9ec1ba5914163df3_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic4.zhimg.com/ec90476aea44b38f9ec1ba5914163df3_r.png&quot;&amp;gt;  随着树的深度加深,子模型的偏差减少,整体模型的准确度得到提升。从理论上来说,子模型训练的后期,随着方差增大,子模型的准确度稍微降低,从而影响整体模型的准确度降低。看图中,似乎取值范围从40到60的情况可以印证这一观点。不妨以1为单位,设定取值范围为40到59,更加细致地分析:  随着树的深度加深,子模型的偏差减少,整体模型的准确度得到提升。从理论上来说,子模型训练的后期,随着方差增大,子模型的准确度稍微降低,从而影响整体模型的准确度降低。看图中,似乎取值范围从40到60的情况可以印证这一观点。不妨以1为单位,设定取值范围为40到59,更加细致地分析:

&amp;lt;img src=&quot;https://pic3.zhimg.com/542741ba16bad6cbafa8e40dcedb17f2_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic3.zhimg.com/542741ba16bad6cbafa8e40dcedb17f2_r.png&quot;&amp;gt;

  有点傻眼了,怎么跟预想的不太一样?为什么模型准确度的变化在40到59之间没有鲜明的“规律”了?要分析这个问题,我们得先思考一下,少一层子节点对子模型意味着什么?若少的那一层给原子模型带来的是方差增大,则新子模型会准确度提高;若少的那一层给原子模型带来的是偏差减小,则新子模型会准确度降低。所以,细粒度的层次变化既可能使整体模型的准确度提升,也可能使整体模型的准确度降低。从而也说明了,该参数更适合进行粗粒度的调整。在训练的现阶段,“抖动”现象的发生说明,此时对该参数的调整已不太合适了。

  对“分裂所需的最小样本数”(min_samples_split)以1为单位,设定取值范围为2到11,得到调参的结果:

&amp;lt;img src=&quot;https://pic3.zhimg.com/4c819bbc6f436b9bb0139c395810cc3a_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic3.zhimg.com/4c819bbc6f436b9bb0139c395810cc3a_r.png&quot;&amp;gt;

  我们看到,随着分裂所需的最小样本数的增加,子模型的结构变得越来越简单,理论上来说,首先应当因方差减小导致整体模型的准确度提升。但是,在训练的现阶段,子模型的偏差增大的幅度比方差减小的幅度更大,所以整体模型的准确度持续下降。该参数的默认值为2,调参后,最优解保持2不变。

  对“叶节点最小样本数”(min_samples_leaf)以1为单位,设定取值范围为1到10,得到调参结果如下:

&amp;lt;img src=&quot;https://pic1.zhimg.com/f351a4c0ef101db74cecd6fd2dba00a0_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic1.zhimg.com/f351a4c0ef101db74cecd6fd2dba00a0_r.png&quot;&amp;gt;

  同“分裂所需的最小样本数”,该参数也在调参后,保持最优解1不变。

  对“最大叶节点数”(max_leaf_nodes)以100为单位,设定取值范围为2500到3400,得到调参结果如下:

&amp;lt;img src=&quot;https://pic4.zhimg.com/61cfbe4c8cb2e5d89f5df06e16a5b117_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic4.zhimg.com/61cfbe4c8cb2e5d89f5df06e16a5b117_r.png&quot;&amp;gt;

  类似于“最大深度”,该参数的增大会带来模型准确的提升,可是由于后期“不规律”的抖动,我们暂时不进行处理。

  通过对以上参数的调参情况,我们可以总结如下:

&amp;lt;img src=&quot;https://pic2.zhimg.com/b7b6a853be7ed798023ab5dbeab1c869_b.png&quot; data-rawwidth=&quot;611&quot; data-rawheight=&quot;197&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;611&quot; data-original=&quot;https://pic2.zhimg.com/b7b6a853be7ed798023ab5dbeab1c869_r.png&quot;&amp;gt;

  接下来,我们固定分裂时参与判断的最大特征(max_features)为38,在Kaggle上提交一次结果:0.96671,比上一次调参好了0.00171,基本与我们预期的提升效果一致。

  还需要继续下一轮坐标下降式调参吗?一般来说没有太大的必要,在本轮中出现了两个发生抖动现象的参数,而其他参数的调整均没有提升整体模型的性能。还是得老调重弹:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。在DR竞赛中,与其期待通过对RandomForestClassifier调参来进一步提升整体模型的性能,不如挖掘出更有价值的特征,或者使用自带特征挖掘技能的模型(正如此题,图分类的问题更适合用神经网络来学习)。但是,在这里,我们还是可以自信地说,通过贪心的坐标下降法,比那些用网格搜索法穷举所有参数组合,自以为得到最优解的朋友们更进了一步。

2.3.2 Gradient Tree Boosting调参案例:Hackathon3.x

  在这里,我们选取Analytics Vidhya上的Hackathon3.x作为案例来演示对GradientBoostingClassifier调参的过程。

2.3.2.1 调整过程影响类参数

  GradientBoostingClassifier的过程影响类参数有“子模型数”(n_estimators)和“学习率”(learning_rate),我们可以使用GridSearchCV找到关于这两个参数的最优解。慢着!这里留了一个很大的陷阱:“子模型数”和“学习率”带来的性能提升是不均衡的,在前期会比较高,在后期会比较低,如果一开始我们将这两个参数调成最优,这样很容易陷入一个局部最优解。下图中展示了这个两个参数的调参结果:

&amp;lt;img src=&quot;https://pic1.zhimg.com/555c664afe073d8c1fba35b75682efe8_b.png&quot; data-rawwidth=&quot;800&quot; data-rawheight=&quot;600&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;800&quot; data-original=&quot;https://pic1.zhimg.com/555c664afe073d8c1fba35b75682efe8_r.png&quot;&amp;gt;

# 图中颜色越深表示整体模型的性能越高

  在此,我们先直觉地选择“子模型数”为60,“学习率”为0.1,此时的整体模型性能(平均准确度为0.8253)不是最好,但是也不差,良好水准。

2.3.2.2 调整子模型影响类参数

  对子模型影响类参数的调整与Random Forest类似。最终我们对参数的调整如下:

&amp;lt;img src=&quot;https://pic3.zhimg.com/6f682e3ad42ec01fe70c8a6952223822_b.png&quot; data-rawwidth=&quot;646&quot; data-rawheight=&quot;113&quot; class=&quot;origin_image zh-lightbox-thumb&quot; width=&quot;646&quot; data-original=&quot;https://pic3.zhimg.com/6f682e3ad42ec01fe70c8a6952223822_r.png&quot;&amp;gt;

  到此,整体模型性能为0.8313,与workbench(0.8253)相比,提升了约0.006。

2.3.2.3 杀一记回马枪

  还记得一开始我们对“子模型数”(n_estimators)和“学习率”(learning_rate)手下留情了吗?现在我们可以回过头来,调整这两个参数,调整的方法为成倍地放大“子模型数”,对应成倍地缩小“学习率”(learning_rate)。通过该方法,本例中整体模型性能又提升了约0.002。

2.4 局部最优解

  目前来说,在调参工作中,广泛使用的仍是一些经验法则。Aarshay Jain对Gradient Tree Boosting总结了一套调参方法,其核心思想在于:首先对过程影响类参数进行调整,毕竟它们对整体模型性能的影响最大,然后依据经验,在其他参数中选择对整体模型性能影响最大的参数,进行下一步调参。这种方法的关键是依照对整体模型性能的影响力给参数排序,然后按照该顺序对的参数进行调整。如何衡量参数对整体模型性能的影响力呢?基于经验,Aarshay提出他的见解:“最大叶节点数”(max_leaf_nodes)和“最大树深度”(max_depth)对整体模型性能的影响大于“分裂所需最小样本数”(min_samples_split)、“叶节点最小样本数”(min_samples_leaf)及“叶节点最小权重总值”(min_weight_fraction_leaf),而“分裂时考虑的最大特征数”(max_features)的影响力最小。

  Aarshay提出的方法和贪心的坐标下降法最大的区别在于前者在调参之前就依照对整体模型性能的影响力给参数排序,而后者是一种“很自然”的贪心过程。还记得2.3.2.1小节中我们讨论过“子模型数”(n_estimators)和“学习率”(learning_rate)的调参问题吗?同理,贪心的坐标下降法容易陷入局部最优解,对Random Forest调参时会稍微好一点,因为当“子模型数”调到最佳状态时,有时就只剩下诸如““分裂时参与判断的最大特征数”等Aarshay认为影响力最小的参数可调了。但是,对Gradient Tree Boosting调参时,遇到局部最优解的可能性就大得多。

  Aarshay同样对Hackathon3.x进行了调参试验,由于特征提取方式的差异,参数赋值相同的情况下,本文的整体模型性能仍与其相差0.007左右(唉,不得不再说一次,特征工程真的很重要)。首先,在过程影响类参数的选择上,Aarshay的方法与贪心的坐标下降法均选择了“子模型数”为60,“学习率”为0.1。接下来,Aarshay按照其定义的参数对整体模型性能的影响力,按序依次对参数进行调整。当子模型影响类参数确定完成后,Aarshay的方法提升了约0.008的整体模型性能,略胜于贪心的坐标下降法的0.006。但是,回过头来继续调试“子模型数”和“学习率”之后,Aarshay的方法又提升了约0.01的整体模型性能,远胜于贪心的坐标下降法的0.002。

  Aarshay的方法和贪心的坐标下降法在哪里分道扬镳的呢?前者先对“最大深度”(max_depth)进行调整,后对“叶节点最小样本数”(min_samples_leaf)进行调整;而后者恰恰相反!

  诶!诶!诶!少侠请住手!你说我为什么要在这篇博文中介绍这种“无用”的贪心的坐标下降法?首先,这种方法很容易凭直觉就想到。人们往往花了很多的时间去搞懂模型的参数是什么含义,对整体模型性能有什么影响,搞懂这些已经不易了,所以接下来很多人选择了最直观的贪心的坐标下降法。通过一个实例,我们更容易记住这种方法的局限性。除了作为反面教材,贪心的坐标下降法就没有意义了吗?不难看到,Aarshay的方法仍有改进的地方,在依次对参数进行调整时,还是需要像贪心的坐标下降法中一样对参数的“动态”影响力进行分析一下,如果这种影响力是“抖动”的,可有可无的,那么我们就不需要对该参数进行调整。

3 总结

  在这篇博文中,我一反常态,花了大部分时间去试验和说明一个有瑕疵的方案。数据挖掘的工作中的方法和技巧,有很大一部分暂时还未被严谨地证明,所以有很大部分人,特别是刚入门的小青年们(也包括曾经的我),误以为其是一门玄学。实际上,尽管没有被严谨地证明,我们还是可以通过试验、分析,特别是与现有方法进行对比,得到一个近似的合理性论证。

  另外,小伙伴们你们有什么独到的调参方法吗?请不要有丝毫吝啬,狠狠地将你们的独门绝技全释放在我身上吧,请大胆留言,残酷批评!

4 参考资料
  1. 《使用sklearn进行集成学习——理论》
  2. Complete Guide to Parameter Tuning in Gradient Boosting (GBM) in Python

  3. 坐标下降法
  4. Digit Recognizer
  5. Hackathon3.x
作者:Bryan__ 发表于2016/8/2 0:39:22 原文链接
阅读:70 评论:0 查看评论

Android (非常重要)如何实现自定义的View

$
0
0

一个设计的好的自定义view,有着丰富的特性和简单的接口。它可以有效的使用cpu和内存。

所以一个自定义view必须满足以下几点:

  1. 符合Android的规范
  2. 提供可以和Android XML 布局相适应的 自定义的 属性
  3. 可以发送访问事件
  4. 兼容不同的android平台

android 框架提供了一些基本的类和XMl标签来帮助你创建一个合适的自定义view, 那么首先

如何创建一个View的类 (Class)

第一步,继承View类

所有的view类第一都是继承与view类的,你的自定义View可以直接继承 view,你也可以继承一个 view 的子类,比如:Button

为了让Android Studio可以和你的 View 交互,你的类中 至少要实现一个 * 把context 和 AttributeSet 作为参数的构造函数 *,这个构造函数可以使得 layout editor 可以编辑一个你的View的实例

class PieChart extends View {
    public PieChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

第二步,定义自定义的属性

需要在 xml 文件里 明确的用 attr 元素 指出 自定义控件 的表现和行为,一个良好设计的 view 是可以通过 xml 文件 添加 和 改变的,你需要如下几步:

添加在 res/values/attrs.xml 文件中

  • 在resource元素的<declare-styleable>中,为你的view自定义属性
  • 特别指明你的xml文件中属性的值
  • 可以在运行时重写属性值
  • 在你的view中写上属性值

如下:

<resources>
   <declare-styleable name="PieChart">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>

这个代码有两个 属性, showTextlabelPosition 属于一个 名字为 Piechat 的 styleable entity

依照惯例来说,这个自定义类的 名字应该和 styleable entity的名字相同

一旦你定义了自定义属性,你就可以在 layout xml 文件中使用,如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
 <com.example.customviews.charting.PieChart
     custom:showText="true"
     custom:labelPosition="left" />
</LinearLayout>

第三步,应用自定义属性

当一个view在layout文件中添加之后,这个xml文件中所有的 属性,都可以被resource bundle 读取并以 AttributeSet 的形式,传入 view 的构造函数中

如下代码:

public PieChart(Context context, AttributeSet attrs) {
   super(context, attrs);
   TypedArray a = context.getTheme().obtainStyledAttributes(
        attrs,
        R.styleable.PieChart,
        0, 0);

   try {
       mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
       mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
   } finally {
       a.recycle();
   }
}

可以看到,我们并没有直接的从 AttributeSet 对象中获取属性,使用obtainStyledAttribute() 方法,获取到一个 TypeArray 中。这样的好处在于,可以避免一些缺陷的发生,比如资源引用是不可用的

第三步 添加属性和事件

属性虽然好,但是在 view 初始化的时候,属性都是只读的。为了提供动态行为,每个属性必须对应一个 getter 和 一个setter 函数, 如下:

public boolean isShowText() {
   return mShowText;
}

public void setShowText(boolean showText) {
   mShowText = showText;
   invalidate();
   requestLayout();
}

在设置中调用的两个函数各有什么用呢?

重要!

一旦你的属性改变了,说明你的view外观要改变,这时要调用 invalidate() 使view无效化,然后你需要 requestLayout 请求 新的 Layout ,重绘制界面

自定义view 应该 同样的 支持事件监听器 ,比如,PieChart 就支持一个 自定义事件 OnCurrentItemChanged 来修改监听者,使得用户可以循环到下一个 pie sllice上


自定义绘制 Custom Drawing

自定义控件最重要的部分大概就是 外观了,根据你的app需求,自定义 绘制 可以很简单也可以很复杂。继续了解吧!

重写 onDraw() 方法

绘制一个 自定义view 最重要的部分就是重写 onDraw() 方法了。onDraw() 方法的参数是一个 Canvas 对象,可以用来绘制它自己。

在你调用任何Canvas中的绘制方法之前,创建一个 Paint 对象非常重要,因为

Canvas 决定画什么,而 Paint 决定怎么画(比如Canvas决定你想在屏幕上画的形状,而Paint定义颜色,字体,风格,阴影等)

代码如下:

private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }

   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);

   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8,        BlurMaskFilter.Blur.NORMAL));

   ...

提前创建好这些 Paint 对象是非常重要的优化,因为Views重绘非常的平凡,但是很多drawing 对象初始化过程是 花销很多的, 在你的 onDraw() 方法中来创建drawing对象的话,那么会降低性能,UI变卡。 最好提前全部创建好

处理 布局事件 Layout Events (测绘)

为了绘制合适,在绘制以前你得知道 大小尺寸 是多少。复杂的view需要执行 多次 布局 计算,永远不要猜你的view有多大,就算只有一个app在使用你的view,这个app也必须处理不同的屏幕尺寸,方向。

虽然View有很多处理测量的方法,但是大部分都需要重写。如果你的view不需要特别的控制大小,那么你需要重写一个一个 方法 onSizeChanged()

不是在绘制时计算,而是在改变时计算
onSizeChanged() 方法在你的View第一次被 指定一个尺寸的时候被调用,如果你的view的尺寸改变了,那么计算位置,面积等其他的值,都在这个 onSizeChanged() 方法里执行,而不是每次 绘制都重新计算一遍。(在 piechart案例中,就是在onSizeChanged方法里计算矩形边际的改变等)

默认是计算padding

当你指定一个 size 的时候,layout manager 是假定你view的所有padding是包含在内的,在你计算view的尺寸的时候,你必须处理这些 padding的值,如下:

// Account for padding
       float xpad = (float)(getPaddingLeft() + getPaddingRight());
       float ypad = (float)(getPaddingTop() + getPaddingBottom());

       // Account for the label
       if (mShowText) xpad += mTextWidth;

       float ww = (float)w - xpad;
       float hh = (float)h - ypad;

       // Figure out how big we can make the pie.
       float diameter = Math.min(ww, hh);

如果你的view的自定义程度要求很高!,那么就实现 onMeasure() 方法。这个方法的参数 View.MeasureSpec 就是父view 想让 你的view显示的大小

这些值是存在 包装过的 integer 对象中,作为优化,你可以使用View.MeasureSpec 静态方法将 这些 信息 解出来,如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on our minimum
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);

   // Whatever the width ends up being, ask for a height that would let the pie
   // get as big as it can
   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);

   setMeasuredDimension(w, h);
}

这段代码目的在于将 Pie的大小设为和 lable 的一样大,值得注意的几个部分:

  • 计算考虑到了 view 的 padding
  • resolveSizeAndState() 方法用来创造一个 final 的 宽 和 高 的 值将,将 view 期望的值 和 传入 onMeasure 方法的值比较,返回一个合适的
  • onMeasure() 方法是没有返回值的,但是强制性的 调用 方法setMeasureDimension() 来传递结果

绘制!

一旦绘图对象创建好了,measure也定义好了。那么就可以实现 onDraw() 方法了。每一个view实现 onDraw() 方法都是不同的,但是有一些相同的操作

  • 绘制文本使用 DrawtText() ,设置字体使用 setTypeface(),设置字体颜色使用 setColor()
  • 绘制一些特定的图形,如 drawRect() drawArc() 等,填充和外边都是调用 setStyle 设置
  • 更加复杂的形状可以用 drawPath() 绘制
  • 通过创建一个 LinearGradient 对象来控制 渐变的填充,调用 setShader() 方法来使用你的 LinearGradient 填充形状
  • 绘制bitmap 使用 drawBitmap()
protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   // Draw the shadow
   canvas.drawOval(
           mShadowBounds,
           mShadowPaint
   );

   // Draw the label text
   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);

   // Draw the pie slices
   for (int i = 0; i < mData.size(); ++i) {
       Item it = mData.get(i);
       mPiePaint.setShader(it.mShader);
       canvas.drawArc(mBounds,
               360 - it.mEndAngle,
               it.mEndAngle - it.mStartAngle,
               true, mPiePaint);
   }

   // Draw the pointer
   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}

创建view的接口

绘制出UI只是创建一个自定义view的一部分,还有一部分就是让你的View响应用户的输入。

处理 输入的手势

就像其他的UI框架一样,android的支持输入事件模型。用户的行为会转为开大回调的事件 。 然后你重写回调就可以 自定义 你的应用应该如何对用户的 行为作出反应了。

android系统中最常见的输入事件就是 touch 事件了,重写 onTouchEvent(MotionEvent) 来 处理事件

 @Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

但是touch事件是 一般是没有什么用的,手势交互发展到现在有多种多样,比如 tapping 轻敲,pulling 下拉,pushing 推……等等

为了将 未加工 的touch 转为 加工过的手势,android 提供了 GestureDetector

为了构造一个 GestureDetector ,你需要两个参数

一个继承了 GestureDetector.SimpleOnGestureListener (或者 GestureDetector.OnGestureListener)的实例,作为手势的监听器。还有我们的自定义类

class mListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());

不管你是否实现 GestureDetector.SimpleOnGestureListener ,实现的 onDown() 方法一定要 return true。这个步骤是必要的,如果你返回一个 false,那么系统会认为你想忽略剩下的手势 手势的判定,那么其他的手势就再也无法取得回调了。

只有当你真的确定 只有某个手势 的时候,你才可以如上使用

一旦你创建好了一个 GestureDetector 那么你就可以在 onTouchEvent 中解析这个touch事件了

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = mDetector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

当你传入的这个 touch event 并不能解析为 手势的任何一种,那么你就可以执行你自定义的手势探测代码。

android 还提供了一些 类 比如 Scroller 可以让实现一些物理的效果 比如滑动

几种优化view的思路:

  1. 不要在onDraw() 方法和 动画running的时候分配内存,这样垃圾回收会造成卡顿
  2. 尽量不要调用 onDraw() 方法,大部分对 onDraw() 调用都是 invalidate() ,所以尽量不使用 invalidate() 方法
  3. 遍历 layout 开销也是巨大的,requestLayout() 的每次调用,Android UI 系统都要遍历整个 view 层来找到 整个view所需的 空间大小。如果产生冲突,可能还得多次遍历。所以尽可能的让你的UI分布扁平
  4. 如果你是一个复杂的UI,那么可以考虑 用 ViewGroup 来表示其布局
作者:qq_28057541 发表于2016/8/2 0:56:50 原文链接
阅读:74 评论:0 查看评论

终端,作业控制与守护进程

$
0
0

进程组

   概念:一个或多个进程的集合。

   每一个进程除了有一个进程ID外,还属于一个进程组,同时也只能属于一个进程组。每个进程组都有一个唯一的进程组ID,且都可以有一个组长进程。一般在进程组中,第一个进程是组长进程。

   为啥要创建进程组呢?为了方便对进程进行管理。假设要完成一个任务,需要同时并发10个进程,当用户处于某种原因要终止这个任务时,如若没有进程组,就需要手动的一个一个的去杀死这10个进程,并且严格按照进行间的关系顺序,否则会打乱进程间的关系,有了进程组,就可以将这10个进程设置一个进程组,他们共有一个组号(pgrp),并且选取一个进程作为组长,(通常选取“辈分”最高的那个,通常该进程的ID就是该进程组的ID)。现在就可以通过杀死整个进程组来关闭这10个进程。组长进程可以创建进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,则该组进程就存在,这与组长进程是否终止无关。


作业

   shell分前后台来控制的是作业或进程组,不是进程。一个前台作业可以由多个进程组成,一个后台可以由多个进程组成。

   作业控制shell可以运行一个前台作业和任意多个后台作业。  

   1、 与作业控制有关的信号:

     我们cat为例(把他放在后台,从终端读)

  (1)由于cat需要读标准输入(也就是终端输入),而后台进程是不能读终端输入的,因此内核发SIGTTIN信号给进程, 该信号的默认处理动作是使进程停止。

 

    [liu153@liu153 7-31_class16]$ cat &

    [1] 895

    [liu153@liu153 7-31_class16]$ //再嗯回车

    

    [1]+  Stopped                 cat

    [liu153@liu153 7-31_class16]$


       (2)jobs命令:查看当前前后前后台有哪些作业

   (3)fg命令:可以将某个作业提至前台运行:

     a、如果该作业的进程组正在后台运行则提至前台运行;

     b、如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续行。

     参数%1表示将第1个作业提至前台运行。

     cat提到前台运行后,挂起等待终端输入,当 输入hello并回车后,cat打印出同样的一行,然后继续挂起等待输入。紧接着, 如果输入Ctrl-Z则向所有前 台进程发SIGTSTP 信号,该信号的默认动作是使进程停止,cat继续以后台作业的形式存在。

    wKiom1eeGCDAiDgdAACD8sRv8ho255.png-wh_50     

        (4)bg命令:可以让某个停止的作业在后台继续运行。也需要给该作业的进程组的每个进程发SIGCONT信号。cat进程继续运行,又要读终端输入,然而它在后台不能读终端输入,所以又收到SIGTTIN信号而停止。

    wKioL1eeGG_AEMOOAAAYbjOmneU583.png-wh_50

  2、给一个停止的进程发SIGTERM与SIGKILL信号的区别:

   (1)kill 命令给一个停止的进程发送SIGTERM信号时并不会立即被处理,而是等到合适的时候处理,默认处理动作是终止进程。

    wKioL1eeGM6DcLAHAADbAAhvSB4284.png-wh_50

  (2)SIGKILL信号既不能被阻塞也不能被忽略 ,也不能用自定义函数捕捉 ,只能按系统的默认动作立刻处理(SIGSTOP 信号也与此类似)。(这样保证了不管什么样的程都能用 SIGKILL终止或者用SIGSTOP停止, 当系统出现异 常时管理员总是有办法杀掉有问题的进程或者暂时停掉怀疑有问题的进程。)

    wKiom1eeGTKiwNJPAACbeNGUS8A634.png-wh_50


作业与进程组的区别:

  如果一个作业中的某个进程又创建了一个子进程,该子进程不属于作业。一旦作业运行结束,shell就把它提到前台,如果原来前台进程还存在,它自动变为后台进程组。


会话

  概念:一个或多个进程组的集合,但只能有一个前台进程组。

   控制进程:建立与控制终端连接的会话首进程。每个会话都有一个会话首领(leader)即创建会话的进程。

   sys_setsid()调用能创建一个会话。

   注意:只有当前进程不是进程组的组长时,才能创建一个新的会话。

   一次会话中应该包括:一个控制进程,一个前台进程和任意多个后台进程。


终端

   控制终端:会话的领头进程打开一个终端,之后,该终端就成为该会话的控制终端。一个会话只能有一个控制终端。

   进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端。一般而言,当用户在某个终端上登录时,一个新的会话就开始了。进程组由组中的领头进程标识,领头进程的进程标识符就是进程组的组标识符。类似地,每个会话也对应有一个领头进程

   同一会话中的进程通过该会话的领头进程和一个终端相连,该终端作为这个会话的控制终端。一个会话只能有一个控制终端,而一个控制终端只能控制一个会话。用户通过控制终端,可以向该控制终端所控制的会话中的进程发送键盘信号。

   当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作


查看终端设备

测试代码:

#include<stdio.h>

#include<unistd.h>

int main()

{

       printf("fd :%d->%s\n",0,ttyname(0));

       printf("fd :%d->%s\n",1,ttyname(1));

       printf("fd :%d->%s\n",2,ttyname(2));

       return 0;

}


终端1运行结果:

[liu153@liu153 7-31_class16]$ ./a.out

fd :0->/dev/pts/2

fd :1->/dev/pts/2

fd :2->/dev/pts/2

终端2运行结果:

[liu153@liu153 7-31_class16]$ ./a.out

fd :0->/dev/pts/0

fd :1->/dev/pts/0

fd :2->/dev/pts/0

[liu153@liu153 7-31_class16]$

终端3运行结果:

[liu153@liu153 7-31_class16]$ ./a.out

fd :0->/dev/pts/3

fd :1->/dev/pts/3

fd :2->/dev/pts/3

[liu153@liu153 7-31_class16]$


守护进程(精灵进程)

  是运行在后台的一种特殊进程,独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程,Linux的大多数服务器就是用守护进程实现的。 守护进程完成许多系统任务。大多数守护进程以d结尾,凡是中括号括起来的都是内核线程。

root         2  0.0  0.0      0     0 ?        S    Jul27   0:00 [kthreadd]

root        17  0.0  0.0      0     0 ?        S    Jul27   0:00 [kacpid]

root        22  0.0  0.0      0     0 ?        S    Jul27   0:00 [ksuspend_usbd]

root        23  0.0  0.0      0     0 ?        S    Jul27   0:04 [khubd]

root        24  0.0  0.0      0     0 ?        S    Jul27   0:00 [kseriod]

root        28  0.0  0.0      0     0 ?        S    Jul27   0:00 [khungtaskd]

root        30  0.0  0.0      0     0 ?        SN   Jul27   0:00 [ksmd]

root        38  0.0  0.0      0     0 ?        S    Jul27   0:00 [pciehpd]

root        40  0.0  0.0      0     0 ?        S    Jul27   0:00 [kpsmoused]

root        72  0.0  0.0      0     0 ?        S    Jul27   0:00 [kstriped]

root      1103  0.0  0.0      0     0 ?        S    Jul27   0:00 [kauditd]

root      2001  0.0  0.0      0     0 ?        S<   Jul27   0:00 [krfcommd]


守护进程的特性:

   1、后台运行(最重要的)

    2、守护进程必须与其运行前的环境隔离开来。

    3、启动方式有其特殊之处:可以在linux系统启动时从启动脚本/etc/rc.d中启动,可以在作业规划进程cround启动。还可以由用户终端(通常是shell)执行。


后台进程与守护进程的区别

  1、后台进程与特定终端关联,与会话紧密相联。

  2、守护进程是后台进程的一种,与终端无关


创建守护进程

   1、调用umask将文件模式创建屏蔽字段设置为0

   2、调用fork函数,父进程退出。

    原因:1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。

        2)保证子进程不是一个 进程组的组长进程。

   3、调用setsid创建一个新会话。

      setsid会导致:

        1)调用进程成为新会话的首进程。

        2)调用进程成为一个进程组的组长进程 。

        3)调用进程没有控制终端。(再次fork一次,保证 daemon进程,之后不会打开tty设备)      

   4、将当前工作目录更改为根目录。

   5、关闭不在需要的文件描述符。

   6、 其他:忽略SIGCHLD信号。


创建守护进程代码:

测试1:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include<signal.h>
#include <fcntl.h>

void my_daemon()
{
    umask(0);// 设置文件掩码为0

    pid_t id = fork();
    if (id == 0)
    {
        // child
        setsid();//  设置 新会话

        chdir("/");// 更换 目录

        close(0);
        close(1);
        close(2);

        signal(SIGCHLD,SIG_IGN);// 注册子进程退出忽略信号
    }
    else
    {
        sleep(14);
        exit(0);// 终止父进程
    }

    close(0);// 关闭标准输入
    int fd0 = open("dev/null", O_RDWR);// 重定向所有标准输出、 错误到/dev/null
    dup2(fd0, 1);
    dup2(fd0, 2);
}
int main()
{
    my_daemon();
    while(1);

}


测试2;

#include<stdio.h>

#include<stdlib.h>

#include<signal.h>

#include<unistd.h>

#include<fcntl.h>

#include<sys/stat.h>

void create_daemon(void)

{

    int i;

    int fd0;

    pid_t pid;

    struct sigaction sa;

    umask(0);//设置文件掩码为0

    if(pid = fork() < 0){

    }else if(pid != 0){

        exit(0);//第一次fork()终止父进程

    }

    setsid();//设置新会话

    sa.sa_handler = SIG_IGN;

    sigemptyset(&sa.sa_mask);

    sa.sa_flags = 0;

    if(sigaction(SIGCHLD,&sa,NULL) < 0){//注册子进程退出忽略信号

        return ;

    }

    if(pid = fork() < 0){//为何要fork两次?->再次fork,终止子进程,保证孙子进程不是话首进程,从而保证后续不会再和其他终端关联

        printf("fork error !\n");

        return ;

    }else if(pid != 0){

    exit(0);

    }

    if(chdir("/")<0){//更改工作目录到根

        printf("child dir error\n");

        return ;

    }

    close(0);

    fd0 = open("/dev/null",O_RDWR);//关闭标准输入,重定向所有标准(输入输出错误)到/dev/null

    dup2(fd0,1);

    dup2(fd0,2);

}

int main()

{

    create_daemon();

    while(1)

    {

        sleep(1);

    }

    return 0;

}

运行监视:

    wKiom1eeG0ehYqZaAABKGoO4fpg693.png-wh_50

关闭标准输入,重定向所有标准(输入输出错误)到/dev/null

    wKioL1eeG1fBLtM2AAAypRdpyKI759.png-wh_50


本文出自 “缘去即成幻” 博客,请务必保留此出处http://liu153.blog.51cto.com/10820414/1832671

作者:Comedly 发表于2016/8/2 1:13:24 原文链接
阅读:61 评论:0 查看评论

Solr之——SolrCloud5.2.1+tomcat7+zookeeper3.4.6环境搭建

$
0
0

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/52090099

一、软件环境配置

环境:Windows 7

tomcat-7.0.56 下载地址:http://tomcat.apache.org/download-70.cgi

Zookeepr v3.4.6 下载地址:http://apache.fayea.com/zookeeper/

solr-5.2.1 下载地址: http://archive.apache.org/dist/lucene/solr/

二、部署过程

1、部署单机版Tomcat7 +Solr5.2.1

第一步:在D盘根目录下建立solrCloud目录。并把apache-tomcat-7.0.56.zip解压到solrCloud目录下,复制apache-tomcat-7.0.56重命名为tomcat7_ser1

如图所示:

第二步,在solrCloud目录下新建solr_home_1 文件夹如下图,并在目录下,创建 home  和 server 文件夹

第三步:把solr-5.2.1\example\example-DIH\solr指定文件复制到solr_home_1\home目录下,如下图


第四步:把solr-5.2.1\server\webapps\solr.war复制到solr_home_1\server目录下。solr.war解压,解压后将solr.war文件删除,如下图



第五步:修改D:/solrCloud/solr_home_1/server/solr/WEB-INF目录下的web.xml文件,里面修改solr/homevalue

<env-entry>
   <env-entry-name>solr/home</env-entry-name>
   <env-entry-value>D:/solrCloud/solr_home_1/home</env-entry-value>
   <env-entry-type>java.lang.String</env-entry-type>
</env-entry>

如下图:


第六步:因为我们未将solr服务放到tomcat webapps下面,所以需要修改D:/solrCloud/tomcat7_ser1/conf/server.xml指向solr服务路径。

在Host节点下加入如下代码:

<Context path="/solr" docBase="D:/solrCloud/solr_home_1/server/solr">
	<Environment name="/solr/home" type="java.lang.String" value="D:/solrCloud/solr_home_1/home" override="true"/>
</Context>
如下图:

第七步:将solr-5.2.1\distsolr-5.2.1\server\lib\ext目录下的jar包复制到solr_home_1\server\solr\WEB-INF\lib



第八步:现在单机版本的就已经配置OK了。启动tomcat,访问http://localhost:8080/solr


2、配置多Tomcat+solr同时运行

第一步:
把tomcat7_ser1复制2份,分别命名tomcat7_ser2,tomcat7_ser3;
把solr_home_1复制2份,分别命名solr_home_2,solr_home_3。
目录结构如下


第二步:修改D:\solrCloud\tomcat7_ser*\conf\server.xml配置

1、 修改 tomcat7_ser* 分别对应 各自solrhome* 和 solr 服务 。
如下图:tomcat7_ser1对应solr_home_1、2对应2 、3对应3

2、  为了三个tomcat能够在一台机器上同时启动,需要在server.xml修改tomcat的端口信息。修改方案如下:


第三步:配置各个tomcatsolr之间的关系,修改D:\solrCloud\solr_home_*\server\solr\WEB-INF目录下的web.xml文件(对应关系)

<env-entry>
   <env-entry-name>solr/home</env-entry-name>
   <env-entry-value>D:/solrCloud/solr_home_1/home</env-entry-value>
   <env-entry-type>java.lang.String</env-entry-type>
</env-entry>

第四步:验证修改是否成功,依次启动三个Tomcat。并在浏览器输入如下的URL

http://localhost:8080/solr/

http://localhost:8081/solr/

http://localhost:8082/solr/

如果都能正常访问到solradmin页面,那么说明配置是成功的。否则就需要检查哪里错了或者遗漏了。

3、配置ZooKeeper集群

这部分的内容与前面tomcat+solr是没有关联的

第一步:解压zookeeper-3.4.6.tar.gzD:\solrCloud目录,重命名为zookeeper-1

 如图所示:


第二步:把D:\solrCloud\zookeeper-1\conf\目录下的zoo_sample.cfg修改为zoo.cfg。并写入如下的配置参数:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=D:/solrCloud/zookeeper-1/data
dataLogDir=D:/solrCloud/zookeeper-1/datalog
clientPort=2181
#autopurge.purgeInterval=1
server.1=127.0.0.1:2881:3881
server.2=127.0.0.1:2882:3882
server.3=127.0.0.1:2883:3883
如下图所示:


并且按照配置创建相应的datadatalog目录。如果不不创建目录是无法正常启动的


data目录中创建文件myid(不需要后缀名),在myid文件中写入数字1并保存退出


第三步:把zookeeper-1复制2份,分别命名为zookeeper-2zookeeper-3。然后修改各个zookeeper-*conf目录下zoo.cfgdataDirdataLogDirclientPort。修改方案如下:


并且修改每个data目录下的myid文件中的内容。zookeeper-11zookeeper-22zookeeper-33

如下图所示:


第四步:启动三个zookeeper。并验证是否配置成功。目录结构以及启动目录如下图:


注意:连接第一台时有异常信息,不用管,等都连接起来就没有异常了。

进入cmd 命令,输入:netstat-ano|findstr 2181 查看端口是否启用


4、搭建Tomcat7+ solr-5.2.1+zookeeper3.4.6集群

前面tomcat+solr能够启动和访问了,而且zookeeper也能启动成功了。接下来就需要把他们关联起来。大家看那么多贴子应该知道(当然不知道也要记住这个知识点):solrcloud的所有配置需要zookeeper统一管理

第一步:将需要的配置库集中放到一个目录中。

D:\solrCloud下创建cloud_conf文件夹作为配置库,如下图:


solr-5.2.1\server\solr\configsets\sample_techproducts_configs\conf下配置文件复制到cloud_conf文件夹中。(也可以使用solr-5.2.1\example\example-DIH\solr\solr\conf中的配置文件,我这里复制的就是solr-5.2.1\example\example-DIH\solr\solr\conf中的配置文件)


第二步:配置zookeeper路径

D:\solrCloud\tomcat7_ser1\bin\catalina.bat文件中,我是在 setlocal上一行添加:

set JAVA_OPTS=-Dbootstrap_confdir=D:/solrCloud/cloud_conf -Dcollection.configName=myconf -DzkHost=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183 -DnumShards=3
如下图:


配置参数解释:

-DzkHost是用来指定zookeeper服务器的ip和端口。

-Dnumshareds=3用来指定当前集群中分片数为3

-Dcollection.configName  是在指定你的配置文件上传到zookeeper后的名字,省略这个参数将导致配置名字为默认的“configuration1

-Dbootstrap_confdir zooKeeper需要准备一份集群配置的副本,所以这个参数是告诉SolrCloud这些配置是放在哪里。同时作为整个集群共用的配置文件。可以看作是第一份solr配置。因为后续我们可以通过上传,来实现多collection

其余tomcat7_ser* catalina.bat设置

set JAVA_OPTS= -DzkHost=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183

第三步:  因为配置由zookeeper统一管理了,所以home下面的solr配置就不起作用了。删除D:\solrCloud\solr_home_*\home\solr文件夹, 修改配置solr.xml,如下图


同时,solr.xml改成如下所示:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<solr>
  <solrcloud>
    <str name="host">${host:}</str>
    <int name="hostPort">${jetty.port:8080}</int>
    <str name="hostContext">${hostContext:solr}</str>
    <bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool>
    <int name="zkClientTimeout">${zkClientTimeout:30000}</int>
    <int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:600000}</int>
    <int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:60000}</int>
  </solrcloud>
  <shardHandlerFactory name="shardHandlerFactory"
    class="HttpShardHandlerFactory">
    <int name="socketTimeout">${socketTimeout:600000}</int>
    <int name="connTimeout">${connTimeout:60000}</int>
  </shardHandlerFactory>
</solr>

hostPort 修改为对应的tomcat端口号 8080 80818082

第四步:重启tomcat。访问任何一个端口都成,比如: http://localhost:8081,能正常访问,且菜单中,出现了Cloud说明我们已经部署成功了




三、问与答

问题一:点击Cloud可以看到Cloud还没有形成分部式分支的图片。是不是有问题?

答:这说明我这现在服务器是干净的,需要我们创建Core来实现是属性那个Collection、那个Shard。当我们创建完Core后分支图就会展现出来了。

我们可以看下Cloud 的 Tree 下面就可以看到我们上传的配置文件,我上传时 -Dcollection.configName=myconf 所以这里也是显示myconf


问题二: 如何创建Core?

答:输入 name , instanceDir, collection, shard,点击AddCore。 说明下,当collection 名称样的core 索引是通用的。(反之collection不一样则索引之前不可相互访问了)

即 你在 new_core 下面创建的索引,在new_core1下面也是能够查询到的。 当我们创建完core之后,分支图就已经出现了。如下:



问题三:如何上传另一份collection

答: 在window cmd模式

java -classpath .;D:\solrCloud\solr_home_1\server\solr\WEB-INF\lib\* org.apache.solr.cloud.ZkCLI -cmd upconfig -zkhost 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183 -confdir D:\solrCloud\cloud-config -confname mycollection
命令说明:
上传(upconfig) 到指定的服务(-zkhost 27.0.0.1:2181, 127.0.0.1:2182, 127.0.0.1:2183)  配置文件路径(-confdir):D:\solrCloud\cloud-config。 配置名称(-confname)为: mycollection
注: 要添加 D:\solrCloud\solr_home_1\server\solr\WEB-INF\lib\*  cmd才能识别  org.apache.solr.cloud.ZkCLI 的命令

问题四:如何删除服务器配置文件

答:在window cmd 模式

1、指定文件删除:

java -classpath .;D:\solrCloud\solr_home_1\server\solr\WEB-INF\lib\* org.apache.solr.cloud.ZkCLI -cmd clear /configs/mycollection -zkhost 127.0.0.1:2181,127.0.0.1:2281,127.0.0.1:2381
说明:清空(clear) /configs 下的 mycollection  文件夹。
如下只有mycollection一份配置了:


2、删除所有

java -classpath .;D:\solrCloud\solr_home_1\server\solr\WEB-INF\lib\* org.apache.solr.cloud.ZkCLI -cmd clear /configs -zkhost 127.0.0.1:2181,127.0.0.1:2281,127.0.0.1:2381

说明:清空(clear)/configs  文件。因为上传的所有zookeeper 都是保存到 /configs 目录下面的,所以当我们删除configs   即将所有配置都删除了。

删除所有后:







作者:l1028386804 发表于2016/8/2 1:13:57 原文链接
阅读:79 评论:0 查看评论

【计算机网络】——网络层,IP地址,IP数据报,数据的分片及组装详解

$
0
0

网络层

  TCP/IP体系中网络层十分重要,本篇文章主要介绍IP(Internet Protocol)协议。
  网络层的主要作用是“实现终端节点之间的通信”。这种终端节点之间的通信也叫作“点对点通信”。
  网络层的下一层——数据链路层,主要作用是在互联同一种数据链路的节点之间进行宝传递。如果要跨越多种数据链路,就要借助网络层了。
  网络层可以跨越不同的数据链路,即使在不同的数据链路上也能实现两端节点之间的数据包传输。

IP基础知识

  本文章从三个作用模块介绍IP协议:
  

  • IP寻址
  • 路由
  • IP分片和组装

    1.IP地址

      如数据链路层的MAC地址一样,MAC地址是用来标识同一个链路中不同计算机的一种识别码。在网络层中,IP地址用来进行“在连接到网络中所有主机中识别出进行通信的目标地址”。因此,在TCP/IP通信中所有主机或路由器都必须设定自己的IP地址。
      IP地址(IPv4)有32为正整数来表示。TCP/IP要求将IP地址分配给每一个参与通信的主机。
      实际上IP地址不是由主机配置的,而是有网卡设置的,并且一个网卡其实可以设置多个IP地址。

    IP地址的组成

      IP地址有<网络号,主机号>两个部分组成。

    IP地址的分类

    IP地址分为A,B,C,D四类。

    1. A类IP地址

    一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的 >最高位必须是“0”, 地址范围从1.0.0.0 到126.0.0.0。可用的A类网络 >有126个,每个网络能容纳1亿多个主机。

    2. B类IP地址

    一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络 >地址的最高位必须是“10”,地址范围从128.0.0.0到 >191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多 >个主机 。

    3. C类IP地址

    一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址 >的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类 >网络可达209万余个,每个网络能容纳254个主机。

    4. D类地址用于多点广播(Multicast)。

    D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算 >机。

    5. E类IP地址

    以“llll0”开始,为将来使用保留。

    全零(“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。

    在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:

    A类地址:10.0.0.0~10.255.255.255

    B类地址:172.16.0.0~172.31.255.255

    C类地址:192.168.0.0~192.168.255.255

    A类地址的第一组数字为1~126。注意,数字0和 127不作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机,不 >能传送。

    B类地址的第一组数字为128~191。

    C类地址的第一组数字为192~223。

子网掩码

  对IP地址进行分类可能会对网络地址进行浪费,因此现在用的都是子网掩码。
  这种方式实际上就是将原来A类、B类、C类等分类中的主机地址部分用作子网地址,可以将源网络分为多个物理网络的一种机制。
  引入了子网掩码后,一个IP地址就有了两个标识码:
  

  • IP地址本身
  • 表示网络部的子网掩码

      子网掩码是一个32位地址,用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上。
      用于子网掩码的位数决定于可能的子网数目和每个子网的主机数目。在定义子网掩码前,必须弄清楚本来使用的子网数和主机数目。

定义子网掩码的步骤为:

A、确定哪些组地址归我们使用。比如我们申请到的网络号为 “210.73.a.b”,该网络地址为c类IP地址,网络标识为“210.73.a”,主机标识为“b”。

B、根据我们所需的子网数以及将来可能扩充到的子网数,用宿主机的一些位来定义子网掩码。比如我们需要12个子网,将来可能需要16个。用第四个字节的前四位确定子网掩码。前四位都置为“1”,即第四个字节为“11110000”,这个数我们暂且称作新的二进制子网掩码。

C、把对应初始网络的各个位都置为“1”,即前三个字节都置为“1”,则子网掩码的间断二进制形式为:“11111111.11111111.11111111.11110000” 。

D、把这个数转化为间断十进制形式为:“255.255.255.240” 。

2.路由控制

  路由控制是将分组数据发送到最终目标地址的功能。
  当IP数据包到达路由器时,路由器首先查找其目标地址,从而再决定下一步应该将这个包发往哪个路由器,然后将包发送过去。当这个IP包到达那个路由器后,会再次查找下一目标地址,一直到找到最终的目标地址将数据包发送给这个节点。
  但是这个过程不是无限循环下去的。在IP数据报中有生存时间这个部分专门用来计算经过了多少路由器转发,我们在设置了生存时间后,美每经一个路由器转发,生存时间都会相应减一。
  IP数据报  

路由控制表

  为了将数据包发送给目标主机,所有主机都维护着一张路由控制表,该表记录IP数据在下一步应该发给哪个路由器。
  路由控制表中记录着网络地址与下一步应该发送至路由器的地址。在发送IP包时,首先要确定IP包首部中的目标地址,再从路由控制表中找到与该地址具有相同网络地址的记录,根据该记录将IP包转发给相应的下一个路由器。
  IP包将根据这个路由表在各个数据链路上传输。

路由控制表的形成方式有两种:

  • 管理员手动设置——静态路由控制
  • 路由器与其他路由器相互交换信息时自动刷新——动态路由控制

    默认路由

      默认路由一般标记为0.0.0.0/0或default。这里的0.0.0.0/0是指没有表示IP地址,而不是0.0.0.0IP地址,地址为0.0.0.0的IP地址应该为0.0.0.0/32.

主机路由

   “IP地址/32”成为主机路由,主机路由意味着要基于主机上网卡上配置的IP地址本身,而不是基于该地址的网络地址部分进行路由。

环回地址

  环回地址是在同一台计算机上的程序之间进行网络通信时使用的默认地址。计算机使用一个特殊的IP地址127.0.0.1作为环回地址。

IP属于面向无连接型

  IP面向无连接,即在发包之前,不需要建立与对端目标地址之间的链接。
  在面向无连接的情况下,即使对端主机关机或不存在,数据包还是会被发送出去,所以在面向无连接的方式下可能会有很多冗余通信。
  但是IP面向无连接主要是因为两点原因:
  
- 为了简化
- 为了提速

  但为了提高可靠性,上一层的TCP采用面向连接型。
 

IP数据报

这里写图片描述

版本 

  占4位,指IP协议的版本。通信双方使用的IP协议版本必须一致。目前广泛使用的IP协议版本号为4(即IPv4)。关于IPv6,目前还处于草案阶段。

首部长度 

  占4位,可表示的最大十进制数值是15。请注意,这个字段所表示数的单位是32位字长(1个32位字长是4字节),因此,当IP的首部长度为1111时(即十进制的15),首部长度就达到60字节。 
  当IP分组的首部长度不是4字节的整数倍时,必须利用最后的填充字段加以填充。因此数据部分永远在4字节的整数倍开始,这样在实现IP协议时较为方便。 
  首部长度限制为60字节的缺点是有时可能不够用。但这样做是希望用户尽量减少开销。最常用的首部长度就是20字节(即首部长度为0101),这时不使用任何选项。

区分服务 

  占8位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。1998年IETF把这个字段改名为区分服务DS(Differentiated Services)。只有在使用区分服务时,这个字段才起作用。

总长度 

  总长度指首部和数据之和的长度,单位为字节。总长度字段为16位,因此数据报的最大长度为2^16-1=65535字节。
  在IP层下面的每一种数据链路层都有自己的帧格式,其中包括帧格式中的数据字段的最大长度,这称为最大传送单元MTU(Maximum Transfer Unit)。当一个数据报封装成链路层的帧时,此数据报的总长度(即首部加上数据部分)一定不能超过下面的数据链路层的MTU值。

标识(identification) 

  占16位。IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值赋给标识字段。 
  但这个“标识”并不是序号,因为IP是无连接服务,数据报不存在按序接收的问题。当数据报由于长度超过网络的MTU而必须分片时,这个标识字段的值就被复制到所有的数据报的标识字段中。相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报。

标志(flag) 

  占3位,但目前只有2位有意义。
● 标志字段中的最低位记为MF(More Fragment)。MF=1即表示后面“还有分片”的数据报。MF=0表示这已是若干数据报片中的最后一个。
● 标志字段中间的一位记为DF(Don’t Fragment),意思是“不能分片”。只有当DF=0时才允许分片。

片偏移 

  占13位。片偏移指出:较长的分组在分片后,某片在原分组中的相对位置。也就是说,相对用户数据字段的起点,该片从何处开始。片偏移以8个字节为偏移单位。这就是说,除了最后一个分片,每个分片的长度一定是8字节(64位)的整数倍。

生存时间 

  占8位,生存时间字段常用的的英文缩写是TTL(Time To Live),表明是数据报在网络中的寿命。由发出数据报的源点设置这个字段。其目的是防止无法交付的数据报无限制地在因特网中兜圈子,因而白白消耗网络资源。最初的设计是以秒作为TTL的单位。
  每经过一个路由器时,就把TTL减去数据报在路由器消耗掉的一段时间。若数据报在路由器消耗的时间小于1秒,就把TTL值减1。当TTL值为0时,就丢弃这个数据报。后来把TTL字段的功能改为“跳数限制”(但名称不变)。路由器在转发数据报之前就把TTL值减1.若TTL值减少到零,就丢弃这个数据报,不再转发。 
  因此,现在TTL的单位不再是秒,而是跳数。TTL的意义是指明数据报在网络中至多可经过多少个路由器。显然,数据报在网络上经过的路由器的最大数值是255.若把TTL的初始值设为1,就表示这个数据报只能在本局域网中传送。

协议 

  占8位,协议字段指出此数据报携带的数据是使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程。

首部检验和 

  占16位。这个字段只检验数据报的首部,但不包括数据部分。这是因为数据报每经过一个路由器,路由器都要重新计算一下首部检验和(一些字段,如生存时间、标志、片偏移等都可能发生变化)。不检验数据部分可减少计算的工作量。

源地址 

  占32位。

目的地址 

  占32位。

IP分片和组装

  在不同数据链路中,MAC帧各自的最大传输单位(MTU:Maximum Transmission Unit)不同。
  MTU的值在以太网中是1500字节,在FDDI中是4352字节,在ATM则为9180字节。
  MAC帧最小字节为56字节,MTU在不同数据链路中是不同的。
  当以太网帧大小不够56字节的时候,会对MAC帧进行填充。而当大小超过MTU的时候,就会进行分片。
  分片的时候,经过分片的数据16位标识都一样,为了组装的时候能够知道哪些是同一片数据。
  那么组装顺序如何保证呢:
  通过片偏移来保证,片偏移为0的数据为数据首部,而下一次偏移量等于当前片偏移+总长度。
  组装的时候也是通过片偏移进行组装的。
  

作者:lixungogogo 发表于2016/8/2 1:42:59 原文链接
阅读:76 评论:0 查看评论

一起talk C栗子吧(第一百八十回:C语言实例--break到哪里)

$
0
0

各位看官们,大家好,上一回中咱们说的是字符和字符串输出函数的例子,这一回咱们说的例子是:break到哪里 。闲话休提,言归正转。让我们一起talk C栗子吧!


看官们,我们这一回的主题是break到哪里,在介绍该主题前,我们先介绍一下break。break是C语言中的一个关键字,在其后面加上分号就构成break语句。啊!这就是一个语句?有看官表示吃惊。我要说的是:这确实是一个语句,break关键字总是这么单枪匹马地构成一条语句。虽然break关键字可以单枪匹马地构成语句,不过break语句总是和循环语句或者switch语句出现在一起。就像金老爷《神雕侠侣》中的黑风双煞,他们总是一起出现,而且让人闻风丧胆。哈哈,看官们别害怕,我们这里没有黑风双煞,我们只有和循环语句或者switch语句一起出现break语句。那么回到我们的主题中来,break语句到底break到哪里了?

要回答这个问题,我们需要把break语句和循环语句或者switch语句放在一起进行说明,因为它们总一起出现在程序中。循环语句或者switch语句,都有自己的作用域,也就是位于大括号范围中的内容。在这两种语句中使用break语句后就会跳出这两种语句的的作用域。我们通过例子来说明,方便大家理解。

    int index = 0;

    while(index < 5)
    {
        //do some thing
        printf("index = %d \n",index);
        index++;

        if(index == 3)
        {
            puts("break loop: while ");
            break;
        }
    }

    puts("hello");

在上面的例子中,while循环语句的作用域就是while语句本身和位于该语句下面大括号中的内容,在使用break语句后,就会跳出while语句的作用域,然后运行位于该作用域后的puts语句,此时它会在终端中输出”hello”。我们在例子中使用的是while循环语句,对于for或者do/while循环语句也是同样的原理,大家可以自己去分析并且动手试试,下面是程序的运行结果,请大家参考:

index = 0 
index = 1 
index = 2 
break loop: while  //index等于3时使用break语句跳出while循环
hello              //运行while循环后的语句

我们介绍完break语句在循环语句中的例子,接下来举一个break语句在switch语句中的例子,请大家参考:

    index = 3;
    switch(index)
    {
    case 1:
        //do some thing
        puts("case 1 runing ");
    break;
    case 3:
        //do some thing
        puts("case 3 runing ");
    break;
    default:
        //do some thing
        puts("case default runing ");
    break;
    }

    puts("hello");

在上面的例子中,switch循环语句的作用域就是switch语句本身和位于该语句下面大括号中的内容,在使用break语句后,就会跳出switch语句的作用域,然后运行位于该作用域后的puts语句,此时它会在终端中输出”hello”。下面是程序的运行结果,请大家参考:

case 3 runing  //运行case 3中的内容,然后使用break跳出switch语句
hello          //运行switch语句后的语句

介绍到这里,我想大家已经明白了break语句break到哪里,接下来我们验证一下大家是不是真理解其中的含义,我们给大家演示一段代码。

    index = 0;
    while(index++ < 5)
    {
        printf("index = %d \n",index);
        switch(index)
        {
            case 1:
                //do some thing
                puts("case 1 running, break switch ");
            break;
            case 2:
                //do some thing
                puts("case 2 running, break switch ");
            break;
            default:
                //do some thing
                puts("default running, break switch ");
            break;
        }

        if(index == 3)
        {
            //do some thing
            puts("if/else breaking ");
            break;
        }
        else
        {
            //do some thing
            puts("if/else running ");
        }

        puts("loop: while running");
    }

    puts("hello");

大家先阅读一下该程序,然后自己动手写出程序的运行结果。下面是程序的运行结果,大家和自己写出的结果对比一下,是不是一样呢?如果结果不一样,那么说明你没有真正理解break语句break到哪里。

index = 1 
case 1 running, break switch  //break语句只是breakswitch作用域外,
if/else running 
loop: while running           //循环仍然在运行,说明break语句没有breakwhile循环作用域外
index = 2 
case 2 running, break switch 
if/else running 
loop: while running
index = 3 
default running, break switch 
if/else breaking             //break语句breakwhile循环的作用域外
hello                        //运行while循环语句后的语句

结合上面的代码和程序的运行结果,我们可以看到,前面的例子中只是单一的while语句或者switch语句,而此处的代码中在while循环中嵌套了switch语句,相比之下会复杂一些。代码中在switch语句中使用了break语句,该语句只是break到了switch作用域外,然而switch语句仍然在while循环语句的作用域中,因此,它不会break到while循环语句外,这点在程序运行结果中体现为不断地执行switch语句中的case,直到index等于3时从if/else语句中的break跳出while循环的作用域。

通过这个例子,我们可以得出以下结论:break语句只能break到自身所在的使用域外,如果作用域有嵌套,那么它不会break到嵌套区域外。这么说可能比较抽象,我们做个简单的图示来说明:

{
    //作用域A
    {
        //作用域B
        break;    //break语句只能break到作用域B外,不会break到作用域A外;
    }

    break;       //break语句可以break到作用域A外;
}

看官们,我把本章回中的代码收集整理成一个文件,该文件位于我的资源中,大家可以点击这里下载使用。

各位看官,关于break到哪里的例子咱们就说到这里。欲知后面还有什么例子,且听下回分解 。


作者:talk_8 发表于2016/8/2 5:37:32 原文链接
阅读:52 评论:0 查看评论

分布式事务(一) 两阶段提交及JTA

$
0
0

  原创文章,转载请务必将下面这段话置于文章开头处。
  本文转发自Jason’s Blog原文链接 http://www.jasongj.com/big_data/two_phase_commit/

分布式事务

分布式事务简介

分布式事务是指会涉及到操作多个数据库(或者提供事务语义的系统,如JMS)的事务。其实就是将对同一数据库事务的概念扩大到了对多个数据库的事务。目的是为了保证分布式系统中事务操作的原子性。分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)。

分布式事务实现机制

如同作者在《SQL优化(六) MVCC PostgreSQL实现事务和多版本并发控制的精华》一文中所讲,事务包含原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

PostgreSQL针对ACID的实现技术如下表所示。

ACID 实现技术
原子性(Atomicity) MVCC
一致性(Consistency) 约束(主键、外键等)
隔离性 MVCC
持久性 WAL

分布式事务的实现技术如下表所示。(以PostgreSQL作为事务参与方为例)

分布式ACID 实现技术
原子性(Atomicity) MVCC + 两阶段提交
一致性(Consistency) 约束(主键、外键等)
隔离性 MVCC
持久性 WAL

从上表可以看到,一致性、隔离性和持久性靠的是各分布式事务参与方自己原有的机制,而两阶段提交主要保证了分布式事务的原子性。

两阶段提交

分布式事务如何保证原子性

在分布式系统中,各个节点(或者事务参与方)之间在物理上相互独立,通过网络进行协调。每个独立的节点(或组件)由于存在事务机制,可以保证其数据操作的ACID特性。但是,各节点之间由于相互独立,无法确切地知道其经节点中的事务执行情况,所以多节点之间很难保证ACID,尤其是原子性。

如果要实现分布式系统的原子性,则须保证所有节点的数据写操作,要不全部都执行(生效),要么全部都不执行(生效)。但是,一个节点在执行本地事务的时候无法知道其它机器的本地事务的执行结果,所以它就不知道本次事务到底应该commit还是 roolback。常规的解决办法是引入一个“协调者”的组件来统一调度所有分布式节点的执行。

XA规范

XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。XA引入的事务管理器充当上文所述全局事务中的“协调者”角色。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)。目前,Oracle、Informix、DB2、Sybase和PostgreSQL等各主流数据库都提供了对XA的支持。

XA规范中,事务管理器主要通过以下的接口对资源管理器进行管理
- xa_open,xa_close:建立和关闭与资源管理器的连接。
- xa_start,xa_end:开始和结束一个本地事务。
- xa_prepare,xa_commit,xa_rollback:预提交、提交和回滚一个本地事务。
- xa_recover:回滚一个已进行预提交的事务。

两阶段提交原理

二阶段提交的算法思路可以概括为:协调者询问参与者是否准备好了提交,并根据所有参与者的反馈情况决定向所有参与者发送commit或者rollback指令(协调者向所有参与者发送相同的指令)。

所谓的两个阶段是指
- 准备阶段 又称投票阶段。在这一阶段,协调者询问所有参与者是否准备好提交,参与者如果已经准备好提交则回复Prepared,否则回复Non-Prepared
- 提交阶段 又称执行阶段。协调者如果在上一阶段收到所有参与者回复的Prepared,则在此阶段向所有参与者发送commit指令,所有参与者立即执行commit操作;否则协调者向所有参与者发送rollback指令,参与者立即执行rollback操作。

两阶段提交中,协调者和参与方的交互过程如下图所示。
Two-phase commit

两阶段提交前提条件

  • 网络通信是可信的。虽然网络并不可靠,但两阶段提交的主要目标并不是解决诸如拜占庭问题的网络问题。同时两阶段提交的主要网络通信危险期(In-doubt Time)在事务提交阶段,而该阶段非常短。
  • 所有crash的节点最终都会恢复,不会一直处于crash状态。
  • 每个分布式事务参与方都有WAL日志,并且该日志存于稳定的存储上。
  • 各节点上的本地事务状态即使碰到机器crash都可从WAL日志上恢复。

两阶段提交容错方式

两阶段提交中的异常主要分为如下三种情况
1. 协调者正常,参与方crash
2. 协调者crash,参与者正常
3. 协调者和参与方都crash

对于第一种情况,若参与方在准备阶段crash,则协调者收不到Prepared回复,协调方不会发送commit命令,事务不会真正提交。若参与方在提交阶段提交,当它恢复后可以通过从其它参与方或者协调方获取事务是否应该提交,并作出相应的响应。

第二种情况,可以通过选出新的协调者解决。

第三种情况,是两阶段提交无法完美解决的情况。尤其是当协调者发送出commit命令后,唯一收到commit命令的参与者也crash,此时其它参与方不能从协调者和已经crash的参与者那儿了解事务提交状态。但如同上一节两阶段提交前提条件所述,两阶段提交的前提条件之一是所有crash的节点最终都会恢复,所以当收到commit的参与方恢复后,其它节点可从它那里获取事务状态并作出相应操作。

JTA

JTA介绍

作为java平台上事务规范JTA(Java Transaction API)也定义了对XA事务的支持,实际上,JTA是基于XA架构上建模的。在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即Java Transaction Service)实现。像很多其他的Java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要有以下几种:
- J2EE容器所提供的JTA实现(如JBoss)。
- 独立的JTA实现:如JOTM(Java Open Transaction Manager),Atomikos。这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。

PostgreSQL两阶段提交接口

  • PREPARE TRANSACTION transaction_id PREPARE TRANSACTION 为当前事务的两阶段提交做准备。 在命令之后,事务就不再和当前会话关联了;它的状态完全保存在磁盘上, 它提交成功有非常高的可能性,即使是在请求提交之前数据库发生了崩溃也如此。这条命令必须在一个用BEGIN显式开始的事务块里面使用。
  • COMMIT PREPARED transaction_id 提交已进入准备阶段的ID为transaction_id的事务
  • ROLLBACK PREPARED transaction_id 回滚已进入准备阶段的ID为transaction_id的事务

典型的使用方式如下

postgres=> BEGIN;
BEGIN
postgres=> CREATE TABLE demo(a TEXT, b INTEGER);    
CREATE TABLE
postgres=> PREPARE TRANSACTION 'the first prepared transaction';
PREPARE TRANSACTION
postgres=> SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | postgres
(1 row)

从上面代码可看出,使用PREPARE TRANSACTION transaction_id语句后,PostgreSQL会在pg_catalog.pg_prepared_xact表中将该事务的transaction_id记于gid字段中,并将该事务的本地事务ID,即23970,存于transaction字段中,同时会记下该事务的创建时间及创建用户和数据库名。

继续执行如下命令

postgres=> \q
SELECT * FROM pg_prepared_xacts;
 transaction |              gid               |           prepared            | owner | database 
-------------+--------------------------------+-------------------------------+-------+----------
       23970 | the first prepared transaction | 2016-08-01 20:44:55.816267+08 | casp  | cqdb
(1 row)

cqdb=> ROLLBACK PREPARED 'the first prepared transaction';            
ROLLBACK PREPARED
cqdb=> SELECT * FROM pg_prepared_xacts;
 transaction | gid | prepared | owner | database 
-------------+-----+----------+-------+----------
(0 rows)

即使退出当前session,pg_catalog.pg_prepared_xact表中关于已经进入准备阶段的事务信息依然存在,这与上文所述准备阶段后各节点会将事务信息存于磁盘中持久化相符。注:如果不使用PREPARED TRANSACTION 'transaction_id',则已BEGIN但还未COMMIT或ROLLBACK的事务会在session退出时自动ROLLBACK。

在ROLLBACK已进入准备阶段的事务时,必须指定其transaction_id

PostgreSQL两阶段提交注意事项

  • PREPARE TRANSACTION transaction_id命令后,事务状态完全保存在磁盘上。
  • PREPARE TRANSACTION transaction_id命令后,事务就不再和当前会话关联,因此当前session可继续执行其它事务。
  • COMMIT PREPAREDROLLBACK PREPARED可在任何会话中执行,而并不要求在提交准备的会话中执行。
  • 不允许对那些执行了涉及临时表或者是创建了带WITH HOLD游标的事务进行PREPARE。 这些特性和当前会话绑定得实在是太紧密了,因此在一个准备好的事务里没什么可用的。
  • 如果事务用SET修改了运行时参数,这些效果在PREPARE TRANSACTION之后保留,并且不会被任何以后的COMMIT PREPAREDROLLBACK PREPARED所影响,因为SET的生效范围是当前session。
  • 从性能的角度来看,把一个事务长时间停在准备好的状态是不明智的,因为它会影响VACUUM回收存储的能力。
  • 已准备好的事务会继续持有它们获得的锁,直到该事务被commit或者rollback。所以如果已进入准备阶段的事务一直不被处理,其它事务可能会因为获取不到锁而被block或者失败。
  • 默认情况下,PostgreSQL并不开启两阶段提交,可以通过在postgresql.conf文件中设置max_prepared_transactions配置项开启PostgreSQL的两阶段提交。

JTA实现PostgreSQL两阶段提交

本文使用Atomikos提供的JTA实现,利用PostgreSQL提供的两阶段提交特性,实现了分布式事务。本文中的分布式事务使用了2个不同机器上的PostgreSQL实例。

本例所示代码可从作者Github获取。

package com.jasongj.jta.resource;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/jta")
public class JTAResource {
  private static final Logger LOGGER = LoggerFactory.getLogger(JTAResource.class);

  @GET
  public String test(@PathParam(value = "commit") boolean isCommit)
      throws NamingException, SQLException, NotSupportedException, SystemException {
    UserTransaction userTransaction = null;
    try {
      Context context = new InitialContext();
      userTransaction = (UserTransaction) context.lookup("java:comp/UserTransaction");
      userTransaction.setTransactionTimeout(600);

      userTransaction.begin();

      DataSource dataSource1 = (DataSource) context.lookup("java:comp/env/jdbc/1");
      Connection xaConnection1 = dataSource1.getConnection();

      DataSource dataSource2 = (DataSource) context.lookup("java:comp/env/jdbc/2");
      Connection xaConnection2 = dataSource2.getConnection();
      LOGGER.info("Connection autocommit : {}", xaConnection1.getAutoCommit());

      Statement st1 = xaConnection1.createStatement();
      Statement st2 = xaConnection2.createStatement();
      LOGGER.info("Connection autocommit after created statement: {}", xaConnection1.getAutoCommit());


      st1.execute("update casp.test set qtime=current_timestamp, value = 1");
      st2.execute("update casp.test set qtime=current_timestamp, value = 2");
      LOGGER.info("Autocommit after execution : ", xaConnection1.getAutoCommit());

      userTransaction.commit();
      LOGGER.info("Autocommit after commit: ",  xaConnection1.getAutoCommit());
      return "commit";

    } catch (Exception ex) {
      if (userTransaction != null) {
        userTransaction.rollback();
      }
      LOGGER.info(ex.toString());
      throw new WebApplicationException("failed", ex);
    }
  }
}

从上示代码中可以看到,虽然使用了Atomikos的JTA实现,但因为使用了面向接口编程特性,所以只出现了JTA相关的接口,而未显式使用Atomikos相关类。具体的Atomikos使用是在WebContent/META-INFO/context.xml中配置。

<Context>
  <Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
    <Resource name="jdbc/1"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource1"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.1"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>

    <Resource name="jdbc/2"
    auth="Container"
    type="com.atomikos.jdbc.AtomikosDataSourceBean"
    factory="com.jasongj.jta.util.EnhancedTomcatAtomikosBeanFactory"
    uniqueResourceName="DataSource_Resource2"
    minPoolSize="2"
    maxPoolSize="8"
    testQuery="SELECT 1"
    xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
    xaProperties.databaseName="postgres"
    xaProperties.serverName="192.168.0.2"
    xaProperties.portNumber="5432"
    xaProperties.user="casp"
    xaProperties.password=""/>  
</Context>
作者:Habren 发表于2016/8/2 6:49:20 原文链接
阅读:27 评论:0 查看评论

HDOJ A Simple Nim (博弈Nim+sg函数)

$
0
0

A Simple Nim

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 195    Accepted Submission(s): 133


Problem Description
Two players take turns picking candies from n heaps,the player who picks the last one will win the game.On each turn they can pick any number of candies which come from the same heap(picking no candy is not allowed).To make the game more interesting,players can separate one heap into three smaller heaps(no empty heaps)instead of the picking operation.Please find out which player will win the game if each of them never make mistakes.
 

Input
Intput contains multiple test cases. The first line is an integer1T100, the number of test cases. Each case begins with an integer n, indicating the number of the heaps, the next line contains N integerss[0],s[1],....,s[n1], representing heaps with s[0],s[1],...,s[n1]            objects  respectively.(1n106,1s[i]109)
 

Output
For each test case,output a line whick contains either"First player wins."or"Second player wins".
 

Sample Input
2 2 4 4 3 1 2 4
 

Sample Output
Second player wins. First player wins.
 

Author
UESTC
 

Source

2016 Multi-University Training Contest 6 


思路:

这道题是一道典型的博弈论Nim问题。Nim问题就是把若干堆火柴进行拿取操作,每次至少拿一根或多根,谁拿了最后一根谁就赢。可是此题还加了一个动作,就是你可以选择不取火柴,而是将一堆超过3根的火柴堆分成三个非零堆,这样的问题叫做“分隔-取走”游戏。刚看此题我是一脸懵逼的,毕竟对博弈论一点没接触过。所以我从网上搜了一下关于博弈论的博客,发现大家关于博弈论都是先把取火柴变为有向图,把每个堆看成是通往顶点的一个点,然后会用到sg这样一个函数来求解的。(文章的末尾会贴上几个介绍博弈论经典问题的链接)

sg值:一个点的SG值就是一个不等于它的后继点的SG的且大于等于零的最小整数。

下面来介绍一下我所理解的sg函数。sg值就是一个用于描述该游戏是否到达必胜或者必败的一个东西,当sg=0时,表示游戏已经结束,你下一步必输;sg ≠ 0时表示游戏还可以继续进行。所以这道题就是用sg来看这个游戏是否结束。


对于sg值的计算方法,有以下几种:1、对于一个数,它的sg值等于它本身。2、对于一个堆的sg,比如m,n,q三个堆,他们的sg(m,n,q)=sg(m)^sg(n)^sg(q)3、还有一种算法就是定义。这种情况是它给了你一个数n的各个后继点的sg值,那么这个数n的sg值就是不等于它的后续点的sg值且大于等于0的最小数。比如后继点的sg值分别为0、1、3、4,那么n的sg就是去掉0、1、3、4之后的最大非负数2。


这里还要再介绍一个概念就是后续点。后续点是指按照题目要求的走法可以走一步达到的那个点。


此外还有异或运算符“ ^ ”,也叫xor。它的运算法则是:给两个数m、n,先把两个数化成二进制数,然后位数上相同的话就的0,不同就得1。举个例子: 1和4,1用二进制写是001,4用二进制写诗100,那么001^100,第一位和第三位不同得1,第二位相同得0,所以得到结果101,也就是5。


然后因为这个题数量级的范围是10的9次方级,所以没办法一个一个去求sg值,所以只能能过sg打表的形式来找出sg变化的规律,然后通过数学归纳总结出sg和x的一个关系式。再根据打表可以看出来规律,每当x到达8的倍数的时候,sg的值就等于x-1,如果x整除8等于7的时候,sg值就等于x+1,其他情况sg就等于x。然后就找到了sg与x之间的规律,那么就能推导所有的x与其所对应的sg之间的表达式。

至于sg值打表,先把vis数组全都初始化为0,然后遍历每个数的sg值vis[sg值出现过的数]都变为1,代表取石子的过程。然后将所有能拆分的都拆分,即用“^”来拆分sg值。然后当出现sg为0的时候,把其所作为sg的值所对应的那个第几个sg记录下来。所得sg打表如图:


然后就可以得到上面的规律啦。


代码:

//sg值打表程序
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;

int sg[32];
bool vis[32];
int a[3];

int main()
{
	for (int i=1; i<32; i++) {
		memset(vis,0,sizeof(vis));
		int j,k;
		for (j=0; j<i; j++) vis[sg[j]]=1;
		for (j=1; j<i; j++)
			for (k=1; j+k<i; k++)
				vis[sg[k]^sg[j]^sg[(i-k-j)]]=1;
		for (j=0; j<32&&vis[j]; j++);
		sg[i]=j;
	}
	for (int i=1; i<32; i++)
		printf("%d %d\n",i,sg[i]);
	return 0;
}

//主程序 
#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <algorithm>  
using namespace std;  
  
int find_sg(int x)  
{  
    if(x%8==0)return x-1;  
    else if(x%8==7)return x+1;  
    return x;  
}  
int main()  
{  
    int T;  
    scanf("%d",&T);  
    while(T--)  
    {  
        int a,n,i,j,ans=0;  
        scanf("%d",&n);  
        for(i=0;i<n;i++)  
        {  
            scanf("%d",&a);  
            ans=ans^find_sg(a);  
        }  
        if(ans==0)printf("Second player wins.\n");  
        else printf("First player wins.\n");  
    }  
    return 0;  
}    




下面贴几个觉得比较好的关于博弈论的链接:

1、博弈论经典问题的全面概括  

2、Nim游戏博弈概括

3、Nim游戏与Sg函数详解

4、取石子游戏的数学问题

5、取走-分隔问题

6、Sg函数介绍

7、Sg函数模板

8、求sg值的问题求sg算法




作者:hhu1506010220 发表于2016/8/5 1:34:58 原文链接
阅读:62 评论:0 查看评论

HDOJ A Boring Question(快速幂+逆元+数学推导)

$
0
0

A Boring Question

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 305    Accepted Submission(s): 162


Problem Description
There are an equation.
0k1,k2,kmn1j<m(kj+1kj)%1000000007=?
We define that (kj+1kj)=kj+1!kj!(kj+1kj)! . And (kj+1kj)=0 while kj+1<kj.
You have to get the answer for each n and m that given to you.
For example,if n=1,m=3,
When k1=0,k2=0,k3=0,(k2k1)(k3k2)=1;
Whenk1=0,k2=1,k3=0,(k2k1)(k3k2)=0;
Whenk1=1,k2=0,k3=0,(k2k1)(k3k2)=0;
Whenk1=1,k2=1,k3=0,(k2k1)(k3k2)=0;
Whenk1=0,k2=0,k3=1,(k2k1)(k3k2)=1;
Whenk1=0,k2=1,k3=1,(k2k1)(k3k2)=1;
Whenk1=1,k2=0,k3=1,(k2k1)(k3k2)=0;
Whenk1=1,k2=1,k3=1,(k2k1)(k3k2)=1.
So the answer is 4.
 

Input
The first line of the input contains the only integerT,(1T10000)
Then T lines follow,the i-th line contains two integers n,m,(0n109,2m109)
 

Output
For eachn and m,output the answer in a single line.
 

Sample Input
2 1 2 2 3
 

Sample Output
3 13
 

Author
UESTC
 

Source
 


思路:

 据说这是道水题…然后只需要推导出一个数学公式...


表示看了之后觉得推不出来...但是涌神告诉我们说,必须要用到快速排序和逆元。快速排序是为了更快的降低时间复杂度,而逆元则是为了在运算中不会出错。比如,两个int类型的数相乘,可能会爆掉,所以这时候只能用逆元来做。类似于之前做过的一道题,求(a/b)%9973的那道题其实是一个道理。


代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#define mod 1000000007
using namespace std;

long long powt(long long a,long long b)
{
    long long r = 1;
    while(b)
    {
        if(b & 1) r = r * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return r;
}

int main()
{
    long long t,n,m;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",((powt(m,n + 1) - 1) * powt(m - 1,mod - 2) % mod + mod) % mod);
    }
    return 0;
}



再贴一个快速幂的模板:

// m^n % k
long long quickpow(long long m,long long n,long long k)
{
    long long b = 1;
    while (n > 0)
    {
          if (n & 1)
             b = (b*m)%k;
          n = n >> 1 ;
          m = (m*m)%k;
    }
    return b;
} 






作者:hhu1506010220 发表于2016/8/5 1:52:00 原文链接
阅读:66 评论:0 查看评论

数据结构——双向链表(用于DXF直线数据处理,做出测试算法)

$
0
0
//#include "stdafx.h"//把这行放在最开始。

#include<string.h>
#include<iostream>
#include<iomanip>
using namespace std;
typedef struct 
{
	double x0;
	double y0;
	double x1;
	double y1;
	int flag;
}LineDxfData;
typedef struct LineNode
{
	LineDxfData linedata;
	struct LineNode* priorNode;
	struct LineNode* nextNode;
}LineNode;
LineNode*LineNodeAdd(LineNode*head,LineDxfData linedata);
int LineNodeShowAll(LineNode*head);
LineNode*LineInsertNode(LineNode*head,LineDxfData linedata);
LineNode*LineFindNode_xyEnd(LineNode*head,LineDxfData linedata);
LineNode*LineFindNode_xyEstart(LineNode*head,LineDxfData linedata);
LineNode*LineFindNode_xySend(LineNode*head,LineDxfData linedata);
LineNode*LineFindNode_xyStart(LineNode*head,LineDxfData linedata);
LineDxfData LineDataSwap(LineDxfData linedata);

int main()
{
	LineDxfData linedata;//过渡存储数据
	LineNode *node=NULL;
	LineNode *head=NULL;
	//printf("链表测试。先输入链表中的数据,格式为:x0 y0 x1 y1\n");
	
	do
	{
		printf("链表测试。先输入链表中的数据,格式为:x0 y0 x1 y1 flag\n");
		fflush(stdin);
		cin>>linedata.x0>>linedata.y0>>linedata.x1>>linedata.y1>>linedata.flag;
		head=LineNodeAdd(head,linedata);
		if(0==linedata.flag)
		{
			break;
		}
		//head=LineNodeAdd(head,linedata);
	}while(1);
	LineNodeShowAll(head);
	do
	{
	printf("请输入增加的数据,格式为:x0 y0 x1 y1 flag\n");
	fflush(stdin);
	cin>>linedata.x0>>linedata.y0>>linedata.x1>>linedata.y1>>linedata.flag;
	head=LineInsertNode(head,linedata);//
	if(0==linedata.flag)
		{
			break;
		}
	}while(1);
	LineNodeShowAll(head);
	
	
	
}
LineNode*LineNodeAdd(LineNode*head,LineDxfData linedata)
{
	LineNode*htemp=NULL;
	LineNode*node=NULL;
	if(!(node=(LineNode*)malloc(sizeof(LineNode))))
	{
		printf("申请内存失败!\n");
		return NULL;
	}
	else
	{
		node->linedata=linedata;
		node->priorNode=node;//node是新申请的内存,单独一块,它的头结点指向自己,尾结点指向NULL
		node->nextNode=NULL;
		if(head==NULL)//空表,初始化的时候指向的是NULL,所以下面的指向有问题。NULL是没有头结点和尾结点的。
		{
			
		/*	head->priorNode=head;
			head->nextNode=node;*/
			head=node;//现在开始head有跟班了,它指向的地址是node不再是NULL,而node指向的是NULL;
			return head;
		}
		else
		{
			htemp=head;
			while(htemp->nextNode!=NULL)//这是尾结点,但不是NULL,只是其尾结点指向NULL,与上面是不同的。
			htemp=htemp->nextNode;
			htemp->nextNode=node;
			node->priorNode=htemp;
			return head;
		}
		
	}
}
int LineNodeShowAll(LineNode*head)
{
	LineNode*htemp;
	htemp=head;
	//while(htemp->nextNode!=NULL)//这样会把尾结点给跳过
	while(htemp)
	{
		cout<<setw(5)<<htemp->linedata.x0<<setw(5)<<  htemp->linedata.y0<<setw(5)<<  htemp->linedata.x1<<setw(5)<<  htemp->linedata.y1<<endl;
		htemp=htemp->nextNode;
	}
	return 0;
}
//插入数据
LineNode*LineInsertNode(LineNode*head,LineDxfData linedata)
{
	LineNode*node=NULL;
	LineNode*pstart;//判断是不是起起重合
	LineNode*pend=NULL;//判断是不是止止重合
	LineNode*pSend=NULL;//判断是不是起止重合
	LineNode*pEstart=NULL;//判断是不是止起重合
	LineDxfData lineNewData;
	
	if(!(node=(LineNode*)malloc(sizeof(LineNode))))//申请内存
	{
		printf("申请内存失败!\n");
		return NULL;
	}
	//申请到了内存后,要判断起起相同,止止相同的情况,这涉及到要调换顺序再插入
	//判断是不是起起重合
	pstart=LineFindNode_xyStart(head,linedata);
	if(pstart)//找到了起起重合的结点,那么要把该结点pstart作为新结点的后结点
	{
		lineNewData=LineDataSwap(linedata);
		node->linedata=lineNewData;//新增加的内存存放的是已经经过调换的数据
		//向前插,顺序基本上是从最前开始,这时候pstart之前的结点是最前结点
		node->priorNode=pstart->priorNode;//把原来存有的前结点给出去,空出pstart->priorNode
		pstart->priorNode->nextNode=node;
		node->nextNode=pstart;
		pstart->priorNode=node;//空出来才能放新东西
		return head;
	}
	//判断是不是止止重合
	pend=LineFindNode_xyEnd(head,linedata);
	 if(pend)//找到了止止重合的结点,那么要把该结点pend作为新结点的后结点
	{
		lineNewData=LineDataSwap(linedata);
		node->linedata=lineNewData;//新增加的内存存放的是已经经过调换的数据
		//向后插,赋值的过程其实就是保证现有的值在传出去之前没有被覆盖。
		pend->nextNode->priorNode=node;
		node->nextNode=pend->nextNode;
		node->priorNode=pend;
		pend->nextNode=node;
		return head;
	}
	//判断是不是原有的终点和新加入的起点重合
	pEstart=LineFindNode_xyEstart(head,linedata);
	if(pEstart)//找到了止起重合的结点,那么要把该结点pEstart作为新结点的前结点
	{
		node->linedata=linedata;//新增加的内存存放的是已经经过调换的数据
		//向后插,赋值的过程其实就是保证现有的值在传出去之前没有被覆盖。
		pEstart->nextNode->priorNode=node;
		node->nextNode=pEstart->nextNode;
		node->priorNode=pEstart;
		pEstart->nextNode=node;
		return head;
		
	}
	//判断是不是原有的起点和新加入的终点重合
	pSend=LineFindNode_xyEnd(head,linedata);
	 if(pSend)//找到了止起重合的结点,那么要把该结点pSend作为新结点的后结点
	{
		node->linedata=linedata;//新增加的内存存放的是已经经过调换的数据
		//向前插,顺序基本上是从最前开始,这时候pSend之前的结点是最前结点
		node->priorNode=pSend->priorNode;//把原来存有的前结点给出去,空出pstart->priorNode
		pSend->priorNode->nextNode=node;
		node->nextNode=pSend;
		pSend->priorNode=node;//空出来才能放新东西
		return head;
	}
	//如果存在新加进来的数据与之前的啥关系没有,那么很简单,就是往后插就可以了。
	head=LineNodeAdd(head,linedata);
	return head;
	
	
}
//判断是不是起起重合
LineNode*LineFindNode_xyStart(LineNode*head,LineDxfData linedata)
{
	LineNode*htemp=NULL;
	double tempoint;
	double x_start,y_start,x_end,y_end;//判断用的值
	x_start=linedata.x0;
	y_start=linedata.y0;
	x_end=linedata.x1;
	y_end=linedata.y1;
	htemp=head;//把头结点传给中间结点,让其去搜索
	//*************************************************************************************//
	//如果新传进来的x0,y0与之前存在的任何结点的x0,y0相同,说明这里的数据乱了,
	//因为要期末相连同才是的,不该是起起终终相同,
	//所以要找到这个数据跟之前哪个结点的数据重合,返回这个结点
	//*************************************************************************************//
	while(htemp)//从头开始遍历一遍
	{	
		if((x_start==htemp->linedata.x0)&&(y_start==htemp->linedata.y0))
		{
			return htemp;
		}
		htemp=htemp->nextNode;												
	}
	return NULL;
}
//判断是不是止止重合
LineNode*LineFindNode_xyEnd(LineNode*head,LineDxfData linedata)
{
	LineNode*htemp=NULL;
	double tempoint;
	double x_start,y_start,x_end,y_end;//判断用的值
	x_start=linedata.x0;
	y_start=linedata.y0;
	x_end=linedata.x1;
	y_end=linedata.y1;
	htemp=head;//把头结点传给中间结点,让其去搜索
	//*************************************************************************************//
	//如果新传进来的x0,y0与之前存在的任何结点的x0,y0相同,说明这里的数据乱了,
	//因为要期末相连同才是的,不该是起起终终相同,
	//所以要找到这个数据跟之前哪个结点的数据重合,返回这个结点
	//*************************************************************************************//
	while(htemp)//从头开始遍历一遍
	{	
		if((x_end==htemp->linedata.x1)&&(y_end==htemp->linedata.y1))
		{
			return htemp;
		}
		htemp=htemp->nextNode;												
	}
	return NULL;
}
//判断是不是原有的终点和新加入的起点重合,若是则要往后插
LineNode*LineFindNode_xyEstart(LineNode*head,LineDxfData linedata)
{
	LineNode*htemp=NULL;
	double tempoint;
	double x_start,y_start,x_end,y_end;//判断用的值
	x_start=linedata.x0;
	y_start=linedata.y0;
	x_end=linedata.x1;
	y_end=linedata.y1;
	htemp=head;//把头结点传给中间结点,让其去搜索
	//*************************************************************************************//
	//如果新传进来的x0,y0与之前存在的任何结点的x0,y0相同,说明这里的数据乱了,
	//因为要期末相连同才是的,不该是起起终终相同,
	//所以要找到这个数据跟之前哪个结点的数据重合,返回这个结点
	//*************************************************************************************//
	while(htemp)//从头开始遍历一遍
	{	
		if((x_start==htemp->linedata.x1)&&(y_start==htemp->linedata.y1))
		{
			return htemp;
		}
		htemp=htemp->nextNode;												
	}
	return NULL;
}
//判断是不是原有的起点和新加入的终点重合
LineNode*LineFindNode_xySend(LineNode*head,LineDxfData linedata)
{
	LineNode*htemp=NULL;
	double tempoint;
	double x_start,y_start,x_end,y_end;//判断用的值
	x_start=linedata.x0;
	y_start=linedata.y0;
	x_end=linedata.x1;
	y_end=linedata.y1;
	htemp=head;//把头结点传给中间结点,让其去搜索
	//*************************************************************************************//
	//如果新传进来的x1,y1与之前存在的任何结点的x0,y0相同,说明这里的数据乱了,
	//因为要期末相连同才是的,不该是起起终终相同,
	//所以要找到这个数据跟之前哪个结点的数据重合,返回这个结点
	//*************************************************************************************//
	while(htemp)//从头开始遍历一遍
	{	
		if((x_end==htemp->linedata.x0)&&(y_end==htemp->linedata.y0))
		{
			return htemp;
		}
		htemp=htemp->nextNode;												
	}
	return NULL;
}
//将x0x1y0y1调换
LineDxfData LineDataSwap(LineDxfData linedata)
{
	LineDxfData LineNewdata;
	LineNewdata.x0=linedata.x1;
	LineNewdata.y0=linedata.y1;
	LineNewdata.x1=linedata.x0;
	LineNewdata.y1=linedata.y0;
	return LineNewdata;
}






作者:qq_30495361 发表于2016/8/5 2:21:23 原文链接
阅读:53 评论:1 查看评论

数论----高斯消元

$
0
0

学了一天的高斯消元,又退了两天,才接着补坑,唉~~自己为什么这么不争气~~难过

主要的学习高斯消元的来源还是论文---何江舟的《高斯消元解线性方程组》


注意几点:

1.equ和var分别代表方程数和未知数

2.在代码中注意k和col的实时变化,k循环结束后表示消元后的最后一个行,col循环后的为第var-1列(0~var)

3.注意无穷解的代码段

4.temp=a[i][var];
for(j=0; j<var; j++) {
if(a[i][j]!=0&&j!=free_index) temp-=a[i][j]*x[j];
}
x[free_index]=temp/a[i][free_index];  

   这代码段刚开始看不懂,但是其实就是手算的过程,画一下就应该能够明白,例如a*x+b*y=c,则求x=(c-b*y)/a;

5.注意浮点数的时候的sgn函数,有误差所以要小心



参考链接:

何江舟-高斯消元法解线性方程组

[数论] 高斯消元(整型和浮点型)

高斯消元法(Gauss Elimination) 分析 & 题解 & 模板——czyuan原创

--------------------------------------------------------------------------------------------------------------------------------------

高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵。
高斯消元法的原理是:
若用初等行变换将增广矩阵 化为 ,则AX = B与CX = D是同解方程组。
所以我们可以用初等行变换把增广矩阵转换为行阶梯阵,然后回代求出方程的解。

以上是线性代数课的回顾,下面来说说高斯消元法在编程中的应用。

首先,先介绍程序中高斯消元法的步骤:
(我们设方程组中方程的个数为equ,变元的个数为var,注意:一般情况下是n个方程,n个变元,但是有些题目就故意让方程数与变元数不同)

1. 把方程组转换成增广矩阵。

2. 利用初等行变换来把增广矩阵转换成行阶梯阵。
枚举k从0到equ – 1,当前处理的列为col(初始为0) ,每次找第k行以下(包括第k行),col列中元素绝对值最大的列与第k行交换。如果col列中的元素全为0,那么则处理col + 1列,k不变。

3. 转换为行阶梯阵,判断解的情况。

① 无解
当方程中出现(0, 0, …, 0, a)的形式,且a != 0时,说明是无解的。

② 唯一解
条件是k = equ,即行阶梯阵形成了严格的上三角阵。利用回代逐一求出解集。

③ 无穷解。
条件是k < equ,即不能形成严格的上三角形,自由变元的个数即为equ – k,但有些题目要求判断哪些变元是不缺定的。
    这里单独介绍下这种解法:
首先,自由变元有var - k个,即不确定的变元至少有var - k个。我们先把所有的变元视为不确定的。在每个方程中判断不确定变元的个数,如果大于1个,则该方程无法求解。如果只有1个变元,那么该变元即可求出,即为确定变元。

以上介绍的是求解整数线性方程组的求法,复杂度是O(n3)。浮点数线性方程组的求法类似,但是要在判断是否为0时,加入EPS,以消除精度问题。



整型高斯消元模板:

#include<cstdio>
#include<iostream>
#include <algorithm>
#include <map>
#include <cmath>
#include <cstring>
using namespace std;

const int maxn =50;
int equ,var;         //有equ个方程,var个变元
int a[maxn][maxn] ;  //增广矩阵
int x[maxn];         //解集
bool free_x[maxn];   //标记是否是不确定的变元,初始化为true,确定为0 

void Debug(void) {
	int i,j;
	for(i=0; i<equ; i++) {
		for(j=0; j<var+1; j++) {
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<endl;
}

inline int gcd(int a,int b) {
	if(!b) return a;
	else return gcd(b,a%b);
}

inline int lcm(int a,int b) {
	return a/gcd(a,b)*b;
}
// 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
//-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
//有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var.
int Gauss(int equ,int var) {
	int i,j,k;
	int max_r;     // 当前这列绝对值最大的行.
	int col;       //当前处理的列
	int ta,tb;
	int LCM,temp;
	int free_x_num;
	int free_index;

	for(int i=0; i<=var; i++) {
		x[i]=0;           //初始化解集 
		free_x[i]=true;
	}
    //转换为阶梯阵.
	col=0;        // 当前处理的列
	
	for(k=0; k<equ&&col<var; k++,col++) {
		max_r=k;
		// 枚举当前处理的行.
        // 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
		for(i=k+1; i<equ; i++) {
			if(abs(a[i][col])>abs(a[max_r][col])) max_r=i;
		}
		// 与第k行交换.
		if(max_r!=k) {
			for(j=k; j<var+1; j++) swap(a[k][j],a[max_r][j]);
		}
		
		// 如果该col列第k行以下全是0了,则处理当前行的下一列.
		if(a[k][col]==0) {
			k--;
			continue;
		}
		// 枚举要删去的行.
		for(i=k+1; i<equ; i++) {
			if(a[i][col]!=0) {
				LCM=lcm(abs(a[i][col]),abs(a[k][col]));
				ta=LCM/abs(a[i][col]);
				tb=LCM/abs(a[k][col]);
				if(a[i][col]*a[k][col]<0) tb=-tb;  //异号的情况是相加
				for(j=col; j<var+1; j++) {
					a[i][j]=a[i][j]*ta-a[k][j]*tb;
				}
			}
		}
	}

	Debug();

    // 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
	for(i=k; i<equ; i++) {
		// 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
		if(a[i][col]!=0) return -1;
	}
	
	// 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
    // 且出现的行数即为自由变元的个数.
	if(k<var) {
		
		// 首先,自由变元有var - k个,即不确定的变元至少有var - k个.
		for(i=k-1; i>=0; i--) {
			// 第i行一定不会是(0, 0, ..., 0)的情况,因为这样的行是在第k行到第equ行.
            // 同样,第i行一定不会是(0, 0, ..., a), a != 0的情况,这样的无解的.

			free_x_num=0;// 用于判断该行中的不确定的变元的个数,如果超过1个,则无法求解,它们仍然为不确定的变元.
			for(j=0; j<var; j++) {
				if(a[i][j]!=0&&free_x[j])   free_x_num++,free_index=j;
			}
			if(free_x_num>1) continue;    // 无法求解出确定的变元.
			//说明就只有一个不确定的变元free_index,那么可以求解出该变元,且该变元是确定的.
			temp=a[i][var];
			for(j=0; j<var; j++) {
				if(a[i][j]!=0&&j!=free_index) temp-=a[i][j]*x[j];
			}
			x[free_index]=temp/a[i][free_index];    // 求出该变元.
			free_x[free_index]=0;      // 该变元是确定的.
		}
		return var-k;        // 自由变元有var - k个.
	}
	
	// 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
    // 计算出Xn-1, Xn-2 ... X0.
	for(i=var-1; i>=0; i--) {
		temp=a[i][var];
		for(j=i+1; j<var; j++) {
			if(a[i][j]!=0) temp-=a[i][j]*x[j];
		}
		if(temp%a[i][i]!=0) return -2;       // 说明有浮点数解,但无整数解.
		x[i]=temp/a[i][i];
	}
	return 0;
}

int main() {

#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
#endif
	int i,j;
	while(scanf("%d%d",&equ,&var)!=EOF) {
		memset(a,0,sizeof(a));
		for(i=0; i<equ; i++) {
			for(j=0; j<var+1; j++) {
				scanf("%d",&a[i][j]);
			}
		}
		
		Debug();
		
		int free_num=Gauss(equ,var);
		if(free_num==-1) cout<<"无解"<<endl;
		if(free_num==-2) cout<<"由浮点数解,无整数解"<<endl;
		else if(free_num>0) {
			cout<<"无穷多解!自由变元个数为 "<<free_num<<endl;
			for(int i=0; i<var; i++) {
				if(free_x[i])  cout<<"x "<<(i+1)<<" 是不确定的"<<endl;
				else cout<<"x"<<(i+1)<<" : "<<x[i]<<endl;
			}
		} else {
			for(i=0; i<var; i++) {
				cout<<"x"<<(i+1)<<" : "<<x[i]<<endl;
			}
		}
		cout<<endl;
	}
	return 0;
}

浮点数高斯消元模板:

#include<cstdio>
#include<iostream>
#include <algorithm>
#include <map>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=10005;
const double eps=1e-12;
double a[maxn][maxn];
int equ,var;     //equ个方程,var个变量 
double x[maxn];   //解集 
bool free_x[maxn];
int n;

void Debug(void) {
	int i,j;
	for(i=0; i<equ; i++) {
		for(j=0; j<var+1; j++) {
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
	cout<<endl;
}

//判断浮点数是否在误差范围内看作等于0 
int sgn(double x)
{
	return (x>eps)-(x<-eps);
}
// 高斯消元法解方程组(Gauss-Jordan elimination).(-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数) 
int Gauss(int equ,int var)
{
	int i,j,k;
	int max_r;   // 当前这列绝对值最大的行.  
	int col;     // 当前处理的列.
	double temp;
	int free_x_num;
	int free_index;
	 // 转换为阶梯阵.  
	col=0;    // 当前处理的列. 
	memset(free_x,true,sizeof(free_x));
	for(k=0;k<equ&&col<var;k++,col++)
	{
		max_r=k;
		for(i=k+1;i<equ;i++)
		{
			if(sgn(fabs(a[i][col])-fabs(a[max_r][col]))>0)
		         max_r=i;
 		}
 		if(max_r!=k)
 		{
 			// 与第k行交换.  
 			for(j=k;j<var+1;j++)
 			   swap(a[k][j],a[max_r][j]);
		}
		if(sgn(a[k][col])==0)
		{
			// 说明该col列第k行以下全是0了,则处理当前行的下一列.  
			k--;continue;
		}
		for(i=k+1;i<equ;i++)
		{
			// 枚举要删去的行.  
			if(sgn(a[i][col])!=0)
			{
				temp=a[i][col]/a[k][col];
				for(j=col;j<var+1;j++)
				{
					a[i][j]=a[i][j]-a[k][j]*temp;
				}
			}
		}
	}
	
	Debug();
	
	for(i=k;i<equ;i++)
	{
		if(sgn(a[i][col])!=0)
		return -1;
	}
	if(k<var)
	{
		for(i=k-1;i>=0;i--)
		{
			free_x_num=0;
			for(j=0;j<var;j++)
			{
				if(sgn(a[i][j])!=0&&free_x[j])
				   free_x_num++,free_index=j;
			}
			if(free_x_num>1) continue;
			temp=a[i][var];
			for(j=0;j<var;j++)
			{
				if(sgn(a[i][j])!=0&&j!=free_index)
				    temp-=a[i][j]*x[j];
			}
			x[free_index]=temp/a[i][free_index];
			free_x[free_index]=0;
		}
		return var-k;
	}
	for(i=var-1;i>=0;i--)
	{
		temp=a[i][var];
		for(j=i+1;j<var;j++)
		{
			if(sgn(a[i][j])!=0)
			  temp-=a[i][j]*x[j];
		}
		x[i]=temp/a[i][i];
	}
	return 0;
}
int main() {

#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
#endif
	int i,j;
	while(scanf("%d%d",&equ,&var)!=EOF) {
		memset(a,0,sizeof(a));
		for(i=0; i<equ; i++) {
			for(j=0; j<var+1; j++) {
				scanf("%lf",&a[i][j]);
			}
		}
		
		Debug();
		
		int free_num=Gauss(equ,var);
		if(free_num==-1) cout<<"无解"<<endl;
		else if(free_num>0) {
			cout<<"无穷多解!自由变元个数为 "<<free_num<<endl;
			for(int i=0; i<var; i++) {
				if(free_x[i])  cout<<"x "<<(i+1)<<" 是不确定的"<<endl;
				else cout<<"x"<<(i+1)<<" : "<<x[i]<<endl;
			}
		} else {
			for(i=0; i<var; i++) {
				cout<<"x"<<(i+1)<<" : "<<x[i]<<endl;
			}
		}
		cout<<endl;
	}
	return 0;
}





作者:Lin_disguiser 发表于2016/8/5 2:31:22 原文链接
阅读:46 评论:0 查看评论

史上最全的iOS之UITextView实现placeHolder占位文字的N种方法

$
0
0

前言

iOS开发中,UITextField和UITextView是最常用的文本接受类和文本展示类的控件。UITextField和UITextView都输入文本,也都可以监听文本的改变。不同的是,UITextField继承自UIControl这个抽象类。UITextView继承自UIScrollView这个实体类。这就导致了UITextView可以多行展示内容,并且还可以像UIScrollView一样滚动。而UITextField只能单独的展示一行内容。从这个角度,UITextView在功能上是优于UITextField的。
但是,众所周知,UITextField中有一个placeholder属性,可以设置UITextField的占位文字,起到提示用户输入相关信息的作用。可是,UITextView就没那么幸运了,apple没有给UITextView提供一个类似于placeholder这样的属性来供开发者使用。而开发中,我们经常会遇到既要占位文字,又要可以多行展示并且可以滚动的控件,单纯的UITextField或者UITextView都不能满足这种产品上的需求。比如,现在市面上的app大多都有一个用户反馈的入口,如下图(一)所示。下面我就把自己能够想到的方法汇总一下,让更多的开发者知道,原来有这么多方法可以实现UITextView的占位文字。


图(一)

方法一

1.把UITextView的text属性当成“placeholder”使用。
2.在开始编辑的代理方法里清除“placeholder”。
3.在结束编辑的代理方法里根据条件设置“placeholder”。

特点:这种方法的特点是,当用户点击了textView,placeholder占位文字就会立马消失,官方的placeholder是当系统监听到用户输入了文字后placeholder才会消失。

// 创建textView
UITextView *textView =[[UITextViewalloc]initWithFrame:CGRectMake(20,70,SCREEN.width-40,100)];
textView.backgroundColor= [UIColor whiteColor];
textView.text = @"我是placeholder";
textView.textColor = [UIColor grayColor];
textView.delegate = self;
[self.view addSubview:textView];

#pragma mark - UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView
{
    if(textView.text.length < 1){
        textView.text = @"我是placeholder";
        textView.textColor = [UIColor grayColor];
    }
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    if([textView.text isEqualToString:@"我是placeholder"]){
        textView.text=@"";
        textView.textColor=[UIColor blackColor];
    }
}

方法二

1.创建textView
2.给textView添加一个UILabel子控件,作为placeholder
3.在文本改变的代理方法里面显示/隐藏UILabel

特点:该方法同样也可以实现类似于placeholder的功能。相比较方法一,方法二可以实现动态监听文本的改变,并非弹出键盘就立即清除placeholder,只有当用户开始输入文本的时候。placeholder才会消失。同样,当用户清空文本的时候,placeholder又会重新显示出来。

#import "WSViewController.h"

@interface WSViewController () <UITextViewDelegate>

@property(nonatomic, weak)UITextView *textView;

@property(nonatomic, weak)UILabel *placeHolder;

@end

@implementation WSViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupTextView];

}

// 添加textView
- (void)setupTextView
{
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200)];
    textView.frame = CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200);

    [self.view addSubview:textView];
    self.textView = textView;

    textView.contentInset = UIEdgeInsetsMake(-64, 0, 0, 0);

    textView.delegate = self;
    [self setupPlaceHolder];


    //在弹出的键盘上面加一个view来放置退出键盘的Done按钮
    UIToolbar * topView = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 30)];
    [topView setBarStyle:UIBarStyleDefault];
    UIBarButtonItem * btnSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
    UIBarButtonItem * doneButton = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(dismissKeyBoard)];
    NSArray * buttonsArray = [NSArray arrayWithObjects:btnSpace, doneButton, nil];

    [topView setItems:buttonsArray];
    [textView setInputAccessoryView:topView];

}

// 给textView添加一个UILabel子控件
- (void)setupPlaceHolder
{
    UILabel *placeHolder = [[UILabel alloc] initWithFrame:CGRectMake(15, -2, SCREEN_WIDTH - 2 * 15, 200)];
    self.placeHolder = placeHolder;

    placeHolder.text = @"我是placeholder";
    placeHolder.textColor = [UIColor lightGrayColor];
    placeHolder.numberOfLines = 0;
    placeHolder.contentMode = UIViewContentModeTop;
    [self.textView addSubview:placeHolder];
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
    if (!textView.text.length) {
    self.placeHolder.alpha = 1;
    } else {
        self.placeHolder.alpha = 0;
    }
}

//关闭键盘
-(void) dismissKeyBoard{
    [self.textView resignFirstResponder];
}

@end

同样地思路,我们也可以把作为占位文字的UILabel用UITextField或者UITextView来替换,同样可以实现带placeholder的textView,在次就不在详述。

方法三

1.自定义UITextView
2.给UITextView添加placeholder和placeholderColor属性
3.重写initWithFrame方法
4.添加通知监听文字改变
5.重写drawRect:方法
6.重写相关属性的set方法

特点:相比计较上面两种方法,这种方法可移植性、拓展性更好,这种方法,不仅乐意随意通过我们添加的placeholder属性设置默认文字,还可以通过我们添加的placeholderColor设置默认文字的颜色。今后,我们只需要写好这么一个自定义UITextView,就可以一劳永逸。

#import <UIKit/UIKit.h>

@interface WSPlaceholderTextView : UITextView
/** 占位文字 */
@property (nonatomic, copy) NSString *placeholder;
/** 占位文字颜色 */
@property (nonatomic, strong) UIColor *placeholderColor;
@end

#import "WSPlaceholderTextView.h"

@implementation WSPlaceholderTextView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 设置默认字体
        self.font = [UIFont systemFontOfSize:15];

        // 设置默认颜色
        self.placeholderColor = [UIColor grayColor];

        // 使用通知监听文字改变
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
    }
    return self;
}

- (void)textDidChange:(NSNotification *)note
{
    // 会重新调用drawRect:方法
    [self setNeedsDisplay];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/**
 * 每次调用drawRect:方法,都会将以前画的东西清除掉
 */
- (void)drawRect:(CGRect)rect
{
    // 如果有文字,就直接返回,不需要画占位文字
    if (self.hasText) return;

    // 属性
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;

    // 画文字
    rect.origin.x = 5;
    rect.origin.y = 8;
    rect.size.width -= 2 * rect.origin.x;
    [self.placeholder drawInRect:rect withAttributes:attrs];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self setNeedsDisplay];
}

#pragma mark - setter
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = [placeholder copy];

    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;

    [self setNeedsDisplay];
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];

    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];

    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];

    [self setNeedsDisplay];
}
@end

方法四

1.自定义UITextView
2.给UITextView添加placeholder和placeholderColor属性
3.重写initWithFrame方法
4.重写drawRect:方法
5.重写相关属性的set方法

特点:这个方法的和方法三很相似,只是没有利用通知来监听文本的改变,需要配合textViewDidChanged:这个文本改变的代理方法使用。

#import <UIKit/UIKit.h>

@interface WSTextView : UITextView
/** 占位文字 */
@property (nonatomic,copy) NSString *placeholder;
/** 占位文字颜色 */
@property (nonatomic,strong) UIColor *placeholderColor;
@end

#import "WSTextView.h"

@implementation WSTextView
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.font = [UIFont systemFontOfSize:15];
        self.placeholderColor = [UIColor lightGrayColor];
        self.placeholder = @"请输入内容";
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;

    [self.placeholder drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) withAttributes:attrs];
}

// 布局子控件的时候需要重绘
- (void)layoutSubviews
{
    [super layoutSubviews];
    [self setNeedsDisplay];

}
// 设置属性的时候需要重绘,所以需要重写相关属性的set方法
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = placeholder;
    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;
    [self setNeedsDisplay];

}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];
    if (text.length) { // 因为是在文本改变的代理方法中判断是否显示placeholder,而通过代码设置text的方式又不会调用文本改变的代理方法,所以再此根据text是否不为空判断是否显示placeholder。
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];
    if (attributedText.length) {
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}
@end

// 应用的时候需要配合UITextView的文本改变的代理方法

#import "ViewController.h"
#import "WSTextView.h"

@interface ViewController ()<UITextViewDelegate>

// @property(nonatomic,weak) WSTextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    WSTextView *textView = [[WSTextView alloc] initWithFrame:CGRectMake(10, 20, self.view.frame.size.width, 30)];
    textView.placeholder = @"ws";
    textView.delegate = self;
    [self.view addSubview:textView];
    // textView.text = @"试试会不会调用文本改变的代理方法"; // 不会调用文本改变的代理方法
    textView.attributedText = [[NSAttributedString alloc] initWithString:@"富文本"];

    // self.textView = textView;
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(WSTextView *)textView // 此处取巧,把代理方法参数类型直接改成自定义的WSTextView类型,为了可以使用自定义的placeholder属性,省去了通过给控制器WSTextView类型属性这样一步。
{
    if (textView.hasText) { // textView.text.length
        textView.placeholder = @"";

    } else {
        textView.placeholder = @"ws";

    }
}
@end

方法五

通过runtime,我们发现,UITextView内部有一个名为“_placeHolderLabel”的私有成员变量。大家知道,Objective-C没有绝对的私有变量,因为我们可以通过KVC来访问私有变量。

特点:相对于上面的4种方法,这种方法更加取巧,虽然Apple官方没有给我们开发者提供类似于placeholder的属性,但是通过运行时,我们遍历出了一个placeHolderLabel的私有变量。这种方法简单易懂,代码量少,推荐大家使用这种方法。

#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];


  // 通过运行时,发现UITextView有一个叫做“_placeHolderLabel”的私有变量
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([UITextView class], &count);

    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *objcName = [NSString stringWithUTF8String:name];
        NSLog(@"%d : %@",i,objcName);
    }

    [self setupTextView];

}
- (void)setupTextView
{
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 100];
    [textView setBackgroundColor:[UIColor greenColor]];
    [self.view addSubview:textView];

    // _placeholderLabel
    UILabel *placeHolderLabel = [[UILabel alloc] init];
    placeHolderLabel.text = @"请输入内容";
    placeHolderLabel.numberOfLines = 0;
    placeHolderLabel.textColor = [UIColor lightGrayColor];
    [placeHolderLabel sizeToFit];
    [textView addSubview:placeHolderLabel];

    [textView setValue:placeHolderLabel forKey:@"_placeholderLabel"];

}

@end



文/VV木公子(简书作者)
原文链接:http://www.jianshu.com/p/9edb8be75e0b
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
作者:pengyuan_D 发表于2016/8/5 2:59:56 原文链接
阅读:52 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live