0%

基本操作

qutochar, delimiter 使用详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 写 csv 文件
# newline='' 可以在读写时移除空白行
import csv
with open('eggs.csv', 'w', newline='') as csvfile:
spamwriter = csv.writer(csvfile)
spamwriter.writerow(['Spam'] * 5 + ['Baked Beans'])
spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])

# cat eggs.csv
# Spam,Spam,Spam,Spam,Spam,Baked Beans
# Spam,Lovely Spam,Wonderful Spam

# 一次性写多行
header = ['name', 'area', 'country_code2', 'country_code3']
data = [
['Albania', 28748, 'AL', 'ALB'],
['Angola', 1246700, 'AO', 'AGO']
]

with open('countries.csv', 'w', encoding='UTF8', newline='') as f:
writer = csv.writer(f)
# write the header
writer.writerow(header)
# write multiple rows
writer.writerows(data)

# 如果数据以 dict 的格式出现,可以使用 DictWriter 简化操作
fieldnames = ['name', 'area', 'country_code2', 'country_code3']
# csv data
rows = [
{'name': 'Algeria',
'area': 2381741,
'country_code2': 'DZ',
'country_code3': 'DZA'},
{'name': 'American Samoa',
'area': 199,
'country_code2': 'AS',
'country_code3': 'ASM'}
]

with open('countries.csv', 'w', encoding='UTF8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)

# 读 csv 文件
import csv
with open('some.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked Beans']
# ['Spam', 'Lovely Spam', 'Wonderful Spam']

实操

有一个 csv 文件,其中有个 column 名为 ‘_raw’ 包含我们需要的信息,写一段脚本解析之

1
2
3
4
5
_raw 中文本为

08:42:50,222 INFO [RESTCallbackSubscriber] [customerId,customerId,null,null,SFAPI,null,null] [IrisSubscriber Container[queue_seb.subscriber.pillar.deactivateuser]1]Postback for event com.company.hermes.core.SFEvent={meta:Meta={priority:0,proxyId:"null",serverName:"null",topic:"com.company.platform.mobile.deactivateuser",ptpName:null,companyId:"customerId",eventId:"a3b43584-3ceb-4760-9c01-699d635f4461",type:"null",sourceArea:"null",effectiveStartDate:"null",publishedAt:"2020-05-31 08:42:39",publishBy:"SFAPI",publishServer:"serverip",externalAllowed:false,filterParameters:{{companyId=customerId, userId=SFAPI, type=null, sourceArea=null, effectiveStartDate=null, publishedAt=1590914553205, publishedBy=SFAPI, externalAllowed=false, publishServer=serverip, priority=0, proxyId=null, serverName=null, topic=com.company.platform.mobile.deactivateuser, ptpName=null}}},body:{"companyId": "customerId", "inactiveUserId": ["E_UUU_21934","E_UUU_21935"]}} sent to https://domain/api/deactivate, (HTTP/1.1 200 OK)

提取目标:publishedAt, publishedAt of filterParameters, inactiveUserId
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import csv
# 拿到 csv 的 _raw 列数据
context = []
rows = []
with open('dump_csv.csv', newline='') as csvfile:
contexts = csv.reader(csvfile)
# 使用 reader = csv.DictReader(csvfile) 的话可以使用 column name 取值
# 例如: reader['companyId'], 不过缺点是要在 with loop 中处理完数据
rows = [row[16] for row in contexts]
rows = [1:]

# 分析 _raw 数据特性,决定使用正则匹配数据
# publishedAt:"(.*?)" 加 ? 表示 非贪婪
# publishedAt=(\d+)
# inactiveUserId": (\[.*?\])
# 以上表达式取 group 1 数据

import re
from datetime import datetime
# re.findAll
# re.match() 从开头开始匹配
# re.search(reg, src) 匹配任意位置

reg1 = 'publishedAt:"(.*?)"'
reg2 = 'publishedAt=(\d+)'
reg3 = 'inactiveUserId": (\[.*?\])'

rowlist = []
for row in rows:
infolist = []
timestr01 = re.search(reg1, row).group(1)
d1 = datetime.strptime(timestr01, '%Y-%m-%d %H:%M:%S')
infolist.append(d1)

timestr2 = int(re.search(reg2, row).group(1))
d2 = datetime.fromtimestamp(timestr2/1000.0)
infolist.append(d2)

users = re.search(reg3, row).group(1)
ulist = eval(users) # string 转化为 list
infolist.append(ulist)
rowlist.append(infolist)

# 把数据根据时间先后排序
sortedList = sorted(rowlist, key=lambda sub: sub[1])

def printList(line):
formatStr01 = '%y-%m-%d %H:%M:%S'
print(line[0].strftime(formatStr01), end=' | ')
print("%15f" % (line[1].timestamp()), end=' | ')
arrStr = str(line[2][:5]) + "..." + str(len(line[2])) if len(line[2]) > 5 else str(line[2])
print(arrStr)

for sub in sortedList:
printList(sub)

Issues

2021-06-09 python+csv & shell 出问题了

Scenraio: 自动化脚本实现批量创建 Jira ticket

Issue desc: python + csv lib 组织一个数据源文件,之后使用 shell 读取,但是数据读取后,format 出问题了,会在末尾包含一个换行

Reproduce:

1
2
3
4
import csv
with open('eggs.csv', 'w', newline='') as csvfile:
spamwriter = csv.writer(csvfile)
spamwriter.writerow(['Spam', 'Baked Beans'])

使用 cat 或者 sed 查看,可以看到末尾包含一个 \r 换行符号

1
2
3
4
cat -v eggs.csv 
# Spam,Baked Beans^M
sed -n 'l' eggs.csv
# Spam,Baked Beans\r$

在 sh 脚本中我会解析这个 csv 文件并使用解析得到的内容作为后续操作的输入.

重现的脚本中,我们拿到解析的 csv 内容并打印出来。打印内容分别加了前后坠便于观察。可以看到第二个 echo 的后缀打印会出问题

1
2
3
4
5
6
7
8
9
10
11
12
13
cat reproduce.sh 
#!/usr/local/bin/bash
while IFS=',' read -r col1 col2
do
echo --$col1---
echo --$col2---
done < eggs.csv
echo "Finish reproduce script..."

./reproduce.sh
# --Spam---
# ---aked Beans
# Finish reproduce script...

echo --$col2--- 换成 echo "--$col2---" | sed -n 'l' 之后再次运行 reproduce.sh 输出如下

1
2
3
4
./reproduce.sh
# --Spam---
# --Baked Beans\r---$
# Finish reproduce script...

回头细想了一下 read -r 是不会读取结尾没有换行的行的,在这个例子中 \r--- 已经是下一行了,而且没有换行,自动跳过了(; ̄ェ ̄)

Solution: 我的解决方案是,不用 python 的 csv write 方法,直接把解析的过程用基础的 file.write() 完成,一行写完自己写 \n 做换行即可

对 python 中涉及到时间的类库做一个大致的了解并收集一些常用的 sample。类库包括:time, date, datetime, timezone 等

datetime 日期时间

时间类型分为感知型和简单型,感知型包含 timezone 信息,简单型则没有这种意义。

  • date 都是简单型的
  • time 和 datetime 可以是简单型也可以是感知型,通过 d.tzinfo 不等于 None 或者 d.tzinfo.utcoffset(d) 部位 None 来确定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from datetime import datetime

# 获取当前时间
datetime.now()
# Out[18]: datetime.datetime(2020, 6, 18, 17, 2, 48, 14847)

# 感知型 now
from datetime import timezone
dt =datetime.now(timezone.utc)

# datetime 得到 s
dt.timestamp()
# Out[41]: 1592472504.59345

# s 转 datetime, ms 的话把时间除1000.0即可 1592472504.59345/1000.0
d = datetime.fromtimestamp(1592472504.59345)
# Out[43]: datetime.datetime(2020, 6, 18, 17, 28, 24, 593450)

date, time, datetime 都支持 strftime(), 只有 datetime 支持 strptime()。

1
2
3
4
5
6
7
8
# strftime: string from time, 即格式化输出时间, 对象方法
now = datetime.now()
now.strftime('[%y%m%d]-[%H:%M:%S]')
# Out[22]: '[200618]-[17:12:46]'

# strptime: string parse to time, 即将字符串转化为时间, 类方法
dt = datetime.strptime('[200618]-[17:12:46]', '[%y%m%d]-[%H:%M:%S]')
# Out[24]: datetime.datetime(2020, 6, 18, 17, 12, 46)

deltatime 时间间隔

1
2
3
4
5
6
7
from datetime import timedelta
delta = timedelta(days=50, seconds=27, microseconds=10, milliseconds=29000, minutes=5, hours=8, weeks=2)
# Out[16]: datetime.timedelta(days=64, seconds=29156, microseconds=10)

# 可以通过 datetime 做计算得到
now - dt
# Out[26]: datetime.timedelta(seconds=329, microseconds=894908)

搜索引擎使用小技巧

  1. - + 关键词:排除不想看到的关键词
  2. “关键词加”:锁定关键词
  3. site:域名 + 关键词, 在指定网站内搜索
  4. filetype:文件格式 + 关键词
  5. intitile: + 关键词,指定标题搜素
  6. intext/allintext: + 关键词,两种指定范围搜索
  7. 以上技巧叠加使用

dump Vs dumps

这两个函数都可以用来做序列化,唯一的区别是 dump 需要指定一个 io,比如打开的文件作为输出的地方,而 dumps 默认是以 stdout 做为输出端的,也就是打印在终端

1
2
3
4
5
6
7
8
import json
a = {'name': 'jack'}
json.dump(a)
# Out[6]: '{"name": "jack"}'

with open('data.json', 'w') as file:
json.dump([a, a], file)
# 当前目录下会生产名为 data.json 的文件,内容为 [{"name": "jack"}, {"name": "jack"}]

load Vs loads

有了前面的基础,理解 load 和 loads 也是一个套路,一个直接从你指定的 string 加载,一个从你指定的文件加载

1
2
3
4
5
6
7
8
ret = json.loads('{"name": "jack"}')
ret, type(ret)
# Out[11]: ({'name': 'jack'}, dict)

with open('data.json') as file:
ret = json.load(file)
print(ret)
# [{'name': 'jack'}, {'name': 'jack'}]

支持中文

写入文件是指定 encoding 和 ensure_ascii 参数,读取时指定 encoding 就可以了

1
2
3
4
5
6
7
8
me = {'name': '我'}
with open('dump3.json', 'w', encoding='utf-8') as file:
json.dump(me, file, ensure_ascii=False)

with open('dump3.json', encoding='utf-8') as file:
ret = json.load(file)
print(ret)
# {'name': '我'}

序列化 Object

序列化对象时可以在 dump(s) 的方法中指定一个自己的序列化规则类, 一种是通过 cls 参数,一种是通过 default 参数。不过有一个需要注意的点是,使用时并不指代整个对象的序列化逻辑,而是对那些不知道怎么序列化的部分给出逻辑,这块挺绕的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 该例子中,Person 是自定义的类,所以调用 dumps 时,如果直接传入,会抛 exception: TypeError: Object of type Person is not JSON serializable
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

# 可以通过指定 default 参数,给出转化规则

def PersonConvert(person):
if isinstance(person, Person):
return person.__dict__
else:
raise TypeError

p = Person('jack',30)
json.dumps(p, default=PersonConvert)
# Out[28]: '{"name": "jack", "age": 30}'

# 也可以通过指定 cls 参数,给出转化规则
class PersonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Person):
return obj.__dict__
else:
return json.JSONEncoder.default(self, obj)

json.dumps(p, cls=PersonEncoder)
# Out[30]: '{"name": "jack", "age": 30}'

# 如果此时我们对 Person 做一下升级,添加一个 datetime 属性
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
self.create_date = datetime.now()

# 那么之前的函数就不够用了,我们除了要处理 Person 的逻辑,还要处理 datetime 的逻辑
def PersonConvertV2(obj):
if isinstance(obj, Person):
return obj.__dict__
elif isinstance(obj, datetime):
return obj.timestamp()
else:
raise TypeError

p2 = Person('Tom', 31)
json.dumps(p2, default=PersonConvertV2)
# Out[46]: '{"name": "Tom", "age": 31, "create_date": 1592802400.657711}'

# 网上有给出比较多经典的转化方式,在转化过程中会携带 class, module 的信息,为反序列化做准备
def obj_to_dict(obj):
if isinstance(obj, Person):
d = {}
d['__class__'] = obj.__class__.__name__
d['__module__'] = obj.__module__
d.update(obj.__dict__)
return d
elif isinstance(obj, datetime):
return obj.timestamp()
else:
raise TypeError

json.dumps(p2, default=obj_to_dict)
# Out[54]: '{"__class__": "Person", "__module__": "__main__", "name": "Tom", "age": 31, "create_date": 1592802400.657711}'

理解了 encode 的逻辑,decode 也差不多。不过逻辑稍微有点区别,他是在遇到 dict 的时候去做判断的。而且从他的输出看,应该是由内而外的进行解析的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 def dict_to_obj(d):
if "level01" in d:
print("l1: %s" % d)
return d
elif "level02" in d:
print("l2: %s" %d)
return d
else:
raise TypeError

json.loads(jstr, object_hook=dict_to_obj)
# l2: {'level02': 'true', 'age': 30}
# l1: {'level01': 'true', 'name': 'jack', 'info': {'level02': 'true', 'age': 30}}
# Out[13]: {'level01': 'true', 'name': 'jack', 'info': {'level02': 'true', 'age': 30}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

def dict_to_obj(our_dict):
"""
Function that takes in a dict and returns a custom object associated with the dict.
This function makes use of the "__module__" and "__class__" metadata in the dictionary
to know which object type to create.
"""
if "__class__" in our_dict:
# Pop ensures we remove metadata from the dict to leave only the instance arguments
class_name = our_dict.pop("__class__")
# Get the module name from the dict and import it
module_name = our_dict.pop("__module__")
# We use the built in __import__ function since the module name is not yet known at runtime
module = __import__(module_name)
# Get the class from the module
class_ = getattr(module,class_name)
# Use dictionary unpacking to initialize the object
obj = class_.__new__(class_)
for key, value in our_dict.items():
if key == 'create_date':
value = datetime.fromtimestamp(value)
setattr(obj, key, value)
else:
obj = our_dict
return obj

jstr = '{"__class__": "Person", "__module__": "__main__", "name": "Jack", "age": 30, "create_date": 1592805275.55762}'
jstr = '{"name":"jack", "info":{"level02": "true", "age":30}}'
o = json.loads(jstr, object_hook=dict_to_obj)
print(o.create_date)
print(type(o.create_date))
# 2020-06-22 13:54:35.557620
# <class 'datetime.datetime'>

其他的一些收获

  • 在 class 的方法中可以有一个 toJSON 的方法快速得到序列化的字符串
  • 在 class 的构造函数里可以有一个 dict 参数用来快速构造对象
1
2
3
4
5
6
7
8
9
10
11
12
class Person04:
def __init__(self, name='', age=-1, pairs=None):
self.name = name
self.age = age
if pairs:
self.__dict__ = pairs

def toJSON(self):
return json.dumps(self,
default=lambda o: o.__dict__,
sort_keys=True,
indent=4)

VSCode setup python 独立运行环境

安装依赖

  1. 安装 pipenv pip install pipenv --user
  2. 创建独立环境 pipenv shell, 还可以通过 pipenv --three/two 指定 python 版本
  3. 修改 pipfile, 使用国内源加速
  4. 安装依赖 pipenv install pdfminer.six
1
2
3
4
[[source]]
name = "pypi"
url = "https://pypi.douban.com/simple"
verify_ssl = true

查看 VSCode 左下角的运行环境是不是你新建的那个,不是的话 pipenv --venv 查看新建 venv 路径, Ctrl + Shift + p 搜索 python: select interpreter 选择你新建的那个 env

reload module after update

如果某些方法正在进行中,可能频繁修改,在 ipython 中调试的时候可以用 reload 来重新加载,也可以指定 ipython 到自动重加载模式 autoreload mode

1
2
import importlib
importlib.reload(PDFParser)

  • ==is 的区别
  • 怎么使得 Object 使用 == 比较相等
  • Set 集合中判断相等

== Vs is

== 用来判断值相等,is 用来判断引用相等

1
2
3
4
5
6
7
8
9
a = [1, 2, 3]
b = a

a == b # true
a is b # true

b = a[:]
a == b # true
a is b # false

怎么使得 Object 使用 == 比较相等

你需要重写 class 的 eq 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self, id, name):
self.id = id
self.name = name

def __eq__(self, other):
if not isinstance(other, Person):
# don't attempt to compare against unrelated types
return NotImplemented
return self.id == other.id

p1 = Person(1, 'a')
p2 = Person(2, 'b')
p3 = Person(1, 'c')

p1 == p2 # false
p1 == p3 # true

Note: 重写 eq 将会使对象变为 unhashable,在存到 Set, Map 等集合中会有影响,你可以重写 hash 来定制

Set 集合中判断相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person:
def __init__(self, id, name):
self.id = id
self.name = name

def __eq__(self, other):
if not isinstance(other, Person):
# don't attempt to compare against unrelated types
return NotImplemented
return self.id == other.id

def __hash__(self):
# necessary for instances to behave sanely in dicts and sets.
return hash(self.id)

set([p1, p2, p3]) # only p1, p2 will be stored

set 中并没有使用新的 object 代替旧的的方法,所以如果想要更新的话只能 remove + add 了

requests lib SSLError

在使用 requests 发送 API 请求的时候,如果网站是 https 的,如果你没有对应的证书就会抛 SSLError, 示例如下:

1
2
3
4
5
6
7
8
9
10
11
headers  = {'Authorization' : 'token xxx'}
url = 'https://github.domain.com/api/v3/users/ixxx'
resp = requests.get(url, headers=headers)

''' Error show as:
SSLError: HTTPSConnectionPool(host='github.wdf.sap.corp', port=443): Max retries exceeded with url: /api/v3/users/i332399 (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)')))

In [7]: resp = requests.get(url, headers=headers, verify=False)
/Users/i306454/gitStore/mycommands/.venv/lib/python3.7/site-packages/urllib3/connectionpool.py:851: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecureRequestWarning)
'''

解决方案有两个

  1. 跳过verify
  2. 指定证书

方案一

requests.get(url, auth=(), verify=False)
但是,这种方式会在发完request之后抛warning,对于强迫症患者说简直不能忍。

方案二

在request中指定证书路径 requests.get(url, auth=auth, verify='/Users/jack/Downloads/my.crt')

在 Java 8 中,引入了 Optional class 给我们在处理无法返回任何值的情况下,有了第三种选择。

Optional 概览,基于 Java 1

他是一个 final 类, 方法列表如下

name 返回值 简述
empty() Optional 返回一个空的实例
filter(Predicate<? super T> predicate) Optional 过滤
flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) Optional 扁平化操作
get() T 取值
ifPresent(Consumer<? super T> action) void 如果值存在,执行给定的操作
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) void 如果存在,执行给定操作,否则运行 empty-base action
isEmpty() boolean 是否为空
isPresent() boolean 是否有值
map(Function<? super T, ? extends U> mapper) Optional 对每个元素操作
of(T value) Optional 生成对象
ofNullable(T value) Optional 生成 empty 或 有值的 optional 对象
or(Supplier<? extends Optional<? extends T>> supplier) Optional present 返回自己,否则返回 supplier 生成的对象
orElse(T other) T present 返回自己,否则返回 else 中指定的值
orElseGet(Supplier<? extends T> supplier) T present 返回自己,否则返回 else 中指定的 spplier 生成的对象
orElseThrow() T 存在值,返回,否则抛 NoSuchElementException
stream() Stream 产生流
toString() String /
1
2
3
4
5
6
Optional<String> op = Optional.of("test");
System.out.println(op.isEmpty()); // false
System.out.println(op.isPresent()); // true
Optional<String> op2 = Optional.empty();
op2.get(); // Exception in thread "main" java.util.NoSuchElementException
String ret = op2.orElse("backup"); // backup

or vs orElseGet: 返回值不同,前者返回 Optional 对象,后者返回的泛型指定的值

item 55

本节要点:

  • Optional 强制客户端对返回值做校验
  • 如果不能从 Optional 中 get 值,会抛 NoSuchElementException
  • 永远不要通过返回 Optional 的方法返回 null, 这违背了设计的本意
  • Optional 本质上与受检测异常相似
  • 容器类,比如 map, stream, 数组和 optional 都不应该装载在 optional 中,你可以返回空的容器,比如空的数组
  • 不要返回基本包装类型的 Option, 有其他的替代品比如 OptionalInt
  • Optional 不要作为map, set 中的键元素,数组也不行
  • Optional 相对而言还是比较消耗资源的,性能要求高的场景谨慎使用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty()) {
throw new IllegalArgumentException("Empty collection");
}

E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return result;
}

使用 Optional 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
if (c.isEmpty()) {
return Optional.empty();
}

E result = null;
for (E e : c) {
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}

使用 Stream 优化

1
2
3
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
return c.stream().max(Comparator.naturalOrder());
}

其他示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 如果没有返回备选
max(words).orElse("other words...");

// 如果没有,抛出异常
max(toys).orElseThrow(TmperTantrumException::new);

ph.parent().map(h -> String.valueIf(h.pid())).orElse("N/A");

// 过滤非空的 Optional 集合
List<Optional<String>> listOfOptionals = Arrays.asList(Optional.empty(), Optional.of("foo"), Optional.empty(), Optional.of("bar"));
// Java 8
List<String> filteredList = listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
// Java9 中可以简化为
List<String> filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());

别用 iPhone 来购买会员之类的虚拟产品!

iPhone 在购买虚拟产品,比如网易云音乐之类的会员时,要比安卓,或者PC直接购买要贵,比例还挺高 20-30% 之间,这是 IOS 平台的雁过拔毛策略。。。直接在费 IOS 平台就可以

概要:

  • Stream 不是数据结构,更像是算法的集合
  • 在流操作过程中不会修改元数据
  • 以 lambda 表达式为参数
  • 惰性
  • 免费提供并行计算能力
  • 元数据可以无限大
  • 类型确定时使用 IntStream 之类的 class 可以提高效率

API 简介

在 Java 8 的 API 中, Stream 内置了 39 个方法。

匹配,检测 source 中是否有符合条件的元素

name return 简述
allMatch(Predicate<? super T> predicate) boolean 全部匹配返回 true
anyMatch(Predicate<? super T> predicate) boolean 只要有一个匹配 true
noneMatch(Predicate<? super T> predicate) boolean 全部匹配返回 true
1
2
3
4
// 检测Stream 中是否有数能被 2 整除
boolean ret = Stream.of(1, 2, 3, 4, 5).anyMatch(x -> x % 2 == 0);
System.out.println(ret);
// output: true

用于产生流对象的方法

name return 简述
builder() static Stream.Builder 返回一个流的构造器
concat(Stream<? extends T> a, Stream<? extends T> b) static Stream 拼接多个流并一起操作
empty() static Stream 创建一个空的流对象
generate(Supplier s) static Stream 传入一个 Supplier 构造器,返回构造器指定的对象
iterate(T seed, UnaryOperator f) static Stream seed 为初始值,UnaryOperator 为算法
limit(long maxSize) Stream 配合其他生成方法指定生成个数
skip(long n) Stream 跳过几个元素,可以结合 iterate, generate 使用
of(T… values) static Stream 生成一个流
of(T t) static Stream /
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream.Builder<String> builder = Stream.builder();
Stream<String> stream = builder.add("Jerry").add("Tom").build();
stream.forEach(System.out::println);
// output: Jerry Tom

// concat sample, concat 中为需要拼接的流对象
Stream.concat(Stream.of("Jerry"), Stream.of("Tom")).forEach(System.out::println);

// 随机产生 3 个整形
Stream<Integer> ret = Stream.generate(new Random()::nextInt).limit(3);

// iterate sample, 0 作为初始值,每次返回值 +1, 返回 3 次
Stream.iterate(0, x -> x+1).limit(3).forEach(System.out::print);
// output: 012

常用的查找函数 max/min/distinct

name return 简述
distinct() Stream 去重
max(Comparator<? super T> comparator) Optional 查找最大值
min(Comparator<? super T> comparator) Optional 查找最小值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// distinct sample
Stream.of(1,2,3,1,2,3).distinct().forEach(System.out::print);
// output: 123

// 如果传入的时对象,那个会更具 equals, hashCode 来判断是不是重复
class Person {
private String name;
private int age;
// Constructor, getter and setter

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}

@Override
public int hashCode() {
return Objects.hash(age, name);
}

@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}

Stream.of(new Person("Jack", 30), new Person("Jack", 30), new Person("Jack", 20)).distinct().forEach(System.out::print);
// output: Person{name='Jack', age=30} Person{name='Jack', age=20}

生成指定类型的 Stream 对象

name return 简述
map(Function<? super T,? extends R> mapper) Stream 返回指定类型的 Stream
mapToDouble(ToDoubleFunction<? super T> mapper) DoubleStream 返回 Double 类型的 Stream
mapToInt(ToIntFunction<? super T> mapper) IntStream 返回 Int 类型的 Stream
mapToLong(ToLongFunction<? super T> mapper) LongStream 返回 Long 类型的 Stream
1
2
3
4
Stream.of("Jack", "Tom").map(x -> "Name: " + x).forEach(System.out::println);
// output: Name: Jack Name: Tom

// 其他几个类似,只不过把返回类型指定了

将流中的处理结果整合输出到集合中

name return 简述
collect(Collector<? super T,A,R> collector) <R,A> R /
collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) R /
1
2
3
4
5
// 将流中的值连接起来
Stream.of("Jack", "Tom").collect(Collectors.joining(", "));

// 将字符串组成 string, length 的键值对
Map<String, Integer> ret = Stream.of("Jack", "Tom").collect(Collectors.toMap(Function.identity(), String::length));

map 及类似的操作

name return 简述
map(Function<? super T,? extends R> mapper) Stream 对流中的元素逐个操作
mapToDouble(ToDoubleFunction<? super T> mapper) DoubleStream /
mapToInt(ToIntFunction<? super T> mapper) IntStream /
mapToLong(ToLongFunction<? super T> mapper) LongStream /
flatMap(Function<? super T,? extends Stream<? extends R>> mapper) Stream 和 map 主要的区别时扁平化
flatMapToDouble(Function<? super T,? extends DoubleStream> mapper) DoubleStream /
flatMapToInt(Function<? super T,? extends IntStream> mapper) IntStream /
flatMapToLong(Function<? super T,? extends LongStream> mapper) LongStream /
1
2
3
4
// 扁平化就是将集合中的集合拆散成基本元素,下例中将 list 中的最基本的元素做平方操作
List<List<Integer>> listOfList = Arrays.asList(Arrays.asList(1,2), Arrays.asList(3,4));
listOfList.stream().flatMap(Collection::stream).map(x -> x*x).forEach(System.out::println);
// output: 1, 4, 9 ,16

过滤

name return 简述
filter(Predicate<? super T> predicate) Stream 根据 predicate 过滤
reduce(BinaryOperator accumulator) Optional 从多个元素中产生一个结果
reduce(T identity, BinaryOperator accumulator) T identity - 初始值
reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator combiner) U combiner 是并行运算时需要指定的值
1
2
3
4
5
6
7
8
Optional<String> ret = Stream.of("Jack", "Tom").reduce(String::concat);
System.out.println(ret.get());
// output: JackTom

// 使用 identity 作为初始值
Optional<String> ret = Optional.ofNullable(Stream.of("Jack", "Tom").reduce("Name Ret:", String::concat));
System.out.println(ret.get());
// output: Name Ret:JackTom

Find*

name return 简述
findAny() Optional 随机返回一个值,并不关心值的内容,在单线程中一般返回第一个,但是不保证
findFirst() Optional 返回第一个
1
System.out.println(Stream.of(1,2,3,4).findFirst().get());

forEach*

name return 简述
forEach(Consumer<? super T> action) void 遍历不保证顺序(多线程下可能会顺序不定)
forEachOrdered(Consumer<? super T> action) void 遍历保证顺序

count

name return 简述
count() long 输出元素个数
peek(Consumer<? super T> action) Stream 得到流对象,可用于调试
sorted() Stream 使用自然排序
sorted(Comparator<? super T> comparator) Stream 定制排序
toArray() Object[] 生成数组
toArray(IntFunction<A[]> generator) A[] /
1
2
3
4
// steam 转化为 array
Stream<String> stringStream = Stream.of("a", "b", "c");
String[] stringArray = stringStream.toArray(String[]::new);
Arrays.stream(stringArray).forEach(System.out::println);

语法糖

创建实例或者调用方法时可以使用 :: 两个冒号的形式调用

Supplier 使用举例

1
2
3
4
5
6
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
Supplier<LocalDateTime> s = LocalDateTime::now;
System.out.println(s.get());

Supplier<String> s1 = () -> dtf.format(LocalDateTime.now());
System.out.println(s1.get());