-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 93.5 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 93.5 KB
1
{"meta":{"title":"心情杂谈","subtitle":null,"description":null,"author":"Angle","url":"https://yangle94.github.io"},"posts":[{"title":"关于String的一个小问题的内存分析","slug":"String","date":"2018-02-28T05:48:26.000Z","updated":"2018-02-28T06:11:09.598Z","comments":true,"path":"2018/02/28/String/","link":"","permalink":"https://yangle94.github.io/2018/02/28/String/","excerpt":"今天遇到了一个很有意思的关于String的问题,\n123456789101112String a = "hello2";final String b = "hello";String d = "hello";String c = b + 2;String e = d + 2;System.out.println("c:": c);System.out.println("e:": e);System.out.println(a == c);System.out.println(a == e);","text":"今天遇到了一个很有意思的关于String的问题, 123456789101112String a = "hello2";final String b = "hello";String d = "hello";String c = b + 2;String e = d + 2;System.out.println("c:": c);System.out.println("e:": e);System.out.println(a == c);System.out.println(a == e); 首先,因为编译器在编译的时候发现,String b是一个常量(final修饰,当然,虚拟机内部是没有final这个修饰符的,会在编译期将代码中的final擦除),相对应的再生成c的时候会直接计算结果,然后将结果(“hello2”)赋值给c,所以,如果反编译(我这里直接使用idea打开生成的class文件)会发现,生成的代码如下: 1234567String a = "hello2";String b = "hello";String d = "hello";String c = "hello2";String e = d + 2; 123456789StringBuilder @Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); } 当然,使用javap反编译后能够看到,String e = d + 2;这里在class文件中,会使用StringBuilder来负责将d与2进行相加(append()),最后,会调用toString方法,然后将结果赋给e。所以,a与c全部指向常量池的“hello2”,而由于StringBuilder的toString方法会在堆中new一个新的String对象,所以a 与 e是不相等的(内存地址不同)。","raw":null,"content":null,"categories":[],"tags":[{"name":"String","slug":"String","permalink":"https://yangle94.github.io/tags/String/"}]},{"title":"random、ThreadLocalRandom、SecureRandom","slug":"random","date":"2018-02-27T08:06:38.000Z","updated":"2018-02-27T14:04:09.565Z","comments":true,"path":"2018/02/27/random/","link":"","permalink":"https://yangle94.github.io/2018/02/27/random/","excerpt":"RandomRandom可以算是我们常用的生成随机数的类,比如\n1234Random random = new Random();int a = random.nextInt(5);\n这样会生成一个0~4之间的数字。","text":"RandomRandom可以算是我们常用的生成随机数的类,比如 1234Random random = new Random();int a = random.nextInt(5); 这样会生成一个0~4之间的数字。 Random构造函数:1234567891011121314151617181920212223242526272829303132333435//无参public Random() { this(seedUniquifier() ^ System.nanoTime());}private static long seedUniquifier() { // L'Ecuyer, "Tables of Linear Congruential Generators of // Different Sizes and Good Lattice Structure", 1999 for (;;) { long current = seedUniquifier.get(); long next = current * 181783497276652981L; if (seedUniquifier.compareAndSet(current, next)) return next; }}private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);//有参public Random(long seed) { if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); else { // subclass might have overriden setSeed this.seed = new AtomicLong(); setSeed(seed); }}private static long initialScramble(long seed) { return (seed ^ multiplier) & mask;} 构造方法主要的作用是生成一个AtomicLong类型的seed,作为实例属性。 nextInt()1234567891011121314151617181920212223242526272829303132public int nextInt() { return next(32);}public int nextInt(int bound) { if (bound <= 0) throw new IllegalArgumentException(BadBound); int r = next(31); int m = bound - 1; if ((bound & m) == 0) // i.e., bound is a power of 2 r = (int)((bound * (long)r) >> 31); else { for (int u = r; u - (r = u % bound) + m < 0; u = next(31)) ; } return r;}protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits));} 我们主要看next()方法,nextseed的计算方式是固定的((oldseed * multiplier + addend) & mask),所以next的返回结果是可计算的,计算得到nextseed后,使用compareAndSet方法将nextseed赋值到seed属性中(cas操作),如果成功,则返回(int)(nextseed >>> (48 - bits)),否则,重新获得oldseed并进行计算。所以,next方法返回值是可预测、有迹可循的,生成的结果是伪随机数。 12345678Random random = new Random(1);int a = random.nextInt(5);random = new Random(1);int b = random.nextInt(5);random = new Random(1);int c = random.nextInt(5); 可以判断,以上代码中 a = b = c。 所以我们一定不能把这个种子写死,用当前时间毫秒数,还是比较好些。另外,重复new Random对象的意义也并不是那么大。 最后说一点,Random是线程安全的,去这里的官方文档可以看到,“Instances of java.util.Random are threadsafe.”。但是在多线程的表现中,他的性能很差。 ThreadLocalRandom12345678910111213141516171819public int nextInt() { return mix32(nextSeed());}final long nextSeed() { Thread t; long r; // read and update per-thread seed UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r;}private static int mix32(long z) { z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);}private static final long GAMMA = 0x9e3779b97f4a7c15L; 在查看ThreadLocalRandom类,nextInt方法主要是查找了当前线程中threadLocalRandomSeed属性的值,每次增加一个GAMMA,再将值重新放入线程中,然后调用mix32进行处理。由于ThreadLocalRandom并未大量使用cas,所以ThreadLocalRandom比Random更快。 SecureRandom在需要频繁生成随机数,或者安全要求较高的时候,不要使用Random,因为Random是可预测的。 This class provides a cryptographically strong random number generator (RNG).SecureRandom 提供加密的强随机数生成器 (RNG),要求种子必须是不可预知的,产生非确定性输出。SecureRandom 也提供了与实现无关的算法,因此,调用方(应用程序代码)会请求特定的 RNG 算法并将它传回到该算法的 SecureRandom 对象中。 如果仅指定算法名称,如下所示: 123SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 如果既指定了算法名称又指定了包提供程序,如下所示: 123SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN"); 使用: 12345678SecureRandom random1 = SecureRandom.getInstance("SHA1PRNG");SecureRandom random2 = SecureRandom.getInstance("SHA1PRNG");for (int i = 0; i < 5; i++) { System.out.println(random1.nextInt() + " != " + random2.nextInt());}","raw":null,"content":null,"categories":[],"tags":[{"name":"JDK8","slug":"JDK8","permalink":"https://yangle94.github.io/tags/JDK8/"}]},{"title":"ConcurrentHashMap(JDK8)","slug":"jdk8-ConcurrentHashMap","date":"2018-02-26T08:28:54.000Z","updated":"2018-03-01T14:34:02.669Z","comments":true,"path":"2018/02/26/jdk8-ConcurrentHashMap/","link":"","permalink":"https://yangle94.github.io/2018/02/26/jdk8-ConcurrentHashMap/","excerpt":"静态常量123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** * table数组的最大长度 */private static final int MAXIMUM_CAPACITY = 1 << 30;/** * 默认table数组长度 */private static final int DEFAULT_CAPACITY = 16;/** * 最大数组长度,toArray以及相关的方法会用到 */static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * 此表的默认并发级别。JDK8中未使用,但是为了兼容原来的版本,加入了该属性 */private static final int DEFAULT_CONCURRENCY_LEVEL = 16;/** * 负载因子。修改这个值仅仅会影响初始容量,实际上不被使用,而是使用了n-n>>2这样的表达式 */private static final float LOAD_FACTOR = 0.75f;/** * 链表转换为红黑树的阈值,当链表大于等于该值时,会将链表转换为红黑树。该值必须大于2, * 并且应该至少有8个 */static final int TREEIFY_THRESHOLD = 8;/** * 当小于这个值的时候,会将红黑树转换为链表 */static final int UNTREEIFY_THRESHOLD = 6;/** * 这个值代表了当进行链表转红黑树的时候,如果table数组的长度小于此值,会进行扩容操作, * 而不是进行红黑树转换工作。通过扩容table数组,减少某个桶内的链表长度,来减少时间。 * 值应该至少为4 * TREEIFY_THRESHOLD, * 以避免在调整大小和设置treeification阈值之间发生冲突。 */static final int MIN_TREEIFY_CAPACITY = 64;/** * 扩容时单核获得的最低桶数大小 */private static final int MIN_TRANSFER_STRIDE = 16;/** * 在sizeCtl中用于生成戳记的比特数。32位数组必须至少有6个 */private static int RESIZE_STAMP_BITS = 16;/** */private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;/** */private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;","text":"静态常量123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** * table数组的最大长度 */private static final int MAXIMUM_CAPACITY = 1 << 30;/** * 默认table数组长度 */private static final int DEFAULT_CAPACITY = 16;/** * 最大数组长度,toArray以及相关的方法会用到 */static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * 此表的默认并发级别。JDK8中未使用,但是为了兼容原来的版本,加入了该属性 */private static final int DEFAULT_CONCURRENCY_LEVEL = 16;/** * 负载因子。修改这个值仅仅会影响初始容量,实际上不被使用,而是使用了n-n>>2这样的表达式 */private static final float LOAD_FACTOR = 0.75f;/** * 链表转换为红黑树的阈值,当链表大于等于该值时,会将链表转换为红黑树。该值必须大于2, * 并且应该至少有8个 */static final int TREEIFY_THRESHOLD = 8;/** * 当小于这个值的时候,会将红黑树转换为链表 */static final int UNTREEIFY_THRESHOLD = 6;/** * 这个值代表了当进行链表转红黑树的时候,如果table数组的长度小于此值,会进行扩容操作, * 而不是进行红黑树转换工作。通过扩容table数组,减少某个桶内的链表长度,来减少时间。 * 值应该至少为4 * TREEIFY_THRESHOLD, * 以避免在调整大小和设置treeification阈值之间发生冲突。 */static final int MIN_TREEIFY_CAPACITY = 64;/** * 扩容时单核获得的最低桶数大小 */private static final int MIN_TRANSFER_STRIDE = 16;/** * 在sizeCtl中用于生成戳记的比特数。32位数组必须至少有6个 */private static int RESIZE_STAMP_BITS = 16;/** */private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;/** */private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; Node123456789101112131415161718192021222324252627282930313233343536373839404142434445464748static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; Node(int hash, K key, V val, Node<K,V> next) { this.hash = hash; this.key = key; this.val = val; this.next = next; } public final K getKey() { return key; } public final V getValue() { return val; } public final int hashCode() { return key.hashCode() ^ val.hashCode(); } public final String toString(){ return key + "=" + val; } public final V setValue(V value) { throw new UnsupportedOperationException(); } public final boolean equals(Object o) { Object k, v, u; Map.Entry<?,?> e; return ((o instanceof Map.Entry) && (k = (e = (Map.Entry<?,?>)o).getKey()) != null && (v = e.getValue()) != null && (k == key || k.equals(key)) && (v == (u = val) || v.equals(u))); } /** * Virtualized support for map.get(); overridden in subclasses. */ Node<K,V> find(int h, Object k) { Node<K,V> e = this; if (k != null) { do { K ek; if (e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; } while ((e = e.next) != null); } return null; } } 可以看到,Node类的val跟next属性全部都由关键字volatile修饰,代表获得的值总会是最新的值。其中,setValue()会直接抛出UnsupportedOperationException异常,所以不能通过setValue()修改value的值。find()用于遍历Node数组,找出符合的Node对象。 cas操作方法123456789101112131415161718@SuppressWarnings("unchecked")static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { //获取obj对象中offset偏移地址对应的object型field的值 return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);}static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) { //在obj的offset位置比较object field和期望的值,如果相同则更新。这个方法的操作应该是原子的,因此提供了一种不可中断的方式更新object field。 return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);}static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { //设置obj对象中offset偏移地址对应的object型field的值为指定值。 U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);} TreeNode1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950static final class TreeNode<K,V> extends Node<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next, TreeNode<K,V> parent) { super(hash, key, val, next); this.parent = parent; } Node<K,V> find(int h, Object k) { return findTreeNode(h, k, null); } //查找hash为h,key为k的节点 final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) { if (k != null) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> q; TreeNode<K,V> pl = p.left, pr = p.right; if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; else if ((pk = p.key) == k || (pk != null && k.equals(pk))) return p; else if (pl == null) p = pr; else if (pr == null) p = pl; else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) p = (dir < 0) ? pl : pr; else if ((q = pr.findTreeNode(h, k, kc)) != null) return q; else p = pl; } while (p != null); } return null; } } 当链表超过指定长度的时候,会将其转换为红黑树,所使用的节点包装类就是TreeBin类,但是,需要注意的是,其并不是直接放在table数组中,而是放在TreeBin对象中,再将TreeBin对象放入table数组。 TreeBin123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657static final class TreeBin<K,V> extends Node<K,V> { TreeNode<K, V> root; volatile TreeNode<K, V> first; volatile Thread waiter; volatile int lockState; static final int WRITER = 1; // set while holding write lock static final int WAITER = 2; // set when waiting for write lock static final int READER = 4; // increment value for setting read lock TreeBin(TreeNode<K, V> b) { super(TREEBIN, null, null, null); this.first = b; TreeNode<K, V> r = null; for (TreeNode<K, V> x = b, next; x != null; x = next) { next = (TreeNode<K, V>) x.next; x.left = x.right = null; if (r == null) { x.parent = null; x.red = false; r = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K, V> p = r; ; ) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K, V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; r = balanceInsertion(r, x); break; } } } } this.root = r; assert checkInvariants(root); } ……省略其他方法 } 可以看到,TreeBin的构造方法其实就是创建了一个红黑树,其中的节点为TreeNode。 ForwardingNode1234567891011121314151617181920212223242526272829303132333435static final class ForwardingNode<K,V> extends Node<K,V> { final Node<K,V>[] nextTable; ForwardingNode(Node<K,V>[] tab) { super(MOVED, null, null, null); this.nextTable = tab; } Node<K,V> find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node<K,V>[] tab = nextTable;;) { Node<K,V> e; int n; if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; for (;;) { int eh; K ek; if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; if (eh < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode<K,V>)e).nextTable; continue outer; } else return e.find(h, k); } if ((e = e.next) == null) return null; } } } } 此类为一个辅助类,仅仅作用于ConcurrentHashMap扩容操作时。只是一个标志节点,并且指向nextTable,它提供find方法。该类也是继承Node节点,其hash为-1,key、value、next均为null。 实例属性123456789101112131415161718192021222324252627282930313233343536373839404142434445/** * 桶数组,长度总为2的n次方,进行第一次操作时进行初始化。 */transient volatile Node<K,V>[] table;/** * 使用的下一个table数组,仅仅在调整大小的时候使用。 */private transient volatile Node<K,V>[] nextTable;/** * ConcurrentHashMap中元素个数,但返回的不一定是当前Map的真实元素个数。基于CAS无锁更 * 新 */private transient volatile long baseCount;/** * volatile修饰的int类型,是其他线程可见的。 * 表初始化和调整大小控制。 * 当负值时,table数组被初始化或调整大小:-1用于初始化,其它的 -(1 +主动调整大小的线程数)。 * 否则,当table数组为空时,保存初始表大小以在创建时使用,或默认为0。初始化后,保存下一个元素count值,以调整表的大小。 */private transient volatile int sizeCtl;/** * 记录下一个切分tables数组的索引 */private transient volatile int transferIndex;/** * 自旋锁(通过CAS锁定)在调整和/或创建反单元时使用 */private transient volatile int cellsBusy;/** * 计数器数组。当非空值时,大小是2的幂 */private transient volatile CounterCell[] counterCells;// 显示用private transient KeySetView<K,V> keySet;private transient ValuesView<K,V> values;private transient EntrySetView<K,V> entrySet; put()、putIfAbsent()12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091public V put(K key, V value) { return putVal(key, value, false); } public V putIfAbsent(K key, V value) { return putVal(key, value, true); } final V putVal(K key, V value, boolean onlyIfAbsent) { //不允许key或者value为null if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; //循环进行,如果赋值成功会break退出 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //如果tab未进行初始化,则进行初始化 if (tab == null || (n = tab.length) == 0) tab = initTable(); //使用tabAt(),查找出改key的hash在table数组 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //使用cas的方式向table数组进行赋值,成功则退出,失败则进入下次循环 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //如果头结点的hash为-1,说明正在扩容,则进入帮助进行扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { //这里代表当前要插入的值发生撞库,需要对链表或者红黑树进行操作 V oldVal = null; //对f加锁 synchronized (f) { //先判断当前头节点是不是还是此f节点,如果不是,说明其它线程已经更改了结构,重新进行for循环尝试。 if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { //如果key相同并且允许覆盖,则进行覆盖 K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } //e = e.next,如果最后一个都不是,则在尾部增加一个 Node<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //如果f是treebin类型 else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } //如果链表长度不为0并且大于等于TREEIFY_THRESHOLD,则将链表转换为红黑树 if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } //size增加1 addCount(1L, binCount); return null; } 按照上面的源码,我们可以确定put整个流程如下: 判空;ConcurrentHashMap的key、value都不允许为null 计算hash。利用spread()计算hash值。 12345static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS;} 遍历table,进行节点插入操作,过程如下: 如果table为空,则表示ConcurrentHashMap未初始化,进行初始化操作:initTable() 根据hash值获取节点的位置i,若该位置为空,则直接插入(不需要加锁)。计算f位置:i=(n – 1) & hash 如果检测到fh = f.hash == -1,则f是ForwardingNode节点,表示有其他线程正在进行扩容操作,则帮助线程一起进行扩容操作 如果f.hash >= 0 表示是链表结构,则遍历链表,如果存在当前key节点则替换value,否则插入到链表尾部。如果f是TreeBin类型节点,则按照红黑树的方法更新或者增加节点 若链表长度 > TREEIFY_THRESHOLD(默认是8),则将链表转换为红黑树结构 调用addCount方法,ConcurrentHashMap的size + 1 初始化table数组1234567891011121314151617181920212223242526272829303132333435/** * Initializes table, using the size recorded in sizeCtl. */private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; //循环判断table数组是否初始化 while ((tab = table) == null || tab.length == 0) { //如果sizeCtl<0(上部分说过,sizeCtl<0代表已经有线程在进行扩容操作) if ((sc = sizeCtl) < 0) //让出cpu时间 Thread.yield(); // lost initialization race; just spin //通过cas操作,使一个线程进行进入 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //重新判断tab是否已经初始化,防止极端情况。 if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; //sc为n的0.75 sc = n - (n >>> 2); } } finally { //给sizeCtl设置下一次的阈值(这里用的是直接复制,而没用cas) sizeCtl = sc; } break; } } return tab;} helpTransfer协助转移元素123456789101112131415161718192021222324/** * Helps transfer if a resize is in progress. */final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { Node<K,V>[] nextTab; int sc; if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length); while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { transfer(tab, nextTab); break; } } return nextTab; } return table;} transfer() 转移方法123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174/** * Moves and/or copies the nodes in each bin to new table. See * above for explanation. */private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; //计算每个线程一次性处理的table数组的长度 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range //判断nextTab是否有值,有值则说明已经有线程完成了nextTable的初始化,当然,这里不能排除多个线程同时进入的并发情况(单就方法内部来说),我查看了transfer的所有调用位置,全部都放在一个cas操作下进行,保证了这个nextTab == null只可能有一个线程进行。 if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; transferIndex = n; } //扩容后table的长度 int nextn = nextTab.length; //创建ForwardingNode ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); //是否推进 boolean advance = true; //提交nextTable之前确保正确 boolean finishing = false; // to ensure sweep before committing nextTab //i代表当前线程所迁移桶的索引(从大到小逆序);bound表示已经分配到当前线程的桶的最小的索引 for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; //更新待迁移的hash桶索引 while (advance) { int nextIndex, nextBound; //--i用来更新桶的索引, if (--i >= bound || finishing) advance = false; //如果未被分配的table数组长度小于等于0 else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } //使用cas,更新transferIndex的值(表示待分配任务的table数组长度) else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { //更新成功,将减去后的table数组长度赋值到bound bound = nextBound; i = nextIndex - 1; advance = false; } } //选择最后一次分配 if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { //最后一个迁移的线程,recheck后,做收尾工作,然后退出 nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { /** 第一个扩容的线程,执行transfer方法之前,会设置 sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) 后续帮其扩容的线程,执行transfer方法之前,会设置 sizeCtl = sizeCtl+1 每一个退出transfer的方法的线程,退出之前,会设置 sizeCtl = sizeCtl-1 那么最后一个线程退出时: 必然有sc == (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2),即 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT */ //不相等,说明不到最后一个线程,直接退出transfer方法 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; //最后退出的线程要重新check下是否全部迁移完毕 i = n; // recheck before commit } } else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { //对桶f进行加锁 synchronized (f) { //判断f是否是当前的桶 if (tabAt(tab, i) == f) { Node<K,V> ln, hn; //fh >= 0代表链表 if (fh >= 0) { //通过fh & n先遍历一次node,获得最后一段hash & n相同的链表,这样分离的时候可以直接使用,不需要再次去new。 int runBit = fh & n; Node<K,V> lastRun = f; for (Node<K,V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } //此段将node列表分为两个链表,算法跟hashmap的分离方法一样。 for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K,V>(ph, pk, pv, ln); else hn = new Node<K,V>(ph, pk, pv, hn); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } //红黑树迁移 else if (f instanceof TreeBin) { TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> lo = null, loTail = null; TreeNode<K,V> hi = null, hiTail = null; int lc = 0, hc = 0; for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V> (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } }} 参考文章ConcurrentHashMap源码分析(JDK8) 扩容实现机制 深入分析ConcurrentHashMap1.8的扩容实现","raw":null,"content":null,"categories":[],"tags":[{"name":"java8","slug":"java8","permalink":"https://yangle94.github.io/tags/java8/"},{"name":"concurrent","slug":"concurrent","permalink":"https://yangle94.github.io/tags/concurrent/"}]},{"title":"HashMap(jdk8)","slug":"jdk8-hashMap","date":"2018-02-01T05:19:49.000Z","updated":"2020-12-20T08:04:43.937Z","comments":true,"path":"2018/02/01/jdk8-hashMap/","link":"","permalink":"https://yangle94.github.io/2018/02/01/jdk8-hashMap/","excerpt":"HashMap静态常量12345678910111213141516171819202122232425262728293031323334353637/** * 默认table数组大小,必须是2的倍数 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * table数组长度最大值,为2^30 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 负载因子(默认) */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 标识由链表转换为红黑树的阈值,只有当某一个链表大于等于此值的时候会把这个链表转换为红黑 * 树。这个数必须要大于2,默认值是8,当然也推荐从8开始往上的值。(查看源码后,发现如果 * table数组的长度小于MIN_TREEIFY_CAPACITY的值的时候,仅仅会进行扩容,并不会进行红黑 * 树转换,见HashMap#treeifyBin()方法) */ static final int TREEIFY_THRESHOLD = 8; /** * 标识有红黑树转换为链表的阈值,只有当某个某个红黑树小于等于这个值的时候,会把红黑树转换 * 为链表。具体方法为TreeNode#untreeify() */ static final int UNTREEIFY_THRESHOLD = 6; /** * 限定table长度的最小值,当大于等于这个值的时候,将这个链表转换为红黑树;否则,仅仅进行 * 扩容。方法见HashMap#treeifyBin() */ static final int MIN_TREEIFY_CAPACITY = 64;","text":"HashMap静态常量12345678910111213141516171819202122232425262728293031323334353637/** * 默认table数组大小,必须是2的倍数 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * table数组长度最大值,为2^30 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 负载因子(默认) */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 标识由链表转换为红黑树的阈值,只有当某一个链表大于等于此值的时候会把这个链表转换为红黑 * 树。这个数必须要大于2,默认值是8,当然也推荐从8开始往上的值。(查看源码后,发现如果 * table数组的长度小于MIN_TREEIFY_CAPACITY的值的时候,仅仅会进行扩容,并不会进行红黑 * 树转换,见HashMap#treeifyBin()方法) */ static final int TREEIFY_THRESHOLD = 8; /** * 标识有红黑树转换为链表的阈值,只有当某个某个红黑树小于等于这个值的时候,会把红黑树转换 * 为链表。具体方法为TreeNode#untreeify() */ static final int UNTREEIFY_THRESHOLD = 6; /** * 限定table长度的最小值,当大于等于这个值的时候,将这个链表转换为红黑树;否则,仅仅进行 * 扩容。方法见HashMap#treeifyBin() */ static final int MIN_TREEIFY_CAPACITY = 64; Node<K,V>(HashMap静态内部类)1234567891011121314151617181920212223242526272829303132333435363738394041static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } } Node注意点: Node类是链表状态下每个节点的是实际类型,它实现了Map.Entity接口。每个Node节点有一个next成员变量,指向下一个Node节点的引用。 Node类为静态内部类,作用域为默认级别,即仅有此类本身以及相同包下的类能够访问。 hash与key两个类型均为final,意味着不能改变。 每个Node节点的hashcode方法是由每个节点的key与value的hashcode方法的^,查看Object.hash()方法,发现当key或者value等于null的时候,hash为0;否则,就会调用这个对象的hashcode方法。同时,这意味着equals同时跟key与value有关系。 setValue方法会返回node中的原值。 equals方法中,并不排斥map中key或者value为null。 HashMap静态方法123456static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 此方法当key为null时返回0,否则,就会用key的hash与hash的前16位异或。 1234567891011121314151617181920static Class<?> comparableClassFor(Object x) { if (x instanceof Comparable) { Class<?> c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) // bypass checks return c; if ((ts = c.getGenericInterfaces()) != null) { for (int i = 0; i < ts.length; ++i) { if (((t = ts[i]) instanceof ParameterizedType) && ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c return c; } } } return null; } comparableClassFor方法用于判断x是不是可比较的(是否实现了Comparable接口),如果实现了此接口则返回Comparable接口中的泛型类型,否则返回null。 123456static int compareComparables(Class<?> kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); } 返回k与x的比较结果。如果x为null或者x的class != kc,返回0。 1234567891011121314/** * Returns a power of two size for the given target capacity. */static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;} 这段就厉害了,此方法声明了一个算法,通过位运算,使得获得的数值是输入的两倍。先看n的位移操作部分,如果是0100,第一次位移后就变为了0110,第二次为0111,以此类推,最后的结果为0111,再加一所得结果为1000,扩容为了下一个2的次方。其原理在于在二进制数中,某个数的最高位上肯定是1,将这个1通过位运算以及或运算,依次向后延伸,所得的数肯定为2^n-1,最后+1,得到2^n次方。 再来看第一句话,第一句话保证了如果输入的本来就是2次方,所得结果是其本身。 当然,这个最后结果最大值为2^30。 HashMap的实例字段1234567891011121314151617181920212223242526272829303132/** * 该字段为每个链表或红黑树的第一个Node组成的数组。此数组在第一次使用的时候进行初始化, * 当分配长度的时候,这个数组的长度总是2^n。 */ transient Node<K,V>[] table; /** * 缓存用于生成entrySet()的对象,此对象将在entrySet()方法第一次调用的时候进行创建。 */ transient Set<Map.Entry<K,V>> entrySet; /** * HashMap的键值对数量 */ transient int size; /** * 此HashMap被修改解构的次数。例如删除、增加等。这个字段用于遍历视图的快速失败。 */ transient int modCount; /** * 进行resize阈值 */ int threshold; /** * 负载因子(final 不可变) */ final float loadFactor; HashMap#put()12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) 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 treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } 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; } HashMap流程图如下: 其中,有两个方法需要注意下: TreeNode#putTreeVal(),此方法用来在红黑树中增加一个值,具体红黑树算法不在这里赘述。 HashMap#treeifyBin(),将链表转换为红黑树,代码如下: 12345678910111213141516171819202122final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } } 由此可见,当talbe为null或者长度小于MIN_TREEIFY_CAPACITY时,仅仅会进行扩容,并不会转换为红黑树。 HashMap#resize()123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } resize()中,对JDK1.7的resize方法进行了改进,JDK1.7中,resize方法会导致新生成的链表中的元素在原链表中的顺序变为相反的顺序(因为每次插入都是在头部进行插入,读取在头部开始读取),而在jdk1.8中则不会,会保留原有顺序(先生成链表,再把头放到talbe数组中)。由于上述原因,jdk1.7中可能会导致node的死循环,从而带来cpu使用率为100%的问题。该问题在1.8中并不存在(死循环问题不存在),但是仍然可能会存在丢失数据的问题,所以并不是线程安全的。 参考文章Java 8系列之重新认识HashMap","raw":null,"content":null,"categories":[],"tags":[{"name":"java8","slug":"java8","permalink":"https://yangle94.github.io/tags/java8/"},{"name":"HashMap","slug":"HashMap","permalink":"https://yangle94.github.io/tags/HashMap/"}]},{"title":"抱歉","slug":"wait","date":"2018-01-31T12:07:33.000Z","updated":"2020-12-20T06:45:42.529Z","comments":true,"path":"2018/01/31/wait/","link":"","permalink":"https://yangle94.github.io/2018/01/31/wait/","excerpt":"","text":"因为最近比较忙,反过来看看,这个文章已经好久没有更新过了。这段时间接触的东西比较多,总算是赶在年前完成了,终于能有时间来整理一下我的心得体会,近段时间会陆续进行更新~~~","raw":null,"content":null,"categories":[],"tags":[]},{"title":"使用docker、nginx、nginx-gen、letsencrypt个人主站升级https","slug":"websiteHttps","date":"2017-11-30T01:25:13.000Z","updated":"2017-11-30T02:41:58.929Z","comments":true,"path":"2017/11/30/websiteHttps/","link":"","permalink":"https://yangle94.github.io/2017/11/30/websiteHttps/","excerpt":"准备工具\ndocker\ndocker-compose\nnginx 镜像\njwilder/docker-gen 镜像\njrcs/letsencrypt-nginx-proxy-companion 镜像\n\n执行操作","text":"准备工具 docker docker-compose nginx 镜像 jwilder/docker-gen 镜像 jrcs/letsencrypt-nginx-proxy-companion 镜像 执行操作 拷贝一份 .env.sample 并改名为.env 123cp .env.sample ./.env 修改.env文件的内容 1234567891011NGINX_WEB=nginx-web #nginx的容器的名字DOCKER_GEN=nginx-gen #nginx-gen容器的名字LETS_ENCRYPT=nginx-letsencrypt #letsencrypt容器的名字IP=0.0.0.0 #公网IP名,(可以不用管,0.0.0.0就好)# Network nameNETWORK=webproxy #docker network的名字# NGINX file pathNGINX_FILES_PATH=/path/to/your/nginx/data #三个容器共享路径 执行 run.sh 123./run.sh 或者 sh run.sh 运行一个测试容器 1234567执行一个http协议访问容器:docker run -d -e VIRTUAL_HOST=your.domain.com \\ --network=webproxy \\ --name my_app \\ httpd:alpine 12345678910执行一个https协议访问容器:docker run -d -e VIRTUAL_HOST=your.domain.com \\ -e LETSENCRYPT_HOST=your.domain.com \\ -e LETSENCRYPT_EMAIL=your.email@your.domain.com \\ -e VIRTUAL_PORT=3000 #设置监听端口 --network=webproxy \\ --name my_app \\ httpd:alpine 此处,请务必把所有需要https代理的容器跟 nginx的三个容器放入同一个network中!原理1.jwilder/docker-gen镜像用来反向生成nginx配置文件,jrcs/letsencrypt-nginx-proxy-companion镜像用来根据生成的配置文件(域名)申请https证书,会自动进行检测,如果过期,自动申请新的https证书。 2.如果要对新的docker镜像使用https,请务必带上LETSENCRYPT_HOST、 LETSENCRYPT_HOST、LETSENCRYPT_EMAIL这三个属性。","raw":null,"content":null,"categories":[],"tags":[{"name":"Docker、Https、Nginx","slug":"Docker、Https、Nginx","permalink":"https://yangle94.github.io/tags/Docker%E3%80%81Https%E3%80%81Nginx/"}]},{"title":"通过docker创建SonarQube代码质量检测平台","slug":"makeSonarQube","date":"2017-10-27T02:13:08.000Z","updated":"2020-12-20T08:04:42.495Z","comments":true,"path":"2017/10/27/makeSonarQube/","link":"","permalink":"https://yangle94.github.io/2017/10/27/makeSonarQube/","excerpt":"作为一个程序,想要写出更健壮的代码,一个好的代码检测工具是必不可少的。某天突然发现了一个代码检测平台工具SonarQube,想要安装一下,发现有docker版本的,特别来安利一下。\n1. 安装postgresql1docker run --name postgresql -e POSTGRES_USER=sonarqube -e POSTGRES_PASSWORD=sonarqube -d postgres\n2. 安装sonarqube1docker run --name sq --link postgresql -e SONARQUBE_JDBC_URL=jdbc:postgresql://postgresql:5432/sonarqube -e SONARQUBE_JDBC_USERNAME=sonarqube -e SONARQUBE_JDBC_PASSWORD=sonarqube -p 9000:9000 -d sonarqube","text":"作为一个程序,想要写出更健壮的代码,一个好的代码检测工具是必不可少的。某天突然发现了一个代码检测平台工具SonarQube,想要安装一下,发现有docker版本的,特别来安利一下。 1. 安装postgresql1docker run --name postgresql -e POSTGRES_USER=sonarqube -e POSTGRES_PASSWORD=sonarqube -d postgres 2. 安装sonarqube1docker run --name sq --link postgresql -e SONARQUBE_JDBC_URL=jdbc:postgresql://postgresql:5432/sonarqube -e SONARQUBE_JDBC_USERNAME=sonarqube -e SONARQUBE_JDBC_PASSWORD=sonarqube -p 9000:9000 -d sonarqube 3. 平台搭建完成 打开 localhost:9000,进行登录,(初始账户为admin,admin),登录成功后会有教程弹出。 2.汉化: 4 附上链接sonarqube docker hub地址spring4all 教程地址汉化参考","raw":null,"content":null,"categories":[],"tags":[{"name":"Docker、SonarQube","slug":"Docker、SonarQube","permalink":"https://yangle94.github.io/tags/Docker%E3%80%81SonarQube/"}]},{"title":"SpringCloud记录点","slug":"springCloudUtil","date":"2017-09-26T03:32:35.000Z","updated":"2017-09-26T07:25:39.000Z","comments":true,"path":"2017/09/26/springCloudUtil/","link":"","permalink":"https://yangle94.github.io/2017/09/26/springCloudUtil/","excerpt":"忽略网络接口application.yml:\n123456spring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.*\n代码位置:org.springframework.cloud.commons.util.InetUtilsProperties","text":"忽略网络接口application.yml: 123456spring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.* 代码位置:org.springframework.cloud.commons.util.InetUtilsProperties 1234/** * List of Java regex expressions for network addresses that will be * preferred. */private List<String> preferredNetworks = new ArrayList<>(); 这个类是一个一个网络工具类的配置文件属性类,由注释可得,preferredNetworks承载了所有需要进行忽略的网络连接名的正则表达式。查看其使用位置 123456789boolean ignoreInterface(String interfaceName) { for (String regex : this.properties.getIgnoredInterfaces()) { if (interfaceName.matches(regex)) { log.trace("Ignoring interface: " + interfaceName); return true; } } return false; } 查看ignoreInterface所调用的位置: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public InetAddress findFirstNonLoopbackAddress() { InetAddress result = null; try { int lowest = Integer.MAX_VALUE; for (Enumeration<NetworkInterface> nics = NetworkInterface .getNetworkInterfaces(); nics.hasMoreElements();) { NetworkInterface ifc = nics.nextElement(); if (ifc.isUp()) { log.trace("Testing interface: " + ifc.getDisplayName()); if (ifc.getIndex() < lowest || result == null) { lowest = ifc.getIndex(); } else if (result != null) { continue; } // @formatter:off if (!ignoreInterface(ifc.getDisplayName())) { for (Enumeration<InetAddress> addrs = ifc .getInetAddresses(); addrs.hasMoreElements();) { InetAddress address = addrs.nextElement(); if (address instanceof Inet4Address && !address.isLoopbackAddress() && !ignoreAddress(address)) { log.trace("Found non-loopback interface: " + ifc.getDisplayName()); result = address; } } } // @formatter:on } } } catch (IOException ex) { log.error("Cannot get first non-loopback address", ex); } if (result != null) { return result; } try { return InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.warn("Unable to retrieve localhost"); } return null; } SpringCloud进行注册的时候会调用本地方法获得当前宿主机的网络信息,在*nix下(包括Mac),类似于ifconfig命令所显示的内容,会遍历其中的内容,所有网卡下到上,然后分别每个网卡内部的地址,保留最后一个非回环地址。 我在当某台Linux机器(宿主机)安装了docker的时候,会安装一个docker0的网桥,docker0的网桥会在宿主机的第一个,导致在宿主机上运行的springboot(dubbo也会)会拿到错误的IP信息,当把docker0网卡加入到这个里头之后,就会避免这个问题。 强制使用正则表达式中的地址以及仅使用站点本地地址123456spring: cloud: inetutils: preferredNetworks: - 192.168 - 10.0 此配置同样位于org.springframework.cloud.commons.util.InetUtilsProperties 12345/** * Use only interfaces with site local addresses. See {@link InetAddress#isSiteLocalAddress()} for more details. */private boolean useOnlySiteLocalInterfaces = false; 查看参数使用位置: 123456boolean ignoreAddress(InetAddress address) { if (this.properties.isUseOnlySiteLocalInterfaces() && !address.isSiteLocalAddress()) { log.trace("Ignoring address: " + address.getHostAddress()); return true; } 由此可见,当不设置的时候,useOnlySiteLocalInterfaces默认值为false,不管拿到的是公网地址还是内网地址,直接进行对下面正则的匹配,当正则不匹配并且地址不是以regex开头的,会返回true(注意方法名是ignoreAddress,true表示忽略,false表示不忽略),如果不匹配则返回false;当useOnlySiteLocalInterfaces设置为true时,如果此IP地址不是内网地址,则直接返回true,如果是内网地址,则进行正则匹配。","raw":null,"content":null,"categories":[],"tags":[{"name":"SpringCloud","slug":"SpringCloud","permalink":"https://yangle94.github.io/tags/SpringCloud/"}]},{"title":"Mac下hexo建站","slug":"creatHexo","date":"2017-05-26T08:46:38.000Z","updated":"2020-12-20T06:45:44.509Z","comments":true,"path":"2017/05/26/creatHexo/","link":"","permalink":"https://yangle94.github.io/2017/05/26/creatHexo/","excerpt":"刚开始建站的时候说过写一篇建站教程,由于当时建站以后因为工作上的各种事儿给耽误了,以后就再也没工夫写了,今天特地来补上。\n\n准备材料\nGitHubPages仓库\nNode.js\nhomebrew\nhexo\ngit(由于Mac自带了,就不需要了)\n\n","text":"刚开始建站的时候说过写一篇建站教程,由于当时建站以后因为工作上的各种事儿给耽误了,以后就再也没工夫写了,今天特地来补上。 准备材料 GitHubPages仓库 Node.js homebrew hexo git(由于Mac自带了,就不需要了) github上创建GitHubPages仓库git 官方参考地址: https://pages.github.com 注意: 创建仓库的时候仓库名一定严格按照 git用户名.github.io 来命名 创建仓库完成之后,再去创建一个其他的仓库,用于保存hexo的整个目录,用来当备份使用,名字随便取。 安装 homebrewhomebrew是Mac下的软件管理程序,类似于centos的yum。 homebrew官方网站 下载安装方法、更换国内镜像源等详情请google,目前我在这里仅仅提供部分命令 12brew update 更新brew列表brew search node.js 查询node.js(可以查看最新版本的node.js,然后brew install node7.js) hexo安装hexohexo官方网站 1npm install -g hexo-cli 安装hexo客户端 建站安装完成hexo后,执行建站命令。 123hexo init <你要存放的目录>cd <你要存放的目录>npm install 配置新建完成后,指定文件夹的目录如下: .├── _config.yml├── package.json├── scaffolds├── source| ├── _drafts| └── _posts└── themes 打开_config.yml,此网站的大部分配置都在里面。 参数 描述 title 网站标题 subtitle 网站副标题 description 网站描述 author 您的名字 language 网站使用的语言 timezone 网站时区。Hexo 默认使用您电脑的时区。时区列表。比如说:America/New_York, Japan, 和 UTC 。 git账户地址以及分支 deploy: type: git repository: git@github.com:yangle94/yangle94.github.io.git branch: master 网址:请把你刚才申请的githubpages的地址放到这里的url,例如我的是https://yangle94.github.io, 就把他放到url后。 参数 描述 默认值 url 网址 root 网站根目录 permalink 文章的 永久链接 格式 :year/:month/:day/:title/ permalink_defaults 永久链接中各部分的默认值 切记切记,yml文件有特殊的配置,类似于键值对的 :后一定要有个空格,不然会报错!!! hexo命令简介1234hexo s 开启本地访问服务器hexo d 上传文件hexo g 生成静态文件hexo clean 清理本地静态文件 备份在你刚才创建的目录下,使用git对源码进行备份,防止丢失。 在github上创建一个仓库专门用来存储此处的源代码,具体操作在github上创建仓库的时候会有提示,再此不进行教程了。 希望大家看完以后早日拥有自己的文章😍","raw":null,"content":null,"categories":[],"tags":[{"name":"Mac","slug":"Mac","permalink":"https://yangle94.github.io/tags/Mac/"},{"name":"hexo","slug":"hexo","permalink":"https://yangle94.github.io/tags/hexo/"}]},{"title":"业务操作数据库时的注意事项","slug":"DBPrecautions","date":"2017-04-07T02:32:18.000Z","updated":"2017-06-15T04:47:03.000Z","comments":true,"path":"2017/04/07/DBPrecautions/","link":"","permalink":"https://yangle94.github.io/2017/04/07/DBPrecautions/","excerpt":"一次数据库崩溃引发的思考 前两天,因为写业务,操作数据库,因为sql不当操作,导致了数据库的崩溃,特此将其拿出进行反思和思考。\n起因:由于业务的原因,为了保留历史记录,所以我在删除的时候使用了delete_flag进行逻辑删除,所以,删除命令就变为了\n12SELECT * FROM tableNmae WHERE delete_flag = 0 UPDATE SET delete_flag = 1 FROM tableNmae = ?\n","text":"一次数据库崩溃引发的思考 前两天,因为写业务,操作数据库,因为sql不当操作,导致了数据库的崩溃,特此将其拿出进行反思和思考。 起因:由于业务的原因,为了保留历史记录,所以我在删除的时候使用了delete_flag进行逻辑删除,所以,删除命令就变为了 12SELECT * FROM tableNmae WHERE delete_flag = 0 UPDATE SET delete_flag = 1 FROM tableNmae = ? 分析此时,虽然看似已经实现了程序所需要的功能,其实隐含着很大的问题。当数据库增加、删除频率以及数据库条数很大的时候,此问题会暴露的十分严重,甚至数据库的崩溃。 首先,我想说的是逻辑删除并非不可用,不过要切实配合好insert以及查询,保证数据库中的重复率不会太高。我当时的错误的做法:使用select语句对数据库中的数据进行存在检测,如果不存在就增加一条,即SELECT * FROM tableNmae WHERE delete_flag = 0 ——-》 INSERT INTO tableName () VALUES (),如果有数据,就跳过。删除时,直接调用UPDATE SET delete_flag = 1 FROM tableNmae = ?。此时,如果删除、增加的数量一多,数据库中会存在非常多的已删除数据,并且在逻辑删除操作的时候,并未加入WHERE delete_flag = 0,导致会将所有的已经逻辑删除的数据再次的删除一遍,数据量一大,直接会将数据库拉崩溃,我当时逻辑删除一条数据所占用的记录数基本到了1000条,在高峰时段直接导致了数据库崩溃。 解决之道:针对此有几种解决方法: 最简单的方法:将逻辑删除换为物理删除,这样无论会有多少次的重复加入数据,在累加的过程中只会保证一条未删除数据的存在。 对逻辑删除加入WHERE delete_flag = 0 这个条件,用于减少一次性更新的条数。每次新增前查询数据库中是否存在对应的已经被删除相同数据,如果有,根据业务逻辑则逻辑删除其它未删除的数据,然后将这条数据的删除位标识更改为0,如果没有在进行增加。 总结:对于频繁的数据操作而言,一定要记得加入where条件使udpate语句更新最小的数量!!!!","raw":null,"content":null,"categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"https://yangle94.github.io/tags/Java/"},{"name":"AutoCloseable","slug":"AutoCloseable","permalink":"https://yangle94.github.io/tags/AutoCloseable/"}]},{"title":"swagger-ui注解","slug":"swagger-annotation","date":"2017-03-14T02:30:50.000Z","updated":"2017-06-15T04:46:59.000Z","comments":true,"path":"2017/03/14/swagger-annotation/","link":"","permalink":"https://yangle94.github.io/2017/03/14/swagger-annotation/","excerpt":"今天使用swagger的时候出现了不少一点问题,body传上来的Json字符串在swagger-ui上的形式总是不符合我的意愿,我找了好久才找到了问题。特别来记录一下。\n注解列表@Api用在类上,说明该类的作用\n@ApiOperation用在方法上,说明方法的作用(当方法所需参数为对象时,)\n@ApiImplicitParams、@ApiImplicitParam@ApiImplicitParams({ @ApiImplicitParam})用在@ApiImplicitParams注解中,指定一个请求参数的各个方面paramType:参数放在哪个地方\n\n\nheader–>请求参数的获取:@RequestHeaderquery–>请求参数的获取:@RequestParampath(用于restful接口)–>请求参数的获取:@PathVariablebody(不常用)form(不常用)name:参数名dataType:参数类型required:参数是否必须传value:参数的意思defaultValue:参数的默认值","text":"今天使用swagger的时候出现了不少一点问题,body传上来的Json字符串在swagger-ui上的形式总是不符合我的意愿,我找了好久才找到了问题。特别来记录一下。 注解列表@Api用在类上,说明该类的作用 @ApiOperation用在方法上,说明方法的作用(当方法所需参数为对象时,) @ApiImplicitParams、@ApiImplicitParam@ApiImplicitParams({ @ApiImplicitParam})用在@ApiImplicitParams注解中,指定一个请求参数的各个方面paramType:参数放在哪个地方 header–>请求参数的获取:@RequestHeaderquery–>请求参数的获取:@RequestParampath(用于restful接口)–>请求参数的获取:@PathVariablebody(不常用)form(不常用)name:参数名dataType:参数类型required:参数是否必须传value:参数的意思defaultValue:参数的默认值 @ApiResponses用于表示一组响应 @ApiResponse用在@ApiResponses中,一般用于表达一个错误的响应信息code:数字,例如400message:信息,例如”请求参数没填好”response:抛出异常的类 @ApiModel描述一个Model的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候) @ApiModelProperty描述一个model的属性 注意事项对于@ApiImplicitParams、@ApiImplicitParam、@ApiModel、@ApiModelProperty的使用情景。首先,对于类似application/x-www-form-urlencoded等除了application/json以外的传输方式,其传输内容实质上是K-V的方式进行传递的,此时可以用@ApiImplicitParams、@ApiImplicitParam对所传入的参数进行描述,但是如果使用application/json的方式传输JSON字符串,这种表示方式是不可用的,因为无法准确表示字符串中每个字段所代表的值。此时,就应该使用 1234@ApiOperation(value="使用httpClient爬取页面", notes="使用httpClient爬取页面", produces = "application/json")@ApiImplicitParam(name = "pageInfoDto", value = "用户详细实体user", required = true, dataType = "PageInfoDto", paramType = "body")@RequestMapping("getValue")public Result getValue(@RequestBody PageInfoDto pageInfoDto) {} 这个时候可以使用一个@ApiImplicitParam来描述所传JSON的信息,千万不可以用@ApiImplicitParams,否则swagger-ui的界面上会出现异常的参数形式。此时,可以配合@ApiModel、 @ApiModelProperty在当前参数的实体类上对信息进行描述。","raw":null,"content":null,"categories":[],"tags":[{"name":"swagger-ui","slug":"swagger-ui","permalink":"https://yangle94.github.io/tags/swagger-ui/"}]},{"title":"maven上传文件到私库","slug":"maven-uploadJar","date":"2017-03-08T03:14:14.000Z","updated":"2017-03-08T08:18:25.000Z","comments":true,"path":"2017/03/08/maven-uploadJar/","link":"","permalink":"https://yangle94.github.io/2017/03/08/maven-uploadJar/","excerpt":"","text":"今天因为在服务器端对webp进行格式转换,发现对于webp的jar在maven的仓库里并没有发现。于是只好自己把webp的jar加入了maven私服。将代码进行copy,以备不时之需。12345发布到本地仓库:mvn install:install-file -DgroupId=[groupId] -DartifactId=[artifactId] -Dversion=[version] -Dpackaging=jar -Dfile=[path to file] 发布到Nexus仓库:mvn deploy:deploy-file -DgroupId==[groupId] -DartifactId=[artifactId] -Dversion=[version] -Dpackaging=jar -Dfile=[path to file] -Durl=[url] -DrepositoryId=[id]","raw":null,"content":null,"categories":[],"tags":[{"name":"maven","slug":"maven","permalink":"https://yangle94.github.io/tags/maven/"}]},{"title":"带有资源的try语句(AutoCloseable)","slug":"AutoCloseable-Interface","date":"2017-03-08T01:45:02.000Z","updated":"2017-03-08T08:17:55.000Z","comments":true,"path":"2017/03/08/AutoCloseable-Interface/","link":"","permalink":"https://yangle94.github.io/2017/03/08/AutoCloseable-Interface/","excerpt":"","text":"今天在关闭输入输出流的时候,觉得总是手动去关闭太麻烦,有没有什么简单的方法。于是乎发现了AutoCloseable这个接口。 重申一下为什么要手动关闭输入输出流对于平常的Java对象来说,如果Java的gc机制发现某个对象已经不可到达,就会启动对此对象的内存回收机制。当然,他什么时候能够把对象回收完了,是根据操作系统的调度来决定的,并不是说gc机制一起动,就能立刻回收此对象的内存。但是对于InputStream和OutputStream以及他们的子类来说,如果开启了某个流,就会有操作系统之外的资源依附在某个Java对象上,这就导致gc认为其实活着的,而不是死亡的,所以并不能出发gc机制。 AutoCloseable接口在Java1.7中提供了新的AutoCloseable接口,以前的Closeable接口扩展了AutoCloseable接口,也就是说,所有的实现了Closeable接口的输入输出流全部实现了AutoCloseable接口,就是说所有的输入输出流都能进行带资源的try catch语句。 12345678910public class InputStreamReaderTest { public static void main(String[] args) { try(BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(new File("/home/angle/test.webp")),"UTF8"),1024)){ System.out.println(reader.readLine()); //这里直接读一行 }catch(IOException e){ e.printStackTrace(); } }} 此时,就不需要我们自己手动去关闭输入、输出流了,带资源的try块儿会帮我们把资源管理好。 注:关于带资源的try语句的3个关键点:由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象。 在try代码中声明的资源被隐式声明为fianl。 通过使用分号分隔每个声明可以管理多个资源。","raw":null,"content":null,"categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"https://yangle94.github.io/tags/Java/"},{"name":"AutoCloseable","slug":"AutoCloseable","permalink":"https://yangle94.github.io/tags/AutoCloseable/"}]},{"title":"考虑用静态工厂方法替代构造函数","slug":"staticFactory","date":"2017-03-02T14:04:54.000Z","updated":"2017-06-15T04:47:00.000Z","comments":true,"path":"2017/03/02/staticFactory/","link":"","permalink":"https://yangle94.github.io/2017/03/02/staticFactory/","excerpt":"静态工厂方法优点\n静态工厂方法有名称,根据名称可以做到见文知意。对于构造方法而言,由于方法签名的原因,一个类只可能一种相同的方法签名,当然,可以通过参数顺序来克服这个问题,但是,相同参数容易让人混乱,而且有可能错过了编译器的异常。\n静态工厂方法可以不必每次调用它的时候都创建一个新的对象。\n可以返回原返回类型的任何子类型对象。1234567891011121314151617181920212223242526272829303132333435363738public interface Service{ //do something}public interface Provider{ Service newService();}public class Service { //不允许外部实例化 private Service(){}; private static final Map<String,Provider> providers = new ConcurrentHashMap<>(); public static final String defKey = "<def>"; //注册 public static void registerDefaultProvider(Provider provider) { registerProvider(defKey, provider); } public static void registerDefaultProvider(String name, Provider provider) { providers.put(name, provider); } //获取 public static Service newInstance() { return newInstance(defkey); } public static Service newInstance(String name) { Provider p = providers.get(name); if(null == p) throw new IlleaglArgumentException("No provider registered with name : " + name); return p.newService(); }}","text":"静态工厂方法优点 静态工厂方法有名称,根据名称可以做到见文知意。对于构造方法而言,由于方法签名的原因,一个类只可能一种相同的方法签名,当然,可以通过参数顺序来克服这个问题,但是,相同参数容易让人混乱,而且有可能错过了编译器的异常。 静态工厂方法可以不必每次调用它的时候都创建一个新的对象。 可以返回原返回类型的任何子类型对象。1234567891011121314151617181920212223242526272829303132333435363738public interface Service{ //do something}public interface Provider{ Service newService();}public class Service { //不允许外部实例化 private Service(){}; private static final Map<String,Provider> providers = new ConcurrentHashMap<>(); public static final String defKey = "<def>"; //注册 public static void registerDefaultProvider(Provider provider) { registerProvider(defKey, provider); } public static void registerDefaultProvider(String name, Provider provider) { providers.put(name, provider); } //获取 public static Service newInstance() { return newInstance(defkey); } public static Service newInstance(String name) { Provider p = providers.get(name); if(null == p) throw new IlleaglArgumentException("No provider registered with name : " + name); return p.newService(); }} 缺点: 类如果不含有公有或者受保护的构造方法,就不能被子类化。因祸得福的是,我们鼓励复合,而不是继承。 静态工厂方法与普通静态方法没有任何区别,“赶着一等公民(构造方法)的活儿,却享受不到一等公民的待遇(Javadoc等工具并不关注静态工厂方法) 何时应该用静态工厂方法 如果需要大量含有共同签名的构造方法的时候。 每次返回的对象是不可变类的时候,可以提前构造好对象供其使用。 需要返回不同的子类的时候。 静态工厂方法命名 valueOf —- 返回值与参数具有相同的值 of —- valueOf的替代 geteInstance —- 通过参数描述返回实例,但是值不一定相同 newInstance —- 创建一个新的对象 getType —- Type表示返回的实例的类型 newType —- 返回一个新的实例","raw":null,"content":null,"categories":[],"tags":[{"name":"JAVA","slug":"JAVA","permalink":"https://yangle94.github.io/tags/JAVA/"}]},{"title":"SpringMVC_Annotation_@RequestBody","slug":"SpringMVC-Annotation-RequestBody","date":"2017-03-01T11:33:40.000Z","updated":"2017-06-15T04:47:00.000Z","comments":true,"path":"2017/03/01/SpringMVC-Annotation-RequestBody/","link":"","permalink":"https://yangle94.github.io/2017/03/01/SpringMVC-Annotation-RequestBody/","excerpt":"@RequestBody注解的源码123456@Target({ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestBody { boolean required() default true;}\n由源码可见,@RequestBody只有一个required属性,默认值为true,该注解会保留至.class文件中,利用反射可以进行查找。","text":"@RequestBody注解的源码123456@Target({ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestBody { boolean required() default true;} 由源码可见,@RequestBody只有一个required属性,默认值为true,该注解会保留至.class文件中,利用反射可以进行查找。 @RequestBody的作用该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等; 它是通过使用HandlerAdapter 配置的HttpMessageConverters来解析post data body,然后绑定到相应的bean上的。 因为配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看FormHttpMessageConverter api; 例如1234@RequestMapping(value = "sendMessage", method = RequestMethod.POST)public String sendMesscage(@RequestBody UserIdCodeDTO userIdCodeMessageDTO) { return userIdCodeMessageDTO.toString();}运用次注解后,SprimgMVC将会把post或者get请求来的信息进行逆序列化,组成对象,前提是你传过来的值跟该对象匹配。 注意!!使用该注解以后,若想传送非json格式代码,需要指定请求头Content-Type为application/json,否则对象所有属性将为null。","raw":null,"content":null,"categories":[],"tags":[{"name":"SpringMVC","slug":"SpringMVC","permalink":"https://yangle94.github.io/tags/SpringMVC/"}]},{"title":"Java transient关键字使用记录","slug":"Java-transient","date":"2017-02-28T14:01:58.000Z","updated":"2020-12-20T06:45:43.188Z","comments":true,"path":"2017/02/28/Java-transient/","link":"","permalink":"https://yangle94.github.io/2017/02/28/Java-transient/","excerpt":"今天对某个对象使用FastJson进行序列化的时候想要把某个属性进行隐藏,找了半天没找到FastJson有什么注解。查询了半天发现谋篇文章有关于transient关键字的介绍,看完突然觉得Java白学了,还有这么一个关键字不知道。\ntransient的作用及使用方法\n我们知道Java中完成对象的序列化需要实现Serilizable接口,实现了该接口则可以进行对象的序列化。然而在实际的开发中,总会有某些属性我们并不想让它进行序列化,可以对此属性加上transient关键字,以后这个属性就不会再被进行序列化和反序列化。当然,这也成为它的局限性,因为没法让序列化、反序列化不同时进行。\n样例代码:12345678910111213class User implements Serializable { private transient String passwd; public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; }}","text":"今天对某个对象使用FastJson进行序列化的时候想要把某个属性进行隐藏,找了半天没找到FastJson有什么注解。查询了半天发现谋篇文章有关于transient关键字的介绍,看完突然觉得Java白学了,还有这么一个关键字不知道。 transient的作用及使用方法 我们知道Java中完成对象的序列化需要实现Serilizable接口,实现了该接口则可以进行对象的序列化。然而在实际的开发中,总会有某些属性我们并不想让它进行序列化,可以对此属性加上transient关键字,以后这个属性就不会再被进行序列化和反序列化。当然,这也成为它的局限性,因为没法让序列化、反序列化不同时进行。 样例代码:12345678910111213class User implements Serializable { private transient String passwd; public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; }} transient使用小结   被transient修饰的变量,如果进行序列化:比如写入到文件中,则此变量就不能再被读取到,会被赋值为null   transient关键字只能修饰成员变量,不能修饰方法和类。如果某个类中需要使用此关键字,必须将该类实现Serilizable接口   静态变量不论是否被修饰,均不可被序列化。此时注意,虽然再次反序列化以后static变量可能还会有值,此时这个值对应的为当前虚拟机中整个类所对应的值,并非因为将该属性序列化时对此属性进行了序列化。因为static变量为类共有,此属性为类共有,而并非对象私有。","raw":null,"content":null,"categories":[],"tags":[{"name":"Java","slug":"Java","permalink":"https://yangle94.github.io/tags/Java/"},{"name":"FastJson","slug":"FastJson","permalink":"https://yangle94.github.io/tags/FastJson/"}]},{"title":"Mac上Github的ssh、http的代理","slug":"github-ssh-pxory","date":"2017-02-25T08:49:42.000Z","updated":"2017-06-15T04:47:02.000Z","comments":true,"path":"2017/02/25/github-ssh-pxory/","link":"","permalink":"https://yangle94.github.io/2017/02/25/github-ssh-pxory/","excerpt":"因为我们身处天朝外加某堵墙的原因,访问Github实在是不稳定,SSH慢的要死,简直了。现在交给大家利用ShadowScoket进行对Github的加速。\n购买一个VPN随便什么VPN都好,我这里用的是TMDVPN,我自己用的也是这个,感觉还不错,也不是很贵,有想要的童鞋请给我发Email:1024920977@qq.com,我买的是50元半年,一个月20G流量,附带邀请码的注册地址。具体使用ShadowScoket的教程他们官网上都有。\n设置Git的代理地址","text":"因为我们身处天朝外加某堵墙的原因,访问Github实在是不稳定,SSH慢的要死,简直了。现在交给大家利用ShadowScoket进行对Github的加速。 购买一个VPN随便什么VPN都好,我这里用的是TMDVPN,我自己用的也是这个,感觉还不错,也不是很贵,有想要的童鞋请给我发Email:1024920977@qq.com,我买的是50元半年,一个月20G流量,附带邀请码的注册地址。具体使用ShadowScoket的教程他们官网上都有。 设置Git的代理地址 https、https代理如各平台的 Shadowsocks 客户端都提供一个本地的 socks5 代理,那么你可以这样设置,让 Git 通过 HTTP 链接 clone 代码时走 socks5 代理,当然,你需要看看你的ShadowScoket的socks5的监听端口是多少(ShadowScoket里头高级设置有显示)。1234//通过 http 链接 clone 代码时走 socks5 代理git config --global http.proxy "socks5://127.0.0.1:1086"//通过 https 链接 clone 代码时走 socks5代理git config --global https.proxy "socks5://127.0.0.1:1086" ssh代理上面设置的 HTTP 代理对这种方式 clone 代码是没有影响的,也就是并不会加速,SSH 的代理需要单独设置,其实这个跟 Git 的关系已经不是很大,我们需要改的,是SSH 的配置。在用户目录下建立如下文件 ~/.ssh/config,对 GitHub 的域名做单独的处理123456789 # 这里必须是 github.com,因为这个跟我们 clone 代码时的链接有关Host github.com # 如果用默认端口,这里是 github.com,如果想用443端口,这里就是 ssh.github.com详见 https://help.github.com/articles/using-ssh-over-the-https-port/ HostName github.com User git # 如果是 HTTP 代理,把下面这行取消注释,并把 proxyport 改成自己的 http 代理的端口 # ProxyCommand socat - PROXY:127.0.0.1:%h:%p,proxyport=6667 # 如果是 socks5 代理,则把下面这行取消注释,并把 6666 改成自己 socks5 代理的端口 # ProxyCommand nc -v -x 127.0.0.1:6666 %h %p整个代理就完成了,以后你要是用http、http和ssh方式访问Github就会走ShadowScoket的socks5的代理,前提是你必须要讲ShadowScoket打开并且链接上vpn服务器,否则,并没有什么卵用。","raw":null,"content":null,"categories":[],"tags":[{"name":"Github","slug":"Github","permalink":"https://yangle94.github.io/tags/Github/"}]},{"title":"自定义Selenium Grid2 Servlet","slug":"SeleniumGridCustomServlet","date":"2017-02-07T07:45:32.000Z","updated":"2017-06-15T04:47:06.000Z","comments":true,"path":"2017/02/07/SeleniumGridCustomServlet/","link":"","permalink":"https://yangle94.github.io/2017/02/07/SeleniumGridCustomServlet/","excerpt":"最近搞selenium grid的时候需要收集每个时刻selenium gird的监控数据,找了半天好不容易在selenium的官网文档的某个角落找到了办法,特此记录。\n官网自定义Sevlet文档\n自定义servlet步骤\n下载selenium-server-standalone-3.0.1.jar,新建一个java工程并将其当作lib包倒入。\n\n工程下新建一个自定义servlet类,继承org.openqa.grid.web.servlet.RegistryBasedServlet或者javax.servlet.http.HttpServlet.类。RegistryBasedServlet类为selenium grid的核心类,如果你想要获得selenium grid的核心数据,请继承该类。\n\n将此项目导出为一个新jar包,并将其与selenium-server-standalone-3.0.1.jar放在同一个目录下。\n\n再此目录下执行\n\n\n1java -cp xxx.jar:selenium-server-standalone-3.0.1.jar org.openqa.grid.selenium.GridLauncherV3 -servlets org.openqa.grid.web.servlet.custom.CustomServlet,\n若为hub则再加入“-role hub”等其它所需参数。","text":"最近搞selenium grid的时候需要收集每个时刻selenium gird的监控数据,找了半天好不容易在selenium的官网文档的某个角落找到了办法,特此记录。 官网自定义Sevlet文档 自定义servlet步骤 下载selenium-server-standalone-3.0.1.jar,新建一个java工程并将其当作lib包倒入。 工程下新建一个自定义servlet类,继承org.openqa.grid.web.servlet.RegistryBasedServlet或者javax.servlet.http.HttpServlet.类。RegistryBasedServlet类为selenium grid的核心类,如果你想要获得selenium grid的核心数据,请继承该类。 将此项目导出为一个新jar包,并将其与selenium-server-standalone-3.0.1.jar放在同一个目录下。 再此目录下执行 1java -cp xxx.jar:selenium-server-standalone-3.0.1.jar org.openqa.grid.selenium.GridLauncherV3 -servlets org.openqa.grid.web.servlet.custom.CustomServlet, 若为hub则再加入“-role hub”等其它所需参数。 注意事项java -cp是classpath的意思,意思是将某些jar加入到classpath中,两个jar用:隔开,此时注意在linux下不支持通配符,所以必须手动将名字补全。-servlets后跟的是自定义servlet的地址,GridLauncherV3为main函数所在位置,在3.0以后的selenium-server-standalone.jar中为GridLauncherV3,2.0为GridLauncher。","raw":null,"content":null,"categories":[],"tags":[{"name":"Selenium Grid","slug":"Selenium-Grid","permalink":"https://yangle94.github.io/tags/Selenium-Grid/"}]},{"title":"docker化selenium grid","slug":"docker-selenium","date":"2017-02-07T07:08:32.000Z","updated":"2020-12-20T08:04:45.034Z","comments":true,"path":"2017/02/07/docker-selenium/","link":"","permalink":"https://yangle94.github.io/2017/02/07/docker-selenium/","excerpt":"因为工作需要,本人构建了一个docker化selenium grid,特此记录,以备其它有用的人使用。\n1 使用场景调研\n\n完成某个功能的所有操作步骤在同一个页面、session中。\n\n对同一个虚拟机中多个浏览器(多个webdriver、不同用户)之间必须进行浏览器层的cookies隔离,保证相互之间cookies不应相互影响。\n\n对同一个虚拟机中多个浏览器(多个webdriver、不同用户)进行模拟键盘输入时,保证所有键盘操作必须相互隔离,保证对应的模拟键盘输入进入其对应的输入框内。\n\n对所有请求进行调度排序。\n\n设定超时时间,对超过时间限制的请求进行删除,保证资源的释放。\n\n对各个selenium节点有对应功能性监控(比如某个节点是否空闲等)。\n\n各个selenium节点对应的日志查看问题。\n\n","text":"因为工作需要,本人构建了一个docker化selenium grid,特此记录,以备其它有用的人使用。 1 使用场景调研 完成某个功能的所有操作步骤在同一个页面、session中。 对同一个虚拟机中多个浏览器(多个webdriver、不同用户)之间必须进行浏览器层的cookies隔离,保证相互之间cookies不应相互影响。 对同一个虚拟机中多个浏览器(多个webdriver、不同用户)进行模拟键盘输入时,保证所有键盘操作必须相互隔离,保证对应的模拟键盘输入进入其对应的输入框内。 对所有请求进行调度排序。 设定超时时间,对超过时间限制的请求进行删除,保证资源的释放。 对各个selenium节点有对应功能性监控(比如某个节点是否空闲等)。 各个selenium节点对应的日志查看问题。 2 Docker化Selenium Grid2还存在的问题 Windows对docker提供的镜像分别为nanoServer和windowsservercore,但其中并不存在GUI、IE等所需内容,查阅官方资料,官方人员说明目前项目并不支持GUI操作。详见关于docker产品经理对于Windows GUI的说明。 目前对于windows镜像需要人为的干预。目前我所采用的方式是为单独建立windows的虚拟机,在虚拟机上对重新部署selenium。在对含有插件的页面进行访问时,需要提前将插件安装好,也就是说需要提前对docker file进行构建。 3 整体构建图4 各个node节点的功能性监控 在selenium grid的hub节点中自带了一个监控页面,用于监控每个节点的运行情况、ip端口信息,包括有多少任务在排队中当前总共有多少任务在运行等,此页面访问地址为selenium hub节点的4444端口。 5 部署步骤 部署selenium hub1docker run -d -p 4444:4444 –name registry.cn-hangzhou.aliyuncs.com/angle/selenium-hub:2.1部署chrome1docker run -d -P -p 5900:5900 –link selenium-hub:hub –name chrome registry.cn-hangzhou.aliyuncs.com/angle/node-chrome-debug:2.1部署firefox1docker run -d -P -p 5901:5900 –link selenium-hub:hub –name firefox registry.cn-hangzhou.aliyuncs.com/angle/node-firefox-debug:2.1部署IEnodeconfig.json12345678910111213141516171819202122232425{“capabilities”: [ { “browserName”: “internet explorer”, “maxInstances”: 3, “seleniumProtocol”: “WebDriver” } ],“proxy”: “org.openqa.grid.selenium.proxy.DefaultRemoteProxy”,“maxSession”: 3,“port”: 5555,“register”: true,“registerCycle”: 5000,“hub”: “http://localhost:4444“,“nodeStatusCheckTimeout”: 5000,“nodePolling”: 5000,“role”: “node”,“unregisterIfStillDownAfter”: 60000,“downPollingLimit”: 2,“debug”: false,“servlets” : [],“withoutServlets”: [],“custom”: {}}下载selenium-server-standalone-3.0.1.jar 下载地址:https://selenium-release.storage.googleapis.com/3.0/selenium-server-standalone-3.0.1.jar 部署命令1java -jar selenium-server-standalone.jar -role node -nodeConfig nodeconfig.json 6 已解决的问题我所用的原始Selenium Grid Docker镜像中有中文乱码问题,需要在docker file中加入字符集解决。Selenium Grid中存在监控功能并不完善,若想要获取此类信息数据,需要加入自定义servlet(关于此章我会在另一片文章中介绍) 7 下载以及中文适配由于selenium官方的docker镜像对于中文兼容性不是很好,会出现某些中文乱码的问题,其实是因为其包含的乌班图镜像中缺少了部分字符集的原因,为了修复这个问题,我特意制作了修复了此问题的镜像并上传到了阿里云镜像服务器(代码中已经替换)。","raw":null,"content":null,"categories":[],"tags":[{"name":"Selenium Grid","slug":"Selenium-Grid","permalink":"https://yangle94.github.io/tags/Selenium-Grid/"},{"name":"Docker","slug":"Docker","permalink":"https://yangle94.github.io/tags/Docker/"}]},{"title":"java内部类实现原理以及注意事项","slug":"innerClass","date":"2016-12-28T07:08:32.000Z","updated":"2017-06-15T04:47:01.000Z","comments":true,"path":"2016/12/28/innerClass/","link":"","permalink":"https://yangle94.github.io/2016/12/28/innerClass/","excerpt":"1.什么是内部类:定义在某个类中的类叫做内部类。\n2.内部类有什么好处:1)内部类方法可以访问类定义所在的做哦用语中的数据,包括私有数据。2)内部类可以对同一个包中的其他类隐藏起来。3)当想要定义一个回调函数且又不想编写大量代码的时候,使用匿名内部类比较便捷。","text":"1.什么是内部类:定义在某个类中的类叫做内部类。 2.内部类有什么好处:1)内部类方法可以访问类定义所在的做哦用语中的数据,包括私有数据。2)内部类可以对同一个包中的其他类隐藏起来。3)当想要定义一个回调函数且又不想编写大量代码的时候,使用匿名内部类比较便捷。 3.内部类实现原理代码样例:12345678910111213141516171819class TalkingClock { private int interval; private boolean beep; public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; } public void start() { ActionListener listener = new TimePrinter(); Time t = new Time(interval,listener); t.start(); } public class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); if(beep) Toolkit.getDefaultToolkit().beep(); } }} 解释:首先,内部类是一种编译器现象,与java虚拟机无关。也就是说,在虚拟机中,类并不存在内部类一说。在上面两个类中,TimePrinterl类是TalkingClock的内部类,当java编译器对此两个类进行编译时,会将内部类TimePrinter编译为TalkingClock$TimePrinter,并在内部类中增加一条外部类的引用,以此来访问外部类对象中的域,并会修改其无参的构造方法为有参的构造方法(增加一个TalkingClock参数),对于外部类TalkingClock,会增加一个static boolean access$0(TalkingClock)方法,具体可以使用java -private innerClass.TalkingClock$TimePrinter(由于在linux等系统的shell中$为关键字,所以此时需要转义)查看。 123456789101112public class TalkingClock$TimePrinter { public TalkingClock$TimePrinter(TalkingClock); public void actionPerformed(java.awt.event.ActionEvent); final TalkingClock this$0;}class TalkingClock { private int interval; private boolean beep; public TalkingClock(int interval, boolean beep); public void start(); static boolean access$0(TalkingClock);} 由此,当内部类中需要访问外部类的私有成员时,内部类可以调用外部类的静态方法进行对私有成员的访问。当然,access$0并不是一个合法的java方法名,因此,在java层面调用此方法名并不现实。但可以用底层的其他方法进行对此方法的调用。","raw":null,"content":null,"categories":[{"name":"ß","slug":"s","permalink":"https://yangle94.github.io/categories/s/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://yangle94.github.io/tags/Java/"}]},{"title":"jq的ajax请求后台未出错却进入error问题","slug":"jqAjaxResError","date":"2016-12-27T07:08:32.000Z","updated":"2017-02-21T08:53:20.000Z","comments":true,"path":"2016/12/27/jqAjaxResError/","link":"","permalink":"https://yangle94.github.io/2016/12/27/jqAjaxResError/","excerpt":"","text":"现象:1234567891011121314jq进行ajax请求,后端并为报错,状态码为200但ajax请求进入error方法。 $.ajax({ url : "http:/www.baidu.com", data : { param : "param" } dataType : "json", success : function(data) { alert("返回成功,进入成功方法"); } error : function() { alert("返回失败,进入失败方法") } }) 检查思路:1.查看后端日志,发现后端并未报错。2.打开谷歌浏览器调试模式,发现状态吗为200。 由此可推断出后台并为发生任何异常,判断并不是因为后端错误所导致。由此可以判断能够接触到返回数据并出现错误的只能是js部分。 最终结果查询资料后发现,对于ajax请求如果规定了返回值的格式(dataType),则返回值一定要符合dataType的格式。若返回的格式跟dataType的格式不相符,js会在解析的时候报错,由此进入了error的方法。后端以前返回值为”success”,将其改为{“isSuccess” : “success”}后进行返回,程序运行成功,进入ajax的success方法。由此可见,若规定了dataType以后,后端返回的数据必须符合此格式,否则,前端js在解析的时候会发生错误。","raw":null,"content":null,"categories":[],"tags":[{"name":"ajax","slug":"ajax","permalink":"https://yangle94.github.io/tags/ajax/"},{"name":"jq","slug":"jq","permalink":"https://yangle94.github.io/tags/jq/"}]},{"title":"开博了~","slug":"index","date":"2016-12-25T07:08:32.000Z","updated":"2020-12-20T06:45:43.743Z","comments":true,"path":"2016/12/25/index/","link":"","permalink":"https://yangle94.github.io/2016/12/25/index/","excerpt":"","text":"哈哈,功夫不负有心人,终于在github page上开了自己的文章,顿时兴奋无比。 本站主要是用在我个人写的一些程序中遇到的问题。本人初出茅庐,希望大家能够不吝赐教。本次仅仅是为了测试搭成的框架是否可以运行,稍后会写一个文章的搭建教程。","raw":null,"content":null,"categories":[],"tags":[{"name":"other","slug":"other","permalink":"https://yangle94.github.io/tags/other/"}]}]}