Long Luo's Life Notes

每一天都是奇迹

In general, Map is a data structure consisting of a set of key-value pairs, and each key can only appears once in the map. This post summarizes Top 9 FAQ of how to use Java Map and its implemented classes. For sake of simplicity, I will use generics in examples. Therefore, I will just write Map instead of specific Map. But you can always assume that both the K and V are comparable, which means K extends Comparable and V extends Comparable.

1. Convert a Map to List

In Java, Map interface provides three collection views: key set, value set, and keyvalue set. All of them can be converted to List by using a constructor or addAll() method. The following snippet of code shows how to construct an ArrayList from a map.

1
2
3
4
5
6
// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.valueSet());
// key value list
List entryList = new ArrayList(map.entrySet());

2. iterate over each entry in a map

Iterating over every pair of key-value is the most basic operation to traverse a map. In Java, such pair is stored in the map entry called Map.Entry.

3. SORT A MAP ON THE KEYS

Map.entrySet() returns a key-value set, therefore the most efficient way of going through every entry of a map is

1
2
3
4
5
6
for (Entry entry:map.entrySet()) {
// get key
K key = entry.getKey();
// get value
V value = entry.getValue();
}

Iterator can also be used, especially before JDK 1.5

1
2
3
4
5
6
7
8
Iterator itr = map.entrySet().iterator();
while(itr.hasNext ( ) ) {
Entry ent ry = i t r . next ( ) ;
/ / g e t k ey
K key = ent ry . getKey ( ) ;
/ / g e t v a l u e
V value = ent ry . getValue ( ) ;
}

60.3 sort a map on the keys Sorting a map on the keys is another frequent operation. One way is to put Map.Entry into a list, and sort it using a comparator that sorts the value. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getKey ( ) . compareTo ( e2 . getKey ( ) ) ; } });

The other way is to use SortedMap, which further provides a total ordering on its keys. Therefore all keys must either implement Comparable or be accepted by the comparator.

4. SORT A MAP ON THE VALUES

One implementing class of SortedMap is TreeMap. Its constructor can accept a comparator. The following code shows how to transform a general map to a sorted map.

SortedMap sortedMap = new TreeMap (new Comparator ( ) { @Override public int compare (K k1 , K k2 ) { return k1 . compareTo ( k2 ) ; } } ) ; sortedMap . putAll (map) ; 60.4 sort a map on the values Putting the map into a list and sorting it works on this case too, but we need to compare Entry.getValue() this time. The code below is almost same as before. Li s t l i s t = new Ar rayLi s t (map. ent rySe t ( ) ) ; Co l l e c t i ons . s o r t ( l i s t , new Comparator ( ) { @Override public int compare ( Entry e1 , Entry e2 ) { return e1 . getValue ( ) . compareTo ( e2 . getValue ( ) ) ; } } ) ; We can still use a sorted map for this question, but only if the values are unique too. Under such condition, you can reverse the key=value pair to value=key. This solution has very strong limitation therefore is not really recommended by me. 60.5 initialize a static/immutable map When you expect a map to remain constant, it’s a good practice to copy it into an immutable map. Such defensive programming techniques will help you create not only safe for use but also safe for thread maps. 60.6. DIFFERENCE BETWEEN HASHMAP, TREEMAP, AND HASHTABLE 217 To initialize a static/immutable map, we can use a static initializer (like below). The problem of this code is that, although map is declared as static final, we can still operate it after initialization, like Test.map.put(3,“three”);. Therefore it is not really immutable. To create an immutable map using a static initializer, we need an extra anonymous class and copy it into a unmodifiable map at the last step of initialization. Please see the second piece of code. Then, an UnsupportedOperationException will be thrown if you run Test.map.put(3,“three”);. public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { map = new HashMap ( ) ; map. put ( 1 , ” one ” ) ; map. put ( 2 , ” two ” ) ; } } public c l a s s Tes t { pr ivate s t a t i c f ina l Map map; s t a t i c { Map aMap = new HashMap ( ) ; aMap. put ( 1 , ” one ” ) ; aMap. put ( 2 , ” two ” ) ; map = Co l l e c t i ons . unmodifiableMap (aMap) ; } } Guava libraries also support different ways of intilizaing a static and immutable collection. To learn more about the benefits of Guava’s immutable collection utilities, see Immutable Collections Explained in Guava User Guide. 60.6 difference between hashmap, treemap, and hashtable There are three main implementations of Map interface in Java: HashMap, TreeMap, and Hashtable. The most important differences include: The order of iteration. HashMap and HashTable make no guarantees as to the order of the map; in particular, they do not guarantee that the order 60.7. A MAP WITH REVERSE VIEW/LOOKUP 218 will remain constant over time. But TreeMap will iterate the whole entries according the “natural ordering” of the keys or by a comparator. key-value permission. HashMap allows null key and null values. HashTable does not allow null key or null values. If TreeMap uses natural ordering or its comparator does not allow null keys, an exception will be thrown. Synchronized. Only HashTable is synchronized, others are not. Therefore, “if a thread-safe implementation is not needed, it is recommended to use HashMap in place of HashTable.” A more complete comparison is | HashMap | HashTable | TreeMap i t e r a t i o n order | no | no | yes null key value | yes yes | yes yes | no yes synchronized | no | yes | no time performance | O( 1 ) | O( 1 ) | O( log n) implementation | buckets | buckets | red black t r e e Read more about HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap. 60.7 a map with reverse view/lookup Sometimes, we need a set of key-key pairs, which means the map’s values are unique as well as keys (one-to-one map). This constraint enables to create an “inverse lookup/view” of a map. So we can lookup a key by its value. Such data structure is called bidirectional map, which unfortunetely is not supported by JDK. 60.8 both apache common collections and guava provide implementation of bidirectional map, called bidimap and bimap, respectively. both enforce the restriction that there is a 1:1 relation between keys and values. 7. shallow copy of a map Most implementation of a map in java, if not all, provides a constructor of copy of another map. But the copy procedure is not synchronized. That means when 60.9. FOR THIS REASON, I WILL NOT EVEN TELL YOU HOW TO USE CLONE() METHOD TO COPY one thread copies a map, another one may modify it structurally. To prevent accidental unsynchronized copy, one should use Collections.synchronizedMap() in advance.

Map copiedMap = Co l l e c t i ons.synchronizedMap (map) ; Another interesting way of shallow copy is by using clone() method. However it is NOT even recommended by the designer of Java collection framework, Josh Bloch. In a conversation about “Copy constructor versus cloning“, he said I often provide a public clone method on concrete classes because people expect it.

It’s a shame that Cloneable is broken, but it happens. Cloneable is a weak spot, and I think people should be aware of its limitations.

60.9 for this reason, i will not even tell you how to use clone() method to copy a map. 8. create an empty map If the map is immutable, use map = Co l l e c t i ons . emptyMap ( ) ;

Otherwise, use whichever implementation. For example map = new HashMap ( ) ;

THE

By Long Luo

Android NDK

静态注册

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.18.1)

project("hello-jni")

add_library(hello-jni SHARED
hello-jni.cpp)

# Include libraries needed for hello-jni lib
target_link_libraries(hello-jni
android
log)

hello-jni/app/src/main/cpp/hello-jni.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <jni.h>
#include <string>

#include "hello_jni.h"


JNIEXPORT jstring JNICALL
Java_me_longluo_hellojni_HelloJniActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from JNI.";
return env->NewStringUTF(hello.c_str());
}


JNIEXPORT jint JNICALL
Java_me_longluo_hellojni_HelloJniActivity_add(JNIEnv *env, jobject /* this */, jint a, jint b) {
int result = a + b;
return result;
}

hello-jni/app/src/main/cpp/hello_jni.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef HELLO_JNI_H
#define HELLO_JNI_H

#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL
Java_me_longluo_hellojni_HelloJniActivity_stringFromJNI(JNIEnv *env, jobject /* this */);

JNIEXPORT jint JNICALL
Java_me_longluo_hellojni_HelloJniActivity_add(JNIEnv *env, jobject /* this */, jint a, jint b);


#ifdef __cplusplus
}
#endif

#endif // HELLO_JNI_H
1
2
3
4
5
6
7
ndkVersion '25.1.8937393'

externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}

动态注册

hello-jni-dynamic/app/src/main/cpp/CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
cmake_minimum_required(VERSION 3.18.1)

# Declares and names the project.

project("hello-jni-dynamic")

# Automatically all files in a directory to a target
file (GLOB_RECURSE HELLOJNI_SRCS CONFIGURE_DEPENDS
"src/*.cpp"
"src/*.h"
)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
hello-jni

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
${HELLOJNI_SRCS})

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
hello-jni

# Links the target library to the log library
# included in the NDK.
${log-lib}
)

hello-jni-dynamic/app/src/main/cpp/src/hello-jni.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "hello-jni.h"

jint JNI_OnLoad(JavaVM *vm, void *reserved){

UnionJNIEnvToVoid uenv;
uenv.venv = nullptr;
jint result = -1;
JNIEnv* env;

ALOGI("JNI_OnLoad");

if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}

env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}

result = JNI_VERSION_1_6;

bail:
return result;
}

hello-jni-dynamic/app/src/main/cpp/src/hello-jni.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#ifndef HELLO_JNI_H
#define HELLO_JNI_H

#include <jni.h>
#include <string>
#include "logger.h"
#include "constants.h"

#define SIZE(X) sizeof(X) / sizeof(X[0])


typedef union {
JNIEnv *env;
void *venv;
} UnionJNIEnvToVoid;


/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
int numMethods) {
jclass clazz;
clazz = env->FindClass(className);

if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}

return JNI_TRUE;
}


/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv *env) {
if (!registerNativeMethods(env, HelloJNI::constants,
HelloJNI::constants_methods,
SIZE(HelloJNI::constants_methods))
) {
return JNI_FALSE;
}

return JNI_TRUE;
}


/*
* This is called by the VM when the shared library is first loaded.
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved);

#endif //HELLO_JNI_H

hello-jni-dynamic/app/src/main/cpp/src/constants.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef HELLO_CONSTANTS_H
#define HELLO_CONSTANTS_H

#include <jni.h>


namespace HelloJNI{

static jfloat JniVersion(JNIEnv *env, jobject object) {
return 1.6;
}

static jstring JniDynamicRegister(JNIEnv *env, jobject object){
return env->NewStringUTF("Hello Jni Dynamic Register");
}

static const char *constants = "me/longluo/hellojni/JniLibrary";

static JNINativeMethod constants_methods[] = {
{"nativeGetJniVersion", "()F", (void *) JniVersion},
{"nativeGetJniDynamicRegister", "()Ljava/lang/String;", (void *) JniDynamicRegister}
};

static std::string jString2String(JNIEnv *env, jstring jStr) {
if (!jStr) {
return "";
}

const jclass stringClass = env->GetObjectClass(jStr);
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jStr, getBytes, env->NewStringUTF("UTF-8"));

size_t length = (size_t) env->GetArrayLength(stringJbytes);
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);

std::string ret = std::string((char *)pBytes, length);
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);

env->DeleteLocalRef(stringJbytes);
env->DeleteLocalRef(stringClass);

return ret;
}
}

#endif //HELLO_CONSTANTS_H

参考文献

  1. JNI
  2. NDK 使用入门
  3. CMake
  4. 将 NDK 与其他构建系统配合使用
  5. 示例:hello-jni

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html

https://medium.com/@sarafanshul/jni-101-introduction-to-java-native-interface-8a1256ca4d8e

https://medium.com/@sarafanshul/jni-201-dynamic-registration-a1ad7fa50b21

https://gist.github.com/okamayana/79c98545eb99c4877979

By Long Luo

招聘其实本质上是在做风险评估,根据简历和面试确定风险最小的那个人。

其实这个风险评估可以推而广之适用于涉及选择的各个场合。

最近在协助公司招人,也为项目组面试了一些人,对招人也有了一些思考。

为了避免招到不合适的人,将风险降到最低,那么就会从各方面均衡考虑。

从面试官的角度来看,为什么要名校和名企出身的人。

第一,因为名企已经帮你做了一次筛选了,尤其是校招进入BAT的,更是万里挑一,所以风险可以降到最低。

第二,学历其实也是一项筛选。学历可以证明你的学习能力以及把握机会的能力(高考是一次可以改变人生轨迹的机会。)。

第三,如果没有名企和学历的光环,那么你就要用你做成功的事情来证明你自己确实出类拔萃。

综合,应聘者需要用一些东西来证明自己比其他应聘者更优秀,而且实力已经得到证明,而不是到新公司体现和证明。

By Long Luo @2017-11-28 at Shenzhen.

By Long Luo

约瑟夫问题( \(\textit{Josephus Problem}\) )是每个学计算机的同学都会遇到的经典编程题,可以考察链表、递归、数学等知识,下面我们就来通过这道题对好好学习下算法和编程吧,Let’s go!

一、什么是约瑟夫问题?

据说著名犹太历史学家 \(\textit{Josephus}\) 有过以下的故事:

在罗马人占领乔塔帕特后,\(39\) 个犹太人与 \(\textit{Josephus}\) 及他的朋友躲到一个洞中,\(39\) 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,\(41\) 个人排成一个圆圈,由第 \(1\) 个人开始报数,每报数到第 \(3\) 人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

然而 \(\textit{Josephus}\) 和他的朋友并不想遵从这个规则,\(\textit{Josephus}\) 要他的朋友先假装遵从,他将朋友与自己安排在第 \(16\) 个与第 \(31\) 个位置,于是逃过了这场死亡游戏。

二、约瑟夫问题算法分析

约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与 \(N\) 个朋友不幸参与了这个游戏,您要如何保护您与您的朋友?

只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:

图1. 约瑟夫问题游戏顺序 Josephus Problem

假如使用编程来求解的话,只要将 \(\textit{array}\) 当作环状来处理就可以了,在 \(\textit{array}\) 中由计数 \(1\) 开始,每找到 \(3\) 个无数据区就填入 \(1\) 个计数,直到计数达 \(41\) 为止,然后将 \(\textit{array}\) 由索引 \(1\) 开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,\(41\) 个人而报数 \(3\) 的约琴夫排列如下所示:

14 36 1 38 15 2 24 30 3 16 34 425 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23

由上可知,最后一个自杀的是在第 \(31\) 个位置,而倒数第二个自杀的要排在第 \(16\) 个位置,而之前的人都死光了,所以他们也就不知道约瑟夫与他的朋友并没有遵守游戏规则了。

这是一道经典算法问题,每个学编程的同学都会遇到。一般常见的问法有以下几种:

  1. 输出自杀顺序;
  2. 输出最后一个自杀同学编号。

下面我们就来动手实践下,具体实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private static int Josephus(int n, int m) {
int[] peopleArr = new int[n];
for (int i = 0; i < n; i++) {
peopleArr[i] = i + 1;
}

int index = 0;
int length = n;
int count = 1;

while (length > 0) {
if (peopleArr[index % n] > 0) {
if (count % m == 0) {
// 找到要出圈的人,并把圈中人数减1
System.out.print(peopleArr[index % n] + " ");
peopleArr[index % n] = -1;
count = 1;
index++;
length--;
} else {
index++;
count++;
}
} else {
// 遇到空位了,就跳到下一位,但count不加1,也就是这个位置没有报数
index++;
}
}

return index;
}
阅读全文 »

By Long Luo

最近在部门内部做了一次知识分享,关于Java反射,因此有了这篇文章:《5分钟学会Java反射》。这篇文章篇幅不长,用了大量示例,力求在很短的时间里让大家明白Java反射知识。

关于 Java 反射,我们需要弄懂以下几个问题:

  1. 反射是什么?
  2. 反射有什么用?
  3. 怎么用反射?

下面我们来一一进行讲解:

一、反射是什么?

Reflection 的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性及方法;对于任何一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象的方法的功能称为Java的反射机制。

1. 自省(Introspection) vs. 反射(Reflection)

反射经常和自省弄混,为了区别,我们先看看两者的详细定义:

自省(Introspection):

Introspection is the ability of a program to examine the type or properties of an object at runtime.

反射(Reflection):

Reflection is the ability of a program to examine and modify the structure and behavior of an object at runtime.

从上述定义,我们可以看出,自省是反射的子集。部分语言支持自省,但是不支持反射,比如C++。

2. 自省示例 vs. 反射示例

自省示例: instanceof操作符用于判断一个对象是否属于一个特定的类。

1
2
3
4
if(obj instanceof Dog) {
Dog d = (Dog)obj;
d.bark();
}

反射实例: Class.forName()方法返回了一个具体类/接口的对象,当然参数需要指定为特定的类名。

1
2
3
4
5
6
// with reflection
Class <?> c = Class.forName("classpath.and.classname");
Object dog = c.newInstance();

Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
m.invoke(dog);

二、 为什么需要反射?

Java反射在框架开发中尤为重要。有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如JDBC),这是反射用得比较多的场景。

编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如ORM框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。

反射机制提供的功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的方法。通过反射甚至可以调用到private的方法;
  5. 在运行时修改构造函数,变量和方法的访问权限。

解耦

假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译

在对类的调用和实例化的时候,通过在配置文件中配置相应的类名,在程序中读取类名,然后通过反射技术在程序中加载和实例化,如常见的数据库驱动程序类,为了达到不依赖特定数据库驱动类,将用到的数据库驱动类名放到配置文件中(常用的有XML文件、Properties文件和文本文件),然后在程序中加载驱动,来实现对数据库的解耦,也就是说只要修改配置文件,就可以方便地更改数据库类型。

例如, Spring使用如下的bean配置:

1
2
3
<bean id="someID" class="com.programcreek.Foo">
<property name="someField" value="someValue"/>
</bean>

Spring在处理<bean>时,会使用Class.forName(String),同时参数为"com.xxx.Foo"用于实例化这个Class。同时,使用反射设置<property>去用于设置特定的值。

这种机制同样也用于Servlet的web应用:

1
2
3
4
<servlet>
<servlet name>someServlet </s e rvl e t􀀀name>
<servlet class>com.programcreek.WhyReflectionServlet</servlet class>
<servlet>
阅读全文 »
0%