学习笔记Java集合12-Set-CopyOnWriteArraySet源码分析

介绍CopyOnWriteArraySet底层是使用CopyOnWriteArrayList存储元素的,所以它并不是使用Map来存储元素的。 但是,我们知道CopyOnWriteArrayList底层其实是一个数组,它是允许元素重复的,那么用它来实现CopyOnWriteArraySet怎么保证元素不重复呢? 源码分析Set类的源码一般都比较短,所以我们直接贴源码上来一行一行分析吧。 public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; // 内部使用CopyOnWriteArrayList存储元素 private final CopyOnWriteArrayList<E> al; // 构造方法 public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } // 将集合c中的元素初始化到CopyOnWriteArraySet中 public CopyOnWriteArraySet(Collection<? extends E> c) { if (c.getClass() == CopyOnWriteArraySet.class) { // 如果c是CopyOnWriteArraySet类型,说明没有重复元素, // 直接调用CopyOnWriteArrayList的构造方法初始化 @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc = (CopyOnWriteArraySet<E>)c; al = new CopyOnWriteArrayList<E>(cc.al); } else { // 如果c不是CopyOnWriteArraySet类型,说明有重复元素 // 调用CopyOnWriteArrayList的addAllAbsent()方法初始化 // 它会把重复元素排除掉 al = new CopyOnWriteArrayList<E>(); al.addAllAbsent(c); } } // 获取元素个数 public int size() { return al.size(); } // 检查集合是否为空 public boolean isEmpty() { return al.isEmpty(); } // 检查是否包含某个元素 public boolean contains(Object o) { return al.contains(o); } // 集合转数组 public Object[] toArray() { return al.toArray(); } // 集合转数组,这里是可能有bug的,详情见ArrayList中分析 public <T> T[] toArray(T[] a) { return al.toArray(a); } // 清空所有元素 public void clear() { al.clear(); } // 删除元素 public boolean remove(Object o) { return al.remove(o); } // 添加元素 // 这里是调用CopyOnWriteArrayList的addIfAbsent()方法 // 它会检测元素不存在的时候才添加 // 还记得这个方法吗?当时有分析过的,建议把CopyOnWriteArrayList拿出来再看看 public boolean add(E e) { return al.addIfAbsent(e); } // 是否包含c中的所有元素 public boolean containsAll(Collection<?> c) { return al.containsAll(c); } // 并集 public boolean addAll(Collection<? extends E> c) { return al.addAllAbsent(c) > 0; } // 单方向差集 public boolean removeAll(Collection<?> c) { return al.removeAll(c); } // 交集 public boolean retainAll(Collection<?> c) { return al.retainAll(c); } // 迭代器 public Iterator<E> iterator() { return al.iterator(); } // equals()方法 public boolean equals(Object o) { // 如果两者是同一个对象,返回true if (o == this) return true; // 如果o不是Set对象,返回false if (!(o instanceof Set)) return false; Set<?> set = (Set<?>)(o); Iterator<?> it = set.iterator(); // 集合元素数组的快照 Object[] elements = al.getArray(); int len = elements.length; // 我觉得这里的设计不太好 // 首先,Set中的元素本来就是不重复的,所以不需要再用个matched[]数组记录有没有出现过 // 其次,两个集合的元素个数如果不相等,那肯定不相等了,这个是不是应该作为第一要素先检查 boolean[] matched = new boolean[len]; int k = 0; // 从o这个集合开始遍历 outer: while (it.hasNext()) { // 如果k>len了,说明o中元素多了 if (++k > len) return false; // 取值 Object x = it.next(); // 遍历检查是否在当前集合中 for (int i = 0; i < len; ++i) { if (!matched[i] && eq(x, elements[i])) { matched[i] = true; continue outer; } } // 如果不在当前集合中,返回false return false; } return k == len; } // 移除满足过滤条件的元素 public boolean removeIf(Predicate<? super E> filter) { return al.removeIf(filter); } // 遍历元素 public void forEach(Consumer<? super E> action) { al.forEach(action); } // 分割的迭代器 public Spliterator<E> spliterator() { return Spliterators.spliterator (al.getArray(), Spliterator.IMMUTABLE | Spliterator.DISTINCT); } // 比较两个元素是否相等 private static boolean eq(Object o1, Object o2) { return (o1 == null) ? o2 == null : o1.equals(o2); }}可以看到,在添加元素时调用了CopyOnWriteArrayList的addIfAbsent()方法来保证元素不重复。 ...

August 18, 2019 · 3 min · jiezi

Java并发18并发设计模式-COW模式CopyonWrite模式的应用领域

在上一篇文章中我们讲到 Java 里 String 这个类在实现 replace() 方法的时候,并没有更改原字符串里面 value[] 数组的内容,而是创建了一个新字符串,这种方法在解决不可变对象的修改问题时经常用到。如果你深入地思考这个方法,你会发现它本质上是一种Copy-on-Write 方法。所谓 Copy-on-Write,经常被缩写为 COW 或者 CoW,顾名思义就是写时复制。 不可变对象的写操作往往都是使用 Copy-on-Write 方法解决的,当然 Copy-on-Write 的应用领域并不局限于 Immutability 模式。下面我们先简单介绍一下 Copy-on-Write 的应用领域,让你对它有个更全面的认识。 Copy-on-Write 模式的应用领域我们知道 CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器,它们背后的设计思想就是 Copy-on-Write;通过 Copy-on-Write 这两个容器实现的读操作是无锁的,由于无锁,所以将读操作的性能发挥到了极致。 除了上面我们说的 Java 领域,很多其他领域也都能看到 Copy-on-Write 的身影:Docker 容器镜像的设计是 Copy-on-Write,甚至分布式源码管理系统 Git 背后的设计思想都有 Copy-on-Write。 CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器在修改的时候会复制整个数组,所以如果容器经常被修改或者这个数组本身就非常大的时候,是不建议使用的。反之,如果是修改非常少、数组数量也不大,并且对读性能要求苛刻的场景,使用 Copy-on-Write 容器效果就非常好了。 一个真实案例RPC 框架中有个基本的核心功能就是负载均衡。服务提供方是多实例分布式部署的,所以服务的客户端在调用 RPC 的时候,会选定一个服务实例来调用,这个选定的过程本质上就是在做负载均衡,而做负载均衡的前提是客户端要有全部的路由信息。 例如在下图中,A 服务的提供方有 3 个实例,分别是 192.168.1.1、192.168.1.2 和 192.168.1.3,客户端在调用目标服务 A 前,首先需要做的是负载均衡,也就是从这 3 个实例中选出 1 个来,然后再通过 RPC 把请求发送选中的目标实例。 ...

July 4, 2019 · 2 min · jiezi

Linux快速复制或删除大量小文件

前言公司需要输送给网安部一批数据集,共计1550w张图片,大约3,5T。 处理过程中同时参考网上的一些方法的实践总结。1:快速大量小文件复制a. 本机不同磁盘之间复制:复制目录$ tar cvf – /home/src_dir | tar xvf – -C /opt复制文件$ tar cf – access.log |tar xf – -C /optTips:快速tar打包的一些小技巧以及常用的tar使用tar 快速打包(仅打包不压缩传输比较快)step1:从文件中生成文件列表$ find . -name ‘.jpg’ -print > jpg.txt*** 匹配多个后缀格式$ find . -regex ‘..png|..jpeg|.*.jpg’ -print >jpg.txt若生成的文件列表比较大,可以借助split拆分成小文件进行并行打包. 若文件数较小,可忽略此步骤。#将 文件 jpg.txt 分成若干个小文件,每个文件500000行(-l 500000),文件前缀为xiu_ ,系数不是字母而是 数字(-d),后缀系数为四位数(-a 4)$ split -l 500000 ../ jpg.txt -d -a 4 xiu_step2: tar怎么从文件中读取文件列表呢?查了很久,用 -T-T, –files-from F get names to extract or create from file Ftips: 注意这里不要加 -v参数啦,对于大量文件,控制台输出是很浪费时间的…$ tar -czf jpg.tar.gz -T yourfile **** 不压缩的话,可以直接打包, 比较快。$ tar -cf jpg.tar.gz -T yourfile tar 常用的一些命令解包 $ tar xvf FileName.tar ## 或者 不输出文件,比较快 $ tar xf FileName.tar 不解包查看打包内容$ tar tvf FileName.tar 不解包统计打包内容文件数以及文件夹### 统计文件数$ tar tvf FileName.tar |grep “^-"|wc -l### 统计文件加$ tar tvf FileName.tar |grep “^d”|wc -lb. 跨网络不同主机之间复制 tar+nc:思路:在网络环境中传输时,打包再结合nc命令,通过管道和tcp端口进行传输。 比如 A往B主机传输数据i. 在机器B上,用nc来监听一个端口,任意都行,只要不被占用;并且将收到的数据用tar展开。-l代表监听模式。 $ nc -l 34183 |tar -C /data1datasets/norm/ -zxf -ii. 接着,在A上通过nc和 tar发送data_01目录。使用一致的34183的端口。 $ tar -zcvf - data_01 |nc 192.168.0.1 341832:快速删除大量小文件 或者 大文件2.1: 快速删除大量小文件rsync提供了一些跟删除相关的参数 rsync –help | grep delete –del an alias for –delete-during –delete delete files that don’t exist on the sending side –delete-before receiver deletes before transfer (default) –delete-during receiver deletes during transfer, not before –delete-after receiver deletes after transfer, not before –delete-excluded also delete excluded files on the receiving side –ignore-errors delete even if there are I/O errors –max-delete=NUM don’t delete more than NUM files 其中–delete-before 接收者在传输之前进行删除操作 可以用来清空目录或文件,如下: 1. 建立一个空目录 mkdir -p /del_blank 2. 确立需要清空的目标目录 /del_data 3. 使用rsync同步删除(注意目录后面的“/”),整体效率会快一个数量级的样子。 rsync –delete-before -a -H -v –progress –stats /del_blank /del_data 选项说明: –delete-before 接收者在传输之前进行删除操作 –progress 在传输时显示传输过程 -a 归档模式,表示以递归方式传输文件,并保持所有文件属性 -H 保持硬连接的文件 -v 详细输出模式 -stats 给出某些文件的传输状态 一般我们不需要显示进度,使用以下命令即可 rsync –delete-before -a -H /del_blank /del_data 这样我们要删除的 del_data目录就会被清空了2.2: 快速删除大文件如何删除特别大的文件(数量级),比如nohup.out这样的实时更新的文件,动辄都是几十个G上百G的,也可 以用rsync来清空大文件,而且效率比较高 。1、创建空文件 touch /data/blank.txt 2、用rsync清空文件 rsync -a –delete-before –progress –stats /data/blank.txt ./nohup.out building file list … 1 file to consider blank.txt 0 100% 0.00kB/s 0:00:00 (xfer#1, to-check=0/1) Number of files: 1 Number of files transferred: 1 Total file size: 0 bytes Total transferred file size: 0 bytes Literal data: 0 bytes Matched data: 0 bytes File list size: 27 File list generation time: 0.006 seconds File list transfer time: 0.000 seconds Total bytes sent: 73 Total bytes received: 31 sent 73 bytes received 31 bytes 208.00 bytes/sec total size is 0 speedup is 0.00 ** tips: 当SRC和DEST文件性质不一致时将会报错 当SRC和DEST性质都为文件【f】时,意思是清空文件内容而不是删除文件 当SRC和DEST性质都为目录【d】时,意思是删除该目录下的所有文件,使其变为空目录 最重要的是,它的处理速度相当快,处理几个G的文件也就是秒级的事 最核心的内容是:rsync实际上用的就是替换原理参考链接:https://blog.csdn.net/xiaoyi2…https://www.cnblogs.com/tryin...http://www.voidcn.com/article… ...

February 27, 2019 · 2 min · jiezi

对象的合并及拷贝

对象的合并及拷贝Object.assign()Object.assign() 方法用于对象的合并,将所有自身的(非继承的)可枚举属性的值从一个或多个源对象拷贝到目标对象。返回目标对象。目标对象自身也会改变。Object.assign(target, …sources)target: 目标对象。sources: 源对象。Object.assign() 合并拷贝属性的限制只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。Object.assign({b: ‘c’}, Object.defineProperty({}, ‘invisible’, { enumerable: false, value: ‘hello’ }))// { b: ‘c’ }参数类型1. 只有一个参数如果只有一个参数,Object.assign() 会直接返回该参数。let obj = {a: 1};Object.assign(obj) === obj // true如果该参数不是对象,则会先转成对象,然后返回。typeof Object.assign(2) // “object"由于 undefined 和 null 无法转成对象,所以如果它们作为参数,就会报错。Object.assign(undefined) // 报错Object.assign(null) // 报错2. 对象 + 非对象非对象参数都会转成对象,如果无法转成对象,就会跳过,不会报错。如果非对象参数为 undefined 和 null ,就会跳过,不会报错,返回的依旧是目标对象参数。let obj = {a: 1};Object.assign(obj, undefined) === obj // trueObject.assign(obj, null) === obj // true如果非对象参数为其他类型的值(即数值、字符串和布尔值),也不会报错。但是,除了字符串会以数组形式拷贝入目标对象,其他值都不会产生效果。这是因为只有字符串的包装对象,会产生可枚举属性。let v1 = ‘abc’;let v2 = true;let v3 = 10;let obj = Object.assign({}, v1, v2, v3);console.log(obj) // { “0”: “a”, “1”: “b”, “2”: “c” }3. 目标对象 + 源对象…(1) 属性值为 null 或 undefined 的属性会正常合并Object.assign() 不会跳过那些属性值为 null 或 undefined 的源对象。var o1 = { a: null, b: 1};var o2 = { c: undefined }; var obj = Object.assign({}, o1, o2);obj // {a: null, b: 1, c: undefined}(2) 同名属性的替换如果目标对象与源对象中的属性具有相同的键,则目标对象属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。var o1 = { a: 1, b: 1, c: 1 };var o2 = { b: 2, c: 2 };var o3 = { c: 3 };var obj = Object.assign({}, o1, o2, o3);obj // { a: 1, b: 2, c: 3 }(3) 浅拷贝Object.assign() 方法实行的是浅拷贝,而不是深拷贝。拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。var obj1 = { a: 0 , b: { c: 0 } };var obj2 = Object.assign({}, obj1);obj2 // { a: 0, b: { c: 0 } };obj2.b.c = 3;obj1 // { a: 0, b: { c: 3 } };obj2 // { a: 0, b: { c: 3 } };(4) 数组的处理Object.assign() 可以用来处理数组,但是会把数组视为键值为数组下标的对象来合并,然而最终的返回形式也是数组。Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]Object.assign({0:1,1:2,2:3},{0:4,1:5}) // {0: 4, 1: 5, 2: 3}(5) 存取器属性的处理Object.assign() 如果遇到存取器定义的属性,会只拷贝值。var obj = { foo: 1, get bar() { return 2; }};var copy = Object.assign({}, obj); copy // { foo: 1, bar: 2 }因此必须使用 Object.getOwnPropertyDescriptors() 方法配合 Object.defineProperties() 方法,就可以实现正确拷贝。但仅限于可拷贝 getter 和 setter ,对于属性的引用类型还是属于浅拷贝。var obj = { foo: { a : 0 }, get bar() { return 2; }};var target = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));Object.getOwnPropertyDescriptor(target, ‘bar’)// { get : ƒ bar(), set : undefined, enumerable : true, configurable : true } obj.foo.a = 6target.foo.a // 6常见用途1. 为对象添加属性class Point { constructor(x, y) { Object.assign(this, {x, y}); }}上面方法通过 Object.assign() 方法,将 x 属性和 y 属性添加到 Point 类的对象实例。2. 为对象添加方法Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· }});// 等同于下面的写法SomeClass.prototype.someMethod = function (arg1, arg2) { ··· };SomeClass.prototype.anotherMethod = function () { ··· };3. 浅克隆对象let obj = {a:5};function clone(origin) { return Object.assign({}, origin);}let aaa = clone(obj); // {a:5}不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin);}4. 合并多个对象let merge = (target, …sources) => Object.assign(target, …sources);如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。let merge = (…sources) => Object.assign({}, …sources);5. 为属性指定默认值const DEFAULTS = { a: 0, b: ‘ccc’};function copy(options) { options = Object.assign({}, DEFAULTS, options); // …}注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。参考链接:Object.assign()深拷贝1. JSON.parse(JSON.stringify(obj))var obj1 = { a: 0 , b: { c: 0}};var obj2 = JSON.parse(JSON.stringify(obj1));obj1.b.c = 4;obj2 // { a: 0, b: { c: 0}}但由于 JSON 的局限性,该方法也不是万能的。比如,如果对象的属性是 undefined、函数、symbol 或 XML 对象,该属性会被 JSON.stringify() 过滤掉,导致拷贝时会缺少属性。let obj = { name:‘dora’, sayHello:function(){ console.log(‘Hello World’); }}let cloneObj = JSON.parse(JSON.stringify(obj));console.log(cloneObj); // {name: “dora”}2. 利用递归对每一层都重新创建对象并赋值从而实现深拷贝function deepClone(source){ let targetObj = source.constructor === Array ? [] : {}; for(let keys in source){ if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === ‘object’){ targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ targetObj[keys] = source[keys]; } } } return targetObj;}let obj = { a: { b: 1, c: 2 }, sayHello: function(){ console.log(‘Hello World’); } }let cloneObj = deepClone(obj);obj.a.b = 4obj // {a:{b: 4, c: 2},sayHello:ƒ ()}cloneObj // {a:{b: 1, c: 2},sayHello:ƒ ()} ...

January 17, 2019 · 3 min · jiezi