问题
遇到一些资源存储和分配的问题,例如,某种资源的取值范围为1-65535,需要动态分配和回收。在Java中最小的数据类型为byte,如果使用byte[65535]在内存和库中存储,至少需要65KB空间,当网络中此类资源池过多时,会造成内存资源大量消耗。
解决思路
利用String来存储和描述id:1-65535的占用情况。如"2-5,20,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535"表示可用id有:2、3、4、5、20、30、40、41、42、43、44、45、105…
在存储和Java内部处理时均以字符串形式处理,不将其转变为byte数组。以节省内存空间。
- 字符串格式中以COMMA="," 作为分割符;以CONN="-"作为连接符;
- 保持有序排列,这样可以用二分法进行快速查找(这里使用了一种二分法的变形,一直没有做理论推导,但确实好用);
Java实现
其中public方法有四个:
releaseId(int) 释放指定的Id资源;
getId(int) 占用指定的Id资源;
getId() 自由占用Id资源,默认策略是最小值优先。
getIdPool 查看当前的Id资源池
测试效果如下:
idpool is 2-5,20,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:2
idpool is 3-5,20,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:4
idpool is 3,5,20,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:20
idpool is 3,5,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:41
idpool is 3,5,30,40,42-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:43
idpool is 3,5,30,40,42,44-45,105-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:105
idpool is 3,5,30,40,42,44-45,106-120,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:120
idpool is 3,5,30,40,42,44-45,106-119,300,400-500,600-700,1000-1050,2000-65535
get Id for targetId:650
idpool is 3,5,30,40,42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
id is not in pool
idpool is 3,5,30,40,42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 3
idpool is 5,30,40,42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 5
idpool is 30,40,42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 30
idpool is 40,42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 40
idpool is 42,44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 42
idpool is 44-45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 44
idpool is 45,106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 45
idpool is 106-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 106
idpool is 107-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 107
idpool is 108-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 108
idpool is 109-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 109
idpool is 110-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 110
idpool is 111-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 111
idpool is 112-119,300,400-500,600-649,651-700,1000-1050,2000-65535
get Id for default: 112
release id: 100
idpool is 100,113-119,300,400-500,600-649,651-700,1000-1050,2000-65535
release id: 99
idpool is 99-100,113-119,300,400-500,600-649,651-700,1000-1050,2000-65535
release id: 98
idpool is 98-100,113-119,300,400-500,600-649,651-700,1000-1050,2000-65535
release id: 101
idpool is 98-101,113-119,300,400-500,600-649,651-700,1000-1050,2000-65535
release id: 101
101 has been in pool
idpool is 98-101,113-119,300,400-500,600-649,651-700,1000-1050,2000-65535
release id: 501
idpool is 98-101,113-119,300,400-501,600-649,651-700,1000-1050,2000-65535
release id: 399
idpool is 98-101,113-119,300,399-501,600-649,651-700,1000-1050,2000-65535
release id: 800
idpool is 98-101,113-119,300,399-501,600-649,651-700,800,1000-1050,2000-65535
release id: 8000
8000 has been in pool
idpool is 98-101,113-119,300,399-501,600-649,651-700,800,1000-1050,2000-65535
release id: 1
idpool is 1,98-101,113-119,300,399-501,600-649,651-700,800,1000-1050,2000-65535
Process finished with exit code 0
测试代码:
package idpool;
/**
* @author shaozhugui
* @date 2021/2/27
*/
public class IdTest {
public static void main(String[] args) {
IdPooImpl idPoo = new IdPooImpl("2-5,20,30,40-45,105-120,300,400-500,600-700,1000-1050,2000-65535");
int[] getIds = {2,4,20,41,43,105,120,650,350};
for (int id : getIds) {
try {
System.out.println("idpool is " + idPoo.getIdPool());
System.out.println("get Id for targetId:" + idPoo.getId(id));
} catch (IllegalAccessException e) {
break;
}
}
for (int i = 1; i < 15; i++) {
try {
System.out.println("idpool is " + idPoo.getIdPool());
System.out.println("get Id for default: " + idPoo.getId());
} catch (IllegalAccessException e) {
break;
}
}
int[] releaseIds = {100,99,98,101,101,501,399,800,8000,1};
for (int releaseId : releaseIds) {
System.out.println("release id: " + releaseId);
idPoo.releaseId(releaseId);
System.out.println("idpool is " + idPoo.getIdPool());
}
}
}
package idpool;
/**
* @author shaozhugui
* @date 2021/2/27
*/
public class IdPooImpl {
private String idPool = "1-65535";//"2-10,20,30,40-100,105-200,300-65535", ""
private static final String COMMA = ",";
private static final String CONN = "-";
public IdPooImpl(String idPool) {
this.idPool = idPool;
}
//释放id
public boolean releaseId(int targetId) {
if (targetId <= 0) {
return false;
}
if("".equals(idPool)) {
idPool = String.valueOf(targetId);
return true;
}
String[] idList = idPool.split(COMMA);
//二分法查找目标id所在的区间(变异二分法)
int index = getIndexByDichotomySearch(idList, targetId);
int start = getStart(idList[index]);
int end = getEnd(idList[index]);
//System.out.println("index is " + index + "; start = " + start + "; end = " + end);
if (targetId >= start && targetId <= end) {
System.out.println(targetId + " has been in pool");
return true;
}
index = targetId > end ? index : index - 1;
// 之后,targetId必然位于index与index+1之间
String temp = "";
if (index >=0 && index + 1 <= idList.length - 1) {
String pre = idList[index];
String next = idList[index + 1];
temp = getTempStr(pre, next, targetId);
} else if (index == -1) {
String next = idList[index + 1];
temp = getTempStr(next, targetId);
} else if (index == idList.length - 1){
String pre = idList[index];
temp = getTempStr2(pre, targetId);
}
StringBuilder builder = new StringBuilder();
for(int i = 0; i < index; i++) {
builder.append(idList[i]).append(COMMA);
}
builder.append(temp);
if (index < idList.length - 2) {
builder.append(COMMA);
for(int i = index + 2; i < idList.length - 1; i++) {
builder.append(idList[i]).append(COMMA);
}
builder.append(idList[idList.length - 1]);
}
idPool = builder.toString();
return true;
}
private String getTempStr2(String pre, int targetId) {
int preStart = getStart(pre);
int preEnd = getEnd(pre);
String temp;
if (targetId - preEnd != 1) {
temp = pre + COMMA + targetId;
} else {
temp = preStart + CONN + targetId;
}
return temp;
}
private String getTempStr(String next, int targetId) {
int nextStart = getStart(next);
int nextEnd = getEnd(next);
String temp;
if (nextStart - targetId != 1) {
temp = targetId + COMMA + next;
} else {
temp = targetId + CONN + nextEnd;
}
return temp;
}
private String getTempStr(String pre, String next, int targetId) {
int preStart = getStart(pre);
int preEnd = getEnd(pre);
int nextStart = getStart(next);
int nextEnd = getEnd(next);
String temp;
if (targetId - preEnd != 1 && nextStart - targetId != 1) {
temp = pre + COMMA + targetId + COMMA + next;
} else if (targetId - preEnd == 1 && nextStart - targetId != 1) {
temp = preStart + CONN + targetId + COMMA + next;
} else if (targetId - preEnd != 1 && nextStart - targetId == 1) {
temp = pre + COMMA + targetId + CONN + nextEnd;
} else {
temp = preStart + CONN + nextEnd;
}
return temp;
}
//指定Id分配
public int getId(int targetId) throws IllegalAccessException {
if("".equals(idPool)) {
System.out.println("id pool is empty.");
throw new IllegalAccessException();
}
String[] idList = idPool.split(COMMA);
//二分法查找目标id所在的区间(变异二分法)
int index = getIndexByDichotomySearch(idList, targetId);
// 到对应的区间获取资源
AllocateResult result = allocateId(targetId, idList[index]);
if (!result.isResult()) {
System.out.println("id is not in pool");
throw new IllegalAccessException();
}
StringBuilder builder = new StringBuilder();
for(int i = 0; i < index; i++) {
builder.append(idList[i]).append(COMMA);
}
if (!"".equals(result.getStr())) {
builder.append(result.getStr()).append(COMMA);
}
for(int i = index + 1; i < idList.length - 1; i++) {
builder.append(idList[i]).append(COMMA);
}
builder.append(idList[idList.length - 1]);
idPool = builder.toString();
return targetId;
}
private AllocateResult allocateId(int targetId, String s) {
AllocateResult result = new AllocateResult();
if (!s.contains(CONN)) {
if (targetId == Integer.parseInt(s)) {
result.setResult(true);
result.setStr("");
}
return result;
}
int start = getStart(s);
int end = getEnd(s);
if (start > targetId || end < targetId) {
return result;
}
result.setResult(true);
if (start == targetId) {
start++;
if (start == end) {
result.setStr(String.valueOf(end));
} else {
result.setStr(start + CONN + end);
}
return result;
}
if (end == targetId) {
end--;
if (start == end) {
result.setStr(String.valueOf(end));
} else {
result.setStr(start + CONN + end);
}
return result;
}
int tempStart = targetId + 1;
int tempEnd = targetId - 1;
StringBuilder builder = new StringBuilder();
builder.append(start);
if (start != tempEnd) {
builder.append(CONN).append(tempEnd);
}
builder.append(COMMA);
if (tempStart != end) {
builder.append(tempStart).append(CONN);
}
builder.append(end);
result.setStr(builder.toString());
return result;
}
private int getIndexByDichotomySearch(String[] idList, int targetId) {
int start = 0;
int end = idList.length - 1;
int middleByEnd = 0;
while (start <= end) {
middleByEnd = (start + end) / 2;
int temp = getEnd(idList[middleByEnd]);
if (temp == targetId) {
return middleByEnd;
} else if (temp < targetId) {
start = middleByEnd + 1;
} else {
end = middleByEnd - 1;
}
}
int start1 = 0;
int end1 = idList.length - 1;
int middleByStart = 0;
while (start1 <= end1) {
middleByStart = (start1 + end1) / 2;
int temp = getStart(idList[middleByStart]);
if (temp == targetId) {
return middleByStart;
} else if(temp < targetId) {
start1 = middleByStart + 1;
} else {
end1 = middleByStart - 1;
}
}
// targetId必然比middleByStart位置的开头小,必然比middleByEnd的结尾大;
// System.out.println("startIndex is " + middleByStart + "; endIndex is " + middleByEnd);
return (middleByEnd + middleByStart) / 2 ;
}
private int getEnd(String s) {
if (!s.contains(CONN)) {
return Integer.parseInt(s);
}
String[] strs = s.split(CONN);
return Integer.parseInt(strs[1]);
}
private int getStart(String s) {
String[] strs = s.split(CONN);
return Integer.parseInt(strs[0]);
}
//自由分配Id,选取最小值
public int getId() throws IllegalAccessException {
if("".equals(idPool)) {
System.out.println("id pool is empty.");
throw new IllegalAccessException();
}
String[] idList = idPool.split(COMMA);
String allocateStr = idList[0];
String[] allocateStrs = allocateStr.split(CONN);
int result = Integer.parseInt(allocateStrs[0]);
String tempStr = "";
if (allocateStrs.length != 1) {
tempStr = allocateStrs[1];
int end = Integer.parseInt(tempStr);
if (end != result + 1) {
tempStr = (result + 1) + CONN + tempStr;
}
}
if (idList.length == 1) {
idPool = tempStr;
return result;
}
StringBuilder builder;
if ("".equals(tempStr)) {
builder = new StringBuilder(idList[1]);
} else {
builder = new StringBuilder(tempStr).append(COMMA).append(idList[1]);
}
for(int j = 2; j < idList.length; j++) {
builder.append(COMMA).append(idList[j]);
}
idPool = builder.toString();
return result;
}
public String getIdPool(){
return idPool;
}
}
class AllocateResult {
private boolean result;
private String str;
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}