如何快速找到想要的信息

我们在一个信息爆炸的时代,互联网给我们带来丰富信息的同时,也造成了很大的负担,如何在网上快速准确的找到想要的信息,是一个很重要的技巧。

常见的信息来源有:

  • 搜索引擎等通用搜索
  • stack over flow等专业领域知识
  • 知乎、搜狗(用于查询微信公众号的内容)等封闭社区

下面两类,主要在于平时的积累,这里重点说说搜索引擎:

搜索引擎

搜索引擎处于互联网的中心,连接了各种信息。使用时,可以从一个点出发,然后不断缩小范围,最终找到想要的信息。

以查询“天龙八部”的出处背景为例:
直接输入“天龙八部”会是这样:

如果知道“天龙八部”与佛教相关,可以输入“天龙八部 佛教”:

如果不知道与佛教相关,则可以先了解它的背景:

常见的拓展信息

  • 本身的描述,如apple可能是水果,也可能是科技公司
  • 背景信息,如历史时间段、行业等
  • 相关信息,如一个人的学校、籍贯等

搜索引擎的功能

google支持以下功能的搜索:

  • 查询多个关键词,以空格分开
  • 查询短语,用英文的引号(“”)分开
  • 查询任意一个词,用 OR 分开
  • 排除某个词,用 - 分开
  • 数字范围,用 .. 表示

同时,还可以对搜索的结果做一些限制:

  • 时间范围
  • 特定网站
  • 在页面的特定位置,如title、页眉、正文等,甚至是url、链接
  • 资源的类型,如pdf、ppt、word等

其他技巧:

  • 通配符 “*”
  • 搜索缓存
    在相应网址前加上“cache:”

ubuntu下打印服务器的安装配置与使用教程

为了假装学习,在某宝上剁了一台HP打印机,用了一阵子后发现个,每次打印都要开台式机(USB打印机连在台式机上),很麻烦,如果能把打印机连在NAS服务器上,就好多了。事不宜迟,马上开搞,以下是搞机的记录:

准备

  • 一台nas服务器
  • 一台hp打印机

在nas上安装打印机驱动

ubuntu上打印机的驱动安装,与具体的型号和品牌有关,hp的是通过hplip工具来实现。具体安装很简单,一路确认就可以。不确定的简单搜索就可以了,以下是参考:

hplip地址: https://developers.hp.com/hp-linux-imaging-and-printing/gethplip

安装详细过程: http://blog.csdn.net/ws_20100/article/details/49120509

安装好后本地打印一张纸试试,看看是否正常。

配置打印服务器

ubnntu下的打印服务器可以通过cups来实现。
具体的安装和配置:

  1. 安装cups

    1
    sudo apt-get install cups cups-client
  2. 备份cups配置文件

    1
    sudo cp /etc/cups/cupsd.conf /etc/cups/cupsd.conf.bak
  3. 用以下内容代替/etc/cups/cupsd.conf:

    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
    # /etc/cups/cupsd.conf
    # Simple CUPS configuration file for a print server
    # which serves printers within a private local area network.
    # - There is no need for additional security within the print server, ie only authorises people can access the machine.
    # This setup also allows access to the CUPS "Administrative tasks" system
    # via your web browser to http://localhost:631
    # File based on Ubuntu 5.10 (Breezy Badger) (Linux version 2.6.12-10-386)
    # Server Directives are explained in http://localhost:631/sam.html
    # 25/04/2006
    # DavidTangye@netscape.net
    ConfigFilePerm 0600
    LogLevel info
    Printcap /var/run/cups/printcap
    RunAsUser Yes
    Port 631
    Include cupsd-browsing.conf
    BrowseAddress @LOCAL
    BrowseAddress 10.0.0.0/8
    BrowseAddress 172.16.0.0/12
    BrowseAddress 192.168.0.0/16
    <Location />
    AuthType None
    Order Deny,Allow
    Deny From All
    Allow From @LOCAL
    Allow From 10.0.0.0/8
    Allow From 172.16.0.0/12
    Allow From 192.168.0.0/16
    </Location>
    <Location /jobs>
    AuthType None
    Order Deny,Allow
    Deny From All
    Allow From @LOCAL
    Allow From 10.0.0.0/8
    Allow From 172.16.0.0/12
    Allow From 192.168.0.0/16
    </Location>
    <Location /printers>
    AuthType None
    Order Deny,Allow
    Deny From All
    Allow From @LOCAL
    Allow From 10.0.0.0/8
    Allow From 172.16.0.0/12
    Allow From 192.168.0.0/16
    </Location>
    <Location /admin>
    AuthType None
    Order Deny,Allow
    Deny From All
    Allow From @LOCAL
    Allow From 10.0.0.0/8
    Allow From 172.16.0.0/12
    Allow From 192.168.0.0/16
    </Location>
  4. 重启cups

    1
    sudo service cups restart

参考资料:
如何在Ubuntu上使用网络打印-ubuntu-wiki
CUPS-wiki (简体中文)#mw-head)
如何在Ubuntu服务器上设置Web配置的打印服务器使用SWAT,CUPS和SAMBA

配置windows客户端

http://localhost:631/admin页面中找到Server Settings,选择”Share printers connected to this system”及其子项”Allow printing from the Internet”,点击”Change Setting”按钮保存设置。
进入http://localhost:631/printers/页面点击自己打印机的名字,复制跳转到的页面的URL,即打印机的地址。然后,就可以在Windows上添加使用Ubuntu共享的打印机了。
(来自 http://www.qingpingshan.com/pc/fwq/324056.html)

配置教程
win7
win10
mac

NAS服务器的搭建-软件篇

顺利装好机器,是时候试试它的威力了,开始折腾软件.

NAS软件功能

  • 外网访问。这么牛逼的东东,光在家用怎么行,必须要能在公网访问啊
  • 文件共享。如samba或者ftp
  • 下载。要支持远程下载,http下载有aria2,bt/pt下载有transmission
  • DLNA服务。可以在线看视频啦~
  • iTunes媒体共享
  • time machine等备份服务
  • 和百度云盘等公有云同步
  • 类似于百度云等云盘的私有云服务

外网访问配置

根据是否有公网ip,可以采用不同的方案。
我用的是电信光纤,是有公网ip的,这个具体和运营商有关,可以打客服电话,说要装监控,需要外网访问,看他怎么说。

有公网Ip

这种方案需要做端口映射。
首先找电信改成桥接模式,或者让电信给一个能做端口映射的光猫。
然后在路由器上设置端口映射或DMZ。
现在已经可以在公网上访问本机了,但是每次拨号ip都会变,最好能有一个域名,能通过这个域名知道现在的ip–这个就是DDNS服务了,大部分人用的都是花生壳的。具体方式请自行google

没有公网ip

可以使用内网穿透。网上评价很差,不建议使用。具体使用方式请自行google

NAS软件方案

NAS服务器的软件,有几种方案:

  • 方案一:NAS裸机安装Windows,可在虚拟机中安装Linux,群晖,软路由。推荐。Windows对硬件兼容性好,驱动普遍支持,能无需配置充分发挥显卡性能,平时可做上网机,观影机,HTPC高清电视。
  • 方案二:NAS裸机安装Linux,可在虚拟机中安装Windows,群晖,软路由。技术宅推荐。Windows还是有些缺陷的,系统及某些流氓软件会莫名其妙唤醒全部硬盘。只要能驱动,Linux多媒体不是问题。没有迅雷下载有点问题,移植的也麻烦,可以在Win虚拟机里面迅雷。
  • 方案三:NAS裸机安装群晖,可在虚拟机中安装Windows,Linux,软路由。不推荐。NAS的显卡都浪费了。NAS中集成的功能当然做得很用户友好,非常方便。但是没有的功能很多需要自己编译源码,没有Linux发行版的软件仓库方便,很鸡肋。

注1:以上群晖都可以换成其他NAS操作系统。
注2:不推荐裸机安装NAS操作系统。

以上内容来自:搭建家庭 NAS 服务器有什么好方案?

NAS裸机安装Linux方案

选择NAS裸机安准Linux是因为我要跑脚本,用Linux会方便很多。同时,网上说的驱动问题我没有碰到,用最新版本的系统应该可以解决这个问题,毕竟Linux对Intel的支持还是很好的。我用的是Ubuntu 17.04.

对现有主流的NAS操作系统做了一个调研,发现NAS操作系统太重了,除openmeidavault外,都需要组Raid,对我而言没有必要。

其中,freenas和nas4free,都使用了ZFS文件系统,而ZFS文件系统依赖ECC内存,同时要求8G以上内存。
考虑以上原因,决定不上NAS操作系统。

类似于百度云等云盘的私有云服务

主流的方案有 owncloud和seafile。评测:
https://www.zhihu.com/question/46318905

网上对seafile的评价较高,所以选择了seafile.具体如何,等我试用一个月再说~~

文件共享

  • ftp/sftp文件协议。使用不方便,pass
  • SMB协议。操作方便,速度很快,推荐使用

下载

留坑待填~~

DLNA服务

留坑待填~~

iTunes媒体共享

留坑待填~~

time machine等备份服务

mac

  • Netatalk。它是AFP协议的开源实现
  • Avahi。它是Apple’s Zeroconf 协议的开源实现
    安装使用方式请自行百度。
     注意文件的权限,建议可以先设成777,等没问题后再恢复 

和百度云盘等公有云同步

留坑待填~~

NAS服务器的搭建--硬件篇

方案

现在主流的硬件服务器方案,有以下几种方案:

  • 使用群辉等现成的NAS解决方案
    优点是简单易上手,对技术的要求较低,有较多的资料和文档可以参考,而且插件较多,可以满足大部分的文件需求。缺点是贵,性价比很低,同时机器的性能很差,没有什么拓展能力。适合普通日常办公使用
  • 使用HP gen8等商用服务器
    优点是硬件比较稳定,但是它的价格也比较贵,而且拓展能力不强。在朋友那看过后,发现gen8还有一个很严重的问题:风扇声音太大了!!!
  • 自己组装机器
    其实网上已经有比较多的资料,找几篇来看看就可以了

群晖的机器对我而言太贵了,HP gen8勉强够用,但是考虑到他的拓展性,还是选择自己组装一台机器。

NAS服务器的要点

NAS服务器不同于一般的机器,它有以下特点:

  • 7*24小时开机,要稳定
  • 盘位较多,最好是6盘位以上,一块系统盘,两块主数据盘,加一块工作盘,同时考虑到以后的拓展,预留两个盘位
  • 安静
  • 功耗低

综合以上的特点,参考以上特点,选择自己想要的配置即可。

我的配置

关于硬件的选择,别人已经说得很详细了,我就不在啰嗦,有兴趣的可以参考:

我的配置:

1
2
3
4
5
cpu: intel j3455,备选的有n3160, 都是连主板一起的,399元
内存: 金士顿 ddr3 1600 4g,150元
机箱: 酷冷至尊特警365,165包邮
电源: 鑫谷核动力-巡洋舰c5 300w 109元
共823元

关于硬盘
硬盘需要单独拿出来说,一般建议买一块固态硬盘作为系统盘,大部分时间是没有数据操作的,这时机械硬盘可以休眠,单独使用固态硬盘即可。

数据盘,也就是机械硬盘,在买的时候需要注意一点:
不要买同一品牌、同一批次的硬盘,因为在同样的环境下,同一品牌同一批次的硬盘,可能会同时坏!

NAS服务器的搭建-介绍

最近为了安装 huginn,在aws和 aliyun 上都尝试部署了,发现性能过差,服务很不稳定,就有了自己搭一台服务器的想法,恰好金华老师向我安利了一把 nas 服务器,在调研后,发现正好能满足我的要求。于是开始折腾,这个系统是对这次折腾的记录。

用NAS服务器来做什么

NAS服务器能做的事情很多,比如:

文件服务

  • 私有文件云,类似于百度云盘,但是不用担心文件会忽然被和谐
  • 文件共享服务,可以把所有的文件存放在同一个地方,并在不同的设备之间共享
  • 文件备份服务,如mac的time machine,可以在连接wifi时自动开始同步
  • 灵活的文件备份策略,可以定期把文件压缩加密后存到其他公有云盘
  • 其他的服务,如远程下载功能,在公司启动下载服务后,自动在家开始下载

其他服务

  • 在线工具服务
  • 脚本和爬虫服务
  • 私有git服务等其他服务
  • 开发工具服务器,如mysql、监控等服务

再开始选择之前,想清楚自己的需求,是作为一个简单的文件服务器,还是希望有更多的灵活性。

备注

huginn,一个IFTTT工具,利用它可以做很多很酷的事情,比如在几个网站监控某件商品,当有比较优惠的活动时,会自动发送通知,再如像葡萄PT只在特定的时间开放注册。

下载超大的excel

结论

通过 POI的SXSSFWorkbook,使用操作系统的临时文件来作为缓存,可以生成超大的excel 文件(我自己测试到500W,就没往下测了)。

记得使用压缩。关键代码

1
2
3
4
5
6
7
8
9
10
SXSSFWorkbook wb = null;
try {
wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true); //压缩临时文件,很重要,否则磁盘很快就会被写满
...
} finally {
if (wb != null) {
wb.dispose();// 删除临时文件,很重要,否则磁盘可能会被写满
}
}

背景

由于业务需要,最近要做一个导出超大数据的功能。之间已经有人做过一版,由于受到POI 导出超大数据量时会出错的影响,它把一个大文件拆成很多个小文件,然后再压缩下载,结果经常出现少一两个文件的问题。

目标

支持单个 excel 的 sheet 导出100w 的数据

方案

导出 csv 文件

首先想到的是导出 csv 文件,最方便。但是调研后,也是最快放弃的,因为它存在两个很严重的问题:

  • 不同系统上的编码不一样,需要人工选择,对于普通用户不做好
  • 没有优化和数据压缩,数据量越大,csv 文件的大小比 excel 更大,当数据导出超过10w 时,csv 文件大小是 excel 的1.5倍
导出格式 1w 10w 30w 50w 70w 90w 100w
csv 4.0K/120ms 50M/1261ms 160M/3828ms 271M/7415ms 381M/8929ms 491M/11356ms 546M/13688ms

每行30个字段,每个字段里的内容由 Math.random()产生

导出 excel 文件

大数据量的情况下,csv 的表现较差。只能考虑 excel. 对 excel 作了一个简单的测试

指标 1w 2w 3w 4w 5w 6w 7w 8w 10w
耗时 3326ms 6483ms 7894 ms 9899 ms 12873 ms 15198 ms 17362 ms 20106 ms 25494 ms
导出文件大小 3.7M 7.4MM 12M 15M 19M 23M 26M 30M 37M
cpu 使用率 100% 100% 100% 100% 100% 200% 200% 800% 900%

cpu 使用率均指稳定时的 cpu 使用率

发现几个很严重的问题:

  • 随着数据量的增大,cpu使用率直线上升,这会给系统带来很大的风险
  • 当数据量超过10w 时,会出现 OOM 异常

excel 在内存里存储地越来越大,研究到了瓶颈。要解决这个问题,有两种方案:

  • 先生成多个小 execel 文件,最后合并成一个大文件。查了文档,发现Java 里的工具都是先读出来,再写到 Workbook 对象里, 这样还是会碰到同样的问题。如果用 excel 的工具,则运维成本过大,因此这个方案行不通
  • 参考操作系统里的虚拟内存,用这个来突破 机器的内存限制。但是磁盘的性能很差,这样做的效率很低。

这时,在 POI 的文档里发现了SXSSFWorkbook,其支持使用临时文件,可以用来生成超大 Excel 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Since 3.8-beta3, POI provides a low-memory footprint SXSSF API built on top of XSSF.
SXSSF is an API-compatible streaming extension of XSSF to be used when very large
spreadsheets have to be produced, and heap space is limited. SXSSF achieves its
low memory footprint by limiting access to the rows that are within a sliding window,
while XSSF gives access to all rows in the document. Older rows that are no longer
in the window become inaccessible, as they are written to the disk.
In auto-flush mode the size of the access window can be specified, to hold a certain
number of rows in memory. When that value is reached, the creation of an additional
row causes the row with the lowest index to to be removed from the access window and
written to disk. Or, the window size can be set to grow dynamically; it can be trimmed
periodically by an explicit call to flushRows(int keepRows) as needed.
Due to the streaming nature of the implementation, there are the following
limitations when compared to XSSF:
* Only a limited number of rows are accessible at a point in time.
* Sheet.clone() is not supported.
* Formula evaluation is not supported

以下是 SXSSFWorkbook的测试结果:

使用缓存文件导出 excel

指标 10w 20w 30w 50w 80w 100w 150w 200w 300w
导出文件大小 37M 74M 111M 184M 295M 368M 552M 736M 1.1G
耗时(ms) 16259 29516 45846 75503 120434 156484 233730 303510 463399
cpu 使用率 100 100 100 100 100 100 100 100 100
内存使用(k) 149460 176576 141940 143700 168460 180168 169632 198320 187484
缓存文件大小 37M 74M 111M 185M 295M 369M 553M 737M 1.1G

可以看到,其在性能与资源耗用上都比较平均,至此,问题完美解决。

SXSSFWorkbook在使用上有一些注意项

  • Note that SXSSF allocates temporary files that you must always clean up explicitly, by calling the dispose method.
1
2
3
4
5
6
7
SXSSF flushes sheet data in temporary files (a temp file per sheet) and the size
of these temporary files can grow to a very large value. For example, for a 20 MB
csv data the size of the temp xml becomes more than a gigabyte. If the size of the
temp files is an issue, you can tell SXSSF to use gzip compression:
SXSSFWorkbook wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true); // temp files will be gzipped

测试代码

生成 csv

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
private static void prcoessCSV(int rowsNum) throws Exception {
try {
long startTime = System.currentTimeMillis();
final int NUM_OF_ROWS = rowsNum;
final int NUM_OF_COLUMNS = 30;
File file = new File("ooxml-scatter-chart_" + rowsNum + ".csv");
BufferedWriter bf = new BufferedWriter(new FileWriter(file));
StringBuffer sb = new StringBuffer();
try {
for (int rownum = 0; rownum < NUM_OF_ROWS; rownum++) {
for (int cellnum = 0; cellnum < NUM_OF_COLUMNS; cellnum++) {
sb.append(Math.random());
if ((cellnum + 1) != NUM_OF_COLUMNS) {
sb.append(",");
}
}
sb.append("\n");
if (rownum % 10000 == 0) {
bf.write(sb.toString());
sb = new StringBuffer();
}
}
bf.close();
} catch (Exception ex) {
ex.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("process " + rowsNum + " spent time:" + (endTime - startTime));
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}

excel,不使用缓存

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
try {
long startTime = System.currentTimeMillis();
final int NUM_OF_ROWS = rowsNum;
final int NUM_OF_COLUMNS = 30;
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("Sheet 1");
// Create a row and put some cells in it. Rows are 0 based.
Row row;
Cell cell;
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = sheet.createRow(rowIndex);
for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
cell = row.createCell(colIndex);
cell.setCellValue(Math.random());
}
}
// Write the output to a file
FileOutputStream out = new FileOutputStream("ooxml-scatter-chart_XSSF_" + rowsNum + ".xlsx");
wb.write(out);
out.close();
wb.close();
long endTime = System.currentTimeMillis();
System.out.println("process " + rowsNum + " spent time:" + (endTime - startTime));
} catch (Exception e) {
e.printStackTrace();
throw e;
}

excel,使用缓存

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
try {
long startTime = System.currentTimeMillis();
final int NUM_OF_ROWS = rowsNum;
final int NUM_OF_COLUMNS = 30;
SXSSFWorkbook wb = null;
try {
wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true); //压缩临时文件,很重要,否则磁盘很快就会被写满
Sheet sh = wb.createSheet();
int rowNum = 0;
for (int num = 0; num < NUM_OF_ROWS; num++) {
if (num % 100_0000 == 0) {
sh = wb.createSheet("sheet " + num);
rowNum = 0;
}
rowNum++;
Row row = sh.createRow(rowNum);
for (int cellnum = 0; cellnum < NUM_OF_COLUMNS; cellnum++) {
Cell cell = row.createCell(cellnum);
cell.setCellValue(Math.random());
}
}
FileOutputStream out = new FileOutputStream("ooxml-scatter-chart_SXSSFW_" + rowsNum + ".xlsx");
wb.write(out);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (wb != null) {
wb.dispose();// 删除临时文件,很重要,否则磁盘可能会被写满
}
}
long endTime = System.currentTimeMillis();
System.out.println("process " + rowsNum + " spent time:" + (endTime - startTime));
} catch (Exception e) {
e.printStackTrace();
throw e;
}

克制

每天都会接触到很多的事物,如新游戏、好电影、很火的小说,抑或是新的流行语言、有趣的发现等,这些东西是如此有趣,以至于每件事都想尝试一下。然而一个残酷的现实就是我们根本没有这么多的时间和精力去处理这么多的信息,也做不好那么多感兴趣的事情。我们的精力是如此有限,如果不注意保护,等精力被耗尽之后,工作的就不是大脑,而是肌肉了.

慢慢发现,碎片化的时间配碎片化的事情会更合适一些,比如地铁上刷RSS和微博,等到电脑前就专心做需要一整块时间的事。RSS和知乎这类流媒体有一个很大的特点,就是信息流基本上是无限的,只要你愿意,可以一直往下刷,而时间也在不知不觉中耗尽。如果是在碎片化的时间做这类事情,就不会发生这种事情,因为会被自然打断:要下车了。

人接受新事物是需要耗费能量的,如果在碎片化的事情上花费太多的精力,会反过来抵制我们做核心工作的能力。但是碎片化的消息本身是无法避免的,所以需要一套机制,能让我们在接受碎片化信息的同时,不耗费过多的精力和时间。实践下来,做一个筛选会提高很多的效率:
快速过滤,选出重点->细读资料后作整理、分类

在整理时,会发现有趣的、需要知道的、想知道的东西太多,然而精力非常有限,所以我们需要保持克制,合适的时候,要能丢掉一部分资料,也丢掉一部分兴趣。对于自己的欲望,始终保持克制

克制是一种美德

rebase导致commit丢失

提交代码合并时,被告知有冲突,同事建议我用rebase来解决,可以保持提交历史的干净.然而我按他的建议进行操作之后,发现我的提交内容丢失了.很郁闷.以下是出错过程:

mkdir rebaselost
cd rebaselost
git init
echo "init content" > a
git commit -m 'init content'
git checkout -b branch1
echo "branch1 content" > a
git commit -m 'branch1'
git checkout master
echo "new master content" > a
git commit -m 'master content'
echo "new master content2" > a
git commit -m 'master content2'
git checkout branch1
git rebase master
git rebase --skip

期望的结果是:

new master content2
branch1 content

实际的结果:

new master content2

问题出在 rebase里的 –skip 参数,加了这个参数后,原来的提交记录会丢失.

要找回原来的提交记录,可以通过 git reflog 来查看所有的操作记录(包括被删除的commit记录),然后通过git checkout 来返回

$ git reflog
fa0f628 HEAD@{0}: rebase: branch1
465b616 HEAD@{1}: rebase: checkout master
d0350f9 HEAD@{2}: checkout: moving from branch1 to d0350f9
7f50104 HEAD@{3}: checkout: moving from master to branch1
465b616 HEAD@{4}: commit: new master status2
7f50104 HEAD@{5}: checkout: moving from d1fe9edd03c54d3b2fdfc0f7a9ea49a7459f5596 to master
d1fe9ed HEAD@{6}: rebase: branch1
7f50104 HEAD@{7}: rebase: checkout master
d0350f9 HEAD@{8}: checkout: moving from branch1 to d0350f9
7f50104 HEAD@{9}: rebase finished: returning to refs/heads/branch1
7f50104 HEAD@{10}: rebase: checkout master
d0350f9 HEAD@{11}: checkout: moving from master to branch1
7f50104 HEAD@{12}: commit: master content
4fe1cf4 HEAD@{13}: checkout: moving from branch1 to master
d0350f9 HEAD@{14}: commit: branch1
4fe1cf4 HEAD@{15}: checkout: moving from master to branch1
4fe1cf4 HEAD@{16}: commit (initial): init content
$ git checkout d0350f9

要避免丢失,在冲突时编辑冲突即可

CopyOnWriteArray浅析

  • CopyOnWriteArrayList的特点与用法
  • CopyOnWriteArrayList的数据结构
  • CopyOnWriteArrayList的常用方法及实现

CopyOnWriteArrayList的特点与用法

CopyOnWriteArrayList的数据结构和用法与ArrayList基本都相同,区别主要在于CopyOnWriteArrayList在添加和删除元素等修改集合状态的操作时,都会重新复制一个新的数组,以保证多线程下的安全性。因为每次修改数据结点都要复制整个对象数组,会比较耗时间,所以CopyOnWriteArrayList适合读多写少情况下的多线程场景,如缓存之类的。

CopyOnWriteArrayList的数据结构

CopyOnWriteArrayList的数据结构与ArrayList的基本一样,只是它不再有扩容之类的操作,因为每次修改都是一个新的数组。。。

CopyOnWriteArrayList的常用方法及实现

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    ...
}

从CopyOnWriteArrayList的类结构就可以看出,它的方法签名与ArrayList基本一致,这里就不再重复了。下面讲讲它方法的特殊实现。

  1. 构造方法
    CopyOnWriteArrayList的默认构造方法会创建一个长度为0的数组来储存数据,而不是像ArrayList那样创建一个长度为10的数组

    /**Sets the array. */    
    final void setArray(Object[] a) {
      array = a;
    }
    
    /**
    * Creates an empty list.
    */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    
  2. 添加方法

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    
  3. 修改方法

    方法在进入和退出时有加锁、释放锁

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);
    
            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    
  4. 删除方法

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);
        return (index < 0) ? false : remove(o, snapshot, index);
    }
    
    /**
     * A version of remove(Object) using the strong hint that given
     * recent snapshot contains o at the given index.
     */
    private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                if (index >= len)
                    return false;
                if (current[index] == o)
                    break findIndex;
                index = indexOf(o, current, index, len);
                if (index < 0)
                    return false;
            }
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * Removes from this list all of the elements whose index is between
     * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
     * Shifts any succeeding elements to the left (reduces their index).
     * This call shortens the list by {@code (toIndex - fromIndex)} elements.
     * (If {@code toIndex==fromIndex}, this operation has no effect.)
     *
     * @param fromIndex index of first element to be removed
     * @param toIndex index after last element to be removed
     * @throws IndexOutOfBoundsException if fromIndex or toIndex out of range
     *         ({@code fromIndex < 0 || toIndex > size() || toIndex < fromIndex})
     */
    void removeRange(int fromIndex, int toIndex) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
    
            if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
                throw new IndexOutOfBoundsException();
            int newlen = len - (toIndex - fromIndex);
            int numMoved = len - toIndex;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, newlen));
            else {
                Object[] newElements = new Object[newlen];
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                setArray(newElements);
            }
        } finally {
            lock.unlock();
        }
    }
    
  5. 遍历

    遍历时,直接使用内部的数组,这样可以避开多线程时的问题,类似的还有indexOf

    public void forEach(Consumer<? super E> action) {
        if (action == null) throw new NullPointerException();
        Object[] elements = getArray();
        int len = elements.length;
        for (int i = 0; i < len; ++i) {
            @SuppressWarnings("unchecked") E e = (E) elements[i];
            action.accept(e);
        }
    }
    
    public int indexOf(E e, int index) {
        Object[] elements = getArray();
        return indexOf(e, elements, index, elements.length);
    }
    

Vector浅析

Vector与ArrayList很像,主要区别在于Vector是线程安全的。

Vector的类声明:

*

As of the Java 2 platform v1.2, this class was retrofitted to
* implement the {@link List} interface, making it a member of the
*
* Java Collections Framework
. Unlike the new collection
* implementations, {@code Vector} is synchronized. If a thread-safe
* implementation is not needed, it is recommended to use {@link
* ArrayList} in place of {@code Vector}.

阅读源码后发现,Vector是通过给所有的外部类加上synchronized来实现线程安全的。这种实现方式很简单,但由于synchronized是对this加锁,所以当需要同时一个对象的多个方法时,效率就会很低。

另外,查看iterator的源码

/**
 * Returns an iterator over the elements in this list in proper sequence.
 *
 * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
 *
 * @return an iterator over the elements in this list in proper sequence
 */
public synchronized Iterator<E> iterator() {
    return new Itr();
}

可以发现,在返回遍历对象后,就已经退出了锁,如果修改了对象之后,再去遍历,则会抛出ConcurrentModificationException异常.

Vector<String> vector = new Vector<String>();
vector.add("abc");
Iterator<String> ite = vector.iterator();
vector.add("bcd");
while (ite.hasNext()) {
    System.out.println(ite.next());
}

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.Vector$Itr.checkForComodification(Vector.java:1184)
    at java.util.Vector$Itr.next(Vector.java:1137)
    at com.showstone.containerresearch.list.VectorResearch.main(VectorResearch.java:14)