首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >配置文件的几种读取方式(Java和Lua)

配置文件的几种读取方式(Java和Lua)

作者头像
深雾
发布2020-07-10 10:33:02
1.2K0
发布2020-07-10 10:33:02
举报
文章被收录于专栏:工具类工具类

前言

在工作中为了方便项目管理,通常会用到配置文件,以前用的都是配置excel表格转成json格式文件,再读取数据,记录一些有用的方法,也提供给大家参考

Java读取properties配置文件

这种解析方式就轻便很多,适用于配置文件数据小的场景

配置文件数据

键值对格式
键值对格式

读取文件方式

	@Override
	protected void register() {
		Properties p = PropertiesUtils.read("platform/huaweih5.properties");
		APP_ID = PropertiesUtils.getString(p, "appid");
		USER_ID = PropertiesUtils.getString(p, "userID");
		PAY_PUBLIC_KEY = PropertiesUtils.getString(p, "payPublicKey");
		PAY_PRIVATE_KEY = PropertiesUtils.getString(p, "payPrivateKey");
		PUBLIC_KEY = PropertiesUtils.getString(p, "publicKey");
		context.put(CHANNEL_NAME, this);
	}

解析方式

/**
 * Properties帮助类
 * @author CharonWang
 *
 */
public class PropertiesUtils {
	private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesUtils.class);

	/**
	 * 读取properties文件
	 * @param filePath
	 * @return
	 */
	public static Properties read(String filePath) {
		Properties properties = new Properties();
		InputStream is = PropertiesUtils.class.getClassLoader().getResourceAsStream(filePath);
		if (is == null) {
			LOGGER.error(String.format("[%s] file path not found.", filePath));
			return null;
		}

		try {
			properties.load(is);
			is.close();
		} catch (IOException e) {
			LOGGER.error(String.format("[%s] file load error.", filePath));
			return null;
		} finally {
		}

		return properties;
	}

	/**
	 * 获取整型值
	 * @param p
	 * @param name
	 * @return
	 */
	public static int getInt(Properties p, String name) {
		String str = p.getProperty(name).trim();
		if (str == null || str.isEmpty()) {
			return 0;
		}
		return Integer.valueOf(p.getProperty(name).trim());
	}

	/**
	 * @param p
	 * @param name
	 * @return
	 */
	public static long getLong(Properties p, String name) {
		String str = p.getProperty(name).trim();
		if (str == null || str.isEmpty()) {
			return 0;
		}
		return Long.valueOf(p.getProperty(name).trim());
	}

	/**
	 * 获取字符串值
	 * @param p
	 * @param name
	 * @return
	 */
	public static String getString(Properties p, String name) {
		return p.getProperty(name).trim();
	}

	/**
	 * 逗号分隔获取字符串List
	 * @param p
	 * @param name
	 * @return
	 */
	public static List<String> dotSplitStringList(Properties p, String name) {
		return getSplitList(p, name, ",");
	}

	/**
	 * 逗号分隔获取整型List
	 * @param p
	 * @param name
	 * @return
	 */
	public static List<Integer> dotSplitIntList(Properties p, String name) {
		return getSplitList(p, name, ",");
	}

	public static List<Long> dotSplitLongList(Properties p, String name) {
		return getSplitList(p, name, ",");
	}

	@SuppressWarnings("unchecked")
	public static <T> List<T> getSplitList(Properties p, String name, String splitChar) {
		if (name == null || name.isEmpty()) {
			return new ArrayList<>();
		}
		String splitString = getString(p, name);
		if (splitString == null || splitString.isEmpty()) {
			return new ArrayList<>();
		}

		String[] splitArray = splitString.split(splitChar.trim());
		List<T> list = new ArrayList<>();
		for (String s : splitArray) {
			if (StringUtils.isNotBlank(s)) {
				list.add((T) s.trim());
			}
		}
		return list;
	}

}

Lua读取config配置文件

先是excel表格转config文件,再读取数据

配置文件数据

这个排版就很舒服了
这个排版就很舒服了

配置文件的读取

实例中通过主键id读取数据

-- 领取奖励
function Achievement:GetReward(achievementid)
    local achievementconfig = server.configCenter.AchievementConfig[achievementid]
    if achievementconfig then
	if achievementconfig.drop then
		local rewards = server.dropCenter:DropGroup(achievementconfig.drop)
			if rewards then
				self.player:GiveItem(rewards, 1, 1,server.baseConfig.YuanbaoRecordType.Achievement)
			end
		end
	end	
	
	if achievementconfig.title then
		self.role.titleeffect:ActivatePart({id = achievementconfig.title,time = 0})
	end
end

读取文件

将配置信息放入server.configCenter中,元表的骚操作是真的多,

function config:ShareConfig()
	server.configCenter = {}
	local mt = {}
	mt.__index = function(_,key)
		local cfg = lua_share.query(key)
		if cfg == nil then
			lua_app.log_error("config query failed",key)
			return {}
		end
		return cfg
	end
	mt.__newindex = function(_,key,value)
		lua_app.log_info("error config new")
	end
	setmetatable(server.configCenter,mt)
end

解析文件

lua_share方法

local function service()
	if handler == 0 then
		handler = lua_app.unique_lua("share/share_svc")
	end
	return handler
end

function sharedata.query(name,call)
	if cache[name] then
		return cache[name]
	end
	local obj = lua_app.call(service(), "lua", "query", name)
	local r = sd.box(obj)
	lua_app.send(service(), "lua", "confirm" , obj)
	lua_app.fork(monitor,name, r, obj,call)
	cache[name] = r
	return r
end

lua_app方法

function unique_lua(name,...)
	local handle = clib.unpack(raw_call(".launcher","lua",clib.pack("unique_process","LuaProxy",name,...)))
	if type(handle) == "number" then
		return handle
	end

	return 0
end

function call(addr,type_name,...)
	local protocol = protocols[type_name]

	if watching_process[addr] == false then
		error("Service is dead")
	end

	local session = clib.new_session()
	local ret = clib.send(addr,session,protocol.id,protocol.pack(...))
	if ret == nil or ret == -1 then
		error("call to invalid address "..tostring(addr))
	end
	return protocol.unpack(yield_call(addr,session))
end

function send(dest,type_name,...)
	local protocol = protocols[type_name]
	if watching_process[dest] == false then
		error("Service is dead")
	end

	return clib.send(dest,0,protocols[type_name].id,protocol.pack(...))
end

function fork(func,...)
	local args = {...}
	local co = new_co(function()
		func(table.unpack(args))
	end)
	table.insert(fork_queue,co)
	return co
end

引用的clib应该是C++文件 share_core方法

function conf.box(obj)
	local gcobj = core.box(obj)
	return setmetatable({
		__parent = false,
		__obj = obj,
		__gcobj = gcobj,
		__key = "",
	} , meta)
end

Java读取json配置文件

先是excel表格转json文件,再读取数据,每次需要新建一个对应配置类,功能实现比较复杂,节省篇幅删减部分字段

配置文件数据

json格式
json格式

fileName对应配置文件名称,新建配置类对应文件字段

@DataFile(fileName = "seize_mine_config")
public class SeizeMineConfig implements ModelAdapter {
	//矿ID
	private int id;
	// 占领箱子
	private String boxReward;
	//消耗
	private String seizeConsume;

	@FieldIgnore
	private List<CountProduct> seizeConsumes;
	
	@Override
	public void initialize() {
	//字符串转物品类 id count
		seizeConsumes = CountProduct.create(seizeConsume);
	}

	@Override
	public IdentiyKey findKey() {
		return IdentiyKey.build(id);
	}

	public int getId() {
		return id;
	}

	public List<CountProduct> getSeizeConsumes() {
		return seizeConsumes;
	}
}

/**
 * model包中的类继承于此
 * resource.dataconfig包中的文件名必需和model类名一样(忽略大小写)
 * @author ludd
 */
public interface ModelAdapter {

	/**
	 * 初始化处理方法(用于model初始化时做一些自定义处理)
	 */
	public void initialize();
	
	public IdentiyKey findKey();
}

配置文件的读取

实例中通过主键id读取数据

// 验证消耗
SeizeMineConfig mineConfig = dataConfig.getConfig(IdentiyKey.build(mineId), SeizeMineConfig.class);
List<CountProduct> seizeConsumes = mineConfig.getSeizeConsumes();

读取接口

/**
 * 数据配置接口 
 * @author ludd
 *
 */
public interface DataConfig {
	/**
	 * 根据类名获取数据配置列表
	 * @param invokeClazz	调用者的Service类
	 * @param modelClass	需要获取的Model类
	 * @return
	 */
	<T extends ModelAdapter> Collection<T> getList(Class<T> modelClass);

	/**
	 * 根据类名获取数据配置列表
	 * @param invokeClass
	 * @param modelClass
	 * @return
	 */
	<T extends ModelAdapter> Collection<T> listAll(ConfigServiceAdapter invokeClazz, Class<T> modelClass);

	/**
	 * 重载配置文件
	 * @param fileName	文件名
	 * @param newData	新的文件流
	 * @return
	 */
	boolean reload(String fileName, URL url) throws Exception;

	/**
	 * 校验配置文件流
	 * @param name
	 * @param inputStream
	 * @return
	 */
	boolean checkModelAdapter(String name, InputStream inputStream);

	/**
	 * 获取配置文件
	 * @param key
	 * @return
	 */
	<T extends ModelAdapter> T getConfig(IdentiyKey key, Class<T> clz);
}

功能实现

**
 * 数据配置接口功能实现
 * @author ludd
 *
 */
@Component
public class DataConfigImpl implements DataConfig, InitializingBean {
	private static final Logger LOGGER = LoggerFactory.getLogger(DataConfigImpl.class);

	/**
	 * 配置文件格式(xml,json)
	 */
	@Autowired(required = false)
	@Qualifier("datacofig.format")
	private String format = "json";

	/**
	 * 配置文件路径
	 */
	@Autowired(required = false)
	@Qualifier("dataconfig.path")
	private String path = "dataconfig" + File.separator;

	/**
	 * 数据配置映射对应的包
	 */
	@Qualifier("dataconfig.package_scan")
	@Autowired(required = false)
	private String packageScan = ".";

	/**
	 * 配置文件扩展名
	 */
	@Qualifier("dataconfig.extension")
	@Autowired(required = false)
	private String extension = ".json";

	@Autowired
	private DataParser dataParser;
	@Autowired
	private ApplicationContext applicationContext;
	/**
	 * 所有数据配置存储集合 key:className value: extend ModelAdapter
	 */
	private static ConcurrentHashMap<String, Map<Object, ModelAdapter>> MODEL_MAPS = new ConcurrentHashMap<>();
	/** model类被哪些Service调用了的索引(用于反向初始化service) */
	private static ConcurrentHashMap<String, Set<ConfigServiceAdapter>> MODEL_BE_INVOKE_MAPS = new ConcurrentHashMap<>();
	/**
	 * model类与名称的映射
	 * key:DataFile.fileName() value:Class
	 */
	private static ConcurrentHashMap<String, Class<ModelAdapter>> MODEL_CLASS_MAPS = new ConcurrentHashMap<>();

	@Override
	public void afterPropertiesSet() throws Exception {
		initModelAdapterList();
		initServiceAdapterList();
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T extends ModelAdapter> Collection<T> getList(Class<T> modelClass) {
		String name = modelClass.getName();
		Map<Object, ModelAdapter> map = MODEL_MAPS.get(name);
		if (map == null) {
			throw new RuntimeException(name + " config not found");
		}
		return (Collection<T>) map.values();
	}

	@Override
	public boolean reload(String fileName, URL url) {
		if (fileName.isEmpty() || url == null) {
			return false;
		}

		String filePath = getFullPath(fileName);
		URL resource = null;
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			resource = getClass().getClassLoader().getResource(filePath);
			inputStream = url.openStream();
			outputStream = new FileOutputStream(URLDecoder.decode(resource.getPath(), "utf-8"));
			byte[] buffer = new byte[1024];
			int readed = 0; // 一次读多个,readed代表当前已读的数据总数
			while ((readed = inputStream.read(buffer)) != -1) {
				// 从第0位写,reader代表读写几位
				outputStream.write(buffer, 0, readed);
			}

			Class<ModelAdapter> clazz = MODEL_CLASS_MAPS.get(fileName);
			if (clazz == null) {
				return false;
			}

			// 重载model
			if (initModelAdapter(clazz)) {
				// 反向重载Service类
				Set<ConfigServiceAdapter> serviceClazzSet = MODEL_BE_INVOKE_MAPS.get(fileName);
				if (serviceClazzSet != null) {
					for (ConfigServiceAdapter service : serviceClazzSet) {
						initServiceAdapter(service);
					}
				}
				LOGGER.error(String.format("reload file:[%s]", fileName));
				return true;
			}
			return false;
		} catch (Exception e) {
			LOGGER.error("{}", e);
		} finally {
			try {
				inputStream.close();
				outputStream.close();
			} catch (Exception e) {
				LOGGER.error("{}", e);
			}
		}
		return false;
	}

	/**
	 * 初始化ModelAdapter
	 */
	private void initModelAdapterList() throws Exception {
		String[] temp = packageScan.split(",");
		// 通过包名扫描获取对应的类集合
		Collection<Class<ModelAdapter>> collection = PackageScanner.scanPackages(temp);
		if (collection == null || collection.isEmpty()) {
			LOGGER.error(String.format("在 [%s]包下没有扫描到实体类!", packageScan));
			return;
		}

		for (Class<ModelAdapter> clazz : collection) {
			initModelAdapter(clazz);
		}
		LOGGER.info("all data config file load complete!");
	}

	/**
	 * 初始化配置
	 * @param clazz
	 * @throws Exception 
	 */
	public boolean initModelAdapter(Class<ModelAdapter> clazz) throws Exception {
		try {
			DataFile df = clazz.getAnnotation(DataFile.class);
			if (df == null) {
				return false;
			}
			Map<Object, ModelAdapter> modelAdapterMap = new HashMap<>();
			for (String fileName : df.fileName()) {
				String fullPath = getFullPath(fileName);
				URL resource = getClass().getClassLoader().getResource(fullPath);
				if (resource == null) {
					LOGGER.error(String.format("load data config file [%s] error. file name [%s] not exists!", clazz.getName(), fullPath));
					return false;
				}
				InputStream input = null;
				input = resource.openStream();
				Map<Object, ModelAdapter> map = dataParser.parse(input, clazz);
				input.close();
				if (map.size() < 1) {
					return false;
				}
				for (ModelAdapter obj : map.values()) {
					obj.initialize();
				}
				synchronized (MODEL_CLASS_MAPS) {
					MODEL_CLASS_MAPS.put(fileName, clazz);
				}
				modelAdapterMap.putAll(map);
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug(String.format("[%s] file load complete!", fullPath));
				}
			}
			synchronized (MODEL_MAPS) {
				MODEL_MAPS.put(clazz.getName(), modelAdapterMap);
			}
			return true;
		} catch (Exception e) {
			LOGGER.error(String.format("file: [%s] read error!", clazz.getName()), e);
			return false;
		}
	}

	/**
	 * 根据文件名获取全路径
	 * @param fileName
	 * @return
	 */
	private String getFullPath(String fileName) {
		return this.path + fileName + this.extension;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends ModelAdapter> T getConfig(IdentiyKey key, Class<T> clz) {
		String name = clz.getName();
		Map<Object, ? extends ModelAdapter> map = MODEL_MAPS.get(name);
		if (map == null) {
			return null;
		}
		if (!map.containsKey(key)) {
			return null;
		}
		return (T) map.get(key);
	}
}

配置文件的解析

文件解析

/**
 * 数据解析接口
 * 
 * @author ludd
 * 
 */
public interface DataParser {

	/**
	 * 读取配置文件后进行解析
	 * 
	 * @param stream  文件流
	 * @param className 解析映射类文件
	 * @return
	 */
	public <T extends ModelAdapter> Map<Object,T> parse(InputStream stream, Class<T> className);
}

@Component
public class JsonDataParser implements DataParser {
	private static final Logger LOGGER = LoggerFactory.getLogger(JsonDataParser.class);

	@Override
	public <T extends ModelAdapter> Map<Object, T> parse(InputStream stream, Class<T> className) {
		StringBuilder sb = new StringBuilder();
		Map<Object, T> objList = new HashMap<Object, T>();
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(stream, "utf-8"));
			while (true) {
				String str = br.readLine();
				if (str == null) {
					break;
				}
				sb.append(str);
			}
		} catch (UnsupportedEncodingException e) {
			LOGGER.error("{}", e);
		} catch (IOException e) {
			LOGGER.error("{}", e);
		}

		String jsonString = sb.toString();
		JSONArray jsonArray = JSON.parseArray(jsonString);
		Map<String, Field> fiedlList = getFieldList(className);

		for (int i = 0; i < jsonArray.size(); i++) {
			JSONObject jsonObj = jsonArray.getJSONObject(i);
			T t;
			try {
				t = className.newInstance();
			} catch (InstantiationException e) {
				LOGGER.error("{}", e);
				continue;
			} catch (IllegalAccessException e) {
				LOGGER.error("{}", e);
				continue;
			}

			Set<String> keySet = jsonObj.keySet();
			for (String string : keySet) {
				if (!jsonObj.containsKey(string)) {
					LOGGER.warn(String.format("[%s]->[%s] column not exists in datafile!", className.getName(), string));
					continue;
				}
				if (fiedlList.containsKey(string) && jsonObj.containsKey(string)) {
					Field f = fiedlList.get(string);
					f.setAccessible(true);
					Object value = null;
					if (f.getType() == byte.class || f.getType() == Byte.class) {
						try {
							value = jsonObj.getByteValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == short.class || f.getType() == Short.class) {
						try {
							value = jsonObj.getShortValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == int.class || f.getType() == Integer.class) {
						try {
							value = jsonObj.getIntValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == long.class || f.getType() == Long.class) {
						try {
							value = jsonObj.getLongValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == float.class || f.getType() == Float.class) {
						try {
							value = jsonObj.getFloatValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == double.class || f.getType() == Double.class) {
						try {
							value = jsonObj.getDoubleValue(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == String.class) {
						try {
							value = jsonObj.getString(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else if (f.getType() == Boolean.class || f.getType() == boolean.class) {
						try {
							value = jsonObj.getBoolean(string);
						} catch (Exception e) {
							LOGGER.warn(String.format("[%s]->[%s] column not data null!", className.getName(), string));
						}
					} else {
						LOGGER.warn(String.format("[%s]->[%s] column not support type!", className.getName(), string));
					}

					if (value != null) {
						try {
							f.set(t, value);
						} catch (IllegalArgumentException e) {
							LOGGER.error("{}", e);
						} catch (IllegalAccessException e) {
							LOGGER.error("{}", e);
						}
					}
				} else {
					LOGGER.warn(String.format("[%s]->[%s] column not exists in class!", className.getName(), string));
				}
			}
			if (t.findKey() == null) {
				throw new RuntimeException(String.format("null config key::%s, class:%s", t.findKey(), className));
			}
			if (objList.containsKey(t.findKey())) {
				throw new RuntimeException(String.format("duplicate config key:%s, class:%s", t.findKey().toString(), className));
			}
			objList.put(t.findKey(), t);
		}
		return objList;
	}

	private static Map<String, Field> getFieldList(Class<?> clazz) {
		Map<String, Field> fieldList = new HashMap<>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			if (field.getName().equals("serialVersionUID") || field.isAnnotationPresent(FieldIgnore.class) == false) {
				fieldList.put(field.getName(), field);
			}
		}
		return fieldList;
	}

}

热更配置文件

自动扫描newconfig路径下是否存在配置文件,如果存在则调用reloadConfig方法

实现方法

@Component
public class ReloadConfig implements InitializingBean {

	private static final Logger LOGGER = LoggerFactory.getLogger(ReloadConfig.class);

	/**
	 * 配置文件路径
	 */
	@Autowired(required = false)
	@Qualifier("dataconfig.newconfig")
	private String path = "newconfig" + File.separator;

	/**
	 * 配置文件扩展名
	 */
	@Autowired(required = false)
	@Qualifier("dataconfig.extension")
	private String extension = ".xml";

	/**
	 * 扫描配置文件变更间隔(毫秒)
	 */
	@Autowired(required = false)
	@Qualifier("dataconfig.flush_time")
	private long flushTime = 10000l;

	/**
	 * 热刷备份文件扩展名
	 */
	private static final String bakExtension = ".bak";

	@Autowired
	DataConfig dataConfig;

	private boolean isRun = true;

	@Autowired
	Schedule schedule;

	@Override
	public void afterPropertiesSet() throws Exception {
		schedule.addEveryMillisecond(new Runnable() {

			@Override
			public void run() {
				if (isRun) {
					reloadConfig();
				}

			}
		}, flushTime);
	}

	private void reloadConfig() {
		isRun = false;
		try {
			for (String name : dataConfig.getAllConfigName()) {
				String filePath = getPath(name);
				URL resource = getClass().getClassLoader().getResource(filePath);
				if (resource != null) {
					boolean result = dataConfig.checkModelAdapter(name, resource.openStream());
					if (result) {
						dataConfig.reload(name, resource);
					}
					LOGGER.error(String.format("load file:[%s] is [%s]", name, result ? "success" : "fail"));

					File f = new File(URLDecoder.decode(resource.getPath(), "utf-8"));
					if (f.exists()) {
						f.delete();
					}
				}
			}
		} catch (Exception ex) {
			LOGGER.warn("{}", ex);
		} finally {
			isRun = true;
		}
	}

	private String getPath(String name) {
		return this.path + name + extension;
	}

	public boolean flushFile(String fileName, String data) {
		byte[] bytes = data.getBytes();
		BufferedOutputStream bos = null;
		FileOutputStream fos = null;
		File file = null;
		String filePath = "";
		try {
			URL resource = getClass().getClassLoader().getResource(path);
			if (resource == null) {
				resource = checkFolderExist();
			}
			filePath = resource.getPath();
			file = new File(filePath + fileName + bakExtension);
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			bos.write(bytes);
		} catch (Exception e) {
			LOGGER.error("write config error.", e);
			return false;
		} finally {
			try {
				if (bos != null) {
					bos.close();
				}
				if (fos != null) {
					fos.close();
				}
			} catch (IOException e1) {
				LOGGER.error("write config error.", e1);
				return false;
			}
			boolean isSuccess = file.renameTo(new File(filePath + fileName + extension));
			if (!isSuccess) {
				LOGGER.warn("rename[" + fileName + "]fail");
				return false;
			}
		}
		return true;
	}

	private URL checkFolderExist() {
		URL url = ClassLoader.getSystemResource("");
		File dir = new File(url.getPath() + path);
		if (!dir.exists() && !dir.isDirectory()) {// 判断文件目录是否存在
			boolean isSuccess = dir.mkdirs();
			if (isSuccess) {
				LOGGER.info("create newconfig folder success...");
			} else {
				LOGGER.warn("create newconfig folder fail");
			}
		}
		return url;
	}
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-06-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Java读取properties配置文件
    • 配置文件数据
      • 读取文件方式
        • 解析方式
        • Lua读取config配置文件
          • 配置文件数据
            • 配置文件的读取
              • 读取文件
              • 解析文件
          • Java读取json配置文件
            • 配置文件数据
              • 配置文件的读取
                • 读取接口
                • 功能实现
              • 配置文件的解析
                • 文件解析
              • 热更配置文件
                • 实现方法
            相关产品与服务
            项目管理
            CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档