此文章内容记录学习 JUC 时的各种问题问题虚假唤醒​​虚假唤醒​​是多线程编程中的一个典型问题，它是指线程在未被明确通知（如 signal() 或 signalAll()）的情况下从等待状态中唤醒。这可能导致程序在条件未满足时继续执行，从而引发逻辑错误为什么会发生虚假唤醒？​​底层操作系统机制​​操作系统底层的条件变量实现（如 POSIX 的 pthread_cond_wait()）允许在无显式信号时唤醒线程（通常出于性能考虑）。Java 作为跨平台语言无法完全避免这一问题。​​中断和超时机制​​当等待的线程被外部中断（interrupt()）或达到超时时间时，也可能意外唤醒。class SharedResource { private int item = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); // 生产者方法：非安全实现 public void produce() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item != 0) { condition.await(); // 可能虚假唤醒后继续执行 } item++; System.out.println("生产: " + item); condition.signal(); } finally { lock.unlock(); } } // 消费者方法：非安全实现 public void consume() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item == 0) { condition.await(); // 可能虚假唤醒后继续执行 } System.out.println("消费: " + item); item--; condition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) { SharedResource share = new SharedResource(); for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Producer" + i).start(); } for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.consume(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Consumer" + i).start(); } } }可以看到已经产生了虚假唤醒解决方案使用while代替ifclass SafeResource { private int value = 0; private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); // 安全的生产者 public void safeIncrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 0) { condition.await(); } value++; System.out.println(Thread.currentThread().getName() + " 安全生产后: " + value); condition.signalAll(); } finally { lock.unlock(); } } // 安全的消费者 public void safeDecrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 1) { condition.await(); } value--; System.out.println(Thread.currentThread().getName() + " 安全消费后: " + value); condition.signalAll(); } finally { lock.unlock(); } } }死锁死锁是多线程编程中的一种严重问题，指​​两个或多个线程永久阻塞​​，每个线程都在等待其他线程释放资源的状态。死锁产生的四个必要条件​​互斥条件​​：资源只能被一个线程占用​​持有等待​​：线程持有资源同时等待其他资源​​不可剥夺​​：资源不能被强制抢占​​循环等待​​：多个线程形成资源等待环示例public class DeadLockExample { public static void main(String[] args) { Object a = new Object(); Object b = new Object(); new Thread(() -> { synchronized (a) { System.out.println("获取到了锁 a, 尝试获取锁 b"); try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b) { System.out.println("获取到了锁 b"); } } }, "A").start(); new Thread(() -> { synchronized (b) { System.out.println("获取到了锁 b, 尝试获取锁 a"); synchronized (a) { System.out.println("获取到了锁 a"); } } }, "B").start(); } }获取到了锁 a, 尝试获取锁 b 获取到了锁 b, 尝试获取锁 a解决方案从四个必要条件入手破坏请求条件破坏不可剥夺条件破坏互斥条件破坏循环等待条件练习创建四个线程，并按照 D -> B -> C -> A 的顺序执行class Control { private int flag = 0; private final int MAX_NUMBER = 4; private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void wait(int target) throws InterruptedException { lock.lock(); try { while(flag % MAX_NUMBER != target - 1) { condition.await(); } flag++; System.out.println(Thread.currentThread().getName()); condition.signalAll(); } finally { lock.unlock(); } } } public class Test01 { public static void main(String[] args) { Control control = new Control(); new Thread( () -> { try { control.wait(4); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "A" ).start(); new Thread( () -> { try { control.wait(2); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "B" ).start(); new Thread( () -> { try { control.wait(3); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "C" ).start(); new Thread( () -> { try { control.wait(1); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "D" ).start(); } }输出D B C A
练手项目开源链接前端 github后端github项目介绍多功能可协同云相册是一个创新的图片管理与分享平台，具有以下特色功能：多空间管理：支持公共空间、私人空间和协作工作空间协同创作：邀请他人共同管理相册内容AI增强：内置AI图像处理功能，支持图像扩展等操作智能采集：管理员可通过关键词自动采集网络图片资源技术栈后端基础框架：Java 17 / Spring Boot 2.x持久层：MySQL / MyBatis-Plus缓存方案：Redis + Caffeine 多级缓存安全框架：Sa-Token文件存储：腾讯云COSAPI文档：Knife4j前端核心框架：Vue 3 + TypeScript状态管理：Pinia路由管理：Vue RouterUI组件库：Ant Design Vue项目亮点采用 Sa-Token RBAC 进行权限控制图片文件的高效存储及压缩Redis + Caffeine 实现多级缓存,显著提高加载速度项目结合 AI大模型 进行图片的扩图多维度的可视化数据分析及搜索~ShardingSphere 动态分表~基于 WebSocket 的团队实时编辑图片网络图片采集未来计划深度整合AI，实现AI分类、打标签、审核等完善权限系统完善VIP系统新增存储桶渠道，目前计划加入CloudFlare R2加入点赞及浏览功能部分网站截图
去年年末时候接触到了某平台的夸克推广活动，就是别人转存你的链接你能从中受益，于是变有了这个项目READMEQuarkBot⚠️ 该项目已经失效因 Gewechat 框架已停止服务，所以此项目已无法正常使用基于 Gewechat 框架二次开发一款支持影视搜索及转存分享的多功能机器人🌟 核心功能🎥 影视/短剧搜索：基于个人聚合的接口和数据库快速查找全网影视资源。🔑 夸克功能集成：检测 Cookie 有效性。检测夸克链接有效性。转存资源并生成分享链接并推送。监测转存更新。📦 资源管理：支持通过机器人口令快速转存截图项目分析菜单该项目主要利用了 Gewechat 框架接收发信息的功能,菜单功能如下// 机器人命令 String newMsg = """ {{name}} {{description}} {{link}} """; String menu = """ 欢迎使用影视资源机器人! """; String adminMenu = """ 管理员命令 ---------------------- #ping #开启/关闭提醒 #更新群组列表 #更新ck 你的COOKIE #更新文件夹id 你的文件夹id 下面请使用空格隔开 ---------------------- 如果完结为 false 的话，则自动开启更新 #提交 名字 描述 链接 (是否完结 true|false) #转存 名字 描述 链接 (是否完结 true|false) #提交并群发 名字 描述 链接 (是否完结 true|false) #转存并群发 名字 描述 链接 (是否完结 true|false) #更新 你自己的分享链接 新的分享链接(没有则写 无 ) (是否完结 true|false) ----------------------- """;因为项目的初衷是自用，所以写的简陋了些，直接将菜单项写死了搜索这个功能还是挺重要的，因为要实现搜剧，这个功能是必不可少的private CommonVO<List<Map<String, String>>> baseSearch(String name,String type) { List<Map<String, String>> data = Collections.synchronizedList(new ArrayList<>()); // 从 Redis 中获取缓存的 list 列表 List<Map<String, String>> o = (List<Map<String, String>>) redisTemplate.opsForHash().get("search-name-list", name); if (o != null && !o.isEmpty()) { log.info("命中缓存 search-name-list"); return CommonVO.OK(o); } // 从数据库中查询 List<QuarkEntity> list = quarkMapper.selectList( new QueryWrapper<QuarkEntity>() .like("name", name) .eq("valid", true) ); List<QuarkEntity> listDuanJu = new ArrayList<>(); if ("duanju".equals(type)) { List<QuarkEntity> duanjuList = duanjuMapper.selectList( new QueryWrapper<DuanjuEntity>().like("name", name) ).stream().map(duanju -> { try { return BeanConverter.convert(duanju, QuarkEntity.class); } catch (Exception e) { log.error("Bean转换失败", e); } return new QuarkEntity(); }).toList(); listDuanJu.addAll(duanjuList); } if (!listDuanJu.isEmpty()) { // 使用CountDownLatch等待异步任务完成 CountDownLatch latch = new CountDownLatch(1); threadExecutor.execute(() -> { try { List<Integer> idsToDelete = new ArrayList<>(); Iterator<QuarkEntity> iterator = listDuanJu.iterator(); while (iterator.hasNext()) { QuarkEntity entity = iterator.next(); try { String checked = quark.checkByUrl(entity.getUrl(), entity.getPassword()); if (StrUtil.isBlank(checked)) { // 收集需要删除的ID idsToDelete.add(entity.getId()); iterator.remove(); } } catch (IOException e) { log.error("检查URL失败，实体ID: {}, 错误: {}", entity.getId(), e.getMessage(), e); } } // 批量删除失效记录 if (!idsToDelete.isEmpty()) { try { duanjuMapper.delete( new QueryWrapper<DuanjuEntity>() .in("id", idsToDelete) ); log.info("成功删除 {} 条失效记录", idsToDelete.size()); } catch (Exception e) { log.error("批量删除失效记录失败: {}", e.getMessage(), e); } } } finally { latch.countDown(); } }); try { // 等待异步任务完成，设置超时时间 if (!latch.await(30, TimeUnit.SECONDS)) { log.warn("URL检查任务超时"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("等待URL检查任务被中断", e); } } if (!list.isEmpty()) { // 使用CountDownLatch等待异步任务完成 CountDownLatch latch = new CountDownLatch(1); threadExecutor.execute(() -> { try { List<QuarkEntity> entitiesToUpdate = new ArrayList<>(); Iterator<QuarkEntity> iterator = list.iterator(); while (iterator.hasNext()) { QuarkEntity entity = iterator.next(); try { String checked = quark.checkByUrl(entity.getUrl(), entity.getPassword()); if (StrUtil.isBlank(checked)) { // 收集需要更新的实体 entitiesToUpdate.add(entity.setValid(false)); iterator.remove(); } } catch (IOException e) { log.error("检查URL失败，实体ID: {}, URL: {}, 错误: {}", entity.getId(), entity.getUrl(), e.getMessage(), e); } } // 批量更新失效记录 if (!entitiesToUpdate.isEmpty()) { try { // 使用MyBatis-Plus的批量更新方法 for (int i = 0; i < entitiesToUpdate.size(); i += 1000) { int endIndex = Math.min(i + 1000, entitiesToUpdate.size()); List<QuarkEntity> batch = entitiesToUpdate.subList(i, endIndex); // 使用MyBatis-Plus的Service层批量更新 quarkMapper.updateBatchById(batch); } log.info("成功更新 {} 条记录为无效状态", entitiesToUpdate.size()); } catch (Exception e) { log.error("批量更新记录状态失败: {}", e.getMessage(), e); } } } finally { latch.countDown(); } }); try { // 等待异步任务完成，设置合理的超时时间 if (!latch.await(30, TimeUnit.SECONDS)) { log.warn("URL检查任务超时"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("等待URL检查任务被中断", e); } } list.addAll(listDuanJu); log.info("正在进行全网搜索: 【{}】",name); data = this.searchAll(name).getData(); HashMap<String, String> map = new HashMap<>(); sortListByKeyCount(data); if (!list.isEmpty()) { list.forEach(item -> map.put(item.getName(),item.getUrl())); data.add(map); moveMapToFirst(data,map); } // 将查询结果存入 Redis 缓存 redisTemplate.opsForHash().put("search-name-list", name, data); redisTemplate.expire("search-name-list", 30, TimeUnit.MINUTES); return CommonVO.OK(data); }这个即后端最重要的搜索接口，采用了Redis作为缓存。先去查询数据库中是否有匹配的结果，然后对其进行 失效性检测 若失效在进行 全网搜全网搜接口这个其实就是聚合了各种夸克的搜索接口，返回的结果由我做一个整合，并进行 失效性检测，具体代码可以去 QuarkBot/search-api at main · Liner03/QuarkBot 查看夸克工具类在转存及分享以及附带文件(广告)分享的时候必然少不了对夸克接口的分享，所以在我参考了其他同类项目，如 quark-auto-save/** * @Author Lin. * @Date 2024/12/5 * 夸克网盘的一些方法 */ @Slf4j @Component public class Quark { @Value("${quark.config.cookie}") private String cookie; @Autowired private OkhttpUtil okhttpUtil; @Autowired @Lazy private FileSaveService fileSaveService; @Autowired @Lazy private LinkService linkService; @Autowired private TempData data; @Getter private final String baseUrl = "https://drive-m.quark.cn"; @Autowired @Lazy private MessageService messageService; @Value("${quark.config.save-path-fid}") private String savePath; @Autowired private UserSettingMapper userSettingMapper; @Getter private final String rootDir = "bot测试"; private Notify notify; @Autowired private Environment environment; @PostConstruct private void init() { renameFile(savePath,rootDir); } public HashMap<String,String> header() { HashMap<String, String> map = new HashMap<>(); map.put("Cookie", cookie); map.put("Content-Type", "application/json"); map.put("User-Agent", Common.randomUA()); return map; } /** * * @return boolean * @author Lin. * 验证cookie的有效性 */ public boolean isValidCookie(String cookie) throws IOException { String url = "https://pan.quark.cn/account/info?fr=pc&platform=pc"; HashMap<String, String> header = header(); header.put("Cookie", cookie); JSONObject json = okhttpUtil.getJsonObject(url, header, okhttpUtil.getOkHttpClient()); return !StrUtil.hasEmpty(json.getString("data")); } public boolean isValidCookie() throws IOException { String url = "https://pan.quark.cn/account/info?fr=pc&platform=pc"; JSONObject json = okhttpUtil.getJsonObject(url, header(), okhttpUtil.getOkHttpClient()); return !StrUtil.hasEmpty(json.getString("data")); } /** * @return String stoken * @author Lin. * 验证资源是否失效 */ public String checkByUrl(String panUrl,String passcode, String cookie) throws IOException { String pwdId = getPwdId(panUrl); if (pwdId == null) { log.warn("错误的url传入{}",panUrl); return null; } // 判断 cookie 有效性 boolean validCookie = false; if(StrUtil.isBlankIfStr(cookie)){ validCookie = isValidCookie(); } else { validCookie = isValidCookie(cookie); } if (!validCookie) { UserSettingEntity entity = userSettingMapper.selectById(1); if (entity.getCookieValid()) { entity.setCookieValid(false); userSettingMapper.updateById(entity); String remindMsg = this.data.getDataByString("remindMsg"); if (StrUtil.isNotBlank(remindMsg) && Boolean.parseBoolean(remindMsg)) messageService.send_to_user(this.data.getDataByString("adminWxid"),"COOKIE已失效!"); log.error("COOKIE失效!"); notify.send("COOKIE失效!"); } return null; } passcode = passcode == null ? "" : passcode; String url = baseUrl + "/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc"; HashMap<String, String> body = new HashMap<>(); body.put("pwd_id", pwdId); body.put("passcode", passcode); JSONObject jsonObject = okhttpUtil.postJsonObject(url, header(), body, okhttpUtil.getOkHttpClient()); if (jsonObject != null && !jsonObject.isEmpty()) { if (jsonObject.getInteger("status") != 200) { log.warn("pwd_id: {}, 错误信息: {}", pwdId, jsonObject.getString("message") ); return null; } return jsonObject.getJSONObject("data").getString("stoken"); } return null; } public String checkByUrl(String panUrl,String passcode) throws IOException { return this.checkByUrl(panUrl, passcode, null); } /** * 获取当前分享下大的 fid 下的所有json数据 pdirFid 初始为0 */ public List<JSONObject> getFileList(String pwdId, String stoken, String pdirFid) { return this.getFileList(pwdId, stoken, header().get("Cookie"), pdirFid); } public List<JSONObject> getFileList(String pwdId, String stoken, String cookie, String pdirFid) { List<JSONObject> listMerge = new ArrayList<>(); int page = 1; int fetchShare = 0; try { while (true) { // 构建请求参数 String url = baseUrl + "/1/clouddrive/share/sharepage/detail"; // 构建查询参数 Map<String, String> queryParams = new HashMap<>(); queryParams.put("pr", "ucpro"); queryParams.put("fr", "pc"); queryParams.put("pwd_id", pwdId); queryParams.put("stoken", stoken); queryParams.put("pdir_fid", pdirFid); queryParams.put("force", "0"); queryParams.put("_page", String.valueOf(page)); queryParams.put("_size", "50"); queryParams.put("_fetch_banner", "0"); queryParams.put("_fetch_share", String.valueOf(fetchShare)); queryParams.put("_fetch_total", "1"); queryParams.put("_sort", "file_type:asc,updated_at:desc"); // 将查询参数拼接到 URL String fullUrl = url + "?" + buildQueryString(queryParams); // 发送请求 HashMap<String, String> header = header(); header.put("cookie", cookie); JSONObject response = okhttpUtil.getJsonObject(fullUrl, header, okhttpUtil.getOkHttpClient()); if (response == null) { break; } // 获取当前页的数据列表 JSONObject data = response.getJSONObject("data"); if (data != null && data.getInteger("is_owner") == 1) { log.error("❌网盘中已经存在该文件，无需再次转存"); return new ArrayList<>(); } JSONArray currentList = response.getJSONObject("data").getJSONArray("list"); if (currentList == null || currentList.isEmpty()) { break; } if (data != null && response.getJSONObject("metadata").getInteger("_total") == 1) { List<JSONObject> list = currentList.toList(JSONObject.class); if (list.get(0).getBoolean("dir")) { listMerge.add(list.get(0)); return listMerge; }else { listMerge.addAll(list); return listMerge; } } // 添加到合并列表中 List<JSONObject> list = currentList.toList(JSONObject.class); for (JSONObject json:list) { if (json.getBoolean("dir")) { listMerge.add(json); }else { listMerge.add(json); } } // 检查是否获取完所有数据 int total = response.getJSONObject("metadata").getInteger("_total"); if (listMerge.size() >= total) { break; } page++; } return listMerge; } catch (Exception e) { log.error("获取文件列表异常", e); return null; } } /** * 获取短剧的基础信息 缩略图 集数 */ public HashMap<String ,Object> getUrlInfo(String pwdId, String stoken, String cookie, String pdirFid) { int retry = 5; int fetchShare = 0; boolean wrapDir = false; HashMap<String, Object> map = new HashMap<>(); try { while (retry > 0) { // 构建请求参数 String url = baseUrl + "/1/clouddrive/share/sharepage/detail"; // 构建查询参数 Map<String, String> queryParams = new HashMap<>(); queryParams.put("pr", "ucpro"); queryParams.put("fr", "pc"); queryParams.put("pwd_id", pwdId); queryParams.put("stoken", stoken); queryParams.put("pdir_fid", pdirFid); queryParams.put("force", "0"); queryParams.put("_page", "1"); queryParams.put("_size", "200"); queryParams.put("_fetch_banner", "0"); queryParams.put("_fetch_share", String.valueOf(fetchShare)); queryParams.put("_fetch_total", "1"); queryParams.put("_sort", "file_type:asc,updated_at:desc"); // 将查询参数拼接到 URL String fullUrl = url + "?" + buildQueryString(queryParams); // 发送请求 HashMap<String, String> header = header(); header.put("cookie", cookie); JSONObject response = okhttpUtil.getJsonObject(fullUrl, header, okhttpUtil.getOkHttpClient()); if (response == null) { retry--; continue; } // 获取当前页的数据列表 JSONObject data = response.getJSONObject("data"); JSONArray list = data.getJSONArray("list"); JSONObject[] listArray = list.toArray(JSONObject.class); if (!wrapDir) { for(JSONObject json: listArray) { if (json.getBoolean("dir")) { pdirFid = json.getString("fid"); wrapDir = true; break; } } } JSONObject metadata = response.getJSONObject("metadata"); JSONArray currentList = data.getJSONArray("list"); if (currentList == null || currentList.isEmpty()) { retry--; continue; } JSONObject[] array = currentList.toArray(JSONObject.class); String total = metadata.getString("_total"); String thumb = null; for (JSONObject json:array) { if ("0.jpg".equals(json.getString("file_name")) || "0.png".equals(json.getString("file_name"))) { thumb = json.getString("preview_url"); break; } } map.put("thumb", thumb); map.put("total", total); retry--; } return map; } catch (Exception e) { log.error("获取文件列表异常", e); return map; } } /** * @param url 分享url * @param savePathFid 转存文件夹id * @param expireDay 过期天数 默认 天 * @param b 是否自动删除 * @return String 分享链接 */ public String saveAndShareAndDel(String url, String savePathFid,String title,String expireDay,boolean b) throws IOException, InterruptedException { log.info("开始转存..."); String stoken = this.checkByUrl(url, null); if (StrUtil.isBlankIfStr(stoken)) { log.warn("❌链接已失效！"); return null; } String pwdId = getPwdId(url); List<JSONObject> list = this.getFileList(pwdId, stoken, "0"); if (list == null) return null; // 如果是自己的 url 则直接返回 if (list.isEmpty()) return url; int dirCount = 0; int fileCount = 0; ArrayList<String> fidList = new ArrayList<>(); ArrayList<String> shareFidTokenList = new ArrayList<>(); for (JSONObject jsonObject : list) { if (jsonObject.getBoolean("dir")) { dirCount++; }else { fileCount++; } shareFidTokenList.add(jsonObject.getString("share_fid_token")); fidList.add(jsonObject.getString("fid")); } log.info("⭕文件总数: {},文件数: {},文件夹数: {}",list.size(),fileCount,dirCount); if (StrUtil.isBlankIfStr(savePathFid)) { log.error("保存目录ID不合法，请重新获取，如果无法获取，请输入0作为文件夹ID"); return null; } String saveAsTopFids = fileSaveService.saveSharedFiles(pwdId, stoken, fidList, shareFidTokenList, savePathFid); if (StrUtil.isBlankIfStr(saveAsTopFids)) return null; ArrayList<String> fids = new ArrayList<>(); fids.add(saveAsTopFids); if (StrUtil.isEmptyIfStr(expireDay)) expireDay = "2"; return this.share(fids,title,expireDay,b); } private final Lock lock = new ReentrantLock(); public String saveAndShareAndDelCustomize(String url, String savePathFid, String adFid, String cookie, String title, String expireDay, boolean delFlag) throws IOException, InterruptedException { if (lock.tryLock()) { try { log.info("开始转存..."); String stoken = this.checkByUrl(url, null, cookie); if (StrUtil.isBlankIfStr(stoken)) { log.warn("❌链接已失效！"); return null; } String pwdId = getPwdId(url); List<JSONObject> list = this.getFileList(pwdId, stoken, cookie, "0"); if (list == null) return null; // 如果是自己的 url 则直接返回原始 url if (list.isEmpty()) return url; int dirCount = 0; int fileCount = 0; ArrayList<String> fidList = new ArrayList<>(); ArrayList<String> shareFidTokenList = new ArrayList<>(); for (JSONObject jsonObject : list) { if (jsonObject.getBoolean("dir")) { dirCount++; } else { fileCount++; } shareFidTokenList.add(jsonObject.getString("share_fid_token")); fidList.add(jsonObject.getString("fid")); } log.info("⭕文件总数: {}, 文件数: {}, 文件夹数: {}", list.size(), fileCount, dirCount); if (StrUtil.isBlankIfStr(savePathFid)) { log.error("保存目录ID不合法，请重新获取，如果无法获取，请输入0作为文件夹ID"); return null; } String saveAsTopFids = fileSaveService.saveSharedFiles(pwdId, stoken, fidList, shareFidTokenList, savePathFid, cookie); if (StrUtil.isBlankIfStr(saveAsTopFids)) return null; ArrayList<String> fids = new ArrayList<>(); fids.add(saveAsTopFids); if (StrUtil.isEmptyIfStr(expireDay)) expireDay = "2"; return this.share(fids, adFid, title, cookie, expireDay, delFlag); } finally { lock.unlock(); } } else { log.warn("转存任务已在执行，跳过本次请求"); } return null; } public String saveAndShare(String url, String savePathFid,String title) throws IOException, InterruptedException { return this.saveAndShareAndDel(url,savePathFid,title,null,true); } /** * 分享文件 默认只有1天 * expired_type 1:永久 2:1天 3:7天 4:30天 */ public String share(List<String> pFidList,String title,String expireDay,boolean b) throws IOException, InterruptedException { log.info("开始生成分享链接"); String property = environment.getProperty("quark.config.ad-fid"); pFidList.add(property); String url = baseUrl + "/1/clouddrive/share?pr=ucpro&fr=pc&uc_param_str"; HashMap<String, Object> data = new HashMap<>(); data.put("fid_list",pFidList); data.put("title",title); data.put("url_type","1"); data.put("expired_type",expireDay); data.put("passcode",""); JSONObject jsonObject = okhttpUtil.postJsonObject(url, header(), data, okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) return null; String taskId = jsonObject.getJSONObject("data").getString("task_id"); url = baseUrl + "/1/clouddrive/task?pr=ucpro&fr=pc&uc_param_str&retry_index=0&task_id=" + taskId; int retry = 10; while (retry > 0) { retry++; Thread.sleep(200 + new Random().nextInt(500)); jsonObject = okhttpUtil.getJsonObject(url, header(), okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) continue; if (StrUtil.isBlankIfStr(jsonObject.getJSONObject("data").getString("share_id"))) continue; break; } Thread.sleep(200 + new Random().nextInt(500)); String shareId = jsonObject.getJSONObject("data").getString("share_id"); url = baseUrl + "/1/clouddrive/share/password?pr=ucpro&fr=pc&uc_param_str"; data.clear(); data.put("share_id",shareId); jsonObject = okhttpUtil.postJsonObject(url, header(), data, okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) return null; Thread.sleep(200 + new Random().nextInt(500)); String shareUrl = jsonObject.getJSONObject("data").getString("share_url"); log.info("分享链接: {}",shareUrl); // 加入定时删除队列 pFidList.remove(property); if (b) linkService.createLinks(pFidList); return shareUrl; } public String share(List<String> pFidList, String adFid, String title, String cookie, String expireDay, boolean delFlag) throws IOException, InterruptedException { log.info("开始生成分享链接"); if (adFid != null) pFidList.add(adFid); String url = baseUrl + "/1/clouddrive/share?pr=ucpro&fr=pc&uc_param_str"; HashMap<String, Object> data = new HashMap<>(); data.put("fid_list", pFidList); data.put("title", title); data.put("url_type", "1"); data.put("expired_type", expireDay); data.put("passcode", ""); HashMap<String, String> header = header(); header.put("Cookie", cookie); JSONObject jsonObject = okhttpUtil.postJsonObject(url, header, data, okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) return null; String taskId = jsonObject.getJSONObject("data").getString("task_id"); url = baseUrl + "/1/clouddrive/task?pr=ucpro&fr=pc&uc_param_str&retry_index=0&task_id=" + taskId; int retry = 10; while (retry > 0) { retry--; Thread.sleep(200 + new Random().nextInt(500)); jsonObject = okhttpUtil.getJsonObject(url, header, okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) continue; if (StrUtil.isBlankIfStr(jsonObject.getJSONObject("data").getString("share_id"))) continue; break; } Thread.sleep(200 + new Random().nextInt(500)); String shareId = jsonObject.getJSONObject("data").getString("share_id"); url = baseUrl + "/1/clouddrive/share/password?pr=ucpro&fr=pc&uc_param_str"; data.clear(); data.put("share_id", shareId); jsonObject = okhttpUtil.postJsonObject(url, header, data, okhttpUtil.getOkHttpClient()); if (jsonObject.isEmpty() || jsonObject.getInteger("status") != 200) return null; Thread.sleep(200 + new Random().nextInt(500)); String shareUrl = jsonObject.getJSONObject("data").getString("share_url"); log.info("分享链接: {}", shareUrl); HashMap<String, Object> map = getUrlInfo(getPwdId(shareUrl), checkByUrl(shareUrl,null), cookie, "0"); this.data.setMapData(shareUrl, map); if (adFid != null) pFidList.remove(adFid); if (delFlag) linkService.createLinks(pFidList, cookie); return shareUrl; } public String share(List<String> pFidList,String title) throws IOException, InterruptedException { return this.share(pFidList,title,"2",true); } /** * 更新资源 */ public String updateSave(String pwdId, String stoken, String pFid,String fid,String shareToken) { return fileSaveService.saveSharedFiles(pwdId, stoken, Collections.singletonList(fid), Collections.singletonList(shareToken), pFid); } /** * 构建查询字符串 */ private String buildQueryString(Map<String, String> params) { return params.entrySet().stream() .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)) .collect(Collectors.joining("&")); } public String getPwdId(String url) { String pattern = "(?<=s/)([^#]+)"; Pattern compiledPattern = Pattern.compile(pattern); Matcher matcher = compiledPattern.matcher(url); if (matcher.find()) { return matcher.group(1); // 返回匹配的部分 } else { return null; // 如果没有找到匹配，返回 null } } /** * 重命名文件 */ public boolean renameFile(String fid, String newName) { try { Map<String, String> bodyMap = new HashMap<>(); bodyMap.put("fid", fid); bodyMap.put("file_name", newName); JSONObject response = okhttpUtil.postJsonObject( baseUrl + "/1/clouddrive/file/rename?pr=ucpro&fr=pc&uc_param_str", header(), bodyMap, okhttpUtil.getOkHttpClient() ); if (response != null && response.getInteger("code") != 0) { log.error("重命名文件失败: {}, 原因: {}", fid, response.getString("message")); return false; } return true; } catch (Exception e) { log.error("重命名文件异常", e); return false; } } /** * 删除文件夹 */ public boolean delete(List<String> fid) throws IOException { String url = baseUrl + "/1/clouddrive/file/delete?pr=ucpro&fr=pc&uc_param_str="; HashMap<String, Object> data = new HashMap<>(); data.put("action_type","2"); data.put("filelist",fid); JSONArray jsonArray = new JSONArray(); data.put("exclude_fids",jsonArray); JSONObject jsonObject = okhttpUtil.postJsonObject(url, header(), data, okhttpUtil.getOkHttpClient()); return !jsonObject.isEmpty() && jsonObject.getInteger("status") == 200; } public boolean delete(List<String> fid, String cookie) throws IOException { String url = baseUrl + "/1/clouddrive/file/delete?pr=ucpro&fr=pc&uc_param_str="; HashMap<String, Object> data = new HashMap<>(); data.put("action_type","2"); data.put("filelist",fid); JSONArray jsonArray = new JSONArray(); data.put("exclude_fids",jsonArray); HashMap<String, String> header = header(); header.put("Cookie",cookie); JSONObject jsonObject = okhttpUtil.postJsonObject(url, header, data, okhttpUtil.getOkHttpClient()); return !jsonObject.isEmpty() && jsonObject.getInteger("status") == 200; } /** * 创建文件夹 * @param dirPath 文件夹绝对路径 * @return String 返回文件夹fid * 对于已经存在的文件夹，则不会再创建，而是直接返回此文件夹的fid */ public String createDirectory(String dirPath) throws IOException { String url = baseUrl + "/1/clouddrive/file?pr=ucpro&fr=pc&uc_param_str="; HashMap<String, Object> body = new HashMap<>(); body.put("pdir_fid", "0"); body.put("file_name", ""); body.put("dir_path", dirPath); body.put("dir_init_lock", false); JSONObject response = okhttpUtil.postJsonObject(url, header(), body, okhttpUtil.getOkHttpClient()); if (response != null && response.getInteger("code") == 0) { return response.getJSONObject("data").getString("fid"); } return null; } /** * @param fid 文件夹id * @return JsonObject */ public JSONObject getDirDetails(String fid) throws IOException { String url = baseUrl + "/1/clouddrive/file/sort?pr=ucpro&fr=pc&uc_param_str&pdir_fid=" + fid + "&_page=1&_size=50&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,updated_at:desc&_fetch_full_path=0"; return okhttpUtil.getJsonObject(url, header(), okhttpUtil.getOkHttpClient()); } /** * 递归获取文件列表 返回Tree */ public Tree<QuarkNode> getAllFileList(String pwdId, String stoken, String pdirFid) { try { Tree<QuarkNode> rootTree = new Tree<>(); rootTree.put("id", "root"); rootTree.put("name", "根目录"); rootTree.put("parentId", "0"); // 递归获取文件树 getFileListRecursive(pwdId, stoken, pdirFid, rootTree); return rootTree; } catch (Exception e) { log.error("获取文件列表异常", e); return null; } } public Tree<QuarkNode> getAllFileList(String pdirFid) { try { Tree<QuarkNode> rootTree = new Tree<>(); rootTree.put("id", "root"); rootTree.put("name", "根目录"); rootTree.put("parentId", "0"); rootTree.put("shareFidToken",null); // 递归获取文件树 getFileListRecursive(pdirFid, rootTree); return rootTree; } catch (Exception e) { log.error("获取文件列表异常", e); return null; } } private void getFileListRecursive(String pwdId, String stoken, String pdirFid, Tree<QuarkNode> parentTree) throws IOException { int page = 1; Map<String, String> queryParams = buildInitialQueryParams(pwdId, stoken); while (true) { // 构建请求URL String url = baseUrl + "/1/clouddrive/share/sharepage/detail"; queryParams.put("pdir_fid", pdirFid); queryParams.put("_page", String.valueOf(page)); String fullUrl = url + "?" + buildQueryString(queryParams); // 发送请求 JSONObject response = okhttpUtil.getJsonObject(fullUrl, header(), okhttpUtil.getOkHttpClient()); if (response == null) break; JSONObject data = response.getJSONObject("data"); if (data == null) break; JSONArray fileList = data.getJSONArray("list"); if (fileList == null || fileList.isEmpty()) break; int total = response.getJSONObject("metadata").getInteger("_total"); // 处理单个文件/文件夹的情况 if (total == 1) { JSONObject singleItem = fileList.getJSONObject(0); // 在处理节点时的修改 QuarkNode node = createQuarkNode(singleItem); Tree<QuarkNode> currentTree = node.toTree(); parentTree.setChildren(Collections.singletonList(currentTree)); if (node.isDir()) { getFileListRecursive(pwdId, stoken, node.getFid(), currentTree); } return; } // 处理多个文件/文件夹的情况 for (int i = 0; i < fileList.size(); i++) { JSONObject item = fileList.getJSONObject(i); QuarkNode node = createQuarkNode(item); Tree<QuarkNode> currentTree = node.toTree(); // 直接使用 QuarkNode 的 toTree 方法 // 将当前树添加到父节点的子节点列表中 if (parentTree.getChildren() == null) { parentTree.setChildren(new ArrayList<>()); } parentTree.getChildren().add(currentTree); if (node.isDir()) { // 递归处理子文件夹 getFileListRecursive(pwdId, stoken, node.getFid(), currentTree); } } // 检查是否需要继续翻页 if (fileList.size() < total) { page++; } else { break; } } } private void getFileListRecursive(String pdirFid, Tree<QuarkNode> parentTree) throws IOException { int page = 1; while (true) { JSONObject response = this.getDirDetails(pdirFid); // 发送请求 if (response == null) break; JSONObject data = response.getJSONObject("data"); if (data == null) break; JSONArray fileList = data.getJSONArray("list"); if (fileList == null || fileList.isEmpty()) break; int total = response.getJSONObject("metadata").getInteger("_total"); // 处理单个文件/文件夹的情况 if (total == 1) { JSONObject singleItem = fileList.getJSONObject(0); // 在处理节点时的修改 QuarkNode node = createQuarkNode(singleItem); Tree<QuarkNode> currentTree = node.toTree(); parentTree.setChildren(Collections.singletonList(currentTree)); if (node.isDir()) { getFileListRecursive(node.getFid(), currentTree); } return; } // 处理多个文件/文件夹的情况 for (int i = 0; i < fileList.size(); i++) { JSONObject item = fileList.getJSONObject(i); QuarkNode node = createQuarkNode(item); Tree<QuarkNode> currentTree = node.toTree(); // 将当前树添加到父节点的子节点列表中 if (parentTree.getChildren() == null) { parentTree.setChildren(new ArrayList<>()); } parentTree.getChildren().add(currentTree); if (node.isDir()) { // 递归处理子文件夹 getFileListRecursive(node.getFid(), currentTree); } } // 检查是否需要继续翻页 if (fileList.size() < total) { page++; } else { break; } } } private QuarkNode createQuarkNode(JSONObject json) { return new QuarkNode() .setFid(json.getString("fid")) .setFileName(json.getString("file_name")) .setPdirFid(json.getString("pdir_fid")) .setShareFidToken(json.getString("share_fid_token")) .setDir(json.getBoolean("dir")); } private Map<String, String> buildInitialQueryParams(String pwdId, String stoken) { Map<String, String> queryParams = new HashMap<>(); queryParams.put("pr", "ucpro"); queryParams.put("fr", "pc"); queryParams.put("pwd_id", pwdId); queryParams.put("stoken", stoken); queryParams.put("force", "0"); queryParams.put("_size", "50"); queryParams.put("_fetch_banner", "0"); queryParams.put("_fetch_total", "1"); queryParams.put("_sort", "file_type:asc,updated_at:desc"); queryParams.put("_fetch_share", "0"); return queryParams; } }其中的方法包括但不限于isValidCookie 验证cookie有效性checkByUrl 验证资源是否有效并返回 stokengetFileList 获取指定分享Fid下的 所有/部分 json数据getUrlInfo 获取短剧的 集数、缩略图saveAndShareAndDel 转存并获取分享链接 可指定过期时间以及是否自动删除updateSave 用于检测资源更新后更新保存的文件renameFile 重命名文件delete 删除文件createDirectory 创建文件夹更新检测思路因为有的资源是长期更新的，所以我就在想如何实现更新自动转存。我的思路是： 转存时记录下所有文件的目录树，利用SpringScheduled定时检测目录树，如果不相同，则更新。 但是有个缺点，如果资源失效了，就无法检测，并且替换成新的又很麻烦，算是个半成品。代码相应的代码可以从 UpdateListService 这个类看一些其他的小功能可指定转存的文件夹可指定分享时附带某个文件可自行配置匹配广告的正则表达式，并决定是替换还是删除可开启信息通知功能：优先使用微信机器人通知，如果机器人掉线，可利用Mail通知可通过web的方式进行机器人登录及注销由于框架限制，机器人目前只实现了以下功能是否自动添加好友添加好友欢迎语自动邀请进群的关键词(支持正则)自动邀请进群的名称(支持多群，一个群满自动换成下一个)指定机器人生效的群组匹配搜剧的关键词(支持正则)搜索各种夸克资源并转存分享(默认分享后半小时删除资源以节省空间)推送每日新增短剧列表支持 WebHook 转存到指定账号并分享由于这个项目最开始是跟人使用，所以有些代码并不规范，有什么问题可以去GitHub提issue
最近在使用Mybatis-Plus的分页查询方法时候，发现并没有生效。查询的参数为{ "current": 1, "pageSize":"2" }但是实际把所有数据都返回了{ "code": 0, "data": { "records": [ { "id": 1923634783594762241, "userAccount": "admin", "createTime": "2025-05-17T07:00:46.000+00:00" }, ... ], "total": 0, "size": 2, "current": 1, "pages": 0 }, "message": "ok" }这里可以看到返回的 VO 中石油total size等字段的，但是没有生效，于是就去官网看文档,于 v3.5.9 起，PaginationInnerInterceptor 已分离出来。如需使用，则需单独引入 mybatis-plus-jsqlparser 依赖 ， 具体请查看 安装 一章。解决办法在 Maven bom标题下，可以看到使用 maven bom 管理依赖，减少版本号的冲突。因为 jsqlparser 5.0+ 版本不再支持 jdk8 针对这个问题解耦 jsqlparser 依赖。 正确打开姿势，引入 mybatis-plus-bom 模块，然后引入 ..starter 和 ..jsqlparser.. 依赖所以需要引入对应的包管理器和依赖<dependencyManagement> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-bom</artifactId> <version>3.5.9+ 版本</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>Spring Boot3 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> </dependency>Spring Boot2 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency>另外还需根据自己JDK版本引入不同的依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser-4.9</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> </dependency>此外，还需要在 Spring Boot 项目中，通过 Java 配置来添加分页插件@Configuration @MapperScan("scan.your.mapper.package") public class MybatisPlusConfig { /** * 添加分页插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加 // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType return interceptor; } }大功告成{ "code": 0, "data": { "records": [ { "id": 1923634783594762241, "userAccount": "admin", "userRole": "admin", "createTime": "2025-05-17T07:00:46.000+00:00" }, { "id": 1924441288589975553, "userAccount": "test001", "userRole": "user", "createTime": "2025-05-19T12:25:32.000+00:00" } ], "total": 10, "size": 2, "current": 1, "pages": 5 }, "message": "ok" }
这是以前刚学 JAVA 时候记录的，现在补充到博客上JAVA学习笔记变量局部变量和全局变量全局变量有默认值，而局部变量即方法中的变量无默认值，因此方法中的变量必须进行复制才能引用局部变量前面不能加修饰符（public，private，等）变量类型注意JAVA中默认的小数常量为double类型，若需要float类型，则在数字末尾添加f或者F当类型为double或是float时，0.*中的0可省略，例如 0.1 可写成 .1自动转换类型低精度到高精度可自动转换转换规则： char->int->long->float->double byte->short->int->long->float->doubleeg：int num = "a"; //a是由char类型转换为int类型，从低精度转换为高精度 char -> int double d = 80 // int -> double自由转换数据类型转为字符串只需要在数值后面加上 "" 即可eg:int a = 10; String b = a + "";String类型转其它需要引入Integer类eg：//转为Int String a = "aa"; int b = Integer.parseInt(a); //转为double double c = Double.parseDouble(a); //以此类推即可运算符+-*/不再赘述%取余a % b => a - a / b * b前++和后++作为独立语句时i++和++i作为独立语句时据等价于i = i + 1作为非独立语句时i++表示先赋值后运算++i表示先运算后赋值eg：int a = 1; int b = a++; //等价于 b = a,a = a + 1 int c = 1; int d = ++c; //等价于 d = c + 1,c = d或c = c + 1三元运算符基本语法条件表达式?表达式1:表达式2运算规则1.若条件表达式返回为True，则执行表达式22.若条件表达式返回为False，则执行表达式2int a = 1; int b = 2; int result = a > b?a++:b--; // 返回result=1 // 这里原来的a，b变量仍然不会改变Switch如下代码，表达式中返回一个结果，case中匹配常量和结果，若相同，则执行同级case下的语句，否则匹配下个case，若没有相同的常量，则默认执行default下的语句表达式的返回值必须是 byte short int srting char enumcase后的语句必须是常量或是 常量表达式，不能是变量当执行完一个case语句没有break语句时，会顺序执行到switch结尾，直到遇到break，注意，期间不管case是否匹配，都会执行下面的语句switch(表达式){ case 表达式结果等于这里的常量时: // 语句1 break; case 常量2: // 语句2 break; default: // 语句三 break; }do...while与while不同的是，while是先判断在执行循环体，do...while是先执行循环体在判断while不一定执行循环体，do...while至少执行一次循环体int count = 0; do{ count++; System.out.println(count); }while(count<=10);break细节通过lable可控制具体跳出的循环体若不指定跳出的循环体，则默认跳出离break最近的循环体lable1:{}break lable1;lable1: for(){ lable2: while(){ lable3 do{ }while(); break lable1; break lable2; break lable3; } }递归函数强烈推荐使用内存分析法下面是案例分析阶乘案例class A{ public int factorial(int num){ if n == 1{ return 1; }else{ return factorial(num - 1) * num; } } } A test = new A(); int ret = test.facaorial(5); System.out.println(ret); // 返回结果是120分析首先传入参数5，进入到第6行的factorial函数中，然后再在栈中新开辟一个内存空间假设为t1，t1的sum为4，然后再在执行factorial函数，在开辟t2，t2sum为3，....t4sum为1，所以t4返回值为1，t4空间销毁回收，返回给t3，即1sum，t3的sum为2，即1*2,返回给t2，返回值为2，然后2*3返回为6，t1是6*4返回给第一次计算的空间即24*5，所以最后结果为120可变参数当一个方法接收的参数类型一样且为多个时，可以用可变参数接收public void static main(String[] args){ class sum{ public int sum(int...nums){ # nums的数据类型相当于数组 System.out.println(nums.length); # 求和 int res = 0; for(int i = ;i < nums.length ; i++){ res += nums[i]; } } } }注意可变参数可以接受数组int...nums 中int为数据类型，... 不可动，nums为形参普通形参和可变参数放在一起时，可变参数要放在普通形参后面一个方法中只能有一个可变参数构造器注意事项格式： [修饰符] 类名(形参){}不能有return语句构造器可以重载构造器中如果有This语句，则This语句必须放在第一条class Person(){ String name; int age; public Person(String pname,int page){ name = pname; age = page } } Person p = new Person("名字",10); System.out.println(p.name + p.age); # 名字10This，Super关键字Super只能在构造器中使用，且This和Super不能同时使用This相当于Python整的self，在类中则代表当前对象Java示例如下class Dog{ String name; int age; public Dog(String name,int age){ this.name = name; this.age = age; } public void t1(){ System.out.println("t1()方法被调用"); } public void t2(){ // 调用t1的方法 this.t1(); } }Python示例如下class Dog: def __init__(self,name, age): self.name = name self.age = age def t1(): print("t1()方法被调用") def t2(): # 调用t1方法 self.t1()访问构造器注意访问构造器语法 this(); 必须放在构造器第一行class Dog{ String name; int age; public Dog(){ this("小明", 20); } public Dog(String name, int age){ this.name = name; this.age = age; System.out.println(this.name + this.age); } } Dog d = new Dog(); // 输出 小明20this(name)在继承中代表访问一个参数只有name的构造器class Person { public static void prt(String s) { System.out.println(s); } Person() { prt("父类·无参数构造方法： "+"A Person."); }//构造方法(1) Person(String name) { prt("父类·含一个参数的构造方法： "+"A person's name is " + name); }//构造方法(2) } public class test extends Person { test() { super(); // 调用父类构造方法（1） prt("子类·调用父类无参数构造方法： "+"A chinese coder."); } test(String name) { super(name);// 调用父类具有相同形参的构造方法（2） prt("子类·调用父类含一个参数的构造方法： "+"his name is " + name); } test(String name, int age) { this(name);// 调用具有相同形参的构造方法（3） prt("子类：调用子类具有相同形参的构造方法：his age is " + age); } public static void main(String[] args) { test cn = new test(); // cn = new test("codersai"); cn = new test("codersai", 18); } } // 输出如下 //父类·无参数构造方法： A Person. //子类·调用父类无参数构造方法： A chinese coder. //父类·含一个参数的构造方法： A person's name is codersai //子类·调用父类含一个参数的构造方法： his name is codersai //子类：调用子类具有相同形参的构造方法：his age is 18Supersuper.属性可以访问跟父类的属性，但不能访问父类的私有属性spuer.方法名(参数列表)可以访问父类的方法，但是不能访问父类私有的方法super()必须放在子类构造器的第一行，且不能与this同时存在访问修饰符类中的变量，函数都可用访问修饰符类也可以用访问修饰符，不过只能同 public 和 默认继承关键词 extends注意事项子类继承了所有的属性和方法，但是私有属性和方法不能在子类直接访问，要通过公用的方法去访问当创建子类对象时，不管子类使用哪个构造器，默认情况下会去调用父类的无参构造器，如果父类中没有无参构造器，则必须在子类的构造器中用 super()去指定父类哪个构造器来完成对父类的初始化工作，否则编译不通过。多态多态因为可以理解为多种形态方法的多态方法的重载，重写实际上就是多态的一种对象的多态1.一个对象的编译类型和运行类型可以不一致2.编译类型在定义对象时，就确定了，不能改变了3.运行类型是可以改变的4.编译类型看定义时 = 号 的左边，运行类型看 = 号 的右边class Animal{ //父类 private String Name; public void setName(String Name){ this.Name = Name; } public String getName(){ return Name; } } class Dog extends Animal{ //子类 private String Name = "Dog"; public String getName(){ return Name; } } class Cat extends Animal{ //子类 private String Name = " Cat"; public String getName(){ return Name; } } public static void main(String[] args){ Animal dog = new Dog(); //dog的编译类型是Animal，运行类型是Dog dog = new Cat(); //此时dog的编译类型还是Animal，运行类型变成了Cat }对象的多态，就是在调用类的时候传入的参数可以是一个对象，通过对象去调用其子类或本类的方法eg：public static void main(String[] args){ Animal dog = new Dog(); Animal cat = new Cat(); call(dog); //输出Dog is runing call(cat); //输出Cat is runing } public void call(Animal name){ System.out.println(name.getName + " is runing"); }向上转型向上转型就是 Animal dog = new Dog() dog去调用Dog类和Animal类的方法，弊端是假如说Dog中有A方法可以调用，Animal中的A方法为私有方法或者没有此方法，那么就无法编译通过。虽然是从子类到父类去寻找方法，但是由于Java特性，无法编译通过，于是有了向下转型向下转型书接上回，dog如何调用Dog类中独有的方法呢？就用到了向下转型，也可以理解为强转，转的 不是编译类型，因为编译类型确定后就无法改变，转的是运行类型。Animal dog = new Dog(); Dog dog1 = (Dog) dog;这样dog1对象就能调用Dog类的方法了。注意语法 子类类型 引用名 = （子类类型）父类引用;要求父类的引用必须指向的是当前目标类型的对象（也就是说上方代码第二行最后一个的dog的运行类型必须是dog1的编译类型）变量不能重写，以向上转型为例，Animal animal = new Dog(); System.out.print(amimal.name) 假设父类Animal和子类Dog都有公共的变量name，则以编译类型的变量为主，打印出来的就是Animal的name。属性看遍历类型，方法看运行类型。instanceof语法： 对象A instanceof 类B 返回布尔值判断对象A的运行类型的类是否为类B或类B的子类，若是则为True，反之为False动态绑定机制当调用对象方法时候，该方法会和该对象的内存地址/运行类型绑定当调用对象属性时，没有动态的绑定机制，哪里声明哪里使用。class A { //父类 public int i =10; public int sum(){ return getI + 10; } public int sum1(){ return i + 10; } public int getI(){ return i; } } class B extends A { //子类 public int i = 20; public int sum(){ return i + 20; } public int getI(){ return i; } public int sum1(){ return i + 10; } } // main方法 A a = new B(); //向上转型 System.out.println(a.sum()); //40 System.out.println(a.sum1()); //30！！！如果删除掉子类B的sum()方法，则调用 a.sum()时候会继承A的sum()方法，返回的时 getI() + 10，而由于动态绑定机制，getI()指向的是a的运行类型也就是B类中的getI()方法，所以此时返回的是30多态数组定义：数组的定义为父类类型，厘米那的实际元素类型为子类类型。代码块代码块和方法不同没有方法名，没有返回值，没有参数，只有方法体，而且不用通过对象或类显式调用，而是在加载类时，，或创建类时隐式调用基本语法[修饰符]{ Code }；注意修饰符为可选项，要写的话，也只能写static代码块分为两类，静态代码块即用static修饰的代码块和普通代码块即没有修饰符的末尾;可写可不写代码块什么时候被加载创建对象实例时创建子对象实例时候，父类的代码块也会被加载（父类的代码块先加载，接着加载子类的代码块）使用类的静态成员时（静态方法，静态变量）ps：使用类的静态成员时普通代码块即非静态代码块不会被执行静态代码块只会被执行1次，非静态的没创建一次就会被执行一次优先级静态代码块和静态属性的优先级一样倘若有多个静态代码块和多个静态属性，则按照书写顺序加载class A{ private static int num = getNum(); static { System.out.println("静态代码块初始化"); } public int getNum(){ System.out.println("getNum方法被调用"); return 100; } } new A();！！！当实例化A类时候，优先初始化第一个静态属性即 num，此时 num调用了 getNum方法，所以优先输出 getNum方法被调用，然后再初始化静态代码块静态代码块和静态属性的优先级高于普通代码块和普通属性普通代码块和普通属性的优先级一样构造器的优先级是最低的class AAA{ public AAA(){ //super(); //调用本类的普通代码块 System.out.println("AAA类的构造器被调用"); } } class BBB extends AAA{ { System.out.println("BBB的普通代码块被调用"); } public BBB(){ //super(); //调用本类的普通代码块 System.out.println("BBB类的构造器被调用"); } } new BBB();每个类的构造器下都有隐藏的代码，调用 super()调用本类的普通代码块所以上述代码的运行结果就是 1 AAA类构造器 2 BBB普通代码块 3 BBB构造器总结执行顺序如下父类的静态代码块和静态属性（优先级一样，按定义顺序执行）子类的静态代码块和静态属性（优先级一样，按定义顺序执行）父类的普通代码块和普通属性（优先级一样，按定义顺序执行）父类的构造方法子类的普通代码块和普通属性（优先级一样，按定义顺序执行）子类的构造方法final关键字结论不能被继承，重写，修改里面的值三种赋值方式定义时构造器中代码块中如果final修饰的属性是静态的，那么只能在代码块和定义时赋值。final修饰的类不能被继承，一般final修饰类后，方法就不用加final修饰了final和staticfinal和static同时使用不会创建类、class Test{ public final static int num = 555; static{ System.out.println("静态代码块被调用"); } }假如只有static没有final，那么调用 Test.num时候静态代码块也会被调用，若加上final，则不会调用静态代码块。抽象类 abstract抽象类就是没有方法体的方法，方便子类继承重写方法，抽象类和抽象方法的定义需要用到 abstract关键字当一个类中存在抽象方法时，该类必须声明为抽象类abstract class Animal{ abstract public void eat(); }细节抽象类不能被实例化抽象类不一定要有抽象方法abstract关键字只能修饰类和方法，不能修饰属性如果一个类继承了抽象类，则必须实现抽象类的所有抽象方法，否则子类也要声明为抽象类抽象方法不能用 private,static,final关键字修饰，因为这些关键字是与重写相违背的接口 interFace基本语法定义接口//接口类中可省略abstract和publicpublic interface Method{ //属性 //方法（1.抽象方法 2.默认default方法 3.静态static方法） void job(); //接口类中可省略abstract和public } //实现（重写接口中的方法） public class Mian implenets Method{ public void job(){ System.out.println("Hello World"); } } public class T1(){ public void run(interface Method){ Method.job(); } } //调用 public class void T2{ public static void main(String[] args){ T1 tt = new T1(); Main mm = new Main(); tt.run(mm); } }注意如果一个类实现了一个接口方法，即 一个类 implenets 接口 则这个类必须实现这个接口的所有的抽象方法在JDK8之后，可以有静态方法如果用到非抽象方法，需要加上default的关键字接口不能被实例化一个类可以同时实现多个接口接口中的属性，只能是final的，且为 public static final，假设我写了一个int x = 1,那么它就是public static final int x = 1;（必须初始化）接口不能继承类，但是接口可以继承extends多个接口内部类局部内部类（有类名）局部内部类可以直接访问外部类的所有成员对象，包括私有的内部类不能用除final的修饰符修饰如果外部类和局部内部类的成员名重名时，遵循就近原则，如果要调用外部类则需要 外部类名.this.成员名匿名内部类（没有类名）基于接口首先定义一个接口innerTest，使其拥有一个test方法以下是内部匿名类的用法，通过实例化一个接口并重写其方法来实现匿名内部类下图是上图的底层源码匿名内部类实际上是有名字的，我们看不到，系统自动去分配，而他的底层源码就是上图所示的形式上图的编译类型是接口的innerTest类型，而他的运行类型则是个底层代码的te的类型基于类跟基于接口的类似很牛逼的使用匿名内部类匿名内部类可以直接当作一个参数使用interface IA{ void test(); } class main{ public static void method(IA i){ i.test(); } public static void main(String[] args){ method(new IA(){ public void test(){ System.out.println("世界名画！"); } }); } } // 输出 世界名画细节匿名类有两种调用模式，第一种就是上面使用的的，利用可动态绑定机制，第二种可以直接去调用class outer{ new test(){ public void test(){ System.out.println("我重写了test函数"); } }.test(); } class test{ public void test(){ System.out.println("test"); } }外部其他类不能访问内部类成员内部类成员内部类是定义在外部类的成员位置成员内部类可以访问外部类的所有成员，包括私有的可以添加任意的访问修饰符，因为它本身就是一个成员如果外部类和成员内部类的方法或者属性变量重名，依然遵守就近原则，访问外部类：外部类.this.方法名/属性名外部其他类调用成员内部类方法一// 假设outer为外部类，inner为成员内部类 outer.inner test = new outer.inner();方法二在外部类中写一个方法返回内部类的对象静态内部类说明：静态内部类仍然定义在外部类的成员位置，只是多了个static修饰可以直接访问外部类的静态成员，包括私有的，但是不能直接访问非静态的因为他也是成员内部类，所以可以使用修饰符（不太一样，无this）如果外部类和成员内部类的方法或者属性变量重名，依然遵守就近原则，访问外部类：外部类.方法名/属性名调用方法同上枚举自定义枚举构造器私有化创建一个静态成员new一个所需的对象，用static final修饰class test{ public int i; public static final test CREATEST1 = new test(1); public static final test CREATEST2 = new test(2); public static final test CREATEST3 = new test(3); private test(int i){ this.i = i; } } // static 和 final 一起使用时候会使类不会被加载 // 调用就直接 test.CREATEST1.XXX // 枚举对象名一般大写关键字enum优化后的代码enum test{ CREATEST1(1),CREATEST2(2),CREATEST3(3),WHAT; public int i; private test(int i){ this.i = i; } private test(){ } }注意使用关键字enum代替classpublic static final test CREATEST1 = new test(1);直接使用CREATEST1(1)代替，若有多个则用逗号隔开CREATEST1(1) 括号里面是实参，对应构造器的形参CREATEST1(1)必须写在最前面如果使用无参构造器，则可以省略 ( ) 直接写常量对象名使用enum定义枚举时候，就不能在继承其他类了，因为它已经隐式的继承了enum类了，然仍然能实现接口注解/元数据@Override该注解表示方法重写了父类的某个方法该注解只能用在某个方法上如果写了@Override注解，java编译器在编译时候会寻找父类中是否含有该方法，如果真的被重写，则编译通过。否则就抛出异常@Deprecated表示程序元素（类，方法）已经过时了可以修饰 类 方法 参数 包 字段等等过时并不能代表不能用@SuppressWarnings抑制编译器警告用法@SuppressWarnings{""}{" "}里面写抑制的类型，all 代表一直所有警告@Target该注解是修饰注解的注解，称为元注解集合List基本方法（实现了Collection的接口方法）add：添加单个元素remove：删除元素（可指定索引或指定删除的对象）contains：查找元素是否存在size：获取元素个数isEmpty：判断是否为空clear：清空addAll：添加多个元素（可以放实现了Collection接口的对象，比如说集合）containsAll：查找多个元素是否都存在（可以放实现了Collection接口的对象，比如说集合）removeAll：删除多个元素（可以放实现了Collection接口的对象，比如说集合）迭代器遍历Iterator iterator = coll.iterator() //得到一个集合的迭代器.hasNext()是否还有下一个元素 编译类型是Object.next() 显示当前元素内容，并对指针进行下移第二次遍历需要重置迭代器，否则就会抛出异常iterator = coll.iterator() //重置迭代器IDEA快速生成whlie循环遍历的快捷键：ititIDEA快速生成增强for循环遍历的快捷键：I增强for循环遍历for(元素类型(Object) name:col){ System.out.println(clo); }SetSet接口介绍无序的（添加和取出的顺序不一样），没有索引不允许有重复的元素，所以最多包含一个null注意：取出的顺序虽然是无序的，但是是固定的，底层算法决定的方法说明因为set没有索引，故不能用get方法取出，遍历的话同上实现了List的接口的类一样，增强for循环和迭代器add方法说明，add方法在这里有一个布尔类型的返回值，用来判断是否添加成功remove 删除指定对象Mapkey:value方法说明put(key,value) 添加：key的值是唯一的不能重复，value的值可以重复，假如说key的值重复了，那么则会替换先前存在key的value的值(Hashtable的key和value都不能为空)remove(key)根据键删除对应的映射关系get(key)获取对应key的value 返回值类型为objectsize()返回k-v的个数isEmpty()判断是否为空clear()清空键值对containsKey(key)查找键是否存在keySet() 返回一个set集合，集合里面是所有的key遍历的六大方式（比List和Set稍微复杂点，但是基本原理相同）利用 keySet()方法获取所有的key，然后用增强for循环get到每一个valueMap map = new HashMap(); map.put("no1","Lin"); map.put("no2","Kenen"); map.put("no3","f"); map.put("no4","z"); Set set = map.keySet(); for(Object i:set){ System.out.println(map.get(i)); }迭代器Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(map.get(key)); }增强for 同上迭代器 同上entrySet set = map.entrySet(); for (Object i:set){ Map.Entry m = (Map.Entry) i; System.out.println(m.getKey()+"-"+m.getValue()); }Collections工具类Collections类能对Set，List，Map等集合进行操作常用方法reverse(List) ：反转列表shuffle(List) ：对列表进行随机排序sort(List) ：排序sort(List,Comparatoe) ：根据Comparator产生的顺序对列表排序swap(List,int,int) ：将指定List的集合的两个元素进行交换位置copy(desp,list) ：将list的内容拷贝到desp中。注意：desp的长度必须>=list的长度，都则会抛出异常boolean replaceALL(List,old,new) ：将List集合中的old值全部替换为new的值frequency(Collection,Object) ：返回指定集合中指定元素出现的次数泛型泛型的通配和继承泛型不具备继承机制 List<Object> list = new list<String>(); //错误<?> 支持任意泛型<? extends A>支持A类及A类的子类，规定了泛型的上限<? super A> 支持A类及A类的父类，规定了泛型的下线举例public void test(List<?> list){ for(Object o:list){ System.out.println(o); } } public void test(List<? extends AA> list){ for(Object o:list){ System.out.println(o); } } public void test(List<? super AA> list){ for(Object o:list){ System.out.println(o); } }多线程创建线程的两种方式继承Thread类，重写run方法实现Runnable接口，重写run方法*为什么是开启新线程是start方法而不是run方法？进入start方法可以看到，最主要开启的新的线程的方法是start0()方法，这个是JVM的内部方法，底层是c/c++编写，实际就是由底层创建了一个新的线程去调用了run方法代码示例//继承实现 public class Cat extends Thread{ @Override public void run(){ int count = 0; while (true){ System.out.println("我是小猫，喵喵喵"+ (++count)); if (count > 8){ break; } try { Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } } } } //实现Runnable接口实现 public class Dog implements Runnable{ @Override public void run(){ int count = 0; while (true){ System.out.println("我是小狗，汪汪汪"+ (++count)); if (count > 8){ break; } try { Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } } } } //主方法 public class Test { public static void main(String[] args) { Cat cat = new Cat(); Dog dog = new Dog(); cat.start(); Thread thread = new Thread(dog); thread.start(); } }总结继承了Thread的类可以直接调用start方法，但由于Java是单继承机制，所以可以实现Runnable接口，此时开启新线程就需要在创建一个新的Thread的类，并传入实例化的对象，然后调用其start的方法区别从Java的设计来看，继承Thread和实现Runnable没有本质的区别实现Runnable的接口方式更适合多个线程共享一个资源的情况，并避免了单继承机制的限制class Te implements Runnable{ public void run(){} } class main{ public static void main(String[] args){ Te t0 = new Te(); Thread t1 = new Thread(t0); Thread t2 = new Thread(t0); t1.start; t2.start; } }线程常用方法setName：设置线程名称getName：获取线程名称start：开始执行线程run：调用线程run方法setPriority：更改线程优先级setPriority：获取线程优先级sleep：休眠interrput：中断线程，不是结束线程 一般用于zheng'zai线程插队yield：线程的礼让，让出CPU，让其他线程执行，但是礼让时间不确定，所以礼让不一定成功join：线程的插队。插队的线程一旦插队成功，则肯定先执行完插入的线程的所以任务（会阻塞线程（主线程也会被阻塞））守护线程引入当子线程是一个无限循环的线程时，主线程退出，子线程并不会退出，为了此时让子线程也退出，就用到了守护线程解决办法线程名.setDaemon(true);Synchronized线程同步机制在多线程编程，一些敏感的数据不允许被多个线程同时访问，此时就是使用同步访问技术，保证数据在任何同一时刻，最多只有一个线程访问，以保证数据的完整性。也可以理解成：线程同步：即当有一个线程对内存进行操作时，其他线程都不可以对这个内存进行操作，知道该线程完成操作，其他线程才能对该内存进行操作实现同步的具体方法-Synchronized同步代码块synchronized (对象){ // 这里放需要被同步的代码 } public void m1(){ // Code synchronized (this){ // Code } }Synchronized放在方法声明中，表示整个方法为同步方法public synchronized void m1(){ // 这里放需要被同步的代码 }注意选择同步代码块的对象this 是指本对象，如果多个new对象对其进行start，那么是锁不住的，所以推荐采用实现Runnable接口的方法实现多线程线程的死锁介绍多个线程占用了对面的资源，但不肯相让，导致了死锁。释放锁会释放锁的操作当前线程的同步方法，同步代码块执行完毕当前线程在同步方法，同步代码块中遇到了return或break当前线程在同步方法，同步代码块中遇到了Error或Exception，导致异常结束当前线程在同步方法，同步代码块中执行了wait()方法，当前线程暂停，并释放锁不会释放锁的操作当前线程在同步方法，同步代码块中执行了 Thread.sleep() 或者 Thread.yield()方法当前线程在同步方法，同步代码块时，其他线程调用了该线程的suspend()方法，该线程将会被挂起，不会被释放四大核心函数式接口函数式接口称谓参数类型用途Consumer<T>消费性接口T对类型为T的对象应用操作，包含方法 void accept(T t)Supplier<T>供给型接口无返回类型为T的对象，包含方法 T get()Function<T,R>函数型接口T对类型为T的对象应用操作，并返回结果，结果是R类型的对象，包含方法 R apply(T t)Predicate<T>判断型接口T确定类型为T的对象是否满足约束，并返回 boolean值，包含方法 boolean test(T t)public class MyTest { // Consumer Interface Demo // 和 JavaScript 的 CallBack 有异曲同工之妙 public void consumer(Integer num, Consumer<Integer> consumer) { consumer.accept(num); } @Test public void testConsumer() { // 可以这么理解 consumer(x,callback) consumer(500,num -> System.out.println("The number is " + num)); } // Supplier Interface Demo public <T> T supplier(Supplier<T> supplier) { return supplier.get(); } public String hello(){ return "hello"; } @Test public void testSupplier() { System.out.println(supplier(() -> hello())); } // Function Interface Demo public <T,R> R function(T t,Function<T,R> function){ return function.apply(t); } @Test public void testFunction() { String function = function(100, num -> String.valueOf(num)); System.out.println(function); } // Predicate Interface Demo public <T> void predicate(T t,Predicate<T> predicate) { System.out.println(predicate.test(t)); } @Test public void testPredicate() { predicate(100,num -> num == 1000); }