news 2026/10/22 17:59:32

用随机森林分类泰坦尼克号:sklearn建模全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用随机森林分类泰坦尼克号:sklearn建模全流程实战

把泰坦尼克号做成一个随机森林分类项目,我一开始觉得有点“杀鸡用牛刀”。但真正跑完之后,我才发现这个组合比想象中更适合用来理解 sklearn 的建模套路:特征工程、交叉验证、超参数调优,每一步都能拿到足够直接的反馈。几行代码下来,你就能得到一个准确率还不错的模型,而且完全不用处理复杂的数据格式。这篇文章就把我实测的完整流程、踩过的坑和调参思路一次性写出来,代码可以直接复制运行。

1. 随机森林和泰坦尼克号这个组合,为什么值得做

1.1 泰坦尼克号数据集到底好在哪

很多初学者一上来就直奔图像、文本这种复杂数据,结果被各种预处理搞得直接劝退。泰坦尼克号数据集所以能成为 Kaggle 入门赛的常青树,原因是它把真实数据处理中会遇到的问题都浓缩在了一个小表格里:有数值型特征(年龄、票价、船舱等级),有类别型特征(性别、登船港、船舱号),有缺失值(Age、Cabin、Embarked),甚至还有那种“看起来能用但实际很容易泄露信息”的字段(姓名、票号)。处理完这些问题,你对“从原始表格到建模输入”这条流水线的理解能直接上一个台阶。

我当时选择用它来跑随机森林,还有一个很实际的考虑:其他模型比如逻辑回归对缺失值、异常值、共线性非常敏感,而随机森林基于决策树的独立生长机制,天然对缺失值不那么致命,可以先用简单的填补策略快速验证整体流程。不是说逻辑回归不好,而是对第一轮建模来说,随机森林能减少很多“为什么结果这么烂”的排查成本。

1.2 随机森林的核心原理和选择逻辑

随机森林的本质是“Bagging + 随机特征选择”的集成学习。它训练出很多棵决策树,每棵树只看到一部分样本(Bootstrap抽样)和一部分特征(比如每次只随机挑选 max_features 个特征找最优分裂),最后通过投票(分类)或平均(回归)汇总结果。这种“集体决策”让模型不会因为单棵树的偏差而抖动,而且天然可以并行训练,所以特别适合作为基准模型。

我在实际项目里选中它的理由有这几个:

  • 几乎不需要做特征缩放。决策树只看分裂阈值,不关心特征的量纲,所以年龄、票价这种数值差异巨大的特征可以直接丢进去,省去标准化步骤。
  • 能输出特征重要性。训练完之后直接通过feature_importances_看哪些特征真正在发力,对后续特征工程有指导意义。
  • 对非线性关系和交互效应友好。如果“女性 + 头等舱”这种组合跟生存率强相关,决策树能自动分裂出来,不需要你手动构造交互项。
  • 不容易过拟合到极致的参数敏感。相比单棵决策树,随机森林只要控制好树的数量和深度,基本不会出现严重过拟合。

热词里的“随机森林回归算法”其实跟这里的分类是同一套机制。区别只在于输出层:回归任务每棵树输出平均值,分类任务每棵树投票。我在实际中做房价预测、销量预测也用同样的调参思路,只是评估指标换成RMSE或者MAE,这里拿泰坦尼克号做分类实战,也完全能迁移。

2. 环境准备与数据预处理

2.1 sklearn 安装和版本选择

如果你是从零开始搭环境,我建议直接用 Anaconda 或者 Python 3.9+ 的虚拟环境,然后跑:

pip install -U scikit-learn pandas numpy matplotlib

“-U”参数会升级到最新版本,避免装到旧版本导致 API 不兼容。一般我在本地用的版本是 scikit-learn 1.2.x 以上,因为新版本对RandomForestClassifier的接口基本稳定,而且cross_validate、GridSearchCV这些工具用起来更顺手。

装完后简单验证一下:

import sklearn print(sklearn.__version__)

如果你网络环境不好,也可以改用:

pip install scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple

这也是我经常使用的做法,速度快很多。

2.2 数据初探:先摸清楚每一列的底细

把数据下载下来之后,不要急着训练,先用 pandas 做一次快速体检:

import pandas as pd df = pd.read_csv("titanic.csv") print(df.shape) print(df.info()) print(df.head())

这里有几列需要特别关注:

  • PassengerId:纯编号,对预测没用,必须删。
  • Name:表面看是人名,但其中包含了 Title(Mr、Mrs、Miss、Master 等),可以提取新特征。
  • Sex:这是个关键的强特征,必须转数值。
  • Age:有缺失值,数值型,后面要处理。
  • SibSp / Parch:兄弟姐妹、父母子女数量,两者可以合并成家庭大小。
  • Ticket:票号,重复性高,通常第一版模型直接丢弃。
  • Fare:票价,数值型,缺失值很少,但有极值。
  • Cabin:缺失率高,建议只保留“是否有记录”。
  • Embarked:登船港,少数缺失,用众数填补。

我先统计一下缺失值比例:

print(df.isnull().sum() / len(df))

实际运行结果通常是:Age 缺失约 20%,Cabin 缺失约 77%,Embarked 缺失约 0.2%。Cabin 缺失这么严重,直接当作普通类别变量塞进模型反而会捣乱,我更倾向于把“有无 Cabin 记录”当成一个特征,来捕捉一种“分配了舱房的人可能信息更完整”的潜在信号。

2.3 特征工程:从原始字段到模型输入

特征工程这一步决定了模型性能的上限,后面调参只是逼近这个上限。我第一版特征工程的做法如下:

from sklearn.model_selection import train_test_split # 1. 提取姓名中的称谓 df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) df['Title'] = df['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare') df['Title'] = df['Title'].replace('Mlle', 'Miss') df['Title'] = df['Title'].replace('Ms', 'Miss') df['Title'] = df['Title'].replace('Mme', 'Mrs') # 2. 家庭大小与是否独自一人 df['FamilySize'] = df['SibSp'] + df['Parch'] + 1 df['IsAlone'] = (df['FamilySize'] == 1).astype(int) # 3. 填补 Age 缺失值(按 Title 分组的中位数) df['Age'] = df.groupby('Title')['Age'].transform(lambda x: x.fillna(x.median())) # 4. 填补 Embarked 缺失值 df['Embarked'] = df['Embarked'].fillna(df['Embarked'].mode()[0]) # 5. Cabin 处理:只保留是否有记录 df['HasCabin'] = df['Cabin'].notna().astype(int) # 6. 年龄分箱,把连续变量离散化 df['AgeBand'] = pd.cut(df['Age'], bins=[0, 16, 32, 48, 64, 100], labels=[0, 1, 2, 3, 4]) # 7. 票价分箱,处理极端值 df['FareBand'] = pd.qcut(df['Fare'].fillna(0), 4, labels=[0, 1, 2, 3], duplicates='drop')

这里我特别想强调年龄缺失值处理。很多人直接拿全体中位数填充,但实际按 Title 分组填充更合理,因为不同身份的人年龄分布差异很大,比如 Master(男孩)和 Mr(成年男性)的年龄中位数完全不同。用一个简单分组统计就能减少系统性偏差。

然后在合并最终特征时,把字符串字段全部映射成数值:

df['Sex'] = df['Sex'].map({'male': 0, 'female': 1}) df['Embarked'] = df['Embarked'].map({'S': 0, 'C': 1, 'Q': 2}) features = ['Pclass', 'Sex', 'Age', 'FamilySize', 'IsAlone', 'HasCabin', 'Title', 'Embarked', 'AgeBand', 'FareBand'] X = df[features] y = df['Survived']

有一点要提醒:Pclass 虽然也是类别,但 1、2、3 的等级排序意义非常明确,转成数值问题不大。Title 这种没有等级含义的类别必须用标签编码或 one-hot,否则模型会误解数字大小关系。实际操作中,我还会用get_dummies或OneHotEncoder处理 Title,具体看模型需求。

2.4 划分训练集和测试集,别忘了分层抽样

划分数据时,我用了分层抽样,确保训练集和验证集的生存率分布与整体一致,防止性别样本偏到某一组里导致验证结果失真:

X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )

stratify=y这行加的代价几乎为零,但能直接避免因为随机划分导致的类别比例失衡。我在实际项目中几乎默认都会带上这个参数。

3. 随机森林模型训练和初版效果

3.1 第一版基准模型,先别急着调参

很多人一上来就想着怎么调参,但正确的第一步是用默认参数跑一个基准模型,记录下训练集和验证集的指标。这样你才知道后面调参到底带来了多少提升。

from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report, roc_auc_score model = RandomForestClassifier(random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_test) y_prob = model.predict_proba(X_test)[:, 1] print("Accuracy:", accuracy_score(y_test, y_pred)) print("AUC:", roc_auc_score(y_test, y_prob)) print(classification_report(y_test, y_pred))

我本地跑出来的初版结果大概是:准确率 0.82 左右,AUC 0.86 左右。这在泰坦尼克号数据集上已经算是一个性价比很高的基线了。你可能会问,为什么默认参数也能取得不错的结果?因为随机森林的集成机制本身就有很强的容错能力,默认的n_estimators=100、max_features='sqrt'已经足够应付这种中小规模表格数据。

3.2 特征重要性:看一下模型在依赖什么

这一点是我最喜欢随机森林的地方。训练完直接打印特征重要性:

import matplotlib.pyplot as plt import numpy as np importance = model.feature_importances_ idx = np.argsort(importance)[::-1] for i in idx: print(f"{features[i]:12s}: {importance[i]:.4f}")

我跑出来的排序一般是:Sex 和 Age 排在最前面,然后是 Fare、Title、FamilySize。Pclass 反而没想象中靠前,这是因为 Pclass 和 Fare、Title 有很强的相关性,重要性被分摊了。理解了这层关系,你就知道为什么不能迷信单一指标,而要结合业务去解读。

3.3 模型预测逻辑初探:decision_path 和预测概率

随机森林的预测逻辑很直观:让每棵树分别判断,然后看哪个类别的票多。但实际操作中,我更常用predict_proba而不是predict,因为概率值可以让你自由调整判断阈值。比如你更关注“把可能幸存的人尽量救出来”还是“避免误把死亡者判为幸存”,不同目标对应不同阈值。这个点后面调参和评估时还会再谈。

如果想看具体某条样本被哪些树判断过,可以用decision_path,但在这种入门级数据集上一般用不到,我觉得先知道 predict 和 predict_proba 的区别就够了。

4. 超参数调优:从 GridSearch 到 RandomSearch

4.1 随机森林超参数,哪些值得优先调

随机森林的超参数不算多,但乱调也浪费时间。我按影响力排序来说明:

  • n_estimators:树的数量。太少会欠拟合,太多会浪费算力,边际收益递减。常见范围 100~500。
  • max_depth:单棵树的最大深度。限制深度能防止单棵树过拟合,但太浅可能损失模型精度。
  • min_samples_split / min_samples_leaf:控制节点分裂条件。一般从 2~10 和 1~5 开始试。这两个参数对控制过拟合非常有效。
  • max_features:每棵树随机选择的特征数。分类问题默认是sqrt(n_features),调大更自由,调小更随机,对稳定性影响较大。
  • bootstrap:默认 True,表示采样的方式。如果你完全关掉它,模型的多样性会变差,一般不建议动。
  • class_weight:如果类别不平衡,可以考虑设'balanced',但泰坦尼克数据集生存率 38% 左右,不算特别失衡,初版可以不调。
  • n_jobs:并行核数,设-1使用所有 CPU。它对模型精度没有影响,但对调参总耗时影响巨大。

我最开始犯过的错误是把所有参数放在一个 GridSearch 里跑,结果搜索空间大到根本等不完。所以正确做法是先粗后细,分层搜索。

4.2 第一步:用 GridSearchCV 固定大方向

先做一轮粗搜索,确定大致范围。代码示例:

from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [100, 200], 'max_depth': [None, 10, 20], 'min_samples_split': [2, 5], 'min_samples_leaf': [1, 2], 'max_features': ['sqrt', 'log2'] } grid = GridSearchCV( RandomForestClassifier(random_state=42), param_grid=param_grid, cv=5, scoring='roc_auc', n_jobs=-1, verbose=1 ) grid.fit(X_train, y_train) print("Best params:", grid.best_params_) print("Best CV AUC:", grid.best_score_)

这里我用scoring='roc_auc'而不是准确率,因为准确率受阈值影响,而 AUC 更能反映模型排序能力,调参时更稳定。cv=5意味着每个参数组合会跑 5 折交叉验证,如果参数组合非常多,耗时也会成倍增长。第一次跑的时候可以把参数范围缩小一点,先出个结果。

运行时间方面,如果你 CPU 核数较多,n_jobs=-1能显著加速。我在四核笔记本上跑这个网格搜索,大概也就一两分钟。热词里有人问“随机森林需要跑多长时间”,其实问题大多出在过大的网格搜索上,而不是随机森林本身。

4.3 第二步:用 RandomizedSearchCV 在更大空间里撒网

粗搜得到一个较优区间之后,再用随机搜索去细化。随机搜索的原理是在参数空间中随机采样固定数量的组合,相比网格搜索,它不会因为某个参数取值多而指数膨胀。

from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint param_dist = { 'n_estimators': randint(50, 500), 'max_depth': [None, 5, 10, 15, 20, 25], 'min_samples_split': randint(2, 10), 'min_samples_leaf': randint(1, 5), 'max_features': ['sqrt', 'log2', 0.3, 0.5], 'bootstrap': [True, False] } random_search = RandomizedSearchCV( RandomForestClassifier(random_state=42), param_distributions=param_dist, n_iter=50, cv=5, scoring='roc_auc', random_state=42, n_jobs=-1 ) random_search.fit(X_train, y_train) print("Best params:", random_search.best_params_) print("Best CV AUC:", random_search.best_score_)

这里n_iter=50表示只尝试 50 个随机组合。我之前在真实项目里用网格搜索,备选组合上万,跑了整整一个晚上;换成随机搜索 100 次后,结果几乎一样,时间缩短到十几分钟。所以在高维参数空间里,RandomizedSearchCV 几乎是必备工具。

4.4 用最佳参数重新训练,并和初版对比

搜索完成之后,用最佳参数重新在全部训练集上训练:

best_model = RandomForestClassifier( n_estimators=300, max_depth=10, min_samples_split=4, min_samples_leaf=2, max_features='sqrt', random_state=42, n_jobs=-1 ) best_model.fit(X_train, y_train) best_pred = best_model.predict(X_test) best_prob = best_model.predict_proba(X_test)[:, 1] print("Tuned Accuracy:", accuracy_score(y_test, best_pred)) print("Tuned AUC:", roc_auc_score(y_test, best_prob))

在我本地的结果中,调参后的 AUC 从 0.86 提升到 0.88 左右,准确率 0.83,提升幅度不是很大。这不是调参没用,而是默认参数已经足够好,特征工程才是上限所在。如果你发现调参后分数不升反降,先检查数据划分是不是出了问题,或者某几个参数组合在交叉验证里选中了一个恰好表现好的局部点。

5. 交叉验证、特征工程和常见问题排查

5.1 从单次划分到 K 折交叉验证

单次划分测试集的结果不够稳定,换一个随机种子就可能有波动。所以我更建议用交叉验证来观察模型的稳定性:

from sklearn.model_selection import cross_validate cv_results = cross_validate( best_model, X, y, cv=5, scoring=['accuracy', 'roc_auc'], return_train_score=True ) print("Val Accuracy: %.3f ± %.3f" % ( cv_results['test_accuracy'].mean(), cv_results['test_accuracy'].std() )) print("Train Accuracy: %.3f ± %.3f" % ( cv_results['train_accuracy'].mean(), cv_results['train_accuracy'].std() ))

如果训练集准确率远高于验证集准确率,说明模型过拟合;如果两者都很低,则可能是欠拟合或特征信息不足。我在跑这个项目时,训练准确率和验证准确率差距大概在 3~5 个百分点,属于比较健康的状态。

5.2 特征工程与调参,哪个更重要

这个项目给我最深的体会是,特征工程带来的提升通常大于超参数调优。比如光是加入Title、FamilySize、HasCabin这几个特征,AUC 就能从 0.82 涨到 0.86。而调参花了好几分钟,AUC 只涨了 0.02。所以我的建议是:先做到特征维度的“从无到有”,再考虑参数的“从有到优”。

热词里出现“遥感随机森林”和“层次聚类”,其实说明随机森林在处理高维、强噪声数据时很受欢迎,遥感分类里经常用它配合光谱和地形特征。这也是随机森林特征重要性好用的一大原因——你不需要做太多前置筛选,模型能站出来告诉你哪些波段有效。思维是相通的。

5.3 常见问题和避坑清单

下面这几个问题,是我在实际运行中频繁遇到或者身边人跑来问我的,整理成速查表:

问题现象可能原因解决办法
训练集准确率接近100%,测试集很差树太多太深,或特征里混入了唯一标识字段(比如 PassengerId、Ticket)限制 max_depth,删掉无意义字段,用交叉验证重新检查
AUC始终卡在 0.8 上不去特征工程太粗糙,缺失值填充方式不合理尝试提取 Title、家庭大小、Cabin 缺失标志等特征
网格搜索跑了半小时还没完参数组合过多或 n_jobs 没设好换 RandomizedSearchCV,缩小参数范围,设置 n_jobs=-1
每次运行结果不一样没有固定 random_state,或数据划分不稳定给模型和 train_test_split 都设置 random_state,并记录
准确率和 AUC 都很低数据预处理阶段出了问题,比如标签没有提出来或者数据类型有误先检查 y 和 X 的 shape 及类型,再检查缺失值填充后的分布
模型效果不错但无法解释树太多,业务逻辑不透明画单棵决策树或看重要特征排序,按业务逻辑验证合理性

5.4 随机森林运行时间和性能优化

热词里有个很朴素的问题:随机森林需要跑多长时间?其实随机森林的训练复杂度大约是 O(n_estimators * m * log n),m 是样本量。对泰坦尼克这种只有几百条样本的数据集,跑几百棵树也就是几秒钟的事。真正耗时的是调参搜索。优化思路有这几个:

  • 让 n_estimators 从 100 开始,如果验证分数涨幅不明显就不再加树。
  • 用warm_start=True增量训练,先跑小规模,再逐步增加树数量。
  • n_jobs=-1并行充分利用多核。
  • 不要用过度精确的网格搜索,优先考虑随机搜索配合少量迭代。

6. 结果评估、模型解释与最后的余量扩展

6.1 不只是准确率:AUC、混淆矩阵和类别概率

泰坦尼克号这个例子,准确率的参考价值其实有限。38% 的幸存率意味着哪怕你全都预测死亡,准确率也有 62%。所以我会同时看 AUC、混淆矩阵、精确率和召回率:

from sklearn.metrics import confusion_matrix, precision_recall_curve cm = confusion_matrix(y_test, y_pred) print(cm) # 观察不同阈值下的精确率-召回率表现 precision, recall, thresholds = precision_recall_curve(y_test, best_prob)

混淆矩阵可以直观告诉你模型犯错的类型:把幸存者判成死亡还是把死亡者判成幸存。在现实场景里这两类错误的代价不一样,比如医疗筛查更看重召回率,风控更看重精确率。随机森林通过 predict_proba 提供概率,你可以根据需要调整阈值,而不是死板地默认用 0.5。

6.2 从概率到决策:调整阈值的心得

我常常用泰坦尼克这个例子来解释阈值调整。如果你把阈值从 0.5 降到 0.3,那么更多“有一定幸存可能”的人会被判为幸存,召回率提高了,但精确率会下降。如果你做的任务是“尽可能找出所有可能幸存的人”,这个调整就是合理的。

在实际项目中,我通常会用precision_recall_curve找到曲线拐点,再结合业务成本选定阈值。拿这个项目练手,正好可以建立“模型输出概率 → 业务决策”的完整思维链路,而不是只停留在 sklearn 的 API 调用上。

6.3 随机森林后续可以怎么扩展

这个项目做完之后,几个自然的扩展方向是:

  • 把随机森林换成梯度提升树(比如HistGradientBoostingClassifier),往往能再提升一点点精度,同时训练更快。
  • 把随机森林做完的特征重要性作为筛选器,再喂给逻辑回归,用线性模型获得强解释性。
  • 加入更多从原始数据里挖掘的交叉特征,比如“女性 + 高票舱 + 家庭小”这种组合,让森林更容易找到强规则。
  • 尝试使用 Stacking,把随机森林、逻辑回归、朴素贝叶斯的结果做融合,理解模型融合的实用性。

热词里提到的“sklearn 层次聚类”虽然跟本项目分类任务不是一类,但如果你想把泰坦尼克乘客特征做无监督分组,看看有没有自然聚类结构,也是挺有意思的探索。同一套 sklearn 学习经验完全可以平滑迁移过去。

7. 写在最后的一点经验

经过这一整轮“随机森林 + Sklearn + 泰坦尼克号”的实战,我最强烈的感受是:这个项目最适合的用法,不是拿它去竞赛刷榜,而是把它当作一个观察机器学习全流程的标本。你能看到数据预处理如何影响上限,看到调参如何逼近上限,看到评估指标如何影响选择,也能看到特征工程和调参在时间成本上的巨大差异。

如果你是自己练手,我建议照着这篇文章的流程从零写一遍,不要复制粘贴就完事。每写一步,停下来问问自己:这个特征到底在表达什么信息?缺失值到底该怎么填才不泄露信息?AUC 变高的背后是排序能力变强,还是某个特征群恰好被吃透了?

等你完整跑通了,再回到真实工作中处理销售预测、客户流失或者风控评分时,你会发现手里那把随机森林的刀,已经比想象中锋利得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 17:59:27

IE、Firefox、Chrome 浏览器兼容差异与实战解决方案

1. 浏览器三巨头的差异到底在哪搞前端或者做企业内网系统的朋友,大概率都遇到过这种场景:同一个页面,在 Chrome 上跑得好好的,到了 Firefox 上某个动画就卡住了,再换到 IE 上直接白屏。这不是代码写得烂,而…

作者头像 李华
网站建设 2026/10/16 21:04:19

分布式计算框架优化实战:从数据倾斜到小文件治理

上个月我们团队接到一个棘手的任务:核心报表链路每天跑一次,耗时从年初的半小时慢慢涨到了两个多小时,业务方每天早上都在群里催数。一开始大家习惯性想加资源,但加了executor、扩了队列,效果也就那样,成本…

作者头像 李华
网站建设 2026/10/22 17:59:26

Oracle汉字转拼音PL/SQL包:从GBK到UTF8的完整解决方案

简介:在Oracle数据库中,将汉字批量转成拼音是数据检索和索引优化时的常见需求。这套PL/SQL包面向数据库开发与数据分析人员,支持UTF8编码,可直接用于分词检索、拼音码排序、前端模糊搜索等场景。压缩包体积仅一百五十六KB&#xf…

作者头像 李华
网站建设 2026/10/16 8:24:04

C语言TCP网络通信编程实战:从三次握手到粘包处理

简介:基于TCP的客户/服务器通信程序大作业,采用C语言实现,覆盖注册、登陆、单聊、私聊、在线人数列表、退出等完整功能,以有连接服务为主、无连接服务为辅,结合事件对象I/O管理完成通信调度,适合计算机网络…

作者头像 李华
网站建设 2026/10/13 22:51:13

夜间灯光数据校正全流程:从DMSP-OLS到NPP-VIIRS的多年面板构建

1. 这套夜间灯光数据,到底解决了什么问题 第一次拿到1992-2024年跨年度的夜间灯光数据集时,说实话我的第一反应不是兴奋,是头大。资料里厚厚一摞文件告诉我,这批栅格数据来自DMSP-OLS和NPP-VIIRS两代传感器,中间还隔了…

作者头像 李华
网站建设 2026/10/13 7:40:45

JSP+Servlet实战:从零搭建蛋糕店售卖网站,吃透JavaWeb核心

简介:这份资源是一套基于JSP与Servlet的JavaWeb蛋糕店售卖网站完整项目源码,面向正在学习JavaWeb开发、需要课程设计或毕业设计参考的初学者与进阶学生。项目在IDEA 2017.3.5下开发,部署于Tomcat 8.5.35,数据库采用MySQL 5.7&…

作者头像 李华