首页 文章 精选 留言 我的

精选列表

搜索[提高],共10000篇文章
优秀的个人博客,低调大师

“docker-app”实用工具分享,大大提高 Compose 文件复用率

本文首发自“Docker公司”公众号(ID:docker-cn)编译丨小东每周一、三、五 与您不见不散! Docker Compose 在开发人员中非常流行,它用来描述应用程序。目前,GitHub 上有超过30万个 Docker Compose 文件。通过在 docker-compose.yml 文件中对一组服务进行描述,就可以在 Docker 上用一条命令轻松的启动一个复杂的多服务应用程序(或简单的单服务应用程序)。这种易用性使得 Docker Compose 非常适合开发团队快速开展项目。 随着时间的推移,Compose 不断发展并添加了许多功能可以在将相同的应用程序部署到生产环境时提供帮助,例如,指定大量副本、内存资源限制或自定义系统日志服务器。但这些属性可能会跟您自己的环境有所差异。有许多不同的策略来解决这个问题,但是最常见的就是依赖于复制和粘贴。例如,为在不同环境中运行的同一应用程序维护多个 Compose 文件是相当常见的,但这会导致了两个问题: 我们一直都在分享 Docker 镜像,但却没有一个很好的方法来共享使用它们的多服务应用程序; 开发人员和运维人员很难围绕 Compose 文件进行协作。这淡化了在代码中描述应用程序的其中一项关键优势 —— 即开发人员和操作人员使用同一个 Compose 文件的机会,并在产品投入生产之前捕获配置问题; 介绍 docker-app 解决这个问题的一种方法是通过构建一个与 Compose 相辅相成的工具来使它更容易用于共享和协作。请注意,这是实验性的,还有很多工作正在进行中,但我们希望获得早期采用者的反馈和意见,这会: 使基于Compose 的应用程序可以共享在 Docker Hub 和 DTR 上; 支持在应用程序描述和每个环境设置之间更紧密的关注点分离; 该实现包含一些额外的元数据文件和一个小型命令行工具。让我们来举个简单的例子。 使用下面的 Compose 文件。它启动一个HTTP服务器,当触发配置的端口时,它会显示出指定的文本。 version: '3.6' services: hello: image: hashicorp/http-echo command: ["-text", "hello world"] ports: - 5678:5678 用 docker-app 命令安装,让我们基于这个 Compose 文件创建一个应用程序包: $ docker-app init --single-file hello $ ls docker-compose.yml hello.dockerapp 这个应用程序包只是一个文本文件(或者是一个目录),在这个例子中叫做 hello.dockerapp。它包含三个YAML文档: 一些元数据 Compose 文件 应用程序的一些设置 它应该是这样的: # This section contains your application metadata. version: 0.1.0 name: hello description: "" maintainers: - name: yourusername email: "" targets: swarm: true kubernetes: true -- # This section contains the Compose file that describes your application services. version: '3.6' services: hello: image: hashicorp/http-echo command: ["-text", "hello world"] ports: - 5678:5678 -- # This section contains the default values for your application settings. {} 让我们编辑设置部分(替换 {})并为我们的应用程序添加以下默认值: port: 5678 text: hello development version: latest 然后修改 Compose 文件部分,添加一些变量: version: '3.6' services: hello: image: hashicorp/http-echo:${version} command: ["-text", "${text}"] ports: - ${port}:5678 最后,您可以通过使用所提供的默认值渲染 Compose 文件来进行测试。 $ docker-app render version: "3.6" services: hello: command: - -text - hello development image: hashicorp/http-echo:latest ports: - mode: ingress target: 5678 published: 5678 protocol: tcp 请注意,这些变量已经被设置值替换。之后,您可以像使用其他 Compose 文件一样来使用该 Compose 文件了。您可以将其保存到磁盘或 Docker 应用栈中亦或是使用 docker-compose 命令来启动应用程序。 $ docker-app render | docker-compose -f – up 这就是它有趣的地方。我们可以在运行时使用 --set 选项来覆盖这些设置。让我们指定不同的选项并再次运行渲染: $ docker-app render --set version=0.2.3 --set port=4567 --set text="hello production" version: "3.6" services: hello: command: - -text - hello production image: hashicorp/http-echo:0.2.3 ports: - mode: ingress target: 5678 published: 4567 protocol: tcp 请注意在生成的 Compose 文件中对端口和版本进行更改。 如果你愿意,你可以创建一个独立的配置文件来存储这些设置。 让我们用以下内容创建prod.yml: version: 0.2.3 text: hello production port: 4567 然后,您可以使用该配置文件显示 Compose 文件,如下所示: $ docker-app render -f prod.yml 这样就可以很容易地为不同的环境单独的设置文件了,从而减少了复制整个 Compose 文件的需要。 如果您想要超越hello world,我们还准备了一些更高级的例子。 您可以在 Docker Compose 中使用环境变量支持来实现与上述类似的内容,但需要您自己编写工具来提供一个不错的用户界面。有了上述惯例,我们可以在上面创建更有趣的东西。 例如,我们可以构建相当有趣的自省工具,就像下面所示的那样,我们计划将简单的变量替换转换为更复杂的模板。 检查和部署应用程序包 docker-app 命令不仅提供了用不同设置来渲染 Compose 文件的方法。它还提供了一些实用工具来与它们进行交互。例如,如果有人给你一个 .dockerapp,这时你可以很容易地了解它的信息,特别是在运行时发现哪些设置是可用的,而不需要读取任何包代码。 $ docker-app inspect hello 0.1.0 Maintained by: gareth A hello world example of a Docker application package. Setting Default ------- ------- port 8080 text hello world version latest 一旦准备好部署应用程序的一个版本,您就可以使用子命令进行部署了。它的工作方式与 docker 应用栈的部署命令完全相同,因此您应该很熟悉这一点。例如,如果您使用的是Docker Desktop 或 Docker EE,那么您就可以将应用程序部署到 Kubernetes,同时覆盖一些暴露的设置。 $ docker-app deploy --set port=4567 --orchestrator=kubernetes docker-app 还有很多实用的工具,你可以在内置的帮助信息中找到,或者等待后续的文章推送。 感兴趣吗? 如果您感兴趣的话,可以浏览 https://github.com/docker/app 来访问 GitHub 仓库。您将会看到基本的文档和几个示例,以及下载最新版本(针对Windows、macOS或Linux)和应用程序源代码的说明。如果您在有任何问题、想法都可以在这个镜像仓库中提交给我们。

优秀的个人博客,低调大师

【Java入门提高篇】Day24 Java容器类详解(七)HashMap源码分析(下)

前两篇对HashMap这家伙的主要方法,主要算法做了一个详细的介绍,本篇主要介绍HashMap中默默无闻地工作着的集合们,包括KeySet,values,EntrySet,以及对应的迭代器:HashIterator,KeyIterator,ValueIterator,EntryIterator和fast-fail 机制。会介绍三个集合的作用以及它们中隐藏的惊人秘密。 KeySet 我们先来看看KeySet,HashMap中的成员变量keySet保存了所有的Key集合,事实上,这是继承自它的父类AbstractMap的成员变量: transient Set<K> keySet; 而keySet方法,也是覆盖了父类的方法: //AbstractMap 中的keySet方法 public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new AbstractSet<K>() { public Iterator<K> iterator() { return new Iterator<K>() { private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public K next() { return i.next().getKey(); } public void remove() { i.remove(); } }; } public int size() { return AbstractMap.this.size(); } public boolean isEmpty() { return AbstractMap.this.isEmpty(); } public void clear() { AbstractMap.this.clear(); } public boolean contains(Object k) { return AbstractMap.this.containsKey(k); } }; keySet = ks; } return ks; } //HashMap 中的keySet方法 /** * 返回一个键值的集合视图,该集合由map支持,因此对map的更改会反映在集合中,反之亦然。 * 如果在对集合进行迭代的过程中修改了map中的映射(除了通过迭代器的删除操作),迭代的结果是未定义的。 * 该集合支持元素删除,通过Iterator.remove,Set.remove,removeAll,retainAll和clear操作 * 从映射中删除相应的映射。 它不支持add或addAll操作。 */ public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } 可以看到,AbstractMap中keySet是一个AbstractSet类型,而覆盖后的keySet方法中,keySet被赋值为KeySet类型。翻翻构造器可以发现,在构造器中并没有初始化keySet,而是在KeySet方法中对keySet进行的初始化(HashMap中都是使用类似的懒加载机制),KeySet是HashMap中的一个内部类,让我们再来看看这个KeySet类型的全貌: final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } } 其实KeySet就是继承自AbstractSet,并覆盖了其中的大部分方法,遍历KeySet时,会使用其中的KeyIterator,至于Spliterator,是为并行遍历设计的,一般是用于Stream的并行操作。forEach方法则是用于遍历操作,将函数式接口操作action应用于每一个元素,我们来看一个小栗子: public class Test { public static void main(String[] args) { Map<String, Integer> map = new HashMap(); map.put("小明", 66); map.put("小李", 77); map.put("小红", 88); map.put("小刚", 89); map.put("小力", 90); map.put("小王", 91); map.put("小黄", 92); map.put("小青", 93); map.put("小绿", 94); map.put("小黑", 95); map.put("小蓝", 96); map.put("小紫", 97); map.put("小橙", 98); map.put("小赤", 99); map.put("Frank", 100); Set<String> ks = map.keySet(); System.out.printf("keySet:%s,keySet的大小:%d,keySet中是否包含Frank:%s", ks, ks.size(), ks.contains("Frank")); System.out.println(); ks.forEach((item) -> System.out.println(item)); } } 输出如下: keySet:[小刚, 小橙, 小蓝, 小力, 小青, 小黑, 小明, 小李, 小王, 小紫, 小红, 小绿, Frank, 小黄, 小赤],keySet的大小:15,keySet中是否包含Frank:true 小刚 小橙 小蓝 小力 小青 小黑 小明 小李 小王 小紫 小红 小绿 Frank 小黄 小赤 如果不记得这个AbstractMap和AbstractSet在容器框架中是什么地位,可以往前翻翻这系列文章的第一篇,看看容器家族的族谱。 但是说了这么多,这个keySet。里面的元素是什么时候放进去的呢?我们自然会想到,大概就是调用put方法往里添加元素的时候,顺便把key放进keySet中,完美!让我们再回顾一下putVal方法,来看看是不是这样的: final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //如果当前table未初始化,则先重新调整大小至初始容量 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //(n-1)& hash 这个地方即根据hash求序号,想了解更多散列相关内容可以查看下一篇 if ((p = tab[i = (n - 1) & hash]) == null) //不存在,则新建节点 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //先找到对应的node if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) //如果是树节点,则调用相应的putVal方法,这部分放在第三篇内容里 //todo putTreeVal e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //如果是链表则之间遍历查找 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { //如果没有找到则在该链表新建一个节点挂在最后 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st //如果链表长度达到树化的最大长度,则进行树化,该函数内容也放在第三篇 //todo treeifyBin treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //如果已存在该key的映射,则将值进行替换 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //修改次数加一 ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } emmmmm,好像没找到?你也许会想,会不会是在TreeNode的putTreeVal方法或者在treeifyBin方法中对key进行插入?好了好了,不要再翻了,其实这个奥秘隐藏在KeySet的迭代器中,再回头看看,它的迭代器返回的是一个KeyIterator,而KeyIterator也是HashMap中的一个内部类,继承自HashMap中的另一个内部类HashIterator。 HashIterator 让我们带着这个疑问,来看看这个HashIterator类里到底有什么玄机: abstract class HashIterator { //指向下一个节点 Node<K,V> next; //当前节点 Node<K,V> current; //为实现 fast-fail 机制而设置的期望修改数 int expectedModCount; //当前遍历到的序号 int index; HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // 移动到第一个非null节点 do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; // fast-fail 机制的实现 即在迭代器往后遍历时,每次都检测expectedModCount是否和modCount相等 // 不相等则抛出ConcurrentModificationException异常 if (modCount != expectedModCount) throw new ConcurrentModificationException(); //如果遍历越界,则抛出NoSuchElementException异常 if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { //如果遍历到末尾,则跳到table中下一个不为null的节点处 do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; //移除节点 removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } } 可以发现,在迭代器中,使用nextNode进行遍历时,先把next引用赋值给current,然后把next.next赋值给next,再获取了外部类HashMap中的table引用(t = table),这样就直接通过遍历table的方式来实现对key,value和entry的读取。 if ((next = (current = e).next) == null && (t = table) != null) { //如果遍历到末尾,则跳到table中下一个不为null的节点处 do {} while (index < t.length && (next = t[index++]) == null); } KeyIterator,ValueIterator,EntryIterator都是HashIterator的子类,实现也很简单,仅仅修改了泛型类型: final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } } final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } } 这样keySet在遍历的时候,就可以通过它的迭代器去遍历访问外部类HashMap中的table,类似的,values和entrySet也是使用相似的方式进行遍历。 public Collection<V> values() { Collection<V> vs = values; if (vs == null) { vs = new Values(); values = vs; } return vs; } final class Values extends AbstractCollection<V> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<V> iterator() { return new ValueIterator(); } public final boolean contains(Object o) { return containsValue(o); } public final Spliterator<V> spliterator() { return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } } public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { return new EntrySpliterator<K,V>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } } 至此,这个未解之谜算是告一段落了。 transient 但是,细心的同学可能会发现,HashMap中的table,entrySet,keySet,value等成员变量,都是用transient修饰的,为什么要这样做呢? 首先,我们还是先说说这个transient是干嘛用的,这就要涉及Java中的序列化了,序列化是什么东西呢? Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息。一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输,一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。 当然,就像数据存储是为了读取那样,序列化后的最终目的是为了恢复成原先的Java对象,要不然序列化后干嘛呢,这个过程就叫做反序列化。 当我们使用实现Serializable接口的方式来进行序列化时,所有字段都会被序列化,那如果不想让某个字段被序列化(比如出于安全考虑,不将敏感字段序列化传输),便可以使用transient关键字来标志,表示不想让这个字段被序列化。 那么问题来了,存储节点信息的table用transient修饰了,那么序列化和反序列化的时候,数据还怎么传输??? emmmm,这又涉及到一个蛋疼的操作,序列化并没有那么简单,实现了Serializable接口后,在序列化时,会先检测这个类是否存在writeObject和readObject方法,如果存在,则调用相应的方法: /** * 将HashMap的实例状态保存到一个流中 */ private void writeObject(java.io.ObjectOutputStream s) throws IOException { int buckets = capacity(); // 写出threshold,loadfactor和所有隐藏的成员 s.defaultWriteObject(); s.writeInt(buckets); s.writeInt(size); internalWriteEntries(s); } /** * 从流中重构HashMap实例 */ private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // 读取threshold,loadfactor和所有隐藏的成员 s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + loadFactor); // 读取并忽略桶的数量 s.readInt(); // 读取映射的数量 int mappings = s.readInt(); if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (如果是0,则使用默认值) // Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); float fc = (float)mappings / lf + 1.0f; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc)); float ft = (float)cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE); SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; table = tab; // 读取键值对信息,然后把映射插入HashMap实例中 for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } } 这确实是一个极其糟糕的设计。。。而且这里还是一个private方法。 那么直接使用默认的序列化不好吗?非要大费周章的骚操作一波?一部分原因是为了解决效率问题,因为HashMap中很多桶是空的,将其序列化没有任何意义,所以需要手动使用 writeObject() 方法,只序列化实际存储元素的数组。另一个很重要的原因便是,HashMap的存储是依赖于对象的hashCode的,而Object.hashCode()方法是依赖于具体虚拟机的,所以同一个对象,在不同虚拟机中的HashCode可能不同,那这样映射到的HashMap中的位置也不一样,这样序列化和反序列化的对象就不一样了。引用大神的一段话: For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn't even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt. 蹩脚翻译一下: 例如,考虑散列表的情况。 它的物理存储是一系列包含键值条目的散列桶。 条目驻留的存储区是其密钥的哈希码的函数,通常,JVM的实现不保证相同。 事实上,它甚至不能保证每次运行都是一样的。 因此,接受哈希表的默认序列化形式将构成严重的错误。 对哈希表进行序列化和反序列化可能会产生不变性被严重损毁的对象。 好了,到此为止,这部分内容算是over了,后面会继续介绍HashMap中最麻烦的一部分,TreeNode让我们师母已呆 记得动动小手点个赞或者点个关注哦,如果觉得不错的话,也欢迎分享给你的朋友,让bug传播的更远一些,呸,说错了,让知识传播的更远一些如果写的有误的地方,欢迎大家及时指出,我会第一时间予以修正,也欢迎提出改进建议,之后还会继续更新,欢迎继续关注! 真正重要的东西,用眼睛是看不见的。

优秀的个人博客,低调大师

【Java入门提高篇】Day23 Java容器类详解(六)HashMap源码分析(中)

上一篇中对HashMap中的基本内容做了详细的介绍,解析了其中的get和put方法,想必大家对于HashMap也有了更好的认识,本篇将从了算法的角度,来分析HashMap中的那些函数。 HashCode 先来说说HashMap中HashCode的算法,在上一篇里,我们看到了HashMap中的put方法是这样的: public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 那这个hash函数又是什么呢?让我们来看看它的真面目: /** * 将高位与低位进行与运算来计算哈希值。因为在hashmap中使用2的整数幂来作为掩码,所以只在当前掩码之上的位上发生 * 变化的散列总是会发生冲突。(在已知的例子中,Float键的集合在小表中保持连续的整数)因此,我们应用一个位运算 * 来向下转移高位的影响。 这是在综合考虑了运算速度,效用和质量之后的权衡。因为许多常见的散列集合已经合理分布 * (所以不能从扩散中受益),并且因为我们使用树来处理bin中发生的大量碰撞的情况,所以我们尽可能以代价最低的方式 * 对一些位移进行异或运算以减少系统损失, 以及合并由于hashmap容量边界而不会被用于散列运算的最高位的影响。 * * todo 扰动函数 */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 可以看出,这里并不是简单的使用了key的hashCode,而是将它的高16位与低16位做了一个异或操作。(“>>>”是无符号右移的意思,即右移的时候左边空出的部分用0填充)这是一个扰动函数,具体效果后面会说明。接下来再看看之前的putval方法: 1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 2 boolean evict) { 3 Node<K,V>[] tab; Node<K,V> p; int n, i; 4 //如果当前table未初始化,则先重新调整大小至初始容量 5 if ((tab = table) == null || (n = tab.length) == 0) 6 n = (tab = resize()).length; 7 //(n-1)& hash 这个地方即根据hash求序号,想了解更多散列相关内容可以查看下一篇 8 if ((p = tab[i = (n - 1) & hash]) == null) 9 //不存在,则新建节点 10 tab[i] = newNode(hash, key, value, null); 11 else { 12 Node<K,V> e; K k; 13 //先找到对应的node 14 if (p.hash == hash && 15 ((k = p.key) == key || (key != null && key.equals(k)))) 16 e = p; 17 else if (p instanceof TreeNode) 18 //如果是树节点,则调用相应的putVal方法,这部分放在第三篇内容里 19 //todo putTreeVal 20 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 21 else { 22 //如果是链表则之间遍历查找 23 for (int binCount = 0; ; ++binCount) { 24 if ((e = p.next) == null) { 25 //如果没有找到则在该链表新建一个节点挂在最后 26 p.next = newNode(hash, key, value, null); 27 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 28 //如果链表长度达到树化的最大长度,则进行树化,该函数内容也放在第三篇 29 //todo treeifyBin 30 treeifyBin(tab, hash); 31 break; 32 } 33 if (e.hash == hash && 34 ((k = e.key) == key || (key != null && key.equals(k)))) 35 break; 36 p = e; 37 } 38 } 39 //如果已存在该key的映射,则将值进行替换 40 if (e != null) { // existing mapping for key 41 V oldValue = e.value; 42 if (!onlyIfAbsent || oldValue == null) 43 e.value = value; 44 afterNodeAccess(e); 45 return oldValue; 46 } 47 } 48 //修改次数加一 49 ++modCount; 50 if (++size > threshold) 51 resize(); 52 afterNodeInsertion(evict); 53 return null; 54 } 注意看第八行的代码: tab[i = (n - 1) & hash] (n - 1) & hash即通过key的hash值来取对应的数组下标,并非是对table的size进行取余操作。 那么,为什么要这样做呢?首先,扰动函数的目的就是为了扩大高位的影响,使得计算出来的数值包含了高 16 位和第 16 位的特性,让 hash 值更加深不可测来降低碰撞的概率。从hash方法的注释中,我们也可以找到答案,一般的散列,其实都是做取余处理,但是HashMap中的table大小是2的整数次幂,也就是说,肯定不是质数,那么在取余的时候,偶数的映射范围势必就要小了一半,这样效果显然就差很多,而且,除法和取余其实是很慢的操作,所以在JDK8中,使用了一种很巧妙的方式来进行散列。首先,table的大小size设置成了2的整数次幂,这样使用size-1就变成了掩码,下面是我找的一张图,能很好的解释这个过程: n是table的大小,默认是16,二进制即为10000,n - 1对应的二进制则为1111,这样再与hash值做“与”操作时,就变成了掩码,除了最后四位全部被置为0,而最后四位的范围肯定会落在(0~n-1)之间,正好是数组的大小范围,散列函数的妙处就在于此了。简直不能更稳,一波操作猛如虎。 那么我们继续上一篇的栗子,我们来一步一步分析一下,小明和小李的hash值的映射过程: 小明的hash值是756692,转换为二进制为10111000101111010100,table的大小是32,n-1=31,对应的二进制为:11111,做“与”运算之后,得到的结果是10100,即为20。 小李的hash值是757012,转换为二进制为10111000110100010100,与11111做与运算后,得到的结果也是10100,即20,于是就与小明发生了冲突,但还是要先来后到,于是小李就挂在了小明后面。 散列函数看完了,我们接下来再看看扩容函数。 扩容函数 扩容函数其实之前也已经见过了,就在上面的putVal方法里,往上面翻一翻,第六行可以看到resize函数,这就是扩容函数,让我们来看看它的庐山真面目: 1 /** 2 * 初始化或将table的大小进行扩容。 如果table为null,则按照字段threshold中的初始容量目标进行分配。 3 * 否则,因为我们使用2次幂进行扩容,所以在新表中,来自每个bin中的元素必须保持在相同的索引处,或者以原偏移量的2次幂进行移动。 4 */ 5 final Node<K,V>[] resize() { 6 Node<K,V>[] oldTab = table; 7 int oldCap = (oldTab == null) ? 0 : oldTab.length; 8 int oldThr = threshold; 9 int newCap, newThr = 0; 10 if (oldCap > 0) { 11 if (oldCap >= MAXIMUM_CAPACITY) { 12 threshold = Integer.MAX_VALUE; 13 return oldTab; 14 } 15 //新的容量扩展成原来的两倍 16 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 17 oldCap >= DEFAULT_INITIAL_CAPACITY) 18 //阈值也调整为原来的两倍 19 newThr = oldThr << 1; // double threshold 20 } 21 else if (oldThr > 0) // initial capacity was placed in threshold 22 newCap = oldThr; 23 else { // zero initial threshold signifies using defaults 24 newCap = DEFAULT_INITIAL_CAPACITY; 25 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 26 } 27 if (newThr == 0) { 28 float ft = (float)newCap * loadFactor; 29 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 30 (int)ft : Integer.MAX_VALUE); 31 } 32 threshold = newThr; 33 @SuppressWarnings({"rawtypes","unchecked"}) 34 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 35 table = newTab; 36 //将旧数组中的node重新散列到新数组中 37 if (oldTab != null) { 38 for (int j = 0; j < oldCap; ++j) { 39 Node<K,V> e; 40 if ((e = oldTab[j]) != null) { 41 oldTab[j] = null; 42 if (e.next == null) 43 newTab[e.hash & (newCap - 1)] = e; 44 else if (e instanceof TreeNode) 45 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 46 else { // preserve order 47 Node<K,V> loHead = null, loTail = null; 48 Node<K,V> hiHead = null, hiTail = null; 49 Node<K,V> next; 50 do { 51 next = e.next; 52 if ((e.hash & oldCap) == 0) { 53 if (loTail == null) 54 loHead = e; 55 else 56 loTail.next = e; 57 loTail = e; 58 } 59 else { 60 if (hiTail == null) 61 hiHead = e; 62 else 63 hiTail.next = e; 64 hiTail = e; 65 } 66 } while ((e = next) != null); 67 if (loTail != null) { 68 loTail.next = null; 69 newTab[j] = loHead; 70 } 71 if (hiTail != null) { 72 hiTail.next = null; 73 newTab[j + oldCap] = hiHead; 74 } 75 } 76 } 77 } 78 } 79 return newTab; 80 } 这里可以看到,如果原来的table还未被初始化的话,调用该函数后就会被扩容到默认大小(16),上一篇中也已经说过,HashMap也是使用了懒加载的方式,在构造函数中并没有初始化table,而是在延迟到了第一次插入元素之后。 当使用put插入元素的时候,如果发现目前的bins占用程度已经超过了Load Factor所设置的比例,那么就会发生resize,简单来说就是把原来的容量和阈值都调整为原来的2倍,之后重新计算index,把节点再放到新的bin中。因为index值的计算与table数组的大小有关,所以扩容后,元素的位置有可能会调整: 以上图为例,如果对应的hash值第五位是0,那么做与操作后,得到的序号不会变,那么它的位置就不会改变,相反,如果是1,那么它的新序号就会变成原来的序号+16,。 好像也不是很多嘛,嗯,算法部分就先介绍到这里了,之后的一篇再来说说HashMap中的EntrySet,KeySet和values(如果时间够的话顺便把迭代器也说一说)。 好了,本篇就此愉快的结束了,最后祝大家端午节快乐!如果觉得内容还不错的话记得动动小手点关注哦,你的支持就是我最大的动力! 真正重要的东西,用眼睛是看不见的。

优秀的个人博客,低调大师

JavaScript 工作原理之十二-网络层探秘及如何提高其性能和安全性

本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第十二章。 正如在之前关于渲染引擎的文章中所讲的那样,我们相信好的和伟大的 JavaScript 开发者之间的差别在于后者不仅仅只是理解了语言的具体细节还了解其内部构造和运行环境。 网络简史 49 年前,ARPAnet 诞生了。它是早期的报文分组交换网络及第一个实现 TCP/IP 协议套件的网络。该网络连通了加利福亚大堂和斯坦福研究所。20 年后,Tim Berners-Lee 分发了一个后来为人所熟知的万维网的 『Mesh』草案。在 49 年的时间里,网络走过了一段漫长的旅程,从仅仅只是是两台电脑间交换数据报文到至少 7500 万台服务器,38 亿人使用互联网以及 13 亿个网站。 本文将试着分析现代浏览器使用哪些技术来自动提升应用性能(有些你甚至不了解),然后着重介绍浏览器网络层。最后,提供一些让浏览器提升网络应用程序性能的技巧。 概述 现代浏览器专门为快速,高效和安全数据传输的网络应用/网站而设计开发的。拥有数以百计的组件运行于各个不同的层级,从进程管理和安全沙箱到 GPU 管线,音频和视频及其它更多等等,网络浏览器更类似于一个操作系统而不仅仅只是一个软件。 浏览器的整体性能是由一些大型的组件所决定的,这些组件包括:解析,布局,样式计算,JavaScript 和 WebAssembly执行,渲染,当然还有网络堆栈。 一般情况下,工程师们会把网络堆栈看成是一个性能瓶颈。经常会发生这样的情况因为从网络抓取所有的资源会堵塞渲染剩下的步骤。为了更加高效的网络层,它需要不仅仅只是扮演套接字管理员的角色。在我们看来获取资源是一个非常简单的机制,但是实际上它集成自身的优化准则,接口和服务的一整套平台。 网页开发者不需要担心单独的 TCP 或者 UDP 数据包,请求格式化,缓存以及其它正在发生的一切。浏览器会处理这些复杂的玩意,这样就可以专注开发自己的程序。但是,知道其内部的原理可以帮助开发者开发出更加高效和安全的程序。 本质上,当用户开始和浏览器发生交互所产生的情况如下: 用户在浏览器地址栏中输入 URL 地址。 在网络上查找指定 URL 的资源,浏览器开始检查本地和应用程序缓存并试着使用本地副本来响应资源的请求。 当缓存不可用,浏览器使用 URL 中的域名然后根据域名从 DNS 处获取服务器的 IP 地址。如果有域名缓存,将不需要进行 DNS 查询。 浏览器创建一个 HTTP 报文表明其请求远程服务器的某个网页。 报文被传输到 TCP 层,该层会在 HTTP 报文头部添加其自身的信息。该信息是保持创建的会话的必要信息。 然后在 IP 层处理报文,该层的主要职责即找出从用户发送报文到远程服务器的路径。在 HTTP 报文头部添加该路径信息。 传输报文到远程服务器。 一旦接收到报文,以类似的方式返回响应数据。 W3C Navigation Timing specification 提供了浏览器接口及浏览器中每个请求背后的可视化计时和性能数据。让我们浏览下这些组件,因为每个组件在获取最佳用户体验方面扮演了重要的角色。 整个网络请求过程是相当复杂的并且有许多的层次结构,每一层都有可能成为性能瓶颈。这就是为什么浏览器使用各种技术努力提升其性能,以便把整个网络通信的性能损耗降至最低。 套接字管理 看些新技术吧: 源-由应用程序协议,域名和端口号的三个部分组成(比如 https, www.example.com, 443) 套接字池-属于同源的一组套接字(所有的主流浏览器都限制套接字池最多只能有 6 个套接字) JavaScript 和 WebAssembly 禁止开发者操作单独的网络套接字的生命周期,这样是相当的明智的。这样不仅仅可以让你头发少掉点而且可以让浏览器自动优化大量的性能,这些性能包括套接字重用,请求优化和延迟绑定,协议协商,强制连接限制及其它的优化措施。 实际上,现代浏览器更一步地将请求管理周期从套接字管理中剥离了出来。用套接字池来组织套接字,以源来分组套接字,每个套接字池强制限制其连接数和安全约束。排队,优先化等待的请求,然后和套接字池中的单个套接字绑定。如果不是服务器主动关闭这些连接,多个请求可以自动重用相同的套接字。 由于创建一个新的 TCP 连接会带来额外的性能开销,重用连接会为其自带来极大的性能提升。默认情况下,当发起请求的时候,浏览器使用所谓的 『keepalive』机制以节省创建到服务器的新连接所耗费的时间。创建一个新的 TCP 连接的平均时间为: 本地请求-23 毫秒 Transcontinental 请求-120 毫秒 Intercontinental 请求-225 毫秒 这样的架构衍生出了一些其它的优化方法。请求可以依据优先级来以不同的顺序执行。浏览器可以优化所有套接字间的带宽分配或者它可以创建套接字以等待预期的请求。 如上所述,这些都是浏览器所控制而不用开发者进行干预。但这并不意味着我们无所事事了。选择正确的数据传输所用的网络通信模式,类型和频率,正确的协议类型以及正确的服务器堆栈隧道/优化对于提升整个程序的性能有着至关重要的作用。 一些浏览器甚至更进一步。例如,当你使用 Chrome 的时候,当用户使用的时候它会进行自我学习从而变得更加快速。它基于访问过的网页和典型的浏览器模式来进行学习,这样就可以预期可能的用户行为且在用户进行任意操作之前进行操作。最简单的例子即当用户悬停在某个链接上的时候预渲染页面。如果你想学习更多关于 Chrome 优化技术的文章,可以查看 High-Performance Browser Networking 这本书的 www.igvita.com/posa/high-p… 章节。 网络安全和沙箱 允许浏览器操作单独的套接字有另一个非常重要的目的即:浏览器就可以针对不被信任的程序资源强制实施一套一致的安全和政策约束措施。例如,浏览器禁止通过 API 直接访问原始网络套接字,因为这样会导致任意可疑的程序随意连接任意主机。浏览器也强制连接数限制以保护服务器免受由于客户端访问而耗尽其资源。 浏览器格式化所有流出的请求以强制格式正确和一致的协议语义来保护服务器。同样地,浏览器会自动解码响应内容以保护用户免受可疑服务器的攻击。 TSL 协商 Transport Layer Security (TLS) 是一个为计算机网络提供通信安全的加密协议。它广泛应用于大量应用程序,其中之一即浏览网页。网站可以使用 TLS 来保证服务器和网页浏览器之间的所有通信安全。 整个 TLS 握手过程包含以下几个步骤: 客户端向服务器发送 『Client hello』 信息,附带着客户端随机值和支持的密码组合。 服务器返回给客户端 『Server hello』信息,附带着服务器随机值。 服务器返回给客户端认证证书及或许要求客户端返回一个类似的证书。服务器返回『Server hello done』信息。 如果服务器要求客户端发送一个证书,客户端进行发送。 客户端创建一个随机的 Pre-Master 密钥然后使用服务器证书中的公钥来进行加密,向服务器发送加密过的 Pre-Master 密钥。 服务器收到 Pre-Master 密钥。服务器和客户端各自生成基于 Pre-Master 密钥的主密钥和会话密钥。 客户端给服务器发送一个 『Change cipher spec』的通知,表明客户端将会开始使用新的会话密钥来哈希和加密消息。客户端也发送了一个 『Client finished』的消息。 服务器接收到『Change cipher spec』的通知然后使用会话钥匙来切换其记录层安全状态为对称加密状态。服务器返回客户端一个 『Server finished』消息。 客户端和服务器现在可以通过建立的安全通道来交换程序数据。所有客户端和服务器之间发送的信息都会使用会话密钥进行加密。 每当发生任何验证失败的时候,用户会收到警告。比如服务器使用自签名的证书。 同源策略 当两个页面的协议,端口(如果有指定)以及主机名都是一样的则称为同源。 以下为一些可能包含跨域的资源示例: <script src=”…”></script> 里面的 JavaScript 代码。语法错误的错误信息仅适用于同源脚本。 <link rel=”stylesheet” href=”…”> 的 CSS。由于 CSS 的松散语法规则,跨域 CSS 要求正确的 Content-Type 头。各个浏览器的限制不同。 <img> 图片 <video> 和 <audio> 媒体文件 <object>,<embed>和<applet> 插件 @font-face 字体。一些浏览器允许跨域字体,其它则要求同源字体。 和 相关的一切内容。网站可以使用 X-Frame-Options头来防止此种跨域交互。 以上的列表还远远不够;该列表旨在强调工作中的『最小特权』原则。浏览器只为应用程序代码暴露出其所必需的接口和资源:应用提供数据和 URL 地址,然后浏览器格式化请求及处理每条连接的整个生命周期。 需要注意的是并没有一个简单的 『同源策略』概念。 相反,有一系列相关的机制来强制限制浏览器的 DOM 访问,cookie 和 会话状态管理,网络连接和其它组件。 资源和客户端状态缓存 最好和最快的请求即不创建请求。在分派一个请求前,浏览器自动检查其资源缓存,进行必要的验证检查然后当指匹配指定的条件时返回一份本地资源拷贝。如果缓存中没有可用的本地资源,则发起网络请求然后把响应内容自动放置于缓存中以备之后的访问(如果这是被允许的)。 浏览器自动为每个资源求值缓存指令。 当条件允许时,浏览器自动重新恢复过期资源 浏览器自动处理缓存和资源回收的大小 管理一个高效和优化的资源缓存是非常困难的。谢天谢地,浏览器为我们处理了整个复杂的玩意,而我们所需要做的即保证服务器返回恰当的缓存指令;想了解更多可以看 客户端资源缓存 文章。你为网页上的所有资源添加 Cache-Control,ETag,和 Last-Modified 的响应头信息。 最后,一个经常被忽略但至关重要的浏览器功能即其提供了验证,会话和 cookie 管理。浏览器为每个源维护单独的『cookie jars』,通过提供必要的程序和服务器接口来读写新的 cookie,会话和认证数据,以及自动挂载和处理适当的 HTTP 头来为我们自动处理整个过程。 例子: 一个简单但明了的用来展示浏览器的延迟会话状态管理的方便性的例子即:多个选项卡或者浏览器窗口可以共享一个认证会话,反之亦然;一个选项卡中的登出操作可以使所有其它打开窗口的会话失效。 应用程序接口和协议 了解了网络服务之后,最终要讲到应用程序接口和协议。众所周知,更底层的结构提供了一组广泛的重要服务:套接字和连接管理,请求和响应处理,各种安全策略,缓存及其它更多的强制措施。每当初始化一个 HTTP 请求或者 XMLHttpRequest,一个持久的服务推事件或者 WebSocket 会话抑或打开一个 WebRTC 连接,我们就是在和部分或者所有这些底层服务进行交互。 没有单一的最佳协议或者接口。每个复杂的程序都会基于不同的要求混合使用不同的传输协议:和浏览器缓存的交互,协议开销,消息延迟,可靠性,数据传输类型以及其它。一些协议拥有低数据传输延迟的特性(比如服务器推事件,WebSocket),但是可能不符合其它重要的场合,比如利用浏览器缓存或者支持任意情况下的二进制数据传输的能力。 一些可以用来提升程序性能和安全的小技巧 一直在请求中使用 『Connection: Keep-Alive』头信息。浏览器默认在请求头中添加 『Connection: Keep-Alive』。保证服务器也使用同样的机制。 使用合适的 Cache-Control,Etag 和 Last-Modified 头信息以便节省浏览器的下载时间。 花些时间调整和优化服务器。这是奥秘所在!注意这一过程是否针对每个程序和所传输的数据。 一直使用 TLS!特别是如果程序中包含有任意类型的认证。 研究浏览器所提供的安全策略并且在程序中强制实施。 扩展 关于字体文件的跨域问题可以查看这里和这里。 打个广告 ^.^ 今日头条招人啦!发送简历到 likun.liyuk@bytedance.com ,即可走快速内推通道,长期有效!国际化PGC部门的JD如下:c.xiumi.us/board/v5/2H…,也可内推其他部门! 本系列持续更新中,Github 地址请查阅这里。 原文发布时间为:2018年06月25日 原文作者:tristan 本文来源: 掘金 如需转载请联系原作者

优秀的个人博客,低调大师

Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类

正文 一、前提 目的:动态加载SD卡中Apk的类。 注意:被加载的APK是未安装的。 相关:本文是本博另外一篇文章:Android动态加载jar/dex的升级版。 截图: 成功截图: 二、准备 准备被调用Android工程:TestB ITest public interfaceITest{ StringgetMoney(); } TestBActivity public classTestBActivity extendsActivity implementsITest{ /** Calledwhentheactivityisfirstcreated. */ @Override public voidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override publicStringgetMoney(){ return"1"; } } 代码说明:很简单的代码。将生成后的TestB.apk拷贝到SD卡的根目录下。 三、调用 调用工程TestA public classTestAActivity extendsActivity{ /** Calledwhentheactivityisfirstcreated. */ @Override public voidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); Stringpath=Environment.getExternalStorageDirectory()+"/"; Stringfilename="TestB.apk"; DexClassLoaderclassLoader= newDexClassLoader(path+filename,path, null,getClassLoader()); try{ ClassmLoadClass=classLoader.loadClass("com.nmbb.TestBActivity"); Constructorconstructor=mLoadClass.getConstructor( newClass[]{}); ObjectTestBActivity=constructor.newInstance( newObject[]{}); MethodgetMoney=mLoadClass.getMethod("getMoney", null); getMoney.setAccessible( true); Objectmoney=getMoney.invoke(TestBActivity, null); Toast.makeText( this,money.toString(),Toast.LENGTH_LONG).show(); } catch(ClassNotFoundExceptione){ e.printStackTrace(); } catch(SecurityExceptione){ e.printStackTrace(); } catch(NoSuchMethodExceptione){ e.printStackTrace(); } catch(IllegalArgumentExceptione){ e.printStackTrace(); } catch(InstantiationExceptione){ e.printStackTrace(); } catch(IllegalAccessExceptione){ e.printStackTrace(); } catch(InvocationTargetExceptione){ e.printStackTrace(); } } } 执行的时候可以发现会自动生成TestB.dex文件。动态加载方面还可以搜索一下"Java动态加载"方面的资料,很有参考价值。可以发现比Android动态加载jar/dex使用起来方便得多。 四、下载 TestA.zip TestB.zip 五、注意 6.1 别忘了加上SDCARD的写权限: android.permission.WRITE_EXTERNAL_STORAGE 6.2 同样注意,不要再两个工程包含package和名称相同的接口,否则报错。(参见Android动态加载jar/dex的后期维护) 六、扩展阅读 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 (强烈推荐:QQ游戏动态调用Activity的方法:通过ClassLoader,loadClass Activity类,然后分别在主工程的onDestroy、onKeyDown、onPause、onRestart、onResume等生命周期方法中反射调用(Method、invoke)子工程的类方法来模拟实现整个生命周期。此外巧妙的通过解压缩APK文件来获取游戏的资源) Android中文Wiki:DexFile 七、缺点 6.1 由于是使用反射,无法取得Context,也就是TestBActivity与普通的类毫无区别,没有生命周期。 八、推荐 Android版 程序员专用搜索 结束 忙着换工作、忙着把翻译组转动Wiki平台、忙着推广翻译组...忙这忙那的,博客已经有近2个月没有写Android方面的文章,终于又可以回来,继续挤时间来写博客,与大家一同探讨与分享。 本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/844928,如需转载请自行联系原作者

优秀的个人博客,低调大师

在Android设备上提高工作效率和安全性的10招

或许阁下刚刚买入第一款安卓设备,或许阁下使用安卓设备已经有一段时间了但却觉得可能没有最有效地利用它。无论是那种情况,阁下今天行好运了:利用各种调整、应用程序、选项和配置等方法可以使得安卓设备更加强大和有效。下面列出10个笔者最喜欢的技巧,以期帮助阁下在安卓平台上获得最大的好处。 1.使用谷歌 关于安卓的第一件要知道的事是,安卓是与谷歌紧密地整合在一起的。如果不利用二者之间这种整合的优势,就白白错过好东西。笔者说的不仅仅是谷歌网盘、日历、邮件、照片等等,还有搜索。“谷歌Now”时下是安卓里与之最紧密整合的工具之一。如果对其没有最佳地加以利用,阁下就可能真是在对安卓行瞎子摸象之道了。设好谷歌Now的热词(可以吼一声“Okay Google”?),接着就可以体会强大的安卓数字助理所拥有的各种细节了。 2.设置自动备份 安卓设备一般都是和谷歌帐户关联在一起的。用安卓设备可以收电邮、处理谷歌文档。而设备上的所有信息待在那……等的是丢了设备的那一天,或者不小心设备掉马路上了,正好一辆车飙过,设备被碾成碎片,随之而来的是钻心的痛苦。没错,没有买保险的话,阁下大不了花大钱再买一款。但是,那上面的数据可是打水漂了。如果想避免这样的悲剧,就一定要进入设备的备份和重置功能里设置备份。安卓可以备份应用程序数据、Wi-Fi密码、其他谷歌服务器的设置、照片、联系人等。 3.安装Tasker程序 如果一定要选一款彻底改变安卓体验的程序,那这款程序就是Tasker。Tasker程序可以将设备最大限度地设成自动模式。Tasker可以基于时间、地点、事件设置自动执行对应的任务。笔者不是那种常常说“在安卓平台里再也找不到比这更强大的好工具”的人,然而,这样的工具还真的就在眼下。Tasker不是免费的,但相对于2.99美元的入门价格,该应用程序为阁下的设备提供的东西是大大地物有所值。不信可以看看26000多个用户的5星级评价。Tasker确实是款5星的软件。 4.安装第三方主屏启动程序 别误会:原始(Stock)安卓自带的主屏启动程序也不错。问题是,很少几款设备用的是真正意义上的原始安卓自带的主屏启动。如果阁下不喜欢现在的主屏互动,那就安装笔者最喜欢的Nova Launcher吧。Nova提供的外观、感觉和功能组合堪称完美。Nova提供手势支持和清净的界面,各方面都好用。但如果Nova不是您的菜,坊间还有很多其他的主屏幕启动程序,总有一款会合阁下的口味的。 5.升级 如果您运行的是过时的安卓版本,请务必定期检查升级。目前,安卓的最新版本是5.1。该版本和老版本比起来简直是天壤之别。随着一次又一次的更新,安卓也越来越好。但唯一的缺陷是,有些设备是不能升级到最新版本的。如果您的安卓智能手机或平板电脑用的是过时版本,而且合同也快结束了,那就到了升级手机的时候了。如果您想最大限度地利用安卓,就要持续最新。 6.安装文件管理器 有时候您需要找一个本地文件(例如下载的文件)。这时候最好的办法就是利用文件管理器。有些设备(如老一点的三星设备)里含一个名为“我的文件”(My Files)的应用程序。“我的文件”尽管凑合能用,但功能远不如诸如Astro File Manager的文件管理器。现代移动文件管理器还涵括大量花里胡哨的东西(如SMB或蓝牙插件),但大多数情况下,用户只需在安卓目录结构里找一个文件而已。这时,如果设备安装了一个文件管理器就会省不少心。 7.设置锁屏密码保护 设置锁屏密码保护虽然不会增加功能或使得设备更强大,但这样做可以保护您的数据。设好了密码、PIN或图案锁定屏幕,可以增加有心人窥看您的数据的难度。没说的。无可争辩。没错,这样做会造成不方便。没错,现在又多了一步才能上Facebook或自拍。但是,阁下的数据最终会诚心感谢阁下。再说一遍:无可争辩。如果在家里或是在工作场所老是要输入密码太烦了,可以设置可信任地点(前提是设备是运行Lollipop的)。如此,当您处于可信任地点500英尺的范围内就无需输入密码/PIN/图案。 8.设置二次验证 接上一个话题……还是安全。皆因移动数据有不少弱点。但我们也无需在设备遗失时交出密钥俯首称臣。有密码保护的主屏有助于防止别人访问您的设备。再多走一步就可以防止别人访问令设备更强大的平台账号:谷歌。如果设置了二次验证,要进入您的谷歌帐户的唯一方法是,使用您的帐号密码和一个由谷歌身份验证器应用程序随机生成的四位数代码 。即刻设置二次验证。现在就设。 9.利用新的Gmail 如果您已经升级成Lollipop了,那就最好习惯一下Gmail应用程序,因为谷歌已经搁置原始Gmail电子邮件工具。不过没关系。Gmail的发展历史已经够长够成熟的了,给它什么东西(甚至Exchange)都能够处理。最妙的是,Gmail并没有想成为接管所有电邮的通用电子邮箱。用户现在反而只需(从屏幕左面开始)右扫屏幕就可以轻松地调出侧边栏在账户之间切换。点选在侧边栏顶部的帐户图像就可以在帐户之间进行切换。 10.切换到谷歌Hangouts 现代人花很多时间来来回回发信息。在安卓上,信息可以是手机短信、谷歌Hangouts或别的形式。要想轻松一点,就安装Hangouts应用吧,Hangouts可以将各种短信和谷歌Hangouts整合到一个优秀的工具里。Hangouts不单是一种可以在一个地方接受不同聊天形式的方法,它实际上比默认短信应用程序更优秀一些。至于谷歌为什么还没有踢走标准消息应用程序,我大概永远也搞不清楚。 原文发布时间为:2015-7-14 本文作者:孙博 本文来自云栖社区合作伙伴至顶网,了解相关信息可以关注至顶网

优秀的个人博客,低调大师

Java架构师与开发者提高效率的10个工具

Java受到全球百万计开发者的追捧,已经演变为一门出色的编程语言。最终,这门语言随着技术的变化,不断的被改善以迎合变化的市场需求。 无论你是否拥有一家科技公司,软件已经成为几乎每一个企业不可或缺的一部分,为了吸引你潜在的顾客,你应该交付给客户一个技术上有创新的产品。那么,Java能提供这样的平台帮你实现这一技术创新。Java贡献者们一直保持着大幅度的更新,以提供最新最强大的功能。 最近发布的Java8完美的诠释了高效和创新的特性,也为那些想要在创新之路上取得成功的企业铺平了道路。然后,合适的完成工作,一些高效的工具是必不可少的。幸运的是有足够丰富的工具来支持Java平台。这些工具使得开发过程更加的简单,快捷和高效。通过应用一些完美的工具,开发人员可以生成一个更优美而健壮的代码。抛开烦恼,让我们一窥众多工具中,那些每个Java开发人员都应该知道的工具。 让你变得更加高效的一些Java工具和类库 Clover Clover是一个很赞的工具,它能帮助测试优化,进一步优化代码。它能够在一些持续集成的系统上或者IDE上运行。它认为那些最容易受编码错误影响的东西非常重要。因此,在测试中会更多的关注那些。它不会让最近的一些代码调整而影响到测试速度,还能在测试完代码后快速的给出反馈。 Jar Jar Links: 你 可以已经注意到,同一个产品不同的版本之间,包或者命名空间出现冲突是一种很常见的情况。Jar Jar Links可以避免这种情况,再次创建时会帮助你选择一个合适的包名。这是一个解决依赖问题的理想方案。这个工具和Minijar Maven 插件比较类似,都是解决同样的问题,但是处理方法不一样。 Guava Guava 提供了许多Google核心库,为Java项目提供了很多便利的方法,像并发库、原语支持、集合操作、字符串处理、缓存等等许多方面。 Bintray 当匿名的从仓库导入一些类库,开发人员可能没有提供详细的信息或是文档。不过,通过Bintray这个社交平台,大家可以查看源代码或者在几个开发人员之间共享出来。它的仓库中收录了超过85000个包。你可以轻松的从中找到需要的类库。 JEXL JEXL主要是一种方便嵌入的表达式语言。不要困惑,想简单点,它就是一个库,让一些脚本,Java框架和应用程序动态执行的库。它能帮助企业平台的技术顾问查看一些隐藏的其他脚本功能,并在需要时能自己构建并实现它们。 IntelliJ 由 JetBrains公司开发,IntelliJ 是一个十分智能的Java IDE,提供了一个非常好用的工具集,以确保在最短时间内生产更好更高效的代码。它提供了一个直观友好的界面、运行流畅、稳定的性能。它和Eclipse 非常的相似,可以选择旗舰版或者社区版来使用。旗舰版提供了商业化的IDE功能和完整的工具集并集成了最新的框架。不过,社区版基本上是一个免费开源的版 本,便于Java、Scala和其他的一些语言编程。 Takipi 这个工具主要是用来确定并通知代码中断的原因。它基本上涵盖了所有的新错误,异常还有一些有代表的错误原因。它会将错误和原因摘要直接通知给开发者,便于他们能够以最优的方案迅速的解决错误。 该工具有以下功能: 检测并提示捕捉到的http错误和异常。 分析不同应用程序和机器真正的代码和变量状态。 还能确定一片代码发生错误的次数,并比较发生错误的频率是否增加。 Librato Librato 是一个托管服务,帮助控制和监测云应用程序。只需要几秒钟,就能轻易的配置一个自定义的监控仪表盘。纯语言绑定使用Clojure、Java等语言。支持集成AWS、Heroku等众多云平台来进行数据收集。当监控的指标超过了定义的阀值,会自动生成报警通知。Librato可以完美的表现任何东西,知道如何将数据变有意义。 FindBugs 此工具通过静态分析帮助定位Java程序错误。FindBugs是免费的。可以使用GUI界面,也可以作为NetBeans、IntelliJ、Eclipse等众多IDE的插件。 Plumbr Plumbr旨在解决实时的性能问题。它能完美的检测内存泄露、低效的垃圾收集、还有线程锁等Java应用程序问题。使用它,能确保项目的顺利进行和最好的性能。你只需要将工具加到你的程序上,如果发现任何缺陷的话会有提示。它提供了内存泄漏、泄漏率、发生在代码的实际位置等所有细节信息。所以它能够提供完美的错误信息,帮助你修复代码。 上述工具旨在辅助Java开发,帮助开发人员简单高效的完成任务。最新版本的Java8和这些强大的工具也加强了对软件业务现代化的支持。 作者:佚名 来源:51CTO

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册