几个有意思的java小题目

null + String

写出下面代码执行结果:

1
2
3
4
5
6
7
// 1. 打印 null String
String s = null;
System.out.println(s);

String str = null;
str = str + "!";
System.out.println(str);

这个片段程序不会出现NPE,正常输出:

1
2
null
null!

我一开始以为第二个输出会抛出NPE。google了一下,看到了这篇文章Java String 对 null 对象的容错处理, 里面有解释:

对于代码片段:

1
2
3
String s = null;
s = s + "!";
System.out.print(s);

编译器生成的字节码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
L0
LINENUMBER 27 L0
ACONST_NULL
ASTORE 1
L1
LINENUMBER 28 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "!"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 1
L2
LINENUMBER 29 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V

这其中涉及到+字符串拼接的原理了:编译器对字符串相加会进行优化,首先实例化一个StringBuilder,然后把相加的字符串按顺序append,最后调用toString返回一个String对象。不信你们看看上面的字节码是不是出现了StringBuilder。因此:

1
2
3
4
5
6
 String s = "a" + "b";
//等价于
StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
String s = sb.toString();

再回到我们的问题,现在我们知道秘密在StringBuilder.append函数的源码中:

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
//针对 String 对象
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//针对非 String 对象
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}

null cast

下面程序的输出结果为:Hello

1
2
3
4
5
6
7
8
9
public class Example {
private static void sayHello() {
System.out.println("Hello");
}

public static void main(String[] args) {
((Example)null).sayHello();
}
}

null作为非基本类型,可以做类型转换,转换后调用静态方法输出字符串。
基本类型,比如int,类型转换时会报告空指针异常,比如 int a = (Integer)null; 原因就是转换过程中会调用intValue(),因此会报告异常。

String 常量池

String s3 = new String(“Cat”) 这句代码会创建几个 String 对象

如果在执行语句之前String常量池中没有Cat字符串,那么会创建2个String;反之只创建1个 String对象。这里涉及到String的常量池。

深入java字符串常量池

参考文章:

Java中的堆和栈

堆和栈都是Java用来在RAM中存放数据的地方。

  • Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。

  • 堆的优势是可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的。但缺点是,由于需要在运行时动态分配内存,所以存取速度较慢。

  • 堆内存满的时候抛出java.lang.OutOfMemoryError: Java Heap Space错误
  • 可以使用-Xms-Xmx JVM选项定义开始的大小和堆内存的最大值
  • 存储在堆中的对象是全局可以被其他线程访问的

  • 栈中主要存放一些基本数据类型的变量(byte,short,int,long,float,double,boolean,char)和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(对象可能在常量池里)(字符串常量对象存放在常量池中。)。
  • 栈的优势是,存取速度比堆快,栈数据可以共享。但缺点是,存放在栈中的数据占用多少内存空间需要在编译时确定下来,缺乏灵活性。
  • 当栈内存满的时候,Java抛出java.lang.StackOverFlowError
  • 和堆内存比,栈内存要小的多
  • 明确使用了内存分配规则(LIFO)
  • 可以使用-Xss定义栈的大小
  • 栈内存不能被其他线程所访问。

静态域

存放静态成员(static定义的)

常量池

存放字符串常量和基本类型常量(public static final

举例说明栈数据可以共享

String 可以用以下两种方式来创建:

1
2
String str1 = newString("abc");
String str2 = "abc";

第一种使用new来创建的对象,它存放在堆中。每调用一次就创建一个新的对象。

第二种是先在栈中创建对象的引用str2,然后查找栈中有没有存放“abc”,如果没有,则将“abc”存放进栈,并将str2指向“abc”,如果已经有“abc”, 则直接将str2指向“abc”。

1
2
3
4
5
public static void main(String[] args) {
String str1 = newString("abc");
String str2 = newString("abc");
System.out.println(str1 == str2);
}

输出结果为:false

1
2
3
4
5
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);
}

输出结果为:true

java中创建Completed future

在Java中如何创建Completed future呢?

Java8中可以Future future = CompletableFuture.completedFuture(value);
Guava中可以Futures.immediateFuture(value)
Apache commons Lang中可以Future<T> future = ConcurrentUtils.constantFuture(T myValue);

MAT对象之间的差异浅堆和保留堆

原文地址:SHALLOW HEAP, RETAINED HEAP

更贴切的标题应该是Difference between Eclipse MAT objects Shallow Heap and Retained Heap

Eclipse MAT (Memory Analyzer Tool) is a powerful tool to analyze heap dumps. It comes quite handy when you are trying to debug memory related problems. In Eclipse MAT two types of object sizes are reported:

  • Shallow Heap
  • Retained Heap

In this article lets study the difference between them. Let’s study how are they calculated?

Shallow-heap-1.png

It’s easier to learn new concepts through example. Let’s say your application’s has object model as shown in Fig #1:

  • Object A is holding reference to objects B and C.
  • Object B is holding reference to objects D and E.
  • Object C is holding reference to objects F and G.

Let’s say each object occupies 10 bytes of memory. Now with this context let’s begin our study.

Shallow Heap size

Shallow heap of an object is its size in the memory. Since in our example each object occupies 10 bytes, shallow heap size of each object is 10 bytes. Very simple.

Retained Heap size of B

From the Fig #1 you can notice that object B is holding reference to objects D and E. So, if object B is garbage collected from memory, there will be no more active references to object D and E. It means D & E can also be garbage collected. Retained heap is the amount of memory that will be freed when the particular object is garbage collected. Thus, retained heap size of B is:

= B’s shallow heap size + D’s shallow heap size + E’s shallow heap size

= 10 bytes + 10 bytes + 10 bytes

= 30 bytes

Thus, retained heap size of B is 30 bytes.

Retained Heap size of C

Object C is holding reference to objects F and G. So, if object C is garbage collected from memory, there will be no more references to object F & G. It means F & G can also be garbage collected. Thus, retained heap size of C is:

= C’s shallow heap size + F’s shallow heap size + G’s shallow heap size

= 10 bytes + 10 bytes + 10 bytes

= 30 bytes

Thus, retained heap size of C is 30 bytes as well

shallow-heap-2-1.png

Retained Heap size of A

Object A is holding reference to objects B and C, which in turn are holding references to objects D, E, F, G. Thus, if object A is garbage collected from memory, there will be no more reference to object B, C, D, E, F and G. With this understanding let’s do retained heap size calculation of A.

Thus, retained heap size of A is:

= A’s shallow heap size + B’s shallow heap size + C’s shallow heap size + D’s shallow heap size + E’s shallow heap size + F’s shallow heap size + G’s shallow heap size

= 10 bytes + 10 bytes + 10 bytes + 10 bytes + 10 bytes + 10 bytes + 10 bytes

= 70 bytes

Thus, retained heap size of A is 70 bytes.

Retained heap size of D, E, F and G

Retained heap size of D is 10 bytes only i.e. their shallow size only. Because D don’t hold any active reference to any other objects. Thus, if D gets garbage collected no other objects will be removed from memory. As per the same explanation objects E, F and G’s retained heap size are also 10 bytes only.

Let’s make our study more interesting

Now let’s make our study little bit more interesting, so that you will gain thorough understanding of shallow heap and retained heap size. Let’s have object H starts to hold reference to B in the example. Note object B is already referenced by object A. Now two guys A and H are holding references to object B. In this circumstance lets study what will happen to our retained heap calculation.

Shallow-heap-3-1.png

In this circumstance retained heap size of object A will go down to 40 bytes. Surprising? Puzzling? 🙂 continue reading on. If object A gets garbage collected, then there will be no more reference to objects C, F and G only. Thus, only objects C, F and G will be garbage collected. On the other hand, objects B, D and E will continue to live in memory, because H is holding active reference to B. Thus B, D and E will not be removed from memory even when A gets garbage collected.

Thus, retained heap size of A is:

= A’s shallow heap size + C’s shallow heap size + F’s shallow heap size + G’s shallow heap size

= 10 bytes + 10 bytes + 10 bytes + 10 bytes

= 40 bytes.

Thus, retained heap size of A will become 40 bytes. All other objects retained heap size will remain undisturbed, because there is no change in their references.

Hope this article helped to clarify Shallow heap size and Retained heap size calculation in Eclipse MAT. You might also consider exploring HeapHero – another powerful heap dump analysis tool, which shows the amount of memory wasted due to inefficient programming practices such as duplication of objects, overallocation and underutilization of data structures, suboptimal data type definitions,….

RuntimeException in Action for tag [rollingPolicy] java.lang.IndexOutOfBoundsException: No group 1

今天一个同事咨询我,他们使用logback发布的时候出现下面的异常:

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
20:46:27,244 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@36:25 - RuntimeException in Action for tag [rollingPolicy] java.lang.IndexOutOfBoundsException: No group 1
at java.lang.IndexOutOfBoundsException: No group 1
at at java.util.regex.Matcher.group(Matcher.java:538)
at at ch.qos.logback.core.rolling.helper.FileFilterUtil.extractCounter(FileFilterUtil.java:109)
at at ch.qos.logback.core.rolling.helper.FileFilterUtil.findHighestCounter(FileFilterUtil.java:93)
at at ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP.computeCurrentPeriodsHighestCounterValue(SizeAndTimeBasedFNATP.java:65)
at at ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP.start(SizeAndTimeBasedFNATP.java:49)
at at ch.qos.logback.core.rolling.TimeBasedRollingPolicy.start(TimeBasedRollingPolicy.java:90)
at at ch.qos.logback.core.joran.action.NestedComplexPropertyIA.end(NestedComplexPropertyIA.java:167)
at at ch.qos.logback.core.joran.spi.Interpreter.callEndAction(Interpreter.java:317)
at at ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:196)
at at ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:182)
at at ch.qos.logback.core.joran.spi.EventPlayer.play(EventPlayer.java:62)
at at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:149)
at at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:135)
at at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:99)
at at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:49)
at at ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:75)
at at ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:148)
at at org.slf4j.impl.StaticLoggerBinder.init(StaticLoggerBinder.java:85)
at at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:55)
at at org.slf4j.LoggerFactory.bind(LoggerFactory.java:128)
at at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:107)
at at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:295)
at at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:269)
at at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:281)

我当时看一下异常堆栈,基本可以确定是logback.xml配置的不正确导致的,贴一下关键的logback.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
<appender name="accessAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/request.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/request.log.%d{yyyy-MM-dd}.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>200MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]WTraceId[%X{wtraceid}] %5p %logger{0}:%L] %msg%n</pattern>
</encoder>
</appender>

排查后发现其中的问题所在了,因为使用了maxFileSize来指定文件轮转大小了,所以需要增加一个counter,也就是增加%ifileNamePattern中。
修改之后正确的配置为:

1
2
3
4
5
6
7
8
9
10
11
12
<appender name="accessAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/request.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/request.log.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>200MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]WTraceId[%X{wtraceid}] %5p %logger{0}:%L] %msg%n</pattern>
</encoder>
</appender>

Error:java: 无效的标记: -version 解决

使用最新版Intellij IDEA以后,编译项目出现Error:java: 无效的标记: -version 解决

后来排查发现是因为公司的super-pom中的maven-compiler-pluginconfiguration有如下的配置:

1
2
3
4
 <compilerArgs>
- <arg>-J-Duser.country=US</arg>
- <arg>-version</arg>
</compilerArgs>

而这个配置会和新版本的idea冲突。把pom中的这个配置删除就好了

Spring中Enum的依赖注入

Spring 依赖注入很简单,没什么值得细说的。但是我之前遇到了一个场景,需要在一个Enum类中注入某一个service。
说实话之前没有遇到过这种情况。虽然我不赞同Enum类有过多的逻辑,但是没有办法,现实就是那么残酷。而且Enum确实可以通过一些手段来注入其他发service的。
比如下面的代码中,为EnumClass枚举类注入OtherService服务,代码示例如下:

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
public enum EnumClass {
A(1), B(2);
EnumClass(int id) {
this.id = id;
}
private int id;
private OtherService otherService;
public int getId() {
return id;
}
public OtherService getOtherService() {
return otherService;
}
public void setOtherService(OtherService otherService) {
this.otherService = otherService;
}
public ResponseType func(){
//use otherService do somethings
return new ResponseType();
}
@Component
public static class EnumClassInner {
@Autowired
private OtherService otherService;
@PostConstruct
public void postConstruct() {
for (EnumClass aEnum : EnumSet.allOf(EnumClass.class))
aEnum.setOtherService(otherService);
}
}
}

java中的日期pattern

经常搞混java中的日期pattern,比如经常记混Hh的区别,所以专门整理一下,便于我以后查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区
```java

常用pattern:

```java
yyyy-MM-dd HH:mm:ss.SSS
yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

常用时区:

1
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT+8")

日期和字符串互转:

1
2
3
4
5
6
private final static DateTimeFormatter fmt1 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
DateTime dateTime = DateTime.parse(date, fmt1)

new DateTime().toString("yyyy-MM-dd HH:mm:ss.SSS")

org.joda.time#Days

Java RSA非对称加密

最近的一个项目中,agent和master双方需要远程通信,但是需要双方认证以及传输的信息加密,因此就选择了RSA这个非对称加密算法实现了netty的handler。

##实现思路
简要的描述一下实现思路:

  • 首先生成一对公钥和私钥
  • 所有的master都使用这个私钥进行加密、解密
  • 所有的agent都使用这个公钥进行加密和解密
  • master发给agent的信息,使用私钥加密,master收到agent的信息,使用私钥解密
  • agent发给master的信息,使用公钥加密,agent收到master的信息,使用公钥解密
  • 无论是agent还是master,对收到的信息,只要解密失败,那么就丢弃

这样相当于实现了agent和master的认证,以及消息的加密传输。挺有意思的。

##生成公钥私钥

###使用java代码生成:

1
2
3
4
5
6
7
8
9
10
private static final String RSA = "RSA";
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
KeyPair keyPair = buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

###shell生成
我在这个项目实现中,是别生成了公钥文件和私钥文件,作为了工程的配置文件来用的,因此使用了shell的命令:

1
2
3
ssh-keygen -t rsa -b 2048 -C "any string"
openssl pkcs8 -topk8 -inform PEM -outform DER -in id_rsa -out private_key.der -nocrypt
openssl rsa -in id_rsa -pubout -outform DER -out public_key.der

命令解释:

  • 第一条命令会生成2048位的rsa的公钥和私钥文件。这个2048可以自己修改,但是最小是1024,位数越大,加密越好,但是加密和解密的速度会更慢。在生成的过程中会询问你文件的路径,我是存放在了当前目录,因此当前目录下会有:id_rsaid_rsa.pub这两个文件。
  • 第二条命令生成java程序可用的私钥文件,其实就是将id_rsa转换为PKCS#8格式,这样java程序能够读取它
  • 第三条命令生成java程序可用的公钥文件,其实以DER格式输出公钥部分,这样java程序可用读取它

这3条命令执行以后,当前目录下会有4个文件:

1
id_rsa          id_rsa.pub      private_key.der public_key.der

我们程序中使用的是后面2个,将其放在代码的resources目录下。

###RSA实现
关于rsa的java实现,直接给出代码吧:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.qunar.qcloudagent.common.encrypt;
import com.google.common.base.Throwables;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAUtil {
private static final String RSA = "RSA";
private static final PrivateKey PRIVATE_KEY = readPrivateKey(
RSAUtil.class.getResource("/").getPath().concat("private_key.der"));
private static Cipher cipher;
private final static PublicKey PUBLIC_KEY = readPublicKey(
RSAUtil.class.getResource("/").getPath().concat("public_key.der"));
static {
try {
cipher = Cipher.getInstance(RSA);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static byte[] encryptWithPrivateKey(byte[] message) throws Exception {
cipher.init(Cipher.ENCRYPT_MODE, PRIVATE_KEY);
return blockCipher(message, Cipher.ENCRYPT_MODE);
}
public static byte[] decryptWithPrivateKey(byte[] encrypted) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, PRIVATE_KEY);
return blockCipher(encrypted, Cipher.DECRYPT_MODE);
}
public static byte[] encryptWithPublicKey(byte[] message) throws Exception {
cipher.init(Cipher.ENCRYPT_MODE, PUBLIC_KEY);
return blockCipher(message, Cipher.ENCRYPT_MODE);
}
public static byte[] decryptWithPublicKey(byte[] encrypted) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, PUBLIC_KEY);
return blockCipher(encrypted, Cipher.DECRYPT_MODE);
}
private static PublicKey readPublicKey(String filename) {
try {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePublic(spec);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
private static PrivateKey readPrivateKey(String filename) {
try {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(RSA);
return kf.generatePrivate(spec);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* 具体为啥这么做,请看文章:{@see http://coding.westreicher.org/?p=23}
*/
private static byte[] blockCipher(byte[] bytes, int mode) throws IllegalBlockSizeException, BadPaddingException {
// string initialize 2 buffers.
// scrambled will hold intermediate results
byte[] scrambled = new byte[0];
// toReturn will hold the total result
byte[] toReturn = new byte[0];
// if we encrypt we use 100 byte long blocks. Decryption requires 128 byte long blocks (because of RSA)
int length = (mode == Cipher.ENCRYPT_MODE) ? 100 : 128;
// another buffer. this one will hold the bytes that have to be modified in this step
byte[] buffer = new byte[length];
for (int i = 0; i < bytes.length; i++) {
// if we filled our buffer array we have our block ready for de- or encryption
if ((i > 0) && (i % length == 0)) {
//execute the operation
scrambled = cipher.doFinal(buffer);
// add the result to our total result.
toReturn = append(toReturn, scrambled);
// here we calculate the length of the next buffer required
int newlength = length;
// if newlength would be longer than remaining bytes in the bytes array we shorten it.
if (i + length > bytes.length) {
newlength = bytes.length - i;
}
// clean the buffer array
buffer = new byte[newlength];
}
// copy byte into our buffer.
buffer[i % length] = bytes[i];
}
// this step is needed if we had a trailing buffer. should only happen when encrypting.
// example: we encrypt 110 bytes. 100 bytes per run means we "forgot" the last 10 bytes. they are in the buffer array
scrambled = cipher.doFinal(buffer);
// final step before we can return the modified data.
toReturn = append(toReturn, scrambled);
return toReturn;
}
private static byte[] append(byte[] toReturn, byte[] scrambled) {
byte[] destination = new byte[toReturn.length + scrambled.length];
System.arraycopy(toReturn, 0, destination, 0, toReturn.length);
System.arraycopy(scrambled, 0, destination, toReturn.length, scrambled.length);
return destination;
}
}

关于上面的代码有一点需要指出的是:RSA加密明文最大长度117字节,解密要求密文最大长度为128字节,所以在加密和解密的过程中需要分块进行。 RSA加密对明文的长度是有限制的,如果加密数据过大会抛出如下异常:Exception in thread “main” javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes。因此上面的blockCipher是用来专门处理这种情况的。

##参考文章

自己实现LRU Cache

今天闲的无事,写一个LRU的缓存练练手,不过提前说好哈,最好的办法是直接使用Guava中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    import com.google.common.cache.CacheBuilder;
import java.util.concurrent.ConcurrentMap;
public class GuavaLRUCache {
public static void main(String[] args) {
ConcurrentMap<String, String> cache =
CacheBuilder.newBuilder()
.maximumSize(2L)
.<String, String>build().asMap();
cache.put("a", "b");
cache.put("b", "c");
System.out.println(cache);
cache.put("a", "d");
System.out.println(cache);
cache.put("c", "d");
System.out.println(cache);
}
}

如果自己实现的话,就比较挫了:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class LRUCache<K, V> {
private final int limit;
private final Map<K, V> cache;
private final Deque<K> deque;
private final ReentrantLock reentrantLock = new ReentrantLock();
private static final int DEFAULT_CAPACITY = 16;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
public LRUCache(int limit) {
this.limit = limit;
deque = new ArrayDeque<K>(limit);
cache = new ConcurrentHashMap<K, V>(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public LRUCache(int limit, int capacity, float loadFactor) {
this.limit = limit;
deque = new ArrayDeque<K>(limit);
cache = new ConcurrentHashMap<K, V>(capacity, loadFactor);
}
public void put(K key, V value) {
V v = cache.get(key);
reentrantLock.lock();
try {
if(v == null){
cache.put(key, value);
deque.removeFirstOccurrence(key);
deque.addFirst(key);
} else {
deque.removeFirstOccurrence(key);
deque.addFirst(key);
}
if(size() > limit){
K key1 = deque.removeLast();
if(key1 != null){
cache.remove(key1);
}
}
}finally {
reentrantLock.unlock();
}
}
public V get(K key) {
V v = cache.get(key);
if(v != null){
reentrantLock.lock();
try {
deque.removeFirstOccurrence(key);
deque.addFirst(key);
} finally {
reentrantLock.unlock();
}
}
return v;
}
public int size() {
return cache.size();
}
@Override
public String toString() {
List<String> values = Lists.newArrayListWithExpectedSize(limit);
reentrantLock.lock();
try {
for (K k : deque) {
V v = cache.get(k);
values.add(k.toString() + " -> " + v.toString());
}
System.out.println(deque);
} finally {
reentrantLock.unlock();
}
return values.toString();
}
//other apis...
public static void main(String[] args) {
LRUCache<String, String> cache = new LRUCache<String, String>(3);
cache.put("key1", "value1");
cache.put("key2", "value2");
cache.put("key3", "value3");
System.out.println(cache);
cache.put("key4", "value4");
System.out.println(cache);
cache.put("key2", "value2");
System.out.println(cache);
cache.put("key4", "value4");
System.out.println(cache);
cache.put("key1", "value1");
System.out.println(cache);
}
}

参考资料

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×