RLE算法
RLE(Run LengthEncoding行程编码)算法是一个简单高效的无损数据压缩算法,其基本思路是把数据看成一个线性序列,而这些数据序列组织方式分成两种情况:一种是连续的重复数据块,另一种是连续的不重复数据块。对于连续的重复数据快采用的压缩策略是用一个字节(我们称之为数据重数属性)表示数据块重复的次数,然后在这个数据重数属性字节后面存储对应的数据字节本身。
示例
简单来说,RLE算法就是用一个字节来记录重复/非重复数量字节数量,N,D1,D2…DN。
一个字节是8位,高位1表示非重复,0表示重复。低7位表示后面字节数量,最大表示127个字符。
如下三组数据示例:
-
1 原始数据
AAAAAAAAAABBBBB (15个字节) -
1 RLE
[10]A [5]B (4个字节) -
2 原始数据
AAAAAAAAAABCDEF (15个字节) -
2 RLE
[10]A [5]BCDEF(8个字节) -
3 原始数据
ABCDEFG (7个字节) -
3 RLE
[7]ABCDEFG(8个字节)
不难发现,当数据中重复的数据较多时,通过RLE算法可以有效的压缩数据;当数据中非重复的数据较多时,通过RLE算法压缩数据有限,甚至有可能增大数据量。
应用场景
通过上面的分析,当已知重复的数据可能较多,且有压缩数据需求的场景时,RLE算法比较适用。
那生活中熟悉的场景有哪些呢?
- 视频压缩:我们知道视频中常常有些静态的背景,很多帧才变化一下,(尤其是监控视频) , 那么RLE算法很适用。
- 黑白图像打印:黑白图像打印场景下,颜色非黑即白,也很符合适用场景。
java压缩和解压缩算法(非递归)
网上这个算法很成熟,但非递归的java算法很少,这里把我写的算法和详细注释分享给大家,希望有用。
/**
* 判断从index开始连续3个字符是否一致,如果剩余字符数<3返回false
*
* @param index
* @param data
* @param len
* @return
*/
private static boolean isRepetitionStart(int index, byte[] data, int len) {
byte b1, b2, b3;
if (index + 2 >= len) {
return false;
}
b1 = data[index];
b2 = data[index + 1];
b3 = data[index + 2];
if (b1 == b2 && b2 == b3) {
return true;
}
return false;
}
/**
* RLE压缩算法,返回压缩后的byte数组
*
* @param src
* @return
*/
public static byte[] rleCompress(byte[] src) {
///缓存压缩数据
ArrayList<Byte> dest = new ArrayList<>();
///记录用于比较的字符
byte newChar, oldChar = 0;
///记录当前数据块儿起始位置和长度
int start, count;
int srcLen = src.length;
int i = 0;
while (i < srcLen) {
count = 0;
///发现重复块儿,AAA
if (isRepetitionStart(i, src, srcLen)) {
///通过while循环找出重复块所有重复的字符,重复块长度>=3
while (i < srcLen) {
///重复块的起始位置进行初始化
if (count == 0) {
oldChar = src[i];
count = 1;
i++;
continue;
}
newChar = src[i];
if (newChar != oldChar) {
///AAAB发现非重复字符,跳出重复块判断
break;
}
count++;
i++;
if (count == 127) {
//重复字符数达到重复块的最大长度,跳出
//最高位用来表示数据块儿相同和不同,数据块长度由低7位表示,最大表示127个数据
break;
}
}
///记录重复块的数据值
/// 第一个byte表示重复块0x00|count,最高位0表示重复块,后7位表示重复块长度
dest.add((byte) count);
///第二个byte表示重复块的char
dest.add(oldChar);
} else {
///非重复块儿,先记录起始位置
start = i;
while (i < srcLen) {
///非重复块儿其实位置进行初始化
if (count == 0) {
if ((i + 2) >= srcLen) {
///剩余2个字符,不可能重复了,则将最后几个字符算作一个不重复块儿,并break结束
count = srcLen - i;
i = srcLen;
break;
} else {
///默认连续3个不连续,把指针移动到第三个char
oldChar = src[i + 2];
count = 3;
i += 3;
continue;
}
}
newChar = src[i];
///ABCC遇到相同的char,跳出不连续块判断,指针后退到上一个字符处
///ABBB场景可能损失一个字符,但影响不大
if (newChar == oldChar) {
i--;
count--;
break;
}
oldChar = newChar;
count++;
i++;
if (count == 127) {
///非重复字符数达到非重复块的最大长度,跳出
///最高位用来表示数据块儿相同和不同,数据块长度由低7位表示,最大表示127个数据
break;
}
}
///记录非重复块的数据值
/// 第一个byte表示非重复块0x80|count,最高位1表示非重复块,后7位表示非重复块长度
dest.add((byte) (count | 0x80));
///将非重复块数据拷贝到dest中
for (int j = 0; j < count; j++) {
dest.add(src[start + j]);
}
}
}
byte[] destArray = new byte[dest.size()];
for (int j = 0; j < dest.size(); j++) {
destArray[j] = dest.get(j);
}
///返回压缩后的byte数组
return destArray;
}
public static byte[] rleUncompress(byte[] src) {
ArrayList<Byte> dest = new ArrayList<>();
int index = 0;
while (index < src.length) {
byte flag = src[index++];
boolean isRepeat = ((flag >> 7) & 0xFF) == 0;
int count = flag & 0x7F;
if (isRepeat) {
byte v = src[index++];
for (int i = 0; i < count; i++) {
dest.add(v);
}
} else {
for (int i = 0; i < count; i++) {
dest.add(src[index++]);
}
}
}
byte[] destArray = new byte[dest.size()];
for (int i = 0; i < dest.size(); i++) {
destArray[i] = dest.get(i);
}
///返回压缩后的byte数组
return destArray;
}