稀疏数组(Sparse array) ,所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。
基本使用场景介绍:当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
举例:假设你需要用程序记录如下一张11*11的棋盘,其中用数字1代表白子,数字2代表黑子。我们可以很明显想到用二维数组去存储棋盘数据。
使用二维数组存储如上数据的缺点:
int[][] chessArr1 = new int[11][11];
使用二维数组我们需要创建一个占用11*11*4字节的内存空间去存储,而就这张棋盘数据来看,实际对我们有用的数据只有4*4个字节内存空间,很明显,这极大的浪费了内存空间。于是我们就引出了使用稀疏数组的存储方式去存储如上棋盘数据。
稀疏数据的记录方式:
从上述结果看来,使用稀疏数组记录棋盘数据,仅仅只需要5*3*4个字节的内存空间。
思路:
遍历整个二维数组,获取有效值的个数,记录为sum
通过sum确定稀疏数组的长度,并创建稀疏数组
int[][] sparsArr = new int[sum + 1][3];
获取二维数组的行列数和有效值数,赋值给稀疏数组的第一行
然后再次遍历整个稀疏数组,获取有效值的位置和值,赋值给稀疏数组。
代码实现:
/**
* 实现将二维数组转换为稀疏数组
*
* @param towDimen 传入的二维数组
* @return 返回的稀疏数组
*/
public int[][] towDimenToSparse(int[][] towDimen) {
int sum = 0; // 用于记录有效值的个数
// 遍历整个二维数组,获取有效值的个数
for (int[] row : towDimen) {
for (int data : row) {
if (data != 0) sum++;
}
}
// 根据获取的有效值个数,确定需要创建的稀疏数组的大小
int[][] sparsArr = new int[sum + 1][3];
sparsArr[0][0] = towDimen.length; // 获取二维数组的行数,赋值给稀疏数组的首行首位
sparsArr[0][1] = towDimen[0].length; // 获取二维数组的列数,赋值给稀疏数组的首行次位
sparsArr[0][2] = sum; // 将有效值的个数赋值给稀疏数组的首行末位
int count = 0; // 记录每次遍历到的有效值所在稀疏数组内的行号
// 遍历整个二维数组,将每个有效值记录到稀疏数组当中
for (int i = 0; i < towDimen.length; i++) {
for (int j = 0; j < towDimen[0].length; j++) {
if (towDimen[i][j] != 0) {
count++;
sparsArr[count][0] = i;
sparsArr[count][1] = j;
sparsArr[count][2] = towDimen[i][j];
}
}
}
return sparsArr;
}
思路:
代码实现:
/**
* 实现将稀疏数组转换为二维数组
*
* @param sparseArr 传入的稀疏二维数组
* @return 由传入的稀疏数组转换而来的二维数组
*/
public int[][] sparseToTowDimen(int[][] sparseArr) {
// 通过传入的稀疏数组首行首位以及首行次位的记录来确定二维数组的长宽,并创建二维数组
int[][] towDimenArr = new int[sparseArr[0][0]][sparseArr[0][1]];
// 遍历稀疏数组的其余行,还原二维数组
for (int i = 1; i < sparseArr.length; i++) {
towDimenArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
return towDimenArr;
}
为方便我们后续测试代码,定义以行列式形式打印显示二维数组的方法实现如下
代码实现:
/**
* 以行列式形式打印显示二维数组
*
* @param towDimenArr 传入待显示的二维数组
*/
public void showTowDimenArr(int[][] towDimenArr) {
for (int[] row : towDimenArr) {
for (int col : row) {
System.out.print(col + "\t\t");
}
System.out.println();
}
}
package com.zhao.test;
import org.junit.Test;
/**
* ClassName: SparseArray
* Package: com.zhao.test
* Description:
*
* @Author XH-zhao
* @Create 2023/3/23 21:19
* @Version 1.0
*/
public class SparseArray {
/**
* 实现将二维数组转换为稀疏数组
*
* @param towDimen 传入的二维数组
* @return 返回的稀疏数组
*/
public int[][] towDimenToSparse(int[][] towDimen) {
int sum = 0; // 用于记录有效值的个数
// 遍历整个二维数组,获取有效值的个数
for (int[] row : towDimen) {
for (int data : row) {
if (data != 0) sum++;
}
}
// 根据获取的有效值个数,确定需要创建的稀疏数组的大小
int[][] sparsArr = new int[sum + 1][3];
sparsArr[0][0] = towDimen.length; // 获取二维数组的行数,赋值给稀疏数组的首行首位
sparsArr[0][1] = towDimen[0].length; // 获取二维数组的列数,赋值给稀疏数组的首行次位
sparsArr[0][2] = sum; // 将有效值的个数赋值给稀疏数组的首行末位
int count = 0; // 记录每次遍历到的有效值所在稀疏数组内的行号
// 遍历整个二维数组,将每个有效值记录到稀疏数组当中
for (int i = 0; i < towDimen.length; i++) {
for (int j = 0; j < towDimen[0].length; j++) {
if (towDimen[i][j] != 0) {
count++;
sparsArr[count][0] = i;
sparsArr[count][1] = j;
sparsArr[count][2] = towDimen[i][j];
}
}
}
return sparsArr;
}
/**
* 实现将稀疏数组转换为二维数组
*
* @param sparseArr 传入的稀疏二维数组
* @return 由传入的稀疏数组转换而来的二维数组
*/
public int[][] sparseToTowDimen(int[][] sparseArr) {
// 通过传入的稀疏数组首行首位以及首行次位的记录来确定二维数组的长宽,并创建二维数组
int[][] towDimenArr = new int[sparseArr[0][0]][sparseArr[0][1]];
// 遍历稀疏数组的其余行,还原二维数组
for (int i = 1; i < sparseArr.length; i++) {
towDimenArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
return towDimenArr;
}
/**
* 以行列式形式打印显示二维数组
*
* @param towDimenArr 传入待显示的二维数组
*/
public void showTowDimenArr(int[][] towDimenArr) {
for (int[] row : towDimenArr) {
for (int col : row) {
System.out.print(col + "\t\t");
}
System.out.println();
}
}
@Test
public void test01() {
// 创建棋盘
int[][] chessArr1 = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
chessArr1[9][10] = 1;
chessArr1[7][10] = 2;
System.out.println("二维数组转稀疏数组:");
int[][] sparseArr = towDimenToSparse(chessArr1);
showTowDimenArr(sparseArr);
System.out.println("稀疏数组转二维数组:");
int[][] towDimen = sparseToTowDimen(sparseArr);
showTowDimenArr(towDimen);
}
}
可以看到,上述结果已经成功实现了稀疏数组与二维数组之间的互转!
/**
* 把稀疏数据保存为文件
* @param sparses 要持久化的稀疏数组
* @param path 持久化到本地的文件路径
*/
public static void sparseToFile(int[][] sparses, String path){
FileWriter fileWriter = null;
try {
File file = new File(path);
if (file.exists()) { //存在
file.delete(); //则删除
}
//目录不存在 则创建
if (!file.getParentFile().exists()) {
boolean mkdir = file.getParentFile().mkdirs();
if (!mkdir) {
throw new RuntimeException("创建目标文件所在目录失败!");
}
}
file.createNewFile();
fileWriter = new FileWriter(path);
for (int[] row : sparses) {
for (int item : row) {
fileWriter.write(item+"\t");
}
//\r\n即为换行
fileWriter.write("\r\n");
}
// 把缓存区内容压入文件
fileWriter.flush();
System.out.println("稀疏数据保存文件成功!");
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileWriter!=null){
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static int[][] fileToSparse(String path){
File file = new File(path);
if(!file.exists()){
System.out.println("文件转稀疏数据失败,文件不能为空!");
return null;
}
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader(file));
String line = null;
//缓存文件里面的值,再解析处理
StringBuilder sb = new StringBuilder ();
int count = 0;
while((line = bufferedReader.readLine())!=null){
//System.out.println("行:"+line);
sb.append(line+"\r\n");
count+=1;
}
//解析sb数据
int sparses[][]=new int[count][3];
String[] splits = sb.toString().split("\r\n");
//第一行记录的是 二维数据的行和列,有效数据长度,不为有效数据
for (int i = 0; i < splits.length; i++) {
String[] temp = splits[i].split("\t");
for(int j=0;j<temp.length;j++){
sparses[i][j] = Integer.parseInt(temp[j]);
}
}
return sparses;
} catch (Exception e) {
e.printStackTrace();
}finally {
if(bufferedReader!=null){
try {
bufferedReader.close();
bufferedReader = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
成功从持久化文件中加载到内存当中!