用户访问session分析-按session粒度进行数据聚合

2023-11-17

思路:之前模拟创建了两张表:user_visit_action 和 user_info

对于user_visit_action表

1. 通过用户传过来的指定日期范围内,从user_visit_action中查询出指定的用户访问数据 变成 actionRDD
2. 将actionRDDw映射成<Sessionid ,Row>的格式:sessionid2ActionRDD
3. 对行为数据按Session 粒度进行分组 : sessionid2ActionsRDD
4. /每一个session分组进行聚合,将session中所有的搜索词和点击品类聚合起来 。获取的数据格式: <userid,partAggrInfo(sessionid,searchKeyWords,clickCategoryIds)>   生成userid2PartAggrInfoRDD

对于user_info表

查询所有用户数据,并映射成<userid,Row>格式 :userid2InfoRDD

聚合操作

1.  将session粒度聚合数据与用户信息进行join  :userid2FullInfoRDD
2. 对join起来的userid2FullInfoRDD数据进行拼接,并且返回<session,fullAggrInfo>格式数据 :sessionid2FullAggrInfoRDD
> partAggrInfo = sessionid +searchKeyWords+clickCategoryIds
> fullAggrInfo = partAggrInfo +age +professional +city + sex
import java.util.Iterator;

import com.ibeifeng.sparkproject.util.StringUtils;
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.hive.HiveContext;

import com.alibaba.fastjson.JSONObject;
import com.sparkproject.conf.ConfigurationManager;
import com.sparkproject.constant.Constants;
import com.sparkproject.dao.ITaskDAO;
import com.sparkproject.dao.impl.DAOFactory;
import com.sparkproject.domain.Task;
import com.sparkproject.test.MockData;
import com.sparkproject.util.ParamUtils;

import scala.Tuple2;

/**
 * 用户访问session 分析spark作业
 * 
 * 接收用户创建的分析任务,用户可能指定的条件如下
 * 1. 时间范围:起始日期到结束日期
 * 2. 性别:男/女
 * 3. 年龄范围
 * 4. 职业:多选
 * 5. 城市:多选
 * 6. 搜索词:多个搜索词,只要某个session中任何一个action搜索过指定的关键词,那么session就符合条件
 * 7. 点击品类:多个品类,只要某个session中任何一个action点击过某个品类,那么session就符合条件
 * 
 * spark作业接受用户创建的任务方法:j2ee在接受用户创建任务请求后,会将其插入mysql的task表中,任务参数以json格式封装在task_param字段中
 * 接着,j2ee平台会执行spark-submit shell脚本,并将taskid作为参数传递给spark-submit shell脚本
 * spark-submit shell脚本在执行时是可以接收参数的,并且将参数传递给spark作业的main函数。参数就封装在main函数的args数组中
 * (这是spark本身提供的特性)
 * @author chenc
 *
 */
public class UserVisitSessionAnalyzeSpark {
    public static void main(String[] args) {
        //构建spark上下文
        SparkConf conf = new SparkConf()
                .setAppName(Constants.SPARK_APP_NAME_SESSION)
                .setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        //从JavaSparkContext取出它自己对应的那个SparkContext
        SQLContext sqlContext = getSQLContext(sc.sc());

        //生成模拟测试数据
        mockData(sc,sqlContext);

        //创建需要使用的DAO组件 
        //用接口类型定义taskDAO
        ITaskDAO taskDAO = DAOFactory.getTaskDAO();

        //如果要根据用户在创建任务时指定的参数来进行数据过滤和筛选,
        //首先得查询出指定的任务,并获取任务的查询参数
        //需要先获取任务的id(从封装好的ParamUtils中的getTaskIdFromArgs方法获取)
        //(spark-submit shell脚本在执行时是可以接收参数的,并且将参数传递给spark作业的main函数。参数就封装在main函数的args数组中)
        Long taskid = ParamUtils.getTaskIdFromArgs(args);
        //通过基于JDBC的TaskDAO组件,查询对应的task信息
        Task task = taskDAO.findById(taskid);
        //将JSON串转换为jsonObject-->拿到了用户的创建参数
        JSONObject taskParam = JSONObject.parseObject(task.getTaskParam());


        //如果要进行session粒度的数据聚合,首先要从user_visit_action表中,查询出来指定日期范围内的行为数据 
        JavaRDD<Row> actionRDD = getActionRDDByDataRange(sqlContext, taskParam);

        // 首先将行为数据按照session_id 进行groupByKey进行分组
        // 此时的数据的粒度是session粒度。然后将session粒度的数据与用户信息数据进行join.就可以获取session粒度的数据
        // 同时数据里还包含了session对应的user的信息
        JavaPairRDD<String, String> sessionid2AggrInfoRDD = aggregateBySession(sqlContext, actionRDD);

        //关闭
        sc.close();


    }
    /**
     * 获取SQLContext
     * 如果是本地测试环境,生成SQLContext对象
     * 如果是生产环境运行,生成HiveContext对象
     * @param sc SparkContext
     * @return SparkContext
     */
    private static SQLContext getSQLContext(SparkContext sc) {
        boolean local = ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if (local) {
            return new SQLContext(sc);
        } else {
            return new HiveContext(sc);
        }
    }
    /**
     * 生成模拟数据(只有是本地模式才会生成)
     * @param sc
     * @param sqlContext
     */
    private static void mockData(JavaSparkContext sc,SQLContext sqlContext) {
        boolean local = ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if(local) {
            MockData.mock(sc, sqlContext);
        }
    }
    /**
     * 通过用户传过来的指定日期范围内,从user_visit_action中查询出指定的用户访问数据
     * @param sqlContext
     * @param taskParam 任务参数
     * @return 行为数据RDD
     */
    private static JavaRDD<Row> getActionRDDByDataRange(SQLContext sqlContext,JSONObject taskParam){
        String startDate = ParamUtils.getParam(taskParam, Constants.PARAM_START_DATE);
        String endDate = ParamUtils.getParam(taskParam, Constants.PARAM_END_DATE);


        String sql = "select * "
                +"from user_visit_action "
                + "where date>='" + startDate + "' "
                + "and date<='" + endDate + "'";
        DataFrame actionDF = sqlContext.sql(sql);
        return actionDF.javaRDD();

    }

    /**
     * 对行为数据按session粒度进行聚合
     * @param actionRDD 行为数据RDD
     * @return session粒度聚合数据
     */
    private static JavaPairRDD<String, String> aggregateBySession(
             SQLContext sqlContext,JavaRDD<Row> actionRDD){
        //现在action中的元素是Row,一个Row是一行用户访问行为记录:比如一次点击或者搜索
        //现在我们需要将这个Row映射成<Sessionid ,Row>的格式
        JavaPairRDD<String, Row> sessionid2ActionRDD = actionRDD.mapToPair(
                /**
                 * PairFunction
                 * 第一个参数:相当于是函数的输入
                 * 第二个和第三个参数:相当于函数的输出(Tuple),分别是Tuple第一个和第二个值
                 */
                new PairFunction<Row, String, Row>() {
                    private static final long SerialVersionUID= 1L;
                    @Override
                    public Tuple2<String, Row> call(Row row) throws Exception {
                        //第2个是sesion_id
                        return new Tuple2<String, Row>(row.getString(2),row);
                    }
        });
        //对行为数据按Session 粒度进行分组
        JavaPairRDD<String, Iterable<Row>> sessionid2ActionsRDD = sessionid2ActionRDD.groupByKey();

        //对每一个session分组进行聚合,将session中所有的搜索词和点击品类聚合起来
        //获取的数据格式: <userid,partAggrInfo(sessionid,searchKeyWords,clickCategoryIds)>
        JavaPairRDD<Long, String> userid2PartAggrInfoRDD = sessionid2ActionsRDD.mapToPair(
                new PairFunction<Tuple2<String,Iterable<Row>>, Long , String>() {

            private static final long SerialVersionUID= 1L;
            @Override
            public Tuple2<Long, String> call(Tuple2<String, Iterable<Row>> tuple) throws Exception {
                String sessionid=tuple._1;
                Iterator<Row> iterator  = tuple._2.iterator();

                //在遍历的时候,要把它搜索过的关键词和它点击过的品类ID给拼起来
                StringBuffer searchKeywordsBuffer = new StringBuffer("");
                StringBuffer clickCategoryIdsBuffer = new StringBuffer("");

                Long userid = null;
                //遍历session所有的访问行为
                while(iterator.hasNext()) {
                     //提取每个访问行为的搜索词字段和点击品类字段
                    Row row = iterator.next();
                    //获取userid
                    if (userid == null) {
                        userid = row.getLong(1);
                    }
                    //search_keyword:5  
                    String searchKeyword = row.getString(5);
                    //click_category_id:6
                    Long clickCategoryId = row.getLong(6);
                    //对数据说明:并非每一行访问行为都有searchKeyword和clickCategoryId 。 只有搜索行为有searchKeyword
                    //只有点击品类行为有clickCategoryId。所以数据可能出现null
                    //
                    //我们决定是否要将搜索词或者点击品类id拼接到字符串中去,首先要满足:
                    //1. 不能是null值
                    //2. 之前的字符串中还没有搜索词或者点击品类id
                    if(StringUtils.isNotEmpty(searchKeyword)) {
                        if(! searchKeywordsBuffer.toString().contains(searchKeyword)) {
                            searchKeywordsBuffer.append(searchKeyword+",");
                        }
                    }
                    if(clickCategoryId != null) {
                        if(! clickCategoryIdsBuffer.toString().contains(String.valueOf(clickCategoryId))) {
                            clickCategoryIdsBuffer.append(clickCategoryId+",");
                        }
                    }


                }

                //聚合
                String searchKeyWords = StringUtils.trimComma(searchKeywordsBuffer.toString());
                String clickCategoryIds = StringUtils.trimComma(clickCategoryIdsBuffer.toString());

                //返回的数据格式<sessionid,partAggrInfo>
                //但是这一步聚合完之后,我们还需要将每一行数据跟对应得用户信息进行聚合
                //问题在:若跟用户信息进行聚合,key就不应该是sessionid,而应该是userid才可以
                //跟<userid,Row>格式的用户信息进行聚合
                //若这里直接返回<sessionid,partAggrInfo>还需要再做一次mapToPair算子将RDD映射为
                //<userid,partAggrInfo>,那么会多此一举
                //所以此处其实可以直接返回的数据格式就是<userid,partAggrInfo>
                //然后和用户信息join的时候,将partAggrInfo关联上userInfo。然后再直接将返回的Tuple的key设置成sessionid
                //最后的数据格式还是<sessionid,fullAggrInfo>

                //聚合数据用什么格式拼接?
                //使用 key=value|key=value
                String partAggrInfo = Constants.FIELD_SESSION_ID + "=" + sessionid + "|"
                        + Constants.FIELD_SEARCH_KEYWORDS +"=" + searchKeyWords + "|"
                        + Constants.FIELD_CLICK_GATEGORY_IDS + "=" + clickCategoryIds;

                //返回:session 对应得userid 包括这个session部分聚合数据
                return new Tuple2<Long ,String>(userid,partAggrInfo);
            }
        });
        //查询所有用户数据,并映射成<userid,Row>格式
        String sql = " select * from user_info";
        JavaRDD<Row> userInfoRDD = sqlContext.sql(sql).javaRDD();

        JavaPairRDD<Long, Row> userid2InfoRDD = userInfoRDD.mapToPair(new PairFunction<Row, Long, Row>() {


            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<Long, Row> call(Row row) throws Exception {
                return new Tuple2<Long,Row>(row.getLong(0),row);
            }
        });
        //将session粒度聚合数据与用户信息进行join
        JavaPairRDD<Long, Tuple2<String, Row>> userid2FullInfoRDD = userid2PartAggrInfoRDD.join(userid2InfoRDD);
        //对join起来的数据进行拼接,并且返回<session,fullAggrInfo>格式数据
        JavaPairRDD<String, String> sessionid2FullAggrInfoRDD = userid2FullInfoRDD.mapToPair(new PairFunction<Tuple2<Long,Tuple2<String,Row>>, String, String>() {

            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<String, String> call(Tuple2<Long, Tuple2<String, Row>> tuple) throws Exception {
                String partAggrInfo = tuple._2._1;
                Row userInfoRow = tuple._2._2;

                //获取session_id
                String sessionid = StringUtils.getFieldFromConcatString(partAggrInfo, "\\|", Constants.FIELD_SESSION_ID);
                //从user表中取出age(3)/professional(4)/city(5)/sex(6)
                int age = userInfoRow.getInt(3);
                String professional = userInfoRow.getString(4);
                String city = userInfoRow.getString(5);
                String sex = userInfoRow.getString(6);

                String fullAggrInfo = partAggrInfo + "|" 
                        + Constants.FIELD_AGE + "=" + age + "|"
                        + Constants.FIELD_PROFESSIONAL + "=" + professional + "|"
                        + Constants.FIELD_CITY + "=" + city + "|"
                        + Constants.FIELD_SEX + "=" + sex;
                return new Tuple2<String,String>(sessionid,fullAggrInfo);
            }
        }) ;
        return sessionid2FullAggrInfoRDD;
    }
}

之前已经获取了通过条件过滤的session,要实现:访问时长在0~3S内的session的数量,占总session数量的比例;4~6s。。。

访问时长:把session的最后一个action的时间减去第一个action的时间
访问步长:session的action的数量(访问页面次数)

采用的方式思路:自定义一个Accumulator,实现较为复杂的计算逻辑,一个Accumulator维护了所有范围区间的数量的统计逻辑。
1. 低耦合。如果说,session数量计算逻辑要改变,那么不用变更session遍历相关的代码,只要维护一个Accumulator里面的代码即可。
2. 如果计算逻辑后期变更或者增加了几个范围,那么也很方便,不用多加几个Accumulator、去修改大量代码。只要维护一个Accumulator里面的代码即可

使用自定义accumulator,可以任意实现自己的复杂分布式计算的逻辑。如果task是分布式的,要进行复杂计算逻辑那么是很难实现的,(可能需要借助redis,维护中间状态,借助于zookeeper实现分布式锁)。但是使用自定义accumulator,可以更方便进行中间状态的维护,不用担心并发和锁的问题。

重构实现思路和重构session聚合

    /**
     * session 聚合统计(统计出访问时长和访问不常,各个区间session数量占据总session数量的比例)
     * 若不进行重构直接实现:
     * 1. actionRDD 映射成<sessionid,Row>格式.
     * 2. 按sessionid聚合,计算每个session的访问时长和步长,生成新的RDD
     * 3. 遍历新生成的RDD,将每个session的访问时长与访问步长,去更新自定义accumulator中对应的值
     * 4. 使用自定义的accumulator中的统计值,计算各个区间的比例
     * 5. 将最后计算出的结果写入MySQL对应表
     * 
     * 问题在于: 
     * 1. 为何继续使用actionRDD去映射?  因为在session聚合时映射已经完成
     * 2. 为了session聚合的功能,是否一定需要单独遍历一遍session? 因为已经有session数据,没必要
     * 过滤session时,已经相当于遍历session,因此没必要再过滤一遍
     * 
     * 重构实现思路:
     * 1. 不生成任何新的RDD
     * 2. 不单独遍历session的数据
     * 3. 可以在进行session聚合时,直接计算每个session的访问时长和访问步长
     * 4. 在进行过滤时,本来就需要遍历所有聚合session信息,此时就可在某个session通过筛选条件后将其访问时长/步长累加至自定义的accumulator上
     * 
     * 第一种方案其实是代码划分(解耦合、可维护)优先,设计优先。第二种方案是性能优先

重构过滤进行统计

计算统计结果并且存入MYSQL

/**
     * 计算各session范围占比,并写入MySQL
     * @param value
     */
    private static void calculateAndPersistAggrStat(String value, long taskid) {
        // 从Accumulator统计串中获取值
        long session_count = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.SESSION_COUNT));  

        long visit_length_1s_3s = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_1s_3s));  
        long visit_length_4s_6s = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_4s_6s));
        long visit_length_7s_9s = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_7s_9s));
        long visit_length_10s_30s = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_10s_30s));
        long visit_length_30s_60s = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_30s_60s));
        long visit_length_1m_3m = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_1m_3m));
        long visit_length_3m_10m = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_3m_10m));
        long visit_length_10m_30m = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_10m_30m));
        long visit_length_30m = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.TIME_PERIOD_30m));

        long step_length_1_3 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_1_3));
        long step_length_4_6 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_4_6));
        long step_length_7_9 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_7_9));
        long step_length_10_30 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_10_30));
        long step_length_30_60 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_30_60));
        long step_length_60 = Long.valueOf(StringUtils.getFieldFromConcatString(
                value, "\\|", Constants.STEP_PERIOD_60));

        // 计算各个访问时长和访问步长的范围
        double visit_length_1s_3s_ratio = NumberUtils.formatDouble(
                (double)visit_length_1s_3s / (double)session_count, 2);  
        double visit_length_4s_6s_ratio = NumberUtils.formatDouble(
                (double)visit_length_4s_6s / (double)session_count, 2);  
        double visit_length_7s_9s_ratio = NumberUtils.formatDouble(
                (double)visit_length_7s_9s / (double)session_count, 2);  
        double visit_length_10s_30s_ratio = NumberUtils.formatDouble(
                (double)visit_length_10s_30s / (double)session_count, 2);  
        double visit_length_30s_60s_ratio = NumberUtils.formatDouble(
                (double)visit_length_30s_60s / (double)session_count, 2);  
        double visit_length_1m_3m_ratio = NumberUtils.formatDouble(
                (double)visit_length_1m_3m / (double)session_count, 2);
        double visit_length_3m_10m_ratio = NumberUtils.formatDouble(
                (double)visit_length_3m_10m / (double)session_count, 2);  
        double visit_length_10m_30m_ratio = NumberUtils.formatDouble(
                (double)visit_length_10m_30m / (double)session_count, 2);
        double visit_length_30m_ratio = NumberUtils.formatDouble(
                (double)visit_length_30m / (double)session_count, 2);  

        double step_length_1_3_ratio = NumberUtils.formatDouble(
                (double)step_length_1_3 / (double)session_count, 2);  
        double step_length_4_6_ratio = NumberUtils.formatDouble(
                (double)step_length_4_6 / (double)session_count, 2);  
        double step_length_7_9_ratio = NumberUtils.formatDouble(
                (double)step_length_7_9 / (double)session_count, 2);  
        double step_length_10_30_ratio = NumberUtils.formatDouble(
                (double)step_length_10_30 / (double)session_count, 2);  
        double step_length_30_60_ratio = NumberUtils.formatDouble(
                (double)step_length_30_60 / (double)session_count, 2);  
        double step_length_60_ratio = NumberUtils.formatDouble(
                (double)step_length_60 / (double)session_count, 2);  

        // 将统计结果封装为Domain对象
        SessionAggrStat sessionAggrStat = new SessionAggrStat();
        sessionAggrStat.setTaskid(taskid);
        sessionAggrStat.setSession_count(session_count);  
        sessionAggrStat.setVisit_length_1s_3s_ratio(visit_length_1s_3s_ratio);  
        sessionAggrStat.setVisit_length_4s_6s_ratio(visit_length_4s_6s_ratio);  
        sessionAggrStat.setVisit_length_7s_9s_ratio(visit_length_7s_9s_ratio);  
        sessionAggrStat.setVisit_length_10s_30s_ratio(visit_length_10s_30s_ratio);  
        sessionAggrStat.setVisit_length_30s_60s_ratio(visit_length_30s_60s_ratio);  
        sessionAggrStat.setVisit_length_1m_3m_ratio(visit_length_1m_3m_ratio); 
        sessionAggrStat.setVisit_length_3m_10m_ratio(visit_length_3m_10m_ratio);  
        sessionAggrStat.setVisit_length_10m_30m_ratio(visit_length_10m_30m_ratio); 
        sessionAggrStat.setVisit_length_30m_ratio(visit_length_30m_ratio);  
        sessionAggrStat.setStep_length_1_3_ratio(step_length_1_3_ratio);  
        sessionAggrStat.setStep_length_4_6_ratio(step_length_4_6_ratio);  
        sessionAggrStat.setStep_length_7_9_ratio(step_length_7_9_ratio);  
        sessionAggrStat.setStep_length_10_30_ratio(step_length_10_30_ratio);  
        sessionAggrStat.setStep_length_30_60_ratio(step_length_30_60_ratio);  
        sessionAggrStat.setStep_length_60_ratio(step_length_60_ratio);  

        // 调用对应的DAO插入统计结果
        ISessionAggrStatDAO sessionAggrStatDAO = DAOFactory.getSessionAggrStatDAO();
        sessionAggrStatDAO.insert(sessionAggrStat);  

结果界面
应该在本地mysql中的session_aggr_stat表中存在如下数据即完成。
这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用户访问session分析-按session粒度进行数据聚合 的相关文章

  • 通过5折交叉验证,实现逻辑回归,决策树,SVM,随机森林,GBDT,Xgboost,lightGBM的评分

    通过 折交叉验证 实现逻辑回归 决策树 SVM 随机森林 GBDT Xgboost lightGBM的评分 导入的包 import pandas as pd import warnings from sklearn preprocessin
  • EasyPoi与EasyExcl操作Excl

    EasyPoi与EasyExcel操作Excel 一 Poi介绍 Poi是操作Excel的一套规范 EasyPoi是Apache公司开发的一套框架 而EasyExcl是阿里开发的一套框架 EasyPoi是将表格一次行全部读到内存中再进行操作
  • ElementUI中遇到的问题

    1 解决在MAC系统中ElementUI中的el table标签中的滚动条太小的问题 解决方案 在CSS样式中输入如下样式 deep el table body wrapper webkit scrollbar width 17px 滚动条
  • 微服务实战(二十) 微服务RPC feign如何进行实体对象传递与接收

    关于微服务RPC远程调用 之前已经简单介绍过feign以及openfeign 并且就参数传递与接收也进行过简单讲解 Nacos Feign简单使用 https blog csdn net u011177064 article details
  • onchange事件,IE下的替换的两种方法

    例如
  • 【满分】【华为OD机试真题2023 JS】查找充电设备组合

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 查找充电设备组合 时间限制 5s 空间限制 256MB 限定语言 不限 题目描述 某个充电站 可提供n个充电设备 每个充电设备均有对应的输出功率 任意个充电设备组合的输出功率总和
  • 88.合并两个有序数组

    合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目 请你 合并 nums2 到 nums1 中 使合并后的数组同样按 非递减顺序
  • 第十三届蓝桥杯大赛软件赛省赛_C/C++大学B组_试题C:刷题统计

    第十三届蓝桥杯大赛软件赛省赛 C C 大学B组 试题C 刷题统计 文章目录 赛题描述 思路 算法实现 赛题描述 思路 2022年04月14日18时42分28秒 初步想到的方法有两种 1 模拟 这个方法在a b极限小n极限大的情况下 一定会超
  • Maven依赖的可传递性

    今天我们来讲一讲Maven工程在引用jar包上的传递性 1 情景分析 假设有两个Maven工程 A和B 其中A引用了已经安装在仓库中的B工程install成的jar包 而B工程本身有对spring core jar的引用 我们可以看到在A工
  • Unity中设置Transform的forward/up/right旋转异常问题

    本文首发于洪流学堂微信公众号 洪流学堂公众号回复forward 获取本文的测试工程 小新 智哥 我在设置人物持枪IK时 想要将枪的方向设置为光标的方向 但是出现了枪旋转混乱的问题 这个该怎么解决啊 大智 具体说说看你是怎么实现的 小新 我先
  • OpenAI的模型下载(chatGPT模型)

    OpenAI于Aug 21 2019提交了一个commit 其公布了更大的774M模型 并且纠正了对之前公布的两个模型的参数估计错误导致的命名不准确问题 修改了其名称 其中 原117M模型更名为124M 345M更名为355M 仅修改名称
  • 三角函数与反三角函数的关系及图像

    文章目录 TOC 1 正弦函数 sin x 反正弦函数 arcsin x 2 余弦函数 cos x 反余弦函数 arccos x 3 反正弦函数 arcsin x 反余弦函数 arccos x 4 正切函数 tan x 余切函数 cot x
  • 20道JavaScript经典面试题

    该篇文章整理了一些前端经典面试题 附带详解 涉及到JavaScript多方面知识点 满满都是干货 建议收藏阅读 前言 如果这篇文章有帮助到你 关注 点赞 鼓励一下作者 文章公众号首发 关注 前端南玖 第一时间获取最新的文章 1 说一说Jav
  • Android增强现实(一)-AR的三种方式(展示篇)

    有一段时间没写博客了 事情比较多 博客进度有点跟不上了 1 Android增强现实 一 AR的三种方式 展示篇 2 Android增强现实 二 支持拖拽控制进度和伸缩的VrGifView 3 Android增强现实 三 3D模型展示器 这段
  • 安全开发-JS应用&NodeJS指南&原型链污染&Express框架&功能实现&审计&WebPack打包器&第三方库JQuery&安装使用&安全检测

    文章内容 环境搭建 NodeJS 解析安装 库安装 安全问题 NodeJS 注入 RCE 原型链 案例分析 NodeJS CTF题目 源码审计 打包器 WebPack 使用 安全 第三方库 JQuery 使用 安全 环境搭建 NodeJS
  • TestComplete数据驱动测试教程(三)——修改记录测试

    TestComplete是一款具有人工智能的自动UI测试工具 利用自动化测试工具和人工智能支持的混合对象识别引擎 轻松检测和测试每个桌面 Web和移动应用程序 本文中我们将讲解如何进行数据驱动的测试 方便大家更快更直接的学习TestComp
  • c++的常用库

    c 的常用库 C 资源大全 关于 C 框架 库和资源的一些汇总列表 内容包括 标准库 Web应用框架 人工智能 数据库 图片处理 机器学习 日志 代码分析等 标准库 C 标准库 包括了STL容器 算法和函数等 C Standard Libr
  • 用C++封装一个Ocr文字识别程序,离线识别,完全免费

    程序封装了RapidOcr模块 源文件路径Tree信息如下 文件夹 PATH 列表 卷序列号为 43EE 6931 D build windows bat CMakeLists txt CMakeSettings json Ocr cpp
  • Centos7版本的安装超级详细

    Centos7的安装超级详细 关于Centos版本下载地址 https archive kernel org centos vault CentOS 7 0 1406 x86 64 DVD iso 标准安装版 一般下载这个就可以了 Cent
  • 线程创建的三种方法

    继承Thread类 1 继承Thread类 2 重写run 方法 3 调用start 方法开启线程 public class testThread1 extends Thread Override public void run run 方

随机推荐

  • Qt浅谈之一:内存泄露(总结)

    一 简介 Qt内存管理机制 Qt 在内部能够维护对象的层次结构 对于可视元素 这种层次结构就是子组件与父组件的关系 对于非可视元素 则是一个对象与另一个对象的从属关系 在 Qt 中 在 Qt 中 删除父对象会将其子对象一起删除 C 中del
  • 谷歌chrome浏览器的源码分析(一)

    随着网络技术的发展 越来越多应用都已经离不开网络 特别像人类大脑一样的知识库的搜索引擎 更加是离不开功能强大的云计算 不过 即便云计算非常强大 但它还不能直接地把结果呈现给用户 这样就需要一个客户端来呈现出来 这个客户端就是浏览器 现在越来
  • 归并排序(分析与模板)

    归并排序 思路 1 确定分界元素mid left right 2 2 递归分解数组 两两组合组成两个有序数组 3 归并 合二为一 int temp 100010 merge sort int num int l int r if l gt
  • std::thread线程命名

    也可以参考我另外一篇文章 另外一篇更详细些 为线程设置名字的最大的好处是在程序出错时 它会出现在 GDB 的出错信息里 可以更快地定位问题 有两种方法可以给线程设置名字 一种在线程的调用函数内部设置 还有一种是在外部对指定线程变量做设置 i
  • 【软件测试】未来软件测试必备的8大技能,你缺少哪几个?

    软件测试工程师是个神奇的职业 他是开发人员与老板之间的传话筒 三夹板 也是开发人员与老板的好帮手 他不仅需要有销售的沟通能力 也需要具备编辑人员的文档撰写技巧 如此一个面面俱到的岗位 他需要具备的技能到底有哪些呢 有逆向思维的能力 曾经采访
  • 算法:两个有序数组合并成一个有序数组 java语言

    题目 有两个有序数组a 和b 将它们合并成数组c 需要c 也是有序数组 思路 新建一个以两个集合长度之和为长度的新数组 从两数组最左边开始比起 把小的放入新集合 并用变量标记后一位置 每次比较都是比较的最左边未比较过的元素 通过变量 循环比
  • 分享一个可交互的小场景(二)

    先看效果 可互动的小场景 再看代码 JS部分
  • 正点原子I.MX6ULL开发板车牌识别项目实战 1

    1 项目总体概述 下图为 车牌识别项目 的系统框图 借助这个框图 简要介绍项目的总体思路和所需要做的准备工作 1 1 总体思路 通过摄像头采集图像信息 并将图像信息传递开发板 这里使用的是OpenCv 开发板收到图像信息之后 通过定时器 周
  • Python解决ModuleNotFoundError: No module named 'Queue'的问题

    我们知道Python2和Python3两个版本之间 有些不兼容的地方 Python3中引入Queue会报出这个问题 Python3中要这样引入 1 import queue Python2中要这样引入 1 import Queue 为了兼容
  • 第十六课,面剔除

    使用OpenGL的面剔除选项 它默认是禁用状态 glEnable GL CULL FACE 直接运行后 我们发现正方体的部分面确实被剔除了 但是却不是背向面 这是因为我们定义的正方体并不是严格遵循逆时针顺序定义的 原理详见教程 这里就不过多
  • python输出文本 去掉引号,如何从导出的python列表中删除逗号,引号和括号?

    You guys were super helpful with my last newbie question so I figured I would give it another shot Right now my Python 3
  • 基于范围的for循环

    一 基于范围的for循环 C 11 1 范围for的语法 2 范围for的使用条件 二 指针空值nullptr 一 基于范围的for循环 C 11 1 范围for的语法 对于一个有范围的集合而言 由程序员来说明循环的范围是多余的 有时候还会
  • 智能聊天机器人实现(源码+解析)

    前言 之前写了一篇 美女图片采集器 源码 解析 得到了众多朋友的支持 发现这样系列的教程还是挺受欢迎的 也激励我继续写下去 也在那一篇文章中提过 美女图片采集只是我先前那个完整APP中的一个功能罢了 还有其他几个比较好玩的尚未开源 之后有时
  • QWidgetAction实现鼠标滑过菜单项图标高亮显示

    需求是鼠标滑过菜单项时 菜单项的文字 icon以及子菜单的小箭头都要高亮显示 qss中只能设置item背景色 文字颜色以及子菜单小箭头的样式 icon的图片不能切换 另外曾经想过用indicator 对action setCheckable
  • Ubuntu18.04安装QT5

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 QT5是什么 二 安装包安装 1 下载安装包 2 安装QT5 3 运行 4 其他方式 总结 前言 最近在学习QT5 在Windows上的安装自然不必多说
  • 爬虫 — 反爬

    目录 一 UA 反爬 二 Cookie 验证与反爬 1 Cookie 简介 2 使用 Cookie 原因 3 Cookie 作用 3 1 模拟登录 3 2 反反爬 三 Referer 反爬 一 UA 反爬 UA User Agent 用户代
  • [机械]“重工业面临两大危机”——向文波(三一重工股份有限公司执行总裁)

    向文波 三一重工股份有限公司执行总裁 向文波是三一重工的掌门人 但深受徐工事件影响 他以业内的视角 适时地向中国重工业的改革发出一个警示信号 提出一个超越 抓大放小 国进民退 等传统国企改革的新命题 产业安全 引起了舆论与政府的重视 中国重
  • 2021.11.13-15总结

    将C语言文件相关的内容学完了 了解了文件相关的函数
  • linux网络管理

    一 网络接口 1 在Linux系统中 主机的网络接口卡通常称为网络接口 使用ifconfig命令来查看网络 2 eth0 是Linux系统中第一块以太网卡的名称 3 lo 是Linux系统中的 环回 网络接口 lo 并不代表真正的网络接口
  • 用户访问session分析-按session粒度进行数据聚合

    思路 之前模拟创建了两张表 user visit action 和 user info 对于user visit action表 1 通过用户传过来的指定日期范围内 从user visit action中查询出指定的用户访问数据 变成 ac