Java 编程技巧之数据结构

导读

唐宋八大家之一欧阳修在《卖油翁》中写道:

翁取一葫芦置于地,以钱覆其口,徐以杓酌油沥之,自钱孔入,而钱不湿。因曰:“我亦无他,唯手熟尔。”

编写代码的"老司机"也是如此,"老司机"之所以被称为"老司机",原因也是"无他,唯手熟尔"。编码过程中踩过的坑多了,获得的编码经验也就多了,总结的编码技巧也就更多了。总结的编码技巧多了,凡事又能够举一反三,编码的速度自然就上来了。笔者从数据结构的角度,整理了一些Java编程技巧,以供大家学习参考。

1.使用HashSet判断主键是否存在

HashSet实现Set接口,由哈希表(实际上是HashMap)支持,但不保证set 的迭代顺序,并允许使用null元素。HashSet的时间复杂度跟HashMap一致,如果没有哈希冲突则时间复杂度为O(1),如果存在哈希冲突则时间复杂度不超过O(n)。所以,在日常编码中,可以使用HashSet判断主键是否存在。

案例:给定一个字符串(不一定全为字母),请返回第一个重复出现的字符。

/** 查找第一个重复字符 */
public static Character findFirstRepeatedChar(String string) {// 检查空字符串if (Objects.isNull(string) || string.isEmpty()) {return null;}// 查找重复字符char[] charArray = string.toCharArray();Set charSet = new HashSet<>(charArray.length);for (char ch : charArray) {if (charSet.contains(ch)) {return ch;}charSet.add(ch);}// 默认返回为空return null;
}

其中,由于Set的add函数有个特性——如果添加的元素已经再集合中存在,则会返回false。可以简化代码为:

if (!charSet.add(ch)) {return ch;
}

2.使用HashMap存取键值映射关系

简单来说,HashMap由数组和链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。如果定位到的数组位置不含链表,那么查找、添加等操作很快,仅需一次寻址即可,其时间复杂度为O(1);如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n)——首先遍历链表,存在即覆盖,不存在则新增;对于查找操作来讲,仍需要遍历链表,然后通过key对象的equals方法逐一对比查找。从性能上考虑,HashMap中的链表出现越少,即哈希冲突越少,性能也就越好。所以,在日常编码中,可以使用HashMap存取键值映射关系。

案例:给定菜单记录列表,每条菜单记录中包含父菜单标识(根菜单的父菜单标识为null),构建出整个菜单树。

/** 菜单DO类 */
@Setter
@Getter
@ToString
public static class MenuDO {/** 菜单标识 */private Long id;/** 菜单父标识 */private Long parentId;/** 菜单名称 */private String name;/** 菜单链接 */private String url;
}/** 菜单VO类 */
@Setter
@Getter
@ToString
public static class MenuVO {/** 菜单标识 */private Long id;/** 菜单名称 */private String name;/** 菜单链接 */private String url;/** 子菜单列表 */private List<MenuVO> childList;
}/** 构建菜单树函数 */
public static List<MenuVO> buildMenuTree(List<MenuDO> menuList) {// 检查列表为空if (CollectionUtils.isEmpty(menuList)) {return Collections.emptyList();}// 依次处理菜单int menuSize = menuList.size();List<MenuVO> rootList = new ArrayList<>(menuSize);Map<Long, MenuVO> menuMap = new HashMap<>(menuSize);for (MenuDO menuDO : menuList) {// 赋值菜单对象Long menuId = menuDO.getId();MenuVO menu = menuMap.get(menuId);if (Objects.isNull(menu)) {menu = new MenuVO();menu.setChildList(new ArrayList<>());menuMap.put(menuId, menu);}menu.setId(menuDO.getId());menu.setName(menuDO.getName());menu.setUrl(menuDO.getUrl());// 根据父标识处理Long parentId = menuDO.getParentId();if (Objects.nonNull(parentId)) {// 构建父菜单对象MenuVO parentMenu = menuMap.get(parentId);if (Objects.isNull(parentMenu)) {parentMenu = new MenuVO();parentMenu.setId(parentId);parentMenu.setChildList(new ArrayList<>());menuMap.put(parentId, parentMenu);}// 添加子菜单对象parentMenu.getChildList().add(menu);} else {// 添加根菜单对象rootList.add(menu);}}// 返回根菜单列表return rootList;
}

3.使用ThreadLocal存储线程专有对象

ThreadLocal提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。

常见的ThreadLocal用法主要有两种:

  1. 保存线程上下文对象,避免多层级参数传递;
  2. 保存非线程安全对象,避免多线程并发调用。

3.1.保存线程上下文对象,避免多层级参数传递

这里,以PageHelper插件的源代码中的分页参数设置与使用为例说明。

设置分页参数代码:

/** 分页方法类 */
public abstract class PageMethod {/** 本地分页 */protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();/** 设置分页参数 */protected static void setLocalPage(Page page) {LOCAL_PAGE.set(page);}/** 获取分页参数 */public static <T> Page<T> getLocalPage() {return LOCAL_PAGE.get();}/** 开始分页 */public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {Page<E> page = new Page<E>(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);Page<E> oldPage = getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}setLocalPage(page);return page;}
}

使用分页参数代码:

/** 虚辅助方言类 */
public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {/** 获取本地分页 */public <T> Page<T> getLocalPage() {return PageHelper.getLocalPage();}/** 获取分页SQL */@Overridepublic String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {String sql = boundSql.getSql();Page page = getLocalPage();String orderBy = page.getOrderBy();if (StringUtil.isNotEmpty(orderBy)) {pageKey.update(orderBy);sql = OrderByParser.converToOrderBySql(sql, orderBy);}if (page.isOrderByOnly()) {return sql;}return getPageSql(sql, page, pageKey);}...
}

使用分页插件代码:

/** 查询用户函数 */
public PageInfo<UserDO> queryUser(UserQuery userQuery, int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);List<UserDO> userList = userDAO.queryUser(userQuery);PageInfo<UserDO> pageInfo = new PageInfo<>(userList);return pageInfo;
}

如果要把分页参数通过函数参数逐级传给查询语句,除非修改MyBatis相关接口函数,否则是不可能实现的。

3.2.保存非线程安全对象,避免多线程并发调用

在写日期格式化工具函数时,首先想到的写法如下:

/** 日期模式 */
private static final String DATE_PATTERN = "yyyy-MM-dd";/** 格式化日期函数 */
public static String formatDate(Date date) {return new SimpleDateFormat(DATE_PATTERN).format(date);
}

其中,每次调用都要初始化DateFormat导致性能较低,把DateFormat定义成常量后的写法如下:

/** 日期格式 */
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");/** 格式化日期函数 */
public static String formatDate(Date date) {return DATE_FORMAT.format(date);
}

由于SimpleDateFormat是非线程安全的,当多线程同时调用formatDate函数时,会导致返回结果与预期不一致。如果采用ThreadLocal定义线程专有对象,优化后的代码如下:

/** 本地日期格式 */
private static final ThreadLocal<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() {@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}
};/** 格式化日期函数 */
public static String formatDate(Date date) {return LOCAL_DATE_FORMAT.get().format(date);
}

这是在没有线程安全的日期格式化工具类之前的实现方法。在JDK8以后,建议使用DateTimeFormatter代替SimpleDateFormat,因为SimpleDateFormat是线程不安全的,而DateTimeFormatter是线程安全的。当然,也可以采用第三方提供的线程安全日期格式化函数,比如apache的DateFormatUtils工具类。

注意:ThreadLocal有一定的内存泄露的风险,尽量在业务代码结束前调用remove函数进行数据清除。

4.使用Pair实现成对结果的返回

在C/C++语言中,Pair(对)是将两个数据类型组成一个数据类型的容器,比如std::pair。

Pair主要有两种用途:

  1. 把key和value放在一起成对处理,主要用于Map中返回名值对,比如Map中的Entry类;
  2. 当一个函数需要返回两个结果时,可以使用Pair来避免定义过多的数据模型类。

第一种用途比较常见,这里主要说明第二种用途。

4.1.定义模型类实现成对结果的返回

函数实现代码:

/** 点和距离类 */
@Setter
@Getter
@ToString
@AllArgsConstructor
public static class PointAndDistance {/** 点 */private Point point;/** 距离 */private Double distance;
}/** 获取最近点和距离 */
public static PointAndDistance getNearestPointAndDistance(Point point, Point[] points) {// 检查点数组为空if (ArrayUtils.isEmpty(points)) {return null;}// 获取最近点和距离Point nearestPoint = points[0];double nearestDistance = getDistance(point, points[0]);for (int i = 1; i < points.length; i++) {double distance = getDistance(point, point[i]);if (distance < nearestDistance) {nearestDistance = distance;nearestPoint = point[i];}}// 返回最近点和距离return new PointAndDistance(nearestPoint, nearestDistance);
}

函数使用案例:

Point point = ...;
Point[] points = ...;
PointAndDistance pointAndDistance = getNearestPointAndDistance(point, points);
if (Objects.nonNull(pointAndDistance)) {Point point = pointAndDistance.getPoint();Double distance = pointAndDistance.getDistance();...
}

4.2.使用Pair类实现成对结果的返回

在JDK中,没有提供原生的Pair数据结构,也可以使用Map::Entry代替。不过,Apache的commons-lang3包中的Pair类更为好用,下面便以Pair类进行举例说明。

函数实现代码:

/** 获取最近点和距离 */
public static Pair<Point, Double> getNearestPointAndDistance(Point point, Point[] points) {// 检查点数组为空if (ArrayUtils.isEmpty(points)) {return null;}// 获取最近点和距离Point nearestPoint = points[0];double nearestDistance = getDistance(point, points[0]);for (int i = 1; i < points.length; i++) {double distance = getDistance(point, point[i]);if (distance < nearestDistance) {nearestDistance = distance;nearestPoint = point[i];}}// 返回最近点和距离return Pair.of(nearestPoint, nearestDistance);
}

函数使用案例:

Point point = ...;
Point[] points = ...;
Pair<Point, Double> pair = getNearestPointAndDistance(point, points);
if (Objects.nonNull(pair)) {Point point = pair.getLeft();Double distance = pair.getRight();...
}

5.定义Enum类实现取值和描述

在C++、Java等计算机编程语言中,枚举类型(Enum)是一种特殊数据类型,能够为一个变量定义一组预定义的常量。在使用枚举类型的时候,枚举类型变量取值必须为其预定义的取值之一。

5.1.用class关键字实现的枚举类型

在JDK5之前,Java语言不支持枚举类型,只能用类(class)来模拟实现枚举类型。

/** 订单状态枚举 */
public final class OrderStatus {/** 属性相关 *//** 状态取值 */private final int value;/** 状态描述 */private final String description;/** 常量相关 *//** 已创建(1) */public static final OrderStatus CREATED = new OrderStatus(1, "已创建");/** 进行中(2) */public static final OrderStatus PROCESSING = new OrderStatus(2, "进行中");/** 已完成(3) */public static final OrderStatus FINISHED = new OrderStatus(3, "已完成");/** 构造函数 */private OrderStatus(int value, String description) {this.value = value;this.description = description;}/** 获取状态取值 */public int getValue() {return value;}/** 获取状态描述 */public String getDescription() {return description;}
}

5.2.用enum关键字实现的枚举类型

JDK5提供了一种新的类型——Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常量使用,这是一种非常有用的功能。

/** 订单状态枚举 */
public enum OrderStatus {/** 常量相关 *//** 已创建(1) */CREATED(1, "已创建"),/** 进行中(2) */PROCESSING(2, "进行中"),/** 已完成(3) */FINISHED(3, "已完成");/** 属性相关 *//** 状态取值 */private final int value;/** 状态描述 */private final String description;/** 构造函数 */private OrderStatus(int value, String description) {this.value = value;this.description = description;}/** 获取状态取值 */public int getValue() {return value;}/** 获取状态描述 */public String getDescription() {return description;}
}

其实,Enum类型就是一个语法糖,编译器帮我们做了语法的解析和编译。通过反编译,可以看到Java枚举编译后实际上是生成了一个类,该类继承了 java.lang.Enum,并添加了values()、valueOf()等枚举类型通用方法。

6.定义Holder类实现参数的输出

在很多语言中,函数的参数都有输入(in)、输出(out)和输入输出(inout)之分。在C/C++语言中,可以用对象的引用(&)来实现函数参数的输出(out)和输入输出(inout)。但在Java语言中,虽然没有提供对象引用类似的功能,但是可以通过修改参数的字段值来实现函数参数的输出(out)和输入输出(inout)。这里,我们叫这种输出参数对应的数据结构为Holder(支撑)类

Holder类实现代码:

/** 长整型支撑类 */
@Getter
@Setter
@ToString
public class LongHolder {/** 长整型取值 */private long value;/** 构造函数 */public LongHolder() {}/** 构造函数 */public LongHolder(long value) {this.value = value;}
}

Holder类使用案例:

/** 静态常量 */
/** 页面数量 */
private static final int PAGE_COUNT = 100;
/** 最大数量 */
private static final int MAX_COUNT = 1000;/** 处理过期订单 */
public void handleExpiredOrder() {LongHolder minIdHolder = new LongHolder(0L);for (int pageIndex = 0; pageIndex < PAGE_COUNT; pageIndex++) {if (!handleExpiredOrder(pageIndex, minIdHolder)) {break;}}
}/** 处理过期订单 */
private boolean handleExpiredOrder(int pageIndex, LongHolder minIdHolder) {// 获取最小标识Long minId = minIdHolder.getValue();// 查询过期订单(按id从小到大排序)List<OrderDO> orderList = orderDAO.queryExpired(minId, MAX_COUNT);if (CollectionUtils.isEmpty(taskTagList)) {return false;}// 设置最小标识int orderSize = orderList.size();minId = orderList.get(orderSize - 1).getId();minIdHolder.setValue(minId);// 依次处理订单for (OrderDO order : orderList) {...}// 判断还有订单return orderSize >= PAGE_SIZE;
}

其实,可以实现一个泛型支撑类,适用于更多的数据类型。

7.定义Union类实现数据体的共存

在C/C++语言中,联合体(union),又称共用体,类似结构体(struct)的一种数据结构。联合体(union)和结构体(struct)一样,可以包含很多种数据类型和变量,两者区别如下:

  1. 结构体(struct)中所有变量是“共存”的,同时所有变量都生效,各个变量占据不同的内存空间;
  2. 联合体(union)中是各变量是“互斥”的,同时只有一个变量生效,所有变量占据同一块内存空间。

当多个数据需要共享内存或者多个数据每次只取其一时,可以采用联合体(union)。

在Java语言中,没有联合体(union)和结构体(struct)概念,只有类(class)的概念。众所众知,结构体(struct)可以用类(class)来实现。其实,联合体(union)也可以用类(class)来实现。但是,这个类不具备“多个数据需要共享内存”的功能,只具备“多个数据每次只取其一”的功能。

这里,以微信协议的客户消息为例说明。根据我多年来的接口协议封装经验,主要有以下两种实现方式。

7.1.使用函数方式实现Union

Union类实现:

/** 客户消息类 */
@ToString
public class CustomerMessage {/** 属性相关 *//** 消息类型 */private String msgType;/** 目标用户 */private String toUser;/** 共用体相关 *//** 新闻内容 */private News news;.../** 常量相关 *//** 新闻消息 */public static final String MSG_TYPE_NEWS = "news";.../** 构造函数 */public CustomerMessage() {}/** 构造函数 */public CustomerMessage(String toUser) {this.toUser = toUser;}/** 构造函数 */public CustomerMessage(String toUser, News news) {this.toUser = toUser;this.msgType = MSG_TYPE_NEWS;this.news = news;}/** 清除消息内容 */private void removeMsgContent() {// 检查消息类型if (Objects.isNull(msgType)) {return;}// 清除消息内容if (MSG_TYPE_NEWS.equals(msgType)) {news = null;} else if (...) {...}msgType = null;}/** 检查消息类型 */private void checkMsgType(String msgType) {// 检查消息类型if (Objects.isNull(msgType)) {throw new IllegalArgumentException("消息类型为空");}// 比较消息类型if (!Objects.equals(msgType, this.msgType)) {throw new IllegalArgumentException("消息类型不匹配");}}/** 设置消息类型函数 */public void setMsgType(String msgType) {// 清除消息内容removeMsgContent();// 检查消息类型if (Objects.isNull(msgType)) {throw new IllegalArgumentException("消息类型为空");}// 赋值消息内容this.msgType = msgType;if (MSG_TYPE_NEWS.equals(msgType)) {news = new News();} else if (...) {...} else {throw new IllegalArgumentException("消息类型不支持");}}/** 获取消息类型 */public String getMsgType() {// 检查消息类型if (Objects.isNull(msgType)) {throw new IllegalArgumentException("消息类型无效");}// 返回消息类型return this.msgType;}/** 设置新闻 */public void setNews(News news) {// 清除消息内容removeMsgContent();// 赋值消息内容this.msgType = MSG_TYPE_NEWS;this.news = news;}/** 获取新闻 */public News getNews() {// 检查消息类型checkMsgType(MSG_TYPE_NEWS);// 返回消息内容return this.news;}...
}

Union类使用:

String accessToken = ...;
String toUser = ...;
List<Article> articleList = ...;
News news = new News(articleList);
CustomerMessage customerMessage = new CustomerMessage(toUser, news);
wechatApi.sendCustomerMessage(accessToken, customerMessage);

主要优缺点:

  • 优点:更贴近C/C++语言的联合体(union);
  • 缺点:实现逻辑较为复杂,参数类型验证较多。

7.2.使用继承方式实现Union

Union类实现:

/** 客户消息类 */
@Getter
@Setter
@ToString
public abstract class CustomerMessage {/** 属性相关 *//** 消息类型 */private String msgType;/** 目标用户 */private String toUser;/** 常量相关 *//** 新闻消息 */public static final String MSG_TYPE_NEWS = "news";.../** 构造函数 */public CustomerMessage(String msgType) {this.msgType = msgType;}/** 构造函数 */public CustomerMessage(String msgType, String toUser) {this.msgType = msgType;this.toUser = toUser;}
}/** 新闻客户消息类 */
@Getter
@Setter
@ToString(callSuper = true)
public class NewsCustomerMessage extends CustomerMessage {/** 属性相关 *//** 新闻内容 */private News news;/** 构造函数 */public NewsCustomerMessage() {super(MSG_TYPE_NEWS);}/** 构造函数 */public NewsCustomerMessage(String toUser, News news) {super(MSG_TYPE_NEWS, toUser);this.news = news;}
}

Union类使用:

String accessToken = ...;
String toUser = ...;
List<Article> articleList = ...;
News news = new News(articleList);
CustomerMessage customerMessage = new NewsCustomerMessage(toUser, news);
wechatApi.sendCustomerMessage(accessToken, customerMessage);

主要优缺点:

  • 优点:使用虚基类和子类进行拆分,各个子类对象的概念明确;
  • 缺点:与C/C++语言的联合体(union)差别大,但是功能上大体一致。

在C/C++语言中,联合体并不包括联合体当前的数据类型。但在上面实现的Java联合体中,已经包含了联合体对应的数据类型。所以,从严格意义上说,Java联合体并不是真正的联合体,只是一个具备“多个数据每次只取其一”功能的类。

8.使用泛型屏蔽类型的差异性

在C++语言中,有个很好用的模板(template)功能,可以编写带有参数化类型的通用版本,让编译器自动生成针对不同类型的具体版本。而在Java语言中,也有一个类似的功能叫泛型(generic)。在编写类和方法的时候,一般使用的是具体的类型,而用泛型可以使类型参数化,这样就可以编写更通用的代码。

许多人都认为,C++模板(template)和Java泛型(generic)两个概念是等价的,其实实现机制是完全不同的。C++模板是一套宏指令集,编译器会针对每一种类型创建一份模板代码副本;Java泛型的实现基于"类型擦除"概念,本质上是一种进行类型限制的语法糖。

8.1.泛型类

以支撑类为例,定义泛型的通用支撑类:

/** 通用支撑类 */
@Getter
@Setter
@ToString
public class GenericHolder<T> {/** 通用取值 */private T value;/** 构造函数 */public GenericHolder() {}/** 构造函数 */public GenericHolder(T value) {this.value = value;}
}

8.2.泛型接口

定义泛型的数据提供者接口:

/** 数据提供者接口 */
public interface DataProvider<T> {/** 获取数据函数 */public T getData();
}

8.3.泛型方法

定义泛型的浅拷贝函数:

/** 浅拷贝函数 */
public static <T> T shallowCopy(Object source, Class<T> clazz) throws BeansException {// 判断源对象if (Objects.isNull(source)) {return null;}// 新建目标对象T target;try {target = clazz.newInstance();} catch (Exception e) {throw new BeansException("新建类实例异常", e);}// 拷贝对象属性BeanUtils.copyProperties(source, target);// 返回目标对象return target;
}

8.4.泛型通配符

泛型通配符一般是使用"?"代替具体的类型实参,可以把"?"看成所有类型的父类。当具体类型不确定的时候,可以使用泛型通配符 "?";当不需要使用类型的具体功能,只使用Object类中的功能时,可以使用泛型通配符 "?"。

/** 打印取值函数 */
public static void printValue(GenericHolder<?> holder) {System.out.println(holder.getValue());
}
/** 主函数 */
public static void main(String[] args) {printValue(new GenericHolder<>(12345));printValue(new GenericHolder<>("abcde"));
}

在Java规范中,不建议使用泛型通配符"?",上面函数可以改为:

/** 打印取值函数 */
public static <T> void printValue(GenericHolder<T> holder) {System.out.println(holder.getValue());
}

8.5.泛型上下界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。泛型上下界的声明,必须与泛型的声明放在一起 。

上界通配符(extends):

上界通配符为”extends”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。例如:List<? extends A>表明这是A某个具体子类的List,保存的对象必须是A或A的子类。对于List<? extends A>列表,不能添加A或A的子类对象,只能获取A的对象。

下界通配符(super):

下界通配符为”super”,可以接受其指定类型或其父类作为泛参。例如:List<? super A>表明这是A某个具体父类的List,保存的对象必须是A或A的超类。对于List<? super A>列表,能够添加A或A的子类对象,但只能获取Object的对象。

PECS(Producer Extends Consumer Super)原则:
作为生产者提供数据(往外读取)时,适合用上界通配符(extends);
作为消费者消费数据(往里写入)时,适合用下界通配符(super)。

在日常编码中,比较常用的是上界通配符(extends),用于限定泛型类型的父类。例子代码如下:

/** 数字支撑类 */
@Getter
@Setter
@ToString
public class NumberHolder<T extends Number> {/** 通用取值 */private T value;/** 构造函数 */public NumberHolder() {}/** 构造函数 */public NumberHolder(T value) {this.value = value;}
}/** 打印取值函数 */
public static <T extends Number> void printValue(GenericHolder<T> holder) {System.out.println(holder.getValue());
}

后记

笔者曾在通信行业从业十余年,接入了各类网管和设备的北向接口协议上百余种,涉及到传输、交换、接入、电源、环境等专业,接触了CORBA、HTTP/HTTPS、WebService、Socket TCP/UDP、串口RS232/485等接口,总结出一套接口协议封装的"方法论"。其中,把接口协议文档中的数据格式转化为Java的枚举、结构体、联合体等数据结构,是接口协议封装中极其重要的一步。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/517722.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何查看使用 Cloud Toolkit 部署应用的实时日志

之前&#xff0c;我们介绍了《在 IntelliJ IDEA 中部署应用到服务器》。最新版本 Cloud Toolkit 新增了对远程服务器应用部署时&#xff0c;实时日志的查看功能。功能预览如下&#xff1a; 打开应用部署的配置界面&#xff0c;如上图所示&#xff0c;点击「Advanced」标签页&am…

这 10 个云计算错误,会让你的业务一蹶不振!

作者 | John Edwards译者 | 火火酱&#xff0c;责编 | Carol出品 | CSDN 云计算&#xff08;CSDNcloud&#xff09;封图 | CSDN 付费下载自视觉中国乐观主义者们总爱说&#xff1a;“乌云总是镶着金边的”&#xff0c;但他们没有说的是&#xff0c;在乌云下常有狂风、暴雨、闪电…

MySQL 8.0.26 简易配置安装教程 (windows 64位)

文章目录一、软件下载解压1. 官网下载2.下载完成后解压二、mysql配置2.1. my.ini2.2. 初始化MySQL2.3. 安装MySQL服务2.4. mysql已经安装过了2.5. 启动MySQL的服务三、密码修改3.1. 登录mysql3.2. 修改密码3.3. 退出mysql四、客户端连接mysql4.1. Navicat4.2. 登录mysql4.3. 操…

类与字节码技术

类文件结构 1.1 魔数 0-3字节&#xff0c;表示它是否是【class】类型的文件 ​ |java| 0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 1.2 版本 4-7字节&#xff0c;表示类的版本00 34 &#xff08;52&#xff09; 表示是Java8 0000000 ca fe ba be 00 00 00…

支付宝 App 是如何建设移动 DevOps 的?

本文系InfoQ对蚂蚁金服技术专家洪锋的采访&#xff0c;洪锋老师即将在 QCon 上海站 2019 分享《移动研发 DevOps 在支付宝 App 内的落地实践》&#xff0c;欢迎关注。 微软 MSDN 上的一篇文章有这样一段话&#xff1a;“移动应用的理想环境需要满足两个条件&#xff0c;一是可以…

淘宝端智能演进和思考

今天分享内容包括三部分&#xff0c;第一是端智能整体趋势和淘宝应用现状&#xff0c;第二是淘宝在应用端智能过程中面临的问题和挑战&#xff0c;以及我们的应对和解决思路&#xff0c;我们构建了端到端的完整技术体系&#xff0c;第三是开源推理引擎 MNN 的最新情况。 01、端…

如何取SQL结果集的第一条记录

在SQL Server数据库中&#xff0c;使用top关键字&#xff1a;SELECT TOP number|percent column_name(s) FROM table_name 在MySQL数据库中&#xff0c;使用LIMIT关键字&#xff1a;SELECT column_name(s) FROM table_name LIMIT number例子:SELECT * FROM Persons LIMIT 1 在O…

从零开始入门 K8s | 可观测性:你的应用健康吗?

一、需求来源 首先来看一下&#xff0c;整个需求的来源&#xff1a;当把应用迁移到 Kubernetes 之后&#xff0c;要如何去保障应用的健康与稳定呢&#xff1f;其实很简单&#xff0c;可以从两个方面来进行增强&#xff1a; 首先是提高应用的可观测性&#xff1b;第二是提高应…

技术差的程序员,90%都输在这点上!骨灰级开发:其实都是在瞎努力!

01从未得到过重视的问题“一流程序员学数学&#xff0c;二流程序员学算法&#xff0c;低端看高端就是黑魔法”。可能有人会以为这是段子&#xff0c;而有过开发经验的都知道&#xff0c;这就是程序员的真实写照&#xff01;而数学不好的程序员&#xff0c;我想你开发过程中&…

layui 单选项 点击显示与隐藏 很实用

html <div class"layui-form-item"><label class"layui-form-label">类型</label><div class"layui-input-block"><input type"radio" name"type" lay-filter"type" value"0&quo…

2. 字节码指令

2. 字节码指令 2.1 入门 public cn.itcast.jvm.t5.HelloWorld();构造方法的字节码指令 ​ 2a b7 00 01 b1 2a> aload_0加载slot 0的局部变量&#xff0c;即this&#xff0c;做为下面的invokespecial 构造方法调用的参数b7 >invokespecial预备调用构造方法&#xff0c…

它是真实的“盗梦空间”?在这里,一切都可能是数据

阿里妹导读&#xff1a;位于杭州阿里巴巴西溪园区旁边的大型商场“亲橙里”2018年正式开业。和传统的线下综合型商场不同的是&#xff0c;亲橙里从规划之初就定位为数字化商场&#xff0c;通过植入自研的IBOS平台完成建筑内的所有子系统的接入&#xff0c;而让建筑和建筑内的设…

BDS-HA:构建高可用、低延迟的HBase服务

HBase可以支持百TB数据规模、数百万QPS压力下的毫秒响应&#xff0c;适用于大数据背景下的风控和推荐等在线场景。阿里云HBase服务了多家金融、广告、媒体类业务中的风控和推荐&#xff0c;持续的在高可用、低延迟、低成本方向上进行优化。目前单集群承诺可用性99.9%&#xff0…

layui 表单动态添加、删除input框

html部分 <div class"layui-form-item" ><label class"layui-form-label">路线</label><div class"layui-input-block" id"last"><div class"layui-input-inline">{empty name"$ways&q…

MySQL 可重复读,差点就让我背上了一个 P0 事故!

来源 |程序通事责编 | Carol封图 | CSDN 付费下载自视觉中国P0 事故&#xff1a;余额多扣&#xff01;这是一个真实的生产事件&#xff0c;事件起因如下&#xff1a;现有一个交易系统&#xff0c;每次产生交易都会更新相应账户的余额&#xff0c;出账扣减余额&#xff0c;入账增…

AliOS Things 3.0应用笔记:摄像头配网 + 钉钉群通知 + 天气显示

给AliOS Things一颗STAR 目录 运行流程效果展示 操作流程 环境配置源码结构配置烧录运行 源码讲解 QR扫码部分GUI部分https client部分 运行流程 本示例有如下3个功能&#xff1a; 摄像头配网。推送消息到钉钉群。显示当天天气。操作流程 环境配置 AliOS Things环境安装&a…

查询字段是date类型的数据

create table A_LOG ( S_PROCNAME VARCHAR2(64), S_TIME DATE not null, S_MSG VARCHAR2(4000), S_USER VARCHAR2(64) ) SELECT * FROM A_LOGa where to_char(a.s_time,yyyy-MM-dd) date 2021-08-01;--4

与阿里合伙人合影,两年净赚一百万,这个草根姑娘有什么魔力?

大学肄业&#xff0c;网店关闭&#xff0c;公司转手&#xff0c;人生的下一步要怎么走&#xff1f;张昕总沉浸在自己的世界里发呆。 直到两年前&#xff0c;张昕随手往几个群里转发了“购买阿里云服务器”的折扣幸运券&#xff0c;半年后&#xff0c;因订单数量排进前十&#…

借助大数据进行社交媒体营销,企业们得这么玩!

作者 | Annie Qureshi译者 | 火火酱&#xff0c;责编 | Carol出品 | CSDN 云计算&#xff08;CSDNcloud&#xff09;封图 | CSDN 付费下载自视觉中国自上世纪80年代以来&#xff0c;“数据”一词就一直是互联网行业的重要术语。随着人们对数字领域的关注度越来越高&#xff0c;…

聊一聊DNS劫持那些事

作为《DNS攻击防范科普系列》的最后一篇&#xff0c;今天我们来好好聊聊DNS劫持。先回顾一下DNS劫持的概念&#xff1f;DNS劫持即通过某种技术手段&#xff0c;篡改正确域名和IP地址的映射关系&#xff0c;使得域名映射到了错误的IP地址&#xff0c;因此可以认为DNS劫持是一种D…