Java图片处理工具类

这段代码是我四年前写的,当时的使用场景为使用tesseract做图片的预处理。功能包含图片二值化、移除杂色、横向切分、水平切分等。

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
import java.awt.Color;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
import java.util.ArrayList;
import java.util.List;

/**
* @author Gao Youbo
* @since 2014-05-29 14:34:13
*/
public class ImageUtils {

public static class SplitItem {

private int x;
private int w;
private int y;
private int h;

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getW() {
return w;
}

public void setW(int w) {
this.w = w;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public int getH() {
return h;
}

public void setH(int h) {
this.h = h;
}

}


/**
* 图片纵向切分(切分为列)
*
* @param image
* @param minWidth 每个汉字的最小宽度,如果汉字的最小宽度小于该参数,那么认为系统将一个汉字截断了
* @return
*/
public static List<BufferedImage> splitLengthwaysWithMinWidth(BufferedImage image, int minWidth) {
if (minWidth < 0) {
minWidth = 0;
}
List<BufferedImage> subImgs = new ArrayList<>();
int width = image.getWidth();
int height = image.getHeight();
int startX = 0;
int endX = 0;
boolean start = false;
boolean end = false;
for (int x = 0; x < width; ++x) {
boolean blank = isXBlank(image, x);
if (!start) { //如果是白色
int space = spaceX(image, x);
x = x + space;
startX = x;
endX = x;
start = true;
}
if (start && !blank) {
endX = x;
}
int wordLength = endX - startX;
if (start && blank && wordLength > 0) {
// 汉字长度小于设定长度,那么认为这不是一个完成的汉字,而是将左右结构的汉字切分成了两份
if (wordLength < minWidth) {
int space = spaceX(image, x);
x = x + space;
} else {
end = true;
endX = x;
}
}
if (start && end && wordLength > 0) {
BufferedImage subImage = image.getSubimage(startX, 0, (endX - startX), height);
subImgs.add(subImage);
start = false;
end = false;
}
}
return subImgs;
}

/**
* x轴上的所有点是空白的(白色的)
*
* @param image
* @param x
* @return
*/
private static boolean isXBlank(BufferedImage image, int x) {
int height = image.getHeight();
for (int y = 0; y < height; y++) {
int rgb = image.getRGB(x, y);
if (isBlack(rgb)) {
return false;
}
}
return true;
}

/**
* 图片纵向切分(切分为列)
*
* @param image
* @param minGap 文字之间的最小间隙,如果间隙文字之间的间隙小于或等于该参数,那么认为该间隙为一个汉字上的正常间隙。主要处理左右结构的一些汉字,例如:”北、川、外...“
* @return
*/
public static List<BufferedImage> splitLengthways(BufferedImage image, int minGap) {
if (minGap < 0) {
minGap = 0;
}
List<BufferedImage> subImgs = new ArrayList<>();
int width = image.getWidth();
int height = image.getHeight();
List<Integer> weightlist = new ArrayList<>();
for (int x = 0; x < width; ++x) {
int count = 0;
for (int y = 0; y < height; ++y) {
if (isBlack(image.getRGB(x, y))) {
count++;
}
}
if (minGap > 0) {
int space = spaceX(image, x);
if (space <= minGap) {
count = count + space;
}
}
weightlist.add(count);
}
List<SplitItem> splitItems = new ArrayList<>();
for (int i = 0; i < weightlist.size(); i++) {
int length = 0;
while (i < weightlist.size() && weightlist.get(i) > 0) {
i++;
length++;
}
if (length > 0) {
int x = i - length;
int w = length;
int y = 0;
int h = height;
SplitItem item = new SplitItem();
item.setX(x);
item.setW(w);
item.setY(y);
item.setH(h);
splitItems.add(item);
}
}
for (SplitItem splitItem : splitItems) {
subImgs.add(image.getSubimage(splitItem.getX(), splitItem.getY(), splitItem.getW(), splitItem.getH()));
}
return subImgs;
}

/**
* X轴上两个字之间的间距
*
* @param image
* @param currentX 当前索引所在的x坐标
* @return
*/
private static int spaceX(BufferedImage image, int currentX) {
int w = image.getWidth();
int h = image.getHeight();
int spaceLength = 0;
for (int x = currentX; x < w; x++) {
boolean space = true;
for (int y = 0; y < h; y++) {
if (isBlack(image.getRGB(x, y))) { //有黑色的,表明非空白
space = false;
break;
}
}
if (space) {
spaceLength++;
} else {
return spaceLength;
}
}
return spaceLength;
}


/**
* y轴上两个字之间的间距
*
* @param image
* @param currentY 当前索引所在的y坐标
* @return
*/
private static int spaceY(BufferedImage image, int currentY) {
int w = image.getWidth();
int h = image.getHeight();
int spaceLength = 0;
for (int y = currentY; y < h; y++) {
boolean space = true;
for (int x = 0; x < w; x++) {
if (isBlack(image.getRGB(x, y))) { //有黑色的,表明非空白
space = false;
break;
}
}
if (space) {
spaceLength++;
} else {
return spaceLength;
}
}
return spaceLength;
}


/**
* 图片横向切分(切分为行)
*
* @param image
* @param minGap 两行之间的最小间隙,如果间隙小于或等于该参数,那么认为没有折行
* @return
*/
public static List<BufferedImage> splitCrosswise(BufferedImage image, int minGap) {
if (minGap < 0) {
minGap = 0;
}
List<BufferedImage> subImgs = new ArrayList<>();
int w = image.getWidth();
int h = image.getHeight();
List<Integer> heightlist = new ArrayList<>();
for (int y = 0; y < h; y++) {
int count = 0;
for (int x = 0; x < w; x++) {
if (ImageUtils.isBlack(image.getRGB(x, y))) {
count++;
}
}
if (minGap > 0) {
int space = spaceY(image, y);
if (space <= minGap) {
count = count + space;
}
}
heightlist.add(count);
}
for (int i = 0; i < heightlist.size(); i++) {
int length = 0;
while (i < heightlist.size() && heightlist.get(i) > 0) {
i++;
length++;
}
if (length > 0) {
int y = i - length;
int x = 0;
int height = length;
int width = w;
BufferedImage bufferedImage = image.getSubimage(x, y, width, height);
subImgs.add(bufferedImage);
}
}
return subImgs;
}

/**
* 图片横向切分(切分为行)
*
* @param image
* @return
*/
public static List<BufferedImage> splitCrosswise(BufferedImage image) {
List<BufferedImage> subImgs = new ArrayList<>();
int w = image.getWidth();
int h = image.getHeight();
List<Integer> heightlist = new ArrayList<>();
for (int y = 0; y < h; y++) {
int count = 0;
for (int x = 0; x < w; x++) {
if (ImageUtils.isBlack(image.getRGB(x, y))) {
count++;
}
}
heightlist.add(count);
}
for (int i = 0; i < heightlist.size(); i++) {
int length = 0;
while (i < heightlist.size() && heightlist.get(i) > 0) {
i++;
length++;
}
if (length > 0) {
int y = i - length;
int x = 0;
int height = length;
int width = w;
BufferedImage bufferedImage = image.getSubimage(x, y, width, height);
subImgs.add(bufferedImage);
}
}
return subImgs;
}

/**
* 删除杂色(图片二值化)
* <p>
* 默认图片中字体颜色为黑色,如果非黑色像素全部替换为白色
*
* @param image
* @return
* @throws java.lang.InterruptedException
*/
public static final BufferedImage removeMotley(BufferedImage image) throws InterruptedException {
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
int grey = 100;
PixelGrabber pixelGrabber = new PixelGrabber(image.getSource(), 0, 0, width, height, pixels, 0, width);
pixelGrabber.grabPixels();
ColorModel cm = ColorModel.getRGBdefault();
for (int i = 0; i < width * height; i++) {
int red, green, blue;
int alpha = cm.getAlpha(pixels[i]);
if (cm.getRed(pixels[i]) > grey) {
red = 255;
} else {
red = 0;
}
if (cm.getGreen(pixels[i]) > grey) {
green = 255;
} else {
green = 0;
}
if (cm.getBlue(pixels[i]) > grey) {
blue = 255;
} else {
blue = 0;
}
pixels[i] = alpha << 24 | red << 16 | green << 8 | blue; //通过移位重新构成某一点像素的RGB值
}
//将数组中的象素产生一个图像
Image tempImg = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, pixels, 0, width));
image = new BufferedImage(tempImg.getWidth(null), tempImg.getHeight(null), BufferedImage.TYPE_INT_BGR);
image.createGraphics().drawImage(tempImg, 0, 0, null);
return image;
}

/**
* 清除空白部分
*
* @param image
* @return
*/
public static BufferedImage removeSpace(BufferedImage image) {
BufferedImage result = removeTBWhite(image);
return removeLRWhite(result);
}

/**
* 移除上下白色部分(top bottom)
*
* @param image
* @return
*/
public static BufferedImage removeTBWhite(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int start = 0;
int end = 0;
Label1:
for (int y = 0; y < height; ++y) {
int count = 0;
for (int x = 0; x < width; ++x) {
if (isBlack(image.getRGB(x, y))) {
count++;
}
if (count >= 1) {
start = y;
break Label1;
}
}
}
Label2:
for (int y = height - 1; y >= 0; --y) {
int count = 0;
for (int x = 0; x < width; ++x) {
if (isBlack(image.getRGB(x, y))) {
count++;
}
if (count >= 1) {
end = y;
break Label2;
}
}
}
return image.getSubimage(0, start, width, end - start + 1);
}

/**
* 移除左右白色部分(left right)
*
* @param image
* @return
*/
public static BufferedImage removeLRWhite(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int start = 0;
int end = 0;
Label1:
for (int x = 0; x < width; ++x) {
int count = 0;
for (int y = 0; y < height; ++y) {
if (isBlack(image.getRGB(x, y))) {
count++;
}
if (count >= 1) {
start = x;
break Label1;
}
}
}
Label2:
for (int x = width - 1; x >= 0; --x) {
int count = 0;
for (int y = height - 1; y >= 0; --y) {
if (isBlack(image.getRGB(x, y))) {
count++;
}
if (count >= 1) {
end = x;
break Label2;
}
}
}
return image.getSubimage(start, 0, end - start + 1, height);
}

/**
* 移除黑色部分
*
* @param img
* @return
*/
public static BufferedImage removeBlack(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
int start = 0;
int end = 0;
Label1:
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
if (isBlack(img.getRGB(x, y))) {
start = y;
break Label1;
}
}
}
Label2:
for (int y = height - 1; y >= 0; --y) {
for (int x = 0; x < width; ++x) {
if (isBlack(img.getRGB(x, y))) {
end = y;
break Label2;
}
}
}
return img.getSubimage(0, start, width, end - start + 1);
}

/**
* 是否是黑色
*
* @param colorInt
* @return
*/
public static boolean isBlack(int colorInt) {
Color color = new Color(colorInt);
return color.getRed() + color.getGreen() + color.getBlue() <= 100;
}

/**
* 是否是白色
*
* @param colorInt
* @return
*/
public static boolean isWhite(int colorInt) {
Color color = new Color(colorInt);
return color.getRed() + color.getGreen() + color.getBlue() > 100;
}
}

小米手机安装Charles证书

平时使用Charles进行接口抓包,新换小米手机之后发现按照之前的流程安装Charles ssl证书不好使,百度了好久才找到一下解决办法。

  • 使用第三方浏览器(我用的是QQ浏览器)下载.pem 格式的文件
  • 将这个文件放入小米的Download文件夹下
  • 将.pem文件修改为.crt 格式
  • 设置—更多设置—系统安全—加密与凭据—从存储设备安装–选择Download文件夹下的文件
  • Finish~~

javacv使用笔记

使用过程中遇到的异常

异常:Could not initialize class org.bytedeco.javacpp.avutil

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.bytedeco.javacpp.avutil
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:274)
at org.bytedeco.javacpp.Loader.load(Loader.java:385)
at org.bytedeco.javacpp.Loader.load(Loader.java:353)
at org.bytedeco.javacpp.avformat$AVFormatContext.<clinit>(avformat.java:2249)
at org.bytedeco.javacv.FFmpegFrameGrabber.startUnsafe(FFmpegFrameGrabber.java:346)
at org.bytedeco.javacv.FFmpegFrameGrabber.start(FFmpegFrameGrabber.java:340)

解决办法:

1
mvn package exec:java -Dplatform.dependencies -Dexec.mainClass=Demo

警告:Warning: data is not aligned! This can lead to a speedloss

出现这个警告是因为ffmpeg要求视频的宽度必须是32的倍数,高度必须是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
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
109
110
111
112
113
114
115
116
117
118
119
120
import com.google.common.collect.Lists;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.bytedeco.javacpp.avcodec;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_imgcodecs;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.List;

/**
* @author Gao Youbo
* @since 2018-08-15 16:43
*/
public class OpenCVUtils {
public static void main(String[] args) throws Exception {
List<BufferedImage> images = grab(new File("/data/opencv/test.mp4"));
int i = 1;
for (BufferedImage image : images) {
ImageIO.write(image, "jpg", new File("/data/opencv/frame/" + i + ".jpg"));
i++;
}

// grabAudioFromVideo(new File("/data/opencv/test.mp4"), new File("/data/opencv/test.aac"));

List<File> files = Lists.newArrayList(FileUtils.listFiles(new File("/data/opencv/frame/"), new String[]{"jpg"}, false));
Collections.sort(files, (o1, o2) -> {
int i1 = NumberUtils.toInt(StringUtils.substringBefore(o1.getName(), "."));
int i2 = NumberUtils.toInt(StringUtils.substringBefore(o2.getName(), "."));
return Integer.compare(i1, i2);
});
record("/data/opencv/out.mp4", files, new File("/data/opencv/test.aac"), 544, 960);
}

/**
* 将多个图片文件合成视频
*
* @param output 输出文件
* @param images 序列帧图片
* @param audioFile 音频
* @param width 宽
* @param height 高
* @throws Exception
*/
public static void record(String output, List<File> images, File audioFile, int width, int height) throws Exception {
try (FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(output, width, height);
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(audioFile)) {
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat("mp4");
recorder.setFrameRate(30);
recorder.setAudioBitrate(192000);
recorder.setAudioQuality(0);
recorder.setSampleRate(44100);
recorder.setAudioChannels(2);
recorder.start();

OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
for (File file : images) {
opencv_core.IplImage image = opencv_imgcodecs.cvLoadImage(file.getPath());
recorder.record(converter.convert(image));
opencv_core.cvReleaseImage(image);
}

grabber.start();
Frame frame;
while ((frame = grabber.grabSamples()) != null) {
recorder.setTimestamp(frame.timestamp);
recorder.recordSamples(frame.sampleRate, frame.audioChannels, frame.samples);
}
}
}

/**
* 从视频中将每一帧的图片提取出来
*
* @param video
* @return
* @throws FrameGrabber.Exception
*/
public static List<BufferedImage> grab(File video) throws Exception {
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(video.getPath())) {
grabber.start();

List<BufferedImage> images = Lists.newArrayList();
Frame frame;
while ((frame = grabber.grabImage()) != null) {
images.add(Java2DFrameUtils.toBufferedImage(frame));
}
return images;
}
}

/**
* 从视频中提取出音频
*
* @param video
* @param outputAudio
*/
public static void grabAudioFromVideo(File video, File outputAudio) throws Exception {
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(video);
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputAudio, 1)) {
grabber.start();
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.start();

Frame frame;
while ((frame = grabber.grab()) != null) {
if (frame.audioChannels == 1) {
recorder.recordSamples(frame.sampleRate, frame.audioChannels, frame.samples);
}
}
}
}

}

图片合成视频简单的封装

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
private static class VideoRecorder implements Closeable {
private FFmpegFrameRecorder recorder;

public VideoRecorder(String output, int width, int height) throws FrameRecorder.Exception {
recorder = new FFmpegFrameRecorder(output, width, height);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat("mp4");
recorder.setFrameRate(FPS);
recorder.setAudioBitrate(192000);
recorder.setAudioQuality(0);
recorder.setSampleRate(44100);
recorder.setAudioChannels(2);
recorder.start();
}

public void addFrame(BufferedImage image) throws FrameRecorder.Exception {
Frame frame = Java2DFrameUtils.toFrame(image);
recorder.record(frame, avutil.AV_PIX_FMT_ARGB);
}

public void addAudio(File audioFile) throws FrameGrabber.Exception, FrameRecorder.Exception {
if (audioFile == null || !audioFile.exists()) {
return;
}
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(audioFile)) {
grabber.start();
Frame frame;
while ((frame = grabber.grabSamples()) != null) {
recorder.recordSamples(frame.sampleRate, frame.audioChannels, frame.samples);
}
}
}

@Override
public void close() throws IOException {
recorder.close();
}
}

解决maven打包时将不必要的包引入进来的问题

我在实际使用中只用到了ffmpeg,但是打包的时候却将flycapture、libdc1394、libfreenect、artoolkitplus、tesseract…等包都打进来了,这些都是我不需要的,下面贴出我的maven配置示例。

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
<properties>
<javacpp.version>1.4.2</javacpp.version>
<!-- 这里要根据自己的平台选择不同的依赖 -->
<!--<javacpp.platform.dependencies>linux-x86_64</javacpp.platform.dependencies>-->
<javacpp.platform.dependencies>macosx-x86_64</javacpp.platform.dependencies>
</properties>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>${javacpp.version}</version>
<exclusions>
<exclusion>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>opencv</artifactId>
<version>3.4.2-${javacpp.version}</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.0.1-${javacpp.version}</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.0.1-${javacpp.version}</version>
<classifier>${javacpp.platform.dependencies}</classifier>
</dependency>
</dependencies>

收藏两首程序员打油诗

1
2
3
4
5
6
7
8
商女不知亡国恨,一天到晚敲代码。
举头望明月,低头敲代码。
洛阳亲友如相问,就说我在敲代码。
少壮不努力,老大敲代码。
垂死病中惊坐起,今天还没敲代码。
生当作人杰,死亦敲代码。
人生自古谁无死,来生继续敲代码。
众里寻他千百度,蓦然回首,那人正在敲代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
写字楼里写字间,写字间里程序员;程序人员写程序,又拿程序换酒钱。
酒醒只在网上坐,酒醉还来网下眠;酒醉酒醒日复日,网上网下年复年。
宁愿老死程序间,只要老板多发钱;小车大房不去想,撰个二千好过年。
若要见识新世面,公务员比程序员;一个在天一在地,而且还比我们闲。
别人看我穿白领,我看别人穿名牌;天生我才写程序,臀大近视肩周炎。

年复一年春光度,度得他人做老板;老板扣我薄酒钱,没有酒钱怎过年。
春光逝去皱纹起,作起程序也委靡;来到水源把水灌,打死不做程序员。
别人笑我忒疯癫,我笑他人命太贱;状元三百六十行,偏偏来做程序员。
但愿老死电脑间,不愿鞠躬老板前;奔驰宝马贵者趣,公交自行程序员。
别人笑我忒疯癫,我笑自己命太贱;不见满街漂亮妹,哪个归得程序员。

不想只挣打工钱,那个老板愿发钱;小车大房咱要想,任我享用多悠闲。
比尔能搞个微软,我咋不能捞点钱;一个在天一在地,定有一日乾坤翻。
我在天来他在地,纵横天下山水间;傲视武林豪杰墓,一樽还垒风月山。
电脑面前眼发直,眼镜下面泪茫茫;做梦发财好几亿,从此不用手指忙。
哪知梦醒手空空,老板看到把我训;待到老时眼发花,走路不知哪是家。

小农村里小民房,小民房里小民工;小民工人写程序,又拿代码讨赏钱。
钱空只在代码中,钱醉仍在代码间;有钱无钱日复日,码上码下年复年。
但愿老死代码间,不愿鞠躬奥迪前,奥迪奔驰贵者趣,程序代码贫者缘。
若将贫贱比贫者,一在平地一在天;若将贫贱比车马,他得驱驰我得闲。
别人笑我忒疯癫,我笑他人看不穿;不见盖茨两手间,财权富贵世人鉴。

DelayQueue使用

DelayQueue特性

  • 队列中的元素都必须实现Delayed,元素可以指定延迟消费时长。
  • 实现了BlockingQueue接口,所以他是一个阻塞队列。
  • 本质上是基于PriorityQueue实现的。

贴一段我在实际生产环境中使用到代码

队列管理

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
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
* @author Gao Youbo
* @since 2018-07-26 19:53
*/
public class DelayQueueManager {
private static final Logger LOG = LoggerFactory.getLogger(DelayQueueManager.class);

private String name;
private ExecutorService executor;
private Thread monitorThread;
private DelayQueue<DelayTask<?>> delayQueue; // 延时队列

public DelayQueueManager(String name, int poolSize) {
this.name = name;
this.executor = Executors.newFixedThreadPool(poolSize);
this.delayQueue = new DelayQueue<>();
init();
}

/**
* 初始化
*/
private void init() {
monitorThread = new Thread(() -> {
execute();
}, "DelayQueueMonitor-" + name);
monitorThread.start();
}

private void execute() {
while (true) {
LOG.info("当前延时任务数量:" + delayQueue.size());
try {
// 从延时队列中获取任务
DelayTask<?> delayTask = delayQueue.take();
if (delayTask != null) {
Runnable task = delayTask.getTask();
if (task != null) {
// 提交到线程池执行task
executor.execute(task);
}
}
} catch (Exception e) {
LOG.error(null, e);
}
}
}

/**
* 添加任务
*
* @param id 任务编号
* @param task 任务
* @param time 延时时间
* @param unit 时间单位
*/
public void put(String id, Runnable task, long time, TimeUnit unit) {
long timeout = TimeUnit.MILLISECONDS.convert(time, unit);
long delayTimeMillis = System.currentTimeMillis() + timeout;
delayQueue.put(new DelayTask<>(id, delayTimeMillis, task));
}

/**
* 添加任务
*
* @param id 任务编号
* @param task 任务
* @param delayTimeMillis 延迟到什么时间点
*/
public void putAt(String id, Runnable task, long delayTimeMillis) {
delayQueue.put(new DelayTask<>(id, delayTimeMillis, task));
}

/**
* 根据任务编号删除任务
*
* @param id
* @return
*/
public boolean removeTaskById(String id) {
DelayTask task = new DelayTask(id, 0, null);
return delayQueue.remove(task);
}

/**
* 删除任务
*
* @param task
* @return
*/
public boolean removeTask(DelayTask task) {
return delayQueue.remove(task);
}
}

延迟任务对象

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

import java.util.Objects;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
* @author Gao Youbo
* @since 2018-07-26 19:54
*/
public class DelayTask<T extends Runnable> implements Delayed {
private final String id;
private final long delayTimeMillis; // 延迟到什么时间点执行
private final T task; // 任务

public DelayTask(String id, long delayTimeMillis, T task) {
this.id = id;
this.delayTimeMillis = delayTimeMillis;
this.task = task;
}

public T getTask() {
return task;
}

@Override
public int compareTo(Delayed o) {
DelayTask other = (DelayTask) o;
long diff = delayTimeMillis - other.delayTimeMillis;
if (diff > 0) {
return 1;
} else if (diff < 0) {
return -1;
} else {
return 0;
}
}

@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.delayTimeMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DelayTask<?> delayTask = (DelayTask<?>) o;
return Objects.equals(id, delayTask.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}

npm发布自己的包

  1. 如果没有账号,那么使用npm adduser去创建账号,中间会引导你输入用户名、密码、邮箱。
  2. 如果已经有账号,需要使用npm login去登录,同样会引导你输入正确的用户名、密码、邮箱。
  3. 登录成功之后使用npm publish将自己的包发不上去。

西安行

总结一下:

  • 历史文化古城,找个导游听一下讲解还是不错的,不建议自己逛。
  • 人居多,天气还热,都晒伤了。
  • 回民街的小吃不正中,就和北京的王府井、武汉的户部巷一样片外地人钱的。
  • 不要被导游忽悠买他们的蓝田玉,就算是兵马俑博物馆里面卖的也不要买,哪怕他们说在这里买到假的就是世界第九大奇迹。
1
2
3
4
5
分享一篇知乎上看到的文章,里面导游的套路、说辞和我们导游是一模一样的,都怀疑是同一个导游了。
作者:嘉禾
链接:https://www.zhihu.com/question/34382541/answer/139343627
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

因为是找了导游游西安的,一路有导游带队。那导游还挺能说的(据说西安导游全靠嘴,因为西安古迹多,有好多故事可以讲),而且导游的确很厉害,介绍了很多西安的人文历史,所以我们挺喜欢那导游的。然后进兵马俑博物馆之前,导游就提醒我们绝对不要在博物馆外头买东西,不是假的就是贵的,连价格也不要问,万一你去了,老板砸个假的蓝田玉,然后说是你碰掉的,说都说不清。我们就多了个心眼,打死不多看一眼那里的商店。博物馆外面的贩卖各种玉石雕刻的都是假的,都是按斤卖的。当地人称“野兽街”。
————————关于秦始皇兵马俑博物馆小贴士的分割线————————
逛兵马俑真的还是要请个讲解员的,因为只有1号坑有兵马俑,而且后半段挖出的兵马俑又被黄土盖上去了,讲解员说是因为兵马俑本来都是有颜色的,但是一出土就受到氧化褪色了,为了保护兵马俑,1号坑的后半截挖了一半又全埋回去了。而2号坑就全是黄土堆,没把兵马俑挖出来 ;3号坑有陶俑马车和碎成渣的兵马俑碎片。
如果没有讲解员,自己去逛根本看不出什么东西,就只是一堆土娃娃。而讲解员会给游客将兵马俑的来历,意义,和秦始皇陵墓的关系和其他陪葬坑。据说在秦始皇陵周围有600多个陪葬坑。嗯,而且还没有盗墓贼光顾过。
————————————————分割线结束——————————————
——————
问了小伙伴一下,真的是蓝田玉,可能是和田玉在我心中太高大了,一直盘旋脑海,已改正
——————
本次经历就是和这个讲解员有关。
进了博物馆后,有个讲解员带我们欣赏兵马俑,导游在外头等我们。因为这个讲解员是导游找的,我觉得应该是很正规的,而且穿的也很正式。在他要推销蓝田玉之前,你完全不会想到坑钱这件事上去。
在讲1,2号坑时,讲解员还是很称职的,而且有问必答,我问了很多关于盗墓的事情,他都回答了。
但是到了3号坑(最后一个游览点),就开始有点问题了。因为3号坑本身就有点小,就是个凹型的坑,相对来说很小,没啥可讲的,然后他就说我们快结束了,可以去看看杨老先生(就是第一个发现秦始皇兵马俑的那位当地百姓),运气好时他在博物馆,可以握手签名什么的。又说道陕西特产蓝田玉。让我们出了博物馆千万不要去门口买商店里的蓝田玉,都是假的(这个他没直说,因为他是当地人,说假的不好,但是都是那个意思),称博物馆门口的街为野兽街,不要问价,和导游提醒我们的一样。为此,我们都觉得这讲解员真不错,好感顿生。之后细讲了下蓝田玉,说蓝田玉分活玉和死玉,活玉是玉养人,死玉是人养玉。外面买的蓝田玉不是假的就是死玉,让我们不要买,要买也请把价格压到100元8个。
那我们就更加觉得有意思了,追问他什么是死玉什么是活玉。百般追问后,他说他身上带了一块,在腰间,补肾健体什么的。我们可以看不能摸。而且要在光亮的地方可以看得到。后来去了门口,他拿出来给我们看,不对光的时候是个墨绿到有点黑色的平安扣。但是一对光就真的是透亮的翠绿色,还带有黑色的暗点(美玉有瑕)。我们当时就很惊奇了。然后他又说,买蓝田玉不用买太好,买中档的就行,便宜的也不要,那是假的。然后我们就问什么是中档,他说就是300~2000左右的,太好也没什么用。
之后我们就出了3号坑,去了有杨老先生在的那个地方,讲解员还说如果老先生不在就看照片。还说他是免费讲解的,所以进了那个地方(请原谅我忘记叫什么馆了),我们的门票就算是给他记了一分,他靠这个得到奖金什么的。然后,那里也讲解什么是蓝田玉的死玉和活玉,还有和假玉真玉的真假对比。我们可以看看。那里出售的所有的蓝田玉都是有宝石鉴定证书的,都是博物馆里统一管理的。之所以有这个展厅,就是因为以前游客买了博物馆外的蓝蓝田玉,发现是假的,但是投诉不了摊贩,就投诉到了博物馆。博物馆为了名誉,就自己弄了个正规的,让游客免得受骗。而且是国际性的,所以不收税,价格会比其他地方便宜10%到15%。甚至觉得是假的,随时可以带着发票退货,如果不小心摔碎了,还能邮寄回碎片,只要承担50%的费用,就能拿个新的。
其实听到这里我就有点怀疑了,因为我觉得所有要我掏钱的人都是骗子。
后来,还是进了那个馆。果然,只有杨老先生的照片。而且旁边和对面全是买蓝田玉的柜台。当时就有点懵。
————之后几段的和田玉我就不改了,因为我当时一直听成了和田玉,没想到蓝田玉。也多亏我听岔了,所以才会认为这些玉是假的,因为和田玉真的很贵。如果当时就知道是蓝田玉,我可能还没那么快跳出骗局。
满眼的和田玉啊,以前只有在电视上看到的和田玉,这可以08奥运金牌上的玉呢!但是就和小商品市场上的玻璃珠子一样堆在柜台上,当时就觉得好假啊。
然后讲解员带我们到了一个柜台,说是给我们讲什么是真玉假玉和死玉活玉。然后拿出两个假的给我们看。因为我们团有25个人,柜台都坐不下了。我就站在旁边了,也没听了,因为就是掏钱买东西的地方,没意思。我同学有在看,一个美容养颜的芙蓉玉手镯,说是280元。还有什么墨绿色的可以买给爸爸,黄色的可以买给妈妈,功效不同。我一看就不对劲,因为在我眼中,玉镯子是很值钱的,尤其是和田玉。这里越看越像骗钱的。而且我们又不会鉴别什么是真的什么是假的,在山寨大国,除了人不能造,什么不能造假啊,我才不信宝石鉴定证书呢,就更加觉得是坑人的。
觉得是骗钱的地方,怎么办?第一件事当然是百度看看,是不是真的骗钱。
百度贴吧上果然有人爆贴说在兵马俑买了2个镯子花了1220元,去鉴定发现是玛瑙做的,还是鉴定费贵。
我立刻把这个帖子给了我同学看,然后拉着我同学走了。被柜台售货员白了好几眼。
所以,进了景区一定要多长心眼,看好钱财。
感谢鉴宝节目,让我对和田玉和玉石有所了解,不会轻易别骗
感谢百度和网友的以身试假,让我们有了警惕之心
再加一个,感谢我运气好,和田玉、蓝田玉傻傻分不清
对了,博物馆门口的野兽街每户人家家产300万左右,怎么来的,你们懂的!(讲解员说的)














看了《洛阳女儿行》除了心痛还是心痛(转载)

看洛阳女儿行第一部的时候,还在读高中二年级,现在情节是什么已经忘的差不多了,只是记得这是一本好书,故事情节、文笔都非常好。最近在微信读书上看到给我推荐洛阳女儿行,于是购买了接着往下看,今天在百度贴吧上看到了这篇书评,感觉写的非常好,于是转载了。

鉴于广大读者时下只能在网上找到整个作品的很小一部分,我这里给个内容简介,顺便加入一些个人看法。整个作品是有六部,目前我只读到读了前五部,第六部还在修改中。

第一部《斑骓待》,分上下两部。上部网上已发布,写青年剑客韩锷为寻找爱侣方柠,走入洛阳城,遭遇种种奇遇,最终发现自己被卷入了一桩陈年的灭门巨案,同时发觉自己其实是被他所爱的方柠,真名杜方柠,以及似乎爱他的于婕,真名余婕,诱入这个很大很大的迷局中的。结尾处,韩锷洛阳城外倚马苦待,但杜方柠告诉她的侍女韩锷终究不会走出她的洛阳,因此她也就不必去赴城外的约会,她还让侍女去探看余婕的墓穴,预示着她尚在人世。

由于情节的迷离和秘术的频现,这个故事略显生涩,但是极有张力,隐约是温瑞安《杀楚》的意境。文中特别的人物是余婕。这个故事中她以一个因面貌丑陋而自伤、因武功平常而有求于人、因言辞突兀的让人惊诧、终在关窍处引刀割喉、嘎然而去的烈女形象出现,但她身上那种历尽人生生死巨变,洞悉尘世前后因果的疲倦感和无力感,却与她激烈的复仇意识和对韩锷的浓烈爱意形成鲜明的对照,使得这个女子一出现就让人再难忘却。实际上,她虽死却始终给读者生的感觉,成为暗处的“洛阳女儿”,伴随着明处的杜方柠一同贯穿全书。尽管余婕是凭她所修行的大荒山秘术,洞悉韩锷是生命中能够给他绝大助力的男人,可是韩锷见她时那种前世、梦中似曾相逢的感觉,在萎靡内魅的洛阳城中,是如此让人迷惘而思忆,想必也会给身处现代都市中的读者感同身受之感。此外,余婕在这个故事里也以轮回巷老人、余姑姑的形象出现,后面还会以“漠上玫”的身份复活,但她在整部《洛阳》中形象,以在这个故事中最为引人追思。

《洛阳》的写法上一点突破于传统武侠作品之处,在于大量的涉及了秘术,如后面要出现的素女门一派的“忌体香”、“枕头咒”、“阿堵盅”,大荒山的“十诧古图轮回阵”,小计天生的“止水清瞳”,龙门异的“龙门二十品”阵法等。秘术最重要的所在,是余婕对于韩锷命运的种种预测,以至于把韩锷的大部分行为都置于一种不可改变的状态,从而让这个故事在整体上充满了迷幻色彩,具有极为强烈的宿命感,有些相似于《英雄志》。其它阵法奇术之类,虽然作者之前作品《杯雪》、《脂剑奇僧录》中已经有过,而且即使在金庸的《射雕》中,也已有所出现,但该作中小椴的写法别具一格,另有新意,与温瑞安的同类描写相仿佛,属于作品中非常有观赏性的一个看点。

《斑骓待》下部写韩锷解救余婕之弟余小计,并从此与其建立了亦师亦友的伙伴关系,之后他返回洛阳城,解除了杜方柠的危局,在洛阳势力的欢呼中与小计一道孤单的走出洛阳、走向长安。韩锷与杜方柠的首度联手,使得这个故事在大局上不乏鲜活之气,但洛阳城外那貌似善意的近乎驱除式的群体送客,却让韩锷满怀苦闷。故事至此告一段落,结尾时,利大夫和杜方柠独特的送别方式,还有小计许下的“让锷哥从此快乐起来”的良愿,让人对这故事的下文充满了善意的期待。

第二部《陇头行》也是上下两个故事。上部写韩锷来到长安,为察探小计的身世而独闯皇城,得到的讯息为他日后的塞外之行打下伏笔。旋即,韩锷在芙蓉园遭遇紫宸高手艾可的暗算,不得以而不退出长安,却在路上与京城秩序的维护者,第一高手俞九阙遭遇,险死还生。这个故事中出现了全书的两大武学高手,俞九阙、卫子矜,他们武功固然绝顶高超,却一失左手一少右手,如此非同一般的关系把故事向多年之前的宫廷暗斗拉伸开去。特别值得注意的是作品中以非常近的距离细细描写了韩锷的父亲,一个当年置弃子于长安城外、时下在长安城中洁厕挑粪为生的老人,以及这种奇异的父子关系在群体中所造成的轰动效果。在小椴独具传奇色彩的文字故事中,这种错失的人物关系对读者所造成的震撼感,尤其强烈而引人深思。后面的故事里还会写到韩锷父亲的死亡,一回极有悲剧意识的沉湖自尽。这样一种父子关系,在底层里把韩锷至于一个异常孤独的境地,使得他具有了本质上的漂泊感,并且一直在试图建设自己自小就丧失的价值意识。

下部从韩锷逗留天水,教习余小计武艺写起,期间二人与老将军王横海相识。不久韩锷闻讯杜方柠长安有事,终于忍不住赶赴观望,却不经意中夺得龙会武会的魁首,乃授封天子使臣,欲往塞外一行。这部分的前舒后疾,前面天水一段文字从容舒缓,尤其写韩余二人适逢麦积山的“花儿会”,即当地一年一度青年男女公开择偶的节日,别有一番旖旎风光。之后写长安城中龙华会的比武夺帅,采用常规武侠小说情节的非常规写法,简练而精粹,带动着情节骤然加速,示意作品要进入新篇章新主题——韩锷将由闲散转入征战。

三部《居延猎》、四部《戎马逸》关系密切,是整个《洛阳》最为波澜壮阔,也是最为写意抒情的两部,写洛阳女儿杜方柠出行的部分。故事中,韩锷与杜方柠再度携手,转战边疆,安定塞外十五城,决荡千里草原沙场;更剑索合璧,力拼咯丹三杀与大漠王,谋刺左贤王与羌戎首领于万军之中。两部作品开阔浩荡,风格上接作者的中篇作品《弓箫缘》,与第一部《斑骓待》诡谲晦涩的形成鲜明的对比,恰好体现出杜方柠从繁荣拥塞的洛阳来到千里平沙的塞外在心理上所生的绝大变化。

尽管《洛阳》的三四两部洋溢着热烈的气氛,却始终为一种弥漫全书的孤寂所萦绕。韩杜两人每于生死关头总是无分彼此,却始终在战局安定略有间隔,让人时刻感觉到两人之间内在的距离感。杜方柠行出洛阳,驰骋疆场,终于有一回让她酣畅尽致的出世恋情,但也终究为现世中的使命所束缚。韩锷侠客行般的英雄主义,始终无法彻底溶入那样一个整体柔媚的时代里,因此他不但在扰攘的洛阳于长安中是孤独的,在塞外的原野里也是孤寂的,热烈的气氛伴随寂寞的情怀。

此外,小椴在他的故事中也放入了关于战争的种种反思,从而使得主人公的种种努力在本质上具有了一定程度的悲剧感。有一段情节依稀曾经在一部很轰动也很受争议的电影里出现。韩杜行刺之前遇到一个儿子被羌戎王杀光的老人,问他恨不恨羌戎王,老人回答:“草原上就是这样的了。乌毕汗是个真英雄,他的心胸大。就算没有他,草原上的各个部落领主们相互争战的还少了?”之后二人在刺杀过程中一直心存犹疑,为羌戎王的气度而心折,谋刺后更对陷入内乱的羌戎人心存愧疚。

这一部分还有一个值得留意的细节。韩锷刺探敌营被发觉,却恰好当时天空中有烟花绽放,吸引了敌军的视线,他才能得以脱身,然后突然就看到荒野中一个老人般的汉家小孩在痛哭着燃放最后一只烟花。在这一瞬间,韩锷错愕了,读者仿佛随之看到:韩锷心中关于童年、关于父亲、关于孤单、关于危险的种种回忆骤然涌现,他也随着那孩子失声痛哭泪流满面。

第四部的结尾部分情节又开始回到宫廷争斗,余小计的皇子身份呼之欲出,暗线中的洛阳女儿余婕的 “漠上玫”分身也以造就完成,长安城中惊心动魄的宫闱之变即将上演了。

第五部《日色赋》写韩锷与余小计回到长安,连番遭遇袭杀中,余小计日益长成,余婕一派大荒山势力凸现,杜方柠所在太子一派岌岌可危。然而临末,韩锷识破余婕诱他刺杀太子的图谋,杜方柠更在俞九阙的默许下毒毙皇帝,太子在他东宫的楼顶远望太极殿。到这里,韩锷的势力已经丰满,掌控兵权,成为左右时局的最主要的力量,他虽一直醉心于塞上的驰骋生涯,却已经有足够的实力拥小计为帝,他问道:“你是更想回塞上,还是更想当皇帝?”

至此,韩锷的特征已经清晰化。最为作品的的头号主人公,他所担当的主要使命却是支持故事的进展。他身上凸现的男性标识,超脱的出世意识,简单的济世情怀,都可以看作一种象征和符号,这些象征与符号不可能在他身上获得圆满的结合而使他成为一个真实的个体。把整个作品比作一个圆,韩锷虽然处在最中心的位置,但圆周的边界才是那个时代真实的存在。小椴架空了韩锷这个人物,借这个人物去寄托浪漫主义的理想,即韩锷时代里一种无法实现的幻想。韩锷虽然有强力的武功和兵权,总是不能越过别人给他设置的边界,反倒一再发现自己的边界被别人所逾越,每每在暂时的实现自己的“理念”之后发现自己实际上走入了女人的圈套。不但对杜方柠、余婕、小殊、艾可如此,对一个普通民女他也无能为力,他进入民女夭夭为他安排的宿处,在夜里遭遇了性侵犯,然后第二天呆呆的注视着夭夭远去。

《洛阳》中行出洛阳的女儿有两个,明线和主旨说的是杜方柠,但副线余婕却一直在暗中追随她的脚步。杜方柠出身豪门世家,却要以联姻的方式把自己出让,在这个尘世中苦苦的挣扎,以维护两个家族的运转。她也曾与爱人乐游原上索剑结盟,曾于壮阔的边疆跃马驰骋,曾城头浴血苦战以守候那不可期待的太平。当洛阳的女子行走在塞外旷野,我们发现她还是一个女子。可是当她回到了洛阳长安,就不得不执掌她那个世家的权柄,承担一个男子的责任。对于余婕,我们不知道这个神秘女子的内心深处埋藏了怎样的痛苦,只看到韩锷一见她的面就生出怜惜的念头,并且在很长的时间里把照顾好小计当作是对她的苦难与死亡的一种交代。如果这只是余婕大荒山秘术的一种手段,那么我们不禁要想,在这个秘术的背后,余婕有怎样的真是情绪?在第六部故事了,作者对蜕变成漠上玫的余婕的情绪当会有一个交代。

结合《洛阳》,小椴作品一个需要注意的特征是对于少年的刻画。从《杯雪》中的小六儿开始,到《长安》中的小稚,《脂剑》中的小苦儿,再到《洛阳》中的小计,甚至还能联想到作者的自己的笔名,小椴的作品总离不开一个占据重要篇幅的的小孩儿。这不能不让人归结为一种创作上的情结,即少年英雄主义情结。简单看来,这似乎可以追溯到每个人在自己少年时代的某种向往——当我们年少的时候是多么渴望的成为一个英雄,即使是追随一个英雄也是那么的美好。从事武侠创作的人们,这种情感很可能比一班人要浓烈的多,那种早期的对英雄的向往很可能是他们从事武侠作品创作的一个原动力,这实际上使得武侠小说具有很强的童话色彩,是 “成人的童话”。浪漫主义情绪突出的作者尤其强调少年在作品中的突出地位,比如还主楼主写《蜀山》,甚至金庸所创造的人物也作品也大都从主人公的少年写起。而小椴作品的少年痕迹则尤其明显,故事往往在少年长成之后就不再继续,少年们在少年时代就已经完成他们的传奇,成长得沧桑而历练。某种程度上,小椴是把小孩子当作大人来写的,从而在有限的时空里把传奇浓缩,以获得更强烈的表现效果。

比起小椴以前塑造的少年,小计有所不同,他不但是皇子,而且天赋异禀,善于秘术,一出世就充满了强烈神秘色彩,身世背后凝聚了绝大的传奇成分。这在某种程度上继续了《脂剑》中的小苦儿这一人物的特征。实际上,小苦儿在《脂剑》中的表现和可能让小椴感到不满意,而且意犹未尽,所以他在《洛阳》里给了小计一个更大的传奇,更大身份。在未完成的第六部里,小计应该是存在着诸多的变数的,《洛阳》文中有指称太子“少帝长安开紫宸”,如果这里“少帝”是隐约指历史上的唐少帝李重茂,那么小计则未免有点暗射李隆基的嫌疑了。 “每个男孩都有梦想成为一个王子的权利”,小计或许也能是给读者这样的期待吧。

第六部,待续中。

其它评价:就背景而言,《洛阳》的历史痕迹非常浓郁,字里行间满一派古旧厚重之气。小椴以浓艳的笔墨的刻画了汉家江山唐代宫廷的风貌,更是让作品里的故事围绕着皇权的争夺展开,但写的却是假史。这里的假史不同于架空,它是架构在唐朝的官制之上,严格按照中晚唐的风物人情去展开情节的,同时在边塞征战中写出了汉时的雄阔与壮丽。就情节而言,也隐有所指,疑似唐睿宗李旦年间,之先有武则天称帝,当时有韦后距帝位一步之遥,之后有太平公主掌权,整个时代都为女性的阴柔和强健所笼罩,小说的题目叫做女儿行,点出作品中对女性地位的强化。      虽然作品的所有的人物和情节都完全是小椴自己的,但是却给人以强烈的历史压迫感——小椴带读者进入他的历史里,伴随着他的江湖。就这感觉的强烈程度而言,超过红猪侠的《庆熹纪事》和孙晓的《英雄志》。《庆熹》用的明清的宫廷格调,却用了汉唐般的远疆杀伐,虽说结合的近乎无缝,但还是让人一看就觉出架空感。《英雄志》本身想表达的超越了故事所处的时代,但要不是情节上与明史本身如此接近,恐怕不容易让人辨出太多历史的味道。

《洛阳》与《杯雪》是大不相同的。计划中的《杯雪》是要写足七卷,但是作品的开篇太精彩,以至于那样精彩的江湖儿女乱世英雄,很难找到足以与之匹配的背景朝代了,所以小椴写完第一部《驼、锋》后,大概很有一种被正史“伤”了一下的感觉,却也绝不甘心抛却他的怀古情节,因此转笔去写洛阳跟长安,写他自己的“历史”而淡化了江湖。《洛阳》中的人物,虽然还是用第三人称,但是和读者的距离却比《杯雪》近的多了。耿苍怀和骆寒的壮阔与傲岸,主要是用远景写的,但《洛阳》中的韩锷和小计,则几乎和读者血脉相连了,也因为如此,他们有了更多的承载与担当,以至于韩锷这个人物逐渐被符号化。不过《洛阳》的开篇则是晦涩而疏远的,之后小椴才逐渐的把主人公拉到读者面前,带着表达倾诉的欲望让他的人物越来越生动充实,直至充实的给人压迫感。

对于《洛阳》,小椴对文字的期待可以归结为“细致绵密,诡异深艳”,用一个词来形容这种文字给我的感觉,就是“炽艳”。而实际上,小椴的在古旧的文字中对现代的句法也是照用不误,比如“城市”这个词,就时常被嵌入到非常现代的词句中:洛阳——“是一个阴污暗浊的城市,虽然远看着它好象闹哄哄的一片橙红瑰丽,可禁不住走近细看,揭开来那一层面纱底下可全都是浊血污泥的晦暗啊。”;长安——“远远的那个城市,依旧冷冷无语地浅灰着”;居延——“你不在时,这个城市,对于我就是空的”(韩锷)。这里的城市带给人的是诸多的隔阂。那个人头拥挤的洛阳,韩锷于杜方柠会相逢无语,唯有黯然而别,可是在乐游原上,在塞外荒原上,他们的距离被无限的拉近。

乐游原是那样的:“乐游原上最好玩的季节却不是春天,而是初冬。乐游原的初冬是苍白的——从苍苍的露变成了白白的霜,光阴暗换。天气渐冷,马蹄儿踏上去,原野静静的,你会听到秋后露水儿在马蹄儿下爆裂的声音已渐渐换做了冬来后薄霜在马蹄下咯吱吱、几不可闻的轻响。但你不用担心颜色太过寡素,早上起来,那霜枯的草上也会有光晕的,黄晕晕的一层,因为天边会有金红的、咸蛋黄样的太阳,照着你,遥遥的温暖与口边的呵气……”

在这样的对比中,空间的距离和感观的距离被分离了,小椴所要表现的,是一种原始的群体之外的情绪上的接近,这样关于两性的描写就成为一个作品很重要的组成部分了。

《洛阳》沿袭了小椴作品感情浓烈的一贯风格,在个别要点关头的描写上甚至与已有作品非常相似。比如“紫宸一星”龚亦惺箭射杜方柠的那一瞬间,那种“在那一刻,他已爱绝了眼前的这个女子”这种异常惊艳的情感,和《长安古意——七月流火》中,清流社杀手吴暑在死斗中对程窈娘“默默无声坚决十年”的喜欢,在情境上是极为相似的。

同时,《洛阳》中大量以象征隐喻手法表现两性关系,无论是力度还是广度上,比之前的作品都大大的加强了。直接的性描写有韩在“花儿会”和民女夭夭的一夕欢好、韩杜二人在塞外行程里的“双修”,朴厄绯施展“迷迭之术”的香艳场景。如果说这些情节在小椴之前的《弓箫缘》和《脂剑》还有迹可寻,只是在描写力度上有所加强,那么其它诸如韩锷自渎的描写、韩在少年时代所遭遇的艾可的“性骚扰”,则已经确实的超越了传统武侠的尺度了。文学评论界一般以张贤亮《男人的一半是女人》作为中国当代性爱文学的开山之作,那么小椴的《洛阳》似乎可以在武侠作品中占据这样一个地位。实际上,小椴在性爱描写中所大量采用隐喻象征手法,也正是张贤亮在他的文学作品中所采用的方式。

值得注意的是,在这种爱与性描写中,韩锷已经失去了作为人物的真实性,而是成为了“性”与“爱”的象征。我们甚至可以认为韩锷是《洛阳》时代里男性意识的唯一标识。除了他之外,再没有一个真正的男人了:第一高手俞九阙是性变态,皇子余小计仍在青春期,老将军往横海已经垂老,杜方柠的丈夫韦得辉先天软骨。所以艾可对韩锷说“你害了我,害得我从此以后再不会对任何男人动心了”,所以书中所有的女子,如杜方柠、余婕、阿姝、小殊、艾可、朴厄绯、夭夭,全都围绕因着韩锷才能存在她们的爱与性。《洛阳》中以男主人公为“性中心”的程度之强烈,已经大大的超越了正常的尺度。但实际上,韩锷的强硬并不是为彰显他自我的强大和他强悍的性意识,与此相反,往往他是作为性行为的被动者甚至性侵犯的对象而存在的,甚至要采用自读的方式来实现自我解脱。这些性的描写在整体上映射出韩锷内心的虚弱与在女人如杜方柠面前的无力感,同时也是映射出那个时代男性的整体衰弱。联系那个时代,不由让人在杜方柠以及余婕身上,小椴是隐约寄托那个时代的强势女性韦后和太平公主的痕迹的。实际上,韩锷虽然拼命维持他男子的硬挺,但其行为却始终被杜方柠和余婕控制于指掌之间。而杜方柠和余婕两个女子,不但独立支撑起各自的世家,而且各自谋划着扶持其自己的天下。余婕借韩锷的力量谋刺太子的计划距功成只有一发之隔,杜方柠更亲自毒杀皇帝与宫廷深处,她们虽然把感情寄托给了韩锷这个男子,但却一直没有被她们的感情所左右价值观和大局观。

此外,作品中也不乏隐喻式的同性之爱的描写,如两大顶尖高手俞九阙与卫子矜之间的特殊情绪,宫廷宠臣陈果子对中原塞外两位帝王爱恨交错,甚至是韩锷与余小计那种洞穿世情的认同感。俞九阙对于卫子矜的性情绪,是潜伏的,出自于对世间完美事物的激赏和对于凡间精灵的激赏。而在陈果子和两位帝王性关系里,他完全是出于被侵犯与被伤害的地位,内在上有隐喻权利与性的关系的成分,而最终他的反叛,是写出了压抑之下的反抗情绪,对此,读者可以参考南冠的《凤起阿房》。而韩锷和余小计的关系,则是写出了男人之间的认同感,在某种程度上强调了男人在本质上是需要男人的认可的。需要注意的是,余小计在韩锷身边的成长过程,同时也是他的性成长的过程,小椴虽然没有明写这一点,但是在细节的隐蔽处,诸如小计为韩锷解除“阿堵盅”,我们是不难发现这种痕迹的。

这种对同性关系的关注,不免让人对照想起《庆熹纪事》和《凤起阿房》。相比而言,《庆熹》冷静细腻,《阿房》背景鲜明,《洛阳》则是一部情绪特征突出的作品,对作者的表现欲望体现较多,因此同性之间的性爱描写也更具有内在的传奇色彩和外在的冲击力。

但是这个同性的主题由于种种原因,小椴似乎并没有完全展开来写,有些地方从现在的作品看是相当的隐讳的,还请读者读到作品之后自行体会

迁户口

孩子要上学,急忙将房产证办下来,户口迁过来。

2018-06-29
今天早上去派出所将新的户口本办理好了,还挺快的,一会儿就弄完了。