o-u-u

卷积神经网络 — CNN(Convolutional Neural Networks)的入门

 人工智能  卷积神经网络 — CNN(Convolutional Neural Networks)的入门已关闭评论
12月 032020
 

转发一篇介绍《卷积神经网络 — CNN(Convolutional Neural Networks)》的入门文章,觉得写的挺好的,来自: medium.com 

卷积神经网络 — CNN 最擅长的就是图片的处理。它受到人类视觉神经系统的启发。

CNN 有2大特点:

能够有效的将大数据量的图片降维成小数据量

能够有效的保留图片特征,符合图片处理的原则

目前 CNN 已经得到了广泛的应用,比如:人脸识别、自动驾驶、美图秀秀、安防等很多领域。

CNN 解决了什么问题?

在 CNN 出现之前,图像对于人工智能来说是一个难题,有2个原因:

  1. 图像需要处理的数据量太大,导致成本很高,效率很低

下面就详细说明一下这2个问题:

需要处理的数据量太大

图像是由像素构成的,每个像素又是由颜色构成的。

Image for post

现在随随便便一张图片都是 1000×1000 像素以上的, 每个像素都有RGB 3个参数来表示颜色信息。

假如我们处理一张 1000×1000 像素的图片,我们就需要处理3百万个参数!

1000×1000×3=3,000,000

这么大量的数据处理起来是非常消耗资源的,而且这只是一张不算太大的图片!

卷积神经网络 — CNN 解决的第一个问题就是「将复杂问题简化」,把大量参数降维成少量参数,再做处理。

更重要的是:我们在大部分场景下,降维并不会影响结果。比如1000像素的图片缩小成200像素,并不影响肉眼认出来图片中是一只猫还是一只狗,机器也是如此。

保留图像特征

图片数字化的传统方式我们简化一下,就类似下图的过程:

Image for post

假如有圆形是1,没有圆形是0,那么圆形的位置不同就会产生完全不同的数据表达。但是从视觉的角度来看,图像的内容(本质)并没有发生变化,只是位置发生了变化

所以当我们移动图像中的物体,用传统的方式的得出来的参数会差异很大!这是不符合图像处理的要求的。

而 CNN 解决了这个问题,他用类似视觉的方式保留了图像的特征,当图像做翻转,旋转或者变换位置时,它也能有效的识别出来是类似的图像。

那么卷积神经网络是如何实现的呢?在我们了解 CNN 原理之前,先来看看人类的视觉原理是什么?

人类的视觉原理

深度学习的许多研究成果,离不开对大脑认知原理的研究,尤其是视觉原理的研究。

1981 年的诺贝尔医学奖,颁发给了 David Hubel(出生于加拿大的美国神经生物学家) 和TorstenWiesel,以及 Roger Sperry。前两位的主要贡献,是“发现了视觉系统的信息处理”,可视皮层是分级的。

人类的视觉原理如下:从原始信号摄入开始(瞳孔摄入像素 Pixels),接着做初步处理(大脑皮层某些细胞发现边缘和方向),然后抽象(大脑判定,眼前的物体的形状,是圆形的),然后进一步抽象(大脑进一步判定该物体是只气球)。下面是人脑进行人脸识别的一个示例:

Image for post

对于不同的物体,人类视觉也是通过这样逐层分级,来进行认知的:

Image for post

我们可以看到,在最底层特征基本上是类似的,就是各种边缘,越往上,越能提取出此类物体的一些特征(轮子、眼睛、躯干等),到最上层,不同的高级特征最终组合成相应的图像,从而能够让人类准确的区分不同的物体。

那么我们可以很自然的想到:可以不可以模仿人类大脑的这个特点,构造多层的神经网络,较低层的识别初级的图像特征,若干底层特征组成更上一层特征,最终通过多个层级的组合,最终在顶层做出分类呢?

答案是肯定的,这也是许多深度学习算法(包括CNN)的灵感来源。

卷积神经网络-CNN 的基本原理

典型的 CNN 由3个部分构成:

  1. 卷积层

如果简单来描述的话:

卷积层负责提取图像中的局部特征;池化层用来大幅降低参数量级(降维);全连接层类似传统神经网络的部分,用来输出想要的结果。

Image for post

下面的原理解释为了通俗易懂,忽略了很多技术细节,如果大家对详细的原理感兴趣,可以看这个视频《卷积神经网络基础》。

卷积 — — 提取特征

卷积层的运算过程如下图,用一个卷积核扫完整张图片:

Image for post

这个过程我们可以理解为我们使用一个过滤器(卷积核)来过滤图像的各个小区域,从而得到这些小区域的特征值。

在具体应用中,往往有多个卷积核,可以认为,每个卷积核代表了一种图像模式,如果某个图像块与此卷积核卷积出的值大,则认为此图像块十分接近于此卷积核。如果我们设计了6个卷积核,可以理解:我们认为这个图像上有6种底层纹理模式,也就是我们用6中基础模式就能描绘出一副图像。以下就是25种不同的卷积核的示例:

Image for post

总结:卷积层的通过卷积核的过滤提取出图片中局部的特征,跟上面提到的人类视觉的特征提取类似。

池化层(下采样) — — 数据降维,避免过拟合

池化层简单说就是下采样,他可以大大降低数据的维度。其过程如下:

Image for post

上图中,我们可以看到,原始图片是20×20的,我们对其进行下采样,采样窗口为10×10,最终将其下采样成为一个2×2大小的特征图。

之所以这么做的原因,是因为即使做完了卷积,图像仍然很大(因为卷积核比较小),所以为了降低数据维度,就进行下采样。

总结:池化层相比卷积层可以更有效的降低数据维度,这么做不但可以大大减少运算量,还可以有效的避免过拟合。

全连接层 — — 输出结果

这个部分就是最后一步了,经过卷积层和池化层处理过的数据输入到全连接层,得到最终想要的结果。

经过卷积层和池化层降维过的数据,全连接层才能”跑得动”,不然数据量太大,计算成本高,效率低下。

Image for post

典型的 CNN 并非只是上面提到的3层结构,而是多层结构,例如 LeNet-5 的结构就如下图所示:

卷积层 — 池化层- 卷积层 — 池化层 — 卷积层 — 全连接层

Image for post

在了解了 CNN 的基本原理后,我们重点说一下 CNN 的实际应用有哪些。

CNN 有哪些实际应用?

卷积神经网络 — CNN 很擅长处理图像。而视频是图像的叠加,所以同样擅长处理视频内容。下面给大家列一些比较成熟的应用:

图像分类、检索

图像分类是比较基础的应用,他可以节省大量的人工成本,将图像进行有效的分类。对于一些特定领域的图片,分类的准确率可以达到 95%+,已经算是一个可用性很高的应用了。

典型场景:图像搜索…

Image for post

目标定位检测

可以在图像中定位目标,并确定目标的位置及大小。

典型场景:自动驾驶、安防、医疗…

Image for post

目标分割

简单理解就是一个像素级的分类。

他可以对前景和背景进行像素级的区分、再高级一点还可以识别出目标并且对目标进行分类。

典型场景:美图秀秀、视频后期加工、图像生成…

识别

人脸识别已经是一个非常普及的应用了,在很多领域都有广泛的应用。

典型场景:安防、金融、生活…

Image for post

骨骼识别

骨骼识别是可以识别身体的关键骨骼,以及追踪骨骼的动作。

典型场景:安防、电影、图像视频生成、游戏…

Image for post

总结

今天我们介绍了 CNN 的价值、基本原理和应用场景,简单总结如下:

CNN 的价值:

  1. 能够将大数据量的图片有效的降维成小数据量(并不影响结果)

CNN 的基本原理:

  1. 卷积层 — 主要作用是保留图片的特征

CNN 的实际应用:

  1. 图片分类、检索

xcode debug时一直显示的是汇编/内存地址界面的解决方法

 xcode  xcode debug时一直显示的是汇编/内存地址界面的解决方法已关闭评论
11月 132020
 

今天使用xcode单步调试代码时,发现一直进入的是汇编语言界面,显示的都是地址界面,无法定位到代码行,懵逼了,后来发现有个设置打开了,估计哪天误点了,具体位置在:

 

Debug -> Debug WorkFlow ->   Always Show Disassembly      (这项不要打勾 /  uncheck)

 

DONE!

常用加解密及摘要算法介绍及使用场景–MD5,SHA1,AES,DES,3DES,RSA,ECC

 加解密  常用加解密及摘要算法介绍及使用场景–MD5,SHA1,AES,DES,3DES,RSA,ECC已关闭评论
10月 292020
 

网上看到的一篇常用加解密算法、摘要算法的介绍文章,转载来自CSDN

数字签名信息加密是前后端开发经常使用的技术。应用场景包括:用户登录,交易,信息通讯等。

1、数字签名

数字签名 简单来说就是通过提供可鉴别 的数字信息 验证自身身份的一种方式。

一套 数字签名 通常定义两种 互补的运算,一个用于签名,另一个用于验证,分别  发送者持有能够代表自己的私钥,由接受者持有的与私钥对应的公钥,能够在接受到来自发送者信息时候用于验证其身份。

2、加密和解密

2.1 加密: 数据加密 基本过程,就是对来的铭文的文件或者数据 按照 某种算法 进行处理,使其成为不可读的一段代码,通常成为 “密文”。通过这样的途径,来达到保护数据 不被别人 非法窃取,阅读的目的。

2.2 解密:加密的逆过程为解密,将 编码的信息 转为为 原有的数据 的过程。

3、对称加密和非对称加密

面试相关问题:举例说一下有哪些常用的对称算法?及其使用场景。
常见的对称加密算法:DES3DESAES

常见的非对称加密算法:RSADSA

散列算法:SHA-1MD5等,其应用场景?

3.1、对称加密(共享密钥加密算法)

在对称加密算法中,使用的密钥只有一个,发送者  接受者 双方都是用这一个密钥进行 加密  解密。这就要求通信双方都必须事先知道这个密钥。

  • 数据加密过程:在对称加密算法中,数据发送方  明文 和加密密钥 一起经过特殊加密处理,生成 密文 进行发送。
  • 数据解密过程:数据接收方,收到密文后,若想读取原数据,则需要使用加密使用的密钥 即相同算法的 逆算法 对密文进行解密,才可以恢复到 可读的明文。

3.2、非对称加密算法(公开密钥加密算法)

在非对称加密算法中,她需要两个密钥,一个称为公开密钥,另一个为私有密钥。因为加密和解密使用的密钥不同,所以称为非对称加密算法。

  • 如果使用公钥对数据进行加密,只有用对应的私钥才可以对其进行解密。
  • 如果使用私钥对数据进行加密,只有用对应的公钥才可以对其进行解密。

4、常用的签名加密算法

4.1MD5算法 (相比于SHA1 安全性低,但是 速度快)

MD5算法用的是哈希函数,他的典型应用是对于一段信息产生信息摘要,以防止被篡改。严格来说MD5不算一种加密算法,而是一种 摘要算法。无论多长的输入,MD5都会输出长度为128bits的一个串(通常用16进制表示32个字符)。


public class MD5 {

    public static final byte[] computeMD5(byte[] content) {

        try {

            MessageDigest md5 = MessageDigest.getInstance(“MD5”);

            return md5.digest(content);

        } catch (NoSuchAlgorithmException e) {

            throw new RuntimeException(e);

        }

    }



    public static void main(String[] args) {

        String s1 = “hello world”;

        byte[] bytes = s1.getBytes();

        System.out.println(s1 +加密后的密文为:+computeMD5(bytes));

    }

}


// 输出结果:hello world经过MD5加密后的密文为:[[email protected]

 

4.2 SHA1算法 (相比于MD5 安全性高,但是 速度慢)

SHA1算法和MD5算法是一样流行 消息摘要算法,然而SHA1的安全性会高于MD5。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。


public class SHA1 {
    public static byte[] computeSHA1(byte[] content) {

        try {

            MessageDigest sha1 = MessageDigest.getInstance(“SHA1”);

            return sha1.digest(content);

        } catch (NoSuchAlgorithmException e) {

            throw new RuntimeException(e);

        }

    }



    public static void main(String[] args) {

        String s1 = “hello world”;

        byte[] bytes = s1.getBytes();

        System.out.println(s1 +加密后的密文为:+computeSHA1(bytes));

    }

}


// 输出结果:hello world经过SHA1加密后的密文为:[[email protected]

 

根据MD5SHA1的信息摘要及不可逆的特性,应用场景有 

  • 密码验证:服务器端存储用户密码加密后的内容,每次密码校验比较的是密文是否相同,确保服务器管理员也无法获取到用户使用的密码。
  • 文件的完整性比较:当下载一个文件时,服务器返回的信息中包括这个文件的MD5(或者SHA1),在本地下载完毕将其进行MD5加密,之后比较两个MD5只进行比较,如果一直则说明文件完整不存在丢包现象。
  • 文件上传:在上传文件信息的时候,将该文件的MD5同时上传给服务器。服务器中存储了这个文件MD5,并存储这个MD5只对应的已上传的字节长度,比如未上传为0,已完成为-1,已上传200自己,则值为200。可以用于匹配该文件在服务器中的状态,方便断点再传。只要源文件没有改,就算文件改了名字,换个账户都可以在服务器中找到对应的文件,避免存储多份相同文件,并可以提高二次上传时的速度。
  • 版权验证:当一个视频或者音创作出来的时候他的MD5是唯一的,翻录过后的版本的MD5会不同,可以用于版权验证。

4.3 HMAC算法

是一个密钥相关的 哈希运算消息认证码,HMAC运算利用 哈希算法(MD5SHA1等),以一个密钥  一个消息作为输入,生成一个消息摘要 作为输出。HMAC 发送方和接收方都有这个key进行计算,而没有这个key的第三方,则无法计算出正确的散列值,这样就可以防止数据被篡改。 该算法在多线程环境下是不安全的。

对称算法和非对称算法:

4.4 对称算法——AES/DES/3DES算法

AES/DES/3DES 都是对称的块加密算法,加解密过程是可逆的,

4.4.1 AES算法

AES加密算法是密码学中的高级加密标准,该加密算法采用的是 对称分组密码体制,密钥长度最少支持 128位,192位,256位,因而有AES128AES192AES256等常用的加密方式。分组长度为128位,算法更易于各种硬件和软件的实现。

AES本身就是为了取代DES的,AES具有更好的安全性,效率和灵活性。

4.4.2 DES算法

DES加密算法是一种 分组密码,以64位位 分组对数据加密,他的密钥长度是56位,加密和解密用同一个算法。
DES加密算法对于密钥进行保密,而公开算法,包括加密和解密算法。这样只有掌握了和发送方 相同密钥的人才能来解读由DES进行加密的密文数据。

4.4.3 3DES算法

是基于DES的对称算法,对 一块数据  三个不同的密钥 进行三次加密,安全性更高。
 

4.5 非对称算法——RSAECC算法

4.5.1 RSA算法

RSA算法是目前最具影响力的公钥加密算法。RSA是第一个同事用于加密和数字签名的算法,它能够抵抗到目前为止已知的所有密码攻击

RSA算法基于一个十分简单的数论试试,将两个大素数相乘十分容易,但是想要对乘积进行因式分解确十分困难,因此可以将乘积公开作为加密密钥。

4.5.2 ECC算法

ECC算法的主要优势是,在某一些情况下,它可以生成比其他方法更小的密钥,比如RSA算法,并提供相当或者更高级别的安全等级。不过他的缺点是 加密和解密操作的实现会比其他机制时间要长,对于CPU的消耗严重。

utm   详细X
基本翻译
abbr. 统一威胁管理(Unified Threat Management);通用横墨卡托投影(Universal Transverse Mercator projection)
网络释义
UTM: 统一威胁管理
UTM UTM: 统一威胁管理
UTM-: 通用横向墨卡托图

标准SQL下分区函数Partition By结合row_number()的用法,以及排序rank()的用法详解(转)

 数据库  标准SQL下分区函数Partition By结合row_number()的用法,以及排序rank()的用法详解(转)已关闭评论
10月 222020
 
网上看到的一篇文章: 描述分区函数Partition By结合row_number()的用法,以及排序rank()的用法
,比如获取分组(分区)中前几条记录。

partition by关键字是分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,partition by用于给结果集分组,如果没有指定那么它把整个结果集作为一个分组,分区函数一般与排名函数一起使用。

准备测试数据:

复制代码
create table Student  --学生成绩表
(
 id int,  --主键
 Grade int, --班级
 Score int --分数
)
go

insert into Student values(1,1,88)
insert into Student values(2,1,66)
insert into Student values(3,1,75)
insert into Student values(4,2,30)
insert into Student values(5,2,70)
insert into Student values(6,2,80)
insert into Student values(7,2,60)
insert into Student values(8,3,90)
insert into Student values(9,3,70)
insert into Student values(10,3,80)
insert into Student values(11,3,80)
复制代码

一、分区函数Partition By的与row_number()的用法

1、不分班按学生成绩排名

select *,row_number() over(order by Score desc) as Sequence from Student

执行结果:

2、分班后按学生成绩排名

select *,row_number() over(partition by Grade order by Score desc) as Sequence from Student

执行结果:

3、获取每个班的前1(几)名

select * from
(
select *,row_number() over(partition by Grade order by Score desc) as Sequence from Student
)T where T.Sequence<=1

执行结果:

 

二、分区函数Partition By与排序rank()的用法

1、分班后按学生成绩排名 该语句是对分数相同的记录进行了同一排名,例如:两个80分的并列第2名,第4名就没有了

select *,rank() over(partition by Grade order by Score desc) as Sequence from Student

执行结果:

2、获取每个班的前2(几)名 该语句是对分数相同的记录进行了同一排名,例如:两个80分的并列第2名,第4名就没有了

select * from
(
select *,rank() over(partition by Grade order by Score desc) as Sequence from Student
)T where T.Sequence<=2

执行结果:

android中SpannableStringBuilder的使用(转)

 android  android中SpannableStringBuilder的使用(转)已关闭评论
10月 132020
 
关于SpannableStringBuilder使用的文章,分享下
1.简介

SpannableStringBuilder和SpannableString也可以用来存储字符串,它们俩都有SetSpan()方法,可以对字符串设置背景色,字体大小颜色,下划线,删除线,粗体斜体等。
SpannableStringBuilder和SpannableString的区别:
SpannableString在构造对象的时候必须一次传入,之后无法再更换String,
SpannableStringBuilder可以使用append方法不断的拼接多个String。
因为Spannable等最终都实现了CharSequence接口,所以可以直接把SpannableString和SpannableStringBuilder通过TextView.setText()设置给TextView。

2.setSpan()

 /*给特定范围的字符串设定Span样式
 * Falg参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作
 * what 对应的各种Span 
 * start Span指定的开始位置,索引从0开始
 * end  Span指定的结束的位置,不包括尾,()包含头不包含尾
 * flags   用来指定范围前后输入新的字符时,会不会应用效果的
  ----Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式 
  ----Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
  ----Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括。
  ----Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括。
 */

void setSpan(Object what, int start, int end, int flags)

演示

 EditText et = (EditText) findViewById(R.id.edittext);
        SpannableStringBuilder stringBuilder =new SpannableStringBuilder("我来测试flags属性");
        ForegroundColorSpan span = new ForegroundColorSpan(Color.RED);
        stringBuilder.setSpan(span,1,3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        et.setText(stringBuilder);

这里用的是 flag : Spanned.SPAN_INCLUSIVE_EXCLUSIVE,在前面插入新字符会改变字体颜色,但范围后面插入的新字符字体颜色不变。

spanablestring_01.png

插入新字符后效果

spanstring_02.png
改变字体大小

AbsoluteSizeSpan span = new AbsoluteSizeSpan(16);

spanstring_text_size.png
改变字体背景颜色

BackgroundColorSpan span = new BackgroundColorSpan(Color.BLUE);

spanstring_text_background.png
改变字体为粗体斜体

StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC);//粗体斜体

spanstring_bold.png
删除线

StrikethroughSpan span = new StrikethroughSpan();//删除线

spanstring_strikethrough.png
下划线

UnderlineSpan span = new UnderlineSpan();//下划线

spanstring_underline.png
文字换为图片

Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);

spanstring_text_to_img.png

示例 (区间价格的显示)
生成效果

spannablestringbuilder_price.png

TextView tvPrice =(TextView)findViewById(R.id.tv_price);
tvPrice.setText(formatPriceString(this,"100.00","230.87","件"));

public static SpannableStringBuilder formatPriceString(Context context, String minPrice, String maxPrice, String unit) {
        try {
            while (minPrice.contains(".") && (minPrice.endsWith("0") || minPrice.endsWith("."))) {
                minPrice = minPrice.subSequence(0, minPrice.length() - 1).toString();
            }
            while (maxPrice.contains(".") && (maxPrice.endsWith("0") || maxPrice.endsWith("."))) {
                maxPrice = maxPrice.subSequence(0, maxPrice.length() - 1).toString();
            }

            Float minPriceFloat = Float.valueOf(minPrice);//100.0
            Float maxPriceFloat = Float.valueOf(maxPrice);//230.87
            DecimalFormat decimalFormat = new DecimalFormat("#0.00");
            boolean isBig = false;
            if (minPriceFloat > tenThousand || maxPriceFloat > tenThousand) {
                isBig = true;
                minPriceFloat = minPriceFloat / 10000;
                maxPriceFloat = maxPriceFloat / 10000;
                minPrice = decimalFormat.format(minPriceFloat);
                maxPrice = decimalFormat.format(maxPriceFloat);
            }
            SpannableStringBuilder builder = new SpannableStringBuilder("价格:¥");
            int dp12 = getPixByDp(12, context);
            int length = builder.length();
            builder.setSpan(new AbsoluteSizeSpan(dp12), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);//字体大小
            builder.setSpan(new ForegroundColorSpan(Color.parseColor("#919191")), 0, length - 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);//字体颜色
            builder.setSpan(new ForegroundColorSpan(Color.parseColor("#eb413d")), length - 1, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

            SpannableString priceSpan = new SpannableString(minPrice + "~" + maxPrice);
            int dp16 = getPixByDp(16, context);
            int lengthPrice = priceSpan.length();
            priceSpan.setSpan(new AbsoluteSizeSpan(dp16), 0, lengthPrice, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            priceSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#eb413d")), 0, lengthPrice, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            builder.append(priceSpan);
            SpannableString unitSpan;
            if (!isBig) {
                unitSpan = new SpannableString("元/" + unit);
            } else {
                unitSpan = new SpannableString("万元/" + unit);
            }
            int lengthUnit = unitSpan.length();
            unitSpan.setSpan(new AbsoluteSizeSpan(dp12), 0, lengthUnit, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            unitSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#919191")), 0, lengthUnit, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            builder.append(unitSpan);
            return builder;
        } catch (Exception e) {

        }
        return new SpannableStringBuilder("");
    }

链接:https://www.jianshu.com/p/a157cd9297b5

android下使用setMovementMethod为TextView添加链接(转)

 android  android下使用setMovementMethod为TextView添加链接(转)已关闭评论
10月 132020
 

为TextView设置链接:

当文字中出现URL、E-mail、电话号码等的时候,我们为TextView设置链接。总结起来,一共有4种方法来为TextView实现链接。我们一一举例介绍;

1. 在xml里添加android:autoLink属性。
android:autoLink :的可选值:none/web/email/phone/map/all,分别代表将当前文本设置为:
普通文本/URL/email/电话号码/map/自动识别,文本显示为可点击的链接。其中:设置为all时,系统会自动根据你的文本格式识别文本类型,如:http为web,tel为电话等;当然,以上内容也可以在Java代码中完成,用法为tv.setAutoLinkMask(Linkify.ALL)。

2. 将显示内容写到资源文件,一般为String.xml中,并且用<a>标签来声明链接,然后激活这个链接,激活链接需要在Java代码中使用setMovementMethod()方法设置TextView为可点击。

3. 用Html类的fromHtml()方法格式化要放到TextView里的文字。然后激活这个链接,激活链接需要在Java代码中使用setMovementMethod()方法设置TextView为可点击。

4. 用Spannable或实现它的类,如SpannableString。与其他方法不同的是,Spannable对象可以为个别字符设置链接(当然也可以为个别字符设置颜色、字体等,实现某些字符高亮显示的效果等)。这个方法同样需要在Java代码中使用setMovementMethod()方法设置TextView为可点击。

三、例:
对于以上内容,我在一个Activity来分别演示:

1.新建set_m_m.xml,这是一个Layout,代码如下:

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

<!–用autoLink=”none”设置普通文本–>
<TextView
android:id=”@+id/mm_tv1_1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”
android:autoLink=”none”/>

<!–用autoLink=”phone”设置电话–>
<TextView
android:id=”@+id/mm_tv1_2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”
android:autoLink=”phone”/>

<!–用autoLink=”all”自动识别–>
<TextView
android:id=”@+id/mm_tv1_3″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”
android:autoLink=”all”/>

<!–将显示内容写到String.xml中–>
<TextView
android:id=”@+id/mm_tv2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”
android:text=”@string/link_string”/>

<!–用Html类的fromHtml()方法–>
<TextView
android:id=”@+id/mm_tv3″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”/>

<!–用Spannable或实现它的类–>
<TextView
android:id=”@+id/mm_tv4″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”/>

</LinearLayout>

2.新建SetMMActivty.java,这是一个活动,代码如下:

package cpj.com.UI_TextView;

import android.os.Bundle;
import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.widget.TextView;

import cpj.com.MyTool.BaseActivity;
import cpj.com.cpjtest.R;

/**
* Created by cpj on 2016/4/27.
*/
public class SetMMActivity extends BaseActivity{

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

//(方法一)用autoLink=”none”设置普通文本
TextView web_tv = (TextView) findViewById(R.id.mm_tv1_1);
web_tv.setText(“(1_1)百度:https://www.baidu.com/”);

//(方法一)用autoLink=”phone”设置电话
TextView phone_tv = (TextView) findViewById(R.id.mm_tv1_2);
phone_tv.setText(“(1_2)电话:15800000000”);

//(方法一)用autoLink=”all”自动识别
TextView all_tv = (TextView) findViewById(R.id.mm_tv1_3);
all_tv.setText(“(1_3)百度:https://www.baidu.com/”);

//(方法二)将显示内容写到String.xml中
TextView string_tv = (TextView) findViewById(R.id.mm_tv2);
string_tv.setMovementMethod(LinkMovementMethod.getInstance());//激活链接

//(方法三)用Html类的fromHtml()方法
TextView html_tv = (TextView) findViewById(R.id.mm_tv3);
html_tv.setText(
Html.fromHtml(
“(3)百度:” + “<a href=’http://www.baidu.com’>链接到百度</a> “)
);
html_tv.setMovementMethod(LinkMovementMethod.getInstance());//激活链接

//(方法四)用Spannable或实现它的类
TextView spannable_tv = (TextView) findViewById(R.id.mm_tv4);
SpannableString ss = new SpannableString(“(4)百度: 点我就可以访问百度首页”);
ss.setSpan(new URLSpan(“http://www.baidu.com”), 7, 18,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);//设置4~18为网站链接
spannable_tv.setText(ss);
spannable_tv.setMovementMethod(LinkMovementMethod.getInstance());//激活链接
}
}

3.用到的String.xml资源,代码如下:

<string name=“link_string”>

(2)百度:<a href=“http://www.baidu.com”>点我进入百度首页!</a>
</string>

最终的执行结果,如下图:


android开源EventBus框架使用介绍

 android  android开源EventBus框架使用介绍已关闭评论
10月 102020
 

网上一篇关于android eventBus框架使用的文章,分享下,来自:https://blog.csdn.net/sdsxtianshi/article/details/80449804

 

1. EventBus 框架

    EventBus,顾名思义即事件总线,是针对Android跨进程、线程通信的优化方案,在一定程度上可以代替Handle、Intent、Brodcast等实现通信;如下图所示即EventBus的运行框架。
这里写图片描述
     在EventBus中主要有以下三个成员

  • Event:事件,可以自定义为任意对象,类似Message类的作用;
  • Publisher:事件发布者,可以在任意线程、任意位置发布Event,已发布的Evnet则由EventBus进行分发;
  • Subscriber:事件订阅者,接收并处理事件,需要通过register(this)进行注册,而在类销毁时要使用unregister(this)方法解注册。每个Subscriber可以定义一个或多个事件处理方法,其方法名可以自定义,但需要添加@Subscribe的注解,并指明ThreadMode(不写默认为Posting)。
1.1 五种ThreadMode
  • Posting:直接在事件发布者所在线程执行事件处理方法;
  • Main:直接在主线程中执行事件处理方法(即UI线程),如果发布事件的线程也是主线程,那么事件处理方法会直接被调用,并且未避免ANR,该方法应避免进行耗时操作;
  • MainOrdered:也是直接在主线程中执行事件处理方法,但与Main方式不同的是,不论发布者所在线程是不是主线程,发布的事件都会进入队列按事件串行顺序依次执行;
  • BACKGROUND:事件处理方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么事件处理方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。
  • Async:不管发布者的线程是不是主线程,都会开启一个新的线程来执行事件处理方法。如果事件处理方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。为避免触发大量的长时间运行的事件处理方法,EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程以限制并发线程的数量。
    后面会通过代码展示五种ThreadMode的工作方式。

2. EventBus的使用流程

1. build.gradle 中添加EventBus的依赖:

dependencies {
    ...
    compile 'org.greenrobot:eventbus:3.1.1'
}

2. 定义Event事件类:

public class myEvent {
    private String mMessage;
            ...
}

3. 添加订阅事件:

EventBus.getDefault().register(this);

4. 定义事件处理方法并添加注解,参数为定义的事件类:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(myEvent me){
            ...
}

5. 发布事件以触发事件处理方法:

EventBus.getDefault().post(new myEvent("this is first message !"));

3. EventBus应用

以下Demo以5中不同的ThreadMode定义了5个事件处理方法,并新开启一个线程作为事件发布者。

3.1 定义Event时间类
/**
 * Created by wise on 2018/5/16.
 */
/**Event事件类**/
public class myEvent {
    private String mMessage;
    public myEvent(String message){
        this.mMessage = message;
    }

    public void setMessage(String message){
        this.mMessage = message;
    }

    public String getmessage(){
        return this.mMessage;
    }

}
3.2 EventBus功能实现
public class EventBusActivity extends AppCompatActivity {
    private static final String TAG = "EventBusActivity";
    private TextView tv_Event;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_bus);
        /**注册事件**/
        EventBus.getDefault().register(this);
        Thread thread1 = new Thread(new myThread1());
        thread1.start();
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMain(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventMain" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
    public void onEventMainOrdered(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventMainOrdered" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onEventPosting(myEvent te){
      Log.d(TAG,te.getmessage() + " onEventPosting" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onEventBackground(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventBackground" + " thread:" + android.os.Process.myTid());
    }
    /**事件处理方法**/
    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onEventAsync(myEvent me){
        Log.d(TAG,me.getmessage() + " onEventAsync" + " thread:" + android.os.Process.myTid());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**解注册**/
        EventBus.getDefault().unregister(this);
    }

    public class myThread1 implements Runnable {

        @Override
        public void run() {
            Log.d(TAG,"" + android.os.Process.myTid());
            try {
                Thread.sleep(1000);
                /**事件发布**/
                EventBus.getDefault().post(new myEvent("this is first message !"));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

执行结果如下:

05-24 17:17:47.335 24447 24478 D EventBusActivity: 24478
05-24 17:17:48.341 24447 24478 D EventBusActivity: this is first message ! onEventBackground thread:24478
05-24 17:17:48.341 24447 24498 D EventBusActivity: this is first message ! onEventAsync thread:24498
05-24 17:17:48.342 24447 24447 D EventBusActivity: this is first message ! onEventMain thread:24447
05-24 17:17:48.342 24447 24447 D EventBusActivity: this is first message ! onEventMainOrdered thread:24447
05-24 17:17:48.344 24447 24478 D EventBusActivity: this is first message ! onEventPosting thread:24478

 

主线程Tid为24447,子线程Tid为24478,可以看到每种ThreadMode的运行方式。另外我们可以通过定义不同的事件类作为post的参数来执行不同的事件执行方法,后一篇会分析源码解释调用事件处理方法的逻辑。

4. EventBus的粘性事件

以上的Demo中,事件订阅者的注册必须在发布事件之前,否则发布之后,订阅者无法接受到事件,而粘性事件则避免了这一问题,粘性事件的发布使用postSticky()方法即可,并在注解中配置sticky参数。

// 订阅粘性事件
@Subscribe(sticky = true)
public void onMessageEvent(myEvent me) {
    ...
}

// 粘性事件发布
EventBus.getDefault().postSticky(new myEvent ("This is sticky event"));

发布一个粘性事件之后,EventBus将在内存中缓存该粘性事件。当有订阅者订阅了该粘性事件,订阅者将接收到该事件。需要注意的是在未移除粘性事件之前,它会一直缓存在内存中,因此在处理完该事件后要及时移除该事件的缓存,移除粘性事件的方法如下:

// 移除指定的粘性事件
removeStickyEvent(Object event);

// 移除指定类型的粘性事件
removeStickyEvent(Class<T> eventType);

// 移除所有的粘性事件
removeAllStickyEvents();

5. EventBus中的优先级

在定义事件处理方法时,还可以在注解中设置该方法的优先级:

@Subscribe(priority = 1)
public void onEvent(myEvent me) {
        ...
}

 

默认情况下,订阅者方法的事件传递优先级为0。数值越大,优先级越高。在相同的线程模式下,更高优先级的订阅者方法将优先接收到事件。注意:优先级只有在相同的线程模式下才有效。

kotlin下 “android.os.NetworkOnMainThreadException” 或 “Only the original thread that created a view hierarchy can touch its views” 解决

 开发  kotlin下 “android.os.NetworkOnMainThreadException” 或 “Only the original thread that created a view hierarchy can touch its views” 解决已关闭评论
9月 272020
 

kotlin下使用协程时,出现错误提示android.os.NetworkOnMainThreadException 或 Only the original thread that created a view hierarchy can touch its views, 可以使用下面的方法解决:

 

1. 使用协程需build.gradle先加入

dependencies {

implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9”
}

 

2.  套用下面的模版:

GlobalScope.launch {
    //TODO("Background processing...")
    withContext(Dispatchers.Main) {
        // TODO("Update UI here!")
    }
    TODO("Continue background processing...")
}

kotlin下泛型使用中in / out 区别

 kotlin  kotlin下泛型使用中in / out 区别已关闭评论
9月 242020
 

在看kotlin泛型资料时,对于使用in或out看得很头大:

  1. 根据需要,泛型参数可以扮演两种角色:生产者(producer)-> out 或消费者(consumer)-> in。生产者角色就意味着泛型参数可读而不可写;消费者角色则相反,可写而不可读。out关键字表明,泛型参数将扮演可读而不可写的生产者角色。也就是说,不能再用var关键字,需要改用val.
  2. 顺便说一下,你可能听到有人用协变(covariance)和逆变(contravariance)来描述outin的用处。

 

最终总结下其实如何使用就是:

  • 父类泛型对象可以赋值给子类泛型对象,用 in;
  • 子类泛型对象可以赋值给父类泛型对象,用 out。

Done!!

PM2 如何优雅的重新加载配置文件(How to gracefully reload JSON/JS file)

 Nodejs  PM2 如何优雅的重新加载配置文件(How to gracefully reload JSON/JS file)已关闭评论
9月 102020
 

PM2使用配置文件启动,但如果修改了配置文件,使用如下命令几乎都无效(invalid)

  1. pm2 restart config.js
  2. pm2 reload config.js

似乎只有下面的粗暴方法才有效(roughly):

  1. pm2 kill
  2. pm2 start config.js

 

其实可以使用下面的命令实现优雅的重载配置文件(gracefully reload config JSON/JS file)

pm2 startOrReload config.js –update-env

 

备注:

pm2 -h 可以看到参数startOrReload , –update-env的解释:

–update-env     

force an update of the environment with restart/reload (-a <=> apply)

 

startOrReload <json>

start or gracefully reload JSON file

9个提升逼格的redis命令(keys,scan,slowlog,rename-command,bigkeys,monitor,info,config,set)

 redis  9个提升逼格的redis命令(keys,scan,slowlog,rename-command,bigkeys,monitor,info,config,set)已关闭评论
9月 082020
 

9个提升逼格的redis命令

非常好的文章,特别是使用bigkeys查找占大内存的key真是方便,推荐https://www.jianshu.com/p/4df5f2356de9

keys

我把这个命令放在第一位,是因为笔者曾经做过的项目,以及一些朋友的项目,都因为使用keys这个命令,导致出现性能毛刺。这个命令的时间复杂度是O(N),而且redis又是单线程执行,在执行keys时即使是时间复杂度只有O(1)例如SET或者GET这种简单命令也会堵塞,从而导致这个时间点性能抖动,甚至可能出现timeout。

强烈建议生产环境屏蔽keys命令(后面会介绍如何屏蔽)。

scan

既然keys命令不允许使用,那么有什么代替方案呢?有!那就是scan命令。如果把keys命令比作类似select * from users where username like '%afei%'这种SQL,那么scan应该是select * from users where id>? limit 10这种命令。

官方文档用法如下:

SCAN cursor [MATCH pattern] [COUNT count]

初始执行scan命令例如scan 0。SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。当SCAN命令的游标参数被设置为0时,服务器将开始一次新的迭代,而当redis服务器向用户返回值为0的游标时,表示迭代已结束,这是唯一迭代结束的判定方式,而不能通过返回结果集是否为空判断迭代结束。

使用方式:

127.0.0.1:6380> scan 0
1) "22"
2)  1) "23"
    2) "20"
    3) "14"
    4) "2"
    5) "19"
    6) "9"
    7) "3"
    8) "21"
    9) "12"
   10) "25"
   11) "7"

返回结果分为两个部分:第一部分即1)就是下一次迭代游标,第二部分即2)就是本次迭代结果集。

slowlog

上面提到不能使用keys命令,如果就有开发这么做了呢,我们如何得知?与其他任意存储系统例如mysql,mongodb可以查看慢日志一样,redis也可以,即通过命令slowlog。用法如下:

SLOWLOG subcommand [argument]

subcommand主要有:

  • get,用法:slowlog get [argument],获取argument参数指定数量的慢日志。
  • len,用法:slowlog len,总慢日志数量。
  • reset,用法:slowlog reset,清空慢日志。

执行结果如下:

127.0.0.1:6380> slowlog get 5
1) 1) (integer) 2
   2) (integer) 1532656201
   3) (integer) 2033
   4) 1) "flushddbb"
2) 1) (integer) 1  ----  慢日志编码,一般不用care
   2) (integer) 1532646897  ----  导致慢日志的命令执行的时间点,如果api有timeout,可以通过对比这个时间,判断可能是慢日志命令执行导致的
   3) (integer) 26424  ----  导致慢日志执行的redis命令,通过4)可知,执行config rewrite导致慢日志,总耗时26ms+
   4) 1) "config"
      2) "rewrite"

命令耗时超过多少才会保存到slowlog中,可以通过命令config set slowlog-log-slower-than 2000配置并且不需要重启redis。注意:单位是微妙,2000微妙即2毫秒。

rename-command

为了防止把问题带到生产环境,我们可以通过配置文件重命名一些危险命令,例如keys等一些高危命令。操作非常简单,只需要在conf配置文件增加如下所示配置即可:

rename-command flushdb flushddbb
rename-command flushall flushallall
rename-command keys keysys

bigkeys

随着项目越做越大,缓存使用越来越不规范。我们如何检查生产环境上一些有问题的数据。bigkeys就派上用场了,用法如下:

redis-cli -p 6380 --bigkeys

执行结果如下:

... ...
-------- summary -------

Sampled 526 keys in the keyspace!
Total key length in bytes is 1524 (avg len 2.90)

Biggest string found 'test' has 10005 bytes
Biggest   list found 'commentlist' has 13 items

524 strings with 15181 bytes (99.62% of keys, avg size 28.97)
2 lists with 19 items (00.38% of keys, avg size 9.50)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

最后5行可知,没有set,hash,zset几种数据结构的数据。string类型有524个,list类型有两个;通过Biggest ... ...可知,最大string结构的key是test,最大list结构的key是commentlist

需要注意的是,这个bigkeys得到的最大,不一定是最大。说明原因前,首先说明bigkeys的原理,非常简单,通过scan命令遍历,各种不同数据结构的key,分别通过不同的命令得到最大的key:

  • 如果是string结构,通过strlen判断;
  • 如果是list结构,通过llen判断;
  • 如果是hash结构,通过hlen判断;
  • 如果是set结构,通过scard判断;
  • 如果是sorted set结构,通过zcard判断。

正因为这样的判断方式,虽然string结构肯定可以正确的筛选出最占用缓存,也可以说最大的key。但是list不一定,例如,现在有两个list类型的key,分别是:numberlist–[0,1,2],stringlist–[“123456789123456789”],由于通过llen判断,所以numberlist要大于stringlist。而事实上stringlist更占用内存。其他三种数据结构hash,set,sorted set都会存在这个问题。使用bigkeys一定要注意这一点。

monitor

假设生产环境没有屏蔽keys等一些高危命令,并且slowlog中还不断有新的keys导致慢日志。那我们如何揪出这些命令是由谁执行的呢?这就是monitor的用处,用法如下:

redis-cli -p 6380 monitor

如果当前redis环境OPS比较高,那么建议结合linux管道命令优化,只输出keys命令的执行情况:

[[email protected] ~]# redis-cli -p 6380 monitor | grep keys 
1532645266.656525 [0 10.0.0.1:43544] "keyss" "*"
1532645287.257657 [0 10.0.0.1:43544] "keyss" "44*"

执行结果中很清楚的看到keys命名执行来源。通过输出的IP和端口信息,就能在目标服务器上找到执行这条命令的进程,揪出元凶,勒令整改。

info

如果说哪个命令能最全面反映当前redis运行情况,那么非info莫属。用法如下:

INFO [section]

section可选值有:

  • Server:运行的redis实例一些信息,包括:redis版本,操作系统信息,端口,GCC版本,配置文件路径等;
  • Clients:redis客户端信息,包括:已连接客户端数量,阻塞客户端数量等;
  • Memory:使用内存,峰值内存,内存碎片率,内存分配方式。这几个参数都非常重要;
  • Persistence:AOF和RDB持久化信息;
  • Stats:一些统计信息,最重要三个参数:OPS(instantaneous_ops_per_sec),keyspace_hitskeyspace_misses两个参数反应缓存命中率;
  • Replication:redis集群信息;
  • CPU:CPU相关信息;
  • Keyspace:redis中各个DB里key的信息;

config

config是一个非常有价值的命令,主要体现在对redis的运维。因为生产环境一般是不允许随意重启的,不能因为需要调优一些参数就修改conf配置文件并重启。redis作者早就想到了这一点,通过config命令能热修改一些配置,不需要重启redis实例,可以通过如下命令查看哪些参数可以热修改:

config get *

热修改就比较容易了,执行如下命令即可:

config set 

例如:config set slowlog-max-len 100config set maxclients 1024

这样修改的话,如果以后由于某些原因redis实例故障需要重启,那通过config热修改的参数就会被配置文件中的参数覆盖,所以我们需要通过一个命令将config热修改的参数刷到redis配置文件中持久化,通过执行如下命令即可:

config rewrite

执行该命令后,我们能在config文件中看到类似这种信息:

# 如果conf中本来就有这个参数,通过执行config set,那么redis直接原地修改配置文件
maxclients 1024
# 如果conf中没有这个参数,通过执行config set,那么redis会追加在Generated by CONFIG REWRITE字样后面
# Generated by CONFIG REWRITE
save 600 60
slowlog-max-len 100

set

set命令也能提升逼格?是的,我本不打算写这个命令,但是我见过太多人没有完全掌握这个命令,官方文档介绍的用法如下:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

你可能用的比较多的就是set key value,或者SETEX key seconds value,所以很多同学用redis实现分布式锁分为两步:首先执行SETNX key value,然后执行EXPIRE key seconds。很明显,这种实现有很严重的问题,因为两步执行不具备原子性,如果执行第一个命令后出现某些未知异常导致无法执行EXPIRE key seconds,那么分布式锁就会一直无法得到释放。

通过SET命令实现分布式锁的正式姿势应该是SET key value EX seconds NX(EX和PX任选,取决于对过期时间精度要求)。另外,value也有要求,最好是一个类似UUID这种具备唯一性的字符串。当然如果问你redis是否还有其他实现分布式锁的方案。你能说出redlock,那对方一定眼前一亮,心里对你竖起大拇指,但嘴上不会说。

Mac 上使用多点触控手势(含鼠标手势)

 mac  Mac 上使用多点触控手势(含鼠标手势)已关闭评论
8月 072020
 

在 Mac 上使用多点触控手势

使用多点触控触控板或妙控鼠标,您可以通过轻点、轻扫、捏合或开合一根或多根手指进行有用的操作。

触控板手势

有关这些手势的更多信息,请选取苹果菜单 () >“系统偏好设置”,然后点按“触控板”。您可以关闭某个手势,更改手势类型,以及了解哪些手势可在您的 Mac 上使用。

触控板手势要求使用妙控板或内建的多点触控触控板。如果您的触控板支持力度触控,您还可以进行“用力点按”操作并获得触感反馈

轻点来点按
用单指轻点来进行点按。

辅助点按(右键点按)
用双指点按或轻点。

智能缩放
用双指轻点两下可放大网页或 PDF,或缩小回原来的大小。

滚动
双指向上或向下滑动可滚动。1

放大或缩小
双指捏合或张开可放大或缩小。

旋转
双指互相以对方为中心移动,可旋转照片或其他项目。

在页面之间轻扫
双指向左或向右轻扫,可显示上一页或下一页。

打开“通知中心”
用双指从右边缘向左轻扫,可显示“通知中心”。

三指拖移
用三根手指拖移屏幕上的项目,然后点按或轻点以放下。可在“辅助功能”偏好设置中开启此功能2

 

查找和数据检测器
用三根手指轻点可查找字词,或者对日期、地址、电话号码和其他数据采取相关操作。

显示桌面
将拇指和另外三根手指同时展开,可显示桌面。

 

“启动台”
将拇指和另外三根手指合拢到一起,可显示“启动台”。

“调度中心”
用四根手指向上轻扫3,可打开“调度中心”。

应用 Exposé
用四根手指向下轻扫3,可查看正在使用的应用的所有窗口。

在全屏应用之间轻扫
用四根手指向左或向右轻扫3,可在桌面与全屏应用之间移动。

 

鼠标手势

有关这些手势的更多信息,请选取苹果菜单 () >“系统偏好设置”,然后点按“鼠标”。您可以从中关闭某个手势,更改手势类型,以及了解哪些手势可在您的 Mac 上使用。鼠标手势要求使用妙控鼠标

辅助点按(右键点按)
点按鼠标的右侧。

滚动
单指向上或向下滑动可滚动。1

智能缩放
用单指轻点两下可放大网页或 PDF,或缩小回原来的大小。

“调度中心”
用双指轻点两下,可打开“调度中心”。

在全屏应用之间轻扫
用双指向左或向右轻扫,可在桌面与全屏应用之间移动。

在页面之间轻扫
用单指向左或向右轻扫,可显示上一页或下一页。

1. 您可以在“辅助功能”偏好设置中关闭触控板滚动功能:选取苹果菜单 >“系统偏好设置”,然后点按“辅助功能”。在“鼠标与触控板”部分中,点按“触控板选项”,然后取消选择“滚动”复选框。

2.“辅助功能”偏好设置还包含单指拖移的选项:选取苹果菜单 >“系统偏好设置”,然后点按“辅助功能”。在“鼠标与触控板”部分中,点按“触控板选项”。选择“启用拖移”,然后从弹出式菜单中选取一个“拖移锁定”选项。点按问号按钮可了解有关每个选项的更多信息。

3. 在某些版本的 macOS中,这个手势使用的是三根手指,而不是四根。

 

转自苹果官网:https://support.apple.com/zh-cn/HT204895

windows/mac os里 microsoft excel下单元格内内容输入时如何回车换行?

 mac  windows/mac os里 microsoft excel下单元格内内容输入时如何回车换行?已关闭评论
8月 042020
 

如何在excel单元格输入时回车换行?

  1. windows时,要在excel单元格内换行,按alt+enter

 

  1. mac下要在excel单元格内换行,按键盘control+option(alt)+enter

DONE!

sublimeREPL下python 2 切换为 python 3

 python, sublime  sublimeREPL下python 2 切换为 python 3已关闭评论
7月 202020
 

转自:https://www.jianshu.com/p/41df9da08f60

1、安装python3

 

$ brew search python
$ brew install python3

这里安装完后不需要单独添加环境变量,程序已经处理好,可以直接运行python3命令。

$ which python
/usr/bin/python

$ which python3
/Library/Frameworks/Python.framework/Versions/3.6/bin/python3

$ echo $PATH
/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

2、安装REPL插件,并设置快捷键绑定。

打开后,搜索 SublimeREPL 安装即可。

设置快捷键:

Preferences -> Key Bindings

[   
    {"keys":["f1"],
    "caption": "SublimeREPL: Python - RUN current file",
    "command": "run_existing_window_command", "args":
    {"id": "repl_python_run",
    "file": "config/Python/Main.sublime-menu"}},

    {"keys":["f2"],
    "caption": "SublimeREPL: Python",
    "command": "run_existing_window_command", "args":
    {"id": "repl_python",
    "file": "config/Python/Main.sublime-menu"}}
]

这里我设置的 F1 运行代码,F2 打开终端。

3、按 F2 运行终端,发现默认集成的是 python2,手动改成 pyhton3,方法如下:

$ sudo find / -iname 'Main.sublime-menu'
$ vim /Users/keithtt/Library/Application\ Support/Sublime\ Text\ 3/Packages/SublimeREPL/config/Python/Main.sublime-menu

"cmd": ["python3", "-i", "-u"],
"cmd": ["python3", "-i", "-u", "-m", "pdb", "$file_basename"],
"cmd": ["python3", "-u", "$file_basename"],
"cmd": {
    "osx": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "linux": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"],
    "windows": ["python3", "-u", "${packages}/SublimeREPL/config/Python/ipy_repl.py"]
},

将该文件中调用 python 命令的地方全部改成 python3 即可。

改完之后再次按 F2 运行终端,效果如下: