推荐算法_CIKM-2019-AnalytiCup 冠军源码解读
最近在帮一初创app写推荐系统,顺便学习一波用户兴趣高速检索的冠军算法。
写总结前贴出冠军代码的git地址:https://github.com/ChuanyuXue/CIKM-2019-AnalytiCup
该算法分三步:基于Apririo的item_CF、特征提取、排序。
先看第一步,item_CF可以说是很传统的算法了。该步被作者分为七个部分:
1、generate_user_logs:把用户分组,并在每个组中统计用户的行为日志,以方便后续的并行化处理
- import multiprocessing as mp
- import time
- import pandas as pd
- import numpy as np
- def reduce_mem_usage(df):
- """ iterate through all the columns of a dataframe and modify the data type
- to reduce memory usage.
- """
- start_mem = df.memory_usage().sum()
- print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
- for col in df.columns:
- col_type = df[col].dtype
- if col_type != object:
- c_min = df[col].min()
- c_max = df[col].max()
- if str(col_type)[:3] == 'int':
- if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
- df[col] = df[col].astype(np.int8)
- elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
- df[col] = df[col].astype(np.int16)
- elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
- df[col] = df[col].astype(np.int32)
- elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
- df[col] = df[col].astype(np.int64)
- else:
- if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
- df[col] = df[col].astype(np.float16)
- elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
- df[col] = df[col].astype(np.float32)
- else:
- df[col] = df[col].astype(np.float64)
- else:
- df[col] = df[col].astype('category')
- end_mem = df.memory_usage().sum()
- print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
- print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
- return df
- def generate_logs_for_each_group(matrix, q):
- user_log = dict()
- for row in matrix:
- user_log.setdefault(row[0], [])
- user_log[row[0]].append(row[1])
- print('This batc is finished')
- q.put(user_log)
reduce_mem_usage()使用了机器学习中panda输入数据通用的内存优化方法,通过memory_usage().sum()统计矩阵的内存使用量,np.iinfo().min()/max()分别用于找到一个列表中的最小/大数,这里np.iinfo(int8).min()/max()的作用类似于找到该数据类型的最小/大值,比较矩阵中的数据是否处于该区间,若处于,则通过astype()的方法改变数据类型,使其运算占用的内存空间最小;在循环遍历的过程中,要保证数据的类型是由范围由小到大排序的。
generate_logs_for_each_group用于构建用户日记字典,其中setdefault()用于在用户日志字典中添加键与对应的默认值;
主函数:
- data = reduce_mem_usage(pd.read_csv(path+'user_behavior.csv', header=None))
- user = pd.read_csv(path+'user.csv', header=None)
- item = pd.read_csv(path+'item.csv', header=None)
- data['day'] = data[3] // 86400
- data['hour'] = data[3] // 3600 % 24
- data = data.drop(3, axis=1)
- data.columns = ['userID','itemID','behavoir','day','hour']
- user.columns = ['userID', 'sex', 'age', 'ability']
- item.columns = ['itemID', 'category', 'shop', 'band']
- data = data.drop_duplicates(['userID','itemID'],keep="last")
- data = data.sort_values(['day','hour'], ascending=True).reset_index(drop=True)
- users = list(set(user['userID']))
- user_groups = [users[i: i + len(users) // CPU_NUMS] for i in range(0, len(users), len(users) // CPU_NUMS)]
- q = mp.Queue()
- for groupID in range(len(user_groups)):
- matrix = data[data['userID'].isin(user_groups[groupID])][['userID','itemID']].values
- task = mp.Process(target=generate_logs_for_each_group, args=(matrix, q, ))
- task.start()
- start_time = time.time()
- print('Waiting for the son processing')
- while q.qsize() != len(user_groups):
- pass
- end_time = time.time()
- print("Over, the time cost is:" + str(end_time - start_time))
for i in range(len(user_groups)):
temp = q.get()
f = open('full_logs/userlogs_group' + str(i) + '.txt','w')
f.write(str(temp))
f.close()
主函数执行数据的导入与处理。这里要注意的就是整个用户组根据CPU的数量被分为几组,以多进程的方式执行generate_logs_for_each_group,生成行首为userID,后跟itemID的矩阵;
最后将生成的用户日志写入文件。
2、generate_hot_table:生成中间文件hot_map(统计的商品的出现次数)、upwardmap与downward_map(商品id映射到实数集[0, m],其中m代表商品总数)与usersActivity_map(用户活跃度)。
- import pandas as pd
- import numpy as np
- # round2 train的路径
- path = '../ECommAI_EUIR_round2_train_20190816/'
- data = pd.read_csv(path + 'user_behavior.csv',header=None)
- data.columns = ['userID','itemID','behavior','timestamp']
- data['day'] = data['timestamp'] // 86400
- data['hour'] = data['timestamp'] // 3600 % 24
- user_times = data[['itemID','userID']].groupby('userID', as_index=False).count()
- user_times.columns = ['userID','itemCount']
- user_times_map = dict(zip(user_times['userID'], user_times['itemCount']))
- len(user_times_map)
- f = open('usersActivity_map.txt', 'w')
- f.write(str(user_times_map))
- f.close()
这段代码使用groupby().count()对数据按userID进行分类,并对每组数目进行统计,统计所得的数标签为'itemCount',随后与userID一起被写入字典,作为用户活跃度map写入文件;
- from sklearn import preprocessing
- le = preprocessing.LabelEncoder()
- item['encoding'] = le.fit_transform(item['itemID'])
- upward_map = dict(zip(item['itemID'], item['encoding']))
- downward_map = dict(zip(item['encoding'], item['itemID']))
在这里preprocessing.LabelEncoder()被用于给商品编号,fit_transform对商品编号序列进行归一化处理,最后生成商品id映射。
- temp = data[['itemID','behavior']].groupby('itemID',as_index=False).count()
- hot_map = dict(zip(temp['itemID'], temp['behavior']))
groupby().count()用法同上。按商品id分组统计,算出商品出现次数。
文件的最后是保存文件函数,不多做解释。
- def save_to_file(trans_map, file_path):
- trans_map = str(trans_map)
- f = open(file_path, 'w')
- f.write(trans_map)
- f.close()
- save_to_file(hot_map,'hot_items_map.txt')
- save_to_file(upward_map,'upward_map.txt')
- save_to_file(downward_map,'downward_map.txt')
3、generate_original_matrix:统计每个group中的相似度矩阵。
- import pandas as pd
- import numpy as np
- import sys
- from scipy.sparse import lil_matrix
- import scipy as scp
- import time
- %load_ext Cython
- ITEM_NUM = 4318201
- def get_logs_from_hardisk(path):
- f = open(path, 'r')
- a = f.read()
- dict_name = eval(a)
- f.close()
- return dict_name
- f = open('usersActivity_map.txt', 'r')
- m = f.read()
- user_times_map = eval(m)
- f.close()
导入数据,不多解释。
- import datetime
- import math
- cpdef calculate_matrix(mat, list user_logs, dict user_times_map):
- cdef int index, i1, i2, count
- cdef list item_log
- cdef tuple u
- count = 0
- for u in user_logs:
- count += 1
- if count % 1000 == 0:
- print('The %d'%count + ' users are finished.')
- print(datetime.datetime.now().strftime('%H:%M:%S'))
- item_log = u[1]
- for index, i1 in enumerate(item_log):
- for i2 in item_log[(index+1): ]:
- weight = 1/(math.log(1+user_times_map[u[0]]))
- mat[i1, i2] += weight
- mat[i2, i1] += weight
- return mat
这部分数据用于计算物品相似度矩阵,在第一个文件生成用户行为日志中,对每一行进行计算,每个用户的item序列对应每行中第二个元素开始的列表;循环遍历物品列表,将物品与所有它之后的物品的相似度加w,w为用户对推荐系统的贡献度,由之前的第二个文件生成的用户活跃度表提供参数,计算公式为:
$w_u=\frac{1}{log(l_u+1)}$
其中l_u为用户活跃度。下面是计算相似度矩阵的主函数:
- user_logs = get_logs_from_hardisk('full_logs/userlogs_group0.txt')
- f = open('upward_map.txt','r')
- upward_map = eval(f.read())
- f.close()
- for u in user_logs:
- user_logs[u] = [int(upward_map[x]) for x in user_logs[u]]
- user_logs = list(user_logs.items())
- for i in range(0, len(user_logs), 10000):
- print('The %d '%i + ' batch is started!')
- print('--------------------------------')
- mat = lil_matrix((ITEM_NUM+1, ITEM_NUM+1), dtype=float)
- mat = calculate_matrix(mat, user_logs[i: i + 10000], user_times_map)
- scp.sparse.save_npz('tmpData/sparse_matrix_%d_batch_group0.npz'%i, mat.tocsr())
- print('save successfully')
- print('--------------------------------')
调入用户日志,调入itemID-实数的文件(用于规范矩阵、减小矩阵规模),将用户日志中的itemID用1~m的实数代替;使用lil_matrix()新建矩阵,分组计算物品相似度,并将相似度矩阵文件保存,第三步操作到此结束。
4、Merge:将多个group中的相似性矩阵合并。
- import pandas as pd
- import numpy as np
- from scipy.sparse import *
- import scipy
- import os
- path = 'tmpData/'
- lenth = 359850
- for i in range(0, 12):
- mat = None
- start = lenth * i
- end = lenth * (i + 1)
- count = 0
- for name in os.listdir('tmpData/'):
- if name[-3:] == 'npz':
- if mat == None:
- mat = load_npz(path + name)[start: end]
- else:
- mat += load_npz(path + name)[start: end]
- count += 1
- scipy.sparse.save_npz('commonMatrix/common_matrix_from_%d_to_%d.npz'%(start, end), mat)
- print('save success for %d batch'%i)
- print('-------------------------')
功能官方文档写的很明显了。主要思想就是分段合并相加相似度矩阵。
5、Save_sparse_to_dense:将相似性矩阵转化为可供快速检索的哈希结构。
- import numpy as np
- from scipy.sparse import *
- import os
- os.listdir('commonMatrix/')
- for name in os.listdir('commonMatrix/'):
- mat = load_npz('commonMatrix/' + name).tolil()
- l = []
- for i in range(mat.shape[0]):
- _, a, b = find(mat[i])
- index = np.where(b > 1.5)
- #l.append(sorted(list(zip(a[index], b[index])),key= lambda x:x[1], reverse=True))
- c = np.array( [round(x,3) for x in b] )
- l.append(sorted(list(zip(a[index], c[index])),key= lambda x:x[1], reverse=True))
- l = str(l)
- f = open('common_dense_valued_small/' + name, 'w')
- f.write(l)
- f.close()
- print('finished')
这段矩阵转哈希表程序首先读入相似度矩阵,对矩阵的每一行进行处理,使用mat()将数组转换成矩阵,使用find()找到非零元素的下标并储存值,下一步将非零元素中大于1.5的元素排序(我也不知道为什么要取1.5 可能是为了降低运算量而作的剪枝),地址和数值被加入表项中。
6、6_Sta_for_SparseMatrix:用于对相似度的改进
- import pandas as pd
- import numpy as np
- import time
- from scipy.sparse import *
- import os
- import re
- ## 统计每个商品的打分次数(用train)
- f = open('hot_items_map.txt', 'r')
- rating_times_map = eval(f.read())
- f.close()
- item_dict = {}
- for name in os.listdir('common_dense_valued_small/'):
- start_time = time.time()
- f = open('common_dense_valued_small/' + name, 'r')
- l = f.read()
- l = eval(l)
- f.close()
- end_time = time.time()
- print('load file: %d sec'%((end_time - start_time)))
- name = re.findall(r'\d+', name)
- start = int(name[0])
- end = int(name[1])
- start_time = time.time()
- for i in range(start, end):
- tmp_list = []
- [tmp_list.append( (x[0], round(x[1] / rating_times_map[i], 4) ) ) for x in l[i - start] if x[0] != i]
- if len(tmp_list) > 0:
- item_dict[i] = sorted(tmp_list,key=lambda x:x[1], reverse=True)[:500]
- end_time = time.time()
- print('This batch is finished, time cost: %d sec'%((end_time - start_time)))
这个文件涉及正则表达式和上一个文件生成的哈希表,首先读入之前生成的商品热度表与哈希表,通过正则表达式findall()读出文件名中的开始item序号与结束item序号(文件命名按分组区间,如:'common_matrix_from_3598500_to_3958350.npz')遍历这个区间内item的哈希表,每个item i、j之间的相似度都除以i的商品热度,作为热度过高的商品的惩罚(若一件商品很热门,则不管用户喜不喜欢,用户对其作出行为的可能性都很大,极有可能出现在不同用户的行为列表中,这样会使得相似度偏大)。将惩罚处理过的哈希表装入item_dict中。
最后,将生成的item_dict写入文件。
- f = open('item_Apriori.txt','w')
- f.write(str(item_dict))
- f.close()
7、generate_recall:官方没有写特别说明,个人理解为这是最后的数据召回。
- import pandas as pd
- import numpy as np
- def load_data(path):
- user = pd.read_csv(path + 'user.csv',header=None)
- item = pd.read_csv(path + 'item.csv',header=None)
- data = pd.read_csv(path + 'user_behavior.csv',header=None)
- data.columns = ['userID','itemID','behavior','timestamp']
- data['day'] = data['timestamp'] // 86400
- data['hour'] = data['timestamp'] // 3600 % 24
- ## 生成behavior的加权
- data['day_hour'] = data['day'] + data['hour'] / float(24)
- data.loc[data['behavior']=='pv','behavior'] = 1
- data.loc[data['behavior']=='fav','behavior'] = 2
- data.loc[data['behavior']=='cart','behavior'] = 3
- data.loc[data['behavior']=='buy','behavior'] = 1
- max_day = max(data['day'])
- min_day = min(data['day'])
- data['behavior'] = (1 - (max_day-data['day_hour']+2)/(max_day-min_day+2)) * data['behavior']
- item.columns = ['itemID','category','shop','brand']
- user.columns = ['userID','sex','age','ability']
- data = pd.merge(left=data, right=item, on='itemID',how='left')
- data = pd.merge(left=data, right=user, on='userID',how='left')
- return user, item, data
首先还是熟悉的数据导入操作,与前面不同的是这里加入了用户行为权值,通过.loc方法将数值赋值到数据矩阵相应的行。最后使用merge()对用户行为数据、用户数据与商品数据进行拼接,这样一条信息就包含用户(ID、性别、年龄etc)、商品(品牌、价格、店家etc)、行为信息了。
- def get_recall_list(train, targetDay, k=300):
- train_logs = dict()
- if targetDay > max(train['day']):
- for row in train[['userID','itemID','behavior']].values:
- train_logs.setdefault(row[0], dict())
- if row[1] in upward_map:
- train_logs[row[0]].setdefault(upward_map[row[1]],0)
- train_logs[row[0]][upward_map[row[1]]] = max(train_logs[row[0]][upward_map[row[1]]],row[2])
- else:
- user_List_test = set(train.loc[train['day']==targetDay,'userID'])
- train = train[train['day'] < targetDay]
- for row in train[['userID','itemID','behavior']].values:
- if row[0] in user_List_test:
- train_logs.setdefault(row[0], dict())
- if row[1] in upward_map:
- train_logs[row[0]].setdefault(upward_map[row[1]],0)
- train_logs[row[0]][upward_map[row[1]]] = max(train_logs[row[0]][upward_map[row[1]]],row[2])
- for each_user in train_logs:
- sum_value = sum(train_logs[each_user].values())
- if sum_value > 0:
- for each_item in train_logs[each_user]:
- train_logs[each_user][each_item] /= sum_value
- result_logs = dict()
- for u in train_logs:
- result_logs.setdefault(u, list())
- for i in set(train_logs[u].keys()):
- if i in item_dict:
- tmp_list = [ (x[0], train_logs[u][i]*x[1]) for x in item_dict[i]]
- result_logs[u] += tmp_list
- for u in result_logs:
- result_logs[u] = get_unique_inorder([(downward_map[x[0]], x[1]) for x in sorted(result_logs[u], key=lambda x:x[1], reverse=True)
- if x[0] not in train_logs[u]], k=300)
- return result_logs
这部分可以说是代码中的核心部分了,倘若要计算的日期大于最大时间戳,则在训练文件中加入用户ID-空字典的键值对,如果该条信息中用户作出行为,则在itemID-编号字典中找出编号作为用户ID-dict()中,字典的键,之前的行为权值取最大值作为值加入。
若预测天数小于最大时间戳,则取数据矩阵中天数是目标天数以前的数据,取目标天数数据作为测试集(代码示例中并未用到);之后所做的与预测大于最大时间戳的操作相同。
对于训练集[(uesrID,itemID, value),……]中的每个user,首先将其键下所有的值相加,在用各个值除以相加所得的和,这步起到数据归一化的作用。
然后是生成对于每个用户物品最终分值的一步,每个用户所对应的每批产生过行为的商品,将归一化的行为权值乘以矩阵中每个与每个物品的相似度,所得结果就是对这个用户而言,每个商品的兴趣度,以[用户,[(商品,兴趣度)……]]排列。
接着将生成的序列降序排序,同时调用downward_map将编号映射回itemID,使用get_unique_inorder()去重,最终result_logs就是对每个用户来说兴趣度最大的300个商品列表矩阵。
最后是几个格式变换函数:
- def generate_pairs(recall):
- result = []
- for u in recall:
- for i in recall[u]:
- result.append([u,i[0],i[1]])
- return result
- def reshape_recall_to_dataframe(recall):
- result = generate_pairs(recall)
- result = pd.DataFrame(result)
- result.columns = ['userID','itemID','apriori']
- return result
在主函数中,召回矩阵首先从[ [用户,[(商品,兴趣度)……] ] ……]的格式转换为[ [用户,商品,兴趣度]……],再被加注标签,最后写入csv文件:
- #path = './'
- path = '../ECommAI_EUIR_round2_train_20190816/'
- ## The target date(16 means online, 15 means underline test, 14 means underline train)
- targetday = 15
- ## The lenth of recall list, the default is 300
- lenth = 300
- ## The name of generated recall file
- name = 'recall_list_round2_%dday_%dlenth.csv'%(targetday, lenth)
- user, item, data = load_data(path = path)
- #tempory_path = './tempory_file/'
- tempory_path = './'
- f = open(tempory_path + 'upward_map.txt','r')
- upward_map = f.read()
- upward_map = eval(upward_map)
- f.close()
- f = open(tempory_path + 'downward_map.txt','r')
- downward_map = f.read()
- downward_map = eval(downward_map)
- f.close()
- f = open(tempory_path + 'item_Apriori.txt','r')
- tmp = f.read()
- item_dict = eval(tmp)
- f.close()
- recall_logs = get_recall_list(data, targetDay=targetday, k=lenth)
- recall_df = reshape_recall_to_dataframe(recall_logs)
- temp = pd.merge(left=recall_df, right=data[data['day'] == targetday][['userID','itemID','behavior']],
- on=['userID','itemID'], how='left').rename(columns={'behavior':'label'})
- len(set(recall_df['userID']) & set(data[data['day'] == targetday]['userID']))
- len(set(recall_df['userID']))
- recall_df.to_csv(name, index=False)
其中还穿插着一些行为引入的操作;至此,基于apriori的itemCF源码分析就结束了。
给我的感受是整个itemCF中数学统计的原理非常简单,代码的精巧体现在对多维度数据的处理和利用上。
推荐算法_CIKM-2019-AnalytiCup 冠军源码解读的更多相关文章
- 推荐算法_CIKM-2019-AnalytiCup 冠军源码解读_2
最近在为机器学习结合推荐算法的优化方法和数据来源想办法.抱着学习的态度继续解读19-AnalytiCup的冠军源码. 第一部分itemcf解读的连接:https://www.cnblogs.com/m ...
- 量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)(转)
量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python) 原文地址:http://blog.csdn.net/u012234115/article/details/728300 ...
- [算法1-排序](.NET源码学习)& LINQ & Lambda
[算法1-排序](.NET源码学习)& LINQ & Lambda 说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛.但必要的基础还是需要了解掌握. 排 ...
- 为什么不推荐Python初学者直接看项目源码
无论是有没有其他语言的经验,入门Python都很简单.Python拥有简单直观的语法,方便的语法糖,以及丰富的第三方库.只要一个基础的Python教程,大家基本上都能无障碍的入门.在入门之后,很多人对 ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
- AFNetworking 3.0 源码解读 总结
终于写完了 AFNetworking 的源码解读.这一过程耗时数天.当我回过头又重头到尾的读了一篇,又有所收获.不禁让我想起了当初上学时的种种情景.我们应该对知识进行反复的记忆和理解.下边是我总结的 ...
- Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现
一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
随机推荐
- c++ 启发式搜索解决八数码问题
本文对八数码问题 启发式搜索 (C++)做了一点点修改 //fn=gn+hn #include<iostream> #include<queue> #include<st ...
- 【Java】抽象类、接口
什么是抽象类? 特点: - 抽象类几乎普通类一样,除了不能实例化 - 不能实例化不代表没有构造器,依然可以声明构造器,便于子类实例化调用 - 具有抽象方法的类,一定是抽象类 abstract 抽象的 ...
- L - Neko does Maths CodeForces - 1152C 数论(gcd)
题目大意:输入两个数 a,b,输出一个k使得lcm(a+k,b+k)尽可能的小,如果有多个K,输出最小的. 题解: 假设gcd(a+k,b+k)=z; 那么(a+k)%z=(b+k)%z=0. a%z ...
- 【论文研读】强化学习入门之DQN
最近在学习斯坦福2017年秋季学期的<强化学习>课程,感兴趣的同学可以follow一下,Sergey大神的,有英文字幕,语速有点快,适合有一些基础的入门生. 今天主要总结上午看的有关DQN ...
- 6. webRTC
webrtc网上封装的很多,demo很多都是一个页面里实现的,今天实现了个完整的 , A 发视频给 B. 1.) A 方 <!DOCTYPE html> <html id=" ...
- deepin15.11小毛病解决
目录 边缘花屏问题 QQ`Tim头像问题 ssh卡死问题 看直播卡 边缘花屏问题 sudo apt install systemsettings 打开kde系统设置 打开显示与设置,修改如图下,基本上 ...
- 算法笔记刷题2(codeup 1928)
又磕了一晚上,多点测试真的很烦 ,完全不知道错哪里,后来发现是我变量名命名不规范导致自己晕了填错了,其实思路还是对的 我觉得书上的做法也还行,但我不太喜欢用二维数组,所以拿以前写的算天数的程序改装了一 ...
- 算法笔记刷题1(codeup 1934)
准备6月份的拼题甲级中(本来现在这两天就考试了,但是因为疫情的原因延期了) 刚刚开始按算法笔记刷题,今天是探索codeup的第一天. 一开始并没有把多点测试当回事,直到一错再错,心态爆炸... 附上我 ...
- Oracle 11g 精简客户端
通常开发人员会装上一个 oracle客户端,但一般不会在自己的机器上安装Oracle database Oracle 客户端安装体积很大,但是装上去了基本上就用2个功能:TNS配置服务名和sqlplu ...
- JVM原理与深度调优(一)
什么是jvm jvm是java虚拟机 运行在用户态.通过应用程序实现java代码跨平台.与平台无关.实际上是"一次编译,到处执行" 1.从微观来说编译出来的是字节码!去到哪个平台都 ...