/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key </tt>, or * <tt>null </tt> if there was no mapping for <tt> key</tt> . * (A <tt>null </tt> return can also indicate that the map * previously associated <tt>null </tt> with <tt> key</tt> .) */ public V put(K key, V value) { if (key == null) return putForNullKey(value); inthash= hash(key); inti= indexFor(hash, table .length ); for (Entry<K,V> e = table[i]; e != null; e = e. next) { Object k; if (e. hash == hash && ((k = e. key) == key || key.equals(k))) { VoldValue= e. value; e. value = value; e.recordAccess( this); return oldValue; } }
/** * Offloaded version of put for null keys */ private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e. next) { if (e. key == null) { VoldValue= e. value; e. value = value; e.recordAccess( this); return oldValue; } } modCount++; addEntry(0, null, value, 0); returnnull; }
/** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ voidaddEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && ( null != table[bucketIndex])) { resize(2 * table. length); hash = ( null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); }
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ voidresize(int newCapacity) { Entry[] oldTable = table; intoldCapacity= oldTable. length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer. MAX_VALUE; return; }
/** * Transfers all entries from current table to newTable. */ voidtransfer(Entry[] newTable, boolean rehash) { intnewCapacity= newTable. length; for (Entry<K,V> e : table) { while( null != e) { Entry<K,V> next = e. next; if ( rehash) { e. hash = null == e. key ? 0 : hash(e. key); } inti= indexFor(e.hash, newCapacity); e. next = newTable[i]; newTable[i] = e; e = next; } } }
/** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo -construction" (cloning, * deserialization). This version needn't worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */ voidcreateEntry( int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = newEntry<>(hash, key, value, e); size++; }
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily </i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key);
returnnull == entry ? null : entry.getValue(); }
也是先判断key是不是null,做特殊处理。直接上代码,不赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/** * Offloaded version of get() to look up null keys. Null keys map * to index 0. This null case is split out into separate methods * for the sake of performance in the two most commonly used * operations (get and put), but incorporated with conditionals in * others. */ private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e. next) { if (e. key == null) return e. value; } returnnull; }
/** * Returns the entry associated with the specified key in the * HashMap. Returns null if the HashMap contains no mapping * for the key. */ final Entry<K,V> getEntry(Object key) { inthash= (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[ indexFor(hash, table.length)]; e != null; e = e. next) { Object k; if (e. hash == hash && ((k = e. key) == key || (key != null && key.equals(k)))) return e; } returnnull; }
/** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the HashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the HashMap fail -fast. (See ConcurrentModificationException). */ transientint modCount;
//thread safe and performance promote publicstatic Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { //when more than two threads run into the first null check same time, to avoid instanced more than one time, it needs to be checked again. if (INSTANCE == null) { INSTANCE = newSingleton(); } } } return INSTANCE; } }
opendal-java 的第一版异步接口互操作实现是基于 Global Reference 的。但这个方案有一个缺陷,那就是 Global Reference 上限是 65535 个。所谓基于 Global Reference 的方案,就是把需要异步完成的 CompletableFuture 对象注册为 JNI 的 Global Reference 并跨线程共享,这意味着整个程序的 API 调用并发上限一定不超过 65535 个。
虽然这个数量对于大部分场景已经够用,但是毕竟是个无谓的开销,且 Global Reference 的访问没有经过特别的优化,很难估计重度使用这个特性会带来怎样的不稳定性。
我曾经构思过基于全局 Future Registry 的解决方案,或者演化成一个类似于跨语言 Actor Model (Dispatcher + Actor with Mailbox) 的方案,但是最终都没有成功写出来。
<artifactSet> <includes> <!-- Unfortunately, the next line is necessary for now to force the execution of the Shade plugin upon all sub modules. This will generate effective poms, i.e. poms which do not contain properties which are derived from this root pom. In particular, the Scala version properties are defined in the root pom and without shading, the root pom would have to be Scala suffixed and thereby all other modules. --> <include>org.apache.flink:force-shading</include> </includes> </artifactSet>
Build time: 2022-02-08 09:58:38 UTC Revision: f0d9291c04b90b59445041eaa75b2ee744162586
Kotlin: 1.5.31 Groovy: 3.0.9 Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 JVM: 17.0.2 (Eclipse Adoptium 17.0.2+8) OS: Mac OS X 10.15.7 x86_64
这个用例当中有两个注意点。
第一个注意点是 protobuf 的配置方式。
可以看到在 dependencies 配置块中声明了 proto 文件的路径。我不记得是不是有默认的查询路径比如 <project>/src/main/proto 这样的,但是建议还是明确写出来为好,毕竟业界也没有什么公认的标准,每个插件工具的假设不一定采用同一套约定。
通常来说,Maven 或 Gradle 项目打包的时候,依赖项都不会进入到最终产物当中。因为打包就只是对你写的这些代码编译出来的 class 文件打包,而不是像 C / Rust 这种产生二进制可执行文件的思路。Java 语言程序运行起来,是需要程序员把所有的依赖项都写进 classpath 里,再指定要运行的类,执行其 Main 方法启动的。这种情况下打包不需要把依赖项都搭进去。
这种方案对于企业自己管理所有依赖,大部分软件是自包含少依赖的大型软件的场景是比较合理的。但是随着互联网的兴起和合作开发效率提升,一个项目依赖大量其他项目的情形越来越多,这些其他项目也有自己的开发周期,往往会产生多个版本的 JAR 包发布产物。这种情况下再要求程序员自己去管理依赖项,管理 classpath 的内容,在生产上就是既繁琐有不可靠的了。