Java 语言中存在四种类型:
前三种是引用类型,类实例和数组是对象,基本类型不是对象。
在 Java 中一共有 8 种基本数据类型,其中有 4 种整型,2 种浮点类型,1 种用于表示 Unicode 编码的字符单元的字符类型和 1 种用于表示真值的 boolean 类型。(一个字节等于 8 个 bit)
类型 | 存储需求 | bit 数 | 取值范围 | 备注 |
---|---|---|---|---|
byte | 1 字节 | 1*8 | -128~127 | |
short | 2 字节 | 2*8 | -32768~32767 | |
int | 4 字节 | 4*8 | -2^31 ~ 2^31-1 | |
long | 8 字节 | 8*8 | -2^63 ~ 2^63-1 |
Integer.MAX_VALUE = 2147483647 Integer.MIN_VALUE = -2147483648
Long.MAX_VALUE = 9223372036854775807 Long.MIN_VALUE = -9223372036854775808
类型 | 存储需求 | bit 数 | 取值范围 | 备注 |
---|---|---|---|---|
float | 4 字节 | 4*8 | 2^-149 ~ (2-2^-23)·2^127 | float 类型的数值有一个后缀 F(例如:3.14F) |
double | 8 字节 | 8*8 | 2^-1074 ~ (2-2^-52)·2^1023 | 没有后缀 F 的浮点数值(如 3.14) 默认为 double 类型, Double 有静态变量 MIN_VALUE 和 MAX_VALUE 。 |
3.char 类型
类型 | 存储需求 | bit 数 | 取值范围 | 备注 |
---|---|---|---|---|
char | 2 字节 | 2*8 | 0 ~ 65,535 |
4.boolean 类型
类型 | 存储需求 | bit 数 | 取值范围 | 备注 |
---|---|---|---|---|
boolean | 1 字节 | 1*8 | false、true |
补充:Java 有一个能够表示任意精度的算术包,通常称为“大数值”(big number)。虽然被称为大数值,但它并不是一种 Java 类型,而是一个 Java 对象。
如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math
包中的两个很有用的类:BigInteger
和 BigDecimal
(Android SDK 中也包含了java.math
包以及这两个类)这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。具体的用法可以参见 Java API。
BigInteger 和 BigDecimal 都是不可变 immutable ,类似于 String, 在使用
BigInteger sum = new BigInteger.valueOf(0);
sum.add(BigInteger.valueOf(10)); // wrong way, sum is still 0
sum = sum.add(BigInteger.valueOf(10)); // right way, add() return a BigInteger Object.
优点:
Builder 模式的优势:
何时使用 Builder 模式:
共有静态成员 final 域
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { … }
public void leaveTheBuilding() {…}
}
静态方法所有调用,都会返回同一个对象引用,不会创建其他 Elvis 实例。
公有的成员是静态工厂方法:
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { … }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {…}
}
希望一个类不能被实例化,则私有化构造方法
String s1 = "string";
String s2 = new String("string"); // don‘t do this
当心无意识的基本类型自动装箱。
Java 内存泄露可能发生在:
类自我管理内存
比如在实现 Stack 栈时,弹出栈时,消除对象引用,结束变量的生命周期。
提供显式的终止方法,要求类客户端在每个实例不再有用的时候调用这个方法。Java 中 FileInputStream,FileOutputStream,Timer 和 Connection 都具有终结方法。
所有对象都通用的方法,非 final 方法(equals、hashCode、toString、clone 和 finalize)都有明确的通用约定,被设计成被覆盖。
类的每个实例都只与它自身相等:
什么时候覆盖 Object.equals ?
equals 等价关系:自反,对称,传递,一致
里氏替换原则 Liskov substitution principle ,类型的任何重要属性也将适用于它的子类型。
将类的基本信息放入。IDE 中一般可以自动生成该方法。
浅拷贝指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
实现对象实例之间的比较。
encapsulation 封装,模块化
原因:有效地解除组成系统各个模块之间的耦合关系,使得模块之间可以独立开发、测试、优化、使用、理解和修改。
类成员的公开级别:
公有的静态 final 域来暴露常量,惯例,使用 大写字母 加 下划线,这些域要么包含基本类型,要么包含指向不可变对象的引用。
未来改变类的内部表示时非常有效,如果类是包级私有,或者私有嵌套类,直接暴露数据域并没有本质的错误。
不可变类 实例不能被修改,实例包含的信息必须在创建时提供,并且在对象生命周期内固定不变。 String 基本类型包装类, BigInteger 和 BigDecimal 是不可变类。
原因:不可变类易于设计、实现和使用,不容易出错,更加安全
类不可变类要遵循一下规则:
优点:1. 不可变对象本质上是线程安全的,他们不要求同步。2. 不可变对象可以被自由地共享,将频繁使用的值提供公有的静态 final 常量,将频繁被请求的实例缓存起来,当现有实例可以符合请求时,不用创建新的实例。基本类型的包装类和 BigInteger 都有这样的静态工厂。3. 不可变对象为其他对象提供了大量的构建。
缺点:不可变类唯一的缺点就是,对于每个不同的值都需要一个单独的对象。
不扩展现有的类,而是在新的类中增加一个私有域引用一个类的实例,这种设计叫做“复合” Composition。
优点:与方法调用不用,继承打破了封装性,子类依赖于父类中特定功能的实现细节,因此复合被提出,不扩展现有的类,而是在新的类中增加一个私有域来组成新的类。
缺点:包装类没什么缺点,但需要注意,包装类不适合用在回调框架 callback framework。
类必须有文档说明它可覆盖的方法的自用性。在发布类之前,编写子类进行测试。
为了允许继承,类需要遵守的约束条件:
两种方法禁止子类化:
Java 提供两种机制,来允许多实现,接口与抽象类。重要区别在于:
优点:
常量接口 constant interface ,不包含任何方法,只包含静态 final 域,每个域都是一个常量。反面,不值得使用。
导出常量可选合理方案,将常量添加到 相关类 或者接口中,尽量使用枚举类型 enum type,否则应该使用不可实例化的工具类 utility class 来导出常量。
接口只应该被用来定义类型,不应该被用来导出常量。
标签类指在类中用标签区别类的行为模式,比如使用枚举变量,更加枚举不同显示不同内容。尽量不使用标签类,使用子类抽象,将标签类中每个方法都定义成一个包含抽象方法的抽象类。
允许程序把“调用特殊函数的能力”存储起来并且传递这种能力,这种机制允许函数调用者通过传递第二个函数,来指定行为。
执行对象上的某项操作,能够实现函数指针。
Comparator
嵌套类 nested class 指被定义在另一个类内部的类。
嵌套类有四种:
除去第一种之外,其他三类都被称为内部类 inner class。
静态成员类,最简单的嵌套类,普通类,静态成员类可以访问外围类的所有成员,包括私有。访问性原则同类中其他成员。
静态成员类,常见用法作为公有辅助类,与外部类一起使用时才有意义。
非静态成员类,语法层面只有一个 static 修饰符的不同,但是两者区别很大。非静态成员类的每一个实例都隐含着与外围类的一个外围实例 enclosing instance。嵌套的实例需要外围类的实例。
非静态成员类的一种常见用法是定义 adapter,它允许外部类的实例被看做是另一个不相关的类的实例。
私有静态成员类 常见用法是代表外围类所代表的对象的组件。
匿名类没有名字,在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。
匿名类的一种常见用法是动态地创建函数对象 function object。 另一种常见用法是创建过程对象 process object ,比如 Runnable , Thread 或者 TimeTask 实例。第三种常见用法是在静态工厂方法的内部。
局部类是嵌套类中用得最少的类。任何可以声明局部变量的地方都可以声明局部类。
成员类的每个实例都需要一个指向外围实例的引用,就要把成员类做成非静态的,否则做成静态的。
嵌套类属于方法内部,只需要一个地方创建实例,匿名类,否则就是局部类
尽早发现错误,最好是编译时就发现。 在 Java 1.5 之前,运行时,集合,才会发现错误。
非受检强制转化警告 unchecked cast warnings
非受检方法调用警告
非受检普通数组创建警告
非受检转换警告 unchecked conversation warnings
尽可能小的使用 SuppressWarnings(“unchecked”) 注解
数组是协变 covariant 的,泛型是不可变的 invariant。
数组在运行时才知道并检查元素类型。泛型在编译时就能检查类型。[[Java 泛型的协变与逆变]]
编写泛型方法和编写泛型类相似
泛型方法的特性是,无需明确指定类型参数的值,编译器通过检查方法参数的类型来计算类型参数的值。类型推导。
参数化类型是不可变的
Interface Iterable<T>
接口,实现这个接口可以让对象使用 foreach 循环,或者使用
// If you have Iterable<String> , you can do:
for (String str : myIterable) {
...
}
大部分的容器类都实现了这个接口。
http://docs.oracle.com/javase/6/docs/api/java/lang/Iterable.html
Java 提供了特殊的参数化类型,有限制的通配符类型 bounded wildcard type
枚举和注解, Java 1.5 版本中增加两个引用类型家族:1. 新的类枚举类型(enum type) 2. 新的接口注解类型(annotation type)。
枚举类型是指由一组固定常量组成合法值的类型。
优点:
所有枚举都有 ordinal 方法,返回枚举常量在类型中的位置,不要使用该方法。永远不要根据枚举的序数导出与它关联的值,而是应该将它保存到实例域中。
不必定义注解类型,但是都应该使用 java 平台所提供的预定义注解类型。
标记接口 marker interface 没有包含任何方法声明的接口,指明一个类实现的某种属性的接口。
设计方法的参数,返回值
在传入可变参数时需要特别注意,外部可能会通过改变传入参数而间接的影响到类内部的实现。因此需要在初始化的时候进行保护性拷贝。
谨慎地选择方法名称,遵循命名习惯
不过于追求提供便利的方法
避免过长的参数列表
有三种方法可以缩短长参数列表
将方法拆解成多个方法
创建辅助类 helper class , 保存参数分组,一般作为静态成员类
从对象构建到方法调用都采用 Builder 模式,多次 setter
对于参数类型,要优先使用接口而非类, Map 接口作为参数,可以传入 Hashtable, HashMap,TreeMap,TreeMap 子映射表 submap 等等
对于 boolean 参数,优先使用两个元素的枚举类型
永远不要导出两个具有相同参数数目的重载方法。
Java 1.5 版本中增加了 可变参数 varargs 方法,一般称为 variable arity method 。可匹配不同长度的变量的方法。
可变参数方法接受 0 个或者多个指定类型的参数,先创建一个数组,数组大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法。
方法的文档应该简洁地描述出它和客户端之间的约定。
几乎每一个局部变量的声明都应该包含一个初始化表达式。
有三种情况无法使用 for-each 循环
使用标准类库,可以充分利用这些编写标准类库的专家知识,以及在你之前其他人的使用经验。
将时间花在应用程序上,而不是底层细节。
都应该熟悉
使用 BigDecimal 做精确计算。
使用 int 或者 long 数值范围没有超过 9 位十进制数,可以使用 int,没有超过 18 位,可用 long ,超过 18 位,就必须使用 BigDecimal.
基本类型 primitive int double boolean
对应的引用类型 reference type ,称为装箱基本类型 boxed primitive ,对应为 Integer、Double、Boolean
何时使用装箱基本类型:
如果可以使用更加合适的数据类型,或者可以编写更加合适的数据类型,就应该避免使用字符串来表示对象。
字符串连接操作符 “+”
获得可接受的性能,使用 StringBuilder 代替 String append 方法
使用接口作为类型,程序会更加灵活
核心反射机制 core reflection facility java.lang.reflect 通过程序来访问关于已装载的类信息的能力。给定 Class 实例,可以获得 Constructor Method 和 Field 实例。
丧失了编译时类型检查的好处
执行反射访问所需要的代码笨拙冗长
性能损失
反射功能只是在设计时被用到,通常普通应用程序在运行时不应该以反射方式访问对象。
JNI 方法调用本地 Native method
不要试图去编写快速的程序 —- 应该努力编写好的程序,速度随之而来。再设计系统时,设计 API、线路层协议和永久数据格式时候,一定要考虑性能因素。
包名,层次,句号分割,小写字母和数字(少使用) 不以 java 和 javax 开头
包名其余部分通常不超过 8 个字符
Java 提供三种可抛出结构:
受检的异常 checked exception
运行时异常 run-time exception
错误 error
期望调用者能够适当地恢复,使用受检异常。
用运行时异常来表明编程错误。
runtimeException 子类
1、 java.lang.ArrayIndexOutOfBoundsException 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。 2、java.lang.ArithmeticException 算术条件异常。譬如:整数除零等。 3、java.lang.NullPointerException 空指针异常。当应用试图在要求使用对象的地方使用了 null 时,抛出该异常。譬如:调用 null 对象的实例方法、访问 null 对象的属性、计算 null 对象的长度、使用 throw 语句抛出 null 等等 4、java.lang.ClassNotFoundException 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历 CLASSPAH 之后找不到对应名称的 class 文件时,抛出该异常。
5、java.lang.NegativeArraySizeException 数组长度为负异常 6、java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常 7、java.lang.SecurityException 安全性异常 8、java.lang.IllegalArgumentException 非法参数异常
2.IOException IOException:操作输入流和输出流时可能出现的异常。 EOFException 文件已结束异常 FileNotFoundException 文件未找到异常
并发
synchronized 保证同一个时刻只有一个线程执行某一段代码块
什么时候使用锁:
文档说明类是否可以被多个线程安全使用
不可变 immutable 类实例不可变,不需要外部同步,String Long BigInteger
无条件的线程安全 unconditionally thread-safe 实例可变,但是有足够的内部同步 ,实例可被并发使用,无需外部同步, Random ConcurrentHashMap
有条件的线程安全 conditionally thread-safe Collections.synchronized
非线程安全 not thread-safe 类实例可变,需要外部同步 ArrayList HashMap
线程对立 thread-hostile 类不能安全地被多个线程并发使用
依赖于线程调度器的程序,很有可能都是不可移植的。
序列化
将对象编码成字节流,对象序列化
序列化成功需要:
Externalizable 接口继承了 java 的序列化接口,并增加了
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
为继承而设计的类,尽量避免去实现 Serializable 接口。
内部类不应该实现 Serializable, 静态成员类可以
画图工具整理,平时少许的画图需求,所以找了这些网站,Draw.io 还是不错的,简单的流程图都能搞定。
国外的免费画图网站,支持 Google Drive,Dropbox 等等
所见即所得,有 Chrome 扩展,可离线,和 Google Drive 有结合
国内的画图网站,可以实时写作
微软出的绘图软件,但是 Windows only
Matplotlib 是 Python 下绘图的首选,可以用来绘制高质量的图像。
R 下绘图标配
MATLAB 是一个优秀的商业软件,它的绘图能力同样强大。
D3 是一个前端的绘图框架。有些情况下,网页是很好的图像呈现方式。因为可以开发一些可互动的可视化效果。
tikz 是 LaTex 下非常强大的画图宏包。不过感觉只适合极客 / 科研党,因为本身用 LaTex 写作的人就少。
SVG 意为可缩放矢量图形(Scalable Vector Graphics)。SVG 使用 XML 格式定义图像。
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100"
style="fill:rgb(0,0,255);stroke-width:1;
stroke:rgb(0,0,0)"/>
</svg>
之前把 Linux Mint Grub2 bootloader 的背景图片更换了一下,直接用的 gnome-look 的 Anonymous Hope 主题 ,这里就记录一下,以便快速恢复。
Gnome-look 这个网站上有非常多人分享的内容,可以到这个网站自行选择自己喜欢的内容。
一般 grub 的主题需要在 /boot/grub/themes
目录下。如果没有这个目录需要手动创建
sudo mkdir -p /boot/grub/themes
然后将下载的文件内容拷贝到该目录下。完成拷贝之后需要修改一下 /etc/default/grub
配置文件,增加如下配置:
GRUB_THEME="/boot/grub/themes/Anonymous-Hope/theme.txt"
然后更新 grub.cfg
sudo update-grub
如果一切都按照步骤,应该可以看到日志显示
Generating grub configuration file ...
Found theme: /boot/grub/themes/Anonymous-Hope/theme.txt
然后一切 OK。
在最近用 Telegram 分享网页的时候,Telegram 会预先抓取网页形成一个缩略预览,但是如果是自己的网站没有适配一些 meta 信息,抓取出来的信息就非常不堪入目。所以优化一下网站在分享的时候的预览图,这个术语叫做 Rich Previews
,想查看网站是否已经优化好,可以查看下面的网站:
想要好看的预览需要如下的标签
Title
最长 65 个字符,否则 Google 结果就会被截断Meta description
最好小于 155 字符og:title
最好不超过 35 个字符,否则预览会截断og:description
65 字符og:url
页面 urlog: image
JPG 或者 PNG,最小分辨率 300 × 200 像素favicon
网站 logo, 32 × 32 像素上面的网站目前支持很多聊天工具,比如 WhatsApp, Telegram, Skype ,还有社交网站 Facebook,Twitter 等等。
举例
<title>Rich Link Preview</title>
<meta name="description" content="Also want these pretty website previews?" />
<meta property="og:title" content="Rich Link Preview" />
<meta property="og:description" content="Also want these pretty website previews?" />
<meta property="og:url" content="http://richpreview.com/" />
<meta property="og:image" content="http://richpreview.com/richpreview.jpg" />
<link rel="shortcut icon" href="http://richpreview.com/favicon.ico" type="image/x-icon" />
这些标签的定义都在 http://ogp.me/ 这个网站上,只要遵循这个 The Open Graph protocol 协议基本上能搞定大部分的网站。
阅读经典的项目是学习的起点。
之前和朋友聊天,说到 Python 项目的结构问题,Python 确实是一门比较灵活的语言,你可以单独执行某个文件,把这些文件扔到一起看起来像个工程,但是外人来看其实就是一个个独立的小文件,小模块,对于小型项目来说可能并不会造成什么问题,但是对于大型项目,尤其是如果需要多人合作,有一个关于工程结构的基本认识是非常必要的。
Python 没有强制的结构规范,但是有一个大家的共识
README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py
项目的结构是项目的门面,简单易懂的项目结构能够让介入的第三人快速的熟悉项目,Python 项目结构没有太多的约束和限制,Python 提供的导入和模块管理使得结构化 Python 相对简单,但也要注意一些比如循环依赖的问题。
为了解决项目结构的问题就不得不提到模块(module),这是 Python 最主要的抽象层,模块允许将代码分为不同的部分,每个部分包含相关的数据和功能。
模块名尽量要短,使用小写,并且避免使用特殊符号,比如(.
)或者 (?
) 等。不推荐在模块名中使用下划线。
# ok
import library.plugin.foo
# not ok
import library.foo_plugin
import 的工作原理,比如 import modu
将会寻找合适的文件,调用目录下的 modu.py
文件,如果没有找到,Python 解释器会递归地在 PYTHONPATH
环境变量中查找该文件,如果没有则抛出 ImportError 异常。一旦找到 modu.py
Python 解释器将在隔离的作用域内执行模块。所有顶层语句都会被执行,包括其他的引用。方法和类的定义会存储到模块字典中。这个模块的变量、方法和类通过命名空间暴露给调用方。
import 语句也可以为 from modu import *
,使用 from modu import func
能够精确定位想要导入的方法,并将其放入到全局命名空间。
# 较差的写法
[...]
from modu import *
[...]
x = sqrt(4) # sqrt 是模块 modu 的一部分么?或是内建函数么?上文定义了么?
# 稍好
from modu import sqrt
[...]
x = sqrt(4) # 如果在 import 语句与这条语句之间,sqrt 没有被重复定义,它也许是模块 modu 的一部分。
# 最好的做法
import modu
[...]
x = modu.sqrt(4) # sqrt 显然是属于模块 modu 的。
Python 提供非常简单的包管理系统,简单地将模块管理机制扩展到一个目录上,就是包。任意包含 __init__.py
文件的目录都被认为是一个 Python 包。如果包内的模块和子包没有代码共享的需求,使用空白 __init__.py
是正常的做法。
导入深层嵌套包可以使用 import very.very.deep.module as mod
可以简化导入冗长子包。
使用无状态的函数是一种更好的编程范式。
把有隐式上下文和副作用的函数与仅包含逻辑的函数(纯函数)谨慎地区分开来,会带来 以下好处:
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行 func() 就相当于执行 foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper
foo() # 执行 foo() 就相当于执行 wrapper()
使用 @
符号,装饰器的语法糖
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
这样就可以省略掉 foo = use_logging(foo)
这句赋值。
带参数的装饰器
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
类装饰器
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
通过 functools.wrap
来找回原函数的元信息
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
Python 类相关的内置装饰器
@staticmathod、@classmethod、@property
上下文管理器是一个 Python 对象,为操作提供了额外的上下文信息。with 语句初始化上下文
with open('file.txt') as f:
contents = f.read()
实现这个功能有两种简单的方法:使用类或使用生成器。 让我们自己实现上面的功能,以使用类方式开始:
class CustomOpen(object):
def __init__(self, filename):
self.file = open(filename)
def __enter__(self):
return self.file
def __exit__(self, ctx_type, ctx_value, ctx_traceback):
self.file.close()
with CustomOpen('file') as f:
contents = f.read()
生成器使用 Python 自带的 contextlib
from contextlib import contextmanager
@contextmanager
def custom_open(filename):
f = open(filename)
try:
yield f
finally:
f.close()
with custom_open('file') as f:
contents = f.read()
由于这两种方法都是一样的,所以我们应该遵循 Python 之禅来决定何时使用哪种。 如果封装的逻辑量很大,则类的方法可能会更好。 而对于处理简单操作的情况,函数方法可能会更好。
在拿到 moto 360 2代国行之前就已经做过调研, 国行并不支持国际版 Android Wear 程序同步,需要用国内阉割版 Android Wear 连接,并且不能使用 Play Store 中各种定制表盘及Android wear 应用。虽然可以通过使用 国际版 Android Wear 1.3 版本连接并同步 moto 360 来激活并使用各种表盘及应用,但是拿到手使用一个星期之后,我依然还是对国行 moto 360 2代表示失望。
在没有了 Google Now 的情况下,语音几乎是不可用状态,国行所带的出门问问,在国际版 Android wear 激活的情况下几乎不可用,真实使用一次都没成功。当然在国内版 Android wear激活的情况下,可能会有一些用处,但是Android wear 的精华就是 Google Now,没有了核心的支持,几乎就沦为只能看时间,只能收通知的尴尬境地。
不过值得赞扬的是续航能力,在一周的使用情况下,12h的待机下来基本还能保证40%以上的电量,这一点值得称赞,但是几乎是一天一冲的情况下,如果能够将续航能力再提高,或许更能吸引用户。单独的无线充电技术,在使用过程也并没有出现太大的问题,至于网上曾经说过的位置放置稍有偏差就无法充电的问题也并没有遇到。
在目前给手表适配的app还不多的情况下,要给微信给个赞,在国际版 Andorid wear 匹配链接下不仅能够收通知,并且在回复时提供表情,语音,语音转文字三种方式,并就使用过程来说,中文识别并没有出现太大问题,语音消息声音大小也合适,至于表情目前似乎只能有内置的一些可选,但是也足够使用。
另一个不错的feature就是这个通知静音的设置,表盘上,从上网下可以调出设置。这个功能本来也是我在原生 Android 上非常喜欢的,可以设置自动睡眠静音,自动在Google日历上出现Event时静音。我希望在我规划好的时间里不被打扰。当然手表作为通知的扩展他的提醒也应该遵循手机的设置。
另外说到二代 6.0.1 的升级,在和国际版相比不支持 WIFI,不支持手势的差距下,在更新了 6.0.1 之后手势的功能被补了回来。
在购买 Moto 360 2代之前,抱有的期望有以下几点:
基于以上的期望,到现在来看
新的 Android Wear 基本就是 Android 通知的外延,Google 在对 Android Wear 的设计中就将其操作固定成卡片式的左右滑动,不同应用间的通知使用上下滑动切换,相同通知的内容,通过向左滑可以进行更多的操作。
在表盘界面,向左滑可以调出所有应用;先下滑可以调出通知设置,静音设置,还有影院模式,进入设置等操作;向上滑动可以调出系统通知,如果不存在未读通知则不显示任何通知。
长按表盘可以更换表盘设计,当然这一点非常值得称赞,我不希望生产商在手表出厂就固定了一套表盘界面,而后就再无法更改,而这一点正是 Moto 360 ,以及 Android Wear 值得称赞的地方,并且将表盘设计开放给第三方能够带来无穷的想象力结果,喜欢指针手表,拿就可以选择指针的表盘设计,如果喜欢电子表盘,也可以选择电子表盘。
在来通知时,通过向右滑动可以忽略消息,向左滑动可以进行更多的操作,比如Gmail,可以选择存档,回复,删除等等;微信可以选择回复,忽略等等。
在早先手机上,Google Fit曾经出现没有再后台自动启动服务而暂停记录的情况,通过 Watch 带了了更多的数据,并且也希望能够对 Google Fit 的数据做更多的补充。如今市面上的“智能手表”,“智能手环”如果不能记步,不能记录心率都快称不上智能设备了。在调研的过程中获知2代 Moto 360 其实并不带 GPS 功能,如果想要使用记录路径的话,其实使用的依然是手机的 GPS,虽然这一点有点不便,但是鉴于记录路径这个功能其实日常并不常用,并且带来的电量消耗也会随之增加,所以在运动版和普通版之间还是选择了普通版,不过有机会还是要尝试一下运动版的。
什么是 SQLAlchemy ?
The Python SQL Toolkit and Object Relational Mapper
首先 要创建 Engine
实例
sqlalchemy.create_engine(*args, **kwargs)
创建 mysql 连接
# driver mysql-python
DB_PATH = "mysql+mysqldb://root:password@host:port/dbname?charset=utf8mb4"
xchat_engine = create_engine(
DB_PATH,
echo =False,
pool_size=40,
pool_recycle=3600
)
连接的字符串 URL 格式 dialect[+driver]://user:password@host/dbname[?key=value..]
,其中
dialect
是使用的数据库 mysql, oracle, postgresql 等等driver
是连接数据库需要的驱动 DBAPI,比如 psycopg2, pyodbc, cx_oracle 等等另外这个字符串也可以使用 sqlalchemy.engine.url.URL 实例。
对于 MySQL 而言,driver 可以有如下选择
# default
engine = create_engine('mysql://scott:tiger@localhost/foo')
# mysql-python
engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo')
# sudo apt-get install libmysqlclient-dev
# pip install mysqlclient
# MySQL-connector-python
engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo')
# OurSQL
engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')
# pymysql
engine = create_engine('mysql+pymysql://user:pass@localhost/foo')
PyMySQL 是使用纯 Python 实现的。
定义和数据库 Map 的实体
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class SomeClass(Base):
__tablename__ = 'some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
declarative_base()
方法会返回一个 base class,所有自己定义的 class 都要从此继承。关于定义类(表)中数据类型的选择,可以参考文末的对应关系。定义好之后 sqlalchemy.schema.Table 和 sqlalchemy.orm.mapper 会产生。结果可以从类变量获取
# access the mapped Table
SomeClass.__table__
# access the Mapper
SomeClass.__mapper__
如果想要自己定义的字段和数据库中字段相区别,可以在 Column 中定义 some_table_id
id = Column("some_table_id", Integer, primary_key=True)
通过 base class 定义的类包含一个 MetaData 通过这个 MetaData 可以定义 Table ,然后通过如下语句就可以直接在数据库中生成定义好的表。
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
加入定义了 User 类,创造一个实例
ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')
做好了一切准备,可以和数据库进行交互,ORM 和数据库打交道的是 Session ,从之前定义的 engine 中创建 sessionmake
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
这个 Session
实例,是一个 session 创造的工厂,之后所有的 session 都可以由他创建
session = Session()
这里创建的 session 实例还不会开启任何连接,当第一次被用到,session 会从 Engine 维护的连接池中获得一个连接,一直持有该连接知道 commit 所有的提交或者显式的关闭 session 对象。
Session 的生命周期模式,记住,Session 只是特定对象的一个工作区,限定到特定的数据库连接,
关于 Session 使用更多的问题 可以参考之前的文章。当然官网也有很多使用说明。
当有了 Session 之后,就可以通过 session 来操作数据库。比如
session.add(ed_user)
当把实例对象 add 到 session 之后,其实不会直接提交给数据库,这个时候只有 flush 操作,这个时候
our_user = session.query(User).filter_by(name='ed').first()
也能够将刚刚 add 的实例检索出来,真正的提交只有执行 session.commit()
之后。
如果发现单一插入数据库比较费时,那么在 SQLAlchemy 1.0.0 之后,可以使用新引入的特性批量操作。
在 Session 中有
简单的例子:
s = Session()
objects = [
User(name="u1"),
User(name="u2"),
User(name="u3")
]
s.bulk_save_objects(objects)
s.commit()
查询
# User objects
for instance in session.query(User).order_by(User.id):
print(instance.name, instance.fullname)
# tuples
for name, fullname in session.query(User.name, User.fullname):
print(name, fullname)
# named tuples, supplied by the KeyedTuple class can be treated much like an ordinary Python object
for row in session.query(User, User.name).all():
print(row.User, row.name)
> <User(name='wendy', fullname='Wendy Williams', password='foobar')> wendy
# 控制返回字段
for row in session.query(User.name.label('name_label')).all():
print(row.name_label)
from sqlalchemy.orm import aliased
user_alias = aliased(User, name='user_alias')
for row in session.query(user_alias, user_alias.name).all():
print(row.user_alias)
for u in session.query(User).order_by(User.id)[1:3]:
print(u)
for name, in session.query(User.name).\
filter_by(fullname='Ed Jones'):
print(name)
for name, in session.query(User.name).\
filter(User.fullname=='Ed Jones'):
print(name)
其他跟多的 filter 操作可以参考这里
在查询时可以通过这些方法来选择返回的数量
query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)
query.all() # return list
query.first() # return first
query.one() # multiple rows found raise an MultipleResultsFound, or no row found raise NoResultFound
query.one_or_none() # if no results are found, it doesn’t raise an error just return None, however, it does raise an error if multiple results are found.
query.scalar() # 调用 one() 方法,一旦成功,返回行的第一列
SQLAlchemy 同样也支持直接使用 Text 来写 sql 语句,具体可以参考官网
计数可以使用 count
session.query(User).filter(User.name.like('%ed')).count()
from sqlalchemy import func
session.query(func.count(User.name), User.name).group_by(User.name).all()
session.query(func.count(User.id)).scalar()
更多关于外键等等,可以查看官网:http://docs.sqlalchemy.org/en/latest/orm/tutorial.html
SQLAlchemy | Python | SQL |
---|---|---|
BigInteger | int | BIGINT |
Integer | int | INTEGER |
Boolean | bool | BOOLEAN or SMALLINT |
Date | datetime.date | DATE (SQLite: STRING ) |
DateTime | datetime.datetime | DATETIME (SQLite: STRING ) |
Enum | str | ENUM or VARCHAR |
Float | float or Decimal | FLOAT or REAL |
Interval | datetime.timedelta | INTERVAL or DATE from epoch |
LargeBinary | byte | BLOB or BYTEA |
Numeric | decimal.Decimal | NUMERIC or DECIMAL |
Unicode | unicode | UNICODE or VARCHAR |
Text | str | CLOB or TEXT |
Time | datetime.time | DATETIME |
在 model 中使用 __table_args__
来定义
class User(BaseModel):
__tablename__ = 'users'
__table_args__ = {
"mysql_engine": "InnoDB", # 引擎
"mysql_charset": "utf8" # 编码
}
...
在定义 Column 时指定
id = Column('id', Integer, primary_key=True, autoincrement=True)
有些时候定义表时需要多列组合唯一,比如
class User(Base):
field1 = Column(Integer, primary_key=True)
field2 = Column(Integer, primary_key=True)
那么可以
class User(Base):
field1 = Column(Integer)
field2 = Column(Integer)
__table_args__ = (
UniqueConstraint('field2', 'field1'),
{},
)
From: StackOverflow
在使用 mysqldb 这个 driver 的时候莫名其妙遇到编码的问题,数据库,表,都是 utf8mb4,在 CentOS6 下面死活用不了 utf8mb4 作为连接编码
can't initialize character set utf8mb4 (path /usr/share/mysql/charsets/)
一直都是这个错误,但在我本地一切 OK,这时候我想是不是机器 driver 的问题,换成 pymysql 之后就 OK 了。
http://docs.sqlalchemy.org/en/latest/dialects/mysql.html#charset-selection
也是醉了。
SQLAlchemy 在映射数据库时会寻找数据的 Primary Key,如果没有会报错。1
sqlalchemy.exc.ArgumentError: Mapper mapped class PicSearchword->pic_searchword could not assemble any primary key columns for mapped table 'pic_searchword'
SQLAlchemy ORM 会必须要求表拥有 Primary Key,而如果代码是建立在 Alchemy Core 之上,使用 Table 和 select() 则没有这个要求。
最近项目使用,总结了目前 Android 中使用到的人脸检测技术,主要分成三部分来介绍:
接下来就依次介绍前三种人脸检测的方法。
android.media
包中的人脸检测API 有如下两个限制:
只要注意这两个限制,另图片眼睛的距离不要太小,其他的代码核心的没几句话。
/**
* There are some limitation in this 用android.media 包中识别人脸package.
* 使用使用使Face Detection API's input Bitmap must :
* <p/>
* 1. config with Config.RGB_565<br/>
* 2. Bitmap width must be even<br/>
* <p/>
* more details can be checked
* http://stackoverflow.com/q/17640206/1820217
*
* @param bitmap Bitmap
*/
private void detectUsingNative(final Bitmap bitmap) {
if (null == bitmap || isRunning) {
if (listener != null) {
listener.onFail();
}
return;
}
facesCount = 0;
final android.media.FaceDetector faceDetector = new android.media.FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MEDIA_MAX_DETECT_FACE_NUMBER);
androidNativeFacesResults = new android.media.FaceDetector.Face[MEDIA_MAX_DETECT_FACE_NUMBER];
final Handler handler = new Handler();
thread = new Thread() {
@Override
public void run() {
facesCount = faceDetector.findFaces(bitmap, androidNativeFacesResults);
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onSuccess();
}
}
});
isRunning = false;
}
};
thread.start();
isRunning = true;
}
Play Service 中的人脸检测是随着 Mobile Vision API 一同出现的,这个库中还有一些其他的API,比如识别二维码,识别文字等等,并且 Play Service 中的人脸识别更准确的说应该叫人脸追踪,在官方实现的 Demo 中,直接调用手机摄像,能够一直追踪镜头中的同一人头像。
以下是部分实现,详细代码可参考文末给出的 GitHub 代码。
/**
* 使用 Play Service 中人脸检测
*
* @param bitmap Bitmap
*/
private void detectUsingGms(Bitmap bitmap) {
if (null == bitmap) {
if (listener != null) {
listener.onFail();
}
return;
}
facesCount = 0;
detector = new FaceDetector.Builder(context)
.setTrackingEnabled(false)
.setLandmarkType(FaceDetector.ALL_LANDMARKS)
.setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
.build();
// This is a temporary workaround for a bug in the face detector with respect to operating
// on very small images. This will be fixed in a future release. But in the near term, use
// of the SafeFaceDetector class will patch the issue.
Detector<Face> safeDetector = new SafeFaceDetector(detector);
// Create a frame from the bitmap and run face detection on the frame.
Frame frame = new Frame.Builder().setBitmap(bitmap).build();
faces = safeDetector.detect(frame);
if (!safeDetector.isOperational()) {
// Note: The first time that an app using face API is installed on a device, GMS will
// download a native library to the device in order to do detection. Usually this
// completes before the app is run for the first time. But if that download has not yet
// completed, then the above call will not detect any faces.
//
// isOperational() can be used to check if the required native library is currently
// available. The detector will automatically become operational once the library
// download completes on device.
if (listener != null) {
listener.onFail();
}
return;
}
if (listener != null) {
listener.onSuccess();
}
}
Face++ 提供了联网的人脸检测服务,需要到其网站上注册开发者账号获取API使用权限。其大概检测代码如下:
/**
* 使用 Face++ 人脸检测
*
* @param file File
*/
private void detectUsingFacePlus(File file) {
if (!file.exists() || isRunning) {
if (listener != null) {
listener.onFail();
}
return;
}
final PostParameters parameters = new PostParameters();
parameters.setImg(file);
final Handler handler = new Handler();
facesCount = 0;
thread = new Thread() {
@Override
public void run() {
boolean hasFace = false;
boolean detectSucceed = false;
Log.d("FacePlusDetect", "Detect Request :" + parameters.toString());
HttpRequests httpRequests = new HttpRequests(FACEPLUSPLUS_APIKEY, FACEPLUSPLUS_APISECRET, false, true);
JSONObject result;
try {
result = httpRequests.detectionDetect(parameters);
if (result != null) {
detectSucceed = true;
JSONArray faces = result.getJSONArray("face");
double imgWidth = result.getDouble("img_width");
double imgHeight = result.getDouble("img_height");
if (faces != null && faces.length() > 0 && null != listener) {
// Has face!!
facesCount = faces.length();
facePlusResults = new RectF[facesCount];
hasFace = true;
for (int i = 0; i < facesCount; i++ ){
float x, y, w, h;
facePlusResults[i] = new RectF();
// 需注意返回结果的center,width,height 都为0~100,百分比
x = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("x");
y = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("y");
w = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("width");
h = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("height");
float realx = (float) (x * imgWidth / 100);
float realy = (float) (y * imgHeight / 100);
float realw = (float) (w * imgWidth / 100);
float realh = (float) (h * imgHeight / 100);
facePlusResults[i].set(realx - realw /2,
realy - realh / 2,
realx + realw / 2,
realy + realh / 2);
}
// String genderStr = playServiceFaces.getJSONObject(0).getJSONObject("attribute").getJSONObject("gender").getString("value");
// gender = Gender.getValueOf(genderStr);
} else {
hasFace = false;
// detectSucceed = true;
// gender = Gender.OTHER;
}
// Log.d("FacePlusDetect", "Detect Result : hasFace = " + hasFace + "; gender = " + gender.toString());
}
} catch (FaceppParseException e) {
detectSucceed = false;
Log.d(TAG, "Detect FaceppParseException !");
e.printStackTrace();
} catch (JSONException e) {
// if (hasFace) {
// gender = Gender.OTHER;
// }
Log.d(TAG, "Detect JSONException !");
e.printStackTrace();
}
if (detectSucceed) {
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onSuccess();
}
}
});
} else {
handler.post(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onFail();
}
}
});
}
isRunning = false;
}
};
thread.start();
isRunning = true;
}
FaceDetectDemo 代码可参考:https://github.com/einverne/AndroidFaceDetectDemo
全部代码可参考 : https://github.com/einverne/Android-Face-Recognition
总结一下目前常用的图片格式转换命令,以及如何在这些常见的格式之间转换,包括 png, jpeg, webp 。
安装 imagemagick 用到的工具在这个包中。
sudo apt install imagemagick
首先检查图片格式
identify temp.jpg
将一张图片转换格式
convert image.jpg image.png
批量转换图片
mogrify -format png /tmp/*.jpg
给图片添加边框
convert path/to/demo.jpg -border 30x30 -bordercolor white bordered.png
更改图片的分辨率
convert path/to/demo.jpg -resize 1920x1080 after-resize.tiff
将目录下所有的 jpg 图片转换成 png 格式,并保存在同目录
for file in *.jpg; do convert $file -resize 50% small-$file.png; done
Google 已经将 webp 的解码工具放到了 libwebp
包中
sudo apt install webp
然后能够使用
dwebp input.webp -o output.png
如果要将 png 文件转换成 webp,可以使用 cwebp
cwebp input.png -o output.webp
或者也可以使用 ffmpeg 来转换格式:
ffmpeg -i file.webp out.png