在 Linux 下,命令行就是一切,GUI 不是任何时候都可用的,所以掌握一定的常用命令,能够方便日常使用,比如查看进程,查看内存占用,等等,这篇文章就总结了一下 Linux 下查看当前系统占用内存的命令。
free
命令是最常用的查看系统使用内存及剩余可用内存的命令。free
命令能够显示系统使用和可用的物理内存,同时能够显示 swap 分区内容使用量。free
命令也能够显示被 kernel 使用的 buffers 和 caches。free
命令通过解析 /proc/meminfo
来收集信息。
free -m
total used free shared buff/cache available
Mem: 15911 8803 1100 886 6007 3750
Swap: 16246 3100 13146
-m
选项表示使用 MB 来显示数据,上面表示总共 15911 MB 内存,使用了 8803 M 内存。
A buffer is something that has yet to be “written” to disk. A cache is something that has been “read” from the disk and stored for later use.
上面的解释非常清晰, buffer 用于存放要输出到 disk 的数据,cache 是存放 disk 上读取的数据,这两者都为了提高 IO 性能由 OS 控制管理的。
还有一些其他比较有用的参数,比如 free -h
显示比较可读 h for human
,还有 free -g
使用 GB 为单位显示信息。
第二种方法就是直接读取 /proc/meminfo
文件, /proc
文件系统并不是真实的文件系统,不真正包含文件,他是虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件。
运行命令 cat /proc/meminfo
检查其中的 MemTotal, MemFree, Buffers, Cached, SwapTotal, SwapFree
这几个字段,显示的内容和 free
命令是一样的。
cat /proc/meminfo
less /proc/meminfo
more/proc/meminfo
egrep --color 'Mem|Cache|Swap' /proc/meminfo
通过 vmstat
和 -s
选项来显示内存使用
vmstat -s
16293800 K total memory
9125168 K used memory
10143256 K active memory
2252796 K inactive memory
1001072 K free memory
746520 K buffer memory
5421040 K swap cache
16636924 K total swap
3174096 K used swap
13462828 K free swap
33736485 non-nice user cpu ticks
57413 nice user cpu ticks
10985936 system cpu ticks
465486969 idle cpu ticks
6378633 IO-wait cpu ticks
0 IRQ cpu ticks
114100 softirq cpu ticks
0 stolen cpu ticks
41745039 pages paged in
125109404 pages paged out
1098642 pages swapped in
2128871 pages swapped out
2199460184 interrupts
3279263642 CPU context switches
1514898192 boot time
3676272 forks
前面几行就能看到内存使用。
vmstat
命令能够显示进程,内存,paging,block IO,traps,disks,和 cpu 的活动及使用状态。
top
命令通常被用来检查每一个进程的内存和 CPU 使用,但其实也可以用他来查看系统整个的内存使用,最上方的数据就能看到。
同样,可视化程度更高的 htop
也能够快速的查看到使用的内存
通过一下命令也可以查看,这个命令只能够查看到物理内存条的大小,但是可以提供给我们一个信息就是系统的总内存,是由两个 8G 内存组成,而不是由 8 个 2G 内存条组成。
sudo dmidecode -t 17
选项 -t
是 --type
缩写
sudo dmidecode -t 17
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.8 present.
Handle 0x001A, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0019
Error Information Handle: Not Provided
Total Width: 64 bits
Data Width: 64 bits
Size: 8192 MB
Form Factor: DIMM
Set: None
Locator: DIMM1
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MHz
Manufacturer: 802C0000802C
Serial Number: 12303315
Asset Tag: 0F161300
Part Number: 8ATF1G64AZ-2G3B1
Rank: 1
Configured Clock Speed: 2133 MHz
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: 1.2 V
Handle 0x001B, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0019
Error Information Handle: Not Provided
Total Width: 64 bits
Data Width: 64 bits
Size: 8192 MB
Form Factor: DIMM
Set: None
Locator: DIMM2
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MHz
Manufacturer: 802C0000802C
Serial Number: 1230330E
Asset Tag: 0F161300
Part Number: 8ATF1G64AZ-2G3B1
Rank: 1
Configured Clock Speed: 2133 MHz
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: 1.2 V
这个输出信息中可以看到电脑使用的两条 8G 的内存,类型 DDR4,速度是 2400 MHz.
下面这个命令也能够查看电脑上安装的内存条
sudo lshw -short -C memory
H/W path Device Class Description
=====================================================
/0/0 memory 64KiB BIOS
/0/18/15 memory 128KiB L1 cache
/0/18/16 memory 1MiB L2 cache
/0/18/17 memory 8MiB L3 cache
/0/14 memory 128KiB L1 cache
/0/19 memory 16GiB System Memory
/0/19/0 memory 8GiB DIMM Synchronous 2400 MHz (0.4 ns)
/0/19/1 memory 8GiB DIMM Synchronous 2400 MHz (0.4 ns)
/0/19/2 memory [empty]
/0/19/3 memory [empty]
/0/100/1f.2 memory Memory controller
同样可以使用 GUI 来查看,Gnome System Monitor 提供一个简单的界面显示系统进程,内存和文件系统。可以使用以下命令在终端开启
gnome-system-monitor
Gson 是 Google 发布的一个用于序列化和反序列化 json 的工具库,可以非常轻松的实现 json 到 java Object 的转变,也同样非常简单的可以将一个 Java 实例序列化为 json。
Gson 包中主要的类有 Gson, GsonBuilder, JsonParser 等等。
JsonParser 是将 json 串解析成 JsonElement 的工具类。JsonParser 有三个 parse()
方法,分别接受不同类型的参数:
内部实现时使用 JsonReader 类进行解析。
Gson 最基本的使用方法,无非就是 toJson()
和 fromJson()
两个函数,对于简单类,可以使用如下方式:
String json = gson.toJson(target); // serializes target to Json
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
如果类中含有数组,会需要用到 toJson(Object, Type)
和 fromJson(String, Type)
这两个方法:
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> target = new LinkedList<String>();
target.add("blah");
Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);
Gson 默认情况下会使用 POJO 一致的属性名去解析和生成 json 字串,但是如果想要解析和生成的时候重命名字段,可以使用 @SerializedName
来重命名。
比如 json 中的字段叫做 email_address
,而在 Java 类中,可以改为 emailAddress。这样,gson 在生成时会自动将 emailAddress 属性,改为 email_address
@SerializedName("email_address")
public String emailAddress;
如果 emailAddress
在 json 中还有其他的方式,也可以使用:
@SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
public String emailAddress;
在将 json 转化为 Java Object 的时候,可以自定义 Deserializer ,格式化其中的某一些字段,比如下面内容,将 dateOfBirth
修改了格式。
public class ActorGsonDeserializer implements JsonDeserializer<ActorGson> {
private SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a");
@Override
public ActorGson deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement jsonImdbId = jsonObject.get("imdbId");
JsonElement jsonDateOfBirth = jsonObject.get("dateOfBirth");
JsonArray jsonFilmography = jsonObject.getAsJsonArray("filmography");
ArrayList<String> filmList = new ArrayList<>();
if (jsonFilmography != null) {
for (int i = 0; i < jsonFilmography.size(); i++) {
filmList.add(jsonFilmography.get(i).getAsString());
}
}
ActorGson actorGson = null;
try {
actorGson = new ActorGson(jsonImdbId.getAsString(), sdf.parse(jsonDateOfBirth.getAsString()), filmList);
} catch (ParseException e) {
e.printStackTrace();
}
return actorGson;
}
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(ActorGson.class, new ActorGsonDeserializer())
.create();
在 java 序列化是,一旦变量被 transient
修饰,变量将不再是持久化的一部分,变量内容在序列化后无法获得访问。同样如果在使用 Gson 序列化 json 的时候,添加关键字 transient 同样,Gson 也会忽略 该字段:
private transient int id;
需要注意的是,如果一个 field 是 static
静态变量,gson 也会排除。Gson 在创建的时候可以使用 excludeFieldsWithModifiers
来指定排除的 field:
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
最早不知道 transient
关键字的时候,看文档中只写了 @Expose
注解,但其实效果是一样的。使用 @Expose
注解来保留关心的字段,其他不需要的字段可以不注解,同样能够达到效果。
private int id; // 忽略 id
@Expose private String name; // 保留 name
如果使用 @Expose
注解,那么则需要使用 GsonBuilder()
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
如果有更加复杂的排除规则,比如某一批 Field,或者指定的 Class 不需要 serialize ,可以使用 ExclusionStrategy
来自定规则。
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.jutils.gson.entity.Car;
import com.jutils.gson.entity.PersonStrategy;
/**
* ExclusionStrategy
*
* A strategy (or policy) definition that is used to decide whether or not a field or top-level class should be serialized or deserialized as part of the JSON output/input.
*/
public class ExcluStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
// ignore id field
if (f.getDeclaringClass() == PersonStrategy.class && f.getName().equals("id")) {
return true;
}
return false;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
if (aClass == Car.class) {
return true;
}
return false;
}
}
比如说上面这个,就忽略 PersonStrategy 类中的 Field “id”,还有 Car 类。如果使用这种方式,那么需要在构造 gson 时:
Gson gson = new GsonBuilder().setExclusionStrategies(new ExcluStrategy()).create();
如果熟悉了这个 ExclusionStrategy
就可以书写自己的注解。
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}
AnnotationExclusionStrategy 类
public class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(Exclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
测试方法
@Test
public void annotationTest() {
PersonAnnotation annotation = new PersonAnnotation(1L, "name", 10);
Gson gson = new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
String s = gson.toJson(annotation);
System.out.println(s);
}
使用 GsonBuilder 来构造 Gson,然后传入 FieldNamingPolicy,这个方法接受很多参数,不仅可以做到将小写下划线转驼峰,还有其他很多功能。
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
FieldNamingPolicy 还有这些
IDENTITY
Using this naming policy with Gson will ensure that the field name is unchanged.
LOWER_CASE_WITH_DASHES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by a dash (-).
LOWER_CASE_WITH_UNDERSCORES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by an underscore (_).
UPPER_CAMEL_CASE
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form.
UPPER_CAMEL_CASE_WITH_SPACES
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form and the words will be separated by a space.
某一些情况下在反序列化 json 到 Object 时,在某些字段 JSON 中缺失时,想要给 Object 提供一个默认值,但是 Gson 在处理原始类型时,比如 int 字段,如果缺失会自动赋值为 0,某些情况下是不符合预期的。Gson 在设定默认值时需要,在 Object 构造函数中初始化该字段,并且实现 InstanceCreator
接口。
public class RawDataInstanceCreator implements InstanceCreator<RawData> {
@Override
public RawData createInstance(Type type) {
return new RawData();
}
}
构造时传入:
Gson gson = new GsonBuilder()
.registerTypeAdapter(RawData.class, new RawDataInstanceCreator())
.create();
举一个比较通俗的例子就是,当一条 JSON 数据返回时,字段值是一个 Double,但是返回的时候是放在字符串中返回的。这个时候就需要用到 TypeAdapter。
class DoubleTypeAdapter extends TypeAdapter<Number> {
@Override
public void write(JsonWriter jsonWriter, Number number) throws IOException {
jsonWriter.value(number);
}
@Override
public Number read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return 0D;
}
String result = jsonReader.nextString();
if ("".equals(result)) {
return 0D;
}
return Double.parseDouble(result);
}
}
然后创建 Gson 时:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Double.class, new DoubleTypeAdapter()).create();
下面是小米路由器折腾记录,包括开启 SSH,然后安装 MT 工具箱,主要是为了其中的两个插件,一个是去广告,一个是 SS 代理,不过附带竟然发现了 frp 插件,开心啊。下面就是具体的记录。
下载 开发版,在后台点击上传安装开发版的 bin,然后等待重启,完成开发版安装。
小米帐号绑定小米路由器,设置路由器可正常上网,并使用手机版小米 WiFi 绑定路由器,然后在绑定小米账号的前提下,进入 https://d.miwifi.com/rom/ssh 这个网站,然后找到 SSH 登录的 root 密码,之后会用到。
工具包使用方法:小米路由器需升级到开发版 0.5.28 及以上,小米路由器 mini 需升级到开发版 0.3.84 及以上,小米路由器 3 即将支持。注意:稳定版不支持。 请将下载的工具包 bin 文件复制到 U 盘(FAT/FAT32 格式)的根目录下,保证文件名为 miwifi_ssh.bin; 断开小米路由器的电源,将 U 盘插入 USB 接口; 按住 reset 按钮之后重新接入电源,指示灯变为黄色闪烁状态即可松开 reset 键; 等待 3-5 秒后安装完成之后,小米路由器会自动重启,之后您就可以尽情折腾啦 :)
如果 Chrome 浏览器出现错误提示:”This site can’t be reached. d.miwifi.com refused to connect. ERR_CONNECTION_REFUSED”,需要手动将http
替换为https
。
在使用 ssh 登录路由器之前确认路由器的 IP 地址,比如我下面例子中会使用 192.168.31.1
来举例。
MT 工具箱是目前第三方插件里面最为方便易用的插件集合
KMS 服务器,VSFTP 服务器,VPN 服务器,远程管理,ARIA2,Koolproxy 广告过滤,阿呆喵广告过滤,Shadowsocks,webshell, frp 服务
Misstar Tools 2.0 工具箱安装,经过上面的几个步骤,开启 SSH 之后,使用 ssh root@192.168.31.1
来连接路由器,使用之前获取的 SSH root 密码登录,进去之后 passwd
修改 root 密码,以方便下一次使用,然后直接执行如下代码,就能安装 MT 工具箱。
wget http://www.misstar.com/tools/appstore/install.sh -O /tmp/install.sh && chmod a+x /tmp/install.sh && /tmp/install.sh
卸载
wget http://www.misstar.com/tools/uninstall.sh -O /tmp/uninstall.sh && chmod +x /tmp/uninstall.sh && /tmp/uninstall.sh
在开启 SSH,并且安装 Misstar Tools 工具箱的前提下,有两种方法可以安装 Shadowsocks 插件,第一种就是使用备份的文件,传入路由器之后运行安装,第二种直接在安装页面修改页面内容,推荐使用第二种方法。
chmod +x ./install_ss & ./install_ss add
chmod +x ./install_ss & ./install_ss del
from: http://bbs.xiaomi.cn/t-13765387
使用 Chrome 打开 MT 插件管理页面,使用开发者工具,定位页面中任意一个 安装 按钮,然后找到代码中的 id="ftp"
字样,修改为 id="ss"
,然后点安装,成功后会回到 MT 工具箱首页,配置使用即可。
按照上面官方的步骤操作完路由器就已经获取 root 权限了,再使用 ssh 工具连接路由即可,建议在进行下一步操作之前备份原版分区文件
root@XiaoQiang:~# cat /proc/mtd
dev: size erasesize name
mtd0: 07f80000 00020000 "ALL"
mtd1: 00080000 00020000 "Bootloader"
mtd2: 00040000 00020000 "Config"
mtd3: 00040000 00020000 "Bdata"
mtd4: 00040000 00020000 "Factory"
mtd5: 00040000 00020000 "crash"
mtd6: 00040000 00020000 "crash_syslog"
mtd7: 00040000 00020000 "reserved0"
mtd8: 00400000 00020000 "kernel0"
mtd9: 00400000 00020000 "kernel1"
mtd10: 02000000 00020000 "rootfs0"
mtd11: 02000000 00020000 "rootfs1"
mtd12: 03580000 00020000 "overlay"
mtd13: 012a6000 0001f000 "ubi_rootfs"
mtd14: 030ec000 0001f000 "data"
查看 U 盘挂载的位置命令:
df -h
U 盘一般是 /extdisks/
开头,后面的可能不一样,我的是: /extdisks/sda4/
备份小米路由器 3G 原版分区到文件,每行是一条命令,分别执行,最后一条可能会报错,可不用理会,最有用的是 mtd0-mtd4
dd if=/dev/mtd0 of=/extdisks/sda4/rom/ALL.bin
dd if=/dev/mtd1 of=/extdisks/sda4/rom/Bootloader.bin
dd if=/dev/mtd2 of=/extdisks/sda4/rom/Config.bin
dd if=/dev/mtd3 of=/extdisks/sda4/rom/Bdata.bin
dd if=/dev/mtd4 of=/extdisks/sda4/rom/Factory.bin
dd if=/dev/mtd5 of=/extdisks/sda4/rom/crash.bin
dd if=/dev/mtd6 of=/extdisks/sda4/rom/crash_syslog.bin
dd if=/dev/mtd7 of=/extdisks/sda4/rom/reserved0.bin
dd if=/dev/mtd8 of=/extdisks/sda4/rom/kernel0.bin
dd if=/dev/mtd9 of=/extdisks/sda4/rom/kernel1.bin
dd if=/dev/mtd10 of=/extdisks/sda4/rom/rootfs0.bin
dd if=/dev/mtd11 of=/extdisks/sda4/rom/rootfs1.bin
dd if=/dev/mtd12 of=/extdisks/sda4/rom/overlay.bin
dd if=/dev/mtd13 of=/extdisks/sda4/rom/ubi_rootfs.bin
dd if=/dev/mtd14 of=/extdisks/sda4/rom/data.bin
首先,下载 Breed 刷入不死 breed,
下载地址:https://breed.hackpascal.net/ (搜索 breed-mt7621-xiaomi-r3g.bin)
用命令下载,在电脑终端中运行
wget https://breed.hackpascal.net/breed-mt7621-xiaomi-r3g.bin
将文件传入路由器
scp breed-mt7621-xiaomi-r3g.bin root@192.168.31.1:/tmp/
进入路由器
ssh root@192.168.31.1
cd /tmp
mtd -r write breed-mt7621-xiaomi-r3g.bin Bootloader
机器会重新启动,指示灯变蓝,确保电脑设置为自动获取 IP 地址,使用网线连接。刷入成功后,断掉电源,用东西顶住复位键不松开,然后再接上电源等待 10 秒左右放开复位键,浏览器输入 http://192.168.1.1 即可进行 Breed Web 恢复界面:
如果要刷入其他固件,打开 Breed Web 恢复控制台,点击左侧“固件更新”,钩选“固件”,选择固件,再点“上传”
更多内容可查看: http://www.right.com.cn/forum/thread-161906-1-1.html
小米路由器基本上沿用了 Linux 的目录结构,但是也有一些区别,在用了一段时间之后发现某些目录被写满了导致一些第三方服务无法开启,也是很恼人了。这里及列一下这些目录的作用,以便于清理。
/ 根目录
bin 二进制可执行命令
boot bootloader 启动相关
data 用户数据文件
dev 设备文件,驱动等等
etc 配置文件
extdisks 外置硬盘挂载点
lib 共享库
mnt 临时挂载点
opt 可选程序安装点
proc 系统内存映射虚拟目录,可用来获取系统信息
root 系统管理员主目录
sbin 系统管理命令
sys
userdisk 路由硬盘(一般为内置)
usr 存放应用程序和文件
tmp 临时存放点
www 浏览器网页存放区
以前也整理过一篇 TP LINK MR12U 刷 Openwrt ,路由器刷机和手机刷机异曲同工。不过刷机需谨慎,稍有错误就有可能导致硬件损坏。前些时候入手了一个斐讯的 K2P,
大致分为几步,第一步开启 telnet,刷入 uboot,然后通过 uboot 刷入新固件。
因为新版本固件 V22.7.8.2版本及之后 需要使用 特殊工具 才能开启 telnet ,所以先要使用这个工具来开启 telnet。然后再使用 a大 的脚本安装 breed。
Opboot 及 Breed进入方法:
如果你当前是官改或其他第三方固件,请在opboot或breed输入:
地址: http://www.right.com.cn/forum/thread-221578-1-1.html
官改版本比较稳定,占用内存小,同时也带去广告,SS/SSR,内网穿透等等功能
地址: http://www.right.com.cn/forum/thread-216468-1-1.html
潘多拉版本,同时也支持 SS/SSR,去广告,定时重启等等功能
地址: http://www.right.com.cn/forum/thread-255053-1-1.html
地址: http://www.right.com.cn/forum/thread-240730-1-1.html
K2P基于openwrt最新稳定版chaos_calmer的固件
荒野无灯 http://www.right.com.cn/forum/thread-218704-1-1.html 这个固件和原版固件的性能对比可以参考这一篇 http://www.acwifi.net/3173.html
下载地址:http://files.80x86.io/router/rom/K2P/
k2p 全套其他的固件,可以参考这个帖子 http://www.right.com.cn/forum/thread-254947-1-1.html
最后总结下,其实大部分的教程都能在恩山找到,在斐讯的论坛里面,找精华贴,能过滤很多杂乱的信息。并且通过 Google 关键词搜索也能够很快的找到答案。
Apache commons 包中 CollectionUtils 常用方法介绍。
org.apache.commons.collections.CollectionUtils;
addAll(Collection<C> collection, C[] elements)
将后面的元素添加到集合中,或者是添加一个非空元素 addIgnoreNull(Collection<T> collection, T object)
。
将两个有序的列表合二为一,使用 collate()
方法
List<Customer> sortedList = CollectionUtils.collate(list1, list2);
使用 collect()
方法
Collection<Address> addressCol = CollectionUtils.collect(list1,
new Transformer<Customer, Address>() {
public Address transform(Customer customer) {
return customer.getAddress();
}
});
containsAll(Collection<?> coll1, Collection<?> coll2)
判断是否 coll2 中所有元素都在 coll1 中
containsAny(Collection<?> coll1, Collection<?> coll2)
判断 coll2 中有没有至少一个元素在 coll1 中
CollectionUtils.isSubCollection(list3, list1)
可以使用该函数来判断两个集合是否是包含关系。
使用 CollectionUtils.filter
可以用来过滤 Collection 中不满足条件的元素。这个函数接收两个参数,第一个参数为列表,第二个参数为 Predicate 用来设置条件。
boolean isModified = CollectionUtils.filter(linkedList1,
new Predicate<Customer>() {
public boolean evaluate(Customer customer) {
return Arrays.asList("Daniel","Kyle").contains(customer.getName());
}
});
filter()
函数是当 Predicate
返回 false 时移除元素;filterInverse()
函数是当 Predicate
返回 true 时移除元素。
如果想要获取过滤的结果,可以使用select
和 selectRejected
这两个函数。
CollectionUtils.isEmpty(null): true
CollectionUtils.isEmpty(new ArrayList()): true
CollectionUtils.isEmpty({a,b}): false
CollectionUtils.isNotEmpty(null): false
CollectionUtils.isNotEmpty(new ArrayList()): false
CollectionUtils.isNotEmpty({a,b}): true
集合a: {1,2,3,3,4,5} 集合b: {3,4,4,5,6,7}
CollectionUtils.union(a, b)(并集): {1,2,3,3,4,4,5,6,7}
CollectionUtils.intersection(a, b)(交集): {3,4,5}
CollectionUtils.disjunction(a, b)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.disjunction(b, a)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.subtract(a, b)(A与B的差): {1,2,3}
CollectionUtils.subtract(b, a)(B与A的差): {4,6,7}
更多的内容可以 Google: https://www.google.com/search?q=CollectionUtils
示例代码可以到 https://github.com/einverne/jutils/tree/master/src/test 来查看。
又到一年的年末,2017 年对于我来说是变化最大的一年,这一年里完成了身份的转变,自此步入社会。这一年年初的时候定下目标年度 100 本书,看来又是无法完成了,现在看豆瓣的标注,也仅仅只有 30 本左右,距离目标甚是遥远。这里也不想再找借口,2017 年中,很大一部分时间共享给了王者荣耀和荒野行动,这两个手游占据了原本打散的碎片化时间。今年也一度差点荒废了 Kindle,Kinde10000 的消失,曾一度让 Kindle 在床头吃灰。
2017 年的读书记录如果再按照以前的分类划分的话,也没太多的变化,唯一增加的分类可能是增加了金融相关的一些书籍。那下面就总结一下好了。
排在首位的永远是小说,在去年读过了大部分的东野圭吾的小说之后,今年似乎找不到主题,除了零星捡漏又看了几部东野圭吾的小说之外,其他作者的很少涉猎。不过今年读过的东野圭吾的小说中也还是有让我记忆比较深刻的几本:
今年看的专业书虽然数量有所增长,但是还是没达到目标。
还有好多都零零落落没看完。
OpenFalcon 是一款企业级、高可用、可扩展的开源监控解决方案,提供实时报警、数据监控等功能,由小米公司开源。使用 Falcon 可以非常容易的监控整个服务器的状态,比如磁盘空间,端口存活,网络流量等等。
最近有些监控需求所以看了一下其中涉及到概念。
Open-Falcon,采用和 OpenTSDB 相似的数据格式:metric、endpoint 加多组 key value 的 tags,举两个例子:
{
metric: load.1min,
endpoint: open-falcon-host,
tags: srv=falcon,idc=aws-sgp,group=az1,
value: 1.5,
timestamp: `date +%s`,
counterType: GAUGE,
step: 60
}
{
metric: net.port.listen,
endpoint: open-falcon-host,
tags: port=3306,
value: 1,
timestamp: `date +%s`,
counterType: GAUGE,
step: 60
}
下面是一段 Python 上报数据的代码,其中涉及到的参数都是必须传的。
#!-*- coding:utf8 -*-
import requests
import time
import json
ts = int(time.time())
payload = [
{
"endpoint": "test-endpoint",
"metric": "test-metric",
"timestamp": ts,
"step": 60,
"value": 1,
"counterType": "GAUGE",
"tags": "idc=lg,loc=beijing",
},
{
"endpoint": "test-endpoint",
"metric": "test-metric2",
"timestamp": ts,
"step": 60,
"value": 2,
"counterType": "GAUGE",
"tags": "idc=lg,loc=beijing",
},
]
r = requests.post("http://127.0.0.1:1988/v1/push", data=json.dumps(payload))
print r.text
说明:
metric
: 最核心的字段,监控指标名称(监控项),代表这个采集项具体度量的是什么,比如是 cpu_idle
呢,还是 memory_free
, 还是 qps
endpoint
: 标明 metric 的主体(属主),比如 metric 是 cpu_idle,那么 endpoint 就表示这是哪台机器的 cpu_idle,一般使用机器的 hostnametimestamp
: 表示上报该数据时的 unix 时间戳,注意是整数,代表的是秒value
: 代表该 metric 在当前时间点的值,float64step
: 表示该数据采集项的上报周期,这对于后续的配置监控策略很重要,必须明确指定。counterType
: 是 Open Falcon 定义的数据类型,取值只能是COUNTER
或者GAUGE
二选一,前者表示该数据采集项为计时器类型(累加值),后者表示其为原值 (注意大小写,这个值是一个波动的值)
- GAUGE:即用户上传什么样的值,就原封不动的存储
- COUNTER:指标在存储和展现的时候,会被计算为 speed,即(当前值 - 上次值)/ 时间间隔
tags
: 监控数据的属性标签,一组逗号分割的键值对,对 metric 进一步描述和细化,可以是空字符串。比如 idc=lg,比如 service=xbox 等,多个 tag 之间用逗号分割说明:这 7 个字段都是必须指定
Metric 监控指标,时序数据。
包括不同类型:
endpoint 通常来指服务器节点。
在 Falcon 中有这样几个不同的接口
Meter 用来累加,可以输出累加和,变化率。
Gauge 用来记录瞬时值,支持 int 64, float 64 类型
Histogram 用来计算统计分布,输出最小值,最大值,平均值,75,95,99 百分比等等。
最近遇到的需求就是如果一段时间内,OpenFalcon 没有收集到数据,也就是 agent 没有采集到数据,程序挂了,或者没有执行,那么就报警。在最开始的时候查看了一下 OpenFalcon 报警函数
all(#3): 最新的 3 个点都满足阈值条件则报警
max(#3): 对于最新的 3 个点,其最大值满足阈值条件则报警
min(#3): 对于最新的 3 个点,其最小值满足阈值条件则报警
sum(#3): 对于最新的 3 个点,其和满足阈值条件则报警
avg(#3): 对于最新的 3 个点,其平均值满足阈值条件则报警
diff(#3): 拿最新 push 上来的点(被减数),与历史最新的 3 个点(3 个减数)相减,得到 3 个差,只要有一个差满足阈值条件则报警
pdiff(#3): 拿最新 push 上来的点,与历史最新的 3 个点相减,得到 3 个差,再将 3 个差值分别除以减数,得到 3 个商值,只要有一个商值满足阈值则报警
lookup(#2,3): 最新的 3 个点中有 2 个满足条件则报警
这一下子就懵了,报警触发的条件都是根据最近上报的几个点的阈值来触发的,而我的需求可能是一段时间内根本没有上报数据。
然后仔细查看文档之后,发现 OpenFalcon 有一个 Nodata 配置,Nodata 的配置正好解决了上面的需求,当机器一段时间内中断上报时,Nodata 配置会上报一个指定的值,然后报警函数就能够根据 Nodata 上报的值来报警。
Nodata 的配置在 OpenFalcon 的后台,在 Nodata 页面添加 Nodata ,填写
name
nodata 的名字,标示什么中断了endpoint
选择 Endpoint ,机器列表,一行一个metric
指定 metrictags
指定 tagstype
暂时只支持 GAUGE周期
秒,与原始监控指标一致上报中断时补发值
当自定义上报中断的时候 Nodata 就会补发,通过补发的值,比如正常的取值是 >0
的正数值,那么补发的值可以写上 -1
,然后通过最近连续的三个 -1
来触发报警。
在服务端接收到打点数据时,在报表中会有一些内置的指标,比如说 75-percentile, 95-percentile, 99-percentile, 999-percentile,CPS-1-min, CPS-5-min, CPS-15-min 等等指标。
对于 percentile 的指标,含义如下:
对于这些指标,还有 min, max, mean 这些指标,这些都比较简单不再赘述。
而另外一种, falcon 中叫做 Meter, 有两种
最近看了一篇文章 介绍了 Open Falcon 项目的起由,这里面提到了 Open Falcon 为了解决 zabbix 的一些问题而被提出
argparse 模块可以轻松的写出友好的命令行交互,让使用者轻松定义命令行参数及使用参数。
首先通过 argparse
来创建一个 ArgumentParser
对象:
parser = argparse.ArgumentParser(description='Process some integers.')
ArgumentParser
对象会保存命令行基本的信息。
构造好了 ArgumentParser
对象之后使用 add_argument()
来往其中添加参数。
>>> parser.add_argument('integers', metavar='N', type=int, nargs='+',
... help='an integer for the accumulator')
>>> parser.add_argument('--sum', dest='accumulate', action='store_const',
... const=sum, default=max,
... help='sum the integers (default: find the max)')
这些信息都被保存在对象中,直到 parse_args()
函数被调用。 integers
参数保存了一个或者一个以上列表的int值, accumulate
参数则会根据传入的参数选择 sum()
或者 max()
方法。
ArgumentParser
通过 parse_args()
来解析和使用参数
>>> parser.parse_args(['--sum', '7', '-1', '42'])
Namespace(accumulate=<built-in function sum>, integers=[7, -1, 42])
关于 ArgumentParser
对象更多的使用方法可以参考官方文档
argparse内置6种动作可以在解析到一个参数时进行触发:
argparse能将参数定义组合成“群组”。默认情况下是使用两个群组,一个是选项的群组,另一个是必须的与位置相关的参数群组。
在基础解析器中使用add_argument_group()来创建一个“身份认证”群组,然后逐个添加身份认证相关的选项到该群组。
import argparse
parser = argparser.ArgumentParser(add_help=False)
group = parser.add_argument_group('authentication')
group.add_argument('--user', action="store")
group.add_argument('--password', action="store")
定义互斥的选项是选项分组特性的一个特例,使用add_mutually_exclusive_group()
而不是add_argument_group()
。
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action="store_true")
print parser.parse_args()
你可以配置单个参数的定义使其能够匹配所解析的命令行的多个参数。根据需要或期望的参数个数,设置nargs为这些标识值之一:
值 | 含义 |
---|---|
N |
参数的绝对个数(例如:3) |
? |
0或1个参数 |
* |
0或所有参数 |
+ |
所有,并且至少一个参数 |
argparse将所有参数值都看作是字符串,除非你告诉它将字符串转换成另一种数据类型。add_argument()
的type参数以一个转换函数作为值,被ArgumentParser
用来将参数值从一个字符串转换成另一种数据类型。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', type=int)
parser.add_argument('-f', type=float)
parser.add_argument('--file', type=file)
try:
print parser.parse_args()
except IOError, msg:
parser.error(str(msg))
要想将一个输入参数限制为一个预定义集中的某个值,则使用choices参数。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--mode', choices=('read-only', 'read-write'))
print parser.parse_args()
values的类型取决于nargs的值。如果该参数允许多个值,则values会是一个列表,即使其仅包含一个列表项。
前段时间 Google 1T 空间到期,这几年积累的邮件,照片,Drive 中的内容已经超过了了 20+GB,突然一下子 Google 的空间就满了,Gmail 提醒我空间不足可能无法接收邮件,Drive 无法上传文件,而我查看了一下空间占用,发现 Google Photos 占用超过了 12G,网上搜了一圈,发现 Google Photos 提供一键压缩,然后瞬间就可以继续无限存储了。
因此诞生了这样一篇文章,来看看现在的 Google Photos 怎么来管理照片。其实到现在为止我还是很怀念被关闭的 Picasa,虽然数据无缝的同步到了新的 Google Photos 中,但是这个产品层面的转变,让我很多习惯都改变了,并且以前很多好用的功能都无法找到代替品。
这里不仅能看到 Google Photos https://drive.google.com/#quota
在桌面网页版设置中 https://photos.google.com/settings,可以点击 RECOVER STORAGE
按钮来一键将 Google Photos 中以前保存的无压缩的照片压缩到 Google 支持的高清不占用空间的格式。Google Photos 在计算空间时 16 megapixel 及以下的照片不算空间,而一般 iPhone 7 拍的照片也只有 12 megapixel,所以完全不用担心损失画质,并且我曾经对比过Google Photos 压缩过的图片和原片,肉眼无法看出差别。这个操作不会影响存放在 Google Drive 中的原图,但是会压缩曾经上传到 Blogger,Google Map,Panoramio,Google+ 和 Google Hangouts 中大于 16 megapixels 的图片。
Google 一路改过来,Picasa -> Google+ Photos -> Google Photos,外链是原来越难看。
Picasa
Google Photos
https://lh3.googleusercontent.com/7wqn4oULz6lckUdJbPZzT094xyJiDewL03QS5BZk5kz9c5vP_wvk7fGxSOtxYvAG9N98blJGfZTAdweG_rYnC7sLIrjBAJfX0uIsub_P6waW9AzWmhM0M23BRRVOjNVn52CZIpKbmxMdr9jyjI27zdFCKJb7ZenbDtsFhkr-O7gaXcKuQkQnXJPOI3I8rgYcLGDa3vggdeyCVneimFmRCWfRX75LtJegBshiF7Jy4-fE6slJalAUqtwRAmBck2kaAofuZOHlpAmSsmaNPuaIWoMuTu_AgX1FU1Xx0f9HONI-F0awr0vPl0en1klvBMWXS8RbW5GHrbOrDXtQbpveJ-KnfXPmtTkjxF7vakMMUvWugpg2eohkWh354ndUoqooOP3LrVc-Q8TjrP_9xxkJLOGCdK-Exh2mhXKiTnz_89iPD5dfI27ZmZdY4cVblw2qAOtJ-KVuGXT6oQy0mjttwuiVJONCz-Hl2Tvuncl9ZTCq0XvOBx6wJWDZELxtust5CXIsrsO4L5cSYlxNG_CBXTuR=s945-no
API 导出
https://lh3.googleusercontent.com/bhmJ-XtoL1wMCw3Q2YA0Ff_L8m52OgFbxYfalNjLO22e=s0
我惊讶于同一个照片,在不同的产品中的地址都是不同的,当然基于产品的逻辑倒也不能说什么,但是 Google Photos 的链接也太难看了。
下面是很早之前发过的帖子
其实到现在Google Photos的功能都不及Picasa
而Photos自带的功能看似神奇却也是Picasa自带功能的延伸
其实最后总结一下就是Google 搞了一个阉割的相册管理工具,未成熟却先发布,说是为了产品线统一其实最后就是把阉割了的Picasa直接放出来给用户用了。
最早的时候,从来都是每一次一个相册一个相册管理我的照片,按照日期和活动名字来命名每一个相册,如果分享的话直接同步到 Picasa 然后分享整个相册,然而移动互联网发展的今天 Google Photos 会自动备份手机中的所有图片,默认会给每一天新建一个相册,虽然现在 Google Photos 中间不会显示每天的相册名字,但是如果在其他地方用API来引用 Google Photos 中的内容会异常头疼,所以现在我已经放弃了。
最原先的时候我会精心挑选几张highlight 的照片来作为一个精选照片,而现在 Google 把所有的不管好坏的照片都备份了,以前 Google+ Photos 还能够自动挑选一些比较精华的照片,但是说实话那个照片也是比较鸡肋的,所以现在的 Google Photos 我都是在备份之后,挑选一些照片 Snapseed 或者 VSCO 再修一修上传。
h5ai 自己的介绍说自己的是为HTTP WEB服务设计的一款现代化的文件索引,主要面向文件,提供了现代化的可视化界面。他是一款功能强大的文件目录列表程序,由德国开发者 Lars Jung 主导开发,提供多种文件列表呈现方式,支持多种主流WEB服务器,可以在线预览文本,图片,音频,视频。
依赖:PHP 5.5+ and works fine with Apache httpd, lighttpd, nginx and Cherokee
在没有 h5ai 之前,我都使用 nginx 的自带的显示文件列表配置
location / {
audoindex on;
}
不过不管是 Apache 还是 Nginx 提供的文件列表都是非常简易的,只会显示当前文件夹下的文件,如果都是压缩文件还好,遇到一些多媒体,图片,音频,视频等等就会有一些不便。
h5ai 的安装非常方便,下载,解压,配置 Nginx,配置 DNS,访问即可,如果需要高级功能,可以再配置,主要的配置修改
sudo apt install php7.0
index index.html index.htm /_h5ai/public/index.php;
配置 Nginx,在 /etc/nginx/sites-available/
下创建 drive.einverne.info
server {
listen 80;
listen [::]:80;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
# Add index.php to the list if you are using PHP
root /var/www/drive.einverne.info/html;
index index.html index.htm /_h5ai/public/index.php;
server_name drive.einverne.info;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
# autoindex on;
try_files $uri $uri/ =404;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}
再将域名的 DNS 配置 A 记录解析到 VPS
在配置的 root 目录 /var/www/drive.einverne.info/html
下
wget https://release.larsjung.de/h5ai/h5ai-0.29.0.zip
unzip h5ai-0.29.0.zip
然后刷新浏览器即可。 哦对了要确保安装了 PHP 7.0 的哈。
h5ai 安装完成之后,可以到 domain/_h5ai/public/index.php
查看 h5ai 的相关信息,默认密码为空。页面中可以查看当前 h5ai 开启的选项。
Ubuntu 16.04 以上
apt-get -y install zip
apt-get -y install ffmpeg # 视频缩略图
apt-get -y install imagemagick # PDF 缩略图
编辑文件 /_h5ai/public/index.php
,在底部增加:
function auth ()
{
$valid_passwords = array ("账号" => "密码");
$valid_users = array_keys($valid_passwords);
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
$validated = (in_array($user, $valid_users)) && ($pass == $valid_passwords[$user]);
if (!$validated) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
die ("Not authorized");
}
}
在头部 <?php
的下一行,增加
auth();
位于 _h5ai/private/conf
目录下。
打包下载: 搜索 “download” 127 行,enabled 由 false 改为 true。
文件信息及二维码: 搜索 “info” 185 行,enabled 由 false 改为 true。
默认简体中文: 搜索 “l10n” 202 行,enabled 由 false 改为 true。
文件及文件夹多选: 搜索 “select” 323 行,enabled 由 false 改为 true。
还有二维码等等功能,看一眼配置基本就能明白。
修改 domain/_h5ai/public/index.php
页面的默认密码:
首先生成自定义 sha512 密码:http://md5hashing.net/hashing/sha512 然后搜索 “passhash”,大概第 10 行,将其密码改成自己生成的。
如果使用 LNMP 一键安装的环境,可能需要修改 php 配置,vim /usr/local/php/etc/php.ini
搜索 scandir、exec、passthru,将其从被禁用的函数中删除。