diff --git a/.gitignore b/.gitignore index f68fa45..cd02f2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea /sandbox/*.iml target +.DS_Store diff --git a/sandbox/pom.xml b/sandbox/pom.xml index 0f6a116..6a24467 100644 --- a/sandbox/pom.xml +++ b/sandbox/pom.xml @@ -3,13 +3,25 @@ 4.0.0 com.cipher sandbox - war + jar 1.0-SNAPSHOT - sandbox Maven Webapp + sandbox http://maven.apache.org + + net.jodah + typetools + 0.6.1 + + + + com.alibaba + fastjson + 1.2.55 + + com.google.guava guava @@ -101,6 +113,24 @@ 1.7.4 + + org.openjdk.jmh + jmh-core + 1.19 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.19 + provided + + + junit + junit + 4.13 + compile + + @@ -110,8 +140,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.8 - 1.8 + 8 + 8 diff --git a/sandbox/src/main/java/com/cipher/checker/CheckParamAspect.java b/sandbox/src/main/java/com/cipher/checker/Checker.java similarity index 51% rename from sandbox/src/main/java/com/cipher/checker/CheckParamAspect.java rename to sandbox/src/main/java/com/cipher/checker/Checker.java index 0617d7a..85654f8 100644 --- a/sandbox/src/main/java/com/cipher/checker/CheckParamAspect.java +++ b/sandbox/src/main/java/com/cipher/checker/Checker.java @@ -1,389 +1,471 @@ -package com.cipher.checker; - -import org.apache.commons.lang3.StringUtils; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.function.BiFunction; - -/** - * 参数校验 切面 - * - * @author cipher - * @date 2017/11/7 - */ -@Aspect -public class CheckParamAspect { - - private static final Logger LOG = LoggerFactory.getLogger(CheckParamAspect.class); - - @Around(value = "@com.cipher.checker.Check") // 这里要换成自定义注解的路径 - public Object check(ProceedingJoinPoint point) throws Throwable { - Object obj; - // 参数校验 - String msg = doCheck(point); - if (!StringUtils.isEmpty(msg)) { - // 这里可以返回自己封装的返回类,例如:return ResultBuilder.unsuccess(msg); - throw new IllegalArgumentException(msg); - } - // 通过校验,继续执行原有方法 - obj = point.proceed(); - return obj; - } - - /** - * 参数校验 - * - * @param point ProceedingJoinPoint - * @return 错误信息 - */ - private String doCheck(ProceedingJoinPoint point) { - // 获取方法参数值 - Object[] arguments = point.getArgs(); - // 获取方法 - Method method = getMethod(point); - // 默认的错误信息 - String methodInfo = StringUtils.isEmpty(method.getName()) ? "" : " while calling " + method.getName(); - String msg = ""; - if (isCheck(method, arguments)) { - Check annotation = method.getAnnotation(Check.class); - String[] fields = annotation.value(); - // 只支持对第一个参数进行校验 - Object vo = arguments[0]; - if (vo == null) { - msg = "param can not be null"; - } else { - for (String field : fields) { - // 解析字段 - FieldInfo info = resolveField(field, methodInfo); - // 获取字段的值 - Object value = ReflectionUtil.invokeGetter(vo, info.field); - // 执行校验规则 - Boolean isValid = info.optEnum.fun.apply(value, info.operatorNum); - msg = isValid ? msg : info.innerMsg; - } - } - } - return msg; - } - - /** - * 解析字段 - * - * @param fieldStr 字段字符串 - * @param methodInfo 方法信息 - * @return 字段信息实体类 - */ - private FieldInfo resolveField(String fieldStr, String methodInfo) { - FieldInfo fieldInfo = new FieldInfo(); - String innerMsg = ""; - // 解析提示信息 - if (fieldStr.contains(SEPARATOR)) { - innerMsg = fieldStr.split(SEPARATOR)[1]; - fieldStr = fieldStr.split(SEPARATOR)[0]; - } - // 解析操作符 - if (fieldStr.contains(Operator.GREATER_THAN_EQUAL.value)) { - fieldInfo.optEnum = Operator.GREATER_THAN_EQUAL; - } else if (fieldStr.contains(Operator.LESS_THAN_EQUAL.value)) { - fieldInfo.optEnum = Operator.LESS_THAN_EQUAL; - } else if (fieldStr.contains(Operator.GREATER_THAN.value)) { - fieldInfo.optEnum = Operator.GREATER_THAN; - } else if (fieldStr.contains(Operator.LESS_THAN.value)) { - fieldInfo.optEnum = Operator.LESS_THAN; - } else if (fieldStr.contains(Operator.NOT_EQUAL.value)) { - fieldInfo.optEnum = Operator.NOT_EQUAL; - } else { - fieldInfo.optEnum = Operator.NOT_NULL; - } - // 不等于空,直接赋值字段 - if (fieldInfo.optEnum == Operator.NOT_NULL) { - fieldInfo.field = fieldStr; - fieldInfo.operatorNum = ""; - } - // 其他操作符,需要分离出字段和操作数 - else { - fieldInfo.field = fieldStr.split(fieldInfo.optEnum.value)[0]; - fieldInfo.operatorNum = fieldStr.split(fieldInfo.optEnum.value)[1]; - } - fieldInfo.operator = fieldInfo.optEnum.value; - // 处理错误信息 - String defaultMsg = fieldInfo.field + " must " + fieldInfo.operator + " " + fieldInfo.operatorNum + methodInfo; - fieldInfo.innerMsg = StringUtils.isEmpty(innerMsg) ? defaultMsg : innerMsg; - return fieldInfo; - } - - // -=================== 对不同类型的值进行校验 起 ======================= - - /** - * 是否不为空 - * - * @param value 字段值 - * @param operatorNum 操作数,这里不需要,只是为了参数统一 - * @return 是否不为空 - */ - private static Boolean isNotNull(Object value, String operatorNum) { - Boolean isNotNull = Boolean.TRUE; - Boolean isStringNull = (value instanceof String) && StringUtils.isEmpty((String) value); - Boolean isCollectionNull = (value instanceof Collection) && CollectionUtils.isEmpty((Collection) value); - if (value == null) { - isNotNull = Boolean.FALSE; - } else if (isStringNull || isCollectionNull) { - isNotNull = Boolean.FALSE; - } - return isNotNull; - } - - /** - * 是否大于 - * - * @param value 字段值 - * @param operatorNum 操作数 - * @return 是否大于 - */ - private static Boolean isGreaterThan(Object value, String operatorNum) { - Boolean isGreaterThan = Boolean.FALSE; - if (value == null) { - return Boolean.FALSE; - } - Boolean isStringGreaterThen = (value instanceof String) && ((String) value).length() > Integer.valueOf(operatorNum); - Boolean isLongGreaterThen = (value instanceof Long) && ((Long) value) > Long.valueOf(operatorNum); - Boolean isIntegerGreaterThen = (value instanceof Integer) && ((Integer) value) > Integer.valueOf(operatorNum); - Boolean isShortGreaterThen = (value instanceof Short) && ((Short) value) > Short.valueOf(operatorNum); - Boolean isFloatGreaterThen = (value instanceof Float) && ((Float) value) > Float.valueOf(operatorNum); - Boolean isDoubleGreaterThen = (value instanceof Double) && ((Double) value) > Double.valueOf(operatorNum); - Boolean isCollectionGreaterThen = (value instanceof Collection) && ((Collection) value).size() > Integer.valueOf(operatorNum); - if (isStringGreaterThen || isLongGreaterThen || isIntegerGreaterThen || - isShortGreaterThen || isFloatGreaterThen || isDoubleGreaterThen || isCollectionGreaterThen) { - isGreaterThan = Boolean.TRUE; - } - return isGreaterThan; - } - - /** - * 是否大于等于 - * - * @param value 字段值 - * @param operatorNum 操作数 - * @return 是否大于等于 - */ - private static Boolean isGreaterThanEqual(Object value, String operatorNum) { - Boolean isGreaterThanEqual = Boolean.FALSE; - if (value == null) { - return Boolean.FALSE; - } - Boolean isStringGreaterThenEqual = (value instanceof String) && ((String) value).length() >= Integer.valueOf(operatorNum); - Boolean isLongGreaterThenEqual = (value instanceof Long) && ((Long) value) >= Long.valueOf(operatorNum); - Boolean isIntegerGreaterThenEqual = (value instanceof Integer) && ((Integer) value) >= Integer.valueOf(operatorNum); - Boolean isShortGreaterThenEqual = (value instanceof Short) && ((Short) value) >= Short.valueOf(operatorNum); - Boolean isFloatGreaterThenEqual = (value instanceof Float) && ((Float) value) >= Float.valueOf(operatorNum); - Boolean isDoubleGreaterThenEqual = (value instanceof Double) && ((Double) value) >= Double.valueOf(operatorNum); - Boolean isCollectionGreaterThenEqual = (value instanceof Collection) && ((Collection) value).size() >= Integer.valueOf(operatorNum); - if (isStringGreaterThenEqual || isLongGreaterThenEqual || isIntegerGreaterThenEqual || - isShortGreaterThenEqual || isFloatGreaterThenEqual || isDoubleGreaterThenEqual || isCollectionGreaterThenEqual) { - isGreaterThanEqual = Boolean.TRUE; - } - return isGreaterThanEqual; - } - - /** - * 是否少于 - * - * @param value 字段值 - * @param operatorNum 操作数 - * @return 是否少于 - */ - private static Boolean isLessThan(Object value, String operatorNum) { - Boolean isLessThan = Boolean.FALSE; - if (value == null) { - return Boolean.FALSE; - } - Boolean isStringLessThen = (value instanceof String) && ((String) value).length() < Integer.valueOf(operatorNum); - Boolean isLongLessThen = (value instanceof Long) && ((Long) value) < Long.valueOf(operatorNum); - Boolean isIntegerLessThen = (value instanceof Integer) && ((Integer) value) < Integer.valueOf(operatorNum); - Boolean isShortLessThen = (value instanceof Short) && ((Short) value) < Short.valueOf(operatorNum); - Boolean isFloatLessThen = (value instanceof Float) && ((Float) value) < Float.valueOf(operatorNum); - Boolean isDoubleLessThen = (value instanceof Double) && ((Double) value) < Double.valueOf(operatorNum); - Boolean isCollectionLessThen = (value instanceof Collection) && ((Collection) value).size() < Integer.valueOf(operatorNum); - if (isStringLessThen || isLongLessThen || isIntegerLessThen || - isShortLessThen || isFloatLessThen || isDoubleLessThen || isCollectionLessThen) { - isLessThan = Boolean.TRUE; - } - return isLessThan; - } - - /** - * 是否少于等于 - * - * @param value 字段值 - * @param operatorNum 操作数 - * @return 是否少于等于 - */ - private static Boolean isLessThanEqual(Object value, String operatorNum) { - Boolean isLessThanEqual = Boolean.FALSE; - if (value == null) { - return Boolean.FALSE; - } - Boolean isStringLessThenEqual = (value instanceof String) && ((String) value).length() <= Integer.valueOf(operatorNum); - Boolean isLongLessThenEqual = (value instanceof Long) && ((Long) value) <= Long.valueOf(operatorNum); - Boolean isIntegerLessThenEqual = (value instanceof Integer) && ((Integer) value) <= Integer.valueOf(operatorNum); - Boolean isShortLessThenEqual = (value instanceof Short) && ((Short) value) <= Short.valueOf(operatorNum); - Boolean isFloatLessThenEqual = (value instanceof Float) && ((Float) value) <= Float.valueOf(operatorNum); - Boolean isDoubleLessThenEqual = (value instanceof Double) && ((Double) value) <= Double.valueOf(operatorNum); - Boolean isCollectionLessThenEqual = (value instanceof Collection) && ((Collection) value).size() <= Integer.valueOf(operatorNum); - if (isStringLessThenEqual || isLongLessThenEqual || isIntegerLessThenEqual || - isShortLessThenEqual || isFloatLessThenEqual || isDoubleLessThenEqual || isCollectionLessThenEqual) { - isLessThanEqual = Boolean.TRUE; - } - return isLessThanEqual; - } - - /** - * 是否不等于 - * - * @param value 字段值 - * @param operatorNum 操作数 - * @return 是否不等于 - */ - private static Boolean isNotEqual(Object value, String operatorNum) { - Boolean isNotEqual = Boolean.FALSE; - if (value == null) { - return Boolean.FALSE; - } - Boolean isStringNotEqual = (value instanceof String) && !value.equals(operatorNum); - Boolean isLongNotEqual = (value instanceof Long) && !value.equals(Long.valueOf(operatorNum)); - Boolean isIntegerNotEqual = (value instanceof Integer) && !value.equals(Integer.valueOf(operatorNum)); - Boolean isShortNotEqual = (value instanceof Short) && !value.equals(Short.valueOf(operatorNum)); - Boolean isFloatNotEqual = (value instanceof Float) && !value.equals(Float.valueOf(operatorNum)); - Boolean isDoubleNotEqual = (value instanceof Double) && !value.equals(Double.valueOf(operatorNum)); - Boolean isCollectionNotEqual = (value instanceof Collection) && ((Collection) value).size() != Integer.valueOf(operatorNum); - if (isStringNotEqual || isLongNotEqual || isIntegerNotEqual || - isShortNotEqual || isFloatNotEqual || isDoubleNotEqual || isCollectionNotEqual) { - isNotEqual = Boolean.TRUE; - } - return isNotEqual; - } - - // -=================== 对不同类型的值进行校验 止 ======================= - - /** - * 判断是否符合参数规则 - * - * @param method 方法 - * @param arguments 方法参数 - * @return 是否符合 - */ - private Boolean isCheck(Method method, Object[] arguments) { - Boolean isCheck = Boolean.TRUE; - // 只允许有一个参数 - if (!method.isAnnotationPresent(Check.class) - || arguments == null - || arguments.length != 1) { - isCheck = Boolean.FALSE; - } - return isCheck; - } - - /** - * 获取方法 - * - * @param joinPoint ProceedingJoinPoint - * @return 方法 - */ - private Method getMethod(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - if (method.getDeclaringClass().isInterface()) { - try { - method = joinPoint - .getTarget() - .getClass() - .getDeclaredMethod(joinPoint.getSignature().getName(), - method.getParameterTypes()); - } catch (SecurityException | NoSuchMethodException e) { - LOG.error("" + e); - } - } - return method; - } - - /** - * 字段信息 - */ - class FieldInfo { - /** - * 字段 - */ - String field; - /** - * 提示信息 - */ - String innerMsg; - /** - * 操作符 - */ - String operator; - /** - * 操作数 - */ - String operatorNum; - /** - * 操作枚举 - */ - Operator optEnum; - } - - /** - * 操作枚举,封装操作符和对应的校验规则 - */ - enum Operator { - /** - * 大于 - */ - GREATER_THAN(">", CheckParamAspect::isGreaterThan), - /** - * 大于等于 - */ - GREATER_THAN_EQUAL(">=", CheckParamAspect::isGreaterThanEqual), - /** - * 小于 - */ - LESS_THAN("<", CheckParamAspect::isLessThan), - /** - * 小于等于 - */ - LESS_THAN_EQUAL("<=", CheckParamAspect::isLessThanEqual), - /** - * 不等于 - */ - NOT_EQUAL("!=", CheckParamAspect::isNotEqual), - /** - * 不为空 - */ - NOT_NULL("not null", CheckParamAspect::isNotNull); - - private String value; - - /** - * BiFunction:接收字段值(Object)和操作数(String),返回是否符合规则(Boolean) - */ - private BiFunction fun; - - Operator(String value, BiFunction fun) { - this.value = value; - this.fun = fun; - } - } - - // -====================== 常量 ========================= - - private static final String SEPARATOR = ":"; - -} +package com.cipher.checker; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * 参数校验 切面 + * + * @author cipher + * @date 2017/11/7 + */ +@Aspect +public class Checker { + + // -====================== constant ========================= + + private static final String SPLITOR = ":"; + + // -====================== log ========================= + + private static final Logger LOG = LoggerFactory.getLogger(Checker.class); + + private ExpressionParser parser = new SpelExpressionParser(); + private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); + private Function unsuccess; + + private Checker() { + } + + /** + * Action performed when check fails + * + * @param unsuccess lambda of the action + */ + public void setUnsuccess(Function unsuccess) { + this.unsuccess = unsuccess; + } + + /** + * checker builder + */ + public static class Builder { + private Checker checker = new Checker(); + + public Builder unsuccess(Function unsuccess) { + checker.setUnsuccess(unsuccess); + return this; + } + + public Checker build() { + return checker; + } + } + + /** + * initialize builder + * + * @return checker builder + * @see Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * aop around the method + * + * @param point ProceedingJoinPoint + * @return method result + * @throws Throwable method exception + */ + @Around(value = "@annotation(cn.ciphermagic.common.checker.Check)") + public Object check(ProceedingJoinPoint point) throws Throwable { + Object obj; + // check param + String msg = doCheck(point); + if (!StringUtils.isEmpty(msg)) { + return unsuccess.apply(msg); + } + obj = point.proceed(); + return obj; + } + + /** + * check param + * + * @param point ProceedingJoinPoint + * @return error message + */ + private String doCheck(ProceedingJoinPoint point) { + // get arguments + Object[] arguments = point.getArgs(); + // get method + Method method = getMethod(point); + String methodInfo = StringUtils.isEmpty(method.getName()) ? "" : " while calling " + method.getName(); + String msg = ""; + if (isCheck(method, arguments)) { + Check annotation = method.getAnnotation(Check.class); + String[] fields = annotation.value(); + Object vo = arguments[0]; + if (vo == null) { + msg = "param can not be null"; + } else { + for (String field : fields) { + FieldInfo info = resolveField(field, methodInfo); + Boolean isValid; + if (info.optEnum == Operator.SPEL) { + isValid = parseSpel(method, arguments, info.field); + } else { + String getMethodName = "get" + StringUtils.capitalize(info.field); + Method getMethod = ReflectionUtils.findMethod(vo.getClass(), getMethodName); + Object value = ReflectionUtils.invokeMethod(getMethod, vo); + isValid = info.optEnum.fun.apply(value, info.operatorNum); + } + if (!isValid) { + msg = info.innerMsg; + break; + } + } + } + } + return msg; + } + + /** + * parse spel expression + * + * @param method method + * @param arguments arguments + * @param spel spel expression + * @return is match + */ + private Boolean parseSpel(Method method, Object[] arguments, String spel) { + String[] params = discoverer.getParameterNames(method); + EvaluationContext context = new StandardEvaluationContext(); + for (int len = 0; len < params.length; len++) { + context.setVariable(params[len], arguments[len]); + } + try { + Expression expression = parser.parseExpression(spel); + return expression.getValue(context, Boolean.class); + } catch (Exception e) { + LOG.error("", e); + return Boolean.FALSE; + } + } + + /** + * parse field + * + * @param fieldStr field string + * @param methodInfo method info + * @return the entity contain field's info + */ + private FieldInfo resolveField(String fieldStr, String methodInfo) { + FieldInfo fieldInfo = new FieldInfo(); + String innerMsg = ""; + // parse error message + if (fieldStr.contains(SPLITOR)) { + if (fieldStr.split(SPLITOR).length == 2) { + innerMsg = fieldStr.split(SPLITOR)[1].trim(); + fieldStr = fieldStr.split(SPLITOR)[0].trim(); + } else { + throw new IllegalArgumentException("@Check annotation error: " + fieldStr); + } + } + // parse operator + if (fieldStr.startsWith("#")) { + fieldInfo.optEnum = Operator.SPEL; + } else if (fieldStr.contains(Operator.GREATER_THAN_EQUAL.value)) { + fieldInfo.optEnum = Operator.GREATER_THAN_EQUAL; + } else if (fieldStr.contains(Operator.LESS_THAN_EQUAL.value)) { + fieldInfo.optEnum = Operator.LESS_THAN_EQUAL; + } else if (fieldStr.contains(Operator.GREATER_THAN.value)) { + fieldInfo.optEnum = Operator.GREATER_THAN; + } else if (fieldStr.contains(Operator.LESS_THAN.value)) { + fieldInfo.optEnum = Operator.LESS_THAN; + } else if (fieldStr.contains(Operator.NOT_EQUAL.value)) { + fieldInfo.optEnum = Operator.NOT_EQUAL; + } else { + fieldInfo.optEnum = Operator.NOT_NULL; + } + // direct assignment field + if (fieldInfo.optEnum == Operator.NOT_NULL || fieldInfo.optEnum == Operator.SPEL) { + fieldInfo.field = fieldStr; + } + // other operators, need to separate fields and operands + else { + fieldInfo.field = fieldStr.split(fieldInfo.optEnum.value)[0]; + fieldInfo.operatorNum = fieldStr.split(fieldInfo.optEnum.value)[1]; + } + fieldInfo.operator = fieldInfo.optEnum.value; + String operatorNum = fieldInfo.operatorNum == null ? "" : " " + fieldInfo.operatorNum; + String defaultMsg = fieldInfo.field + " must " + fieldInfo.operator + operatorNum + methodInfo; + fieldInfo.innerMsg = StringUtils.isEmpty(innerMsg) ? defaultMsg : innerMsg; + return fieldInfo; + } + + /** + * is not null + * + * @param value field's value + * @param operatorNum the num of operator + * @return is not null + */ + private static Boolean isNotNull(Object value, String operatorNum) { + Boolean isNotNull = Boolean.TRUE; + Boolean isStringNull = (value instanceof String) && StringUtils.isEmpty(value); + Boolean isCollectionNull = (value instanceof Collection) && CollectionUtils.isEmpty((Collection) value); + if (value == null) { + isNotNull = Boolean.FALSE; + } else if (isStringNull || isCollectionNull) { + isNotNull = Boolean.FALSE; + } + return isNotNull; + } + + /** + * is greater than + * + * @param value field value + * @param operatorNum operatorNum + * @return is greater than + */ + private static Boolean isGreaterThan(Object value, String operatorNum) { + Boolean isGreaterThan = Boolean.FALSE; + if (value == null) { + return Boolean.FALSE; + } + boolean isStringGreaterThen = (value instanceof String) && ((String) value).length() > Integer.valueOf(operatorNum); + boolean isLongGreaterThen = (value instanceof Long) && ((Long) value) > Long.valueOf(operatorNum); + boolean isIntegerGreaterThen = (value instanceof Integer) && ((Integer) value) > Integer.valueOf(operatorNum); + boolean isShortGreaterThen = (value instanceof Short) && ((Short) value) > Short.valueOf(operatorNum); + boolean isFloatGreaterThen = (value instanceof Float) && ((Float) value) > Float.valueOf(operatorNum); + boolean isDoubleGreaterThen = (value instanceof Double) && ((Double) value) > Double.valueOf(operatorNum); + boolean isCollectionGreaterThen = (value instanceof Collection) && ((Collection) value).size() > Integer.valueOf(operatorNum); + if (isStringGreaterThen || isLongGreaterThen || isIntegerGreaterThen || + isShortGreaterThen || isFloatGreaterThen || isDoubleGreaterThen || isCollectionGreaterThen) { + isGreaterThan = Boolean.TRUE; + } + return isGreaterThan; + } + + /** + * is greater than or equal to + * + * @param value field value + * @param operatorNum operatorNum + * @return is greater than or equal to + */ + private static Boolean isGreaterThanEqual(Object value, String operatorNum) { + Boolean isGreaterThanEqual = Boolean.FALSE; + if (value == null) { + return Boolean.FALSE; + } + boolean isStringGreaterThenEqual = (value instanceof String) && ((String) value).length() >= Integer.valueOf(operatorNum); + boolean isLongGreaterThenEqual = (value instanceof Long) && ((Long) value) >= Long.valueOf(operatorNum); + boolean isIntegerGreaterThenEqual = (value instanceof Integer) && ((Integer) value) >= Integer.valueOf(operatorNum); + boolean isShortGreaterThenEqual = (value instanceof Short) && ((Short) value) >= Short.valueOf(operatorNum); + boolean isFloatGreaterThenEqual = (value instanceof Float) && ((Float) value) >= Float.valueOf(operatorNum); + boolean isDoubleGreaterThenEqual = (value instanceof Double) && ((Double) value) >= Double.valueOf(operatorNum); + boolean isCollectionGreaterThenEqual = (value instanceof Collection) && ((Collection) value).size() >= Integer.valueOf(operatorNum); + if (isStringGreaterThenEqual || isLongGreaterThenEqual || isIntegerGreaterThenEqual || + isShortGreaterThenEqual || isFloatGreaterThenEqual || isDoubleGreaterThenEqual || isCollectionGreaterThenEqual) { + isGreaterThanEqual = Boolean.TRUE; + } + return isGreaterThanEqual; + } + + /** + * is less than + * + * @param value field value + * @param operatorNum operatorNum + * @return is less than + */ + private static Boolean isLessThan(Object value, String operatorNum) { + Boolean isLessThan = Boolean.FALSE; + if (value == null) { + return Boolean.FALSE; + } + boolean isStringLessThen = (value instanceof String) && ((String) value).length() < Integer.valueOf(operatorNum); + boolean isLongLessThen = (value instanceof Long) && ((Long) value) < Long.valueOf(operatorNum); + boolean isIntegerLessThen = (value instanceof Integer) && ((Integer) value) < Integer.valueOf(operatorNum); + boolean isShortLessThen = (value instanceof Short) && ((Short) value) < Short.valueOf(operatorNum); + boolean isFloatLessThen = (value instanceof Float) && ((Float) value) < Float.valueOf(operatorNum); + boolean isDoubleLessThen = (value instanceof Double) && ((Double) value) < Double.valueOf(operatorNum); + boolean isCollectionLessThen = (value instanceof Collection) && ((Collection) value).size() < Integer.valueOf(operatorNum); + if (isStringLessThen || isLongLessThen || isIntegerLessThen || + isShortLessThen || isFloatLessThen || isDoubleLessThen || isCollectionLessThen) { + isLessThan = Boolean.TRUE; + } + return isLessThan; + } + + /** + * is less than or equal to + * + * @param value field value + * @param operatorNum operatorNum + * @return is less than or equal to + */ + private static Boolean isLessThanEqual(Object value, String operatorNum) { + Boolean isLessThanEqual = Boolean.FALSE; + if (value == null) { + return Boolean.FALSE; + } + boolean isStringLessThenEqual = (value instanceof String) && ((String) value).length() <= Integer.valueOf(operatorNum); + boolean isLongLessThenEqual = (value instanceof Long) && ((Long) value) <= Long.valueOf(operatorNum); + boolean isIntegerLessThenEqual = (value instanceof Integer) && ((Integer) value) <= Integer.valueOf(operatorNum); + boolean isShortLessThenEqual = (value instanceof Short) && ((Short) value) <= Short.valueOf(operatorNum); + boolean isFloatLessThenEqual = (value instanceof Float) && ((Float) value) <= Float.valueOf(operatorNum); + boolean isDoubleLessThenEqual = (value instanceof Double) && ((Double) value) <= Double.valueOf(operatorNum); + boolean isCollectionLessThenEqual = (value instanceof Collection) && ((Collection) value).size() <= Integer.valueOf(operatorNum); + if (isStringLessThenEqual || isLongLessThenEqual || isIntegerLessThenEqual || + isShortLessThenEqual || isFloatLessThenEqual || isDoubleLessThenEqual || isCollectionLessThenEqual) { + isLessThanEqual = Boolean.TRUE; + } + return isLessThanEqual; + } + + /** + * is not equal + * + * @param value field value + * @param operatorNum operatorNum + * @return is not equal + */ + private static Boolean isNotEqual(Object value, String operatorNum) { + Boolean isNotEqual = Boolean.FALSE; + if (value == null) { + return Boolean.FALSE; + } + boolean isStringNotEqual = (value instanceof String) && !value.equals(operatorNum); + boolean isLongNotEqual = (value instanceof Long) && !value.equals(Long.valueOf(operatorNum)); + boolean isIntegerNotEqual = (value instanceof Integer) && !value.equals(Integer.valueOf(operatorNum)); + boolean isShortNotEqual = (value instanceof Short) && !value.equals(Short.valueOf(operatorNum)); + boolean isFloatNotEqual = (value instanceof Float) && !value.equals(Float.valueOf(operatorNum)); + boolean isDoubleNotEqual = (value instanceof Double) && !value.equals(Double.valueOf(operatorNum)); + boolean isCollectionNotEqual = (value instanceof Collection) && ((Collection) value).size() != Integer.valueOf(operatorNum); + if (isStringNotEqual || isLongNotEqual || isIntegerNotEqual || + isShortNotEqual || isFloatNotEqual || isDoubleNotEqual || isCollectionNotEqual) { + isNotEqual = Boolean.TRUE; + } + return isNotEqual; + } + + /** + * is meets the parameter rules + * + * @param method method + * @param arguments arguments + * @return is meets + */ + private Boolean isCheck(Method method, Object[] arguments) { + Boolean isCheck = Boolean.TRUE; + if (!method.isAnnotationPresent(Check.class) || arguments == null) { + isCheck = Boolean.FALSE; + } + return isCheck; + } + + /** + * get the method + * + * @param joinPoint ProceedingJoinPoint + * @return method + */ + private Method getMethod(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + if (method.getDeclaringClass().isInterface()) { + try { + method = joinPoint + .getTarget() + .getClass() + .getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes()); + } catch (SecurityException | NoSuchMethodException e) { + LOG.error("" + e); + } + } + return method; + } + + /** + * file info + */ + class FieldInfo { + /** + * field + */ + String field; + /** + * prompt message + */ + String innerMsg; + /** + * operator + */ + String operator; + /** + * num of operator + */ + String operatorNum; + /** + * enum of operator + */ + Operator optEnum; + } + + /** + * enum of operator + */ + enum Operator { + /** + * spel expression + */ + SPEL("match spel expression", null), + /** + * GreaterThan + */ + GREATER_THAN(">", Checker::isGreaterThan), + /** + * GreaterThanEqual + */ + GREATER_THAN_EQUAL(">=", Checker::isGreaterThanEqual), + /** + * LessThan + */ + LESS_THAN("<", Checker::isLessThan), + /** + * LessThanEqual + */ + LESS_THAN_EQUAL("<=", Checker::isLessThanEqual), + /** + * NotEqual + */ + NOT_EQUAL("!=", Checker::isNotEqual), + /** + * NotNull + */ + NOT_NULL("not null", Checker::isNotNull); + + private String value; + private BiFunction fun; + + Operator(String value, BiFunction fun) { + this.value = value; + this.fun = fun; + } + } + +} diff --git a/sandbox/src/main/java/com/cipher/data_structure/E_Binary_Search_Tree/BST.java b/sandbox/src/main/java/com/cipher/data_structure/E_Binary_Search_Tree/BST.java index 922421d..b42fb04 100644 --- a/sandbox/src/main/java/com/cipher/data_structure/E_Binary_Search_Tree/BST.java +++ b/sandbox/src/main/java/com/cipher/data_structure/E_Binary_Search_Tree/BST.java @@ -93,23 +93,6 @@ private void postOrder(Node node) { } } - public void preOrderNR() { - if (root != null) { - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - Node cur = stack.pop(); - System.out.println(cur.e); - if (cur.right != null) { - stack.push(cur.right); - } - if (cur.left != null) { - stack.push(cur.left); - } - } - } - } - public void levelOrder() { if (root != null) { Queue queue = new LinkedList<>(); @@ -234,14 +217,85 @@ public Node(E e) { } } + public void inOrderNR() { + if (root != null) { + Stack stack = new Stack<>(); + while (!stack.isEmpty() || root != null) { + if (root != null) { + stack.push(root); + root = root.left; + } else { + Node curr = stack.pop(); + System.out.println(curr.e); + root = curr.right; + } + } + } + } + + public void preOrderNR() { + if (root != null) { + Stack stack = new Stack<>(); + while (!stack.isEmpty() || root != null) { + if (root != null) { + System.out.println(root.e); + stack.push(root); + root = root.left; + } else { + Node curr = stack.pop(); + root = curr.right; + } + } + } + } + + public void postOrderNR2() { + Node cur = root; + Stack stack = new Stack<>(); + stack.push(cur); + while (!stack.isEmpty()) { + Node peek = stack.peek(); + if (peek.left != null && peek.left != cur && peek.right != cur) { + stack.push(peek.left); + } else if (peek.right != null && peek.right != cur) { + stack.push((peek.right)); + } else { + Node temp = stack.pop(); + System.out.println(temp.e); + cur = temp; + } + } + } + + public void postOrderNR() { + LinkedList list = new LinkedList<>(); + if (root != null) { + Stack stack = new Stack<>(); + while (!stack.isEmpty() || root != null) { + if (root != null) { + list.addFirst(root.e); + stack.push(root); + root = root.right; + } else { + Node curr = stack.pop(); + root = curr.left; + } + } + } + for (E e : list) { + System.out.println(e); + } + } + public static void main(String[] args) { BST bst = new BST<>(); int[] nums = {5, 3, 6, 2, 4, 8}; for (Integer i : nums) { bst.add(i); } - bst.remove(6); - bst.levelOrder(); +// bst.inOrderNR(); +// bst.preOrderNR(); + bst.preOrderNR(); } } diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/FileOperation.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/FileOperation.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/FileOperation.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/FileOperation.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/BSTMap.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/BSTMap.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/BSTMap.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/BSTMap.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LeetCode349.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LeetCode349.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LeetCode349.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LeetCode349.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LeetCode350.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LeetCode350.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LeetCode350.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LeetCode350.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LinkedListMap.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LinkedListMap.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/LinkedListMap.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/LinkedListMap.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/Main.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/Main.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/Main.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/Main.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/Map.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/Map.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/map/Map.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/map/Map.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/resource/a-tale-of-two-cities.txt b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/resource/a-tale-of-two-cities.txt similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/resource/a-tale-of-two-cities.txt rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/resource/a-tale-of-two-cities.txt diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/resource/pride-and-prejudice.txt b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/resource/pride-and-prejudice.txt similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/resource/pride-and-prejudice.txt rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/resource/pride-and-prejudice.txt diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/BSTSet.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/BSTSet.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/BSTSet.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/BSTSet.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/LeetCode804.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/LeetCode804.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/LeetCode804.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/LeetCode804.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/LinkedListSet.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/LinkedListSet.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/LinkedListSet.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/LinkedListSet.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/Main.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/Main.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/Main.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/Main.java diff --git a/sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/Set.java b/sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/Set.java similarity index 100% rename from sandbox/src/main/java/com/cipher/data_structure/F_Set_And_Map/set/Set.java rename to sandbox/src/main/java/com/cipher/data_structure/F_Set_and_Map/set/Set.java diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/Aspect.java b/sandbox/src/main/java/com/cipher/interview/annotation/Aspect.java new file mode 100644 index 0000000..5843caf --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/Aspect.java @@ -0,0 +1,17 @@ +package com.cipher.interview.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author cipher + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Aspect { + + Class type(); + +} diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/IAspect.java b/sandbox/src/main/java/com/cipher/interview/annotation/IAspect.java new file mode 100644 index 0000000..b7e3124 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/IAspect.java @@ -0,0 +1,11 @@ +package com.cipher.interview.annotation; + +/** + * @author cipher + */ +public interface IAspect { + + void before(); + void after(); + +} diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/IOrder.java b/sandbox/src/main/java/com/cipher/interview/annotation/IOrder.java new file mode 100644 index 0000000..5168032 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/IOrder.java @@ -0,0 +1,9 @@ +package com.cipher.interview.annotation; + +public interface IOrder { + + void pay() throws InterruptedException; + + void show(); + +} diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/ObjectFactory.java b/sandbox/src/main/java/com/cipher/interview/annotation/ObjectFactory.java new file mode 100644 index 0000000..48e7600 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/ObjectFactory.java @@ -0,0 +1,45 @@ +package com.cipher.interview.annotation; + +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.LinkedList; + +/** + * @author cipher + */ +@SuppressWarnings("unchecked") +public class ObjectFactory { + + public static T newInstance(Class clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + + Annotation[] annotations = clazz.getAnnotations(); + LinkedList aspects = new LinkedList<>(); + + for (Annotation annotation : annotations) { + if (annotation instanceof Aspect) { + Class type = ((Aspect) annotation).type(); + IAspect aspect = (IAspect) (type.getConstructor().newInstance()); + aspects.push(aspect); + } + } + + T inst = clazz.getConstructor().newInstance(); + return (T) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), (proxy, method, args) -> { + aspects.forEach(IAspect::before); + Object result = method.invoke(inst); + aspects.forEach(IAspect::after); + return result; + }); + } + + @Test + public void test() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, InterruptedException { + IOrder order = ObjectFactory.newInstance(Order.class); + order.pay(); + order.show(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/Order.java b/sandbox/src/main/java/com/cipher/interview/annotation/Order.java new file mode 100644 index 0000000..aa15965 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/Order.java @@ -0,0 +1,33 @@ +package com.cipher.interview.annotation; + +import org.junit.Test; + +import java.lang.reflect.Proxy; + +@Aspect(type = TimeUsageAspect.class) +public class Order implements IOrder { + + int state = 0; + + @Override + public void pay() throws InterruptedException { + Thread.sleep(50); + this.state = 1; + } + + @Override + public void show() { + System.out.println("order status:" + this.state); + } + + @Test + public void testProxy() throws InterruptedException { + IOrder order = new Order(); + IOrder proxy = (IOrder) Proxy.newProxyInstance(Order.class.getClassLoader(), new Class[]{IOrder.class}, (proxy1, method, args) -> { + System.out.println("before invoke method:" + method); + return method.invoke(order); + }); + proxy.pay(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/annotation/TimeUsageAspect.java b/sandbox/src/main/java/com/cipher/interview/annotation/TimeUsageAspect.java new file mode 100644 index 0000000..1f191d8 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/annotation/TimeUsageAspect.java @@ -0,0 +1,17 @@ +package com.cipher.interview.annotation; + +public class TimeUsageAspect implements IAspect { + + long start; + + @Override + public void before() { + start = System.currentTimeMillis(); + } + + @Override + public void after() { + long usage = System.currentTimeMillis() - start; + System.out.format("time usage : %dms\n", usage); + } +} diff --git a/sandbox/src/main/java/com/cipher/interview/buffer/BufferExampleTest.java b/sandbox/src/main/java/com/cipher/interview/buffer/BufferExampleTest.java new file mode 100644 index 0000000..23aac25 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/buffer/BufferExampleTest.java @@ -0,0 +1,69 @@ +package com.cipher.interview.buffer; + +import org.junit.Test; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Random; + +/** + * @author cipher + */ +public class BufferExampleTest { + + private static final long WORD_NUM = 1000000000; + + @Test + public void gen() throws IOException { + Random r = new Random(); + String fileName = "word"; + + int bufferSize = 4 * 1024; + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(fileName), bufferSize); + + long start = System.currentTimeMillis(); + + for (int i = 0; i < WORD_NUM; i++) { + for (int j = 0; j < 5; j++) { + outputStream.write(97 + r.nextInt(5)); + } + outputStream.write(' '); + } + outputStream.close(); + System.out.println(System.currentTimeMillis() - start); + } + + @Test + public void testChinese() { + String raw = "长坂桥头杀气生,横枪立马眼圆睁。一声好似轰雷震,独退曹家百万兵。"; + Charset charset = StandardCharsets.UTF_8; + byte[] bytes = charset.encode(raw).array(); + byte[] bytes2 = Arrays.copyOfRange(bytes, 0, 11); + + ByteBuffer bbuf = ByteBuffer.allocate(12); + CharBuffer cbuf = CharBuffer.allocate(12); + + bbuf.put(bytes2); + bbuf.flip(); + + charset.newDecoder().decode(bbuf, cbuf, true); + cbuf.flip(); + + char[] tmp = new char[cbuf.length()]; + if (cbuf.hasRemaining()) { + cbuf.get(tmp); + System.out.println("here:" + new String(tmp)); + } + + System.out.format("limit-pos=%d\n", bbuf.limit() - bbuf.position()); + + bytes2 = Arrays.copyOfRange(bbuf.array(), bbuf.position(), bbuf.limit()); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/buffer/WordCountTest.java b/sandbox/src/main/java/com/cipher/interview/buffer/WordCountTest.java new file mode 100644 index 0000000..39ecdc6 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/buffer/WordCountTest.java @@ -0,0 +1,122 @@ +package com.cipher.interview.buffer; + +import org.junit.Test; + +import java.io.*; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author cipher + */ +public class WordCountTest { + + private static final ExecutorService POOL = Executors.newFixedThreadPool(16); + private static final AtomicInteger count = new AtomicInteger(); + + + private static HashMap countByString(String str) { + HashMap map = new HashMap<>(str.length()); + StringTokenizer tokenizer = new StringTokenizer(str); + while (tokenizer.hasMoreTokens()) { + String word = tokenizer.nextToken(); + incKey(word, map, 1); + } + return map; + } + + private static void incKey(String key, HashMap map, Integer n) { + if (map.containsKey(key)) { + map.put(key, map.get(key) + n); + } else { + map.put(key, n); + } + } + + static class CountTask implements Callable> { + private final long start; + private final long end; + private final String fileName; + + public CountTask(String fileName, long start, long end) { + this.start = start; + this.end = end; + this.fileName = fileName; + + } + + @Override + public HashMap call() throws Exception { + FileChannel channel = new RandomAccessFile(this.fileName, "rw").getChannel(); + // [start, end] -> Memory + // Device -> Kernel Space -> UserSpace(buffer) -> Thread + MappedByteBuffer mbuf = channel.map(FileChannel.MapMode.READ_ONLY, this.start, this.end - this.start); + String str = StandardCharsets.US_ASCII.decode(mbuf).toString(); + return countByString(str); + } + } + + public void run(String fileName, long chunkSize) throws ExecutionException, InterruptedException { + File file = new File(fileName); + long fileSize = file.length(); + + long position = 0; + + long startTime = System.currentTimeMillis(); + ArrayList>> tasks = new ArrayList<>(); + while (position < fileSize) { + long next = Math.min(position + chunkSize, fileSize); + CountTask task = new CountTask(fileName, position, next); + position = next; + Future> future = POOL.submit(task); + tasks.add(future); + } + System.out.format("split to %d tasks\n", tasks.size()); + + HashMap totalMap = new HashMap<>(16); + for (Future> future : tasks) { + HashMap map = future.get(); + System.out.println(count.getAndIncrement()); + for (Map.Entry entry : map.entrySet()) { + incKey(entry.getKey(), totalMap, entry.getValue()); + } + } + + System.out.println("time:" + (System.currentTimeMillis() - startTime) + "ms"); + System.out.println("total:" + totalMap.size()); + System.out.println(totalMap.get("ababb")); + } + + @Test + public void count() throws ExecutionException, InterruptedException { + WordCountTest counter = new WordCountTest(); + System.out.println("processors:" + Runtime.getRuntime().availableProcessors()); + counter.run("word", 1024 * 1024 * 20); + } + + @Test + public void compareWithSingle() throws IOException { + BufferedInputStream in = new BufferedInputStream(new FileInputStream("word")); + byte[] buf = new byte[4 * 1024]; + int len; + HashMap total = new HashMap<>(16); + long startTime = System.currentTimeMillis(); + while ((len = in.read(buf)) != -1) { + byte[] bytes = Arrays.copyOfRange(buf, 0, len); + String str = new String(bytes); + HashMap hashMap = countByString(str); + for (Map.Entry entry : hashMap.entrySet()) { + String key = entry.getKey(); + incKey(key, total, entry.getValue()); + } + } + System.out.println("time:" + (System.currentTimeMillis() - startTime) + "ms"); + System.out.println(total.get("ababb")); + System.out.println(total.size()); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/collection/RandomStringGenerator.java b/sandbox/src/main/java/com/cipher/interview/collection/RandomStringGenerator.java new file mode 100644 index 0000000..59028d3 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/collection/RandomStringGenerator.java @@ -0,0 +1,41 @@ +package com.cipher.interview.collection; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * @author cipher + */ +public class RandomStringGenerator implements Iterable { + + private final List list; + + public RandomStringGenerator(List list) { + this.list = list; + } + + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public T next() { + return list.get((int) (list.size() * Math.random())); + } + }; + } + + public static void main(String[] args) { + List list = Arrays.asList("List", "Tree", "Array"); + RandomStringGenerator gen = new RandomStringGenerator<>(list); + for (String s : gen) { + System.out.println(s); + } + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/proxy/Aspect.java b/sandbox/src/main/java/com/cipher/interview/proxy/Aspect.java new file mode 100644 index 0000000..09a7309 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/proxy/Aspect.java @@ -0,0 +1,63 @@ +package com.cipher.interview.proxy; + + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author cipher + */ +@SuppressWarnings("unchecked") +public interface Aspect { + + /** + * before + */ + void before(); + + /** + * after + */ + void after(); + + /** + * getProxy + * + * @param cls cls + * @param aspects aspects + * @param t + * @return T + * @throws NoSuchMethodException NoSuchMethod Exception + * @throws InvocationTargetException InvocationTarget Exception + * @throws InstantiationException Instantiation Exception + * @throws IllegalAccessException IllegalAccess Exception + */ + static T getProxy(Class cls, String... aspects) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + List aspectInsts = Arrays.stream(aspects).map(name -> { + try { + Class clazz = Class.forName(name); + return (Aspect) clazz.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + }).filter(Objects::nonNull) + .collect(Collectors.toList()); + T inst = cls.getConstructor().newInstance(); + return (T) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), (o, m, args) -> { + for (Aspect aspect : aspectInsts) { + aspect.before(); + } + Object result = m.invoke(inst); + for (Aspect aspect : aspectInsts) { + aspect.after(); + } + return result; + }); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/proxy/IOrder.java b/sandbox/src/main/java/com/cipher/interview/proxy/IOrder.java new file mode 100644 index 0000000..d773db7 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/proxy/IOrder.java @@ -0,0 +1,9 @@ +package com.cipher.interview.proxy; + +public interface IOrder { + + void pay() throws InterruptedException; + + void show(); + +} diff --git a/sandbox/src/main/java/com/cipher/interview/proxy/Order.java b/sandbox/src/main/java/com/cipher/interview/proxy/Order.java new file mode 100644 index 0000000..76b2e8a --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/proxy/Order.java @@ -0,0 +1,21 @@ +package com.cipher.interview.proxy; + +/** + * @author cipher + */ +public class Order implements IOrder { + + int state = 0; + + @Override + public void pay() throws InterruptedException { + Thread.sleep(50); + this.state = 1; + } + + @Override + public void show() { + System.out.println("order status:" + this.state); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/proxy/ProxyExampleTest.java b/sandbox/src/main/java/com/cipher/interview/proxy/ProxyExampleTest.java new file mode 100644 index 0000000..334642f --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/proxy/ProxyExampleTest.java @@ -0,0 +1,19 @@ +package com.cipher.interview.proxy; + +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; + +/** + * @author cipher + */ +public class ProxyExampleTest { + + @Test + public void testProxy() throws InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + IOrder order = Aspect.getProxy(Order.class, "com.cipher.interview.proxy.TimeUsageAspect"); + order.pay(); + order.show(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/interview/proxy/TimeUsageAspect.java b/sandbox/src/main/java/com/cipher/interview/proxy/TimeUsageAspect.java new file mode 100644 index 0000000..3f083bc --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/proxy/TimeUsageAspect.java @@ -0,0 +1,21 @@ +package com.cipher.interview.proxy; + +/** + * @author cipher + */ +public class TimeUsageAspect implements Aspect { + + long start; + + @Override + public void before() { + start = System.currentTimeMillis(); + } + + @Override + public void after() { + long usage = System.currentTimeMillis() - start; + System.out.format("time usage : %dms\n", usage); + + } +} diff --git a/sandbox/src/main/java/com/cipher/interview/stream/Event.java b/sandbox/src/main/java/com/cipher/interview/stream/Event.java new file mode 100644 index 0000000..bf74a4a --- /dev/null +++ b/sandbox/src/main/java/com/cipher/interview/stream/Event.java @@ -0,0 +1,60 @@ +package com.cipher.interview.stream; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * @author cipher + */ +public class Event { + + T data; + + public Event(T data) { + this.data = data; + } + + static class EventData { + Integer id; + String msg; + + public EventData(Integer id, String msg) { + this.id = id; + this.msg = msg; + } + + @Override + public String toString() { + return "EventData{" + "id=" + id + ", msg='" + msg + '\'' + '}'; + } + } + + static class Transforms { + static EventData transform(Integer id) { + switch (id) { + case 0: + return new EventData(id, "Start"); + case 1: + return new EventData(id, "Running"); + case 2: + return new EventData(id, "Done"); + case 3: + return new EventData(id, "Fail"); + default: + return new EventData(id, "Error"); + } + } + } + + Event map(Function f) { + return new Event<>(f.apply(this.data)); + } + + public static void main(String[] args) { + Stream> s = Stream.of(new Event<>(1), new Event<>(2), new Event<>(0), new Event<>(10)); + + s.map(event -> event.map(Transforms::transform)).forEach(e -> { + System.out.println(e.data); + }); + } +} diff --git a/sandbox/src/main/java/com/cipher/javassist/Example.java b/sandbox/src/main/java/com/cipher/javassist/Example.java index e38523e..2c7d3c0 100644 --- a/sandbox/src/main/java/com/cipher/javassist/Example.java +++ b/sandbox/src/main/java/com/cipher/javassist/Example.java @@ -105,10 +105,10 @@ public static void test05() throws Exception { } public static void main(String[] args) throws Exception { -// test01(); + test01(); // test02(); // test03(); - test04(); +// test04(); // test05(); } diff --git a/sandbox/src/main/java/com/cipher/jmh/Test.java b/sandbox/src/main/java/com/cipher/jmh/Test.java new file mode 100644 index 0000000..533e138 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/jmh/Test.java @@ -0,0 +1,47 @@ +package com.cipher.jmh; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Fork(1) +@Warmup(iterations = 1) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Threads(1) +@State(Scope.Benchmark) +public class Test { + + private Map hashMap; + private Map synchronizedMap; + + @Setup + public void setUp() { + this.hashMap = new HashMap<>(); + this.synchronizedMap = Collections.synchronizedMap(new HashMap<>()); + } + + @Benchmark + public void testHashMap() { + this.hashMap.put(System.nanoTime(), System.nanoTime()); + } + + @Benchmark + public void testSynchronizedMap() { + this.synchronizedMap.put(System.nanoTime(), System.nanoTime()); + } + + public static void main(String[] args) throws RunnerException { + final Options opts = new OptionsBuilder().include(Test.class.getSimpleName()).build(); + new Runner(opts).run(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/jvm/Hello.java b/sandbox/src/main/java/com/cipher/jvm/Hello.java new file mode 100644 index 0000000..5ee3beb --- /dev/null +++ b/sandbox/src/main/java/com/cipher/jvm/Hello.java @@ -0,0 +1,14 @@ +package com.cipher.jvm; + +/** + * 对比 class 字节码查看 + */ +public class Hello { + + private static String msg = "Goods morning"; + + public static void main(String[] args) { + System.out.println("msg = " + msg); + } + +} diff --git a/sandbox/src/main/java/com/cipher/jvm/JHSDB_TestCase.java b/sandbox/src/main/java/com/cipher/jvm/JHSDB_TestCase.java new file mode 100644 index 0000000..f6512f6 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/jvm/JHSDB_TestCase.java @@ -0,0 +1,30 @@ +package com.cipher.jvm; + +/** + * staticObj, instanceObj, localObj 存放在哪里 + * -Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops + * + * @author: CipherCui + * @since 2020/6/30 + */ +public class JHSDB_TestCase { + + static class Test { + static ObjectHolder staticObj = new ObjectHolder(); + ObjectHolder instanceObj = new ObjectHolder(); + + void foo() { + ObjectHolder localObj = new ObjectHolder(); + System.out.println("down"); // 这里设一个断点 + } + } + + private static class ObjectHolder { + } + + public static void main(String[] args) { + Test test = new JHSDB_TestCase.Test(); + test.foo(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/method_name/FnConverter.java b/sandbox/src/main/java/com/cipher/method_name/FnConverter.java new file mode 100644 index 0000000..8bbf53a --- /dev/null +++ b/sandbox/src/main/java/com/cipher/method_name/FnConverter.java @@ -0,0 +1,34 @@ +package com.cipher.method_name; + +import java.io.Serializable; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Method; + +public class FnConverter { + + @FunctionalInterface + public interface Fn extends Serializable { + Object apply(T source); + } + + public static String convertFnToString(Fn fn) { + return fnToFieldName(fn); + } + + public static String fnToFieldName(Fn fn) { + try { + Method method = fn.getClass().getDeclaredMethod("writeReplace"); + method.setAccessible(Boolean.TRUE); + SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn); + return serializedLambda.getImplMethodName(); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException(e); + } + } + + public static void main(String[] args) { + String fieldName = FnConverter.convertFnToString(Foo::getBar); + System.out.println("方法名:" + fieldName); + } + +} diff --git a/sandbox/src/main/java/com/cipher/method_name/Foo.java b/sandbox/src/main/java/com/cipher/method_name/Foo.java new file mode 100644 index 0000000..57b2deb --- /dev/null +++ b/sandbox/src/main/java/com/cipher/method_name/Foo.java @@ -0,0 +1,15 @@ +package com.cipher.method_name; + +public class Foo { + + private Integer bar; + + public Integer getBar() { + return bar; + } + + public void setBar(Integer bar) { + this.bar = bar; + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/IOClient.java b/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/IOClient.java deleted file mode 100644 index 03f9280..0000000 --- a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/IOClient.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cipher.netty.A_what_is_netty.client; - -import java.io.IOException; -import java.net.Socket; -import java.util.Date; - -/** - * @Author: CipherCui - * @Description: - * @Date: Created in 15:51 2018/9/28 - */ -public class IOClient { - - public static void main(String[] args) { - new Thread(() -> { - try { - Socket socket = new Socket("127.0.0.1", 8000); - while (true) { - try { - socket.getOutputStream().write((new Date() + ": hello world").getBytes()); - Thread.sleep(2000); - } catch (Exception e) { - } - } - } catch (IOException e) { - } - }).start(); - } - -} diff --git a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/NettyClient.java b/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/NettyClient.java deleted file mode 100644 index 62b667c..0000000 --- a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/client/NettyClient.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.cipher.netty.A_what_is_netty.client; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.string.StringEncoder; - -import java.util.Date; - -/** - * @Author: CipherCui - * @Description: - * @Date: Created in 16:15 2018/9/28 - */ -public class NettyClient { - - public static void main(String[] args) throws InterruptedException { - Bootstrap bootstrap = new Bootstrap(); - NioEventLoopGroup group = new NioEventLoopGroup(); - bootstrap.group(group) - .channel(NioSocketChannel.class) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) { - ch.pipeline().addLast(new StringEncoder()); - } - }); - Channel channel = bootstrap.connect("127.0.0.1", 8000).channel(); - while (true) { - channel.writeAndFlush(new Date() + ": hello world!"); - Thread.sleep(2000); - } - } - -} diff --git a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/IOServer.java b/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/IOServer.java deleted file mode 100644 index b0cc3fa..0000000 --- a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/IOServer.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.cipher.netty.A_what_is_netty.server; - -import java.io.IOException; -import java.io.InputStream; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * @Author: CipherCui - * @Description: - * @Date: Created in 15:42 2018/9/28 - */ -public class IOServer { - - public static void main(String[] args) throws Exception { - ServerSocket serverSocket = new ServerSocket(8000); - // 接收新连接线程 - new Thread(() -> { - while (true) { - try { - // 阻塞方法获取新的连接 - Socket socket = serverSocket.accept(); - // 每一个新的连接都创建一个线程,负责读取数据 - new Thread(() -> { - try { - int len; - byte[] data = new byte[1024]; - InputStream inputStream = socket.getInputStream(); - // 按字节流方式读取数据 - while ((len = inputStream.read(data)) != -1) { - System.out.println(new String(data, 0, len)); - } - } catch (IOException e) { - } - }).start(); - } catch (IOException e) { - } - } - }).start(); - } - -} diff --git a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NIOServer.java b/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NIOServer.java deleted file mode 100644 index 7fc7de3..0000000 --- a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NIOServer.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.cipher.netty.A_what_is_netty.server; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.charset.Charset; -import java.util.Iterator; -import java.util.Set; - -/** - * @Author: CipherCui - * @Description: - * @Date: Created in 16:03 2018/9/28 - */ -public class NIOServer { - - public static void main(String[] args) throws IOException { - Selector serverSelector = Selector.open(); - Selector clientSelector = Selector.open(); - - new Thread(() -> { - try { - // 对应IO编程中服务端启动 - ServerSocketChannel listenerChannel = ServerSocketChannel.open(); - listenerChannel.socket().bind(new InetSocketAddress(8000)); - listenerChannel.configureBlocking(false); - listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT); - - while (true) { - // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms - if (serverSelector.select(1) > 0) { - Set set = serverSelector.selectedKeys(); - Iterator keyIterator = set.iterator(); - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isAcceptable()) { - try { - // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector - SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); - clientChannel.configureBlocking(false); - clientChannel.register(clientSelector, SelectionKey.OP_READ); - } finally { - keyIterator.remove(); - } - } - } - } - } - } catch (IOException ignored) { - } - }).start(); - - new Thread(() -> { - try { - while (true) { - // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms - if (clientSelector.select(1) > 0) { - Set set = clientSelector.selectedKeys(); - Iterator keyIterator = set.iterator(); - - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isReadable()) { - try { - SocketChannel clientChannel = (SocketChannel) key.channel(); - ByteBuffer byteBuffer = ByteBuffer.allocate(1024); - // (3) 读取数据以块为单位批量读取 - clientChannel.read(byteBuffer); - byteBuffer.flip(); - System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer).toString()); - } finally { - keyIterator.remove(); - key.interestOps(SelectionKey.OP_READ); - } - } - } - } - } - } catch (IOException ignored) { - } - }).start(); - } - -} diff --git a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NettyServer.java b/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NettyServer.java deleted file mode 100644 index f7069e6..0000000 --- a/sandbox/src/main/java/com/cipher/netty/A_what_is_netty/server/NettyServer.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.cipher.netty.A_what_is_netty.server; - -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.string.StringDecoder; - -/** - * @Author: CipherCui - * @Description: - * @Date: Created in 16:11 2018/9/28 - */ -public class NettyServer { - - public static void main(String[] args) { - ServerBootstrap serverBootstrap = new ServerBootstrap(); - NioEventLoopGroup boss = new NioEventLoopGroup(); - NioEventLoopGroup worker = new NioEventLoopGroup(); - serverBootstrap - .group(boss, worker) - .channel(NioServerSocketChannel.class) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(NioSocketChannel ch) { - ch.pipeline().addLast(new StringDecoder()); - ch.pipeline().addLast(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) { - System.out.println(msg); - } - }); - } - }) - .bind(8000); - } - -} diff --git a/sandbox/src/main/java/com/cipher/netty/client/ClientHandler.java b/sandbox/src/main/java/com/cipher/netty/client/ClientHandler.java new file mode 100644 index 0000000..df81138 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/client/ClientHandler.java @@ -0,0 +1,53 @@ +package com.cipher.netty.client; + +import com.cipher.netty.core.PacketCodec; +import com.cipher.netty.protocol.Packet; +import com.cipher.netty.protocol.request.LoginRequestPacket; +import com.cipher.netty.protocol.response.LoginResponsePacket; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.util.Date; +import java.util.UUID; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 15:54 2019/3/19 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelActive(ChannelHandlerContext ctx) { + + System.out.println(new Date() + ": 客户端开始登录"); + + // 创建登录对象 + LoginRequestPacket loginRequestPacket = new LoginRequestPacket(); + loginRequestPacket.setUserId(UUID.randomUUID().toString()); + loginRequestPacket.setUsername("cipher"); + loginRequestPacket.setPassword("123"); + + // 编码 + ByteBuf buffer = PacketCodec.INSTANCE.encode(ctx.alloc(), loginRequestPacket); + + // 写数据 + ctx.channel().writeAndFlush(buffer); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ByteBuf byteBuf = (ByteBuf) msg; + Packet packet = PacketCodec.INSTANCE.decode(byteBuf); + if (packet instanceof LoginResponsePacket) { + LoginResponsePacket loginResponsePacket = (LoginResponsePacket) packet; + if (loginResponsePacket.isSuccess()) { + System.out.println(new Date() + ": 客户端登录成功"); + } else { + System.out.println(new Date() + ": 客户端登录失败,原因:" + loginResponsePacket.getReason()); + } + } + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/client/NettyClient.java b/sandbox/src/main/java/com/cipher/netty/client/NettyClient.java new file mode 100644 index 0000000..fa68462 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/client/NettyClient.java @@ -0,0 +1,56 @@ +package com.cipher.netty.client; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 13:53 2019/3/19 + */ +public class NettyClient { + + public static final int MAX_RETRY = 5; + + public static void main(String[] args) { + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + Bootstrap bootstrap = new Bootstrap(); + bootstrap + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new ClientHandler()); + } + }); + connect(bootstrap, "127.0.0.1", 1000, MAX_RETRY); + } + + private static void connect(Bootstrap bootstrap, String host, int port, int retry) { + bootstrap.connect(host, port).addListener(future -> { + if (future.isSuccess()) { + System.out.println("连接成功!"); + } else if (retry == 0) { + System.err.println("重试次数已用完,放弃连接!"); + } else { + int order = (MAX_RETRY - retry) + 1; + int delay = 1 << order; + System.err.println(new Date() + ": 连接失败,第" + order + "次重连,等待" + delay + "秒后重连..."); + bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit + .SECONDS); + } + }); + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/constant/Command.java b/sandbox/src/main/java/com/cipher/netty/constant/Command.java new file mode 100644 index 0000000..ef19f84 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/constant/Command.java @@ -0,0 +1,14 @@ +package com.cipher.netty.constant; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:35 2019/3/19 + */ +public class Command { + + public static final Byte LOGIN_REQUEST = 1; + + public static final Byte LOGIN_RESPONSE = 2; + +} diff --git a/sandbox/src/main/java/com/cipher/netty/constant/SerializerAlgorithm.java b/sandbox/src/main/java/com/cipher/netty/constant/SerializerAlgorithm.java new file mode 100644 index 0000000..6e2954d --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/constant/SerializerAlgorithm.java @@ -0,0 +1,15 @@ +package com.cipher.netty.constant; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:35 2019/3/19 + */ +public class SerializerAlgorithm { + + /** + * json 序列化标识 + */ + public static final byte JSON = 1; + +} diff --git a/sandbox/src/main/java/com/cipher/netty/core/PacketCodec.java b/sandbox/src/main/java/com/cipher/netty/core/PacketCodec.java new file mode 100644 index 0000000..3d40a89 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/core/PacketCodec.java @@ -0,0 +1,73 @@ +package com.cipher.netty.core; + +import com.cipher.netty.constant.Command; +import com.cipher.netty.protocol.request.LoginRequestPacket; +import com.cipher.netty.protocol.Packet; +import com.cipher.netty.protocol.response.LoginResponsePacket; +import com.cipher.netty.serialize.Serializer; +import com.cipher.netty.serialize.impl.JSONSerializer; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 15:45 2019/3/19 + */ +public class PacketCodec { + + public static final PacketCodec INSTANCE = new PacketCodec(); + private static final int MAGIC_NUMBER = 0x12345678; + private static final Map> PACKET_MAP; + private static final Map SERIALIZER_MAP; + + static { + PACKET_MAP = new HashMap<>(); + PACKET_MAP.put(Command.LOGIN_REQUEST, LoginRequestPacket.class); + PACKET_MAP.put(Command.LOGIN_RESPONSE, LoginResponsePacket.class); + + SERIALIZER_MAP = new HashMap<>(); + Serializer serializer = new JSONSerializer(); + SERIALIZER_MAP.put(serializer.getSerializerAlgorithm(), serializer); + } + + public ByteBuf encode(ByteBufAllocator byteBufAllocator, Packet packet) { + // 1. 创建 ByteBuf 对象 + ByteBuf byteBuf = byteBufAllocator.ioBuffer(); + // 2. 序列化 Java 对象 + byte[] bytes = Serializer.DEFAULT.serialize(packet); + // 3. 实际编码过程 + byteBuf.writeInt(MAGIC_NUMBER); + byteBuf.writeByte(packet.getVersion()); + byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlgorithm()); + byteBuf.writeByte(packet.getCommand()); + byteBuf.writeInt(bytes.length); + byteBuf.writeBytes(bytes); + return byteBuf; + } + + public Packet decode(ByteBuf byteBuf) { + // 跳过 magic number + byteBuf.skipBytes(4); + // 跳过版本号 + byteBuf.skipBytes(1); + // 序列化算法标识 + byte serializeAlgorithm = byteBuf.readByte(); + // 指令 + byte command = byteBuf.readByte(); + // 数据包长度 + int length = byteBuf.readInt(); + byte[] bytes = new byte[length]; + byteBuf.readBytes(bytes); + Class requestType = PACKET_MAP.get(command); + Serializer serializer = SERIALIZER_MAP.get(serializeAlgorithm); + if (requestType != null && serializer != null) { + return serializer.deserialize(requestType, bytes); + } + return null; + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/protocol/Packet.java b/sandbox/src/main/java/com/cipher/netty/protocol/Packet.java new file mode 100644 index 0000000..968b52d --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/protocol/Packet.java @@ -0,0 +1,27 @@ +package com.cipher.netty.protocol; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:26 2019/3/19 + */ +public abstract class Packet { + + /** + * 协议版本 + */ + private Byte version = 1; + + /** + * 指令 + */ + public abstract Byte getCommand(); + + public Byte getVersion() { + return version; + } + + public void setVersion(Byte version) { + this.version = version; + } +} diff --git a/sandbox/src/main/java/com/cipher/netty/protocol/request/LoginRequestPacket.java b/sandbox/src/main/java/com/cipher/netty/protocol/request/LoginRequestPacket.java new file mode 100644 index 0000000..eebba4b --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/protocol/request/LoginRequestPacket.java @@ -0,0 +1,47 @@ +package com.cipher.netty.protocol.request; + +import com.cipher.netty.constant.Command; +import com.cipher.netty.protocol.Packet; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:27 2019/3/19 + */ +public class LoginRequestPacket extends Packet { + + private String userId; + + private String username; + + private String password; + + @Override + public Byte getCommand() { + return Command.LOGIN_REQUEST; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/sandbox/src/main/java/com/cipher/netty/protocol/response/LoginResponsePacket.java b/sandbox/src/main/java/com/cipher/netty/protocol/response/LoginResponsePacket.java new file mode 100644 index 0000000..b28ae87 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/protocol/response/LoginResponsePacket.java @@ -0,0 +1,38 @@ +package com.cipher.netty.protocol.response; + +import com.cipher.netty.protocol.Packet; + +import static com.cipher.netty.constant.Command.LOGIN_RESPONSE; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 16:00 2019/3/19 + */ +public class LoginResponsePacket extends Packet { + private boolean success; + + private String reason; + + @Override + public Byte getCommand() { + return LOGIN_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/readme.md b/sandbox/src/main/java/com/cipher/netty/readme.md deleted file mode 100644 index 625f87f..0000000 --- a/sandbox/src/main/java/com/cipher/netty/readme.md +++ /dev/null @@ -1,25 +0,0 @@ -## Netty 入门与实战:仿写微信 IM 即时通讯系统 - -### A. Netty 与传统网络编程的区别 - - 客户端每隔两秒发送一个带有时间戳的 "hello world" 给服务端,服务端收到之后打印。 - -- 传统IO编程 - -- JDK NIO 实现 - -- Netty 实现 - -### B. 服务端和客户端启动流程 - -### C. 客户端和服务端双向通信 - - 客户端连接成功之后,向服务端写一段数据 ,服务端收到数据之后打印,并向客户端回一段数据 - -### D. 客户端登录 - - 实现客户端登录到服务端的过程 - -### E. 客户端与服务端收发消息 - - 在控制台输入一条消息之后按回车,校验完客户端的登录状态之后,把消息发送到服务端,服务端收到消息之后打印并且向客户端发送一条消息,客户端收到之后打印 \ No newline at end of file diff --git a/sandbox/src/main/java/com/cipher/netty/serialize/Serializer.java b/sandbox/src/main/java/com/cipher/netty/serialize/Serializer.java new file mode 100644 index 0000000..15e9e50 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/serialize/Serializer.java @@ -0,0 +1,29 @@ +package com.cipher.netty.serialize; + +import com.cipher.netty.serialize.impl.JSONSerializer; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:29 2019/3/19 + */ +public interface Serializer { + + Serializer DEFAULT = new JSONSerializer(); + + /** + * 序列化算法 + */ + byte getSerializerAlgorithm(); + + /** + * java 对象转换成二进制 + */ + byte[] serialize(Object object); + + /** + * 二进制转换成 java 对象 + */ + T deserialize(Class clazz, byte[] bytes); + +} diff --git a/sandbox/src/main/java/com/cipher/netty/serialize/impl/JSONSerializer.java b/sandbox/src/main/java/com/cipher/netty/serialize/impl/JSONSerializer.java new file mode 100644 index 0000000..3a8947b --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/serialize/impl/JSONSerializer.java @@ -0,0 +1,29 @@ +package com.cipher.netty.serialize.impl; + +import com.alibaba.fastjson.JSON; +import com.cipher.netty.constant.SerializerAlgorithm; +import com.cipher.netty.serialize.Serializer; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:31 2019/3/19 + */ +public class JSONSerializer implements Serializer { + + @Override + public byte getSerializerAlgorithm() { + return SerializerAlgorithm.JSON; + } + + @Override + public byte[] serialize(Object object) { + return JSON.toJSONBytes(object); + } + + @Override + public T deserialize(Class clazz, byte[] bytes) { + return JSON.parseObject(bytes, clazz); + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/server/NettyServer.java b/sandbox/src/main/java/com/cipher/netty/server/NettyServer.java new file mode 100644 index 0000000..3f6de6c --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/server/NettyServer.java @@ -0,0 +1,47 @@ +package com.cipher.netty.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 14:13 2019/3/15 + */ +public class NettyServer { + + public static void main(String[] args) { + NioEventLoopGroup bossGroup = new NioEventLoopGroup(); + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 1024) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) { + ch.pipeline().addLast(new ServerHandler()); + } + }); + bind(bootstrap, 1000); + } + + private static void bind(final ServerBootstrap bootstrap, final int port) { + bootstrap.bind(port).addListener(future -> { + if (future.isSuccess()) { + System.out.println("端口[" + port + "]绑定成功!"); + } else { + System.out.println("端口[" + port + "]绑定失败!"); + bind(bootstrap, port + 1); + } + }); + } + +} diff --git a/sandbox/src/main/java/com/cipher/netty/server/ServerHandler.java b/sandbox/src/main/java/com/cipher/netty/server/ServerHandler.java new file mode 100644 index 0000000..8d58da4 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/netty/server/ServerHandler.java @@ -0,0 +1,46 @@ +package com.cipher.netty.server; + +import com.cipher.netty.core.PacketCodec; +import com.cipher.netty.protocol.Packet; +import com.cipher.netty.protocol.request.LoginRequestPacket; +import com.cipher.netty.protocol.response.LoginResponsePacket; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.util.Date; + +/** + * @Author: CipherCui + * @Description: + * @Date: Created in 16:06 2019/3/19 + */ +public class ServerHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + System.out.println(new Date() + ": 客户端开始登录……"); + ByteBuf requestByteBuf = (ByteBuf) msg; + Packet packet = PacketCodec.INSTANCE.decode(requestByteBuf); + if (packet instanceof LoginRequestPacket) { + LoginRequestPacket loginRequestPacket = (LoginRequestPacket) packet; + LoginResponsePacket loginResponsePacket = new LoginResponsePacket(); + loginResponsePacket.setVersion(packet.getVersion()); + if (valid(loginRequestPacket)) { + loginResponsePacket.setSuccess(true); + System.out.println(new Date() + ": 登录成功!"); + } else { + loginResponsePacket.setReason("账号密码校验失败"); + loginResponsePacket.setSuccess(false); + System.out.println(new Date() + ": 登录失败!"); + } + ByteBuf responseByteBuf = PacketCodec.INSTANCE.encode(ctx.alloc(), loginResponsePacket); + ctx.channel().writeAndFlush(responseByteBuf); + } + } + + private boolean valid(LoginRequestPacket loginRequestPacket) { + return loginRequestPacket.getUsername().equals("cipher"); + } + +} diff --git a/sandbox/src/main/java/com/cipher/pattern/proxy/dynamic/cglib/CarLogProxy.java b/sandbox/src/main/java/com/cipher/pattern/proxy/dynamic/cglib/CarLogProxy.java index 06d9cfe..1252704 100644 --- a/sandbox/src/main/java/com/cipher/pattern/proxy/dynamic/cglib/CarLogProxy.java +++ b/sandbox/src/main/java/com/cipher/pattern/proxy/dynamic/cglib/CarLogProxy.java @@ -31,6 +31,7 @@ public Object getProxy(Class clazz) { * @return * @throws Throwable */ + @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("日志开始......"); // 代理类调用父类的方法 diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/readme.md b/sandbox/src/main/java/com/cipher/redis_algorithm/readme.md new file mode 100644 index 0000000..35d4216 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/readme.md @@ -0,0 +1 @@ +## redis算法java实现 \ No newline at end of file diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Constant.java b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Constant.java new file mode 100644 index 0000000..0fedcd5 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Constant.java @@ -0,0 +1,20 @@ +package com.cipher.redis_algorithm.skiplist; + +/** + * 跳跃表用到的各种常量 + * + * @author cipher + */ +public class Constant { + + /** + * 节点层高最大值 + */ + public static final int ZSKIPLIST_MAXLEVEL = 64; + + /** + * 控制节点层高的概率,当 p=0.25 时,跳跃表的期望层高是 1/(1-0.25)≈1.33 + */ + public static final float ZSKIPLIST_P = 0.25f; + +} diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Test.java b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Test.java new file mode 100644 index 0000000..95d2118 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Test.java @@ -0,0 +1,14 @@ +package com.cipher.redis_algorithm.skiplist; + +public class Test { + + public static void main(String[] args) { + Zskiplist zsl = Zskiplist.zslCreate(); + ZskiplistOp.zslInsert(zsl, 31, "C"); + ZskiplistOp.zslInsert(zsl, 41, "D"); + ZskiplistOp.zslInsert(zsl, 21, "B"); + ZskiplistOp.zslInsert(zsl, 1, "A"); + System.out.println(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Zskiplist.java b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Zskiplist.java new file mode 100644 index 0000000..376dc0a --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/Zskiplist.java @@ -0,0 +1,95 @@ +package com.cipher.redis_algorithm.skiplist; + +import static com.cipher.redis_algorithm.skiplist.Constant.ZSKIPLIST_MAXLEVEL; + +/** + * 跳跃表结构,用于管理节点 + * + * @author cipher + */ +public class Zskiplist { + + /** + * 指向跳跃表头节点, + * 头节点是一个特殊节点,它的 level 数组元素个数固定为 64。 + * 头节点的 ele 为空,score 为 0 + * 头节点不计入跳跃表的总长度 + */ + private ZskiplistNode header; + + /** + * 指向跳跃表尾节点,用于从后向前遍历 + */ + private ZskiplistNode tail; + + /** + * 跳跃表的长度,除头结点外,所有节点的个数 + */ + private int length; + + /** + * 跳跃表的高度,即跳跃表所有节点中层高最高的那个节点的层高 + */ + private int level; + + /** + * 创建跳跃表 + * + * @return 跳跃表结构 + */ + public static Zskiplist zslCreate() { + Zskiplist zsl = new Zskiplist(); + + // 跳跃表层高初始化为 1,长度初始化为 0 + zsl.level = 1; + zsl.length = 0; + + // 创建头节点,头节点不存储有序集合的 member 信息 + zsl.header = ZskiplistNode.zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, null); + + // 头节点的 level 数组的每项 forward 都为 null,span 值都为 0 + for (int j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { + zsl.header.getLevel()[j].setForward(null); + zsl.header.getLevel()[j].setSpan(0); + } + + // 头节点的后退指针为 null + zsl.header.setBackward(null); + + // 尾节点指向 null + zsl.tail = null; + return zsl; + } + + public ZskiplistNode getHeader() { + return header; + } + + public void setHeader(ZskiplistNode header) { + this.header = header; + } + + public ZskiplistNode getTail() { + return tail; + } + + public void setTail(ZskiplistNode tail) { + this.tail = tail; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } +} diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistNode.java b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistNode.java new file mode 100644 index 0000000..25be363 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistNode.java @@ -0,0 +1,121 @@ +package com.cipher.redis_algorithm.skiplist; + +/** + * 跳跃表节点 + * 跳跃表由多个节点构成,每个节点由多个层 {@link ZskiplistLevel} 构成 + * + * @author cipher + */ +public class ZskiplistNode { + + /** + * 用于存储字符串类型的数据, + * 即有序集合的 member 信息 + */ + private String ele; + + /** + * 用于存储排序的分值, + * 所有节点的分值是按从小到大排序的, + * 分值相同时,按 {@link ZskiplistNode#ele} 的字典序进行排序 + */ + private double score; + + /** + * 后退指针,只能指向当前节点最底层的前一个节点, + * 头节点和第一个节点的 backward 指向 null, + * 从后向前遍历跳跃表时使用 + */ + private ZskiplistNode backward; + + /** + * 用于存储当前节点的层,数组长度为 1~64 的随机值, + * 值越大概率越低 + */ + private ZskiplistLevel[] level; + + /** + * 创建跳跃表节点时,待创建节点的层高,分值,member 等都已确定 + * + * @param level 节点层高 + * @param score 分值 + * @param ele 元素值 + * @return 跳跃表节点 + */ + public static ZskiplistNode zslCreateNode(int level, double score, String ele) { + ZskiplistNode node = new ZskiplistNode(); + node.ele = ele; + node.score = score; + node.level = new ZskiplistLevel[level]; + for (int i = 0; i < node.level.length; i++) { + node.level[i] = new ZskiplistLevel(); + } + return node; + } + + public String getEle() { + return ele; + } + + public void setEle(String ele) { + this.ele = ele; + } + + public double getScore() { + return score; + } + + public void setScore(double score) { + this.score = score; + } + + public ZskiplistLevel[] getLevel() { + return level; + } + + public void setLevel(ZskiplistLevel[] level) { + this.level = level; + } + + public ZskiplistNode getBackward() { + return backward; + } + + public void setBackward(ZskiplistNode backward) { + this.backward = backward; + } + + /** + * 节点中的层 + */ + public static class ZskiplistLevel { + + /** + * 指向本层下一个节点,尾节点指向 null + */ + private ZskiplistNode forward; + + /** + * forward 指向节点与本节点之间的元素个数。 + * span 值越大,跳过的节点个数越多 + */ + private int span; + + public ZskiplistNode getForward() { + return forward; + } + + public void setForward(ZskiplistNode forward) { + this.forward = forward; + } + + public int getSpan() { + return span; + } + + public void setSpan(int span) { + this.span = span; + } + } + +} diff --git a/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistOp.java b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistOp.java new file mode 100644 index 0000000..6c908cc --- /dev/null +++ b/sandbox/src/main/java/com/cipher/redis_algorithm/skiplist/ZskiplistOp.java @@ -0,0 +1,181 @@ +package com.cipher.redis_algorithm.skiplist; + +import java.math.BigDecimal; +import java.util.Random; + +import static com.cipher.redis_algorithm.skiplist.Constant.ZSKIPLIST_MAXLEVEL; +import static com.cipher.redis_algorithm.skiplist.Constant.ZSKIPLIST_P; +import static com.cipher.redis_algorithm.skiplist.ZskiplistNode.zslCreateNode; + +/** + * 跳跃表操作 + * + * @author cipher + */ +public class ZskiplistOp { + + /** + * 插入节点,共需要 4 个 步骤: + * ① 收集需要更新的节点与步长信息; + * ② 获取随机层高,补全需要更新的节点 + * ③ 创建并分层插入节点,同时更新同层前一节点步长信息; + * ④ 更新新增节点未涉及层节点的步长信息,以及调整 backward + * + * @param zsl 跳跃表结构 + * @param score 待插入节点的分值 + * @param ele 待插入节点的元素 + * @return 跳跃表的头节点 + */ + public static ZskiplistNode zslInsert(Zskiplist zsl, double score, String ele) { + + // 插入节点时,需要更新被插入节点每层的前一个节点。由于每层更新的节点不一样, + // 所以将插入新增节点后每层受影响节点存在 update 数组中,update[i]为第 i + 1 层会受影响节点 + ZskiplistNode[] update = new ZskiplistNode[ZSKIPLIST_MAXLEVEL]; + + // 将每层头节点与会受影响的节点中间存在节点数存在 rank 数组中,rank[i] 为头节点与第 i + 1 层会受影响节点中间存在的节点数, + // 在更新 update[i] 的 span 和设置新插入节点的 span 时用到 + Integer[] rank = new Integer[ZSKIPLIST_MAXLEVEL]; + + // 第一步,收集需要更新的节点与步长信息 + ZskiplistNode x = zsl.getHeader(); + // 从最高层开始向下一层一层地遍历 + for (int i = zsl.getLevel() - 1; i >= 0; i--) { + + // 第一次 for 循环的遍历,即 i==zsl.getLevel()-1 时步长从 0 开始计算, + // 后序遍历的步长需要在上一层的基础上开始计算,因此 rank[i] 初始为 rank[i+1] + rank[i] = i == (zsl.getLevel() - 1) ? 0 : rank[i + 1]; + + // 这里 white 循环是同一层中的节点遍历,为了找到本层中新插入节点的前一个节点, + // 需要满足以下条件: + // 1、本层指向的下一个节点不为空; + // 2、本层指向的下一个节点的分值小于新插入节点的分值,分值相同时用元素值来比较 + while (x.getLevel()[i].getForward() != null && + (x.getLevel()[i].getForward().getScore() < score || + (x.getLevel()[i].getForward().getScore() == score && + x.getLevel()[i].getForward().getEle().compareTo(ele) < 0))) { + // 步长累加 + rank[i] += x.getLevel()[i].getSpan(); + // x 移动到下一个节点 + x = x.getLevel()[i].getForward(); + } + + // 记录本层中需要修改的节点,即新插入节点的前一个节点 + update[i] = x; + } + + // 第二步,获取随机层高,补全需要更新的节点 + // 获取随机层高 + int level = zslRandomLevel(); + // 如果新插入节点的层高大于跳跃表的原高度,那么高出的这几层的头节点也是需要更新信息的 + if (level > zsl.getLevel()) { + // 从高出的层数开始向上遍历 + for (int i = zsl.getLevel(); i < level; i++) { + + // 因为是高出的层,所以只有头节点需要更新,步长自然为 0 + rank[i] = 0; + update[i] = zsl.getHeader(); + + // 先将 span 值赋值为跳跃表的总长度,方便后序计算 + // 注意这时的 span 不是最终值,在插入节点后会重新计算赋值 + update[i].getLevel()[i].setSpan(zsl.getLength()); + } + // 因为新插入节点的层高大于跳跃表的原高度,所以更新跳跃表的高度 + zsl.setLevel(level); + } + + // 第三步,创建并分层插入节点,同时更新同层前一节点步长信息 + ZskiplistNode newNode = zslCreateNode(level, score, ele); + // 从第 0 层开始,向上一层一层地遍历 + for (int i = 0; i < level; i++) { + + // 本层中插入节点 + // 相当于链表的插入操作, A -> B 更新为 A -> newNode -> B + newNode.getLevel()[i].setForward(update[i].getLevel()[i].getForward()); + update[i].getLevel()[i].setForward(newNode); + + /** + * 说明: + * 步长的赋值操作有点难懂,稍微解释一下,因为跳跃表的每个节点都至少有一层(这是由 zslRandomLevel() 方法保证的), + * 因此跳跃表的第一层是一个普通链表,一个节点挨着一个节点,没有跨过任何节点。 + * update[0] 表示第一层需要修改的节点,这个节点必定为第一层中紧挨着新插入节点的前一个节点, + * rank[0] 就是紧挨着新插入节点的前一个节点到头节点的距离。 + */ + + // 设置新增节点步长信息 + // 因为 rank[0] 表示紧挨着新插入节点的前一个节点到头节点的距离(见上面的说明), + // 所以 (rank[0] - rank[i]) 表示 update[i] 节点到紧挨着新插入节点的前一个节点的距离, + // update[i] 的 span 值表示原跳跃表中 update[i] 到下一节点的距离, + // 这是 update[i] 和 update[i] 的下一节点之间要插入新增的节点,update[i] -> next 更新为 update[i] -> newNode -> next + // 所以新增节点到下一节点的距离为 update[i].span - (rank[0] - rank[i]) + newNode.getLevel()[i].setSpan(update[i].getLevel()[i].getSpan() - ((rank[0] - rank[i]))); + + // 更新本层中新插入节点的前一个节点,即 update[i] 的步长信息, + // 因为 rank[0] 表示紧挨着新插入节点的前一个节点到头节点的距离(见上面的说明), + // 所以 (rank[0] - rank[i]) 表示 update[i] 节点到紧挨着新插入节点的前一个节点的距离, + // 因此这个距离加 1 就是 update[i] 节点到新插入节点的距离 + /** + * rank[i] (rank[0]-rank[i]) + * / \ / \ + * Li: head -----------> A ------------ + * . + * . rank[0] + * . / \ 1 + * . / \ / \ + * L0: head -----------> A ------------> B -----> newNode + */ + update[i].getLevel()[i].setSpan((rank[0] - rank[i]) + 1); + } + + // 更新新增节点未涉及层节点的步长信息,以及调整 backward + // 能进入此循环,表示新插入节点的层高小于跳跃表原高度, + // 那么高于新插入节点层高的那些需要更新的节点,步长需要加 1 (因为在它和它的下一个节点之间插入了新增节点) + for (int i = level; i < zsl.getLevel(); i++) { + int span = update[i].getLevel()[i].getSpan(); + update[i].getLevel()[i].setSpan(span + 1); + } + + // 调整 backward + // update[0] 表示紧挨着新插入节点的前一个节点,每个节点的后退指针只有一个,与层数无关 + // 所以新插入节点的后退指针指向 update[0],如果 update[0] 是头节点,则指向 null + newNode.setBackward((update[0] == zsl.getHeader()) ? null : update[0]); + // 如果新插入节点的下一个节点不是空,表示新插入节点不是最后一个节点, + // 那么需要更新新插入节点的下一个节点的后退指针,指向新插入节点 + if (newNode.getLevel()[0].getForward() != null) { + newNode.getLevel()[0].getForward().setBackward(newNode); + } + // 如果新插入节点是最后一个节点,则需要更新跳跃表的尾节点为新插入节点 + else { + zsl.setTail(newNode); + } + // 插入节点后,跳跃表的长度加 1 + zsl.setLength(zsl.getLength() + 1); + return newNode; + } + + /** + * Redis 通过此函数随机生成一个 1~64 的值,作为新建节点的高度, + * 值越大出现的概率越低,节点层高确定之后便不会再修改 + * + * @return 层高 + */ + public static int zslRandomLevel() { + int level = 1; + Random random = new Random(); + while (round(random.nextFloat()) < ZSKIPLIST_P) { + level += 1; + } + return Math.min(level, ZSKIPLIST_MAXLEVEL); + } + + /** + * 保留小数点两位 + * 注意:redis 中是使用位运算来比较大小,这里为了便于理解,改成直接比较小数 + * + * @param num 原值 + * @return 保留小数点两位后的值 + */ + private static float round(float num) { + return new BigDecimal(num).setScale(2, BigDecimal.ROUND_HALF_UP).floatValue(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/test/NonReentrantLock.java b/sandbox/src/main/java/com/cipher/test/NonReentrantLock.java new file mode 100644 index 0000000..1843b4a --- /dev/null +++ b/sandbox/src/main/java/com/cipher/test/NonReentrantLock.java @@ -0,0 +1,74 @@ +package com.cipher.test; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +public class NonReentrantLock implements Lock { + + private static class Sync extends AbstractQueuedSynchronizer { + @Override + protected boolean isHeldExclusively() { + return getState() == 1; + } + + @Override + protected boolean tryAcquire(int acquires) { + assert acquires == 1; + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + + @Override + protected boolean tryRelease(int releases) { + assert releases == 1; + if (getState() == 0) { + throw new IllegalMonitorStateException(); + } + setExclusiveOwnerThread(null); + setState(0); + return true; + } + + Condition newCondition() { + return new ConditionObject(); + } + } + + private final Sync sync = new Sync(); + + @Override + public void lock() { + sync.acquire(1); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(1); + } + + @Override + public boolean tryLock() { + return sync.tryAcquire(1); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + return sync.tryAcquireNanos(1, unit.toNanos(time)); + } + + @Override + public void unlock() { + sync.release(1); + } + + @Override + public Condition newCondition() { + return sync.newCondition(); + } + +} diff --git a/sandbox/src/main/java/com/cipher/test/Solution.java b/sandbox/src/main/java/com/cipher/test/Solution.java new file mode 100644 index 0000000..0a40c37 --- /dev/null +++ b/sandbox/src/main/java/com/cipher/test/Solution.java @@ -0,0 +1,76 @@ +package com.cipher.test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class Solution { + Map areaMap = new HashMap<>(); + int[][] dirs = new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + private boolean inArea(int[][] grid, int i, int j) { + return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length; + } + + public int largestIsland(int[][] grid) { + int res = 0; + int index = 2; + boolean hasIsland = false; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1) { + areaMap.put(index, curArea(grid, i, j, index)); + index++; + hasIsland = true; + } + } + } + if (!hasIsland) { + return 1; + } + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 0) { + res = Math.max(res, newArea(grid, i, j)); + } + } + } + return res; + } + + private int curArea(int[][] grid, int i, int j, int index) { + if (!inArea(grid, i, j)) { + return 0; + } + if (grid[i][j] != 1) { + return 0; + } + grid[i][j] = index; + int res = 1; + for (int[] dir : dirs) { + res += curArea(grid, i + dir[0], j + dir[1], index); + } + return res; + } + + private int newArea(int[][] grid, int i, int j) { + Set set = new HashSet<>(); + int res = 1; + for (int[] dir : dirs) { + int ti = i + dir[0]; + int tj = j + dir[1]; + if (inArea(grid, ti, tj) && grid[ti][tj] != 0 && !set.contains(grid[ti][tj])) { + res += areaMap.get(grid[ti][tj]); + set.add(grid[ti][tj]); + } + } + return res; + } + + public static void main(String[] args) { + Solution solution = new Solution(); + int i = solution.largestIsland(new int[][]{{1, 1}, {1, 1}}); + System.out.println(); + } +} \ No newline at end of file diff --git a/sandbox/src/main/java/com/cipher/test/Test.java b/sandbox/src/main/java/com/cipher/test/Test.java index 0683328..2f24dcd 100644 --- a/sandbox/src/main/java/com/cipher/test/Test.java +++ b/sandbox/src/main/java/com/cipher/test/Test.java @@ -1,28 +1,8 @@ -package com.cipher.test; - -import java.io.*; - -public class Test { - - public static void main(String[] args) throws Exception { - String path = "C:\\Users\\cipher\\Desktop\\语言包\\配置文件"; - File dir = new File(path); - File[] list = dir.listFiles(); - StringBuilder sb = new StringBuilder(); - String s; - for (File file : list) { - sb.append(">>>>>>>>>>>>>>>>>>>>> ").append(file.getName()).append("\n"); - FileReader reader = new FileReader(file); - BufferedReader bReader = new BufferedReader(reader); - while ((s = bReader.readLine()) != null) { - sb.append(s).append("\n"); - } - bReader.close(); - } - - File file = new File("F:\\sum.xml"); - PrintStream ps = new PrintStream(new FileOutputStream(file)); - ps.println(sb.toString()); - } - -} +package com.cipher.test; + +public class Test { + + public static void main(String[] args) throws Exception { + } + +}