存储2048个Minecraft ItemTag的最优数据结构方案问询
解决Minecraft模组中动态物品堆叠容量的系统化实现问题
背景
我正在开发一款Minecraft 1.21版本的模组(基于Java 21),项目目标是:
- 利用
max_stack_size数据组件,让用户通过直观的配置方式设置物品默认最大堆叠容量——只需把物品加入max_stack_size_N.json,ItemTag逻辑会自动处理后续流程。 - 移除硬编码的
99最大堆叠上限,替换为更灵活的方案。
目前我仅完成了N为2的幂次的情况,通过手动逐个定义Tag、注册监听器,再在Mixin里逐一判断:
public class ModTags{ public static final TagKey<Item> IS_STACK_SIZE_1 = createTag("stack_size_1" ); public static final TagKey<Item> IS_STACK_SIZE_2 = createTag("stack_size_2" ); public static final TagKey<Item> IS_STACK_SIZE_4 = createTag("stack_size_4" ); [...] public static final TagKey<Item> IS_STACK_SIZE_2048 = createTag("stack_size_2048"); [...] addReloadListener("stack_size_1" ); addReloadListener("stack_size_2" ); addReloadListener("stack_size_4" ); [...] addReloadListener("stack_size_2048"); }}
@Mixin(ItemStack.class) public abstract class ItemStack_SizeMixin implements ComponentHolder, FabricItemStack { [...] @Inject(method="getMaxCount", at = @At("HEAD")) private void updateMaxStackSizeWithTag(CallbackInfoReturnable<Integer> cir){ ItemStack thisAsStack = (ItemStack)(Object) this; if ( thisAsStack.isIn(ModTags.Items.IS_STACK_SIZE_2048)) ChangeStackSize(thisAsStack, 2048 ); else if ( thisAsStack.isIn(ModTags.Items.IS_STACK_SIZE_1024)) ChangeStackSize(thisAsStack, 1024 ); [...] } }
这种方案扩展性极差,现在需要替换成能支持任意数值(比如372这类非2的幂次)的系统,但缺乏相关Java实现经验。之前尝试用ArrayList实现时一直触发栈溢出,想知道如何系统化定义、存储并检查最多2048个ItemTag,满足自定义堆叠容量的需求。
解决方案
核心思路
放弃手动逐个定义Tag的方式,改用动态映射+自动注册的模式,把堆叠容量数值与对应的TagKey绑定,避免重复代码,同时解决扩展性问题。
步骤1:用Map动态管理Tag与数值的映射
修改ModTags类,用HashMap存储所有堆叠容量对应的TagKey,同时自动完成Tag创建和监听器注册:
public class ModTags { // 存储堆叠容量到TagKey的映射 public static final Map<Integer, TagKey<Item>> STACK_SIZE_TAGS = new HashMap<>(); // 最大支持的堆叠容量,可根据需求调整 private static final int MAX_SUPPORTED_SIZE = 2048; // 替换为你的模组ID private static final String MOD_ID = "你的模组ID"; static { // 自动初始化1到2048的所有Tag for (int size = 1; size <= MAX_SUPPORTED_SIZE; size++) { String tagPath = "stack_size_" + size; TagKey<Item> tag = TagKey.of(Registry.ITEM_KEY, new Identifier(MOD_ID, tagPath)); STACK_SIZE_TAGS.put(size, tag); // 自动注册重载监听器 addReloadListener(tagPath); } } // 保留原有的Tag创建逻辑 private static TagKey<Item> createTag(String path) { return TagKey.of(Registry.ITEM_KEY, new Identifier(MOD_ID, path)); } // 保留原有的监听器注册逻辑 private static void addReloadListener(String tagPath) { ServerResourceManagerBuilder.registerReloadListener( new TagReloadListener(Registry.ITEM_KEY, new Identifier(MOD_ID, tagPath)) ); } }
步骤2:优化Mixin中的堆叠容量判断逻辑
替换原有的链式if-else,改为通过物品的Tag集合反向匹配堆叠容量,同时避免递归调用导致栈溢出:
@Mixin(ItemStack.class) public abstract class ItemStack_SizeMixin implements ComponentHolder, FabricItemStack { // 替换为你的模组ID private static final String MOD_ID = "你的模组ID"; @Inject(method = "getMaxCount", at = @At("HEAD"), cancellable = true) private void updateMaxStackSizeWithDynamicTags(CallbackInfoReturnable<Integer> cir) { ItemStack stack = (ItemStack) (Object) this; Item item = stack.getItem(); // 遍历物品的所有Tag,匹配自定义堆叠容量Tag for (TagKey<Item> tag : item.getRegistryEntry().streamTags().toList()) { Identifier tagId = tag.id(); // 只处理本模组的Tag if (!MOD_ID.equals(tagId.getNamespace())) continue; String tagPath = tagId.getPath(); if (tagPath.startsWith("stack_size_")) { try { // 提取堆叠容量数值 int targetSize = Integer.parseInt(tagPath.substring("stack_size_".length())); // 直接返回自定义容量,终止原方法执行 cir.setReturnValue(targetSize); return; } catch (NumberFormatException e) { // 格式错误的Tag,直接忽略 continue; } } } // 未找到自定义Tag时,保留原方法逻辑 } }
栈溢出问题的解决要点
之前用ArrayList出现栈溢出,大概率是因为递归调用或循环引用:
- 确保Mixin的
getMaxCount方法中不会再次调用getMaxCount(比如原有的ChangeStackSize方法如果内部触发了这个调用,就会导致无限递归) - 用
cir.setReturnValue直接终止原方法执行,避免重复修改栈对象 - 遍历Tag时不要修改物品的Tag集合,防止循环遍历
额外优化建议
- 可添加配置文件,让用户自定义
MAX_SUPPORTED_SIZE的值 - 加入缓存逻辑:用
Map<Item, Integer>缓存已处理物品的堆叠容量,避免每次调用getMaxCount都遍历Tag - 给用户添加提示:不要给同一个物品添加多个
stack_size_NTag,否则只会生效第一个匹配的Tag
内容的提问来源于stack exchange,提问作者AnDrew the Awesome
相关产品推荐
相关产品推荐

