Pipfile
文件添加或删除安装的包,Pipfile.lock
来锁定安装包的版本和依赖信息。requirements.txt
, 使用 Pipfile
和 Pipfile.lock
来代替pyenv
的条件下,可以自动安装需要的 Python 版本这里就不得不提到 pyenv 了,pyenv
能用来管理不同的 Python 版本,结合 pyenv-virtualenv
也能够快速创建虚拟环境,不过这个 pipenv
提供了另外一种思路。
安装:
pip install pipenv
基本使用
--three / --two Use Python 3/2 when creating virtualenv.
如果在 macOS 下:
brew install pipenv
pipenv install package
pipenv update package
升级包在创建虚拟环境时,我们可以使用 python 版本信息
pipenv --python 3
pipenv --python 3.6.1
pipenv --python 2.7.13
pipenv 会自动扫描系统寻找合适的版本信息,如果找不到的话,同时又安装了 pyenv, 它会自动调用 pyenv 下载对应的版本的 python
类似 pip
pipenv install requests
pipenv install requests==2.19.1
pipenv install --dev requests # 安装开发环境依赖
如果 install 后面没有任何 package 会自动安装所有,第一次安装包会自动生成 lock 文件
兼容 requirements.txt
pipenv install -r path/to/requirements.txt
同样可以使用 Pipfile
和 Pipfile.lock
文件来生成 requirements.txt
pipenv lock -r
pipenv lock -r -d # 生成 dev requirements
所以一个基本流程就是,对于 pipenv 管理的项目,使用 pipenv lock
来冻结管理,在分享给别人之后使用 pipenv install
来安装依赖。
直接修改 Pipfile
文件
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[[source]]
url = "http://pypi.home.kennethreitz.org/simple"
verify_ssl = false
name = "home"
[dev-packages]
[packages]
requests = {version="*", index="home"}
maya = {version="*", index="pypi"}
records = "*"
pipenv shell
exit
pipenv graph
requests==2.19.1
- certifi [required: >=2017.4.17, installed: 2018.8.13]
- chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
- idna [required: >=2.5,<2.8, installed: 2.7]
- urllib3 [required: >=1.21.1,<1.24, installed: 1.23]
pipenv uninstall --all
pipenv 默认集成了 flake8
pipenv check --style hello.py
关于 pipenv 和 pyenv 等等其他的区别,可以看这个回答 ,如果想要在 IntelliJ 中使用 pipenv ,2018.2 更新的版本中,也已经支持 了。
pipenv 也使用 pyenv 来做 Python 的版本管理,所以基本上,分工明确了,pyenv 用来区分 Python 版本,pipenv 用来管理包依赖。
Wireshark 是一款网络分析工具,也是学习网络协议的工具,原先介绍过的 Charles ,mitmproxy 等 HTTP 抓包工具,都局限于 HTTP/HTTPS 请求,对于更底层的 TCP/IP,UDP 等协议就无能为力了。Wireshark 可以抓取网卡上的网络包,并实时展示,Wireshark 包括了过滤器,协议显示等等工具。
Wireshark 和其他工具的区别,比如 Charles,mitmproxy,Fiddler 等。Charles, mitmproxy,Fiddler 是专门用来捕获 HTTP,HTTPS 请求的。Wireshark 能获取 HTTP,也能获取 HTTPS,但是不能解密 HTTPS,所以 Wireshark 看不懂 HTTPS 中的内容。总结,如果是处理 HTTP,HTTPS 还是用 Charles, mitmproxy, Fiddler 等,其他协议比如 TCP,UDP,IP,ICMP 等就用 Wireshark
各大系统的安装文件:https://www.wireshark.org/download.html
Linux 下可以使用 PPA
sudo add-apt-repository ppa:wireshark-dev/stable && sudo apt-get update
sudo apt-get install wireshark
安装过程中选择 YES,让普通用户也能够抓包。
如果启动之后遇到
couldn't run /usr/bin/dumpcap in child process: Permission Denied.
这样的问题,
sudo dpkg-reconfigure wireshark-common
选择 “YES”, 将当前用户添加到 group
sudo adduser $USER wireshark
然后再登出登入 1
打开 Wireshark 就可以看到很多网络硬件可以选择,任选其中一块网卡就能够抓取经过这个网卡的所有流量包。常见的设备名字,或者网卡名字有这样几个:
还有一些设备名字可以参考之前的文章。比如我笔记本使用无线网卡连接了 WIFI,那么进入 Wireshark 之后选择 wlan0
设备,自动进入抓包,可以看到经过无线网卡的所有请求包。
Wireshark 的界面大致可以分成三个部分,最上面的部分为原始数据包预览,可以在该面板中看到抓取的包大致内容,包括序号,耗时,原始地址,目标地址,协议,长度,基本信息等等,分别使用不同的颜色标记了,这个颜色可以在设置 View -> Coloring Rules
中设置,根据不同的协议,或者自定义一些过滤规则,将关心的内容以不同的颜色标记出。
面板中间是封包详细信息 (Packet Details Pane),这个面板是最重要的,用来查看协议中的每一个字段。各行信息分别为
面板最下面一栏是数据包真正传输的内容,以十六进制和 ASCII 显示出来。
https://wiki.wireshark.org/CaptureFilters
两种过滤器的目的是不同的。
捕捉过滤器(CaptureFilters):用于决定将什么样的信息记录在捕捉结果中。需要在开始捕捉前设置。捕捉过滤器是数据经过的第一层过滤器,它用于控制捕捉数据的数量,以避免产生过大的日志文件。
显示过滤器(DisplayFilters):在捕捉结果中进行详细查找。他们可以在得到捕捉结果后随意修改。显示过滤器是一种更为强大(复杂)的过滤器。它允许您在日志文件中迅速准确地找到所需要的记录。
两种过滤器使用的语法是完全不同的。
http://openmaniak.com/cn/wireshark_filters.php
Wireshark 实际分析下三次握手的过程
在 wireshark 中输入 http 过滤, 然后选中 GET /tankxiao HTTP/1.1 的那条记录,右键然后点击”Follow TCP Stream”,
这样做的目的是为了得到与浏览器打开网站相关的数据包
可以看到 wireshark 截获到了三次握手的三个数据包。第四个包才是 HTTP 的, 这说明 HTTP 的确是使用 TCP 建立连接的。
第一次握手数据包
客户端发送一个 TCP,标志位为 SYN,序列号为 0, 代表客户端请求建立连接。
第二次握手的数据包
服务器发回确认包,标志位为 SYN,ACK. 将确认序号 (Acknowledgement Number) 设置为客户的 ISN 加 1 以. 即 0+1=1
第三次握手的数据包
客户端再次发送确认包 (ACK) SYN 标志位为 0,ACK 标志位为 1. 并且把服务器发来 ACK 的序号字段 +1, 放在确定字段中发送给对方. 并且在数据段放写 ISN 的 +1
https://askubuntu.com/a/778170/407870 ↩
Chevereto 是一款分享照片的程序,可以非常轻松得在自己的服务器上搭建照片分享程序,功能强大,外观精美。Chevereto 本身是收费使用的,一次性付费,终身使用,但是其开源版本可以免费使用。
目前 Chevereto 的价格是 $39,可以免费升级到 V4 版本。作者已经发生声明,在 2021 年底将终止 Free 版本的维护。
推荐使用 Docker 安装 Chevereto:
在安装之前请先检查需要的系统配置,至少保证 VPS 安装有
安装依赖
apt-get install nginx mysql-server php7.0 php7.0-common php7.0-curl php7.0-mysql php7.0-gd php7.0-xml php7.0-mbstring
从官网下载最新版本 压缩包
wget https://github.com/Chevereto/Chevereto-Free/archive/1.0.9.tar.gz
新建虚拟主机,修改域名 A 记录指向 VPS,然后配置对应的 vim /etc/nginx/sites-enabled/photo.einverne.info
由于 chevereto 默认提供基于 Apache 环境的伪静态规则,故 nginx 的配置是不能用的,需要自己添加规则
server {
listen 80;
listen [::]:80;
root /var/www/photo.einverne.info/html;
index index.php index.html index.htm;
server_name server_domain_or_IP;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
nginx 配置中还要注意一个 vim /etc/nginx/nginx.conf
配置中增加:
server { client_max_body_size 20M; //other lines… }
修改完重新加载 Nginx 配置 /etc/init.d/nginx reload
。
安装完 MySQL 之后需要为 Chevereto 新建一个数据库:
mysql -u root -p # 登录mysql
create database photo; # 创建 photo 数据库
默认的PHP上传大小在配置中略有不同,如果想要增大每张照片上传的大小,不仅上面 Nginx 中需要配置,同理 PHP 配置中也需要修改如下 vim /etc/php/7.0/fpm/php.ini
:
max_execution_time
max_input_time
memory_limit
post_max_size
upload_max_filesize
修改完重新加载PHP配置 /etc/init.d/php7.0-fpm reload
。
在做完这一系列配置之后,将之前下载的压缩包,在 /var/www/photo.einverne.info/html/
目录下解压,然后使用域名访问。如果一切都没有问题,那么 Chevereto 会显示要求数据配置。要求填写:数据库名、数据库用户名、数据库用户密码,还有数据库表头。
这几项在前面安装时都已经完成,新建的数据库名,还有 MySQL 的用户名和密码,最后的数据表头名可以不变。然后下一步会填写管理员的一些信息,最后完成就好。
设置中文,网上很多说需要修改密码,其实,在设置管理员面板中能够直接修改语言为中文。
修改图片存储路径:默认是在/images文件夹内,修改方法为在config.php修改define(‘DIR_IM’,’images/‘);
,这一步其实现在也能够在设置中直接修改。
如果上传遇到问题,界面上显示
Server error (Internal server error)
一般的情况就是 Nginx 或者 PHP 的上传大小设置不对,上传的图片大于了 Nginx 或者 PHP 能够处理的大小,这是时候调整上传的大小就可以。调试方法如下;
没有给出任何信息,查看 nginx 错误日志
tailf /var/log/nginx/error.log
发现如下错误
2018/02/05 19:21:12 [error] 2693#2693: *8 client intended to send too large body: 1318270 bytes, client: 172.xxx.xxx.xxx, server: photo.einverne.info, request: "POST /json HTTP/1.1", host: "photo.einverne.info", referrer: "http://photo.einverne.info/dashboard/settings/system"
解决办法
vi /etc/nginx/nginx.conf
添加
client_max_body_size 20M;
然后 /etc/init.d/nginx reload
重新加载 nginx 服务配置。
最近国内又火了一个答题游戏,回答正确12题平分多少多少万奖金。先不说他们都抄袭 HQ Trivia 这款App,但我这两天一直在想一个问题,为什么这一类的应用能火起来,难道就是因为最后平分100万,200万,现金带来的刺激吗?我想答案一定不是的,除了最开始的玩家或许还能分到几十块钱,在入场玩家越来越多的情况下,每个人瓜分到的奖励一定是越来越少的。那到底是什么能让一个应用一夜之间火到大江南北?
很多人之前说过是主播带火了”吃鸡”游戏,这一点我是认同的,但这应该只是能火的其中一个因素,也就是在正常的宣传情况下,带来的口碑一层层的堆叠,能少能有游戏能够做好老少咸宜,并且也能够在不同人群中拥有大多数的好评。所以游戏本身的可玩性,娱乐性是应该需要得到保证的,玩家能够在游戏中得到乐趣,并且通过游戏提供的场景能够扩展出来不同的玩法,而这些玩法又足够好玩。这里就要说道在吃鸡游戏前面1分钟左右的等待时间,有个叫平底锅的主播用来喊麦,还有人在游戏中组队交友。
然后这就说道了能够火,并且能够让绝大部分人能够接受的第二大原因,就是这个游戏需要有足够的社交属性,不管是陌生人社交,还是熟悉的好友的社交。在足够话题性的同时,能够让三五好友聚到一起开黑,说语音,开黑。这是这个时代的需要,在吃鸡火起来之前的“王者荣耀”我想也是因为能够拉上三五亲朋好友组个队,带个节奏,尤其在过年短短的几天时间内,好多亲朋好友从五湖四海聚到一家,每个人从事的职业和活动没有太多交集,唯有一局王者荣耀才能让气氛足够的活跃起来。所以这里也要提到另一个能够流行的原因了,也就是让绝大部分人获取的方法足够简单。
Steam 上的绝地求生或许正是因为他对设备要求过高,而过滤掉很大一部分玩家,才有了网易在国内能够通过荒野行动,快速的聚集了近2亿的玩家,让足够多的人,只用下载一个App,不到几分钟的时间就上手玩,才有能力让这个游戏走的足够远。
再其次,如果一个游戏想要不被这个时代淘汰,不断的进化,并且有能力让超越游戏的内容,比如游戏竞技,或者游戏的Cosplay出现,才能够立于不败之地,回想以前玩过的War 3,孵化出了Dota,Dota2,还有 LOL,可是暴雪自己却没有赶上,也略遗憾。而其实这一点也很难做到,无数的手机益智类游戏,火玩一波,再来一波,愤怒的小鸟,植物大战僵尸都已经存在回忆中了。
说道这里再回头来看答题分奖金这个应用,他能火正是也正是因为他足够有话题性,也足够有社交属性,失散多年的好友,同学,亲戚可能都被拉来一起玩,并且我从来没有看到过玩一个答题游戏,能够在短短几天时间内,开黑群,语音发答案,自动搜索答案助手等等延伸品出现。再这个游戏也足够被绝大多数人接受,官方打的宣传语就是,答题不仅能赢钱还能学习知识。这一些因素促成了这个游戏必定成为2018年开年的热点,但其实呢?我也再想另外一个问题,这个模式能够持续多长时间,爆红之后带来的用户如何沉淀。
首先这个应用能够吸引足够的目标,并且能吸引一定的广告主,这是一定的,但如果到足够需要花半个小时时间来赢取2 、 3 元的奖励,到最后还能剩下多少衷心的用户。我觉得如果使用这样的方式来吸引用户来吸取内容,未免不是一个极好的宣传方式,但如果频繁开场次,绝大多数的用户会厌倦了每天9点定时打开手机等待一串废话。
2018年的农历新年马上就要到来了,可想而知这样一个应用必定还有爆发的一次机会,但以后如何走,就只能拭目以待了。
用盒子也已经很多年了,几年来家里,自己用,也积累了一些常用的应用。这两天又拿到了 T1 盒子,又才想起来整理这样一份单子,这样不用每一次都一遍一遍的尝试了。记得以前 VST ,泰捷视频都还很不错的时候,再后来广电发了禁令,再后来这片市场混乱发展,各家大型网站优酷,爱奇艺又不敢公开大搞,却又在背后偷偷摸摸。再到现在几乎被什么芒果,CIBN 垄断,内容没什么可看,却什么都要收费。我始终抱有一个观点,如果电视盒子这一块开放发展,国内的厂家完全能够占领全世界的盒子市场,好几年前用的 Android 盒子就已经能够满足我的大部分需求,并且应用设计也早 Google 自己推出 Android TV 盒子以及规范 Android TV 应用好多年。可惜这一块市场被一道禁令打到了地下。
Android 盒子安装应用的方法,大概可以分为这几个:
当然对于一个新的设备,通过 adb 安装一个应用市场,然后通过应用市场下载其他应用是最简单的方式。
Apple 有 App Store, Google 有 Play Store,电视盒子直到现在依然还在乱斗:
地址就不都给了,Google 搜一下很快。
电视直播的应用,以前用 VST 和 泰捷还行,不过现在已经废了,然后现在 HDP 做的也还不错。
2018 年 3 月 3 号更新,发现了一款叫做 超级直播 的应用,非常好用。使用超级直播的时候,在播放页面点击设置,然后选择 “二维码扫一扫开启更多功能”,并在二维码显示之后,连续多次点击出现的二维码多次,即可解锁隐藏功能。隐藏功能开启 6000 以后的频道,包括可以查看多个台湾、香港的频道,频道范围在 6xxx 可能上下有些偏差,65xx 开始会有抢先观看的电影。
一下排名按照易用程度:
放一张截图
网络视频是我用的最多的了,我本人用 哔哩哔哩 最多
盒子带 samba ,能读局域网内 samba 共享的视频,那就需要一个本地播放器,最好支持的解码格式越多越好,在 Android 手机上我买过 MX Player Pro,不过免费的 KMPlayer 也不错。
说到乐播投屏这个应用,还是我去实体店,然后有一个店员向我展示一个只有魔方大小的投影仪时,用的应用,将手机的屏幕投影到投影仪上,我就记住了这个投屏应用,回来发现,iOS 投屏还是不错的。
其他体验还不错的应用:
今天突然想到,我平时看的最多的 YouTube ,是有电视版的啊,最近又把路由器更新了一下,局域网使用 YouTube 完全没问题啊,然后就下载了几个 YouTube for TV 的应用,发现只有下面这一个不需要依赖 Play Service,然后,在侧边栏设置中,有一个关联码,和手机关联,然后就可以非常轻松的将手机上的 YouTube 视频投送到电视盒子上,然后再到投影仪上。太舒服了。
YouTube for TV
版本:1.12.10 发布时间:2018-01-12
下载地址: https://apkpure.com/youtube-tv-watch-record-live-tv/com.google.android.apps.youtube.unplugged
OkHttp是一个非常高效的HTTP客户端,默认情况下:
阻塞请求
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
响应可读时回调 Callback 接口,读取响应时阻塞当前线程
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Request request, Throwable throwable) {
throwable.printStackTrace();
}
@Override public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
});
}
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
流的方式提交请求体
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
POST 方式提交文件
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
public void run() throws Exception {
RequestBody formBody = new FormEncodingBuilder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
awk 是一个强大的文本分析工具,它是 Linux 中功能强大的数据处理引擎之一,awk 可以非常轻松地处理比如每行都是相同格式的文本,比如日志,csv 格式等等。相对于 grep 的查找,sed 的编辑,awk 在其对数据分析并生成报告时,显得尤为强大。
当使用 awk 命令处理一个或者多个文件时,它会依次读取文件的每一行内容,然后对其进行处理,awk 命令默认从 stdio 标准输入获取文件内容,awk 使用一对单引号来表示一些可执行的脚本代码,在可执行脚本代码里面,使用一对花括号来表示一段可执行代码块,可以同时存在多个代码块。awk 的每个花括号内同时可以有多个指令,每一个指令用分号分隔,awk 其实就是一个脚本编程语言。
awk 命令和 sed 命令结构相同,通常情况下,awk 将每个输入行解释为一条记录而每一行中的内容(由空格或者制表符分隔)解释为每一个字段,一个或者多个连续空格或者制表符看做定界符。awk 中 $0
代表整个记录。
awk ' /MA/ { print $1 }' list
解释:打印包含 MA 的行中的第一个单词。再举一个具体的例子,比如
echo 'this is one world\nthat is another world' | awk '{print $1}'
那么输出就是 awk 处理之后的每一行第一个字符也就是:
this
that
awk 命令的基本格式
awk [options] 'script' file
options
这个表示一些可选的参数选项,script
表示 awk 的可执行脚本代码(一般被{}
花括号包围),这个是必须的。file
这个表示 awk 需要处理的文件,注意需要是纯文本文件(意味着 awk 能够处理)。
之前提到的,awk 默认的分割符为空格和制表符,awk 会根据这个默认的分隔符将每一行分为若干字段,依次用 $1
, $2
,$3
来表示,可以使用 -F
参数来指定分隔符
awk -F ':' '{print $1}' /etc/passwd
解释:使用 -F
来改变分隔符为 :
,比如上面的命令将 /etc/passwd
文件中的每一行用冒号 :
分割成多个字段,然后用 print 将第 1 列字段的内容打印输出
在 awk 中同时指定多个分隔符,比如现在有这样一个文件 some.log 文件内容如下
Grape(100g)1980
raisins(500g)1990
plum(240g)1997
apricot(180g)2005
nectarine(200g)2008
现在我们想将上面的 some.log 文件中按照 “水果名称(重量)年份” 来进行分割
$ awk -F '[()]' '{print $1, $2, $3}' some.log
Grape 100g 1980
raisins 500g 1990
plum 240g 1997
apricot 180g 2005
nectarine 200g 2008
在 -F
参数中使用一对方括号来指定多个分隔符,awk 处理 some.log 文件时就会使用 “(“ 或者 “)” 来对文件的每一行进行分割。
awk 除了 $
和数字表示字段还有一些其他的内置变量:
$0
这个表示文本处理时的当前行,$1
表示文本行被分隔后的第 1 个字段列,$2
表示文本行被分割后的第 2 个字段列,$3
表示文本行被分割后的第 3 个字段列,$n 表示文本行被分割后的第 n 个字段列$NF
就表示最后一个字段,$(NF-1)
就表示倒数第二个字段比如我们有这么一个文本文件 fruit.txt 内容如下,用它来演示如何使用 awk 命令工具
peach 100 Mar 1997 China
Lemon 150 Jan 1986 America
Pear 240 Mar 1990 Janpan
avocado 120 Feb 2008 china
awk '{print $0}' fruit.txt # 表示打印输出文件的每一整行的内容
awk '{print $1}' fruit.txt # 表示打印输出文件的每一行的第 1 列内容
awk '{print $1, $2}' fruit.txt
文件的每一行的每一列的内容除了可以用 print 命令打印输出以外,还可以对其进行赋值
awk '{$2 = "***"; print $0}' fruit.txt
上面的例子就是表示通过对 $2
变量进行重新赋值,来隐藏每一行的第 2 列内容,并且用星号 *
来代替其输出
在参数列表中加入一些字符串或者转义字符之类的东东
awk '{print $1 "\t" $2 "\t" $3}' fruit.txt
像上面这样,你可以在 print 的参数列表中加入一些字符串或者转义字符之类的东东,让输出的内容格式更漂亮,但一定要记住要使用双引号。
awk 内置 NR 变量表示每一行的行号
awk '{print NR "\t" $0}' fruit.txt
1 peach 100 Mar 1997 China
2 Lemon 150 Jan 1986 America
3 Pear 240 Mar 1990 Janpan
4 avocado 120 Feb 2008 china
awk 内置 NF 变量表示每一行的列数
awk '{print NF "\t" $0}' fruit.txt
5 peach 100 Mar 1997 China
5 Lemon 150 Jan 1986 America
5 Pear 240 Mar 1990 Janpan
5 avocado 120 Feb 2008 china
awk 中 $NF 变量的使用
awk '{print $NF}' fruit.txt
上面这个 $NF
就表示每一行的最后一列,因为 NF 表示一行的总列数,在这个文件里表示有 5 列,然后在其前面加上 $
符号,就变成了 $5
,表示第 5 列
awk '{print $(NF - 1)}' fruit.txt
1997
1986
1990
2008
上面 $(NF-1) 表示倒数第 2 列, $(NF-2) 表示倒数第 3 列,依次类推。
awk 'NR % 6' # 打印出了 6 倍数行之外的其他行
awk 'NR > 5' # 打印第 5 行之后内容,类似 `tail -n +6` 或者 `sed '1,5d'`
awk 'NF >= 6' # 打印大于等于 6 列的行
awk '/foo/ && /bar/' # 打印匹配 `/foo/` 和 `/bar/` 的行
awk '/foo/ && !/bar/' # 打印包含 `/foo/` 不包含 `/bar/` 的行
awk '/foo/ || /bar/' # 或
awk '/foo/,/bar/' # 打印从匹配 `/foo/` 开始的行到 `/bar/` 的行,包含这两行
awk 还提供了一些内置函数,比如:
toupper()
用于将字符转为大写tolower()
将字符转为小写length()
长度substr()
子字符串sin()
正弦cos()
余弦sqrt()
平方根rand()
随机数更多的方法可以参考:man awk
awk '{print FILENAME "\t" $0}' demo1.txt demo2.txt
当你使用 awk 同时处理多个文件的时候,它会将多个文件合并处理,变量 FILENAME 就表示当前文本行所在的文件名称。
在脚本代码段前面使用 BEGIN 关键字时,它会在开始读取一个文件之前,运行一次 BEGIN 关键字后面的脚本代码段, BEGIN 后面的脚本代码段只会执行一次,执行完之后 awk 程序就会退出
awk 'BEGIN {print "Start read file"}' /etc/passwd
awk 脚本中可以用多个花括号来执行多个脚本代码,就像下面这样
awk 'BEGIN {print "Start read file"} {print $0}' /etc/passwd
awk 的 END 指令和 BEGIN 恰好相反,在 awk 读取并且处理完文件的所有内容行之后,才会执行 END 后面的脚本代码段
awk 'END {print "End file"}' /etc/passwd
awk 'BEGIN {print "Start read file"} {print $0} END {print "End file"}' /etc/passwd
可以在 awk 脚本中声明和使用变量
awk '{msg="hello world"; print msg}' /etc/passwd
awk 声明的变量可以在任何多个花括号脚本中使用
awk 'BEGIN {msg="hello world"} {print msg}' /etc/passwd
在 awk 中使用数学运算,在 awk 中,像其他编程语言一样,它也支持一些基本的数学运算操作
awk '{a = 12; b = 24; print a + b}' company.txt
上面这段脚本表示,先声明两个变量 a = 12 和 b = 24,然后用 print 打印出 a 加上 b 的结果。
请记住 awk 是针对文件的每一行来执行一次单引号 里面的脚本代码,每读取到一行就会执行一次,文件里面有多少行就会执行多少次,但 BEGIN 和 END 关键字后脚本代码除外,如果被处理的文件中什么都没有,那 awk 就一次都不会执行。
awk 还支持其他的数学运算符
+ 加法运算符
- 减法运算符
* 乘法运算符
/ 除法运算符
% 取余运算符
在 awk 中使用条件判断
比如有一个文件 company.txt 内容如下
yahoo 100 4500
google 150 7500
apple 180 8000
twitter 120 5000
如果要判断文件的第 3 列数据,也就是平均工资小于 5500 的公司,然后将其打印输出
awk '$3 < 5500 {print $0}' company.txt
上面的命令结果就是平均工资小于 5500 的公司名单,$3 < 5500
表示当第 3 列字段的内容小于 5500 的时候才会执行后面的 {print $0} 代码块
awk '$1 == "yahoo" {print $0}' company.txt
awk 还有一些其他的条件操作符如下
< 小于
<= 小于或等于
== 等于
!= 不等于
> 大于
>= 大于或等于
~ 匹配正则表达式
!~ 不匹配正则表达式
使用 if 指令判断来实现上面同样的效果
awk '{if ($3 < 5500) print $0}' company.txt
上面表示如果第 3 列字段小于 5500 的时候就会执行后面的 print $0
比如现在我们有这么一个文件 poetry.txt 内容如下:
This above all: to thine self be true
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival
使用正则表达式匹配字符串 “There” ,将包含这个字符串的行打印并输出
awk '/There/{print $0}' poetry.txt
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
使用正则表达式配一个包含字母 t 和字母 e ,并且 t 和 e 中间只能有任意单个字符的行
awk '/t.e/{print $0}' poetry.txt
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival
如果只想匹配单纯的字符串 “t.e”, 那正则表达式就是这样的 /t.e/ ,用反斜杠来转义 . 符号 因为 . 在正则表达式里面表示任意单个字符。
使用正则表达式来匹配所有以 “The” 字符串开头的行
awk '/^The/{print $0}' poetry.txt
在正则表达式中 ^ 表示以某某字符或者字符串开头。
使用正则表达式来匹配所有以 “true” 字符串结尾的行
awk '/true$/{print $0}' poetry.txt
在正则表达式中 $ 表示以某某字符或者字符串结尾。
awk '/m[a]t/{print $0}' poetry.txt
No matter how dark long, may eventually in the day arrival
上面这个正则表达式 /m[a]t/
表示匹配包含字符 m ,然后接着后面包含中间方括号中表示的单个字符 a ,最后包含字符 t 的行,输出结果中只有单词 “matter” 符合这个正则表达式的匹配。因为正则表达式 [a] 方括号中表示匹配里面的任意单个字符。
继续上面的一个新例子如下
awk '/^Th[ie]/{print $0}' poetry.txt
这个例子中的正则表达式 /^Th[ie]/ 表示匹配以字符串 “Thi” 或者 “The” 开头的行,正则表达式方括号中表示匹配其中的任意单个字符。
再继续上面的新的用法
awk '/s[a-z]/{print $0}' poetry.txt
正则表达式 /s[a-z]/
表示匹配包含字符 s 然后后面跟着任意 a 到 z 之间的单个字符的字符串,比如 “se”, “so”, “sp” 等等。
正则表达式 [] 方括号中还有一些其他用法比如下面这些
[a-zA-Z] 表示匹配小写的 a 到 z 之间的单个字符,或者大写的 A 到 Z 之间的单个字符
[^a-z] 符号 `^` 在方括号里面表示取反,也就是非的意思,表示匹配任何非 a 到 z 之间的单个字符
正则表达式中的星号 *
和加号 +
的使用方法,*
表示匹配星号前字符串 0 次或者多次,+
和星号原理差不多,只是加号表示任意 1 个或者 1 个以上,也就是必须至少要出现一次。
正则表达式问号 ? 的使用方法,正则中的问号 ?
表示它前面的字符只能出现 0 次 或者 1 次。
正则表达式中的 {} 花括号用法,花括号 {} 表示规定它前面的字符必须出现的次数,像这个 /go{2}d/ 就表示只匹配字符串 “good”,也就是中间的字母 “o” 必须要出现 2 次。
正则表达式中的花括号还有一些其他的用法如下
/go{2,10}d/ 表示字母 "o" 只能可以出现 2 次,3 次,4 次,5 次,6 次 ... 一直到 10 次
/go{2,}d/ 表示字母 "o" 必须至少出现 2 次或着 2 次以上
正则表达式中的圆括号表示将多个字符当成一个完整的对象来看待。比如 /th(in){1}king/ 就表示其中字符串 “in” 必须出现 1 次。而如果不加圆括号就变成了 /thin{1}king/ 这个就表示其中字符 “n” 必须出现 1 次。
接上面正则使用,比如文件中有行数据
/abc/def/123 456
/abc/def/222 456
想要移除 123,保留之前的字母和后面的数字,则可以使用
awk 'sub(/[0-9]+/,"",$1)' /path/to/file
使用 awk 过滤 history 输出,找到最常用的命令
history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head
过滤文件中重复行
awk '!x[$0]++' <file>
将一行长度超过 72 字符的行打印
awk 'length>72' file
查看最近哪些用户使用系统
last | grep -v "^$" | awk '{ print $1 }' | sort -nr | uniq -c
假设有一个文本,每一行都是一个 int 数值,想要计算这个文件每一行的和,可以使用
awk '{s+=$1} ENG {printf "%.0f", s}' /path/to/file
斐讯投资送硬件这路子是着魔了,所以在斐讯 K2P 之后又入手了这个 T1 的盒子,配置 2G RAM,16G Sdcard,配置还不错。接口有 USB,网口,HDMI 口,还有 一个 AV 输出口,日常使用是没有任何问题的,初尝试一下非常流畅,不过让我不爽的是,第一次进入竟然需要验证手机,说是 CIBN 盒子的验证,如果不注册还不让进入。
CPU:Amlogic S912 八核 Cortex-A53 CPU up to 2.0GHz GPU:ARM Mali-T820MP3 GPU up to 750MHz
2G RAM+16G ROM,双频 WIFI,且支持 ac,但只是单天线
首先,进入盒子的设置 – 高级 – 远程调试打开,然后下载 adb, 开发过 Android 的人,或者大概熟悉 Android 的人一定用过这个命令,我之前也写过文章,总结 adb 常用的命令。
将盒子通电,将盒子联网(可以使用网线,也可以使用无线网络),只要能够在局域网中有一个 IP 地址,然后进路由器里记录,找到盒子的 IP(192.168.x.x)地址。
adb connect 192.168.x.x (盒子 IP)
adb shell
pm list packages # 可以用来查看安装的应用列表
pm uninstall -k --user 0 com.feixun.dangbeimarket #自带 cibn 当贝市场
pm uninstall -k --user 0 com.tianci.ad #广告
pm uninstall -k --user 0 com.feixun.qiyiguo #自带爱奇艺
pm uninstall -k --user 0 cn.cibn.health #健康中国
pm uninstall -k --user 0 com.pptv.tvsports.preinstall #CIBN 体育
pm uninstall -k --user 0 com.phicomm.phiweather #天气
pm uninstall -k --user 0 com.pplive.atv #聚精彩
pm uninstall -k --user 0 cn.cibntv.ott #CIBN 高清影视
pm uninstall -k --user 0 com.phicomm.update #斐讯 OTA 升级
pm uninstall -k --user 0 com.phicomm.datatracker #斐讯数据收集
如果想要恢复最原始的状态,在设置选项里,恢复出厂设置,还是最原来的状态。
如果路由器支持过滤广告,可以添加如下两条规则:
asimgs.cp61.ott.cibntv.net
hoisin.coocaatv.com
简单的方法,只通过遥控器操作,先用系统自带的当贝市场,找到“视频加速器”这个应用,安装然后运行,就会在视频加速器运行界面的下方,看到有“当贝市场”,再安装这个当贝市场。
如果你有需要安装本地文件的 APK 安装包,有两种方式,推荐使用 adb 方式安装,先将想要安装的 apk,下载到本地文件,然后 adb 连接盒子
adb connect <ip>
adb install /path/to/app.apk # 安装本地路径下的应用
直接将本地的应用安装到盒子。
如果安装不成功,记得禁用安装验证,在 adb 连接的情况下
settings put global package_verifier_enable 0
或者用“悟空遥控器”这个应用,当贝市场里可以搜索它来安装,但也要在手机上下载安装“悟空遥控器”APP,里面有个“本地 APK 推送”,用它就可以安装你想要装的应用了,只要有 APK 安装包。
最后要强烈推荐一个投屏 App,乐播投屏,因为斐讯 T1 并没有 DLNA 的功能,但幸而只要一个 App 就能够解决,这样就能够将手机爱奇艺上的内容快速的投屏到盒子上。
恩山最近的帖子更新了 T1 su 的密码,记录一下
31183118
最后这篇文章 是我使用过并且觉得不错的 Android TV 的应用,可以参考。
在 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();