diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bbc36c013..e183e0bfb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: Build and Deploy on: push: - branches: [ master ] + branches: [master] jobs: build-and-deploy: @@ -10,20 +10,20 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2.3.1 - with: + with: persist-credentials: false - - name: Set Ruby 2.7 + - name: Set Ruby 3.1 uses: actions/setup-ruby@v1 with: - ruby-version: 2.7 + ruby-version: 3.1 - name: Install and Build run: | gem install bundler bundle install bundle exec jekyll build - + - name: Deploy uses: JamesIves/github-pages-deploy-action@3.6.2 with: diff --git a/Gemfile.lock b/Gemfile.lock index 7650455f87..cb433f4262 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,96 +1,95 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.3.5) + activesupport (6.0.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.1.8) - dnsruby (1.61.5) + commonmarker (0.23.6) + concurrent-ruby (1.2.0) + dnsruby (1.61.9) simpleidn (~> 0.1) - em-websocket (0.5.2) + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.12.0) - ffi (>= 1.3.0) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.7.0) - faraday (1.3.0) - faraday-net_http (~> 1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords - faraday-net_http (1.0.1) - ffi (1.14.2) + execjs (2.8.1) + faraday (2.7.2) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5) forwardable-extended (2.6.0) gemoji (3.0.1) - github-pages (212) - github-pages-health-check (= 1.17.0) - jekyll (= 3.9.0) + github-pages (227) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.2) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.6) + jekyll-commonmark-ghpages (= 0.2.0) jekyll-default-layout (= 0.1.4) jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) jekyll-readme-index (= 0.3.0) jekyll-redirect-from (= 0.16.0) jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.2) + jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.7.1) + jekyll-seo-tag (= 2.8.0) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.1.1) - jekyll-theme-cayman (= 0.1.1) - jekyll-theme-dinky (= 0.1.1) - jekyll-theme-hacker (= 0.1.2) - jekyll-theme-leap-day (= 0.1.1) - jekyll-theme-merlot (= 0.1.1) - jekyll-theme-midnight (= 0.1.1) - jekyll-theme-minimal (= 0.1.1) - jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.4) - jekyll-theme-slate (= 0.1.1) - jekyll-theme-tactile (= 0.1.1) - jekyll-theme-time-machine (= 0.1.1) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) jemoji (= 0.12.0) - kramdown (= 2.3.0) + kramdown (= 2.3.2) kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.3) mercenary (~> 0.3) minima (= 2.5.1) - nokogiri (>= 1.10.4, < 2.0) + nokogiri (>= 1.13.6, < 2.0) rouge (= 3.26.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.0) + github-pages-health-check (1.17.9) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (>= 2.0.2, < 5.0) + public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.14.0) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - http_parser.rb (0.6.0) + http_parser.rb (0.8.0) i18n (0.9.5) concurrent-ruby (~> 1.0) - jekyll (3.9.0) + jekyll (3.9.2) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -108,12 +107,12 @@ GEM jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.3.1) - commonmarker (~> 0.14) - jekyll (>= 3.7, < 5.0) - jekyll-commonmark-ghpages (0.1.6) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1.2) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.2.0) + commonmarker (~> 0.23.4) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) rouge (>= 2.0, < 4.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) @@ -124,6 +123,8 @@ GEM jekyll-github-metadata (2.13.0) jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) html-pipeline (~> 2.3) jekyll (>= 3.7, < 5.0) @@ -136,57 +137,57 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.2) + jekyll-remote-theme (0.4.3) addressable (~> 2.0) jekyll (>= 3.5, < 5.0) jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.7.1) + jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) jekyll-swiss (1.0.0) - jekyll-theme-architect (0.1.1) - jekyll (~> 3.5) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.1) - jekyll (~> 3.5) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.1) - jekyll (~> 3.5) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.2) + jekyll-theme-hacker (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.1) - jekyll (~> 3.5) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.1) - jekyll (~> 3.5) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.1) - jekyll (~> 3.5) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.1) - jekyll (~> 3.5) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.1) - jekyll (~> 3.5) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.4) + jekyll-theme-primer (0.6.0) jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.1.1) - jekyll (~> 3.5) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.1) - jekyll (~> 3.5) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.1) - jekyll (~> 3.5) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-titles-from-headings (0.5.3) jekyll (>= 3.3, < 5.0) @@ -196,12 +197,12 @@ GEM gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.0) + kramdown (2.3.2) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.3) - listen (3.4.1) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) @@ -209,37 +210,36 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.14.4) - multipart-post (2.1.1) - nokogiri (1.12.5-x86_64-darwin) + minitest (5.17.0) + nokogiri (1.13.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.13.10-x86_64-darwin) racc (~> 1.4) - nokogiri (1.12.5-x86_64-linux) + nokogiri (1.13.10-x86_64-linux) racc (~> 1.4) - octokit (4.20.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.6) - racc (1.5.2) - rb-fsevent (0.10.4) + public_suffix (4.0.7) + racc (1.6.2) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.5) rouge (3.26.0) - ruby-enum (0.9.0) - i18n - ruby2_keywords (0.0.4) - rubyzip (2.3.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) simpleidn (0.2.1) unf (~> 0.1.4) terminal-table (1.8.0) @@ -247,16 +247,17 @@ GEM thread_safe (0.3.6) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.9) + tzinfo (1.2.11) thread_safe (~> 0.1) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) webrick (1.7.0) - zeitwerk (2.4.2) + zeitwerk (2.6.6) PLATFORMS + arm64-darwin-22 x86_64-darwin-20 x86_64-linux @@ -266,4 +267,4 @@ DEPENDENCIES webrick (~> 1.7) BUNDLED WITH - 2.2.13 + 2.3.26 diff --git a/LICENSE b/LICENSE index 5ec214b971..27d0e197d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Zhuang Ma +Copyright (c) 2013 Fulong Ma Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 995697195a..7c748d8cc8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Usage -无 +draft: true表示是草稿。 ## Acknowledge diff --git a/_config.yml b/_config.yml index 4c08f7ba29..85f838eed2 100644 --- a/_config.yml +++ b/_config.yml @@ -7,18 +7,17 @@ date_format: "ordinal" title: Fulongのblog subtitle: "Hi" description: "Fulongのblog" -# subtitle: "Legendary" -subtitle: "逃跑可耻但有用" +subtitle: "Logbook" keywords: logbook, mafulong timezone: Asia/Shanghai encoding: "utf-8" -side_bar_repo_limit: 4 -# 下面涉及js引用,先不能改 +side_bar_repo_limit: 3 repository: mafulong/mafulong.github.io # 对 css 和 js 资源的 cdn 加速配置 cdn: jsdelivr: - enabled: true + enabled: true + branch: "built" # 组件配置 components: # 分享 @@ -27,7 +26,6 @@ components: # 不蒜子访问统计 busuanzi: enabled: true - start_date: 2020-05-03 # 压缩页面内容 compress_html: clippings: all @@ -64,29 +62,18 @@ navs: href: /archives/ label: Achieves - # - - # href: /open-source - # label: Open-Source + - + href: /open-source + label: Open-Source -# - -# href: /wiki -# label: Wiki - href: /bookmark label: Bookmark -# - -# href: /links -# label: Links - href: /about label: About -# ---------------- # -# RSS # -# ---------------- # -# subscribe_rss: /feed.xml - # ---------------- # # Jekyll # # ---------------- # @@ -117,7 +104,7 @@ collections: # Comments # # ---------------- # # support provider: disqus, gitment, gitalk -comments_provider: gitalk +# comments_provider: gitalk # !!!重要!!! 请修改下面这些信息为你自己申请的 # !!!Important!!! Please modify infos below to yours # https://disqus.com @@ -131,11 +118,11 @@ comments_provider: gitalk # client_id: d2e1cbbd298958076462 # client_secret: b42a4178e5fd4a7cf63189ef4b1453b05c375709 # https://github.com/gitalk/gitalk#install -gitalk: - owner: mafulong - repo: blog-comment - clientID: 99d40aa8c51f2e320555 - clientSecret: 08d7d01185f19e4e205adc2b36a6750d92d640c2 +# gitalk: + # owner: mafulong + # repo: blog-comment + # clientID: 99d40aa8c51f2e320555 + # clientSecret: 08d7d01185f19e4e205adc2b36a6750d92d640c2 # 在使用其它评论组件时可点击显示 Disqus lazy_load_disqus : false @@ -146,14 +133,12 @@ simple_jekyll_search: # 是否支持全文搜索 fulltext: false # 最多显示多少条搜索结果 - limit: 16 + limit: 20 # ---------------- # # Google # # ---------------- # google: - # analytics_id: UA-80669434-1 - analytics_id: G-SS4VDLWLNC adsense: footer: false sidebar: false diff --git a/_data/links.yml b/_data/links.yml deleted file mode 100644 index 71cac97ce9..0000000000 --- a/_data/links.yml +++ /dev/null @@ -1,72 +0,0 @@ - -# - name: Connie 酱 -# url: https://connie99.github.io -# src: life - -# - name: 只宁静不致远 -# url: http://zxning.github.io -# src: life - -# - name: Blog Something -# url: http://chenxiaoyoyo.github.io -# src: life - -# - name: 日新亭 -# url: http://www.fengerzh.com/ -# src: life - -# - name: wanandroid -# url: http://www.wanandroid.com/ -# src: life - -# - name: ZZY补完计划 -# url: https://zzycreate.github.io/ -# src: life - -# - name: 村农 -# url: http://www.why-dong.com/ -# src: life - -# - name: 求生之路 -# url: http://blog.xiaolaoni.xin/ -# src: life - -# - name: 王玉超 -# url: https://yuchao.wang/ -# src: life - -# - name: 董小姐的草原 -# url: https://candy961.github.io/ -# src: life - -# - name: 水寒的博客 -# url: https://dp2px.com -# src: www - -# - name: 孙不服的博客 -# url: https://sunbufu.github.io/ -# src: www - -# - name: HwiLu's 博客 -# url: https://hwilu.github.io/ -# src: www - -# - name: 有次博客 -# url: http://you.ci/ -# src: www - -# - name: 呓城之城 -# url: http://yicheng.zdyrs.com/ -# src: www - -# - name: 秦大圣的博客 -# url: https://qinjisheng.top/ -# src: www - -# - name: 极客玩家大白 -# url: https://geekplayers.com/ -# src: www - -# - name: 见字如面 -# url: https://hiwannz.com -# src: www diff --git a/_data/skills.yml b/_data/skills.yml deleted file mode 100644 index 4575e85c17..0000000000 --- a/_data/skills.yml +++ /dev/null @@ -1,19 +0,0 @@ -# - name: Software Engineer Keywords -# keywords: -# - Java -# - C++ -# - Python -# - Design Patterns - -# - name: Mobile Developer Keywords -# keywords: -# - Android -# - Reverse Engineering - -# - name: Windows Developer Keywords -# keywords: -# - Win32 SDK -# - DuiLib -# - WTL -# - COM -# - WinDbg diff --git a/_data/social.yml b/_data/social.yml deleted file mode 100644 index 609e4c6ec1..0000000000 --- a/_data/social.yml +++ /dev/null @@ -1,12 +0,0 @@ -- sitename: GitHub - name: mafulong - url: https://github.com/mafulong - -- sitename: 博客 - name: LogBook - url: http://mafulong.github.io - -- sitename: 知乎 - name: mafulong知乎 - url: https://www.zhihu.com/people/ma-fu-long-86 - diff --git a/_data/template.md b/_data/template.md deleted file mode 100644 index 5ef1a00a40..0000000000 --- a/_data/template.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: wiki -title: Wiki Template -categories: [cate1, cate2] -description: some word here -keywords: keyword1, keyword2 ---- - -Content here diff --git a/_includes/categories.html b/_includes/categories.html deleted file mode 100644 index de2c117a8c..0000000000 --- a/_includes/categories.html +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: default ---- - -{% assign assets_base_url = site.url %} -{% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} -{% endif %} -
-
-
-
-
-

{{ page.title }}

-
-
-
- {% include sidebar-qrcode.html %} -
-
-
-
- -
-
-
-
- {{ content }} -
- -
- {% include comments.html %} -
-
-
- {% include sidebar-search.html %} - {% include sidebar-categories-nav.html %} - -
-
-
- \ No newline at end of file diff --git a/_includes/footer.html b/_includes/footer.html index 337f5cc66f..a9476309aa 100755 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -43,6 +43,20 @@ + + {% if site.components.share.enabled %} {% endif %} @@ -84,8 +98,8 @@ {% endif %} {% if page.mindmap %} - - + + {% endif %} -
diff --git a/_includes/header.html b/_includes/header.html index 817307bbf8..b5d4f37144 100755 --- a/_includes/header.html +++ b/_includes/header.html @@ -16,6 +16,7 @@ + {% assign highlight_base_url = 'https://mazhuang.org/rouge-themes/dist' %} {% if site.cdn.jsdelivr.enabled %} {% assign highlight_base_url = 'https://cdn.jsdelivr.net/gh/mzlogin/rouge-themes@master/dist' %} diff --git a/_includes/page.html b/_includes/page.html deleted file mode 100644 index f8c7f371de..0000000000 --- a/_includes/page.html +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: default ---- - -{% assign assets_base_url = site.url %} -{% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} -{% endif %} -
-
-
-
-
-

{{ page.title }}

- {% if page.subtitle %} -
- - {{ page.subtitle}} - -
- {% endif %} -
-
- -
-
-
- -
-
-
-
- {{ content }} -
- -
- {% include comments.html %} -
-
- -
-
- \ No newline at end of file diff --git a/_includes/sidebar-ad.html b/_includes/sidebar-ad.html index 6fbaf11869..e8e5398e91 100755 --- a/_includes/sidebar-ad.html +++ b/_includes/sidebar-ad.html @@ -1,8 +1,3 @@ {% if site.google.adsense.sidebar and site.url contains 'mafulong.github.io' %} - - {% endif %}% \ No newline at end of file diff --git a/_includes/sidebar-recent-update.html b/_includes/sidebar-recent-update.html new file mode 100755 index 0000000000..68e612e4ab --- /dev/null +++ b/_includes/sidebar-recent-update.html @@ -0,0 +1,11 @@ + +

Popular Posts

+ diff --git a/_includes/sidebar-search.html b/_includes/sidebar-search.html index ed6c900857..f6bdf81bf5 100644 --- a/_includes/sidebar-search.html +++ b/_includes/sidebar-search.html @@ -1,27 +1,50 @@ -

Search

- + + SimpleJekyllSearch({ + searchInput: document.getElementById('search_box'), + resultsContainer: document.getElementById('search_results'), + {% if site.cdn.jsdelivr.enabled and site.url contains 'mafulong.github.io' %} + json: 'https://cdn.jsdelivr.net/gh/mafulong/mafulong.github.io@built/assets/search_data.json', + {% else %} + json: '{{ site.url }}/assets/search_data.json', + {% endif %} + searchResultTemplate: '
  • {title}
  • ', + noResultsText: 'No results found', + limit: {{ site.simple_jekyll_search.limit }}, + fuzzy: false, + exclude: ['Welcome'] + }); + window.onload = function(){ + var query_text = window.location.search.substring(1); + var vars = query_text.split("&"); + for (var i=0;i diff --git a/_includes/visit-stat.html b/_includes/visit-stat.html index b156a3fa45..23fe1ac07a 100644 --- a/_includes/visit-stat.html +++ b/_includes/visit-stat.html @@ -8,11 +8,5 @@ - - - - - -
    {% endif %} \ No newline at end of file diff --git a/_layouts/categories.html b/_layouts/categories.html index d07595da2b..414ab8c0e6 100755 --- a/_layouts/categories.html +++ b/_layouts/categories.html @@ -4,7 +4,7 @@ {% assign assets_base_url = site.url %} {% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} +{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@'| append: site.cdn.jsdelivr.branch %} {% endif %}
    diff --git a/_layouts/default.html b/_layouts/default.html index 9c762fad3d..5d05f3f6a6 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -4,9 +4,8 @@ {% assign assets_base_url = site.url %} {% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} +{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@'| append: site.cdn.jsdelivr.branch %} {% endif %} -{% assign assets_images_url = ' - - - -
    - - - - diff --git a/_layouts/page.html b/_layouts/page.html index 7ba323b0cb..84f121ade0 100755 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -4,7 +4,7 @@ {% assign assets_base_url = site.url %} {% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} +{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@'| append: site.cdn.jsdelivr.branch %} {% endif %}
    @@ -45,6 +45,7 @@

    {{ page.title }}

    {% include sidebar-search.html %} {% include sidebar-categories-cloud.html %} + {% include sidebar-recent-update.html %} {% include sidebar-popular-repo.html %}
    diff --git a/_layouts/post.html b/_layouts/post.html index a544affaf9..93e302cd11 100755 --- a/_layouts/post.html +++ b/_layouts/post.html @@ -4,7 +4,7 @@ {% assign assets_base_url = site.url %} {% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} +{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@'| append: site.cdn.jsdelivr.branch %} {% endif %}
    @@ -61,6 +61,7 @@

    {{ page.title }}

    {% include sidebar-search.html %} {% include sidebar-post-nav.html %} + {% include sidebar-recent-update.html %}
    diff --git a/_layouts/wiki.html b/_layouts/wiki.html deleted file mode 100755 index 68c3ac2e32..0000000000 --- a/_layouts/wiki.html +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: default ---- - -{% assign assets_base_url = site.url %} -{% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} -{% endif %} -
    -
    -
    -
    -
    -

    {{ page.title }}

    -
    -
    -
    - {% include sidebar-qrcode.html %} -
    -
    -
    -
    - -
    -
    -
    - {% include content-header-ad.html %} -
    - {{ content }} - -
    - - {% include content-footer-ad.html %} -
    - {% include comments.html %} -
    -
    -
    - {% include sidebar-search.html %} - {% include sidebar-post-nav.html %} -
    -
    -
    - diff --git a/_posts/Algorithms/leetcode/2022-07-16-2335. Minimum Amount of Time to Fill Cups.md b/_posts/Algorithms/leetcode/2022-07-16-2335. Minimum Amount of Time to Fill Cups.md new file mode 100644 index 0000000000..77e574190c --- /dev/null +++ b/_posts/Algorithms/leetcode/2022-07-16-2335. Minimum Amount of Time to Fill Cups.md @@ -0,0 +1,78 @@ +--- +layout: post +category: leetcode +title: 2335. Minimum Amount of Time to Fill Cups +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-amount-of-time-to-fill-cups/) + +You have a water dispenser that can dispense cold, warm, and hot water. Every second, you can either fill up `2` cups with **different** types of water, or `1` cup of any type of water. + +You are given a **0-indexed** integer array `amount` of length `3` where `amount[0]`, `amount[1]`, and `amount[2]` denote the number of cold, warm, and hot water cups you need to fill respectively. Return *the **minimum** number of seconds needed to fill up all the cups*. + + + +**Example 1:** + +``` +Input: amount = [1,4,2] +Output: 4 +Explanation: One way to fill up the cups is: +Second 1: Fill up a cold cup and a warm cup. +Second 2: Fill up a warm cup and a hot cup. +Second 3: Fill up a warm cup and a hot cup. +Second 4: Fill up a warm cup. +It can be proven that 4 is the minimum number of seconds needed. +``` + +**Example 2:** + +``` +Input: amount = [5,4,4] +Output: 7 +Explanation: One way to fill up the cups is: +Second 1: Fill up a cold cup, and a hot cup. +Second 2: Fill up a cold cup, and a warm cup. +Second 3: Fill up a cold cup, and a warm cup. +Second 4: Fill up a warm cup, and a hot cup. +Second 5: Fill up a cold cup, and a hot cup. +Second 6: Fill up a cold cup, and a warm cup. +Second 7: Fill up a hot cup. +``` + +**Example 3:** + +``` +Input: amount = [5,0,0] +Output: 5 +Explanation: Every second, we fill up a cold cup. +``` + + + +**Constraints:** + +- `amount.length == 3` +- `0 <= amount[i] <= 100` + +## solution + +We can use the technique of greedy and category discussion. + +At every step only choose the max-2 cups to be filled. + +```python +class Solution: + def fillCups(self, amount: List[int]) -> int: + a, b, c = sorted(amount, reverse=True) + if b + c < a: + return a + t = b + c - a + if t % 2 == 0: + return (t // 2) + a + # t is odd + return ((t - 1) // 2) + a + 1 +``` + diff --git a/_posts/Algorithms/leetcode/2022-07-16-2336. Smallest Number in Infinite Set.md b/_posts/Algorithms/leetcode/2022-07-16-2336. Smallest Number in Infinite Set.md new file mode 100644 index 0000000000..9ced85dc92 --- /dev/null +++ b/_posts/Algorithms/leetcode/2022-07-16-2336. Smallest Number in Infinite Set.md @@ -0,0 +1,81 @@ +--- +layout: post +category: leetcode +title: 2336. Smallest Number in Infinite Set +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/smallest-number-in-infinite-set/) + +You have a set which contains all positive integers `[1, 2, 3, 4, 5, ...]`. + +Implement the `SmallestInfiniteSet` class: + +- `SmallestInfiniteSet()` Initializes the **SmallestInfiniteSet** object to contain **all** positive integers. +- `int popSmallest()` **Removes** and returns the smallest integer contained in the infinite set. +- `void addBack(int num)` **Adds** a positive integer `num` back into the infinite set, if it is **not** already in the infinite set. + + + +**Example 1:** + +``` +Input +["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"] +[[], [2], [], [], [], [1], [], [], []] +Output +[null, null, 1, 2, 3, null, 1, 4, 5] + +Explanation +SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet(); +smallestInfiniteSet.addBack(2); // 2 is already in the set, so no change is made. +smallestInfiniteSet.popSmallest(); // return 1, since 1 is the smallest number, and remove it from the set. +smallestInfiniteSet.popSmallest(); // return 2, and remove it from the set. +smallestInfiniteSet.popSmallest(); // return 3, and remove it from the set. +smallestInfiniteSet.addBack(1); // 1 is added back to the set. +smallestInfiniteSet.popSmallest(); // return 1, since 1 was added back to the set and + // is the smallest number, and remove it from the set. +smallestInfiniteSet.popSmallest(); // return 4, and remove it from the set. +smallestInfiniteSet.popSmallest(); // return 5, and remove it from the set. +``` + + + +**Constraints:** + +- `1 <= num <= 1000` +- At most `1000` calls will be made **in total** to `popSmallest` and `addBack`. + +## solution + +One way, we can use the heapq + set. Another way, we can divide it into two parts, maintain the smallest num and a sortedset so that no initial heap in the first. + +```python +import heapq + + +class SmallestInfiniteSet: + + def __init__(self): + self.h = list(range(1, 1001, 1)) + self.s = set(self.h) + heapq.heapify(self.h) + + def popSmallest(self) -> int: + if self.h: + r = heapq.heappop(self.h) + self.s.remove(r) + return r + return -1 + + def addBack(self, num: int) -> None: + if num in self.s: + return + self.s.add(num) + heapq.heappush(self.h, num) + +``` + + + diff --git "a/_posts/Algorithms/leetcode/2022-08-18-2375. \346\240\271\346\215\256\346\250\241\345\274\217\344\270\262\346\236\204\351\200\240\346\234\200\345\260\217\346\225\260\345\255\227.md" "b/_posts/Algorithms/leetcode/2022-08-18-2375. \346\240\271\346\215\256\346\250\241\345\274\217\344\270\262\346\236\204\351\200\240\346\234\200\345\260\217\346\225\260\345\255\227.md" new file mode 100644 index 0000000000..d918b09610 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-08-18-2375. \346\240\271\346\215\256\346\250\241\345\274\217\344\270\262\346\236\204\351\200\240\346\234\200\345\260\217\346\225\260\345\255\227.md" @@ -0,0 +1,79 @@ +--- +layout: post +category: leetcode +title: 2375. 根据模式串构造最小数字 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/construct-smallest-number-from-di-string/) + +给你下标从 **0** 开始、长度为 `n` 的字符串 `pattern` ,它包含两种字符,`'I'` 表示 **上升** ,`'D'` 表示 **下降** 。 + +你需要构造一个下标从 **0** 开始长度为 `n + 1` 的字符串,且它要满足以下条件: + +- `num` 包含数字 `'1'` 到 `'9'` ,其中每个数字 **至多** 使用一次。 +- 如果 `pattern[i] == 'I'` ,那么 `num[i] < num[i + 1]` 。 +- 如果 `pattern[i] == 'D'` ,那么 `num[i] > num[i + 1]` 。 + +请你返回满足上述条件字典序 **最小** 的字符串 `num`。 + + + +**示例 1:** + +``` +输入:pattern = "IIIDIDDD" +输出:"123549876" +解释: +下标 0 ,1 ,2 和 4 处,我们需要使 num[i] < num[i+1] 。 +下标 3 ,5 ,6 和 7 处,我们需要使 num[i] > num[i+1] 。 +一些可能的 num 的值为 "245639871" ,"135749862" 和 "123849765" 。 +"123549876" 是满足条件最小的数字。 +注意,"123414321" 不是可行解因为数字 '1' 使用次数超过 1 次。 +``` + +**示例 2:** + +``` +输入:pattern = "DDD" +输出:"4321" +解释: +一些可能的 num 的值为 "9876" ,"7321" 和 "8742" 。 +"4321" 是满足条件最小的数字。 +``` + + + +**提示:** + +- `1 <= pattern.length <= 8` +- `pattern` 只包含字符 `'I'` 和 `'D'` 。 + + +## solution + +```python +''' +贪心: 数字其实就是123456789,遇到连续的D,就将连续的数字reverse, 其实就是找连续的I,连续的D,然后分段。 +''' +class Solution: + def smallestNumber(self, pattern: str) -> str: + n = len(pattern) + i = 0 + ans = [] + while i < n: + if i and pattern[i] == "I": + i += 1 + while i < n and pattern[i] == "I": + i += 1 + ans.append(i) + i0 = i + while i < n and pattern[i] == "D": + i += 1 + for x in range(i + 1, i0, -1): + ans.append(x) + return "".join(map(str, ans)) + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-08-27-2386. \346\211\276\345\207\272\346\225\260\347\273\204\347\232\204\347\254\254 K \345\244\247\345\222\214.md" "b/_posts/Algorithms/leetcode/2022-08-27-2386. \346\211\276\345\207\272\346\225\260\347\273\204\347\232\204\347\254\254 K \345\244\247\345\222\214.md" new file mode 100644 index 0000000000..3184ef4b94 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-08-27-2386. \346\211\276\345\207\272\346\225\260\347\273\204\347\232\204\347\254\254 K \345\244\247\345\222\214.md" @@ -0,0 +1,136 @@ +--- +layout: post +category: leetcode +title: 2386. 找出数组的第 K 大和 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/find-the-k-sum-of-an-array/) + +给你一个整数数组 `nums` 和一个 **正** 整数 `k` 。你可以选择数组的任一 **子序列** 并且对其全部元素求和。 + +数组的 **第 k 大和** 定义为:可以获得的第 `k` 个 **最大** 子序列和(子序列和允许出现重复) + +返回数组的 **第 k 大和** 。 + +子序列是一个可以由其他数组删除某些或不删除元素排生而来的数组,且派生过程不改变剩余元素的顺序。 + +**注意:**空子序列的和视作 `0` 。 + + + +**示例 1:** + +``` +输入:nums = [2,4,-2], k = 5 +输出:2 +解释:所有可能获得的子序列和列出如下,按递减顺序排列: +- 6、4、4、2、2、0、0、-2 +数组的第 5 大和是 2 。 +``` + +**示例 2:** + +``` +输入:nums = [1,-2,3,4,-10,12], k = 16 +输出:10 +解释:数组的第 16 大和是 10 。 +``` + + + +**提示:** + +- `n == nums.length` +- `1 <= n <= 105` +- `-109 <= nums[i] <= 109` +- `1 <= k <= min(2000, 2n)` + +## solution + +[参考](https://leetcode.cn/problems/find-the-k-sum-of-an-array/solution/zhuan-huan-dui-by-endlesscheng-8yiq/) + +要记住 + +- 数组有序后,可以用堆动态生成子序列的和,按递增顺序,每取一个后便加下一个,同时当前也可减掉。 +- 回溯只要控制深度复杂度也可观,可用于二分等 + + + +优先队列做法 + +```python + +class Solution: + def kSum(self, nums: List[int], k: int) -> int: + s = 0 + for i, x in enumerate(nums): + if x >= 0: + s += x + else: + nums[i] = -x + import heapq + h = [(-s, 0)] + nums.sort() + for _ in range(k - 1): + l, i = heapq.heappop(h) + if i < len(nums): + heapq.heappush(h, (l + nums[i], i + 1)) + if i: + heapq.heappush(h, (l + nums[i] - nums[i - 1], i + 1)) + return - h[0][0] +``` + + + +二分做法 + +```python +class BinarySearch: + # If you wanna binary search big integer, plz set data range, which can be used as same as big integer low and high + # find the first index that value >= val + def bisect_left(data, val, key=None): + l, r = 0, len(data) - 1 + if key is None: + key = lambda x: data[mid] + while l <= r: + mid = (l + r) // 2 + if key(mid) >= val: + r = mid - 1 + else: + l = mid + 1 + return l +class Solution: + def kSum(self, nums: List[int], k: int) -> int: + s = 0 + for i, x in enumerate(nums): + if x >= 0: + s += x + else: + nums[i] = -x + total = sum(nums) + nums.sort() + + # 不超过limit的子序列数量 + def count(limit): + cnt = 0 + + # 算i之后的子序列数量,不包括空序列 + def f(cur, i): + nonlocal cnt + if cnt >= k - 1 or i >= len(nums) or cur + nums[i] > limit: + return + cnt += 1 + f(cur + nums[i], i + 1) + f(cur, i + 1) + + f(0, 0) + return cnt + + j = BinarySearch.bisect_left(range(total), k - 1, key=count) + + return s - j + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-09-2430. \345\257\271\345\255\227\346\257\215\344\270\262\345\217\257\346\211\247\350\241\214\347\232\204\346\234\200\345\244\247\345\210\240\351\231\244\346\225\260.md" "b/_posts/Algorithms/leetcode/2022-10-09-2430. \345\257\271\345\255\227\346\257\215\344\270\262\345\217\257\346\211\247\350\241\214\347\232\204\346\234\200\345\244\247\345\210\240\351\231\244\346\225\260.md" new file mode 100644 index 0000000000..c3ee467c5d --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-09-2430. \345\257\271\345\255\227\346\257\215\344\270\262\345\217\257\346\211\247\350\241\214\347\232\204\346\234\200\345\244\247\345\210\240\351\231\244\346\225\260.md" @@ -0,0 +1,131 @@ +--- +layout: post +category: leetcode +title: 2430. 对字母串可执行的最大删除数 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/maximum-deletions-on-a-string/) + +给你一个仅由小写英文字母组成的字符串 `s` 。在一步操作中,你可以: + +- 删除 **整个字符串** `s` ,或者 +- 对于满足 `1 <= i <= s.length / 2` 的任意 `i` ,如果 `s` 中的 **前** `i` 个字母和接下来的 `i` 个字母 **相等** ,删除 **前** `i` 个字母。 + +例如,如果 `s = "ababc"` ,那么在一步操作中,你可以删除 `s` 的前两个字母得到 `"abc"` ,因为 `s` 的前两个字母和接下来的两个字母都等于 `"ab"` 。 + +返回删除 `s` 所需的最大操作数。 + + + +**示例 1:** + +``` +输入:s = "abcabcdabc" +输出:2 +解释: +- 删除前 3 个字母("abc"),因为它们和接下来 3 个字母相等。现在,s = "abcdabc"。 +- 删除全部字母。 +一共用了 2 步操作,所以返回 2 。可以证明 2 是所需的最大操作数。 +注意,在第二步操作中无法再次删除 "abc" ,因为 "abc" 的下一次出现并不是位于接下来的 3 个字母。 +``` + +**示例 2:** + +``` +输入:s = "aaabaab" +输出:4 +解释: +- 删除第一个字母("a"),因为它和接下来的字母相等。现在,s = "aabaab"。 +- 删除前 3 个字母("aab"),因为它们和接下来 3 个字母相等。现在,s = "aab"。 +- 删除第一个字母("a"),因为它和接下来的字母相等。现在,s = "ab"。 +- 删除全部字母。 +一共用了 4 步操作,所以返回 4 。可以证明 4 是所需的最大操作数。 +``` + +**示例 3:** + +``` +输入:s = "aaaaa" +输出:5 +解释:在每一步操作中,都可以仅删除 s 的第一个字母。 +``` + + + +**提示:** + +- `1 <= s.length <= 4000` +- `s` 仅由小写英文字母组成 + +## solution + +[参考](https://leetcode.cn/problems/maximum-deletions-on-a-string/solution/xian-xing-dppythonjavacgo-by-endlesschen-gpx9/) 不加特例判断会超时,因此使用hash string更优 + +```python +class Solution: + def deleteString(self, s: str) -> int: + n = len(s) + if len(set(s)) == 1: return n # 特判全部相同的情况 + lcp = [[0] * (n + 1) for _ in range(n + 1)] # lcp[i][j] 表示 s[i:] 和 s[j:] 的最长公共前缀 + for i in range(n - 1, -1, -1): + for j in range(n - 1, i, -1): + if s[i] == s[j]: + lcp[i][j] = lcp[i + 1][j + 1] + 1 + f = [0] * n + for i in range(n - 1, -1, -1): + for j in range(1, (n - i) // 2 + 1): + if lcp[i][i + j] >= j: # 说明 s[i:i+j] == s[i+j:i+2*j] + f[i] = max(f[i], f[i + j]) + f[i] += 1 + return f[0] +``` + +hash string也会超时 + + + +```python +class StringHash: + def __init__(self, s=""): + self.MOD = 998244353 + self.BASE = 131 + # 计算前缀哈希值 + n = len(s) + P = [0] * (n + 1) + P[0] = 1 + for i in range(1, n + 1, 1): + P[i] = P[i - 1] * self.BASE % self.MOD + H = [0] * (n + 1) + for i in range(1, n + 1, 1): + H[i] = (H[i - 1] * self.BASE + ord(s[i - 1])) % self.MOD + self.H = H + self.P = P + + ''' + s的[l,r]区间的hash值,闭区间, l从1开始 + ''' + + def get_hash(self, l=0, r=0): + l, r = l + 1, r + 1 + return (self.H[r] - self.H[l - 1] * self.P[r - l + 1] % self.MOD + self.MOD) % self.MOD + + +class Solution: + def deleteString(self, s: str) -> int: + n = len(s) + dp = [1] * n + hash = StringHash(s) + for i in range(n - 1, -1, -1): + for j in range(i + 1, n): + if (j - i + 1) % 2 == 0: + mid = (i + j) // 2 + if hash.get_hash(i, mid) == hash.get_hash(mid+1, j): + # print(i, j, mid) + dp[i] = max(dp[i], dp[mid+1]+1) + # print(dp[i]) + # print(dp) + return dp[0] +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-09-6202. \344\275\277\347\224\250\346\234\272\345\231\250\344\272\272\346\211\223\345\215\260\345\255\227\345\205\270\345\272\217\346\234\200\345\260\217\347\232\204\345\255\227\347\254\246\344\270\262.md" "b/_posts/Algorithms/leetcode/2022-10-09-6202. \344\275\277\347\224\250\346\234\272\345\231\250\344\272\272\346\211\223\345\215\260\345\255\227\345\205\270\345\272\217\346\234\200\345\260\217\347\232\204\345\255\227\347\254\246\344\270\262.md" new file mode 100644 index 0000000000..949501f164 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-09-6202. \344\275\277\347\224\250\346\234\272\345\231\250\344\272\272\346\211\223\345\215\260\345\255\227\345\205\270\345\272\217\346\234\200\345\260\217\347\232\204\345\255\227\347\254\246\344\270\262.md" @@ -0,0 +1,119 @@ +--- +layout: post +category: leetcode +title: 6202. 使用机器人打印字典序最小的字符串 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string/) + +给你一个字符串 `s` 和一个机器人,机器人当前有一个空字符串 `t` 。执行以下操作之一,直到 `s` 和 `t` **都变成空字符串:** + +- 删除字符串 `s` 的 **第一个** 字符,并将该字符给机器人。机器人把这个字符添加到 `t` 的尾部。 +- 删除字符串 `t` 的 **最后一个** 字符,并将该字符给机器人。机器人将该字符写到纸上。 + +请你返回纸上能写出的字典序最小的字符串。 + + + +**示例 1:** + +``` +输入:s = "zza" +输出:"azz" +解释:用 p 表示写出来的字符串。 +一开始,p="" ,s="zza" ,t="" 。 +执行第一个操作三次,得到 p="" ,s="" ,t="zza" 。 +执行第二个操作三次,得到 p="azz" ,s="" ,t="" 。 +``` + +**示例 2:** + +``` +输入:s = "bac" +输出:"abc" +解释:用 p 表示写出来的字符串。 +执行第一个操作两次,得到 p="" ,s="c" ,t="ba" 。 +执行第二个操作两次,得到 p="ab" ,s="c" ,t="" 。 +执行第一个操作,得到 p="ab" ,s="" ,t="c" 。 +执行第二个操作,得到 p="abc" ,s="" ,t="" 。 +``` + +**示例 3:** + +``` +输入:s = "bdda" +输出:"addb" +解释:用 p 表示写出来的字符串。 +一开始,p="" ,s="bdda" ,t="" 。 +执行第一个操作四次,得到 p="" ,s="" ,t="bdda" 。 +执行第二个操作四次,得到 p="addb" ,s="" ,t="" 。 +``` + + + +**提示:** + +- `1 <= s.length <= 105` +- `s` 只包含小写英文字母。 + +## solution + +本题是经典贪心:求出栈序列的最小字典序。 + +我们首先将题目描述进行转化:有一个初始为空的栈,给定字符的入栈顺序,求字典序最小的出栈序列。 + +```python +class Solution: + def robotWithString(self, s: str) -> str: + stack = [] + import collections + counter = collections.Counter(s) + ans = [] + ''' + 栈模拟, 栈顶是最小的就弹出,否则就继续入栈等有更小的弹出 + ''' + for c in s: + # 判断后面是否还有比它小的 + counter[c] -= 1 + minc = c + for x in string.ascii_lowercase: + if counter[x] > 0: + minc = x + break + # print(c, minc) + stack.append(c) + while stack and stack[-1] <= minc: + ans.append(stack.pop()) + return "".join(ans + stack[::-1]) + + +class Solution: + def robotWithString(self, s: str) -> str: + stack = [] + ans = [] + minc = [None] * (len(s) + 1) + t = 'z' + for i in range(len(s) - 1, -1, -1): + t = min(t, s[i]) + minc[i] = t + minc[-1] = chr(ord('z') + 1) + ''' + 栈模拟, 栈顶是最小的就弹出,否则就继续入栈等有更小的弹出 + ''' + for i, c in enumerate(s): + # 判断后面是否还有比它小的 + stack.append(c) + while stack and stack[-1] <= minc[i + 1]: + ans.append(stack.pop()) + return "".join(ans + stack) + + +if __name__ == '__main__': + f = Solution().robotWithString + print("actual:", f("caba"), "should:", None) + print("actual:", f("bac"), "should:", None) + print("actual:", f("bdda"), "should:", None) +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-16-6207. \347\273\237\350\256\241\345\256\232\347\225\214\345\255\220\346\225\260\347\273\204\347\232\204\346\225\260\347\233\256.md" "b/_posts/Algorithms/leetcode/2022-10-16-6207. \347\273\237\350\256\241\345\256\232\347\225\214\345\255\220\346\225\260\347\273\204\347\232\204\346\225\260\347\233\256.md" new file mode 100644 index 0000000000..3b8f7268b5 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-16-6207. \347\273\237\350\256\241\345\256\232\347\225\214\345\255\220\346\225\260\347\273\204\347\232\204\346\225\260\347\233\256.md" @@ -0,0 +1,71 @@ +--- +layout: post +category: leetcode +title: 6207. 统计定界子数组的数目 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/count-subarrays-with-fixed-bounds/) + +给你一个整数数组 `nums` 和两个整数 `minK` 以及 `maxK` 。 + +`nums` 的定界子数组是满足下述条件的一个子数组: + +- 子数组中的 **最小值** 等于 `minK` 。 +- 子数组中的 **最大值** 等于 `maxK` 。 + +返回定界子数组的数目。 + +子数组是数组中的一个连续部分。 + + + +**示例 1:** + +``` +输入:nums = [1,3,5,2,7,5], minK = 1, maxK = 5 +输出:2 +解释:定界子数组是 [1,3,5] 和 [1,3,5,2] 。 +``` + +**示例 2:** + +``` +输入:nums = [1,1,1,1], minK = 1, maxK = 1 +输出:10 +解释:nums 的每个子数组都是一个定界子数组。共有 10 个子数组。 +``` + + + +**提示:** + +- `2 <= nums.length <= 105` +- `1 <= nums[i], minK, maxK <= 106` + +## solution + +双指针 + +```python +class Solution: + def countSubarrays(self, nums: List[int], minK: int, maxK: int) -> int: + left = -1 + minI, maxI = -1, -1 + ans = 0 + for i, v in enumerate(nums): + if v < minK or v > maxK: + # 清空窗口 + left = i + minI = maxI = -1 + continue + if v == minK: + minI = i + if v == maxK: + maxI = i + if maxI != -1 and minI != -1: + ans += min(minI, maxI) - left + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-16-6211. \345\210\233\345\273\272\344\273\267\345\200\274\347\233\270\345\220\214\347\232\204\350\277\236\351\200\232\345\235\227.md" "b/_posts/Algorithms/leetcode/2022-10-16-6211. \345\210\233\345\273\272\344\273\267\345\200\274\347\233\270\345\220\214\347\232\204\350\277\236\351\200\232\345\235\227.md" new file mode 100644 index 0000000000..e733848d31 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-16-6211. \345\210\233\345\273\272\344\273\267\345\200\274\347\233\270\345\220\214\347\232\204\350\277\236\351\200\232\345\235\227.md" @@ -0,0 +1,90 @@ +--- +layout: post +category: leetcode +title: 6211. 创建价值相同的连通块 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/create-components-with-same-value/) + +有一棵 `n` 个节点的无向树,节点编号为 `0` 到 `n - 1` 。 + +给你一个长度为 `n` 下标从 **0** 开始的整数数组 `nums` ,其中 `nums[i]` 表示第 `i` 个节点的值。同时给你一个长度为 `n - 1` 的二维整数数组 `edges` ,其中 `edges[i] = [ai, bi]` 表示节点 `ai` 与 `bi` 之间有一条边。 + +你可以 **删除** 一些边,将这棵树分成几个连通块。一个连通块的 **价值** 定义为这个连通块中 **所有** 节点 `i` 对应的 `nums[i]` 之和。 + +你需要删除一些边,删除后得到的各个连通块的价值都相等。请返回你可以删除的边数 **最多** 为多少。 + + + +**示例 1:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210161356949.png) + +``` +输入:nums = [6,2,2,2,6], edges = [[0,1],[1,2],[1,3],[3,4]] +输出:2 +解释:上图展示了我们可以删除边 [0,1] 和 [3,4] 。得到的连通块为 [0] ,[1,2,3] 和 [4] 。每个连通块的价值都为 6 。可以证明没有别的更好的删除方案存在了,所以答案为 2 。 +``` + +**示例 2:** + +``` +输入:nums = [2], edges = [] +输出:0 +解释:没有任何边可以删除。 +``` + + + +**提示:** + +- `1 <= n <= 2 * 104` +- `nums.length == n` +- `1 <= nums[i] <= 50` +- `edges.length == n - 1` +- `edges[i].length == 2` +- `0 <= edges[i][0], edges[i][1] <= n - 1` +- `edges` 表示一棵合法的树。 + +## solution + +[参考](https://leetcode.cn/problems/create-components-with-same-value/solution/by-endlesscheng-u03q/) + +```python +class Solution: + def componentValue(self, nums: List[int], edges: List[List[int]]) -> int: + n = len(nums) + import collections + graph = collections.defaultdict(list) + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + total = sum(nums) + target = 0 + + # return the sum of subtree mod target + def dfs(u, fa=-1): + cur = nums[u] + for v in graph[u]: + if v == fa: continue + t = dfs(v, u) + if t == -1: + return -1 + cur += t + if cur == target: + return 0 + elif cur > target: + return -1 + return cur + + for k in range(min(n, total // max(nums)), 1, -1): + if total % k == 0: + target = total // k + if dfs(0, -1) == 0: + return k - 1 + return 0 + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-23-407. \346\216\245\351\233\250\346\260\264 II.md" "b/_posts/Algorithms/leetcode/2022-10-23-407. \346\216\245\351\233\250\346\260\264 II.md" new file mode 100644 index 0000000000..74c45a99b1 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-23-407. \346\216\245\351\233\250\346\260\264 II.md" @@ -0,0 +1,83 @@ +--- +layout: post +category: leetcode +title: 407. 接雨水 II +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/trapping-rain-water-ii/) + +给你一个 `m x n` 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。 + + + +**示例 1:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210232302169.jpg) + +``` +输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] +输出: 4 +解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。 +``` + +**示例 2:** + +![img](https://assets.leetcode.com/uploads/2021/04/08/trap2-3d.jpg) + +``` +输入: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]] +输出: 10 +``` + + + +**提示:** + +- `m == heightMap.length` +- `n == heightMap[i].length` +- `1 <= m, n <= 200` +- `0 <= heightMap[i][j] <= 2 * 104` + + + +## solution + +[参考](https://leetcode.cn/problems/trapping-rain-water-ii/solution/gong-shui-san-xie-jing-dian-dijkstra-yun-13ik/) + +Dijkstra. 从外向内扩展,路径定义为water高度。 + +```python +class Directions: + dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + diagonal_dirs = [(1, 1), (1, -1), (-1, 1), (-1, -1)] + all_dirs = dirs + diagonal_dirs + +class Solution: + def trapRainWater(self, heightMap: List[List[int]]) -> int: + m, n = len(heightMap), len(heightMap[0]) + h = [] + import heapq + dist = {} + for i in range(m): + heapq.heappush(h, (heightMap[i][0], i, 0)) + heapq.heappush(h, (heightMap[i][n - 1], i, n - 1)) + for j in range(1, n - 1): + heapq.heappush(h, (heightMap[0][j], 0, j)) + heapq.heappush(h, (heightMap[m - 1][j], m - 1, j)) + ans = 0 + while h: + ph, x, y = heapq.heappop(h) + if (x, y) in dist: continue + dist[(x, y)] = max(heightMap[x][y], ph) + if ph > heightMap[x][y]: + ans += ph - heightMap[x][y] + for dx, dy in Directions.dirs: + nx, ny = x + dx, y + dy + if 0 <= nx < m and 0 <= ny < n and (nx, ny) not in dist: + heapq.heappush(h, (dist[(x, y)], nx, ny)) + # print(dist) + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-23-6216. \344\275\277\346\225\260\347\273\204\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\345\274\200\351\224\200.md" "b/_posts/Algorithms/leetcode/2022-10-23-6216. \344\275\277\346\225\260\347\273\204\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\345\274\200\351\224\200.md" new file mode 100644 index 0000000000..140d0c7964 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-23-6216. \344\275\277\346\225\260\347\273\204\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\345\274\200\351\224\200.md" @@ -0,0 +1,75 @@ +--- +layout: post +category: leetcode +title: 6216. 使数组相等的最小开销 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-cost-to-make-array-equal/) + +给你两个下标从 **0** 开始的数组 `nums` 和 `cost` ,分别包含 `n` 个 **正** 整数。 + +你可以执行下面操作 **任意** 次: + +- 将 `nums` 中 **任意** 元素增加或者减小 `1` 。 + +对第 `i` 个元素执行一次操作的开销是 `cost[i]` 。 + +请你返回使 `nums` 中所有元素 **相等** 的 **最少** 总开销。 + + + +**示例 1:** + +``` +输入:nums = [1,3,5,2], cost = [2,3,1,14] +输出:8 +解释:我们可以执行以下操作使所有元素变为 2 : +- 增加第 0 个元素 1 次,开销为 2 。 +- 减小第 1 个元素 1 次,开销为 3 。 +- 减小第 2 个元素 3 次,开销为 1 + 1 + 1 = 3 。 +总开销为 2 + 3 + 3 = 8 。 +这是最小开销。 +``` + +**示例 2:** + +``` +输入:nums = [2,2,2,2,2], cost = [4,2,8,1,3] +输出:0 +解释:数组中所有元素已经全部相等,不需要执行额外的操作。 +``` + + + +**提示:** + +- `n == nums.length == cost.length` +- `1 <= n <= 105` +- `1 <= nums[i], cost[i] <= 106` + +## solution + +[参考](https://leetcode.cn/circle/discuss/uO4WuN/) + +![image-20221023180324838](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210231803876.png) + +```python +class Solution: + def minCost(self, nums: List[int], cost: List[int]) -> int: + d = list(zip(nums, cost)) + d = sorted(d) + mid = None + count = 0 + total = sum(cost) + for a, b in d: + count += b + if count >= total // 2: + mid = a + break + return sum(abs(a - mid) * c for a, c in d) + + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-23-6217. \344\275\277\346\225\260\347\273\204\347\233\270\344\274\274\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" "b/_posts/Algorithms/leetcode/2022-10-23-6217. \344\275\277\346\225\260\347\273\204\347\233\270\344\274\274\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" new file mode 100644 index 0000000000..1370155333 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-23-6217. \344\275\277\346\225\260\347\273\204\347\233\270\344\274\274\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" @@ -0,0 +1,103 @@ +--- +layout: post +category: leetcode +title: 6217. 使数组相似的最少操作次数 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-number-of-operations-to-make-arrays-similar/) + +给你两个正整数数组 `nums` 和 `target` ,两个数组长度相等。 + +在一次操作中,你可以选择两个 **不同** 的下标 `i` 和 `j` ,其中 `0 <= i, j < nums.length` ,并且: + +- 令 `nums[i] = nums[i] + 2` 且 +- 令 `nums[j] = nums[j] - 2` 。 + +如果两个数组中每个元素出现的频率相等,我们称两个数组是 **相似** 的。 + +请你返回将 `nums` 变得与 `target` 相似的最少操作次数。测试数据保证 `nums` 一定能变得与 `target` 相似。 + + + +**示例 1:** + +``` +输入:nums = [8,12,6], target = [2,14,10] +输出:2 +解释:可以用两步操作将 nums 变得与 target 相似: +- 选择 i = 0 和 j = 2 ,nums = [10,12,4] 。 +- 选择 i = 1 和 j = 2 ,nums = [10,14,2] 。 +2 次操作是最少需要的操作次数。 +``` + +**示例 2:** + +``` +输入:nums = [1,2,5], target = [4,1,3] +输出:1 +解释:一步操作可以使 nums 变得与 target 相似: +- 选择 i = 1 和 j = 2 ,nums = [1,4,3] 。 +``` + +**示例 3:** + +``` +输入:nums = [1,1,1,1,1], target = [1,1,1,1,1] +输出:0 +解释:数组 nums 已经与 target 相似。 +``` + + + +**提示:** + +- `n == nums.length == target.length` +- `1 <= n <= 105` +- `1 <= nums[i], target[i] <= 106` +- `nums` 一定可以变得与 `target` 相似。 + +## solution + +[参考](https://leetcode.cn/problems/minimum-number-of-operations-to-make-arrays-similar/solution/by-endlesscheng-lusx/) + +![image-20221023183508545](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210231835582.png) + +```python +class Solution: + def makeSimilar(self, nums: List[int], target: List[int]) -> int: + nums = sorted(nums, key=lambda x: (x % 2, x)) + target = sorted(target, key=lambda x: (x % 2, x)) + ans = 0 + for i in range(len(nums)): + ans += abs(nums[i] - target[i]) + return ans // 4 + + +class Solution: + def makeSimilar(self, nums: List[int], target: List[int]) -> int: + nums = sorted(nums) + target = sorted(target) + a = [] + b = [] + for v in nums: + if v % 2 == 1: + a.append(v) + else: + b.append(v) + c = [] + d = [] + for v in target: + if v % 2 == 1: + c.append(v) + else: + d.append(v) + ans = 0 + for i in range(len(a)): + ans += abs(a[i] - c[i]) // 2 + for i in range(len(b)): + ans += abs(b[i] - d[i]) // 2 + return ans // 2 +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-24-915. \345\210\206\345\211\262\346\225\260\347\273\204.md" "b/_posts/Algorithms/leetcode/2022-10-24-915. \345\210\206\345\211\262\346\225\260\347\273\204.md" new file mode 100644 index 0000000000..e6de0d5396 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-24-915. \345\210\206\345\211\262\346\225\260\347\273\204.md" @@ -0,0 +1,66 @@ +--- +layout: post +category: leetcode +title: 915. 分割数组 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/partition-array-into-disjoint-intervals/) + +给定一个数组 `nums` ,将其划分为两个连续子数组 `left` 和 `right`, 使得: + +- `left` 中的每个元素都小于或等于 `right` 中的每个元素。 +- `left` 和 `right` 都是非空的。 +- `left` 的长度要尽可能小。 + +*在完成这样的分组后返回 `left` 的 **长度*** 。 + +用例可以保证存在这样的划分方法。 + + + +**示例 1:** + +``` +输入:nums = [5,0,3,8,6] +输出:3 +解释:left = [5,0,3],right = [8,6] +``` + +**示例 2:** + +``` +输入:nums = [1,1,1,0,6,12] +输出:4 +解释:left = [1,1,1,0],right = [6,12] +``` + + + +**提示:** + +- `2 <= nums.length <= 105` +- `0 <= nums[i] <= 106` +- 可以保证至少有一种方法能够按题目所描述的那样对 `nums` 进行划分。 + +## solution + +下面为一次遍历,使用后悔法。 + +```python +class Solution: + def partitionDisjoint(self, nums: List[int]) -> int: + n = len(nums) + leftpos = 0 + leftmax = nums[0] + currentmax = nums[0] + for i,v in enumerate(nums): + currentmax = max(currentmax, v) + if v < leftmax: + leftpos = i + leftmax = currentmax + return leftpos + 1 + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-10-29-862. \345\222\214\350\207\263\345\260\221\344\270\272 K \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" "b/_posts/Algorithms/leetcode/2022-10-29-862. \345\222\214\350\207\263\345\260\221\344\270\272 K \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" new file mode 100644 index 0000000000..6b83df161c --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-10-29-862. \345\222\214\350\207\263\345\260\221\344\270\272 K \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204.md" @@ -0,0 +1,120 @@ +--- +layout: post +category: leetcode +title: 862. 和至少为 K 的最短子数组 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) + +给你一个整数数组 `nums` 和一个整数 `k` ,找出 `nums` 中和至少为 `k` 的 **最短非空子数组** ,并返回该子数组的长度。如果不存在这样的 **子数组** ,返回 `-1` 。 + +**子数组** 是数组中 **连续** 的一部分。 + + + + + +**示例 1:** + +``` +输入:nums = [1], k = 1 +输出:1 +``` + +**示例 2:** + +``` +输入:nums = [1,2], k = 4 +输出:-1 +``` + +**示例 3:** + +``` +输入:nums = [2,-1,2], k = 3 +输出:3 +``` + + + +**提示:** + +- `1 <= nums.length <= 105` +- `-105 <= nums[i] <= 105` +- `1 <= k <= 109` + +## solution + +单调队列 + +```python +''' +主要是去除无用数据,维护右端点 +可以单调栈二分,也可以单调队列 +参考: https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/solution/liang-zhang-tu-miao-dong-dan-diao-dui-li-9fvh/ +''' + +class Solution: + def shortestSubarray(self, nums: List[int], k: int) -> int: + ans = float('inf') + n = len(nums) + pre = [0] + for v in nums: + pre.append(pre[-1] + v) + import collections + q = collections.deque() + # 单调队列,j< i, 向右, 如果s[i] - s[j] >= k, j则不会再作为可作为答案的左端点了; 递增队列 对于s[j] >= s[i], 则j也可以舍弃 + for i, v in enumerate(pre): + while q and pre[q[-1]] >= v: + q.pop() + q.append(i) + while q and v - pre[q[0]] >= k: + ans = min(ans, i - q[0]) + q.popleft() + return ans if ans != float('inf') else -1 + + +``` + + + +```python +# 单调栈 + 二分 +class BinarySearch: + # If you wanna binary search big integer, plz set data range, which can be used as same as big integer low and high + # find the first index that value >= val + def bisect_left(data, val, l=None, r=None, key=None): + if l is None or r is None: + l, r = 0, len(data) - 1 + if key is None: + key = lambda x: data[mid] + while l <= r: + mid = (l + r) // 2 + if key(mid) >= val: + r = mid - 1 + else: + l = mid + 1 + return l + +class Solution: + def shortestSubarray(self, nums: List[int], k: int) -> int: + ans = float('inf') + pre = [0] + for v in nums: + pre.append(pre[-1] + v) + # print(pre) + q = [] + # 单调递增栈, 栈上二分 + for i, v in enumerate(pre): + while q and pre[q[-1]] >= v: + q.pop() + q.append(i) + t = BinarySearch.bisect_left(q, True, key=lambda x: v - pre[q[x]] < k) + if t - 1 >= 0: + ans = min(ans, i - q[t - 1]) + return ans if ans != float('inf') else -1 + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-11-04-1674. \344\275\277\346\225\260\347\273\204\344\272\222\350\241\245\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" "b/_posts/Algorithms/leetcode/2022-11-04-1674. \344\275\277\346\225\260\347\273\204\344\272\222\350\241\245\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" new file mode 100644 index 0000000000..2d9cf05345 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-11-04-1674. \344\275\277\346\225\260\347\273\204\344\272\222\350\241\245\347\232\204\346\234\200\345\260\221\346\223\215\344\275\234\346\254\241\346\225\260.md" @@ -0,0 +1,106 @@ +--- +layout: post +category: leetcode +title: 1674. 使数组互补的最少操作次数 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-moves-to-make-array-complementary/) + +给你一个长度为 **偶数** `n` 的整数数组 `nums` 和一个整数 `limit` 。每一次操作,你可以将 `nums` 中的任何整数替换为 `1` 到 `limit` 之间的另一个整数。 + +如果对于所有下标 `i`(**下标从** `0` **开始**),`nums[i] + nums[n - 1 - i]` 都等于同一个数,则数组 `nums` 是 **互补的** 。例如,数组 `[1,2,3,4]` 是互补的,因为对于所有下标 `i` ,`nums[i] + nums[n - 1 - i] = 5` 。 + +返回使数组 **互补** 的 **最少** 操作次数。 + + + +**示例 1:** + +``` +输入:nums = [1,2,4,3], limit = 4 +输出:1 +解释:经过 1 次操作,你可以将数组 nums 变成 [1,2,2,3](加粗元素是变更的数字): +nums[0] + nums[3] = 1 + 3 = 4. +nums[1] + nums[2] = 2 + 2 = 4. +nums[2] + nums[1] = 2 + 2 = 4. +nums[3] + nums[0] = 3 + 1 = 4. +对于每个 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互补的。 +``` + +**示例 2:** + +``` +输入:nums = [1,2,2,1], limit = 2 +输出:2 +解释:经过 2 次操作,你可以将数组 nums 变成 [2,2,2,2] 。你不能将任何数字变更为 3 ,因为 3 > limit 。 +``` + +**示例 3:** + +``` +输入:nums = [1,2,1,2], limit = 2 +输出:0 +解释:nums 已经是互补的。 +``` + + + +**提示:** + +- `n == nums.length` +- `2 <= n <= 105` +- `1 <= nums[i] <= limit <= 105` +- `n` 是偶数。 + +## solution + +假设 `res[x]` 表示的是,`nums[i] + nums[n - 1 - i]` 为 `x` 的时候,需要多少次操作。 + +我们只需要计算出所有的 `x` 对应的 `res[x]`, 取最小值就好了。 + +根据题意,`nums[i] + nums[n - 1 - i]` 最小是 `2`,即将两个数都修改为 `1`;最大是 `2 * limit`,即将两个数都修改成 `limit`。 + +所以,`res[x]` 中 `x` 的取值范围是 `[2, 2 * limit]`。我们用一个 `res[2 * limit + 1]` 的数组就好。 + +```python +class Diff: + def __init__(self, n): + self.n = n + self.diff = [0] * (n + 1) + + # 给[l,r] + val + def add(self, l, r, val): + self.diff[l] += val + self.diff[r + 1] -= val + + # 差分数组还原成原数组 + def restore(self): + ans = [0] * self.n + ans[0] = self.diff[0] + for i in range(1, self.n): + ans[i] = ans[i - 1] + self.diff[i] + return ans + +class Solution: + def minMoves(self, nums: List[int], limit: int) -> int: + diff = Diff(2 * limit + 1) + n = len(nums) + for i in range((len(nums) + 1) // 2): + a, b = nums[i], nums[n - 1 - i] + if a > b: + a, b = b, a + ''' + 0: a+b + 2: [2, a+1) or (b+limit, 2*limit] + 1: + ''' + diff.add(2, a, 2) + diff.add(b + limit + 1, 2 * limit, 2) + diff.add(a + 1, b + limit, 1) + diff.add(a + b, a + b, -1) + # print(diff.restore()) + return min(diff.restore()[2:]) +``` + diff --git "a/_posts/Algorithms/leetcode/2022-11-06-6232. \346\234\200\345\260\217\347\247\273\345\212\250\346\200\273\350\267\235\347\246\273.md" "b/_posts/Algorithms/leetcode/2022-11-06-6232. \346\234\200\345\260\217\347\247\273\345\212\250\346\200\273\350\267\235\347\246\273.md" new file mode 100644 index 0000000000..ddaa802f80 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-11-06-6232. \346\234\200\345\260\217\347\247\273\345\212\250\346\200\273\350\267\235\347\246\273.md" @@ -0,0 +1,102 @@ +--- +layout: post +category: leetcode +title: 6232. 最小移动总距离 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-total-distance-traveled/) + +X 轴上有一些机器人和工厂。给你一个整数数组 `robot` ,其中 `robot[i]` 是第 `i` 个机器人的位置。再给你一个二维整数数组 `factory` ,其中 `factory[j] = [positionj, limitj]` ,表示第 `j` 个工厂的位置在 `positionj` ,且第 `j` 个工厂最多可以修理 `limitj` 个机器人。 + +每个机器人所在的位置 **互不相同** 。每个工厂所在的位置也 **互不相同** 。注意一个机器人可能一开始跟一个工厂在 **相同的位置** 。 + +所有机器人一开始都是坏的,他们会沿着设定的方向一直移动。设定的方向要么是 X 轴的正方向,要么是 X 轴的负方向。当一个机器人经过一个没达到上限的工厂时,这个工厂会维修这个机器人,且机器人停止移动。 + +**任何时刻**,你都可以设置 **部分** 机器人的移动方向。你的目标是最小化所有机器人总的移动距离。 + +请你返回所有机器人移动的最小总距离。测试数据保证所有机器人都可以被维修。 + +**注意:** + +- 所有机器人移动速度相同。 +- 如果两个机器人移动方向相同,它们永远不会碰撞。 +- 如果两个机器人迎面相遇,它们也不会碰撞,它们彼此之间会擦肩而过。 +- 如果一个机器人经过了一个已经达到上限的工厂,机器人会当作工厂不存在,继续移动。 +- 机器人从位置 `x` 到位置 `y` 的移动距离为 `|y - x|` 。 + + + +**示例 1:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211061747130.png) + +``` +输入:robot = [0,4,6], factory = [[2,2],[6,2]] +输出:4 +解释:如上图所示: +- 第一个机器人从位置 0 沿着正方向移动,在第一个工厂处维修。 +- 第二个机器人从位置 4 沿着负方向移动,在第一个工厂处维修。 +- 第三个机器人在位置 6 被第二个工厂维修,它不需要移动。 +第一个工厂的维修上限是 2 ,它维修了 2 个机器人。 +第二个工厂的维修上限是 2 ,它维修了 1 个机器人。 +总移动距离是 |2 - 0| + |2 - 4| + |6 - 6| = 4 。没有办法得到比 4 更少的总移动距离。 +``` + +**示例 2:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211061747289.png) + +``` +输入:robot = [1,-1], factory = [[-2,1],[2,1]] +输出:2 +解释:如上图所示: +- 第一个机器人从位置 1 沿着正方向移动,在第二个工厂处维修。 +- 第二个机器人在位置 -1 沿着负方向移动,在第一个工厂处维修。 +第一个工厂的维修上限是 1 ,它维修了 1 个机器人。 +第二个工厂的维修上限是 1 ,它维修了 1 个机器人。 +总移动距离是 |2 - 1| + |(-2) - (-1)| = 2 。没有办法得到比 2 更少的总移动距离。 +``` + + + +**提示:** + +- `1 <= robot.length, factory.length <= 100` +- `factory[j].length == 2` +- `-109 <= robot[i], positionj <= 109` +- `0 <= limitj <= robot.length` +- 测试数据保证所有机器人都可以被维修。 + +## solution + +动态规划 o(nnm) 一定要往局部思考,想到动态规划才行。 + +```python +class Solution: + def minimumTotalDistance(self, robot: List[int], factory: List[List[int]]) -> int: + robot = sorted(robot) + factory = sorted(factory) + n = len(robot) + + def f(i, j): + if j == 0 and i == 0: + return 0 + if j == 0 or i == 0: + return float('inf') + res = float('inf') + cost = 0 + for k in range(0, factory[j - 1][1] + 1): + if i - k < 0: break + if k > 0: + cost += abs(factory[j - 1][0] - robot[i - k]) + # print(i, j, k, cost) + res = min(res, f(i - k, j - 1) + cost) + # print(i, j, res) + return res + + ans = f(n, len(factory)) + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2022-12-04-6256. \345\260\206\350\212\202\347\202\271\345\210\206\346\210\220\345\260\275\345\217\257\350\203\275\345\244\232\347\232\204\347\273\204.md" "b/_posts/Algorithms/leetcode/2022-12-04-6256. \345\260\206\350\212\202\347\202\271\345\210\206\346\210\220\345\260\275\345\217\257\350\203\275\345\244\232\347\232\204\347\273\204.md" new file mode 100644 index 0000000000..a381b04ca4 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-12-04-6256. \345\260\206\350\212\202\347\202\271\345\210\206\346\210\220\345\260\275\345\217\257\350\203\275\345\244\232\347\232\204\347\273\204.md" @@ -0,0 +1,160 @@ +--- +layout: post +category: leetcode +title: 6256. 将节点分成尽可能多的组 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/divide-nodes-into-the-maximum-number-of-groups/) + +给你一个正整数 `n` ,表示一个 **无向** 图中的节点数目,节点编号从 `1` 到 `n` 。 + +同时给你一个二维整数数组 `edges` ,其中 `edges[i] = [ai, bi]` 表示节点 `ai` 和 `bi` 之间有一条 **双向** 边。注意给定的图可能是不连通的。 + +请你将图划分为 `m` 个组(编号从 **1** 开始),满足以下要求: + +- 图中每个节点都只属于一个组。 +- 图中每条边连接的两个点 `[ai, bi]` ,如果 `ai` 属于编号为 `x` 的组,`bi` 属于编号为 `y` 的组,那么 `|y - x| = 1` 。 + +请你返回最多可以将节点分为多少个组(也就是最大的 `m` )。如果没办法在给定条件下分组,请你返回 `-1` 。 + + + +**示例 1:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212041542634.png) + +``` +输入:n = 6, edges = [[1,2],[1,4],[1,5],[2,6],[2,3],[4,6]] +输出:4 +解释:如上图所示, +- 节点 5 在第一个组。 +- 节点 1 在第二个组。 +- 节点 2 和节点 4 在第三个组。 +- 节点 3 和节点 6 在第四个组。 +所有边都满足题目要求。 +如果我们创建第五个组,将第三个组或者第四个组中任何一个节点放到第五个组,至少有一条边连接的两个节点所属的组编号不符合题目要求。 +``` + +**示例 2:** + +``` +输入:n = 3, edges = [[1,2],[2,3],[3,1]] +输出:-1 +解释:如果我们将节点 1 放入第一个组,节点 2 放入第二个组,节点 3 放入第三个组,前两条边满足题目要求,但第三条边不满足题目要求。 +没有任何符合题目要求的分组方式。 +``` + + + +**提示:** + +- `1 <= n <= 500` +- `1 <= edges.length <= 104` +- `edges[i].length == 2` +- `1 <= ai, bi <= n` +- `ai != bi` +- 两个点之间至多只有一条边。 + +## solution + +``` +二分图才可以分组 / 也可以通过bfs后,比较和start点距离判断,a<->b, 则dis[a]!=dis[b]才可以分组 +``` + +```python + +class Graph: + def bfs(graph, start): + ''' + :param graph: collection.defaultdict(list) + :param start: start point + :return: + ''' + q = collections.deque() + q.append(start) + visit = set() + visit.add(start) + level = 0 + while len(q): + level += 1 + for _ in range(len(q)): + u = q.popleft() + for v in graph[u]: + if v not in visit: + visit.add(v) + q.append(v) + return visit, level + ''' + 划分连同分量, 返回每个联通分量的点list,可以多个联通分量 + ''' + def groups(self, points, graph) -> List[List[int]]: + groups = [] + total_visit = set() + for i in points: + visit = set() + def dfs(u): + nonlocal visit + visit.add(u) + for v in graph[u]: + if v not in visit: + dfs(v) + + if i not in total_visit: + dfs(i) + if visit: + for l in visit: + total_visit.add(l) + groups.append(list(visit)) + return groups + + # 判断是否是二分图,input可以是多个子连通图。graph: 链接表 + def isBipartite(self, graph) -> bool: + ans = True + color = collections.defaultdict(int) + n = len(graph) + + def dfs(i): + nonlocal ans + if not ans: + return + if color[i] == 0: + color[i] = 1 + cur = color[i] + for neighbor in graph[i]: + if color[neighbor] == cur: + ans = False + break + if color[neighbor] == 0: + color[neighbor] = 2 if cur == 1 else 1 + dfs(neighbor) + + for i in range(n): + if color[i] == 0: + dfs(i) + # print(color) + return ans + +class Solution: + def magnificentSets(self, n: int, edges: List[List[int]]) -> int: + graph = collections.defaultdict(list) + for u, v in edges: + graph[u].append(v) + graph[v].append(u) + # 二分图才可以分组 + if not Graph().isBipartite(graph): + return -1 + # 划分联通分量 + groups = Graph().groups(range(1, n + 1), graph) + ans = 0 + for group in groups: + t = 0 + # 对联通分量的每个节点用bfs求最大长度 + for i in group: + t = max(t, Graph.bfs(graph, i)[1]) + ans += t + return ans + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-12-25-6270. \346\257\217\347\247\215\345\255\227\347\254\246\350\207\263\345\260\221\345\217\226 K \344\270\252.md" "b/_posts/Algorithms/leetcode/2022-12-25-6270. \346\257\217\347\247\215\345\255\227\347\254\246\350\207\263\345\260\221\345\217\226 K \344\270\252.md" new file mode 100644 index 0000000000..adcaed6940 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-12-25-6270. \346\257\217\347\247\215\345\255\227\347\254\246\350\207\263\345\260\221\345\217\226 K \344\270\252.md" @@ -0,0 +1,76 @@ +--- +layout: post +category: leetcode +title: 6270. 每种字符至少取 K 个 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right/) + +给你一个由字符 `'a'`、`'b'`、`'c'` 组成的字符串 `s` 和一个非负整数 `k` 。每分钟,你可以选择取走 `s` **最左侧** 还是 **最右侧** 的那个字符。 + +你必须取走每种字符 **至少** `k` 个,返回需要的 **最少** 分钟数;如果无法取到,则返回 `-1` 。 + + + +**示例 1:** + +``` +输入:s = "aabaaaacaabc", k = 2 +输出:8 +解释: +从 s 的左侧取三个字符,现在共取到两个字符 'a' 、一个字符 'b' 。 +从 s 的右侧取五个字符,现在共取到四个字符 'a' 、两个字符 'b' 和两个字符 'c' 。 +共需要 3 + 5 = 8 分钟。 +可以证明需要的最少分钟数是 8 。 +``` + +**示例 2:** + +``` +输入:s = "a", k = 1 +输出:-1 +解释:无法取到一个字符 'b' 或者 'c',所以返回 -1 。 +``` + + + +**提示:** + +- `1 <= s.length <= 105` +- `s` 仅由字母 `'a'`、`'b'`、`'c'` 组成 +- `0 <= k <= s.length` + +## solution + +双指针。 + +```python +class Solution: + def takeCharacters(self, s: str, k: int) -> int: + cnt = collections.Counter() + + def check(cnt): + for c in "abc": + if cnt[c] < k: + return False + return True + + n = len(s) + r = n + while not check(cnt): + r -= 1 + if r < 0: + return -1 + cnt[s[r]] += 1 + ans = n - r + for l in range(n): + cnt[s[l]] += 1 + while r < n and cnt[s[r]] > k: + cnt[s[r]] -= 1 + r += 1 + ans = min(ans, l + 1 + n - r) + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2022-12-25-6272. \345\245\275\345\210\206\345\214\272\347\232\204\346\225\260\347\233\256.md" "b/_posts/Algorithms/leetcode/2022-12-25-6272. \345\245\275\345\210\206\345\214\272\347\232\204\346\225\260\347\233\256.md" new file mode 100644 index 0000000000..787337d55e --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-12-25-6272. \345\245\275\345\210\206\345\214\272\347\232\204\346\225\260\347\233\256.md" @@ -0,0 +1,79 @@ +--- +layout: post +category: leetcode +title: 6272. 好分区的数目 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/number-of-great-partitions/) + +给你一个正整数数组 `nums` 和一个整数 `k` 。 + +**分区** 的定义是:将数组划分成两个有序的 **组** ,并满足每个元素 **恰好** 存在于 **某一个** 组中。如果分区中每个组的元素和都大于等于 `k` ,则认为分区是一个好分区。 + +返回 **不同** 的好分区的数目。由于答案可能很大,请返回对 `109 + 7` **取余** 后的结果。 + +如果在两个分区中,存在某个元素 `nums[i]` 被分在不同的组中,则认为这两个分区不同。 + + + +**示例 1:** + +``` +输入:nums = [1,2,3,4], k = 4 +输出:6 +解释:好分区的情况是 ([1,2,3], [4]), ([1,3], [2,4]), ([1,4], [2,3]), ([2,3], [1,4]), ([2,4], [1,3]) 和 ([4], [1,2,3]) 。 +``` + +**示例 2:** + +``` +输入:nums = [3,3,3], k = 4 +输出:0 +解释:数组中不存在好分区。 +``` + +**示例 3:** + +``` +输入:nums = [6,6], k = 2 +输出:2 +解释:可以将 nums[0] 放入第一个分区或第二个分区中。 +好分区的情况是 ([6], [6]) 和 ([6], [6]) 。 +``` + + + +**提示:** + +- `1 <= nums.length, k <= 1000` +- `1 <= nums[i] <= 109` + +## solution + +[参考](https://leetcode.cn/problems/number-of-great-partitions/solution/ni-xiang-si-wei-01-bei-bao-fang-an-shu-p-v47x/) 考虑计算**坏分区**的数目,即第一个组或第二个组的元素和小于 k*k* 的方案数。根据对称性,我们只需要计算第一个组的元素和小于 k*k* 的方案数,然后乘 22 即可。 + +因此原问题转换成从 \textit{nums}*nums* 中选择若干元素,使得元素和小于 k*k* 的方案数,这可以用 **01 背包**求解。 + +```python +MOD = int(1e9 + 7) +INF = int(1e20) +class Math1: + pass +class Solution: + def countPartitions(self, nums: List[int], k: int) -> int: + # 统计bad数量,总数减去即可。 + if sum(nums) < 2*k:return 0 + n = len(nums) + dp = [0 for _ in range(k)] + dp[0] = 1 + for i, v in enumerate(nums): + for t in range(k-1, 0, -1): + if t >= v: + dp[t] += dp[t - v] % MOD + bad = sum(dp) + return (pow(2, n, MOD) - bad*2 + MOD) % MOD + +``` + diff --git "a/_posts/Algorithms/leetcode/2022-12-25-6276. \347\273\237\350\256\241\345\220\214\344\275\215\345\274\202\346\236\204\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" "b/_posts/Algorithms/leetcode/2022-12-25-6276. \347\273\237\350\256\241\345\220\214\344\275\215\345\274\202\346\236\204\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" new file mode 100644 index 0000000000..6754ad5df7 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-12-25-6276. \347\273\237\350\256\241\345\220\214\344\275\215\345\274\202\346\236\204\345\255\227\347\254\246\344\270\262\346\225\260\347\233\256.md" @@ -0,0 +1,125 @@ +--- +layout: post +category: leetcode +title: 6276. 统计同位异构字符串数目 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/count-anagrams/) + +给你一个字符串 `s` ,它包含一个或者多个单词。单词之间用单个空格 `' '` 隔开。 + +如果字符串 `t` 中第 `i` 个单词是 `s` 中第 `i` 个单词的一个 **排列** ,那么我们称字符串 `t` 是字符串 `s` 的同位异构字符串。 + +- 比方说,`"acb dfe"` 是 `"abc def"` 的同位异构字符串,但是 `"def cab"` 和 `"adc bef"` 不是。 + +请你返回 `s` 的同位异构字符串的数目,由于答案可能很大,请你将它对 `109 + 7` **取余** 后返回。 + + + +**示例 1:** + +``` +输入:s = "too hot" +输出:18 +解释:输入字符串的一些同位异构字符串为 "too hot" ,"oot hot" ,"oto toh" ,"too toh" 以及 "too oht" 。 +``` + +**示例 2:** + +``` +输入:s = "aa" +输出:1 +解释:输入字符串只有一个同位异构字符串。 +``` + + + +**提示:** + +- `1 <= s.length <= 105` +- `s` 只包含小写英文字母和空格 `' '` 。 +- 相邻单词之间由单个空格隔开。 + +## solution + +方法1: 大数阶乘除法,要用费马小定理。 + +方法2: 用comb换种思路,原来是阶乘相除,可以通过comb相乘,此时无除法。 + +```python +MOD = 10 ** 9 + 7 +class BigIntDivide: + def mod_inverse(i): + # 调用取模的乘幂运算, pow复杂度是log(exp)即log(MOD) + return pow(i, MOD - 2, MOD) + + def divide_mod(a, b): + ''' + 计算(a/b) % MOD, 除法变乘法,前提是b和MOD互为质数 + ''' + # 如果有多个b,比如a/b1/b2, 那就可以递归。a*mod_inverse(b1) % mod * mod_inverse(b2) % mod这样 + return a * BigIntDivide.mod_inverse(b) % MOD + + def divide_mods(a, b=[]): + ''' + 计算a /(b1*b2*b3) % MOD等形式,前提是b和MOD互为质数 + ''' + r = a % MOD + for i, v in enumerate(b): + r *= BigIntDivide.mod_inverse(v) + r %= MOD + return r + +class Math1: + + import functools + @functools.lru_cache(None) + def factorial_mod_cache(n): + if n == 0 or n == 1: + return 1 + return Math1.factorial_mod_cache(n - 1) * n % MOD +class Solution: + def countAnagrams(self, s: str) -> int: + d = s.split(' ') + + factorial_mod = Math1.factorial_mod_cache + + def calcu(x): + import collections + counter = collections.Counter(x) + n = len(x) + return BigIntDivide.divide_mods(factorial_mod(n), [factorial_mod(v) for v in counter.values()]) + + # print(calcu("too")) + + ans = 1 + for v in d: + ans *= calcu(v) + ans %= MOD + return ans % MOD +``` + + + +方法2 [参考](https://leetcode.cn/problems/count-anagrams/solution/python3pailiezuh-by-musing-clarkeeae-o2zg/) + + + +```python +MOD = 10 ** 9 + 7 + + +class Solution: + def countAnagrams(self, s: str) -> int: + ans = 1 + for word in s.split(): + n = len(word) + for v in Counter(word).values(): + ans *= comb(n, v) + ans %= MOD + n -= v + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2022-12-25-6295. \346\234\200\345\260\217\345\214\226\344\270\244\344\270\252\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/_posts/Algorithms/leetcode/2022-12-25-6295. \346\234\200\345\260\217\345\214\226\344\270\244\344\270\252\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" new file mode 100644 index 0000000000..2147956263 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2022-12-25-6295. \346\234\200\345\260\217\345\214\226\344\270\244\344\270\252\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\244\247\345\200\274.md" @@ -0,0 +1,87 @@ +--- +layout: post +category: leetcode +title: 6295. 最小化两个数组中的最大值 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimize-the-maximum-of-two-arrays/) + +给你两个数组 `arr1` 和 `arr2` ,它们一开始都是空的。你需要往它们中添加正整数,使它们满足以下条件: + +- `arr1` 包含 `uniqueCnt1` 个 **互不相同** 的正整数,每个整数都 **不能** 被 `divisor1` **整除** 。 +- `arr2` 包含 `uniqueCnt2` 个 **互不相同** 的正整数,每个整数都 **不能** 被 `divisor2` **整除** 。 +- `arr1` 和 `arr2` 中的元素 **互不相同** 。 + +给你 `divisor1` ,`divisor2` ,`uniqueCnt1` 和 `uniqueCnt2` ,请你返回两个数组中 **最大元素** 的 **最小值** 。 + + + +**示例 1:** + +``` +输入:divisor1 = 2, divisor2 = 7, uniqueCnt1 = 1, uniqueCnt2 = 3 +输出:4 +解释: +我们可以把前 4 个自然数划分到 arr1 和 arr2 中。 +arr1 = [1] 和 arr2 = [2,3,4] 。 +可以看出两个数组都满足条件。 +最大值是 4 ,所以返回 4 。 +``` + +**示例 2:** + +``` +输入:divisor1 = 3, divisor2 = 5, uniqueCnt1 = 2, uniqueCnt2 = 1 +输出:3 +解释: +arr1 = [1,2] 和 arr2 = [3] 满足所有条件。 +最大值是 3 ,所以返回 3 。 +``` + +**示例 3:** + +``` +输入:divisor1 = 2, divisor2 = 4, uniqueCnt1 = 8, uniqueCnt2 = 2 +输出:15 +解释: +最终数组为 arr1 = [1,3,5,7,9,11,13,15] 和 arr2 = [2,6] 。 +上述方案是满足所有条件的最优解。 +``` + + + +**提示:** + +- `2 <= divisor1, divisor2 <= 105` +- `1 <= uniqueCnt1, uniqueCnt2 < 109` +- `2 <= uniqueCnt1 + uniqueCnt2 <= 109` + +## solution + +二分最大元素的可行值,得到的下界即为最大值的最小值。 + +判断过程中分为三类:保证在范围内有充足的数不是第一个数的倍数;不是第二个数的倍数;不为公倍数的数总数不少于总共要取的数。根据这三个条件即得到结果。 + +```python +class Math1: + def lcm(a, b): + import math + gcd = math.gcd(a, b) + return int(a * b / gcd) + +class Solution: + def minimizeSet(self, divisor1: int, divisor2: int, uniqueCnt1: int, uniqueCnt2: int) -> int: + lcm = Math1.lcm(divisor1, divisor2) + + def can(x): + a = x - x // divisor1 # arr1能放的数量 + b = x - x // divisor2 # arr2能放的数量 + c = x // lcm # arr1和2都不能放的 + return (a >= uniqueCnt1) and (b >= uniqueCnt2) and (x - c) >= uniqueCnt1+uniqueCnt2 + import bisect + r = bisect.bisect_left(range(2*uniqueCnt1 +2*uniqueCnt2), True, key=can) + return r +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-14-1819. \345\272\217\345\210\227\344\270\255\344\270\215\345\220\214\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260\347\232\204\346\225\260\347\233\256.md" "b/_posts/Algorithms/leetcode/2023-01-14-1819. \345\272\217\345\210\227\344\270\255\344\270\215\345\220\214\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260\347\232\204\346\225\260\347\233\256.md" new file mode 100644 index 0000000000..7933268bd0 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-14-1819. \345\272\217\345\210\227\344\270\255\344\270\215\345\220\214\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260\347\232\204\346\225\260\347\233\256.md" @@ -0,0 +1,94 @@ +--- +layout: post +category: leetcode +title: 1819. 序列中不同最大公约数的数目 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/number-of-different-subsequences-gcds/) + +给你一个由正整数组成的数组 `nums` 。 + +数字序列的 **最大公约数** 定义为序列中所有整数的共有约数中的最大整数。 + +- 例如,序列 `[4,6,16]` 的最大公约数是 `2` 。 + +数组的一个 **子序列** 本质是一个序列,可以通过删除数组中的某些元素(或者不删除)得到。 + +- 例如,`[2,5,10]` 是 `[1,2,1,**2**,4,1,**5**,**10**]` 的一个子序列。 + +计算并返回 `nums` 的所有 **非空** 子序列中 **不同** 最大公约数的 **数目** 。 + + + +**示例 1:** + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301141325146.png) + +``` +输入:nums = [6,10,3] +输出:5 +解释:上图显示了所有的非空子序列与各自的最大公约数。 +不同的最大公约数为 6 、10 、3 、2 和 1 。 +``` + +**示例 2:** + +``` +输入:nums = [5,15,40,5,6] +输出:7 +``` + + + +**提示:** + +- `1 <= nums.length <= 105` +- `1 <= nums[i] <= 2 * 105` + +## solution + +![image-20230114132644447](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301141326476.png) + + + +O(nlogn) 其中遍历每个数的倍数总时间复杂度为o(nlogn) 参考调和级数。 Or this [link](https://leetcode.cn/problems/number-of-different-subsequences-gcds/solution/ji-bai-100mei-ju-gcdxun-huan-you-hua-pyt-get7/) + +```python +MOD = int(1e9 + 7) +INF = int(1e20) + +import sortedcontainers +import bisect +import heapq +class Math1: + def gcd_list(self, nums): + import math + cur = nums[0] + for i in range(1, len(nums)): + cur = math.gcd(cur, nums[i]) + return cur + +class Solution: + def countDifferentSubsequenceGCDs(self, nums: List[int]) -> int: + n, mx = len(nums), max(nums) + ans = 0 + has = [False for _ in range(mx+1)] + for v in nums: + has[v] += 1 + gcd_list = Math1().gcd_list + for i in range(1, mx + 1): + x = [] + for j in range(i, mx + 1, i): + if has[j]: + x.append(j) + if x: + # print(i, x) + p = gcd_list(x) + if p == i: + ans += 1 + return ans + +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-28-2541. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260 II.md" "b/_posts/Algorithms/leetcode/2023-01-28-2541. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260 II.md" new file mode 100644 index 0000000000..c440f8c3ea --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-28-2541. \344\275\277\346\225\260\347\273\204\344\270\255\346\211\200\346\234\211\345\205\203\347\264\240\347\233\270\347\255\211\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260 II.md" @@ -0,0 +1,76 @@ +--- +layout: post +category: leetcode +title: 2541. 使数组中所有元素相等的最小操作数 II +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-operations-to-make-array-equal-ii/) + +给你两个整数数组 `nums1` 和 `nums2` ,两个数组长度都是 `n` ,再给你一个整数 `k` 。你可以对数组 `nums1` 进行以下操作: + +- 选择两个下标 `i` 和 `j` ,将 `nums1[i]` 增加 `k` ,将 `nums1[j]` 减少 `k` 。换言之,`nums1[i] = nums1[i] + k` 且 `nums1[j] = nums1[j] - k` 。 + +如果对于所有满足 `0 <= i < n` 都有 `num1[i] == nums2[i]` ,那么我们称 `nums1` **等于** `nums2` 。 + +请你返回使 `nums1` 等于 `nums2` 的 **最少** 操作数。如果没办法让它们相等,请你返回 `-1` 。 + + + +**示例 1:** + +``` +输入:nums1 = [4,3,1,4], nums2 = [1,3,7,1], k = 3 +输出:2 +解释:我们可以通过 2 个操作将 nums1 变成 nums2 。 +第 1 个操作:i = 2 ,j = 0 。操作后得到 nums1 = [1,3,4,4] 。 +第 2 个操作:i = 2 ,j = 3 。操作后得到 nums1 = [1,3,7,1] 。 +无法用更少操作使两个数组相等。 +``` + +**示例 2:** + +``` +输入:nums1 = [3,8,5,2], nums2 = [2,4,1,6], k = 1 +输出:-1 +解释:无法使两个数组相等。 +``` + + + +**提示:** + +- `n == nums1.length == nums2.length` +- `2 <= n <= 105` +- `0 <= nums1[i], nums2[j] <= 109` +- `0 <= k <= 105` + +## solution + +任选两个元素,一个+k,一个-k,问最少操作次数,使这个数组和另一个数组相同。累计操作次数即可,注意k为0的情况 + +```python + +class Solution: + def minOperations(self, nums1: List[int], nums2: List[int], k: int) -> int: + op1, op2 = 0, 0 + d = [] + for a, b in zip(nums1, nums2): + d.append(a - b) + if sum(d) != 0: return -1 + for v in d: + if k == 0: + # k==0时特判,不能相除法了。 + if v != 0: return -1 + continue + if abs(v) % k != 0: return -1 + if v > 0: + op1 += v // k + else: + op2 += (-v) // k + if op1 != op2: + return -1 + return op1 +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-28-2542. \346\234\200\345\244\247\345\255\220\345\272\217\345\210\227\347\232\204\345\210\206\346\225\260.md" "b/_posts/Algorithms/leetcode/2023-01-28-2542. \346\234\200\345\244\247\345\255\220\345\272\217\345\210\227\347\232\204\345\210\206\346\225\260.md" new file mode 100644 index 0000000000..3697889093 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-28-2542. \346\234\200\345\244\247\345\255\220\345\272\217\345\210\227\347\232\204\345\210\206\346\225\260.md" @@ -0,0 +1,90 @@ +--- +layout: post +category: leetcode +title: 2542. 最大子序列的分数 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/maximum-subsequence-score/) + +给你两个下标从 **0** 开始的整数数组 `nums1` 和 `nums2` ,两者长度都是 `n` ,再给你一个正整数 `k` 。你必须从 `nums1` 中选一个长度为 `k` 的 **子序列** 对应的下标。 + +对于选择的下标 `i0` ,`i1` ,..., `ik - 1` ,你的 **分数** 定义如下: + +- `nums1` 中下标对应元素求和,乘以 `nums2` 中下标对应元素的 **最小值** 。 +- 用公示表示: `(nums1[i0] + nums1[i1] +...+ nums1[ik - 1]) * min(nums2[i0] , nums2[i1], ... ,nums2[ik - 1])` 。 + +请你返回 **最大** 可能的分数。 + +一个数组的 **子序列** 下标是集合 `{0, 1, ..., n-1}` 中删除若干元素得到的剩余集合,也可以不删除任何元素。 + + + +**示例 1:** + +``` +输入:nums1 = [1,3,3,2], nums2 = [2,1,3,4], k = 3 +输出:12 +解释: +四个可能的子序列分数为: +- 选择下标 0 ,1 和 2 ,得到分数 (1+3+3) * min(2,1,3) = 7 。 +- 选择下标 0 ,1 和 3 ,得到分数 (1+3+2) * min(2,1,4) = 6 。 +- 选择下标 0 ,2 和 3 ,得到分数 (1+3+2) * min(2,3,4) = 12 。 +- 选择下标 1 ,2 和 3 ,得到分数 (3+3+2) * min(1,3,4) = 8 。 +所以最大分数为 12 。 +``` + +**示例 2:** + +``` +输入:nums1 = [4,2,3,1,1], nums2 = [7,5,10,9,6], k = 1 +输出:30 +解释: +选择下标 2 最优:nums1[2] * nums2[2] = 3 * 10 = 30 是最大可能分数。 +``` + + + +**提示:** + +- `n == nums1.length == nums2.length` +- `1 <= n <= 105` +- `0 <= nums1[i], nums2[j] <= 105` +- `1 <= k <= n` + +## solution + +排序后,按从小到大枚举 + +```python +class Solution: + def maxScore(self, nums1: List[int], nums2: List[int], k: int) -> int: + a1, a2 = nums1, nums2 + n = len(a1) + d = zip(a2, a1) + # 排序 + d = sorted(d, key=lambda x: (x[0], x[1])) + # 统计每个a2元素大于a2元素对应的最大a1和 + right_max_sumv = collections.defaultdict(int) + cur = 0 + import heapq + h = [] + # print(d) + for i in range(len(d) - 1, -1, -1): + b, c = d[i] + heapq.heappush(h, c) + cur += c + if len(h) > k: + x = heapq.heappop(h) + cur -= x + # print(h, cur) + if len(h) == k: + right_max_sumv[b] = cur + ans = 0 + # print(right_max_sumv) + for k, v in right_max_sumv.items(): + ans = max(ans, v * k) + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-28-2546. \346\211\247\350\241\214\351\200\220\344\275\215\350\277\220\347\256\227\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" "b/_posts/Algorithms/leetcode/2023-01-28-2546. \346\211\247\350\241\214\351\200\220\344\275\215\350\277\220\347\256\227\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" new file mode 100644 index 0000000000..e067a7d306 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-28-2546. \346\211\247\350\241\214\351\200\220\344\275\215\350\277\220\347\256\227\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211.md" @@ -0,0 +1,58 @@ +--- +layout: post +category: leetcode +title: 2546. 执行逐位运算使字符串相等 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/apply-bitwise-operations-to-make-strings-equal/) + +给你两个下标从 **0** 开始的 **二元** 字符串 `s` 和 `target` ,两个字符串的长度均为 `n` 。你可以对 `s` 执行下述操作 **任意** 次: + +- 选择两个 **不同** 的下标 `i` 和 `j` ,其中 `0 <= i, j < n` 。 +- 同时,将 `s[i]` 替换为 (`s[i]` **OR** `s[j]`) ,`s[j]` 替换为 (`s[i]` **XOR** `s[j]`) 。 + +例如,如果 `s = "0110"` ,你可以选择 `i = 0` 和 `j = 2`,然后同时将 `s[0]` 替换为 (`s[0]` **OR** `s[2]` = `0` **OR** `1` = `1`),并将 `s[2]` 替换为 (`s[0]` **XOR** `s[2]` = `0` **XOR** `1` = `1`),最终得到 `s = "1110"` 。 + +如果可以使 `s` 等于 `target` ,返回 `true` ,否则,返回 `false` 。 + + + +**示例 1:** + +``` +输入:s = "1010", target = "0110" +输出:true +解释:可以执行下述操作: +- 选择 i = 2 和 j = 0 ,得到 s = "0010". +- 选择 i = 2 和 j = 1 ,得到 s = "0110". +可以使 s 等于 target ,返回 true 。 +``` + +**示例 2:** + +``` +输入:s = "11", target = "00" +输出:false +解释:执行任意次操作都无法使 s 等于 target 。 +``` + + + +**提示:** + +- `n == s.length == target.length` +- `2 <= n <= 105` +- `s` 和 `target` 仅由数字 `0` 和 `1` 组成 + +## solution + +找规律。 [参考](https://leetcode.cn/problems/apply-bitwise-operations-to-make-strings-equal/solution/nao-jin-ji-zhuan-wan-yi-xing-dai-ma-by-e-0fce/) + +```python +class Solution: + def makeStringsEqual(self, s: str, target: str) -> bool: + return ('1' in s) == ('1' in target) +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-28-2547. \346\213\206\345\210\206\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\344\273\243\344\273\267.md" "b/_posts/Algorithms/leetcode/2023-01-28-2547. \346\213\206\345\210\206\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\344\273\243\344\273\267.md" new file mode 100644 index 0000000000..c345b2e99a --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-28-2547. \346\213\206\345\210\206\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\344\273\243\344\273\267.md" @@ -0,0 +1,102 @@ +--- +layout: post +category: leetcode +title: 2547. 拆分数组的最小代价 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/minimum-cost-to-split-an-array/) + +给你一个整数数组 `nums` 和一个整数 `k` 。 + +将数组拆分成一些非空子数组。拆分的 **代价** 是每个子数组中的 **重要性** 之和。 + +令 `trimmed(subarray)` 作为子数组的一个特征,其中所有仅出现一次的数字将会被移除。 + +- 例如,`trimmed([3,1,2,4,3,4]) = [3,4,3,4]` 。 + +子数组的 **重要性** 定义为 `k + trimmed(subarray).length` 。 + +- 例如,如果一个子数组是 `[1,2,3,3,3,4,4]` ,`trimmed([1,2,3,3,3,4,4]) = [3,3,3,4,4]` 。这个子数组的重要性就是 `k + 5` 。 + +找出并返回拆分 `nums` 的所有可行方案中的最小代价。 + +**子数组** 是数组的一个连续 **非空** 元素序列。 + + + +**示例 1:** + +``` +输入:nums = [1,2,1,2,1,3,3], k = 2 +输出:8 +解释:将 nums 拆分成两个子数组:[1,2], [1,2,1,3,3] +[1,2] 的重要性是 2 + (0) = 2 。 +[1,2,1,3,3] 的重要性是 2 + (2 + 2) = 6 。 +拆分的代价是 2 + 6 = 8 ,可以证明这是所有可行的拆分方案中的最小代价。 +``` + +**示例 2:** + +``` +输入:nums = [1,2,1,2,1], k = 2 +输出:6 +解释:将 nums 拆分成两个子数组:[1,2], [1,2,1] 。 +[1,2] 的重要性是 2 + (0) = 2 。 +[1,2,1] 的重要性是 2 + (2) = 4 。 +拆分的代价是 2 + 4 = 6 ,可以证明这是所有可行的拆分方案中的最小代价。 +``` + +**示例 3:** + +``` +输入:nums = [1,2,1,2,1], k = 5 +输出:10 +解释:将 nums 拆分成一个子数组:[1,2,1,2,1]. +[1,2,1,2,1] 的重要性是 5 + (3 + 2) = 10 。 +拆分的代价是 10 ,可以证明这是所有可行的拆分方案中的最小代价。 +``` + + + +**提示:** + +- `1 <= nums.length <= 1000` +- `0 <= nums[i] < nums.length` +- `1 <= k <= 109` + + + +## solution + +一维DP。 + +dp(i)表示以0...i的代价,求dp(n-1)。 + +用map维护出现不止一次的元素个数。 + +```python +INF = int(1e20) +class Solution: + def minCost(self, nums: List[int], k: int) -> int: + n = len(nums) + dp = [INF for _ in range(n)] + for i in range(n): + res = INF + cnt = collections.Counter() + t = 0 + for j in range(i, -1, -1): + cnt[nums[j]] += 1 + if cnt[nums[j]] == 2: + t += 2 + elif cnt[nums[j]] > 2: + t += 1 + res = min(res, (dp[j - 1] if j - 1 >= 0 else 0) + k + t) + # if i == n-1 and j == 2: + # print("x") + dp[i] = res + # print(dp) + return dp[n - 1] +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-29-6339. \345\260\206\347\217\240\345\255\220\346\224\276\345\205\245\350\203\214\345\214\205\344\270\255.md" "b/_posts/Algorithms/leetcode/2023-01-29-6339. \345\260\206\347\217\240\345\255\220\346\224\276\345\205\245\350\203\214\345\214\205\344\270\255.md" new file mode 100644 index 0000000000..6a74e28a91 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-29-6339. \345\260\206\347\217\240\345\255\220\346\224\276\345\205\245\350\203\214\345\214\205\344\270\255.md" @@ -0,0 +1,74 @@ +--- +layout: post +category: leetcode +title: 6339. 将珠子放入背包中 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/put-marbles-in-bags/) + +你有 `k` 个背包。给你一个下标从 **0** 开始的整数数组 `weights` ,其中 `weights[i]` 是第 `i` 个珠子的重量。同时给你整数 `k` 。 + +请你按照如下规则将所有的珠子放进 `k` 个背包。 + +- 没有背包是空的。 +- 如果第 `i` 个珠子和第 `j` 个珠子在同一个背包里,那么下标在 `i` 到 `j` 之间的所有珠子都必须在这同一个背包中。 +- 如果一个背包有下标从 `i` 到 `j` 的所有珠子,那么这个背包的价格是 `weights[i] + weights[j]` 。 + +一个珠子分配方案的 **分数** 是所有 `k` 个背包的价格之和。 + +请你返回所有分配方案中,**最大分数** 与 **最小分数** 的 **差值** 为多少。 + + + +**示例 1:** + +``` +输入:weights = [1,3,5,1], k = 2 +输出:4 +解释: +分配方案 [1],[3,5,1] 得到最小得分 (1+1) + (3+1) = 6 。 +分配方案 [1,3],[5,1] 得到最大得分 (1+3) + (5+1) = 10 。 +所以差值为 10 - 6 = 4 。 +``` + +**示例 2:** + +``` +输入:weights = [1, 3], k = 2 +输出:0 +解释:唯一的分配方案为 [1],[3] 。 +最大最小得分相等,所以返回 0 。 +``` + + + +**提示:** + +- `1 <= k <= weights.length <= 105` +- `1 <= weights[i] <= 109` + +## solution + +脑筋急转弯 + +不要关注端点,要关注分割位置,收益就是分割位置左右两边 + +```python + +class Solution: + def putMarbles(self, weights: List[int], k: int) -> int: + # 不要关注端点,要关注分割位置,收益就是分割位置左右两边 + d = [] + for i in range(len(weights) - 1): + a, b = weights[i], weights[i + 1] + d.append(a + b) + d.sort() + # print(d) + ans = -sum(d[:k - 1]) + d = list(reversed(d)) + ans += sum(d[:k - 1]) + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-29-6340. \347\273\237\350\256\241\344\270\212\345\215\207\345\233\233\345\205\203\347\273\204.md" "b/_posts/Algorithms/leetcode/2023-01-29-6340. \347\273\237\350\256\241\344\270\212\345\215\207\345\233\233\345\205\203\347\273\204.md" new file mode 100644 index 0000000000..8bd342be32 --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-29-6340. \347\273\237\350\256\241\344\270\212\345\215\207\345\233\233\345\205\203\347\273\204.md" @@ -0,0 +1,85 @@ +--- +layout: post +category: leetcode +title: 6340. 统计上升四元组 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/count-increasing-quadruplets/) + +给你一个长度为 `n` 下标从 **0** 开始的整数数组 `nums` ,它包含 `1` 到 `n` 的所有数字,请你返回上升四元组的数目。 + +如果一个四元组 `(i, j, k, l)` 满足以下条件,我们称它是上升的: + +- `0 <= i < j < k < l < n` 且 +- `nums[i] < nums[k] < nums[j] < nums[l]` 。 + + + +**示例 1:** + +``` +输入:nums = [1,3,2,4,5] +输出:2 +解释: +- 当 i = 0 ,j = 1 ,k = 2 且 l = 3 时,有 nums[i] < nums[k] < nums[j] < nums[l] 。 +- 当 i = 0 ,j = 1 ,k = 2 且 l = 4 时,有 nums[i] < nums[k] < nums[j] < nums[l] 。 +没有其他的四元组,所以我们返回 2 。 +``` + +**示例 2:** + +``` +输入:nums = [1,2,3,4] +输出:0 +解释:只存在一个四元组 i = 0 ,j = 1 ,k = 2 ,l = 3 ,但是 nums[j] < nums[k] ,所以我们返回 0 。 +``` + + + +**提示:** + +- `4 <= nums.length <= 4000` +- `1 <= nums[i] <= nums.length` +- `nums` 中所有数字 **互不相同** ,`nums` 是一个排列。 + +## solution + +统计四元组。 + +枚举中间的两个数。O(n2) + +需要预处理简化时间复杂度。 + +```python +class Solution: + def countQuadruplets(self, nums: List[int]) -> int: + n = len(nums) + # i,j,k,l. i j: + cnt_greater_j[j][i] = cnt + if nums[i] > nums[j]: + cnt += 1 + i -= 1 + ans = 0 + for j in range(n): + for k in range(j + 1, n): + if nums[j] > nums[k]: + ans += cnt_less_k[k][j] * cnt_greater_j[j][k] + return ans +``` + diff --git "a/_posts/Algorithms/leetcode/2023-01-30-1664. \347\224\237\346\210\220\345\271\263\350\241\241\346\225\260\347\273\204\347\232\204\346\226\271\346\241\210\346\225\260.md" "b/_posts/Algorithms/leetcode/2023-01-30-1664. \347\224\237\346\210\220\345\271\263\350\241\241\346\225\260\347\273\204\347\232\204\346\226\271\346\241\210\346\225\260.md" new file mode 100644 index 0000000000..9df6dbaaba --- /dev/null +++ "b/_posts/Algorithms/leetcode/2023-01-30-1664. \347\224\237\346\210\220\345\271\263\350\241\241\346\225\260\347\273\204\347\232\204\346\226\271\346\241\210\346\225\260.md" @@ -0,0 +1,98 @@ +--- +layout: post +category: leetcode +title: 1664. 生成平衡数组的方案数 +tags: leetcode +--- + +## title +[problem link](https://leetcode.cn/problems/ways-to-make-a-fair-array/) + +给你一个整数数组 `nums` 。你需要选择 **恰好** 一个下标(下标从 **0** 开始)并删除对应的元素。请注意剩下元素的下标可能会因为删除操作而发生改变。 + +比方说,如果 `nums = [6,1,7,4,1]` ,那么: + +- 选择删除下标 `1` ,剩下的数组为 `nums = [6,7,4,1]` 。 +- 选择删除下标 `2` ,剩下的数组为 `nums = [6,1,4,1]` 。 +- 选择删除下标 `4` ,剩下的数组为 `nums = [6,1,7,4]` 。 + +如果一个数组满足奇数下标元素的和与偶数下标元素的和相等,该数组就是一个 **平衡数组** 。 + +请你返回删除操作后,剩下的数组 `nums` 是 **平衡数组** 的 **方案数** 。 + + + +**示例 1:** + +``` +输入:nums = [2,1,6,4] +输出:1 +解释: +删除下标 0 :[1,6,4] -> 偶数元素下标为:1 + 4 = 5 。奇数元素下标为:6 。不平衡。 +删除下标 1 :[2,6,4] -> 偶数元素下标为:2 + 4 = 6 。奇数元素下标为:6 。平衡。 +删除下标 2 :[2,1,4] -> 偶数元素下标为:2 + 4 = 6 。奇数元素下标为:1 。不平衡。 +删除下标 3 :[2,1,6] -> 偶数元素下标为:2 + 6 = 8 。奇数元素下标为:1 。不平衡。 +只有一种让剩余数组成为平衡数组的方案。 +``` + +**示例 2:** + +``` +输入:nums = [1,1,1] +输出:3 +解释:你可以删除任意元素,剩余数组都是平衡数组。 +``` + +**示例 3:** + +``` +输入:nums = [1,2,3] +输出:0 +解释:不管删除哪个元素,剩下数组都不是平衡数组。 +``` + + + +**提示:** + +- `1 <= nums.length <= 105` +- `1 <= nums[i] <= 104` + +## solution + +找规律题 + +当删除某个下标后,新的偶数和 = 这个下标左边的偶数和 + 这个下标右边的奇数和 + +当删除某个下标后,新的奇数和 = 这个下标左边的奇数数和 + 这个下标右边的偶数数和 + +```python + +class Solution: + def waysToMakeFair(self, nums: List[int]) -> int: + odd, even = 0, 0 + for i, v in enumerate(nums): + if i % 2 == 0: + even += v + else: + odd += v + lo, le = 0, 0 + total = sum(nums) + # print(total, odd, even) + ans = 0 + for i, v in enumerate(nums): + if i % 2 == 0: + teven = odd - lo + le + if teven == total - v - teven: + ans += 1 + # print(i, v, teven) + le += v + else: + todd = even - le + lo + if todd == total - v - todd: + ans += 1 + # print(i, v, todd) + lo += v + return ans +``` + diff --git "a/_posts/DailyLife/2018-02-07-\347\202\222\350\217\234.md" "b/_posts/DailyLife/2018-02-07-\347\202\222\350\217\234.md" deleted file mode 100644 index 49258f5d0b..0000000000 --- "a/_posts/DailyLife/2018-02-07-\347\202\222\350\217\234.md" +++ /dev/null @@ -1,132 +0,0 @@ ---- -layout: post -category: DailyLife -title: 炒菜 -tags: DailyLife ---- - -## 炒菜 - -### 土豆丝+肉丝 - -土豆去皮变成丝。 需要先洗一遍,去掉淀粉,否则氧化发黑,可以放水里,用到再捞出来。 - - - -肉丝洗3遍。 - - - -热锅, 放油, 底部全湿, 放肉丝, 2勺 料酒, 2勺生抽, 半勺老抽。 - - - -放土豆丝, 每5s翻炒,放鸡精, 如果老抽多了可以不放盐。 菜基本熟时放葱。 - - - - - -### 炒卷心菜 - -大小 长3*3大小,不要梗。 - - - -热锅冷油, 油比肉丝少点。 放卷心菜, 生抽2勺, 鸡精, 葱。 - - - -**要干锅时加热水。** - - - -### 五花肉+荷兰豆 - -不需要豆油 - -五花肉过于肥腻,需要锅里不放油 闷一会,因为不是肉丝所以没那么容易老。 - -直到肉变色,炸出油。 - -加蒜 加料酒,生抽老抽。因为肉比较大,可以加点水让配料入味 - -然后放荷兰豆炒。 - - - -### 蒜薹+肉丝 - -蒜薹切段,煮50秒,捞出来,这是为了避免蒜薹熟的慢。 - -肉丝做法 + 蒜薹。 - -### 红烧鸡翅根 - -鸡翅根切几道,红烧时入味 - -鸡翅根煮熟。 - -放油,鸡翅根,料酒,生抽,老抽 - -加水,4分之一高度。 - -加盐。 - -放葱。 - -味素。 - -红烧到无水 - - -### 茄子肉丝 -茄子 切成丝: - -1. 先斜着切片,5mm厚即可,茄子熟时缩的多。 - -2. 片再切丝,5mm宽 - -肉丝做法 - -茄子含水少,避免干锅需要加点水。 - - -### 芹菜肉丝 - -芹菜: -1. 长度2cm -2. 宽的芹菜要中间切一刀分开 -3. 先去叶子,再去根,再切 - -肉丝做法 - -芹菜含水多 - -### 红烧鸡爪 - -洗干净 去指甲 - -鸡爪炖2min,快熟那种 - -油,白砂糖让溶解,蒜, - -放鸡爪,生抽2勺,老抽和耗油各1勺, 耗油是提现的 - -一罐300ml的啤酒,没有啤酒就热水, 水大概刚好到鸡爪上限那, 挺多的 - -一个八角 - -中火20min, 直到快没有汤了 - -葱花,味素。 - - - -## 韭菜鸡蛋 - -- 一把韭菜,4个鸡蛋。 -- 韭菜洗干净,去死皮,切根,切长度为1.5-2cm左右 -- 锅里放油,放韭菜大概8-9分熟,颜色变了就可以,捞出来韭菜去水。 -- 放油,放鸡蛋,7-8分熟,成块了,放韭菜,加鸡精,加盐。 -- 注意:韭菜2-3s就变颜色了,要勤翻 diff --git "a/_posts/English/2021-01-02-\345\217\221\351\237\263-\345\217\221\351\237\263\350\247\204\345\210\231.md" "b/_posts/English/2021-01-02-\345\217\221\351\237\263-\345\217\221\351\237\263\350\247\204\345\210\231.md" index 03912e5f8b..e19dd35695 100644 --- "a/_posts/English/2021-01-02-\345\217\221\351\237\263-\345\217\221\351\237\263\350\247\204\345\210\231.md" +++ "b/_posts/English/2021-01-02-\345\217\221\351\237\263-\345\217\221\351\237\263\350\247\204\345\210\231.md" @@ -5,43 +5,7 @@ title: 发音-发音规则 tags: English --- -## 总结 - -发音规则 昂克 - -- 辅音元音连读: not at all, 如果辅音是/r/ /l/ /ŋ/, 辅音双写: for a -- 元+元,添加连读,自动添加/j/或/w/, 只有i和u。do it, go out. /ɔː/自动加r, saw in -- 特殊添加元音: /n/+/s/=/n ts/ since -- 辅+辅, 三组内的略读只读后面的,所有辅音相同略读,浊音在前清辅音在后可略读前面的:v f, z s,ð θ. 不可略读:/tʃ/ /dʒ/ -- 特殊略读 nt+元音, 省略t :advantage -- 吞音,三组里碰到后面是三组里非兄弟辅音就吞音(也包含其他非爆破辅音)。吞音保留时值和状态不发音, that cat -- 吞音喉音停顿:/tən/ /dən/ 只留ən, written -- 略读 h-droping: him, his, he, her -- 略读 g-droping: doing. ing里的 -- 变音: about you. would you, rock you. /t/+/ju/;/d/+/ju/。 miss you: sh -- 清辅音+元,清辅音浊化 -- 不同意群不连读 -- 元音+元音,如果读音相近,可以连一起,比如law office. lawwfis -- schewa: for, of, have, or, and等,元音变成ə - -**总结2:** - -> 视频: https://www.bilibili.com/video/BV1SZ4y1K7Lr?p=6&spm_id_from=pageDriver - -- 元音挨着元音不容易读,所以才会区分a和an -- 同化&异化 - - 同化:前面音 改变 - - 常见爆破音: k,t,d, 后加辅音,前面音发一半,也叫失去爆破。 - - 浊音变清音,比如have to, of course, 的v变f. - - inperson读imperson, n->m,嘴唇闭合。 - - 同化:后面音 改变 - - 比如变复数读音 - - 相互作用 - - s+j: bless you. z+j:as you wish. t+j: meet you. d+j: did you. 类似的educate, tissue. - - 辅音连读 - - 异化, r接元音会读r, 否则不发音 -- 省音: t,d。 来自其他视频,**t+辅音里的t省略,nt+元的t省略, nd+辅的d省略** -- 节奏: 等时性。 +- ## 重音 @@ -58,6 +22,22 @@ tags: English > 链接: https://www.bilibili.com/video/av19992217?from=search&seid=14758661060311518388 +### 总结 + +- 辅+元,**连读**: not at all, 如果辅音是/r/ /l/ /ŋ/, 辅音双写: for a +- 元+元,添加连读,当尾元音是i和u时,自动添加/j/或/w/。do it, go out. /ɔː/自动加r, saw in +- 特殊添加连读: /n/+/s/=/n ts/ since +- 辅+辅,**连读**, 三组内的略读只读后面的,所有辅音相同略读,浊音在前清辅音在后可略读前面的:v f, z s,ð θ. 不可略读:/tʃ/ /dʒ/ +- 辅+辅,**吞音**,三组里碰到后面是三组里非兄弟辅音就吞音(也包含其他非爆破辅音)。吞音保留时值和状态不发音, that cat +- 吞音喉音停顿:/tən/ /dən/ 只留ən, written +- 略读 h-droping: him, his, he, her。 give him /givim/ +- 略读 g-droping: doing. ing里的 +- 变音: about you. would you, rock you. /t/+/ju/;/d/+/ju/。 miss you: sh +- ~~清辅音+元,清辅音浊化~~ +- 不同意群不连读 +- 元音+元音,如果读音相近,可以连一起,比如law office. lawwfis +- schewa: for, of, have, or, and等,元音变成ə + ### 清辅音浊化 S后面的清辅音浊化现象可以这样归纳:清辅音跟着一个元音,前面又有一个 s ,无论是在单词的最前面还是中间,只要是在重读音节或次重读音节里,一般都读成对应浊辅音,如stand,strike,speak,sky 等等,值得一提的是strike不是不用变,而是它“tr”本来所发的音已是浊辅音,但也要变成“dr”所发的那个音 (如dream中的“dr”所发的音). @@ -177,7 +157,225 @@ i经常弱读为e,所以give hime读为后面的,这个经常用后面的 ![image-20210102230759698](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv1/v1/109.png) -### 弱读 缩写 其他 -- + + +## 100个最常见的英语单词发音 + +> [参考](https://www.zhihu.com/zvideo/1390373231258382336) , 包含了常见单词的弱读,比如him, to, do, of等。 + +笔记 + + + +正常schewa: + +- a,an,that, for, as( he's as tall as me), do, at, but, from, or, an, one, should, could, so, some, than + +不弱读的: + +- This, by, they, we, say, out, also. + +意群结尾p不发音,what's up + +1期: + +- the: the发音后面的/ə/ 当the后面接元音时可变成/i/ +- I am: I'm 有时只有一个音/m/, I'm sorry / m_ 'sorry/ +- you are: yer. +- It is: /its/ 有时可省略元音 /ts/ ts cool +- we are: /wər/ +- they are: /ðər/ +- to: tə, go to时, t变d +- Of: /əv/ 在kind of, sort of里省略/v/, kanda +- and: no /d/ or just /n/, ex: And I + +2期: + +- With: 后面轻音化, /θ/ +- he, him, 省略/h/, ex: what does he want +- You: /jə/ + +5期: + +- Who /u/, ex: Anyone who wants to come can come. + +6期: + +- Can /cən/ +- Him /əm/. Give him: /givem/ + +7期: + +- into /ində/. 这里的t不遵循t发音规则。 + +8期下: + +- Its: /ts/ 没有前面/i/了。 + +10期下: + +- give是实词,但give me时弱读, give me that: gimme 省略v +- us: əs + + + + + +## /t/发音 + +[参考](https://rachelsenglish.com/podcast/004-t-pronunciations-flap-t-stop-t-true-t/) + +### True T + +开头或者重音节,或者后面接r。 + +- True T Rule 1: At the beginning of a word + - Exception #1: in the TR cluster (train, try) – then it can sound like a CH + - Exception #2: the words to, today, tomorrow. These may begin with a Flap T. +- True T Rule 2: At the beginning of a stressed syllable (attain, until). This includes secondary stress. In a dictionary, secondary stress is marked with this: ˌ Primary stress is marked with this: ˈ + - Exception: when it’s followed by R (attribute, attract) + +### Stop T + +- How to make a Stop sound: Stop T vs. No T: Buy vs. bite +- Stop T Rule 1: When the T is followed by a consonant sound (definitely, bluntly) 后面接辅音 + - Exception: when the sound before the T is an R. Then it’s a Stop T (partly) : r+t+辅音 ,t不变 +- Stop T Rule 2: Make the T a Stop T when it’s the last sound in a thought group. :意群结尾 + - Exception: when the T is in a cluster, then we usually pronounce it (fact, best) : + + + +如果后面 是元音或者双元音 或者是意群的结尾,t就发音。可能是flap T, stop T + +### Flap T + +> [参考](https://www.goalsenglish.com/lessons/flap-t-sound-american-english-accent) + +- 类似d的发音 +- 发生flap T情况 + - Flap T Rule 1: a T is a Flap T between two vowels or diphthongs (beautiful, city) 元音间 If a ‘t’ is between two vowels, whether in a word or between two words, it will be pronounced as a ‘soft d.’ + - Exception: If the T begins a stressed syllable. Then it’s a True T (attain, attack) 重音上 + - Flap T Rule 2: a T is a Flap T after an R before a vowel or diphthong (party, dirty). Applies to linked phrase: (a lot of, about it) + - Exception: if the T begins a stressed syllable. Then it’s a True T (partake) + +例子 + +- it always + +### Drop T + +- Dropped T Rule 1: T can be dropped after N (center, internet) nt的t会省略 + - Exception: when there is a syllable split between N and T (until, intense) +- Dropped T Rule 2: Americans often drop the T between two other consonants (exactly, perfectly). This applies to phrases where two words link (just because) + - Exception: Not when the consonant before the T is an R (partly) + +例子 + +- shouldn't, didn't,won't + +### 总结 + +t前面是n? drop T + +t前面是辅音? + +- 后面是辅音? drop T +- 后面是元音? stop T + +t前面是元音? + +- 后面是元音? flap T +- 后面是辅音? drop T + +t是意群结尾? Stop T. p也类似。 + +只有两个元音间的t是d,否则stop / drop + + + +杂记: + +t+辅音里的t省略, 其实是停顿,但和省略差不多 + +nt比较特殊: nt+元的t省略, nd+辅的d省略 + +## /d/发音 + +nd+辅的d省略 + +## 重读和弱读 + +### 重读 + +**一般来讲我们应该重读content words, 就是有实际内容、实际意义的词**,例如:名词,实义动词,形容词,副词,数量词。名词方面,比如像mountain, history等;实义动词就是真正发出动作的词,比如像establish, jump等;形容词比如像stunning, terrifying这样的词;副词方面比如quickly, extremely等。这些词都是有实际意义的,也就是我所说的内容词,来表达句子意义的词,所以我们应该把它们做重读的处理。 + + + +**一般来讲哪些单词不重读,就是function words功能词,即那些起到语法功能作用的词,**比如像代词,介词,限定词,连词以及助动词等等,都是语法结构会运用到的词,这些词对句子的意义并不构成直接的影响。什么样的词属于限定词,比如像冠词the, a/an; some, few, my都是限定词;助动词像be动词,be, do, must这些都属于助动词;介词after, of, above, opposite, out of, on behalf of这些有介词意义的短语也包括在内。还有像连词,比如and, so, 还有代词this, him, our, you, it, 这些词在句子当中是起到语法意义的词。 + + + +总结: 重读的只有副词、名词、形容词、动词。但content words有时也不会重读,如果还有其他的重读的词的话。 + + + +### 弱读 + +1、重读的单词不会出现弱读现象,要读的响亮 + +这些词一般是表示句子含义的词,如名词,动词,副词,形容词 (today 今天, apple 苹果) + + + +2、非重读的部分的单词要读轻 + +这些词一般起到语法功能,如介词、连词、助动词等 (and 和, but但是,for为了) + + + + + +弱读现象存在于句子的非重读单词部分 + +对于句子非重读部分的单词 + +1、如果有弱读发音,就可以进行弱读 + +2、如果没有弱读发音,发本来的音即可 + + + +不能弱读情况 + +1、句子最后一个单词不能弱读 + +2、特意强调的单词不能弱读,要重读 + + + +## 英音美音区别 + +todo + + + +## 其它 + +> 视频: https://www.bilibili.com/video/BV1SZ4y1K7Lr?p=6&spm_id_from=pageDriver + +- 元音挨着元音不容易读,所以才会区分a和an +- 同化&异化 + - 同化:前面音 改变 + - 常见爆破音: k,t,d, 后加辅音,前面音发一半,也叫失去爆破。 + - 浊音变清音,比如have to, of course, 的v变f. + - inperson读imperson, n->m,嘴唇闭合。 + - 同化:后面音 改变 + - 比如变复数读音 + - 相互作用 + - s+j: bless you. z+j:as you wish. t+j: meet you. d+j: did you. 类似的educate, tissue. + - 辅音连读 + - 异化, r接元音会读r, 否则不发音 +- 省音: t,d。 来自其他视频,**t+辅音里的t省略,nt+元的t省略, nd+辅的d省略** +- 节奏: 等时性。 diff --git "a/_posts/English/2021-01-02-\345\217\221\351\237\263-\351\237\263\346\240\207.md" "b/_posts/English/2021-01-02-\345\217\221\351\237\263-\351\237\263\346\240\207.md" index 3db6572c3d..e36709805e 100644 --- "a/_posts/English/2021-01-02-\345\217\221\351\237\263-\351\237\263\346\240\207.md" +++ "b/_posts/English/2021-01-02-\345\217\221\351\237\263-\351\237\263\346\240\207.md" @@ -7,6 +7,12 @@ tags: English ## 发音-音标 +欧陆词典用的是DJ音标新版。也是咱们常用的 + +影响发音的因素:圆唇度、舌位高低、舌位前后。舌位影响了开头大小。因此学音标应以舌位为主。 + +### 音标分类 + 分类1 ![音标分类表1](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv1/v1/123.png) @@ -17,14 +23,14 @@ tags: English -æ : ai, such as trap. - - +### BBC经典发音教程 [可学习的bilibili视频: BBC经典发音教程](https://www.bilibili.com/video/BV1Y4411M7Ac?p=2) +### 音标发音文字注解 + 元音: - /ɪ/ 舌抵下齿,双唇扁平分开,牙床近于全舌,发短促之“一”音。 是字母i或y在单词中的发音,发此音要短促而轻快。 **bit, hit, minute/minit/** @@ -68,6 +74,7 @@ tags: English - /r/ 唇形稍圆,舌身略凹,舌尖上卷,振动声带。 - /w/ 双唇突出,呈尖圆形,舌后升向软腭,气息流过,须振动声带。 - /j/ 双唇微开,舌抵下齿贴近硬腭,气流摩擦而出。 +- /dz/ 类似汉语里资。对比/ts/ @@ -84,6 +91,8 @@ tags: English +### 音标区分 + **长元音和短元音区别:不是长短,是松和紧。松元音只动舌头和声带,不动嘴唇。** **双元音和单元音区别:[e]是单元音,发声短促:dead [ded] , [ai]是双元音,有从[a]到[i]的一个过渡过程:die [dai] (双元音过渡在汉语里是没有的)** @@ -92,10 +101,36 @@ tags: English 常见区别: -- [辅音/m/、/n/与/ŋ/](https://www.bilibili.com/video/BV1Ae41147Ng/?spm_id_from=trigger_reload) -- [/æ/ 和 /e/](/æ/ 和 /e/) -- [/u:/和/u/](/u:/和/u/) -- [清辅音θ与s](https://www.bilibili.com/video/BV1X7411S7V8?spm_id_from=333.337.search-card.all.click) +- [辅音/m/、/n/与/ŋ/](https://www.bilibili.com/video/BV1Ae41147Ng/?spm_id_from=trigger_reload) m是闭口,n嘴唇微张但牙齿闭合,最后那个是中度张嘴。 +- [/æ/ 和 /e/](https://www.bilibili.com/video/BV1o7411M7cV?spm_id_from=333.999.0.0&vd_source=4c51dba622ffb91bed5205311847907b) ae和/e/的区别,前者两指宽,像绵羊叫,后者一指宽。 +- [双元音/aɪ/与 单元音/æ/](https://www.bilibili.com/video/BV1xg4y187CT?spm_id_from=333.999.0.0&vd_source=4c51dba622ffb91bed5205311847907b) 前者双元音后者单元音 但语速快了就无法区分了 +- [/u:/和/u/](https://www.bilibili.com/video/BV1d7411M7i1?spm_id_from=333.999.0.0) +- [清辅音θ与s](https://www.bilibili.com/video/BV1X7411S7V8?spm_id_from=333.337.search-card.all.click) 前者 上下齿咬舌尖。 后者双唇微开,上下齿接近于合拢状态 + +### 元音图 + +元音图 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208131030725.jpg) + +注意: 红色是汉语拼音,黄色是美国英语。不圆唇的在前,圆唇在后。开口大小就是舌位高低 + + + +元音的舌面不能与硬腭接触,否则就成了辅音。 + + + +这有一个看和听发音的[网站](https://tfcs.baruch.cuny.edu/consonants-vowels/) + + + +### 英语兔学习笔记 + +可每日英语听力上看,免费。 + +- /ɪ/舌位比/i:/低,类似军训一二一里的一。bit, fit, hit, fish. 舌位过高是错误方式,不能读 中文 币 的韵母。 +- ## 音节 @@ -117,7 +152,39 @@ tags: English 比如单词later和latter。我们把这两个词按照这种原理分开,分别就是la/ter和lat/ter。 -那么这样分完有什么意义呢,当然有意义,而且意义重大,因为它能让你正确判断单词的发音。不用看音标。看下面 +那么这样分完有什么意义呢,当然有意义,而且意义重大,因为它能让你正确判断单词的发音。不用看音标。 + + + +另外 辅音+l/e结尾,也可以构成音节 apple/pl/ bicycle/baskl/ + +### 重读音节 + +什么是重读音节呢?首先普及一下单音节的单词是没有重读符号的,但是要重读。比如:get,let,must.say等。 + +1. 一个音节就重读:(没有重读符号)me/mi/ lake/lek/ bread/bred/ + +2. 一般2、3个音节的单词,重读在第一音节上 sister/'sst/ tiger/tag/ + + 特俗 + + ```python + \1. a开头+辅音 + + 举例:away/ we/ ago/gu/ + + \2. be/re开头 + + 举例:begin/bgn/ reply/rpla/ + + \3. de/es/im/mis开头 + + 举例:delay/dle/ improve/mpruv/ + ``` + + + +3. 3个或以上音节的单词,重读在倒数第三个音节上。 'family de'mocracy phy'losophy @@ -133,22 +200,26 @@ tags: English 2、**闭音节**:英语中以一个或几个辅音字母(r w y 除外)结尾而中间只有一个元音字母的音节,称为**闭音节**。闭音节的元音字母不发它字母本身的音。 -如果这个音节在单词中是重读,那就是**重读闭音节**了。 +总结: 开音节:only元 或 者元+辅+不发音e; 闭音节: 剩下的 + +### 重读闭音节 + +如果这个音节在单词中是闭音节并且重读,那就是**重读闭音节**了。 重读闭音节即两个辅音中间夹一个短元音(即**“辅元辅”结尾**。) -重读闭音节结尾,且词尾只有一个辅音字母,要**双写**。 +重读闭音节结尾,且词尾只有一个辅音字母,变ing要**双写**。 重读闭音节三要素 1.必须是重读音节; -2.最后只有一个辅音字母;只有一个元音; +2.最后只有一个辅音字母 3.只有一个元音,元音字母发短元音 -总结: 开音节:only元 或 者元+辅+不发音e; 闭音节: 剩下的 + ## 字母发音规则 @@ -158,6 +229,8 @@ tags: English +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208242353490.png) + aeiou在非重读闭音节里,一般发/ə/, 比如doctor后面的o @@ -241,4 +314,27 @@ z [ z ] -![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220112133608.jpg) \ No newline at end of file +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220112133608.jpg) + + + +## 常见单词音标 + +who when where why what whom which + +while [/waɪl/](cmd://Speak/_us_/while) + +why [ /waɪ/](cmd://Speak/_us_/why) + +when [/wɛn/](cmd://Speak/_us_/when) + + + +Allow: [美 /əˈlaʊ/](cmd://Speak/_us_/allow) a lao. + +provide. [美 /prə'vaɪd/](cmd://Speak/_us_/provide) p rou + +Begin: [美 /bɪˈɡɪn/](cmd://Speak/_us_/begin) bi gin + +Again: [美 /ə'ɡɛn/](cmd://Speak/_us_/again) a gan + diff --git "a/_posts/English/2021-01-02-\350\257\255\346\263\225-\345\215\225\350\257\215\345\220\204\347\247\215\345\275\242\345\274\217\345\217\230\345\214\226\350\247\204\345\210\231.md" "b/_posts/English/2021-01-02-\350\257\255\346\263\225-\345\215\225\350\257\215\345\220\204\347\247\215\345\275\242\345\274\217\345\217\230\345\214\226\350\247\204\345\210\231.md" index add11756d2..95c5f8eeb9 100644 --- "a/_posts/English/2021-01-02-\350\257\255\346\263\225-\345\215\225\350\257\215\345\220\204\347\247\215\345\275\242\345\274\217\345\217\230\345\214\226\350\247\204\345\210\231.md" +++ "b/_posts/English/2021-01-02-\350\257\255\346\263\225-\345\215\225\350\257\215\345\220\204\347\247\215\345\275\242\345\274\217\345\217\230\345\214\226\350\247\204\345\210\231.md" @@ -17,7 +17,7 @@ tags: English ![image-20210102200549238](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv1/v1/134.png) -这里的辅音字母指除元音字母aeiou之外的字母,非音标发音指写法。 +下面的辅音字母指除元音字母aeiou之外的字母,非音标发音指写法。 **名词所有格's 的读音与名词复数和动词第三人称单数相同** diff --git "a/_posts/English/2021-12-11-English\350\257\255\346\263\225.md" "b/_posts/English/2021-12-11-English\350\257\255\346\263\225.md" index 49c0659b8b..65c6abe165 100644 --- "a/_posts/English/2021-12-11-English\350\257\255\346\263\225.md" +++ "b/_posts/English/2021-12-11-English\350\257\255\346\263\225.md" @@ -74,3 +74,95 @@ he tells me that u have died [参考个人单独blog](https://mafulong.github.io/2021/01/02/%E8%AF%AD%E6%B3%95-%E8%99%9A%E6%8B%9F%E8%AF%AD%E6%B0%94/) +## “限制性定语从句”和“非限制性定语从句”的区别 + +> [参考](https://zhuanlan.zhihu.com/p/30548008) + +> She despised people ***who flattered her***. 这是限制性的。非限制性就是表定语 非限制的 +> (她看不起**那些吹捧她的**人。) + +在上面的示例中,定语从句“who flattered her”起到了**限制名词**的作用(即缩小了名词的表示范围)。 + +因此,我们称这类定语从句为**限制性定语从句**。 + + + +在有些场景下,先行词的指向已经是唯一的了,此时就无法再缩小这个词的表示范围了。例如: + +> I made a card for mom ***who loves me most**.* (×) +> (我给**最爱我的**那个妈妈做了一张卡片。) + +每个人只有一个妈妈,所以“最爱我的那个妈妈”的表述明显不合逻辑,此时我们就需要用到**非限制性定语从句**。 + +所谓的非限制性定语从句,就是**使用“逗号”将定语从句与先行词隔开**,即: + +> I made a card for mom, ***who loves me most**.* +> (我给妈妈做了一张卡片,**她最爱我**。) + +因为非限制性定语从句的作用是:**不直接修饰先行词,只为先行词提供一些补充的信息。** + + + +①限定性定语从句: 从句不能省略,如果省略整个句子意思不完整。 + +非限定性定语从句: 从句可以省略,如果省略整个句子意思仍然完整 。 + +②限定性定语从句: 先行词可以用that 引导。 + +非限定性定语从句: 先行词不可以用that 引导。 + +eg. I like read the book that you lend me. + +③限定性定语从句: 引导词有时可以省略。 + +非限定性定语从句: 引导词不可以省略。 + +④限定性定语从句: 主句与从句不需要用逗号隔开。 + +非限定性定语从句: 主句与从句需要用逗号隔开。 + +⑤限定性定语从句: 从句只修饰先行词。 + +非限定性定语从句:从句既可以修饰先行词,也可以修饰整个句子或句子的一部分。 + + + +## 逗号使用 + +> [参考](https://zhuanlan.zhihu.com/p/46736877) + +1、当有三个或三个以上并列的单词或短语时,这些单词或短语要用逗号隔开,而且最后一个单词或短语一般都会用and或or连接,如: + +We had coffee, cheese and crackers and grapes. + +2、当两个前置形容词并列且可以调换顺序时,要用逗号隔开,如: + +He is a strong, healthy man. vs. He is a healthy, strong man. + + + +1、如果是以从句开头,则要在从句末尾用逗号隔开,如: + +If you are not sure about this, let me know now. + +2、如果开头的是介绍性短语,一般也要用逗号隔开,如: + +Having finally arrived in town, we went shopping. + +3、如果介绍性短语是以介词开头引导的,介词后面如果跟的是句子的主语,则要用逗号。则无论这个短语包含多少个单词,都无需用逗号隔开,如: + +Between your house on Main Street and my house on Grand Avenue, the mayor's mansion stands proudly. + + + +1、同位语,如: + +My best friend, Joe, arrived. + +2、非限定性定语从句(非限制作用,也不能用that),如: + +Jill, who is my sister, shut the door. + +3、分词短语,如: + +The man, knowing it was late, hurried home. diff --git "a/_posts/English/2022-08-26-\350\213\261\350\257\255-\350\277\236\350\257\215.md" "b/_posts/English/2022-08-26-\350\213\261\350\257\255-\350\277\236\350\257\215.md" new file mode 100644 index 0000000000..2c077f1550 --- /dev/null +++ "b/_posts/English/2022-08-26-\350\213\261\350\257\255-\350\277\236\350\257\215.md" @@ -0,0 +1,612 @@ +--- +layout: post +category: English +title: 英语-连词 +tags: English +--- + +## 连词 + + + +## 不仅而且Not only but also + +[参考](https://en-grammar.xiao84.com/201612/27635.html) + +比较自由。also可省略, but also之间也可以加词。 + +可连接主语、谓语动词... + +The Americans and the British **not only** speak the same language **but also** share a large number of social customs. + + + +Note: + +- ”not only A but also B”结构中的A和B通常是同等成分。因此这句话是错误的“He not only plays the piano, but also the violin.” 可以改成“He plays **not only** the piano, but also the violin” + +## 也(too, as well, also, either) + +too和as well两者都可用在肯定句或疑问句的句末。两者通常可以换用。 + +She likes pizza as well. + +=She likes pizza too. + + + +- also: 常在**肯定句句中** +- too: 常在**肯定句句末**,前面常有逗号 +- either: 常在**否定句句末** He isn't a singer either. + + + +来自欧陆词典: + +[also](dic://also) 比too正式一些,语气较重,只用于肯定句,一般紧靠动词。 +[too](dic://too) 语气较轻,多用于口语,在肯定句中使用,通常位于句末。 +[as well](dic://as well) 一般不用否定句,通常放在句末,强调时可放在句中。 +[either](dic://either) 用于否定句,放在句末,之前加逗号。 +[likewise](dic://likewise) 是书面语用词。 + + + +也不nor + +He can't speak english, **nor** can I. + +## 除了 + +I did **nothing but** watch it + +## 然而 + +whereas + +Some praise him, **whereas** others condemn him + + + +## 尽管 + +**Although** she's young, she knows a lot. + +In spite of sth + +Despite sth + +## 只要 + +As long as I'm free, I will help you. + +**only if (连词)只有** + +The lawyer is paid only if he wins. + +当且仅当: if and only if xx + +## 要是xx就好了 + +if only + +There had to be an answer — he was sure he could tease it out if only he had time. + +## 以免 + +in case + +You should be careful **in case** there's a fire. + +## 为了 + +We study hard in order that we can pass the team. + + + +## 刚一。。。就。 + +推荐as soon as. + +**As soon as** I stepped inside,my glasses misted over. + +The stars came out **as soon as** darkness fell. + + + +**Hardly** I entered the room **when** the bell rang. + +**No sooner** had I come home **than** it began to rain. + +**Scarcely** had she entered the room **when** the phone rang. + +## 既然现在 + +**Now that** you are on duty, you should clean the classroom. + +## 无论 + +**Whether** you are sick **or** not, I'll help you. + +**No matter** how bad the weather is, the children will play foot-ball on the playground, notwithstanding + +**No matter** what it takes I will do the work. + +**Whatever** the reason, just hang in there. + +## 而不是 + +They relied on brains **rather than** brawn + +He preferred to die **instead of** stealing. + +## 尽可能地快 + +as soon as possible, soon: *adv.* 不久;即刻,马上 + +We must correct our defects **as soon as possible.** + +as quickly as possible. + +## 如果...将会怎么样? + +**What if** the car breaks down? + + + +## 15类英语连接词 + +https://zhuanlan.zhihu.com/p/277394043 + +整理分享15类英语连接词,超级全。 + +### 表示强调 + +still 然而,仍然 + +indeed 事实上 + +apparently 显然 + +oddly enough 奇怪的是 + +of course 当然 + +after all 毕竟 + +significantly 明显地;显著地 + +interestingly 有趣的是 + +also 并且,另外 + +surely 肯定/必定地 + +certainly 当然 + +undoubtedly 毫无疑问地 + +in any case 在任何情况下;无论如何 + +anyway 反正;不管怎样;总之 + +above all 首先,尤其是;最重要的是 + +in fact 事实上 + +especially 特别是 + +obviously 很明显 [ /'ɑbvɪəsli/](cmd://Speak/_us_/obviously) + +clearly 无疑的 + +例:In fact, he is not an honest man. + +事实上,他不是个诚实的人。 + + + +### 表示比较 + +like 就像 + +similarly 类似的 be similar to + +likewise 同样的;照样的 副词。 + +in the same way 以同样的方式 + +in the same manner 以类似的方式 + +equally 同样地;相等程度地 + +例:I will help you solve this problem, but equally you need to give me some reward. + +我会帮你们解决这个问题,但是同样地,你们需要付出一些回报。 + + + +### 表示对比 + +by contrast 相比之下 + +on the contrary 相反 + +while 而;虽然,尽管 + +whereas 而 + +on the other hand 另一方面 + +unlike 不像 + +instead 而不是;而;相反 + +but 但 + +conversely 相反 + +different from 不同于 + +however 然而 + +nevertheless 不过 + +otherwise 否则 + +yet 然而, 但是 + +in contrast 与此相反;比较起来 + +例:On the other hand, they are also unwilling to adopt our advice. + +另一方面,他们也不愿意采纳我们的建议。 + + + +### 表示列举 + +for example 例如 + +for instance 例如 + +such as 如 + +take …for example 以……为例 + +except (for) 要不是由于 *except for* 除了…以外;要不是由于 + +to illustrate 为了说明(举例说明) + +例:We bought some fruits such as apples, oranges and pears. + +我们买了一些水果,有苹果橘子还有梨。 + + + +### 表示时间 + +later 后来;稍后;随后 + +next 然后;其次;接下来;下一... + +then 然后 + +finally 最终终于 + +at last 最后 + +eventually 最终 [ɪ'vɛntʃuəli/](cmd://Speak/_us_/eventually) + +meanwhile 与此同时 + +from now on 从现在开始 I have made up my mind not to go skating from now on. 我决心从此不再去滑冰. + +at the same time 同时 + +for the time being 暂时 adv + +in the end 最后 + +immediately 立即 + +in the meantime 在此期间;于此际;与此同时 + +in the meanwhile 同时;在此期间 + +recently 最近 + +soon 很快 + +now and then 偶尔;有时;不时 occasionally + +during 在…...的期间;在......期间的某个时候 The pond iced over during the night. + +nowadays 如今 + +since 自……以后;自……以来;此后;之前 + +lately 最近 + +as soon as 一旦 + +afterwards 后来 + +temporarily 暂时 + +earlier 早些时候 + +now 现在 + +after a while 不久;过了一会儿 + +例:She answered the question immediately. + +她迅速回答了这个问题。 + + + +### 表示顺序 + +first 首先,第一 + +second 第二 + +third 第三 + +then 然后 + +finally 最后 + +to begin with 首先,本来,一方面 To begin with, we must consider the faculties of the staff all-sidedly. + +first of all 首先 + +in the first place 首先 + +last 最后 + +next 下一个 + +above all 最重要的是 + +last but not the least 最后一点,也是非常重要的一点 + +first and most important 第一点也是最重要的一点 + +例:First of all, you should obey rules as a student. + +作为一个学生,首先你要遵守学校里的规章制度。 + + + +### 表示可能 + +presumably 大概 + +probably 可能 + +perhaps 也许 + +例:Perhaps he will not show up today. + +他今天可能不会露面了。 + + + +### 表示解释 + +in other words 换句话说 + +in fact 事实上 + +as a matter of fact 事实上 + +that is 即;就是说;换言之 + +namely 也就是;即是;换句话说 Namely, a return to white supremacy and institutional slavery. + +in simpler terms 简单来说 **To put it simply**, everything happens instantaneously. **In short,** the building would consist of two floors. + +例:He departed from his hometown, namely, London. + +他离开了自己的故乡,也就是伦敦。 + + + +### 表示递进 + +What is more 更重要的是;此外,而且 + +in addition 另外,此外 + +and 并且,以及 + +besides 况且,再说;此外,以及 + +also 而且;同样;还有 + +furthermore 此外 + +too 也;还 + +moreover 此外;而且 + +furthermore 此外 + +as well as 以及 + +additionally 另外 + +again 再说;又 + +例:Susan is a beautiful girl, what is more, she works hard all the time. + +苏珊是个漂亮的女孩,而且,她也一直努力工作。 + + + +### 表示让步 + +although 虽然 + +after all 毕竟 + +in spite of …尽管…… + +despite 尽管 + +even if 即使 + +even though 尽管 + +though 虽然 + +admittedly 不可否认 + +whatever may happen 不管三七二十一 + +例:Although you told a lie before, we believe in you now. + +虽然你以前说过谎,但我们现在相信你。 + + + +### 表示转折 + +however 然而 + +rather than 而不是;宁可…也不愿 + +instead of 而不是... + +but 但 + +yet 然而 + +on the other hand 另一方面 + +unfortunately 不幸的是 + +whereas 然而;鉴于; 反之 + +例:However, he did not pass the exam. + +然而,他没有通过考试。 + + + +### 表示原因 + +for this reason 因为这个原因 + +due to 由于 + +thanks to 多亏了 + +because 因为 + +because of 由于 + +as 因为,由于 + +since 因为,由于 + +owing to 由于 + +例:She broke up with her boyfriend because of a terrible quarrel. + +因为大吵了一架,她和男友分手了。 + + + +### 表示结果 + +as a result 结果.... + +thus 因此 + +hence 因此 + +so 所以 + +therefore 因此 + +accordingly 因此,于是 + +consequently 因此;结果;所以; 从而 + +as a consequence 因此,结果 + +例:Therefore, I decide to make an apology to him. + +因此,我决定向他道歉。 + + + +### 表示总结 + +on the whole 基本上,大体上;总的来说 + +in conclusion 总之;最后 + +in a word 总之 + +to sum up 总而言之;概括地说 + +in brief 简言之 + +in summary 总之 + +to conclude 最后;总之 + +to summarize 简而言之 + +in short 总之;简言之 + +例:In conclusion, we d better take measures as soon as possible. + +总之,我们最好尽可能快采取措施。 + + + +### 其他连接词 + +mostly 主要是;通常;多半地 + +occasionally 偶尔;有时 + +currently 目前 + +naturally 自然而然地 + +mainly 主要是 + +exactly 恰好地;正是 + +evidently 毫无疑问地; 显然 + +frankly 坦率地说;老实说 to be honest + +commonly 一般;通常;普遍 + +for this purpose 为此;有鉴于此 + +to a large extent 在很大程度上 + +for most of us 对我们大多数人来说 + +in many cases 在许多情况下 + +in this case 在这种情况下 + +例:Frankly, I do not mind her opinions at all. + +坦白说,我一点都不在乎她的看法。 + + + +according to 根据 + +## 参考 + +- https://www.sohu.com/a/330378162_100219060 diff --git "a/_posts/Games/Xbox/2021-10-03-xbox\345\267\264\350\245\277\346\234\215\350\264\255\344\271\260\346\225\231\347\250\213.md" "b/_posts/Games/Xbox/2021-10-03-xbox\345\267\264\350\245\277\346\234\215\350\264\255\344\271\260\346\225\231\347\250\213.md" deleted file mode 100644 index d8dd198080..0000000000 --- "a/_posts/Games/Xbox/2021-10-03-xbox\345\267\264\350\245\277\346\234\215\350\264\255\344\271\260\346\225\231\347\250\213.md" +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: post -category: Xbox -title: xbox巴西服购买教程 -tags: Xbox ---- - -## xbox巴西服购买教程 - - - -1. 微软账号个人信息编辑,编辑所在国家,添加账单和送货地址,选巴西的。https://account.microsoft.com/profile/?fref=home.banner.profile-column.title - - ``` - 平台如果无法编辑删除,就新加个然后设为首选账单地址。 - - Rua Estados Unidos, No. 1071 - 城市: CRISOLOGO - 洲: 圣保罗州01427-001 - 巴西 - 61-21958270 - ``` - -2. 添加付款方式,信用卡支付 - -3. xbox登录西班牙服,记住购买时不能开加速器。选择已有购买方式就可以了。 \ No newline at end of file diff --git a/_posts/Tech/AWS/2021-09-30-AWS Overall.md b/_posts/Tech/AWS/2021-09-30-AWS Overall.md new file mode 100644 index 0000000000..4ee54bd11f --- /dev/null +++ b/_posts/Tech/AWS/2021-09-30-AWS Overall.md @@ -0,0 +1,268 @@ +--- +layout: post +category: AWS +title: AWS Overall +tags: AWS +--- + +## Cloud Notes + +IaaS、PaaS 和SaaS 的区别: 基础设施即服务(IaaS) 为云服务提供硬件,其中包括服务器、网络和存储。 平台即服务(PaaS) 除了提供IaaS 可提供的所有硬件之外,还提供操作系统和数据库。 软件即服务(SaaS) 提供了最多的支持,即为您的最终用户提供除其数据之外的所有服务。 + +[参考](https://www.zhihu.com/question/20387284) + +如果你是一个网站站长,想要建立一个网站。不采用云服务,你所需要的投入大概是:买服务器,安装服务器软件,编写网站程序。 + +现在你追随潮流,采用流行的云计算, + +如果你采用IaaS服务,那么意味着你就不用自己买服务器了,随便在哪家购买虚拟机,但是还是需要自己装服务器软件 + +而如果你采用PaaS的服务,那么意味着你既不需要买服务器,也不需要自己装服务器软件,只需要自己开发网站程序 + +如果你再进一步,购买某些在线论坛或者在线网店的服务,这意味着你也不用自己开发网站程序,只需要使用它们开发好的程序,而且他们会负责程序的升级、维护、增加服务器等,而你只需要专心运营即可,此即为SaaS。 + + + +## AWS Overall Notes + +云从业者Note: https://github.com/Matthewow/AWS-CLF-StudyNotes + + + +很好overall: https://www.zhihu.com/question/22314873 + + + +云的基础是计算、存储、网络,这三部分涵盖了互联网应用的各个方面,所有的云服务也是围绕这三部分去展开。 + + + +AWS的服务是按照region来划分的,在部署自己的应用之前,需要选择region,比如美国有us-west, us-east regions, 中国有cn-north, cn-west regions。基本上按照服务用户所在的区域来选择region,服务中国的用户就选择中国的region,服务美国的用户就选择美国的region。否则这个网络传输的成本就非常高,而且中国区其独有的网络环境,导致其他地区的服务是无法访问的。一个region又划分为多个AZ (availability zone), 一般情况下,我们需要把服务器均匀分布在多个AZ,为了避免单点故障,也就是我们所说的灾备多活。 + + + +选择好region后,就需要部署自己的[VPC](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/vpc/home) (virtual private cloud),一个VPC定义了一个私有隔离的网络环境。在VPC里面,我们部署所有的计算、网络资源。计算资源就是我们的服务器,AWS最出名的就是[EC2](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/ec2) (elastic compute cloud)。在部署EC2时,我们首先预估应用需要消耗的计算资源(cpu,磁盘,带宽等等),选择EC2的型号和数量。然后将所有的EC2分割成多个[ASG](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/ec2autoscaling/home) (auto scaling group), 一个ASG就相当于一个可弹性收缩的机器群,只要定义好扩容和缩容的指标,ASG就可以自己分配机器的数量。比如我们要求在EC2 CPU升到40%就扩容一倍,在CPU降到10%就缩容一倍,这样一个ASG里面机器CPU的消耗就一直均衡地保持在20%左右。具体分割成几个ASG,一般依据这个region有几个AZ来定,比如[us-east-1 region](https://www.zhihu.com/search?q=us-east-1+region&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})有3个AZ,就分割成三个ASG,一个AZ部署一个ASG,这三个ASG在接受流量方面没有任何区别。接下来就是网络资源,每个VPC都有自己的ACL(acess control list),一个ACL定义了inbound rules和outbound rules,分别限制了访问IP的限制和访出IP的限制。VPC的网络资源被划分成多个子网subnets,一个subnet是一组IP地址的集合,前面说到的ASG就部署在[subnet](https://www.zhihu.com/search?q=subnet&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})里面。一般来说subnet的数据量跟region AZ的数据成正比,每个AZ分配一个public subnet和一个private subnet,那么us-east-1的VPC就会有6个subnets。我们将ASG部署在private subnet里面,只允许vpc内部的IP访问,用于保护机器资源。因为public subnet是对外的,所以我们在public subnet里面部署ELB (elastic load balancer),用于接受vpc外的请求。有人会问如果我们想登录到ASG的EC2上面,应该怎么做?解决办法是在public subnet里面launch一个跳板机,我们先登录到跳板机,然后从跳板机里面登录到EC2上面。 + +接下来就是存储资源,AWS提供多种选择,我们最熟悉的应该就是[S3](https://link.zhihu.com/?target=https%3A//s3.console.aws.amazon.com/s3/home) (simple storage service)。S3是面向对象存储的服务,可以用来做数据归档、备份、恢复,或者作为数据分析、AI、Machine learning的数据湖来使用。通俗的理解就是我们的磁盘,它存储的key就是一个目录路径,相当于磁盘的目录,value是一个object,相当于文件或者子目录。在线的存储根据功能的不同也有很多选择,比较出名的而且是我用过的有三个。第一个是[DynamoDB](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/dynamodb/home),它是document-based NoSQL DB。DynamoDB是Amazon内部使用最频繁的数据,几乎90%的存储都会选择DynamoDB,绝对地超过RDS。这个现象的原因在这篇文章[Dynamo: Amazon’s Highly Available Key-value Store](https://link.zhihu.com/?target=https%3A//www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf)里面有解释,同时DDIA (Design [data-intensive application](https://www.zhihu.com/search?q=data-intensive+application&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})) 这本书也给了同样的解释。总结就是:Amazon内部的存储90%以上都是单对多的关系,DynamoDB作为一个分布式的key-value DB,在可用性、扩展性方面非常适合这种单对多的存储结构。而且DynamoDB是最终一致性,进一步增加了它的可用性。关于DynamoDB我后面会单独出一篇介绍它的blog。而对于多对多的存储,我们就会用[RDS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/rds) (relational database service),RDS字面理解就是[关系型数据库](https://www.zhihu.com/search?q=关系型数据库&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})。AWS RDS上面托管了多种关系型数据库,包括mysql、oracle、MS SQL、aurora、MariaDB和PostgreSQL这六种数据库,其中我使用过mysql和aurora。在Amazon内部,[aurora](https://link.zhihu.com/?target=https%3A//docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html)使用的更频繁,它是兼容mysql和PostgreSQL的结合体,具体内容可见这篇文章[Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases](https://link.zhihu.com/?target=https%3A//www.allthingsdistributed.com/files/p1041-verbitski.pdf)。我使用的最后一种存储是[AWS elasticsearch](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/esv3/home) ,当时为了实现一个搜索功能,因为涉及到模糊匹配,全文搜索,就采用了[aws es](https://www.zhihu.com/search?q=aws+es&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})。aws es跟主流的es已经分道扬镳,目前在aws上面称为openSearch service。 + +介绍完计算、网络、存储,接下来我想从应用的角度,讲讲在实际应用中,我们应该怎样使用AWS的服务。首先说消息队列,这个广泛应用的基础功能,AWS提供了[SQS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sqs/v2/home)和[SNS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sns/v3/home)。SQS是一个分布式的队列消息service,SNS是一个分布式的发布-订阅消息service。具体有人会问这两者有什么区别,这里给出了回答: [What is the difference between Amazon SNS and Amazon SQS?](https://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/13681213/what-is-the-difference-between-amazon-sns-and-amazon-sqs)。 在我的实践中,SQS和SNS会结合起来使用,首先应用发布消息到SQS的queue里面,然后SNS消费这个queue的消息,放到自身的topic里面持久保存,然后其他的应用订阅这个topic,消费里面的消息。 + + + +建设完一个应用,然后就是DevOps。AWS在[托管代码](https://www.zhihu.com/search?q=托管代码&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2333079486})、编译代码、部署应用、监控应用方面也提供了一整套服务,从前到后,[codeCommit](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/codesuite/codecommit) -> [codeArtifact](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/codesuite/codeartifact) -> [codeBuild](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/codesuite/codebuild) -> [codeDeploy](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/codesuite/codedeploy) -> [codePipeline](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/codesuite/codepipeline)。这一套实现了应用的continuous integration和continuous deployment。在监控方面,AWS提供了[cloudWatch](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/cloudwatch),可以以应用维度收集日志,视图化监控指标。AWS还提供了一个服务叫cloudFormation,这个服务在应用迁移的过程中,非常有用。Amazon内部很多都是国际化业务,应用需要部署到各个region。cloudFormation以yaml文本的形式记录下一个应用涉及到的各个服务资源配置,放在一个template里面。迁移到不同的region时,只需要一键run coudFormation template, 就可以部署好所有的AWS资源。 + + + +Notes: + +- region: 包含多个AZ, named by location 用aws需要选个region + +- AZ: availability zone. 包含一主多从的cluster,多个data center, 可以有另一个AZ继续保持主从。 region分多个AZ主要是为了容灾。 + +- region ---> AZ ----> data center 都是一对多。 一般情况下,region之间不会保持同步,互相独立,除非客户允许。 + + + +![image-20220930233334175](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209302333289.png) + + + +- 选择region考量: compliance, latency, price, service availability + +## 术语 + +- IAM: identify and access management. 同account也是aws account level, account每月会进行计费,可以任意region里创建资源, work globally. 像s3等存储都是region level. EC2等就是AZ level. 意味着一个ec2不能分布在两个AZ。 IAM可以创建用户,然后授予权限给某个group + +- SQS: simple queue service + +- SNS: simple notification service. 发一些同事用的,比如发短信等。 + +- Aws route 53是 aws DNS + +- Amazon CouldFront 是CDN + +- 对象存储 + + - S3: simple storage service + +- cloudwatch for monitoring + +- ELB: elastic load balancer + +- EC2是 virtual machines. 全称: elastic compute cloud. 一共三种compute资源: vm, container services, serverless + +- AMI: amazon machine image 就是一堆配置,比如什么系统,安装哪些附加软件。可使用AMI启动一个同配置的实例。 类似docker的image + +- AWS Elastic Beanstalk 是一个应用程序管理平台,可以帮助客户轻松部署和扩展 Web 应用程序和服务。它将构建块(例如 EC2、Amazon RDS、Elastic Load Balancing、AWS Auto Scaling 和 Amazon CloudWatch)的预置、应用程序的部署、运行状况监控从用户身上分离出来,让用户可以集中精力编写代码。您只需指定要部署的容器映像、CPU 和内存要求、端口映射和容器链接即可。 + + Elastic Beanstalk 将自动处理所有的具体事务,包括预置 Amazon ECS 集群、均衡负载、自动扩展、监控以及在集群中放置容器。如果您希望利用容器的各种优势,但只想通过上传容器映像,在开发到生产等环节部署应用程序时享受到简易性,则 Elastic Beanstalk 非常适合。如果您需要对自定义应用程序架构进行更多精细化的控制,则可以直接使用 Amazon ECS。 + +- EBS: elastic block store. EC2也需要本地存储,这就是EBS 块存储。例如SSD等。它是外部挂载形式提供的,实例关机了,数据也还在,也可以做备份。适合那种临时数据,而非长期存储。一次只能挂载到一个AZ里的一个实例。 如果想多个实例连一个存储,可以用EFS/FSx, 前者linux,后者windows。 + + + +![image-20220930235011369](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209302350393.png) + +![image-20220930234951120](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209302349145.png) + +![image-20220930234939946](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209302349976.png) + +![image-20220930235109632](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209302351656.png) + + + +## Example: build an application like facebook ,a social media app. + +AWS介绍视频: [youtube](https://www.youtube.com/watch?v=Z3SYDTMP3ME&ab_channel=AWSTrainingCenter) + + + +![image-20221001104944376](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210011049445.png) + + + +![image-20221001110948079](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210011109118.png) + + + +![image-20221001144122955](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210011441985.png) + + + +加一些安全组件,IAM负责每个附件是否可访问,account管理范围等。 KMS负责数据的加密。 ACM负责access certificate management, 比如https证书等,在ELB这块。 WAF是防火墙,比如防止Ddos攻击之类的,在elb之前。Inspectoer负责监控每个service的安全,类似容器里一个agent。 + + + +![image-20221001150745631](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210011507662.png) + + + +codeCommit类似github这样代码仓库。 + +## Example: WordPress + +[link](http://www.cloudbin.cn/?p=2647) + +## 考证 + +[参考](https://zhuanlan.zhihu.com/p/138652055) + +先考**AWS Certified Solutions Architect - Associate** AWS 认证助理级解决方案架构师考试,这门考试更注重 AWS 的架构设计。助理级解决方案架构师需要基本了解 AWS 的几个基础的组件,比如 EC2,VPC, IAM, S3, Route53 等,掌握它们分别是什么,使用场景是什么,和传统数据中心的区别和优势等等 + + + +云从业者那个可以看这个: https://github.com/Matthewow/AWS-CLF-StudyNotes + + + + + +## 对象存储: S3及S3 Glacier + +> [官网](https://aws.amazon.com/s3/storage-classes/) + +Amazon S3 is object-level storage, which means that if you want to change a part of a file, you have to make the change and then re-upload the entire modified file. 对象存储,比较像字节的TOS。 也是以bucket隔离的,然后bucket可设置是否公开,可设置每个文件多版本, 可作为一个静态博客的host。 [对象存储参考](https://mafulong.github.io/2022/10/06/%E5%9D%97%E5%AD%98%E5%82%A8%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8%E5%92%8C%E6%96%87%E4%BB%B6%E5%AD%98%E5%82%A8/) + +Amazon S3 Glacier is a great storage choice when low storage cost is paramount, your data is rarely retrieved, and retrieval latency of several hours is acceptable. If your application requires fast or frequent access to your data, consider using Amazon S3. Objects stored in Amazon S3 Glacier are called *archives*. 相比s3,价格便宜,适合平时不太访问的归档数据。 + + + +## 计算层:EC2 + + + +3种付费方案: + +1. on-demand, 运行时收费,按秒计费,价格固定。 +2. Reserved instances. (RIs) 预订 选定期的,有折扣。这种情况下即便选择不预付,非运行时也算收费。 它需要绑定一个instance type. +3. Spot instances. 类似出钱,然后aws自己评估这价格可以给几个实例这样。如果不够就掐掉实例。是最便宜的,但需要容忍突然停。 + +Dedicated hosts: 把物理机控制也给你,贵。和其他公司隔离开。 + + + +## 数据库 + +RDB: Amazon RDS, Redshit, Aurora. + +NoSQL: DynamoDB, Nepture, ElastiCache + + + + + +## IAM + + + +IAM (Identity Access Management) 由这些东西组成: + +- Users +- Groups 用户组 +- Roles 角色可以分配给AWS服务,让AWS服务有访问其他AWS资源的权限。 举个例子,我们可以赋予EC2实例一个角色,让其有访问S3的读写权限(后面课程会有关于这一点的实操) +- Policy Documents 策略。 策略具体定义了能访问哪些AWS资源,并且能执行哪些操作(比如List, Read, Write等) 策略的文档以JSON的格式展现 + +``` +// An example policy: allowing any access to any resource +{ + "Version": "2012-10-17" + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] +} +``` + + + +IAM 是 Global的,不属于任何一个 region + +Root 账号是你第一次配置账号的时候创建的,它拥有完全的 Admin aceess + +新 User 刚创建时是没有权限的。 + + + +首先要知道, AWS 提供了许许多多种类的服务或者说资源供我们使用, 这些资源挂在我们的 AWS 账户下, 这个账户就是我们第一次用 AWS 时用邮箱和密码申请的, 以后我们所有的资源申请, 账单费用都会挂到这个户头. + +那么 IAM 算什么呢? IAM 不是 AWS 的专有名词, 它是一个通用概念, 全称是 Identity and Access Management, 其要解决的两个问题就是身份认证 (Authtication) 和授权 (Authorization). 为此 AWS IAM 设计了用户, 角色, 用户组, 权限策略等概念和机制 + + + +场景 + +1. 自己公司的员工想访问 AWS Console 查看所在项目组的基础设施 +2. AWS 账户里的 EC2/Lambda 实例想访问同账户下的一台 RDS +3. 公网里用户的手机想要访问我们的后端存储(借助 API Gateway) + + + +AWS IAM 并不关心你创建的 IAM User 是给人用还是给程序用 + + + +AWS 官方建议 root 用户的唯一用途, 就是[用来创建你的第一个 IAM 用户](https://link.zhihu.com/?target=https%3A//docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html%23lock-away-credentials), 把这第一个 IAM 用户设置为管理员, 然后以后的工作都用这个管理员用户来进行. + + + +## SNS vs SQS + +AWS提供了[SQS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sqs/v2/home)和[SNS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sns/v3/home)。SQS是一个分布式的队列消息service,SNS是一个分布式的发布-订阅消息service。具体有人会问这两者有什么区别,这里给出了回答: [What is the difference between Amazon SNS and Amazon SQS?](https://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/13681213/what-is-the-difference-between-amazon-sns-and-amazon-sqs)。 在我的实践中,SQS和SNS会结合起来使用,首先应用发布消息到SQS的queue里面,然后SNS消费这个queue的消息,放到自身的topic里面持久保存,然后其他的应用订阅这个topic,消费里面的消息。 + + + +sqs是一对一,不能一对多,消息可持久化,不推只能等拉,拉完就删除。 + +sns可一对多,不能持久化,push模型。 + +sqs及aws笔记,更全, [link](http://www.cloudbin.cn/?p=2530) + + + +## 参考 + +[参考](http://www.cloudbin.cn/?tag=aws) + diff --git a/_posts/Tech/AWS/2022-10-04-AWS network.md b/_posts/Tech/AWS/2022-10-04-AWS network.md new file mode 100644 index 0000000000..a0826ac349 --- /dev/null +++ b/_posts/Tech/AWS/2022-10-04-AWS network.md @@ -0,0 +1,324 @@ +--- +layout: post +category: AWS +title: AWS Network +tags: AWS +--- + +# AWS network + +## 名词解释 + +[参考](https://docs.amazonaws.cn/vpc/latest/userguide/what-is-amazon-vpc.html) + +- **Virtual Private Cloud (VPC)** + + [VPC](https://docs.amazonaws.cn/vpc/latest/userguide/configure-your-vpc.html) 是一个虚拟网络,与您在自己的数据中心中运行的传统网络极为相似。创建 VPC 后,您可以添加子网。翻译成中文是虚拟私有云。 + +- **子网** + + [子网](https://docs.amazonaws.cn/vpc/latest/userguide/configure-subnets.html)是您的 VPC 内的 IP 地址范围。子网必须位于单个可用区中。在添加子网后,您可以在 VPC 中部署 Amazon 资源。 + +- **IP 寻址** + + 您可以将 IPv4 地址和 IPv6 地址分配到 VPC 和子网。您还可以将您的公有 IPv4 和 IPv6 GUA 地址带到 Amazon 并将其分配到 VPC 中的资源,例如 EC2 实例、NAT 网关和网络负载均衡器。创建 VPC 时,需要为其分配一个 IPv4 CIDR 块(一系列私有 IPv4 地址)、一个 IPv6 CIDR 块或同时分配两种 CIDR 块(双堆栈)。 + + 私有 IPv4 地址无法通过 Internet 访问。IPv6 地址具有全球唯一性,可以配置为保持私有或通过互联网进行访问。 + +- **路由选择** + + 使用[路由表](https://docs.amazonaws.cn/vpc/latest/userguide/VPC_Route_Tables.html)决定将来自您的子网或网关的网络流量定向到何处。 + +- **网关和端点** + + [网关](https://docs.amazonaws.cn/vpc/latest/userguide/extend-intro.html)将您的 VPC 连到其他网络。例如,使用[互联网网关](https://docs.amazonaws.cn/vpc/latest/userguide/VPC_Internet_Gateway.html)将您的 VPC 连接到网络。使用 [VPC 端点](https://docs.amazonaws.cn/vpc/latest/privatelink/privatelink-access-aws-services.html)私下连接到 Amazon Web Services,无需使用互联网网关或 NAT 设备。 + +- **对等连接** + + 使用 [VPC 对等连接](https://docs.amazonaws.cn/vpc/latest/peering/)在两个 VPC 中的资源之间路由流量。 + +- **流量镜像** + + 从网络接口[复制网络流量](https://docs.amazonaws.cn/vpc/latest/mirroring/),然后将其发送到安全和监控设备进行深度数据包检查。 + +- **中转网关** + + 将[中转网关](https://docs.amazonaws.cn/vpc/latest/userguide/extend-tgw.html)用作中央枢纽,以在 VPC、VPN 连接和 Amazon Direct Connect 连接之间路由流量。 + +- **VPC 流日志** + + [流日志](https://docs.amazonaws.cn/vpc/latest/userguide/flow-logs.html)捕获有关在 VPC 中传入和传出网络接口的 IP 流量的信息。 + +- **VPN 连接** + + 使用 [Amazon Virtual Private Network (Amazon VPN)](https://docs.amazonaws.cn/vpc/latest/userguide/vpn-connections.html) 将 VPC 连接到您的本地网络。 + +## VPC + +**Amazon Virtual Private Cloud (Amazon VPC)**允许你在已定义的虚拟网络内启动AWS资源。这个虚拟网络与你在数据中心中运行的传统网络极其相似,并会为你提供使用AWS的可扩展基础设施的优势。 + +简单来说,VPC就是一个AWS用来隔离你的网络与其他客户网络的虚拟网络服务。在一个VPC里面,用户的数据会逻辑上地与其他AWS租户分离,用以保障数据安全。 + +**可以简单地理解为一个VPC就是一个虚拟的数据中心**,在这个虚拟数据中心内我们可以创建不同的子网(公有网络和私有网络),搭建我们的网页服务器,应用服务器,数据库服务器等等服务。 + +### VPC有如下特点 + +- VPC内可以创建多个子网 + +- 可以在选择的子网上启动EC2实例 + +- 在每一个子网上分配自己规划的IP地址 + +- 每一个子网配置自己的路由表 + +- 创建一个Internet Gateway并且绑定到VPC上,让EC2实例可以访问互联网 + +- VPC对你的AWS资源有更安全的保护 + +- 部署针对实例的安全组(Security Group) + +- 部署针对子网的**网络控制列表(Network Access Control List)** + +- 一个VPC可以跨越多个可用区(AZ) + +- **一个子网只能在一个可用区(AZ)内** + +- 安全组(Security Group)是 + + 有状态的 + + ,而网络控制列表(Network Access Control List)是 + + 无状态的 + + - 有状态:如果入向流量被允许,则出向的响应流量会被自动允许 + - 无状态:入向规则和出向规则需要分别单独配置,互不影响 + - 具体的区别挨踢小茶会在后续的章节详细讲解 + +- VPC的子网掩码范围是从/28到/16,不能设置在这个范围外的子网掩码 + +- VPC可以通过Virtual Private Gateway (VGW) 来与企业本地的数据中心相连 + +- VPC可以通过AWS PrivateLink访问其他AWS账户托管的服务(VPC终端节点服务) + + + +### VPC Peering + +**VPC Peering**可是两个VPC之间的网络连接,通过此连接,你可以使用IPv4地址在两个VPC之间传输流量。这两个VPC内的实例会和如果在同一个网络一样彼此通信。 + +- 可以通过AWS内网将一个VPC与另一个VPC相连 +- 同一个AWS账号内的2个VPC可以进行VPC Peering +- 不同AWS账号内的VPC也可以进行VPC Peering +- 不支持VPC Transitive Peering + - 如果VPC A和VPC B做了Peering + - 而且VPC B和VPC C做了Peering + - 那么VPC A是**不能**和VPC C进行通信的 + - 要通信,只能将VPC A和VPC C进行Peering + +### 默认VPC + +- 在每一个区域(Region),AWS都有一个默认的VPC +- 在这个VPC里面所有子网都绑定了一个路由表,其中有默认路由(目的地址 0.0.0.0/0)到互联网 +- 所有在默认VPC内启动的EC2实例都可以直接访问互联网 +- 在默认VPC内启动的EC2实例都会被分配公网地址和私有地址 + +如下图所示,我们在某一个区域内有一个VPC,这个VPC的网络是172.31.0.0/16 + +在这个VPC内有2个子网,分别是172.31.0.0/20 和 172.31.16.0/20。这两个子网内都有一个EC2实例,每一个实例拥有一个该子网的私有地址(172.31.x.x)以及一个AWS分配的公网IP地址(203.0.113.x)。 + +这两个实例关联了一个主路由表,该路由表拥有一个访问172.31.0.0/16 VPC内流量的路由条目;还有一个目的为 0.0.0.0/0 的默认路由条目,指向Internet网关。 + +因此这两个实例都可以通过Internet网关访问外网。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211192248477.png) + +### VPC终端节点(VPC Endpoints) + +在一般的情况下,如果你需要访问S3服务,EC2实例或者DynamoDB的资源,你需要通过Internet公网来访问这些服务。有没有更快速、更安全的访问方式呢? + +**VPC终端节点(VPC Endpoints)**提供了这种可能性。 + +VPC终端节点能建立VPC和一些AWS服务之间的高速、私密的“专线”。这个专线叫做PrivateLink,使用了这个技术,你无需再使用Internet网关、NAT网关、VPN或AWS Direct Connect连接就可以访问到一些AWS资源了! + + + +## 工作原理 + +[参考](https://docs.amazonaws.cn/vpc/latest/userguide/how-it-works.html) + +### IP 寻址 +创建 VPC 时,需要为其分配一个 IPv4 CIDR 块(一系列私有 IPv4 地址)、一个 IPv6 CIDR 块或同时分配两种 CIDR 块(双堆栈)。 + +私有 IPv4 地址无法通过 Internet 访问。IPv6 地址具有全球唯一性,可以配置为保持私有或通过互联网进行访问。 + +公有 IP 地址将从 Amazon 的公有 IP 地址池分配,它不与您的账户关联。在公有 IP 地址与您的实例取消关联后,该地址即释放回该池,并且不再可供您使用。您不能手动关联或取消关联公有 IP 地址。而是在某些情况下,我们从您的实例释放该公有 IP 地址,或向其分配新地址。有关更多信息,请参阅适用于 Linux 实例的 Amazon EC2 用户指南 中的公有 IP 地址。 + +### 访问 Internet + +> 私有网络不能访问互联网,需要互联网网关,私有网络内部之间可通信。 + +原定设置 VPC 包含一个互联网网关,而且每个原定设置子网都是公有子网。您在默认子网中启动的每个实例都有一个私有 IPv4 地址和一个公有 IPv4 地址。这些实例可以通过 Internet 网关与 Internet 通信。通过互联网网关,您的实例可通过 Amazon EC2 网络边界连接到 Internet。 + +默认情况下,您启动到非默认子网中的每个实例都有一个私有 IPv4 地址,但没有公有 IPv4 地址,除非您在启动时特意指定一个,或者修改子网的公有 IP 地址属性。这些实例可以相互通信,但无法访问 Internet。 + +您可以通过以下方式为在非默认子网中启动的实例启用 Internet 访问:将一个互联网网关附加到该实例的 VPC(如果其 VPC 不是默认 VPC),然后将一个弹性 IP 地址与该实例相关联。 + +或者,您还可以使用网络地址转换 (NAT) 设备,以允许 VPC 中的实例发起到互联网的出站连接,但阻止来自互联网的未经请求的入站连接。NAT 将多个私有 IPv4 地址映射到一个公有 IPv4 地址。您可以使用弹性 IP 地址配置 NAT 设备,并通过互联网网关将其与互联网相连。您可以通过 NAT 设备将私有子网中的实例连接到互联网,NAT 设备会将来自实例的流量路由到互联网网关,并将所有响应路由到该实例。 + +如果您将 IPv6 CIDR 块与 VPC 关联并为实例分配 IPv6 地址,则实例可以通过互联网网关通过 IPv6 连接到互联网。或者,实例也可以使用仅出口互联网网关经由 IPv6 发起到互联网的出站连接。IPv6 流量独立于 IPv4 流量;您的路由表必须包含单独的 IPv6 流量路由。 + + + +默认情况下,默认子网为公有子网,因为主路由表会将指定发往 Internet 的子网流量发送到 Internet 网关。 + + + +### NAT + +NAT的全程是“**Network Address Translation**”,中文解释是“**网络地址转换**”,它可以让整个机构只使用一个公有的IP地址出现在Internet上。 + +NAT是一种把内部私有地址(192.168.1.x,10.x.x.x等)转换为Internet公有地址的协议,它一定程度上解决了公网地址不足的问题。 + +- NAT实例需要创建在公有子网内 + + + + + +**堡垒机(Bastion Host)**又叫做跳板机(Jump Box),主要用于运维人员远程登陆服务器的集中管理。运维人员首先登陆到这台堡垒机(公网),然后再通过堡垒机管理位于内网的所有服务器。 + +堡垒机可以对运维人员的操作行为进行控制和审计,同时可以结合Token等技术达到更加安全的效果。 + +## VPC场景 + +**带单个公有子网的 VPC**: 此场景的配置包含一个有单一公有子网的 Virtual Private Cloud (VPC),以及一个 Internet 网关以启用 Internet 通信。如果您要运行单一层级且面向公众的 Web 应用程序,如博客或简单的网站,则我们建议您使用此配置。 + +**带有公有和私有子网的 VPC (NAT)**:这个场景的配置包括一个有公有子网和私有子网的 Virtual Private Cloud (VPC)。如果您希望运行面向公众的 Web 应用程序,并同时保留不可公开访问的后端服务器,我们建议您使用此场景。常用例子是一个多层网站,其 Web 服务器位于公有子网之内,数据库服务器则位于私有子网之内。您可以设置安全性和路由,以使 Web 服务器能够与数据库服务器建立通信。 + +公有子网中的实例可直接将出站流量发往 Internet,而私有子网中的实例不能这样做。但是,私有子网中的实例可使用位于公有子网中的网络地址转换 (NAT) 网关访问 Internet。数据库服务器可以使用 NAT 网关连接到 Internet 进行软件更新,但 Internet 不能建立到数据库服务器的连接。 + +## **Security Groups(安全组)** + +[参考](https://zhuanlan.zhihu.com/p/151419823) + +VPC 网络安全组标志 VPC 中的哪些流量可以发往 EC2 实例或从 EC2 发出。安全组指定具体的入向和出向流量规则,并精确到源地址(入向)和目的地址(出向)。这些安全组是与 EC2 实例而非子网关联的。 +默认情况下,流量只允许出,不允许入。 + + + +Security Group(SG)通过控制IP和端口来控制出站入站规则,可以用于EC2,RDS及下面将要用到的VPC Endpoint。 + + + +## 网络ACL(NACL) + +**网络访问控制列表(NACL)**与安全组(Security Group)类似,它能在子网的层面控制所有入站和出站的流量,为VPC提供更加安全的保障。 + + + +- 在你的**默认VPC**内会有一个默认的网络ACL(NACL),它会**允许**所有入向和出向的流量 +- 你可以创建一个自定义的网络ACL,在创建之初所有的入向和出向的流量都会被**拒绝**,除非进行手动更改 +- 对于所有VPC内的子网,每一个子网都需要关联一个网络ACL。如果没有关联任何网络ACL,那么子网会关联默认的网络ACL +- 一个网络ACL可以关联多个子网,但一个子网只能关联一个网络ACL +- 网络ACL包含了一系列(允许或拒绝)的规则,网络ACL会按顺序执行,一旦匹配就结束,不会再继续往下匹配 +- 网络ACL有入向和出向的规则,每一条规则都可以配置允许或者拒绝 +- 网络ACL是无状态的(安全组是有状态的) + - 被允许的入向流量的响应流量必须被精准的出向规则所允许(反之亦然) + - 一般至少需要允许临时端口(TCP 1024-65535) + - 关于临时端口的知识,可以参见[这里](https://docs.aws.amazon.com/zh_cn/AmazonVPC/latest/UserGuide/VPC_ACLs.html#VPC_ACLs_Ephemeral_Ports) + +## Difference between Internet Gateway and NAT Gateway + + + +参考 + +- Internet Gateway (IGW) allows instances with public IPs to access the internet. +- NAT Gateway (NGW) allows instances with no public IPs to access the internet. + +## 参考 + +[参考](https://juejin.cn/post/6949072638145003556) + +大部分 AWS 服务都需要以 VPC 为基础进行构建,比如最常用的 EC2,ALB,及无服务器服务 ECS Fargate。 vb + +当我们在一个 VPC 中创建 Subnet 时需要给 Subnet 选择一个 AZ(Availability Zone),一个 Subnet 只能选择建在一个 AZ 中。 + + + +**选择region** + +因为国内政策法规原因,AWS 在中国的服务与 AWS Global 服务略有不同。 + +AWS Global 的 Region 之间是通过主干网相连的,AWS 中国区的服务没有通过主干网与 AWS Global 相连,只有中国区内部两个 Region,北京和宁夏是相连接的。 + +在创建 VPC 时并不需要添写 AZ(Availability Zone)信息,VPC 只与 Region 有关。 + + + +Subnet 是最终承载大部分 AWS 服务的组件,比如 EC2, ECS Fargate,RDS。 + +Subnet 分为两种 Private Subnet 和 Public Subnet。 + +简单来说,不能直接访问 internet 的 Subnet 就是 Private Subnet,能直接访问 internet 的就是 Public Subnet。 + + + +Security Group(SG)通过控制IP和端口来控制出站入站规则,可以用于EC2,RDS及下面将要用到的VPC Endpoint。 + + + +VPC Endpoint用来直接连接VPC与AWS相关服务,比如RDS AIP,S3。 + +当系统安全要求比较高时,EC2处于的Subnet可能被限制,无法访问internet,这时EC2就无法访问AWS的一些服务,比如SSM。 + +这时我们可以利用VPC Endpoint把VPC和所需要访问的服务连接起来,然后EC2就可以不经internet访问到所需的服务。 + + + +[参考](https://juejin.cn/post/6954169148318433288) + +RT(Route Table)与Subnet相关连,用来描述网络路由。IGW: Internet gateway IGW是一个独立的组件配置在VPC上,使得VPC可以访问internet + +我们给VPC加了IGW之后,需要修改Subnet相关的路由,确保访问Internet的请求发送到IGW。 + +每个VPC中有一个默认的主RT,自动关联VPC内的每一个Subnet。我们现在为Subnet “ts-public-1”单独创建一个新的RT。 + + + +- 新建的Subnet就是Private Subnet +- 在Private Subnet中配置了到IGW的路由后,就变成Public Subnet +- Public Subnet中的EC2还要再配置一个Public IP或者EIP就可以访问Internet +- 如果EC2可以访问internet,其关联的Security Group入站规则如果允许从internet访问,那么这个EC2就可以从internet中直接访问到。 + + + +1. 实践中我们把应用程序,数据库放在Private Subnet中,阻止从internet访问。把堡垒机和ALB(Application Load balancer)放在Public Subnet,允许从internet访问。 + +2. 一般我们会建两套Public Subnet和Private Subnet,分别放在不同的AZ中,防止其中一个AZ出问题。这时如果配置NAT,也需要在两个Public Subnet中各配置一个NAT。 + + + +[参考](http://www.cloudbin.cn/?tag=aws) 暂无Note + +## 总结 + +VPC里多个AZ, 每个AZ都需要至少一个子网,默认是公有子网。但如果有internet访问不到的实例或者数据库,则需建个私有子网,私有子网默认不能访问internet,internet也不能访问私有子网。 + +要走互联网必须走internet gateway,它对整个vpc生效, public subnet可直接通过IGW与互联网互联,私有子网再通过NAT走公有子网是可以访问internet的,反向不能。 + +和互联网连接时都需要有个公网ip,这个是从amazon分配的。 + + + +## Route 53 + +**Amazon Route 53**是一种高可用、高扩展性的云DNS服务。 + +不同的DNS记录: + +- **CNAME** – CNAME (Canonical Name)可以将一个域名指向另一个域名。比如将aws.xiaopeiqing.com指向xiaopeiqing.com +- Alias记录 – 和CNAME类似,又叫做别名记录,可以将一个域名指向另一个域名。 + - **和CNAME最大的区别是,Alias可以应用在根域(Zone Apex)。即可以为xiaopeiqing.com的根域创建Alias记录,而不能创建CNAME** + - 别名记录可以节省你的时间,因为Route53会自动识别别名记录所指的记录中的更改。例如,假设example.com的一个别名记录指向位于lb1-1234.us-east-2.elb.amazonaws.com上的一个ELB负载均衡器。如果该负载均衡器的IP地址发生更改,Route53将在example.com的DNS应答中自动反映这些更改,而无需对包含example.com的记录的托管区域做出任何更改。 弹性负载均衡器(ELB)没有固定的IPv4地址,在使用ELB的时候永远使用它的DNS名字。很多场景下我们需要绑定DNS记录到ELB的endpoint地址,而不绑定任何IP diff --git a/_posts/Tech/AWS/2022-11-18-AWS Compute.md b/_posts/Tech/AWS/2022-11-18-AWS Compute.md new file mode 100644 index 0000000000..5c602ab6fa --- /dev/null +++ b/_posts/Tech/AWS/2022-11-18-AWS Compute.md @@ -0,0 +1,130 @@ +--- +layout: post +category: AWS +title: AWS Compute +tags: AWS +--- + +## AWS Compute + +## AMI + +AMI: amazon machine image 就是一堆配置,比如什么系统,安装哪些附加软件。可使用AMI启动一个同配置的实例。 类似docker的image + +**Amazon Machine Image (AMI)** 是亚马逊AWS提供的系统镜像,这个AMI包含了如下的信息: + +- 由实例的操作系统、应用程序和应用程序相关的配置组成的模板 +- 一个指定的需要在实例启动时附加到实例的卷的信息(比方说定义了使用8 GB的General Purpose SSD卷) + +下图所示的是AMI的生命周期,你可以创建并注册一个AMI,并且可以使用这个AMI来创建一个EC2实例。同时你也可以将这个AMI复制到同一个AWS区域或者不同的AWS区域。你同样也可以注销这个AMI镜像。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211182358363.png) + +- **AMI是区域化的**,只能使用本区域的AMI来创建实例;但你可以将AMI从一个区域复制到另一个区域 + + + + + +## 弹性伸缩(Auto Scaling) + +**亚马逊弹性伸缩(Auto Scaling)**能**自动地**增加/减少EC2实例的数量,从而让你的应用程序一直能保持可用的状态。 + +你可以预定义Auto Scaling,使其在需求高峰期自动增加EC2实例,而在需求低谷自动减少EC2实例。这样不仅能让你的应用程序一直保持健康的状态,而且也节省了你为EC2实例所付出的费用。 + +Auto Scaling 适用于那些需求稳定的应用程序,同时也适用于在每小时、每天、甚至每周都有需求变化的应用程序。 + +- Auto Scaling能保证你一直拥有一定数量的EC2实例来分担应用程序的负载 +- Auto Scaling能带来更高的容错性、更好的可用性和更高的性价比 +- 你可以控制伸缩的策略来决定在什么时候终止和创建EC2实例,以处理动态变化的需求 +- 默认情况下,Auto Scaling能控制每一个可用区内所运行的实例数量尽量平均 + - 为了达到这个目标,Auto Scaling在需要启动新实例的时候,会选择一个目前拥有运行实例最少的可用区 + +Auto Scaling的构成组件: + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211190006430.png) + +### 启动配置(Launch Configuration) + +- 启动配置是弹性伸缩组用来启动EC2实例的时候所使用的模板 +- 启动配置包含了镜像文件(AMI),实例类型、密钥对、安全组和挂载的存储设备 +- 一个启动配置可以关联多个Auto Scaling组 +- **启动配置一经创建不能被更改,只能删除重建** +- 启动配置中可以使用CloudWatch的基础监控(Basic Monitoring)或者详细监控(Detail Monitoring) +- Auto Scaling automatically creates a launch configuration directly from an EC2 instance. + +### 弹性伸缩组(Auto Scaling Group) + +- 弹性伸缩组(ASG)是弹性伸缩的核心,它包含了多个拥有类似配置/类型的EC2实例,这些实例被逻辑上认为是一样的 +- 弹性伸缩组需要的几个参数: + - **启动配置(Launch Configuration)**:它决定了EC2使用什么模板,模板内容包括了镜像文件(AMI),实例类型、密钥对、安全组和挂载的存储设备 + - **最小和最大的性能**:决定了在弹性伸缩的情况下,EC2实例数量的浮动范围 + - **所需的性能**:决定了这个弹性伸缩组要保持的运作所需要的基本的EC2实例数量;如果没有填写,则默认为其数值等同于最小的性能 + - **可用区和子网**:定义EC2实例启动时候所在的可用区和子网信息 + - **参数和健康检查**:参数定义了何时启动新实例,何时终止旧实例;健康检查决定了实例的健康状态。 +- **如果一个EC2实例的健康状态变成“不健康”,那么ASG会终止这个EC2实例,并且自动启动一个新的EC2实例** +- 弹性伸缩组(ASG)只能在某一个AWS区域内运行,不能跨越多个区域 +- 如果启动配置(Launch Configuration)有更新,那么之后启动的新EC2实例会使用新的启动配置,而旧的EC2实例不受影响 +- 从AWS管理平台你可以直接删除一个弹性伸缩组(ASG);从AWS CLI你只能先将最小的性能和需求的性能两个参数设置为0,才能删除这个弹性伸缩组。 + + + +## ECS + +**Amazon Elastic Container Service (ECS)**是一个有高度扩展性的**容器管理服务**。它可以轻松运行、停止和管理集群上的Docker容器,你可以将容器安装在EC2实例上,或者使用**Fargate**来启动你的服务和任务。 + +Amazon ECS可以在一个区域内的多个可用区中创建高可用的应用程序容器,你可以定义集群中运行的Docker镜像和服务。而且你可以充分利用AWS内部的**Amazon ECR (Elastic Container Registry)**或者外部的Registry(比如Docker Hub或自建的Registry)来存储和提取容器镜像。 + + + +我们可以将标准化的代码、运行环境、系统工具等等打包成一个标准的集装箱,这个集装箱叫做**Docker镜像**(Docker Image)。这个Docker镜像的概念类似于EC2中的AMI (Amazon Machine Image)。 + +这些镜像文件通常会通过Dockerfile来构建,并且最终存放到**注册表(Registry)**内。这个Registry可以理解为摆放集装箱的码头,我们在需要某个类型的集装箱的时候就到码头去取。这类Registry可以是Amazon的ECR,也可以是公网上的Docker Hub,或者自己私有的Registry。 + + + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211192204245.png) + +### ECS创建举例 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211192208630.png) + + + +## **Lambda** + +使用**AWS Lambda**,你无需配置和管理任何服务器和应用程序就能运行你的代码。只需要上传代码,Lambda就会处理运行并且根据需要自动进行横向扩展。因此Lambda也被称为**无服务(Serverless)**函数。 + +要让AWS Lambda的代码执行,需要设定一些触发器(比如CloudWatch Log,CloudWatch Event,API Gateway等),因此Lambda函数被认为是**事件驱动的(Event-Driven)**。 + +在传统的应用部署过程中,我们往往需要安装操作系统 -> 安装应用程序 -> 配置环境并部署代码,而且往往还需要不定时地为操作系统和应用程序打补丁和进行维护。使用AWS Lambda就方便很多,只需要上传代码,AWS就会在需要的时候帮你运行。我们不再需要(也无法接触)任何操作系统层面的东西,也节省了非常多的部署时间,可以更专心地编写代码。 + + + +### AWS Lambda的特点 + +- 没有服务器/无服务,或者说真实的服务器由AWS管理 +- 只需要为运行的代码付费,不需要管理服务器和操作系统 +- **持续性/自动的性能伸缩** +- 非常便宜 +- AWS只会在代码运行期间收取相应的费用,代码未运行时不产生任何费用 +- **代码的最长执行时间是15分钟,如果代码执行时间超过15分钟,则需要将1个代码细分为多个** + +### 触发器有哪些 + +- **API Gateway** +- **AWS IoT** +- **CloudWatch Events** 比如cron job定时任务 +- CloudWatch Logs +- CodeCommit +- DynamoDB +- S3 +- SNS +- Cognito Sync Trigger +- SQS应该也可以? + + + +## 参考 + +[参考](http://www.cloudbin.cn/?tag=aws) diff --git a/_posts/Tech/AWS/2022-11-18-AWS MQ.md b/_posts/Tech/AWS/2022-11-18-AWS MQ.md new file mode 100644 index 0000000000..d8e16b7db2 --- /dev/null +++ b/_posts/Tech/AWS/2022-11-18-AWS MQ.md @@ -0,0 +1,85 @@ +--- +layout: post +category: AWS +title: AWS MQ +tags: AWS +--- + +## AWS MQ + +## SQS(Simple Queue Service) + +SQS有两种不同类型的队列,它们分别是: + +- **标准队列**(Standard Queue) +- **FIFO队列**(先进先出队列) + +### 标准队列 + +标准队列拥有**无限的吞吐量**,所有消息都会**至少传递一次**,并且它会尽最大努力进行排序。 + +标准队列是默认的队列类型。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211202300608.png) + +### FIFO队列 + +FIFO (First-in-first-out)队列在不使用批处理的情况下,**最多支持300TPS**(每秒300个发送、接受或删除操作)。 + +在队列中的消息都只会**不多不少地被处理一次**。 + +FIFO队列严格保持消息的**发送和接收顺序**。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211202300750.png) + +更多关于标准队列和FIFO队列的区别,可以查看[我需要哪种类型的队列?](https://docs.aws.amazon.com/zh_cn/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html#sqs-queue-types) + +### SQS的其他特点 + +- SQS是靠应用程序去**拉取的**,而不能主动推送给应用程序,推送服务我们使用**SNS(Simple Notification Service)** +- 消息会以256 KB的大小存放 +- 消息会在队列中保存1分钟~14天,默认时间是4天 +- 可见性超时(Visibility Timeout) + - 即当SQS队列收到新的消息并且被拉取走进行处理时,会触发Visibility Timeout的时间。这个消息不会被删除,而是会被设置为不可见,用来防止该消息在处理的过程中再一次被拉取 + - 当这个消息被处理完成后,这个消息会在SQS中被删除,表示这个任务已经处理完毕 + - 如果这个消息在Visibility Timeout时间结束之后还没有被处理完,则这个消息会设置为可见状态,等待另一个程序来进行处理 + - 因此**同一个消息可能会被处理两次(或以上)** + - 这个超时时间最大可以设置为**12小时** +- 标准SQS队列保证了每一个在队列内的消息都至少会被处理一次 +- 长轮询(Long Polling) + - 默认情况下,Amazon SQS使用**短轮询(Short Polling)**,即应用程序每次去查询SQS队列,SQS都会做回应(哪怕队列一直是空的) + - 使用了长轮训,应用程序每次去查询SQS队列,SQS队列不会马上做回应。而是等到队列里有消息可处理时,或者等到设定的超时时间再做出回应。 + - 长轮询可以一定程度减少SQS的花销 + +## SNS (Simple Notification Service) + +**SNS (Simple Notification Service)** 是一种完全托管的发布/订阅消息收发和移动通知服务,用于协调向订阅终端节点和客户端的消息分发。 + +和**SQS (Simple Queue Service)**一样,SNS也可以轻松分离和扩展微服务,分布式系统和无服务应用程序,对程序进行**解耦**。 + +我们可以使用SNS将消息推送到SQS消息队列中、AWS Lambda函数或者HTTP终端节点上。 + +SNS通知还可以发送推送通知到IOS,安卓,Windows和基于百度的设备,也可以通过电子邮箱或者SMS短信的形式发送到各种不同类型的设备上。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211202301979.png) + +### SNS的一些特点 + +- SNS是实时的**推送服务(Push)**,有别于SQS的**拉取服务(Pull/Poll)** +- 拥有简单的API,可以和其他应用程序兼容 +- 可以通过多种不同的传输协议进行集成 +- 便宜、用多少付费多少的服务模型 +- 在AWS管理控制台上就可以进行简单的操作 + +## SNS vs SQS + +AWS提供了[SQS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sqs/v2/home)和[SNS](https://link.zhihu.com/?target=https%3A//console.aws.amazon.com/sns/v3/home)。SQS是一个分布式的队列消息service,SNS是一个分布式的发布-订阅消息service。具体有人会问这两者有什么区别,这里给出了回答: [What is the difference between Amazon SNS and Amazon SQS?](https://link.zhihu.com/?target=https%3A//stackoverflow.com/questions/13681213/what-is-the-difference-between-amazon-sns-and-amazon-sqs)。 在我的实践中,SQS和SNS会结合起来使用,首先应用发布消息到SQS的queue里面,然后SNS消费这个queue的消息,放到自身的topic里面持久保存,然后其他的应用订阅这个topic,消费里面的消息。 + + + +sqs是一对一,不能一对多,消息可持久化,不推只能等拉,拉完就删除。 + +sns可一对多,不能持久化,push模型。 + +sqs及aws笔记,更全, [link](http://www.cloudbin.cn/?p=2530) + diff --git a/_posts/Tech/AWS/2022-11-18-AWS Storage.md b/_posts/Tech/AWS/2022-11-18-AWS Storage.md new file mode 100644 index 0000000000..ed5683197f --- /dev/null +++ b/_posts/Tech/AWS/2022-11-18-AWS Storage.md @@ -0,0 +1,214 @@ +--- +layout: post +category: AWS +title: AWS Storage +tags: AWS +--- + +## AWS Storage + +## dynamoDB + +[论文讲解](http://systemdesigns.blogspot.com/2016/01/dynamodb.html) + +[深入探讨 Amazon DynamoDB 的设计模 式、流复制和全局表](https://sides-share.s3.cn-north-1.amazonaws.com.cn/AWS+Webinar+2019/PDF/Amazon+DynamoDB+webinar.pdf) + +[官网](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.LowLevelAPI.html#Programming.LowLevelAPI.DataTypeDescriptors) + +[AWS 如何实现数据跨区域同步](https://techsummit.ctrip.com/pdf/songye.pdf) + +[MongoDB 与 DynamoDB 正面交锋](https://www.modb.pro/db/432414) + + + +## EBS + +### EBS的特点 + +- 亚马逊EBS卷提供了**高可用、可靠、持续性的块存储**,EBS可以依附到一个正在运行的EC2实例上 +- 如果你的EC2实例需要使用数据库或者文件系统,那么建议使用EBS作为首选的存储设备 +- EBS卷的存活可以脱离EC2实例的存活状态。也就是说在终止一个实例的时候,你可以选择保留该实例所绑定的EBS卷 +- EBS卷可以依附到**同一个可用区(AZ)**内的任何实例上 +- EBS卷可以被加密,如果进行了加密那么它存有的所有已有数据,传输的数据,以及制造的镜像都会被加密 +- **EBS卷可以通过快照(Snapshot)来进行(增量)备份,这个快照会保存在S3 (Simple Storage System)上** +- 你可以使用任何快照来创建一个基于该快照的EBS卷,并且随时将这个EBS卷应用到**该区域**的任何实例上 +- EBS卷创建的时候已经固定了可用区,并且**只能给该可用区的实例使用**。如果需要在其他可用区使用该EBS,那么可以创建快照,并且使用该快照创建一个在其他可用区的新的EBS卷 +- **快照还可以复制到其他的AWS区域** + +### EBS (Elastic Block Storage)小结 + +- EBS的不同类型,需要了解不同类型的EBS主要的使用场景 + - 通用型SSD – GP2 (高达10,000 IOPS),适用于启动盘,低延迟的应用程序等 + - 预配置型SSD – IO1 (超过10,000 IOPS),适用于IO密集型的数据库 + - 吞吐量优化型HDD -ST1,适用于数据仓库,日志处理 + - HDD Cold – SC1 – 适合较少使用的冷数据 + - HDD, Magnetic +- 不能将EBS挂载到多个EC2实例上,一个EBS只能挂载到1个EC2实例上。 + - 如果有共享数据盘的需求,请使用EFS (Elastic File System) +- 根EBS卷默认是不能进行加密的,但可以使用第三方的加密工具(例如BitLocker)对其进行加密 + - *除了根磁盘外的其他卷是可以加密的* + +### EBS快照(Snapshot)小结 + +- *EBS的快照会被保存到S3(Simple Storage System)上* + +- *你可以对一个EBS卷创建一个快照,这个快照会被保存到S3上* + +- 快照实际上是 + + 增量备份 + + ,只有在上次进行快照之后更改的数据才会被添加的S3上 + + - 因此第一次快照所花费的时间比较长 + - 而第二次以后的快照所花费的时间相对短很多 + +- 对加密的EBS卷创建快照,创建后的快照也会是加密的 + +- 从加密的快照恢复的EBS卷也会是加密的 + +- 你可以分享快照给其他账户或AWS市场,但仅限于这个快照是没有进行过加密的 + +- 要为一个作为根设备的EBS卷创建快照的话,建议停止这个实例再做快照 + +### 实例存储(Instance Store) + +- 实例存储也叫做**短暂性存储(Ephemeral Storage)** +- 实例存储的实例不能被停止(只能重启或终止),如果这个实例出现故障,那么在上面的所有数据将会丢失 +- 使用EBS的实例可以被停止,停止后EBS上的数据不会丢失 +- 重启使用实例存储的实例或者重启使用EBS的实例都不会导致数据丢失 + + + +## AWS EBS, S3和EFS的区别 + +- AWS S3对于静态页面的托管、多媒体分发、版本管理、大数据分析、数据存档来说都非常有用。S3可以和AWS CloudFront结合使用而达到更快的上传和下载速度。 +- AWS EBS是可以用来做数据库或托管应用程序的持续性文件系统,EBS具有很高的IO读写速度并且即插即用。 只能被单个EC2实例访问 +- 相比前面两种存储,AWS EFS是比较新的一项服务。它提供了可以在多个EC2实例之间共享的网络文件系统,功能类似于NAS设备。可以用EFS来处理大数据分析、多媒体处理和内容管理。 + +## S3 + +Amazon **Simple Storage Service (S3)** 是互联网存储解决方案,它提供了一个简单的Web接口,让其存储的数据和文件在互联网的任何地方给任何人访问。 + +文件对象存储。 + + + +### S3基本特性 + +- S3是**对象存储**,可以在S3上存储各种类型的文件,它不是**块存储**(EBS是块存储) +- 文件大小可以从0 字节到5 TB + - 使用Single Operation上传只能上传*最大5 GB*的文件 + - 使用分段上传(Multipart Upload)可以对文件进行分段上传,最大支持上传*5 TB*的文件 +- S3的总存储空间是**无限大**的 +- 文件存储在**存储桶(Bucket)**内,可以理解存储桶就是一个文件夹 +- S3的名字是需要**全球唯一**的,不能与任何区域的任何人拥有的S3重名 +- 存储桶创建之后会生成一个URL,命名类似于https://s3-ap-northeast-1.amazonaws.com/aws_xiaopeiqing_com + - **S3是以HTTPS的形式展现的,而非HTTP** + - ap-northeast-1表示了当前桶所在的区域 + - aws_xiaopeiqing_com表示了S3存储桶的名字,全球唯一 +- S3拥有99.99%(4个9)的可用性(Availability) + - 可用性可以理解为系统的uptime时间,即在一个自然年内(365天)有52.56分钟系统不可用 +- S3拥有99.999999999%(11个9)的持久性(Durability) + - 持久性可以认为是数据完整性/数据安全性,即在一千亿个存储在S3上的文件会有大概 1 个文件是不可读的 +- S3的存储桶创建的时候可以选择所在区域(Region),但不能选择可用区(AZ),AWS会负责S3的高可用、容灾问题 + - S3创建的时候可以选择某个AWS区域,一旦选择了就不能更改 + - 如果要在其他区域使用该S3的内容,可以使用**跨区域复制** +- S3拥有不同的等级(Standard, Stantard-IA, Onezone-IA, RRS, Glacier) +- 启用了**版本控制(Version Control)**你可以恢复S3内的文件到之前的版本 +- S3可以开启生命周期管理,对文件在不同的生命周期进行不同的操作 + - 比如,文件在创建30天后迁移到便宜的S3等级(S3-IA),再经过30天进行归档(迁移到Glacier),再过30天就进行删除 +- 要启用生命周期管理需要先启用版本控制功能 +- S3支持加密功能 +- 使用访问控制列表(Access Control Lists)和桶策略(Bucket Policy)可以控制S3的访问安全 +- 在S3上成功上传了文件,你将会得到一个**HTTP 200**的状态反馈 + +### 不同的S3存储类型 + +- **Standard – 默认的存储类:**如果上传对象时未注明则S3会分配这个类型的存储 +- **Standard – IA(Infrequently Accessed):**用于保存不经常访问的数据,但是需要访问的时候也能很快地访问到。存储的价格比标准S3便宜,但是读取的费用比标准的S3高,也因为如此才要把不经常访问的数据放到这种类型的S3上。并且数据跨了多个AWS地理位置。 +- **Intelligent_Tiering** 智能分层(S3 智能分层): 这种储存类别将对象存储在两个访问层中,一个是频繁访问的层,一个是不频繁访问的层;如果对象`30`天内未访问,则会被移动至不频繁访问的层,如果不频繁访问层中的对象被访问,则会被移动至频繁访问的层;频繁访问的层的存储费用与`STANDARD`一样,不频繁访问层的存储费用与`STANDARD_IA`一样,该储存类别的请求费用与`STANDARD`一样,**该储存类别有额外的监控费用**; +- **Onezone – IA:**同上,但数据只保存到一个AWS可用区内 +- **Glacier:**非常便宜,仅用于做归档。从Glacier读取数据需要花费3-5个小时。 +- **Glacier Deep Archive:** S3 Glacier Deep Archive 是 Amazon S3 成本最低的存储类,支持每年可能访问一两次的数据的长期保留和数字预留。它是为客户设计的 – 特别是那些监管严格的行业,如金融服务、医疗保健和公共部门 – 为了满足监管合规要求,将数据集保留 7-10 年或更长时间。S3 Glacier Deep Archive 还可用于备份和灾难恢复使用案例,是成本效益高、易于管理的磁带系统替代,无论磁带系统是本地库还是非本地服务都是如此。S3 Glacier Deep Archive 是 Amazon S3 Glacier 的补充,后者适合存档,其中会定期检索数据并且每隔几分钟可能需要一些数据。存储在 S3 Glacier Deep Archive 中的所有对象都将接受复制并存储在至少三个地理分散的可用区中,受 99.999999999% 的持久性保护,并且可在 12 小时内恢复。 + +## CloudFront CDN + +**Amazon CloudFront**是一种全球**内容分发网络(CDN)**服务,可以安全地以低延迟和高传输速度向浏览者分发数据、视频、应用程序和API。 + + + +- **边缘站点(Edge Location)**:边缘站点是内容缓存的地方,它存在于多个网络服务提供商的机房,它和AWS区域和可用区是完全不一样的概念。截至2018年中,AWS目前一共有100多个边缘站点。 +- **源(Origin)**:这是CDN缓存的内容所使用的源,源可以是一个S3存储桶,可以是一个EC2实例,一个弹性负载均衡器(ELB)或Route53,甚至可以是AWS之外的资源。 +- **分配(Distribution)**:AWS CloudFront创建后的名字 +- 分配分为两种类型,分别是 + - **Web Distribution**:一般的网站应用 + - **RTMP (Real-Time Messaging Protocol)**:媒体流 +- 你不只是可以从边缘站点读取数据,你还可以往边缘站点写入数据(比如上传一个文件),边缘站点会将你写入的数据同步到源上 +- 在CloudFront上的文件会被缓存在边缘节点,缓存的时间是**TTL(Time To Live)**。文件存在超过这个时间,缓存会被自动清除 +- 如果在到达TTL时间之前,你希望更新文件,那么你也可以**手动清除缓存**,但你将会被AWS**收取一定的费用** + + + +## Multi-AZ高可用 + +我们可以把AWS RDS数据库部署在多个**可用区(AZ)**内,以提供高可用性和故障转移支持。 + +使用Multi-AZ部署模式,RDS会在不同的可用区内配置和维护一个主数据库和一个备用数据库,主数据库的数据会自动复制到备用数据库中。 + +使用这种部署模式,可以为我们提供数据冗余,减少在系统备份期间的I/O冻结(上面有提到)。同时,更重要的是可以防止数据库实例的故障和单个可用区的故障。 + +如下图所示,我们可以在两个可用区内分别部署主数据库和备用数据库。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211192338177.png) + +目前Multi-AZ支持以下数据库: + +- Oracle +- PostgreSQL +- MySQL +- MariaDB +- SQL Server + +值得注意的是,Aurora数据库本身就支持多可用区部署的高可用设置,因此不需要为Aurora数据库特别开启这个功能。 + +在上次实验中我们有讲到,创建了RDS数据库之后我们会得到一个数据库的URL Endpoint。在开启Multi-AZ的情况下,这个URL Endpoints会根据主/备数据库的健康状态自动解析到IP地址。对于应用程序来说,我们只需要连接这个URL地址即可。 + +**高可用的设置只是用来解决灾备的问题,并不能解决读取性能的问题;要提升数据库读取性能,我们需要用到Read Replicas。** + + + +### 只读副本(Read Replicas) + +我们可以在源数据库实例的基础上,复制一种新类型的数据库实例,称之为**只读副本(Read Replicas)**。我们对源数据库的任何更新,都会**异步**更新到只读副本中。 + +因此,我们可以将应用程序的数据库读取功能转移到Read Replicas上,来减轻源数据库的负载。 + +对于有大量读取需求的数据库,我们可以使用这种方式来进行灵活的数据库扩展,同时突破单个数据库实例的性能限制。 + +Read Replicas还有如下的特点: + +- Read Replicas是用来提高读取性能的,不是用来做灾备的 +- 要创建Read Replicas需要源RDS实例开启了自动备份的功能 +- 可以为数据库创建最多**5个**Read Replicas +- 可以为Read Replicas创建Read Replicas(如下图所示) +- 每一个Read Replicas都有自己的URL Endpoint +- 可以为一个启用了Multi-AZ的数据库创建Read Replicas +- Read Replicas可以提升成为独立的数据库 +- 可以创建位于另一个区域(Region)的Read Replicas + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211192340392.png) + +目前Read Replicas支持以下数据库: + +- Aurora +- PostgreSQL +- MySQL +- MariaDB +- Oracle + +https://amazonaws-china.com/cn/rds/details/read-replicas/ + +## 参考 + +[参考](http://www.cloudbin.cn/?p=1968) diff --git a/_posts/Tech/AWS/2022-11-26-AWS DynamoDB.md b/_posts/Tech/AWS/2022-11-26-AWS DynamoDB.md new file mode 100644 index 0000000000..306c83421e --- /dev/null +++ b/_posts/Tech/AWS/2022-11-26-AWS DynamoDB.md @@ -0,0 +1,131 @@ +--- +layout: post +category: AWS +title: AWS DynamoDB +tags: AWS +--- + +## AWS DynamoDB + +> [论文讲解](http://systemdesigns.blogspot.com/2016/01/dynamodb.html) + +Dynamo在某些故障的场景中将牺牲一致性。 + +Dynamo的系统假设和要求: +1)query model:对数据项简单的读,写是通过一个主键唯一性标识。状态存储为一个由唯一性键确定二进制对象。没有横跨多个数据项的操作,也不需要关系方案(relational schema)。这项规定是基于观察相当一部分Amazon的服务可以使用这个简单的查询模型,并不需要任何关系模式。Dynamo的目标应用程序需要存储的对象都比较小(通常小于1MB)。 + +2)ACID属性:ACID是一种保证数据库事务可靠地处理的属性。在数据库方面的,对数据的单一的逻辑操作被称作所谓的交易。Amazon的经验表明,在保证ACID的数据存储提往往有很差的可用性。Dynamo的目标应用程序是高可用性,弱一致性(ACID“中的C”)。Dynamo不提供任何数据隔离(Isolation)保证,只允许单一的关键更新。 + +3)efficiency:系统需运作在一般的commodity hardware上。Amazon平台的服务都有着严格的延时要求, 鉴于对状态的访问在服务操作中起着至关重要的作用,存储系统必须能够满足那些严格的SLA,服务必须能够通过配置Dynamo,使他们不断达到延时和吞吐量的要求。因此,必须在成本效率,可用性和耐用性保证之间做权衡。 + + + +提供get, put操作。 + + + +最终一致性。 + +### Partition + +按key做partition, 一致性Hash。 + +### Replication + +replica, 复制,用了NWR,让用户做一致性的选择。读数据时如果有不同版本,会所有版本数据都返回回去。 + +### Data Versioning + +多版本。Vector Clock 一个Vector Clock可以理解为一个<节点编号,计数器>对的列表。每一个版本的数据都会带上一个Vector Clock。Dynamo中,最重要的是要保证写操作的高可用性,即“Always Writeable”,这样就不可避免的牺牲掉数据的一致性。如上所述,Dynamo中并没有对数据做强一致性要求,而是采用的最终一致性(eventual consistency)。若不保证各个副本的强一致性,则用户在读取数据的时候很可能读到的不是最新的数据。Dynamo中将数据的增加或删除这种操作都视为一种增加操作,即每一次操作的结果都作为一份全新的数据保存,这样也就造成了一份数据会存在多个版本,分布在不同的节点上。这种情况类似于版本管理中的多份副本同时有多人在修改。多数情况下,系统会自动合并这些版本,一旦合并尝试失败,那么冲突的解决就交给应用层来解决。这时系统表现出来的现象就是,一个GET(KEY)操作,返回的不是单一的数据,而是一个多版本的数据列表,由用户决定如何合并。这其中的关键技术就是Vector Clock。 + + + +其实就是读修复。 + +### Failure Detection + +临时性故障,采用Hinted Handoff提示移交机制 + +为防止要写入节点宕机导致操作失败,采用提示移交机制将操作相关数据写入到随机节点,宕机节点恢复后可根据这些数据进行重放,最终获得数据一致性。 + + + + 以N=3为例,如果在一次写操作时发现节点A挂了,那么本应该存在A上的副本就会发送到D上,同时在D中会记录这个副本的元信息(MetaData)。其中有个标示,表明这份数据是本应该存在A上的,一旦节点D之后检测到A从故障中恢复了,D就会将这个本属于A的副本回传给A,之后删除这份数据。Dynamo中称这种技术为“Hinted Handoff”。 + + + +另外为了应对整个机房掉线的故障,Dynamo中应用了一个很巧妙的方案。每次读写都会从”Preference List”列表中取出R或W个节点。那么只要在这个列表生成的时候,让其中的节点是分布于不同机房的,自然数据就写到了不同机房的节点上。 + + + +对于某节点非临时性故障,利用反熵得到丢失数据进行恢复。一些数据存储有后台进程,不断查找副本之间的数据差异,将任何缺少的数据从一个副本复制到另一个副本。和基于主节点复制的复制日志不同,此反熵过程不保证任何特定的顺序复制写入,并且会引入明显的同步滞后 + + + +### 表、索引 + +> [深入探讨 Amazon DynamoDB 的设计模 式、流复制和全局表](https://sides-share.s3.cn-north-1.amazonaws.com.cn/AWS+Webinar+2019/PDF/Amazon+DynamoDB+webinar.pdf) + +![image-20221126172356212](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211261723281.png) + +分区键和排序键共同唯一的标识一条记录 + +本地二级索引 Local Secondary Index (LSI) 单表上的。可以选择与表不同的排序键。同一个分区键。强一致性更新。 + + + +全局二级索引 - Global Secondary Index (GSI) 可以选择与表不同的分区键以及排序键 每个索引分区会对应所有的表分区 + + + +![image-20221126172812498](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211261728526.png) + + + +对比 + +- Global Secondary + - 索引的尺寸没有上限 + - 读写容量和表是独立的 + - 只支持最终一致性 +- Index Local Secondary Index + - 索引保存在表的分区中,因此一个表 分区的尺寸的上限是10GB + - 使用的是表上定义的RCU和WCU + - 强一致性 + + + + +## 其他 + + + +> [官网](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.LowLevelAPI.html#Programming.LowLevelAPI.DataTypeDescriptors) + +begin_with这个操作要记得。其他后面看。 + + + +> [AWS 如何实现数据跨区域同步](https://techsummit.ctrip.com/pdf/songye.pdf) + +没啥子东西,数据操作优先同区域内,主要靠复制。 + + + +> [MongoDB 与 DynamoDB 正面交锋](https://www.modb.pro/db/432414) + +没啥东西 + + + +> [通俗易懂之DynamoDB(一) ----分区键、排序键、GSI](https://zhuanlan.zhihu.com/p/101965292) + +**getItem、query和scan** + +这三个操作都是查询操作,效率分别是:getItem > query > scan + +getItem是根据primary key进行查询,可以理解为通过primary key在hashMap上查询,速度是最快的,缺点是必须知道primary key且只能查询单个,使用情况相对较少。 + +scan是全表扫描,是最慢的一个,理论上能不用就不用,只有实在走投无路才考虑全表扫描。 + +query是最常见的方式,在dynamoDB的使用中,我们唯一的目的就是写出高效的查询query。 diff --git a/_posts/Tech/AWS/2022-12-14-AWS CloudWatch.md b/_posts/Tech/AWS/2022-12-14-AWS CloudWatch.md new file mode 100644 index 0000000000..38746d77c0 --- /dev/null +++ b/_posts/Tech/AWS/2022-12-14-AWS CloudWatch.md @@ -0,0 +1,19 @@ +--- +layout: post +category: AWS +title: AWS CloudWatch +tags: AWS +published: false +--- + +## AWS CloudWatch + +主要是Dashboard, metrics, alarms等。 + +metrics的dimension不能用in操作。只能取1个或者不取。 + + + +## 待更新 + +期待后续... diff --git "a/_posts/Tech/Algorithms/2018-01-18-\345\210\206\346\236\220\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" "b/_posts/Tech/Algorithms/2018-01-18-\345\210\206\346\236\220\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" index 85488e17cd..4025af158b 100644 --- "a/_posts/Tech/Algorithms/2018-01-18-\345\210\206\346\236\220\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" +++ "b/_posts/Tech/Algorithms/2018-01-18-\345\210\206\346\236\220\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" @@ -20,8 +20,6 @@ T(n) = n^klogn if k==log(b)a [递归的时间复杂度分析](https://blog.csdn.net/qq_36582604/article/details/81661236) - - 我们先看下面这个例子 ![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv2/v2/10.png) @@ -30,8 +28,6 @@ T(n) = n^klogn if k==log(b)a ![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv1/v1/20.png) - - [归并排序时间复杂度分析](https://blog.csdn.net/qq_32534441/article/details/95098059) ``` @@ -39,7 +35,7 @@ T(n)=2*T(n/2)+n 第一层n, 第二层2*(n/2), 第三层4*(n/4), 每层都有个n,高度Logn ``` -[合并k个链表的时间复杂度分析](https://blog.csdn.net/qq_22080999/article/details/80669993) +[合并 k 个链表的时间复杂度分析](https://blog.csdn.net/qq_22080999/article/details/80669993) 分治的话: @@ -49,45 +45,42 @@ T(kn) = 2T(k/2*n) + kn 高度log2(k),每层累加都是kn, 因此最终是log2(k) * kn ``` -方法1:暴力,k个链表按顺序合并 +方法 1:暴力,k 个链表按顺序合并 时间复杂度: (n+n)+(2n+n)+...+ ((k-1)n + n) = (1+2+...+k-1)n + (k-1)n = (1+2+...+k-1+k)n -n = (k^2+k-1)/2*n = O(k^2*n) -方法2:将k*n个结点放到vector,再将vector排序,再将结点顺序相连 +方法 2:将 k\*n 个结点放到 vector,再将 vector 排序,再将结点顺序相连 -设有K个链表,平均每个链表有n个结点,时间复杂度: +设有 K 个链表,平均每个链表有 n 个结点,时间复杂度: kN*logkN +kN = O(kN*logkN) -方法3:对k个链表进行分治,两两进行合并 +方法 3:对 k 个链表进行分治,两两进行合并 -设有k个链表,平均每个链表有n个结点,时间复杂度: +设有 k 个链表,平均每个链表有 n 个结点,时间复杂度: -第一轮:进行k/2次,每次处理2n个数字 +第一轮:进行 k/2 次,每次处理 2n 个数字 -第2轮,进行k/4次,每次处理4n个数字 +第 2 轮,进行 k/4 次,每次处理 4n 个数字 ... -最后一次,进行k/(2^logk)次,每次处理2^logk*n个数 - -2n*k/2+...+2^logk*n * k/(2^logk) - +最后一次,进行 k/(2^logk)次,每次处理 2^logk\*n 个数 +2n*k/2+...+2^logk*n \* k/(2^logk) ## 常见算法时间复杂度 时间复杂度总结: -prim o(e+vlogv), 主要是优先队列top v次,每次log(v), 然后边遍历总计2e. +prim o(e+vlogv), 主要是优先队列 top v 次,每次 log(v), 然后边遍历总计 2e. kruskal o(eloge), 主要是边的排序耗时上。 -dijsktra o(e+vlogv), 边总计e, 优先队列取了v次,每次top log(v), 这里指的斐波那契堆,该堆insert 为o(1),其他和二叉堆一致。 - -floyd wallshal, o(n3) +dijsktra o(e+vlogv), 边总计 e, 优先队列取了 v 次,每次 top log(v), 这里指的斐波那契堆,该堆 insert 为 o(1),其他和二叉堆一致。 -bellman-ford, o(ve), 每个边松弛一次,共循环v次,o(ve); +floyd wallshal, o(n3) +bellman-ford, o(ve), 每个边松弛一次,共循环 v 次,o(ve); diff --git "a/_posts/Tech/Algorithms/2018-03-13-\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227\345\255\220\344\270\262.md" "b/_posts/Tech/Algorithms/2018-03-13-\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227\345\255\220\344\270\262.md" index 4913182da9..f941661942 100644 --- "a/_posts/Tech/Algorithms/2018-03-13-\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227\345\255\220\344\270\262.md" +++ "b/_posts/Tech/Algorithms/2018-03-13-\345\212\250\346\200\201\350\247\204\345\210\222\344\271\213\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227\345\255\220\344\270\262.md" @@ -1,7 +1,7 @@ --- layout: post category: Algorithms -title: 动态规划之公共子序列子串 +title: 动态规划之公共子序列/子串/前缀 tags: Algorithms --- @@ -183,3 +183,23 @@ public class LCString { } ``` + +## 最长前缀LCP Longest common prefix + +求两个字符串任意两个位置开头的最长公共前缀。时间复杂度o(n2) 如果是暴力需要o(n3) 枚举i,j然后到头。 + + + +```scala +class Strings: + def LongestCommonPrefix(a, b): + # lcp[i][j] 表示 s[i:] 和 s[j:] 的最长公共前缀 + n, m = len(a), len(b) + lcp = [[0] * (m + 1) for _ in range(n + 1)] + for i in range(n - 1, -1, -1): + for j in range(m - 1, -1, -1): + if a[i] == b[j]: + lcp[i][j] = lcp[i + 1][j + 1] + 1 + return lcp +``` + diff --git "a/_posts/Tech/Algorithms/2018-08-01-\351\230\237\345\210\227\345\222\214\346\240\210.md" "b/_posts/Tech/Algorithms/2018-08-01-\351\230\237\345\210\227\345\222\214\346\240\210.md" index af863894ef..ef2859e924 100644 --- "a/_posts/Tech/Algorithms/2018-08-01-\351\230\237\345\210\227\345\222\214\346\240\210.md" +++ "b/_posts/Tech/Algorithms/2018-08-01-\351\230\237\345\210\227\345\222\214\346\240\210.md" @@ -165,4 +165,13 @@ class Solution: -类似题目: [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) \ No newline at end of file +类似题目: [394. 字符串解码](https://leetcode-cn.com/problems/decode-string/) + + + + + +给定入栈顺序,求某个出栈顺序。 + +1. [6202. 使用机器人打印字典序最小的字符串](https://mafulong.github.io/2022/10/09/6202.-%E4%BD%BF%E7%94%A8%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%89%93%E5%8D%B0%E5%AD%97%E5%85%B8%E5%BA%8F%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2/) +2. [栈的压入、弹出序列](https://mafulong.github.io/2018/10/20/%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5-%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97/) \ No newline at end of file diff --git "a/_posts/Tech/algorithms/2018-08-13-\346\254\247\346\213\211\345\233\236\350\267\257.md" "b/_posts/Tech/Algorithms/2018-08-13-\346\254\247\346\213\211\350\267\257\345\276\204.md" similarity index 87% rename from "_posts/Tech/algorithms/2018-08-13-\346\254\247\346\213\211\345\233\236\350\267\257.md" rename to "_posts/Tech/Algorithms/2018-08-13-\346\254\247\346\213\211\350\267\257\345\276\204.md" index 6b221feac6..3e19382e31 100644 --- "a/_posts/Tech/algorithms/2018-08-13-\346\254\247\346\213\211\345\233\236\350\267\257.md" +++ "b/_posts/Tech/Algorithms/2018-08-13-\346\254\247\346\213\211\350\267\257\345\276\204.md" @@ -1,14 +1,16 @@ --- layout: post category: Algorithms -title: 欧拉回路 +title: 欧拉路径 tags: Algorithms --- ## 欧拉回路定义及判断 -如果能从图的某一顶点出发,每条边恰好经过一次,这样的路线称为欧拉道路(Eulerian Path)。 +如果能从图的某一顶点出发,每条边恰好经过一次,这样的路线称为**欧拉道路(Eulerian Path)。也叫欧拉路径** + +如果每条边恰好经过一次,且能回到起点,这样的路线称为**欧拉回路**(Eulerian Circuit)。 + -如果每条边恰好经过一次,且能回到起点,这样的路线称为欧拉回路(Eulerian Circuit)。 对于无向图G,当且仅当G 是连通的,且最多有两个奇点,则存在欧拉道路。 @@ -22,7 +24,7 @@ tags: Algorithms - 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。 - 通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。 -- 具有欧拉回路的无向图称为欧拉图。 +- **具有欧拉回路的无向图称为欧拉图。** - 具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。 @@ -41,6 +43,10 @@ tags: Algorithms 给定一个 *n* 个点 *m* 条边的图,要求从指定的顶点出发,经过所有的边恰好一次(可以理解为给定起点的「一笔画」问题)其实就是求欧拉路径。 +如果在有欧拉回路中的图,它就求的是欧拉回路。如果非欧拉回路的图就是欧拉路径。 + + + Hierholzer 算法用于在连通图中寻找欧拉路径,其流程如下:[参考](https://leetcode-cn.com/problems/reconstruct-itinerary/solution/zhong-xin-an-pai-xing-cheng-by-leetcode-solution/) @@ -70,11 +76,17 @@ def dfs(u): stack = stack[::-1] ``` +以上是求点的路径。 有向图还是无向图都没关系,无向图就变两个有向的边就行。 + + + +如果是求边路径,pop后进入stack即可。 + ## 相关题目 -- [332. 重新安排行程](https://leetcode-cn.com/problems/reconstruct-itinerary/) -- [753. 破解保险箱](https://leetcode-cn.com/problems/cracking-the-safe/) 求锁所有密码,就是抽象出多个节点,然后求欧拉回路. +- [332. 重新安排行程](https://leetcode-cn.com/problems/reconstruct-itinerary/) 求欧拉路径 点的顺序 +- [753. 破解保险箱](https://leetcode-cn.com/problems/cracking-the-safe/) 求锁所有密码,就是抽象出多个节点,然后求欧拉路径. - [5932. 合法重新排列数对](https://leetcode-cn.com/problems/valid-arrangement-of-pairs/) diff --git "a/_posts/Tech/Algorithms/2021-01-25-\345\215\232\345\274\210\350\256\272.md" "b/_posts/Tech/Algorithms/2021-01-25-\345\215\232\345\274\210\350\256\272.md" index 2273a453fe..b86928dd25 100644 --- "a/_posts/Tech/Algorithms/2021-01-25-\345\215\232\345\274\210\350\256\272.md" +++ "b/_posts/Tech/Algorithms/2021-01-25-\345\215\232\345\274\210\350\256\272.md" @@ -3,6 +3,7 @@ layout: post category: Algorithms title: 博弈论 tags: Algorithms +recent_update: true --- ## 博弈论 diff --git "a/_posts/Tech/Algorithms/2021-03-11-\344\270\221\346\225\260.md" "b/_posts/Tech/Algorithms/2021-03-11-\344\270\221\346\225\260.md" deleted file mode 100644 index b28798fa3a..0000000000 --- "a/_posts/Tech/Algorithms/2021-03-11-\344\270\221\346\225\260.md" +++ /dev/null @@ -1,57 +0,0 @@ ---- -layout: post -category: Algorithms -title: 丑数 -tags: Algorithms ---- - -## 丑数 - -[[剑指 Offer 49. 丑数](https://leetcode-cn.com/problems/chou-shu-lcof/) - -[313. 超级丑数](https://leetcode-cn.com/problems/super-ugly-number/) - -### 使用优先队列 - -```python -class Solution: - def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int: - import heapq - hq = [1] - res = [] - for _ in range(n): - top = heapq.heappop(hq) - res.append(top) - while hq and hq[0] == top: - heapq.heappop(hq) - for k in primes: - heapq.heappush(hq, top * k) - print(res) - return res[-1] -``` - - - -### 使用多指针 - -```c++ -//cpp: -class Solution { -public://别人的代码就是精简,惭愧啊,继续学习。 - int GetUglyNumber_Solution(int index) { - if (index < 7)return index; - vector res(index); - res[0] = 1; - int t2 = 0, t3 = 0, t5 = 0, i; - for (i = 1; i < index; ++i) - { - res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5)); - if (res[i] == res[t2] * 2)t2++; - if (res[i] == res[t3] * 3)t3++; - if (res[i] == res[t5] * 5)t5++; - } - return res[index - 1]; - } -}; -``` - diff --git "a/_posts/Tech/Algorithms/2021-03-11-\345\244\232\350\267\257\345\275\222\345\271\266 \344\270\221\346\225\260.md" "b/_posts/Tech/Algorithms/2021-03-11-\345\244\232\350\267\257\345\275\222\345\271\266 \344\270\221\346\225\260.md" new file mode 100644 index 0000000000..bbf460d84d --- /dev/null +++ "b/_posts/Tech/Algorithms/2021-03-11-\345\244\232\350\267\257\345\275\222\345\271\266 \344\270\221\346\225\260.md" @@ -0,0 +1,124 @@ +--- +layout: post +category: Algorithms +title: 多路归并 丑数 +tags: Algorithms +--- + +[找第k小及变种,个人另一篇博客,内容有重合](https://mafulong.github.io/2022/01/03/%E6%89%BE%E7%AC%ACk%E5%B0%8F%E5%8F%8A%E5%8F%98%E7%A7%8D/) + + + +## 丑数 + +[[剑指 Offer 49. 丑数](https://leetcode-cn.com/problems/chou-shu-lcof/) + + + +### 使用优先队列 + +```python +class Solution: + def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int: + import heapq + hq = [1] + res = [] + for _ in range(n): + top = heapq.heappop(hq) + res.append(top) + while hq and hq[0] == top: + heapq.heappop(hq) + for k in primes: + heapq.heappush(hq, top * k) + print(res) + return res[-1] +``` + + + +### 使用多指针 + +```c++ +//cpp: +class Solution { +public:/ + int GetUglyNumber_Solution(int index) { + if (index < 7)return index; + vector res(index); + res[0] = 1; + int t2 = 0, t3 = 0, t5 = 0, i; + for (i = 1; i < index; ++i) + { + res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5)); + if (res[i] == res[t2] * 2)t2++; + if (res[i] == res[t3] * 3)t3++; + if (res[i] == res[t5] * 5)t5++; + } + return res[index - 1]; + } +}; +``` + +多路归并。 + +## 超级丑数 + +不只2,3,5 + +![image-20221122214259661](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211222143705.png) + +[313. 超级丑数](https://leetcode-cn.com/problems/super-ugly-number/) + +```python +class Solution: + def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int: + import heapq + hpush, hpop = heapq.heappush,heapq.heappop + h = [] + for i in range(len(primes)): + hpush(h, (primes[i], i, 0)) + ans = [1] + for i in range(1,n): + if not h: + return -1 + val = h[0][0] + ans.append(val) + while h and h[0][0] == val: + _,j,idx = hpop(h) + # print(idx,ans) + hpush(h, (ans[idx]*primes[j], j, idx+1)) + return ans[-1] + + +``` + + + +## 其它多路归并 + +- [373. 查找和最小的K对数字](https://mafulong.github.io/2022/01/03/373.-%E6%9F%A5%E6%89%BE%E5%92%8C%E6%9C%80%E5%B0%8F%E7%9A%84K%E5%AF%B9%E6%95%B0%E5%AD%97/) 给定两个以升序排列的整数数组 `nums1` 和 `nums2` , 以及一个整数 `k` 。定义一对值 `(u,v)`,其中第一个元素来自 `nums1`,第二个元素来自 `nums2` 。请找到和最小的 `k` 个数对 `(u1,v1)`, ` (u2,v2)` ... `(uk,vk)` 。 + +```python +class Solution: + def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: + # equal with 多路链表归并,用heap取最小那个,然后移动指针,也就是超级丑数 + # 性能o(klog(k)) + import heapq + h = [] + for j in range(min(len(nums2),k)): + heapq.heappush(h, (nums1[0]+nums2[j], 0, j)) + res = [] + while k > 0 and h: + _,i,j = heapq.heappop(h) + res.append([nums1[i], nums2[j]]) + if i+1 < len(nums1): + heapq.heappush(h, (nums1[i+1]+nums2[j], i+1,j)) + k-=1 + return res +``` + + + +- [有序矩阵的 Kth Element](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/description/) + - 多路归并 + - 二分法更优。 diff --git "a/_posts/Tech/Algorithms/2021-06-21-\345\215\225\350\260\203\351\230\237\345\210\227\345\222\214\345\215\225\350\260\203\346\240\210.md" "b/_posts/Tech/Algorithms/2021-06-21-\345\215\225\350\260\203\351\230\237\345\210\227\345\222\214\345\215\225\350\260\203\346\240\210.md" index ceae6d6f9c..84eeee3fd0 100644 --- "a/_posts/Tech/Algorithms/2021-06-21-\345\215\225\350\260\203\351\230\237\345\210\227\345\222\214\345\215\225\350\260\203\346\240\210.md" +++ "b/_posts/Tech/Algorithms/2021-06-21-\345\215\225\350\260\203\351\230\237\345\210\227\345\222\214\345\215\225\350\260\203\346\240\210.md" @@ -96,6 +96,11 @@ class Solution: 求左边右边, 最小的大于等于arr[i]的位置,一种方式是sortedList, 一种方式是sorted(range(N), lambda x:arr[x]) +### 最大宽度 + +求最大坡度, 坡度定义: i < j 且 A[i] <= A[j]。这样的坡的宽度为 j - i。找每个元素比它大的最后一个位置 +可以按https://leetcode.cn/submissions/detail/208988818/ 中,按val对range(n)排序,o(nlogn),此时对排序位置i的元素最大坡度就是右边最大j +也可以单调栈o(n)求,先求递减栈,然后倒序枚举,如果比栈顶大,则栈顶元素后续就无用可pop了 ## 单调队列 @@ -146,4 +151,3 @@ class Solution: - [862. 和至少为 K 的最短子数组](https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/) 求前缀和,对前缀和队列递增,求最大差值。 - diff --git "a/_posts/Tech/Algorithms/2021-08-12-\345\233\236\346\226\207.md" "b/_posts/Tech/Algorithms/2021-08-12-\345\233\236\346\226\207.md" index e782af0844..ce69a52ac5 100644 --- "a/_posts/Tech/Algorithms/2021-08-12-\345\233\236\346\226\207.md" +++ "b/_posts/Tech/Algorithms/2021-08-12-\345\233\236\346\226\207.md" @@ -9,7 +9,7 @@ tags: Algorithms -#### Manacher 算法 +#### Manacher 算法 给你一个字符串 `s`,找到 `s` 中最长的回文子串。 @@ -19,8 +19,12 @@ tags: Algorithms 这里边长0表示a,边长1表示aaa -``` - n = len(s) +```python + ''' + 须先变为回文皆奇数长度, 可提前转换: s1 = '#' + '#'.join(list(s)) + '#' + ''' + + n = len(s) arm_len = [0] * n def expand(left, right): @@ -42,6 +46,23 @@ tags: Algorithms right = i + arm_len[i] ``` +时间复杂度o(n). + + + +根据以上得到的arm_len 判断某个子串是不是回文。 + +```python + def is_palindrome(s, i, j, armlen): + mid = (i + j) // 2 # 前半部分或中间 + # 找中心点,然后对应到s1上 + t = 2 * mid + 1 + if (j - i + 1) % 2 == 0: + t += 1 + return armlen[t] >= j - i + 1 + +``` + ## 回文技巧 @@ -49,3 +70,10 @@ tags: Algorithms DP: - 用 P(i,j) 表示字符串 s 的第 i到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串 . 变成区间dp + + + +## 应用 + +- [647. 回文子串](https://leetcode.cn/problems/palindromic-substrings/) 给你一个字符串 `s` ,请你统计并返回这个字符串中 **回文子串** 的数目。1.中心扩展o(n2), 2. manacher o(n) +- [6236. 不重叠回文子字符串的最大数目](https://leetcode.cn/problems/maximum-number-of-non-overlapping-palindrome-substrings/) 实际是dp,判断s(i,j)是不是回文。 diff --git "a/_posts/Tech/Algorithms/2021-09-08-\347\212\266\346\200\201\345\216\213\347\274\251.md" "b/_posts/Tech/Algorithms/2021-09-08-\347\212\266\346\200\201\345\216\213\347\274\251.md" index ea30e809e2..85843010a6 100644 --- "a/_posts/Tech/Algorithms/2021-09-08-\347\212\266\346\200\201\345\216\213\347\274\251.md" +++ "b/_posts/Tech/Algorithms/2021-09-08-\347\212\266\346\200\201\345\216\213\347\274\251.md" @@ -3,6 +3,7 @@ layout: post category: Algorithms title: 状态压缩 tags: Algorithms +recent_update: true --- ## 状态压缩 diff --git a/_posts/Tech/Algorithms/2021-12-05-BFS.md b/_posts/Tech/Algorithms/2021-12-05-BFS.md index d5097a7ac7..ffd0c10075 100644 --- a/_posts/Tech/Algorithms/2021-12-05-BFS.md +++ b/_posts/Tech/Algorithms/2021-12-05-BFS.md @@ -11,29 +11,22 @@ tags: Algorithms - 在append时检查visit标记和设置visit标记 - 只能解决长度为1的问题 -``` -const visited = {} -function bfs() { - let q = new Queue() - q.push(初始状态) - while(q.length) { - let i = q.pop() - if (visited[i]) continue - for (i的可抵达状态j) { - if (j 合法) { - q.push(j) - } - } - } - // 找到所有合法解 -} -``` + + +也可以在queue pop时再check 是否visit和标记visit,时间复杂度加边数。 + + 题目: - [求最少转弯的路径](https://mafulong.github.io/2018/08/27/bfs%E6%9C%80%E5%B0%8F%E8%BD%AC%E5%BC%AF%E8%B7%AF%E5%BE%84/) 每次转弯时加1 - [126. 单词接龙 II](https://leetcode.cn/problems/word-ladder-ii/) 输入所有bfs路径,层次遍历 + dfs回溯pre. +## 从外向内扩展 + +- [407. 接雨水 II](https://leetcode.cn/problems/trapping-rain-water-ii/) 从外向内dijkstra。 +- [417. 太平洋大西洋水流问题](https://leetcode.cn/problems/pacific-atlantic-water-flow/) 从外向内bfs/dfs。 + ## 双向BFS > [双向bfs模板参考](https://leetcode-cn.com/problems/open-the-lock/solution/gong-shui-san-xie-yi-ti-shuang-jie-shuan-wyr9/) diff --git "a/_posts/Tech/Algorithms/2021-12-23-\344\270\200\350\207\264\346\200\247hash.md" "b/_posts/Tech/Algorithms/2021-12-23-\344\270\200\350\207\264\346\200\247hash.md" index 8b0b8fa32a..cf90d17eaa 100644 --- "a/_posts/Tech/Algorithms/2021-12-23-\344\270\200\350\207\264\346\200\247hash.md" +++ "b/_posts/Tech/Algorithms/2021-12-23-\344\270\200\350\207\264\346\200\247hash.md" @@ -5,6 +5,8 @@ title: 一致性hash tags: Algorithms --- +# 一致性hash + ## 一致性hash 1. 首先,我们将hash算法的值域映射成一个具有232 次方个桶的空间中,即0~(232)-1的数字空间。现在我们可以将这些数字头尾相连,组合成一个闭合的环形。 @@ -104,4 +106,44 @@ class HashSeverMgr(object): ## 参考 - [一致性 hash 原理及实现(python 版)](https://xie.infoq.cn/article/e7182d18df48bc26eeb30b207) -- [图解一致性hash算法和实现](https://aijishu.com/a/1060000000007241) \ No newline at end of file +- [图解一致性hash算法和实现](https://aijishu.com/a/1060000000007241) + +## 补充 + +增加节点/删除节点都需要先执行挪数据这个操作,如果没这个操作,可能会读空,但读空范围因为一致性hash的存在,导致影响有限,对于缓存应该是没关系的,如果是持久化的就需要备份,不能让读空。 + + + +# hash slot + +集群: +是一个提供多个Redis(分布式)节点间共享数据的程序集。 +集群部署 +Redis 集群的键空间被分割为 16384 hash个槽(slot), 集群的最大节点数量也是 16384 个 + +分片: + Redis Cluster在设计中没有使用一致性哈希(Consistency Hashing),而是使用数据分片引入哈希槽(hash slot)来实现; + +一个 Redis Cluster包含16384(0~16383)即2^14个哈希槽,存储在Redis Cluster中的所有键都会被映射到这些slot中,集群中的每个键都属于这16384个哈希槽中的一个,集群使用公式slot=CRC16(key)/16384来计算key属于哪个槽,其中CRC16(key)语句用于计算key的CRC16 校验和。 + + + +这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务, 所**以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.** + + + +数据迁移 +数据迁移可以理解为slot(槽)和key的迁移,这个功能很重要,极大地方便了集群做线性扩展,以及**实现平滑的扩容或缩容。** + + + + + +和一致性哈希相比 + +1. 它并不是闭合的,key的定位规则是**根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点**,而一致性哈希是根据hash(key)的值来顺时针找第一个hash(ip)的节点,从而确定key存储在哪个节点。 +2. 一致性哈希是创建虚拟节点来实现节点宕机后的数据转移并保证数据的安全性和集群的可用性的, 当节点不可用时,分摊给多个其他节点,因为虚节点的存在。redis cluster是采用master节点有多个slave节点机制来保证数据的完整性的,master节点写入数据,slave节点同步数据。当master节点挂机后,slave节点会通过选举机制选举出一个节点变成master节点,实现高可用。但是这里有一点需要考虑,如果master节点存在热点缓存,某一个时刻某个key的访问急剧增高,这时该mater节点可能操劳过度而死,随后从节点选举为主节点后,同样宕机,一次类推,造成缓存雪崩即热点缓存问题。 + + + +两者都是要挪数据的! \ No newline at end of file diff --git "a/_posts/Tech/Algorithms/2022-05-01-\346\225\260\345\255\246.md" "b/_posts/Tech/Algorithms/2022-05-01-\346\225\260\345\255\246.md" index d776a249c9..c0fdbde4a9 100644 --- "a/_posts/Tech/Algorithms/2022-05-01-\346\225\260\345\255\246.md" +++ "b/_posts/Tech/Algorithms/2022-05-01-\346\225\260\345\255\246.md" @@ -5,8 +5,6 @@ title: 数学 tags: Algorithms --- - - ## 排列组合 > [参考](https://baike.baidu.com/item/%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88/706498) @@ -19,6 +17,52 @@ tags: Algorithms ![image-20211002195906842](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20211002195912.png) +![image-20220904145610986](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209041456830.png) + +另外 C(0,0) = 1, 0 的为 1。 + +组合数性质 + +![image-20220904145924154](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209041459183.png) + +### 大组合数求解边模 + +利用组合数性质. o(n2) + +```python + def comb_mod(self, n, m, mod=10 ** 9 + 7): + ''' + 大组合数计算C(n,m),边求边mod, 利用组合数性质C(n,m) = 1*C(n-1,m-1) + C(n-1,m) + ''' + dp = [[0 for _ in range(n+1)] for _ in range(n+1)] + dp[0][0] = 1 + for i in range(1, n+1): + dp[i][0] = 1 + for j in range(1, i+1): + dp[i][j] = (dp[i-1][j-1] + dp[i-1][j]) % mod + return dp[n][m] + +``` + +组合数求和公式 + +![image-20220904150115865](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209041501896.png) + + + +### python中排列组合 + +```python + # 组合 + math.comb # n! / (k! * (n - k)!) + math.perm # n! / (n - k)! + +``` + + + + + ## 等差等比数列 @@ -29,20 +73,18 @@ tags: Algorithms image-20210801124109093 - - ## 复杂的复杂度计算 ### 幂函数和对数函数和指数函数对比 - - ![image-20220223194514512](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220223194514.png) ### 调和级数 ![image-20220223193553568](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220223193558.png) +这里有用到: [link](https://leetcode.cn/problems/number-of-different-subsequences-gcds/solution/ji-bai-100mei-ju-gcdxun-huan-you-hua-pyt-get7/) + ### 二项式定理 ![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20211002200037.png) @@ -53,16 +95,20 @@ tags: Algorithms ## 中位数 -在一步操作中,你可以使数组中的一个元素加 `1` 或者减 `1` ,所有数都靠近**中位数**可使所有数组元素相等时移动数最少。 相关题目: [最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/) +在一步操作中,你可以使数组中的一个元素加 `1` 或者减 `1` ,所有数都靠近**中位数**可使所有数组元素相等时移动数最少。 相关题目: [最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/) +即中位数到所有数距离和最小,如果是偶数,可以在中位数两侧的数据构成的区间内任意取值,对结果无影响证明: +![image-20221023121438346](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210231214395.png) + +另一个相似题目:有cost,需转化拆分,然后再算中位数,[6216. 使数组相等的最小开销](https://mafulong.github.io/2022/10/23/6216.-%E4%BD%BF%E6%95%B0%E7%BB%84%E7%9B%B8%E7%AD%89%E7%9A%84%E6%9C%80%E5%B0%8F%E5%BC%80%E9%94%80/) ## 素数 -### 判断是否素数和求1-n的素数求某数的素数 +### 判断是否素数 o(sqrt(N)) ```python -# 判断某数是否是素数 +# 判断某数是否是素数 o(sqrt(n)) def is_prime(a): if a <= 1: return False import math @@ -71,7 +117,16 @@ def is_prime(a): return True -# 求1-n每个数的素数,以下时间复杂度O(n) 朴素筛法 +``` + +### 求 1-n 的所有素数 筛法 o(N) + +#### 埃氏筛法 + +```python +# 求1-n每个数的素数,以下时间复杂度O(nloglogn) 接近o(n) +# 如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。 +# Eratosthenes 筛法(埃拉托斯特尼筛法,简称埃氏筛法) def get_all_prime(n): a = [False] * n res = [] @@ -82,21 +137,31 @@ def get_all_prime(n): for j in range(2 * i, n, i): a[j] = True return res +``` + +#### **线性筛法** 也称为 **Euler 筛法**(欧拉筛法) + +埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。 -# 求某数的质因数列表,比如8,是[(2,3)], 6是[(2,1),(3,1)] -def calcu(a): - counter = collections.Counter() - prime = get_all_prime(a + 1) - for p in prime: - while a % p == 0: - counter[p] += 1 - a /= p - return counter.items() +如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 o(n)了。 + +```python +def get_all_prime(n): + a = [False] * n + res = [] + for i in range(2, n): + if a[i]: continue + # a[i]是素数 + res.append(i) + for j in range(2 * i, n, i): + if a[j]: break # 多了个这行 + a[j] = True + return res ``` ## 平方数 -[先看Cyc2018](http://www.cyc2018.xyz/%E7%AE%97%E6%B3%95/Leetcode%20%E9%A2%98%E8%A7%A3/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%95%B0%E5%AD%A6.html#_1-%E5%B9%B3%E6%96%B9%E6%95%B0) +[先看 Cyc2018](http://www.cyc2018.xyz/%E7%AE%97%E6%B3%95/Leetcode%20%E9%A2%98%E8%A7%A3/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%95%B0%E5%AD%A6.html#_1-%E5%B9%B3%E6%96%B9%E6%95%B0) ### 生成所有平方数 @@ -106,8 +171,6 @@ def calcu(a): 间隔为等差数列,使用这个特性可以得到从 1 开始的平方序列。 - - ### 3 的 n 次方 [Power of Three (Easy)](https://leetcode-cn.com/problems/power-of-three/description/) @@ -118,33 +181,96 @@ public boolean isPowerOfThree(int n) { } ``` -## 因数 +## 除法 + +```python + # b % a == 0 + # 表示b能被a整除,a可以整除b, 被除数永远都是有这个『被』 +``` + +表示b能被a整除,a可以整除b, 被除数永远都是有这个『被』 + + + +大整数除法,除法中取模。 + + + +## 因数理论 ### 素数分解 -每一个数都可以分解成素数的乘积,例如 84 = 22 * 31 * 50 * 71 * 110 * 130 * 170 * … +每一个数都可以分解成素数的乘积,例如 84 = 22 _ 31 _ 50 _ 71 _ 110 _ 130 _ 170 \* … ### 整除 -令 x = 2m0 * 3m1 * 5m2 * 7m3 * 11m4 * … +令 x = 2m0 _ 3m1 _ 5m2 _ 7m3 _ 11m4 \* … -令 y = 2n0 * 3n1 * 5n2 * 7n3 * 11n4 * … +令 y = 2n0 _ 3n1 _ 5n2 _ 7n3 _ 11n4 \* … 如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。 -### 最大公约数最小公倍数 +### 最大公约数最小公倍数的素数表示 + +每个质因数的乘积 + +x 和 y 的最大公约数为:gcd(x,y) = 2min(m0,n0) _ 3min(m1,n1) _ 5min(m2,n2) \* ... + +x 和 y 的最小公倍数为:lcm(x,y) = 2max(m0,n0) _ 3max(m1,n1) _ 5max(m2,n2) \* ... + +### 约数个数和约数之和 + +如果 N = p1^c1 _ p2^c2 _ ... _pk^ck +约数个数: (c1 + 1) _ (c2 + 1) _ ... _ (ck + 1) +约数之和: (p1^0 + p1^1 + ... + p1^c1) _ ... _ (pk^0 + pk^1 + ... + pk^ck) + + -x 和 y 的最大公约数为:gcd(x,y) = 2min(m0,n0) * 3min(m1,n1) * 5min(m2,n2) * ... +## 因数相关问题 -x 和 y 的最小公倍数为:lcm(x,y) = 2max(m0,n0) * 3max(m1,n1) * 5max(m2,n2) * ... +### 试除法求所有约数: -### 求质因数和对应计数 +#### 求一个数的因子列表 o(sqrt(n)) -o(n)近似 +o(sqrt(n)) + +```python +divisors = [] +d = 1 +while d * d <= k: # 预处理 k 的所有因子 + if k % d == 0: + divisors.append(d) + if d * d < k: # 避免 d= k/d的情况,此时如果append会重复 + divisors.append(k / d) + d += 1 +``` + +#### 统计 1-n 每个数的因子列表 o(nlogn) + +o(nlogn) + +```python +MX = 100001 +divisors = [[] for _ in range(MX)] # 每个value就是key的因子列表,乘积肯定>value,因为因子之间可能有倍数 +for i in range(1, MX): # 预处理每个数的所有因子,时间复杂度 O(MlogM),M=1e5 + for j in range(i, MX, i): + divisors[j].append(i) +``` + +### 分解质因数 + +#### 求某数质因数列表 o(sqrt(N)) + +o(sqrt(N)) + +求某数的质因数列表,比如 8,是[2,2,2] + +枚举[2, sqrt(n)+1), 如果是质因数,就接着除,最大大于 1,它本身就是质数。 + +也叫 求欧拉函数 ```python # 求质因数列表 -# Python Version def breakdown(N): result = [] from math import sqrt @@ -158,54 +284,175 @@ def breakdown(N): return result ``` -### 统计1-n每个数的因子列表 +#### 统计 1-n 每个数的质因数列表 -o(nlogn) +筛法求欧拉函数 + +类似,暂时不写。 + +### gcd 求最大公约数和最小公倍数 欧几里得算法 + +欧几里得算法 ```python -MX = 100001 -divisors = [[] for _ in range(MX)] # 每个value就是key的因子列表,乘积肯定>value,因为因子之间可能有倍数 -for i in range(1, MX): # 预处理每个数的所有因子,时间复杂度 O(MlogM),M=1e5 - for j in range(i, MX, i): - divisors[j].append(i) +class Math1: + def gcd(self, a, b): + if b == 0: + return a + return self.gcd(b, a % b) ``` -### 求一个数的因子列表 +最小公倍数就是 a\*b/gcd(a,b) -o(sqrt(n)) + + +### 多个数求最大公约数和最小公倍数 + +多个数的最大公约数: ```python -divisors = [] -d = 1 -while d * d <= k: # 预处理 k 的所有因子 - if k % d == 0: - divisors.append(d) - if d * d < k: # 避免 d= k/d的情况,此时如果append会重复 - divisors.append(k / d) - d += 1 +def gcd_list(self, nums): + import math + cur = nums[0] + for i in range(1, len(nums)): + cur = math.gcd(cur, nums[i]) + return cur ``` -### gcd求最大公约数: +多个数的最小公倍数: -```c++ -int gcd(int a, int b) { - return b == 0 ? a : gcd(b, a%b); -} +> 注意这里不是直接多个数乘积除以他们的gcd呀,而且不断一个一个加计算的。 -int gcd(int a, int b) { - while (b != 0) { - int t = a%b; - a = b; - b = t; - } - return a; -} +```python +def lcm_list(self, nums): + import math + prod = 1 + for v in nums: + prod = prod * v / math.gcd(prod, v) + return prod +``` + +### 1到n里有多少个数可整除a? + +```python +n//a +``` + + + +### 1到n里有多少个a的因数? + +[参考另一个博客](https://mafulong.github.io/2018/04/30/%E8%AE%A1%E7%AE%97n%E7%9A%84%E9%98%B6%E4%B9%98%E4%B8%AD%E6%9C%89%E5%A4%9A%E5%B0%91%E4%B8%AAk/) + +```python + def calcu(x,a): + ''' + 1-x中有多少个因数a, 比如1-26里有5,10, 15, 20, 各自1个5, 5*5两个5,一共6个 + ''' + r = 0 + while x: + # 贡献5的数量,贡献5*5的数量,贡献5*5*5的数量 + r += x //a + x //= a + return r + print(calcu(26, 5)) +``` + + + +## 大整数取模问题 + +### 大数相乘取模 + +```python + def prod(d=[], mod=10 ** 9 + 7): + r = d[0] % mod + for v in d[1:]: + r *= v + r %= mod + return r +``` + + + +### 大数相除取模 费马小定理 + +如果a/b,然后a和b都是大数要取模,这时不能相乘取模来计算 + +`(a/b)%c=(a%c)/(b%c)`是**不成立**的 + + + +> [除法取模](https://leetcode.cn/problems/count-anagrams/solution/by-simpleson-crwb/) + +```python + +# 原: i//j%MOD +# 现: i*modInverse(j)%MOD +MOD = int(1e9 + 7) + +class BigIntDivide: + def mod_inverse(sefl, i): + # 调用取模的乘幂运算, pow复杂度是log(exp)即log(MOD) + return pow(i, MOD - 2, MOD) + + def divide_mod(self, a, b): + ''' + 计算(a/b) % MOD, 除法变乘法,前提是b和MOD互为质数 + ''' + # 如果有多个b,比如a/b1/b2, 那就可以递归。a*mod_inverse(b1) % mod * mod_inverse(b2) % mod这样 + return a * self.mod_inverse(b) % MOD + + def divide_mods(self, a, b=[]): + ''' + 计算a /(b1*b2*b3) % MOD等形式,前提是b和MOD互为质数 + ''' + r = a % MOD + for i, v in enumerate(b): + r *= self.mod_inverse(v) + r %= MOD + return r + + +``` + + + +### 大数幂取模 + +```python +r = pow(base=2, exp=3, mod=3) +print(r) +``` + + + +## 阶乘 + +```python +import math +math.factorial(3) ``` -最小公倍数就是a*b/gcd(a,b) + + +## 分配问题 + +### a能否分成满足条件的两份,其中只能分给指定一份 + +[2513. 最小化两个数组中的最大值](https://leetcode.cn/problems/minimize-the-maximum-of-two-arrays/) 给你两个数组 arr1 和 arr2 ,它们一开始都是空的。你需要往它们中添加正整数,使它们满足以下条件: + +arr1 包含 uniqueCnt1 个 互不相同 的正整数,每个整数都 不能 被 divisor1 整除 。 +arr2 包含 uniqueCnt2 个 互不相同 的正整数,每个整数都 不能 被 divisor2 整除 。 +arr1 和 arr2 中的元素 互不相同 。 +给你 divisor1 ,divisor2 ,uniqueCnt1 和 uniqueCnt2 ,请你返回两个数组中 最大元素 的 最小值 。 +参考, [link](https://leetcode.cn/circle/discuss/YeBDQY/view/B8caF0/) + +**Key**: 判断过程中分为三类:保证在范围内有充足的数不是第一个数的倍数;不是第二个数的倍数;不为公倍数的数总数不少于总共要取的数。根据这三个条件即得到结果。 + ## 进制转换 ```c++ @@ -222,11 +469,9 @@ int gcd(int a, int b) { [可以参考](https://blog.csdn.net/u013349653/article/details/51367453) - 十进制小数转二进制数:“乘以2取整,顺序排列”(乘2取整法) - - 例: (0.625)10= (0.101)2 - +十进制小数转二进制数:“乘以 2 取整,顺序排列”(乘 2 取整法) +例: (0.625)10= (0.101)2 image-20220603233141181 @@ -242,7 +487,23 @@ int gcd(int a, int b) { ## 幂 -### 快速幂 +### 快速幂 O(logk) + +求 m^k mod p,时间复杂度 O(logk)。 + +```c++ +int qmi(int m, int k, int p) +{ + int res = 1 % p, t = m; + while (k) + { + if (k&1) res = res * t % p; + t = t * t % p; + k >>= 1; + } + return res; +} +``` ### 求根号 @@ -252,7 +513,7 @@ int gcd(int a, int b) { ### 求根号变种 小数精度 -`while l<=r`不用变,只需要变步长即可。eps可以比期望精度再小一个量级。 +`while l<=r`不用变,只需要变步长即可。eps 可以比期望精度再小一个量级。 ```python @@ -277,8 +538,6 @@ if __name__ == '__main__': print(f(0.04, 1e-10)) ``` +## Acwing 数学 - -## Acwing数学 - -[参考](https://www.acwing.com/file_system/file/content/whole/index/content/3273/) 本文只涉及部分 +[参考](https://www.acwing.com/file_system/file/content/whole/index/content/3273/) 本文只涉及部分 diff --git "a/_posts/Tech/Algorithms/2022-07-31-\347\256\200\345\215\225\345\233\276\347\232\204\346\234\200\345\244\247\347\216\257\346\234\200\351\225\277\351\223\276.md" "b/_posts/Tech/Algorithms/2022-07-31-\347\256\200\345\215\225\345\233\276\347\232\204\346\234\200\345\244\247\347\216\257\346\234\200\351\225\277\351\223\276.md" new file mode 100644 index 0000000000..60c620721d --- /dev/null +++ "b/_posts/Tech/Algorithms/2022-07-31-\347\256\200\345\215\225\345\233\276\347\232\204\346\234\200\345\244\247\347\216\257\346\234\200\351\225\277\351\223\276.md" @@ -0,0 +1,127 @@ +--- +layout: post +category: Algorithms +title: 简单图的最大环最长链 +tags: Algorithms +--- + +## 最大环最长链 + +https://www.cnblogs.com/lfri/p/15758120.html + +最大环是针对的是出度为1的图,否则会多环重合。 + +最长链是无环的图,求一个最长路径的长度。 + + + +## 最大环 + +### 有向图 + +有多种方法: + +- 一种是先用拓扑排序将外链去掉,再dfs每一个环 +- DFS: 另一种是从某一点出发,记录途径的点,如果遇到已经访问过的点,说明找到了环的入口。减去起始点到入口的距离,就是环的长度。 +- 还有一种有并查集,对于`x->y`,如果`x`和`y`同属于一个集合,说明形成了一个环。 + + + +dfs代码 + +```python +class Solution: + def longestCycle(self, edges: List[int]) -> int: + n = len(edges) + vis = collections.defaultdict(bool) + ans = -1 + for i in range(n): + if vis[i]: continue + path = [] + cur = i + while not vis[cur]: + vis[cur] = True + path.append(cur) + cur = edges[cur] + if cur == -1: break + if cur == -1: continue + for j in range(len(path)): + if path[j] == cur: + t = len(path) - j + ans = max(ans, t) + break + return ans +``` + +### 无向图 + +和有向图类似,略 + +## 最长链 + +等价问题: [310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) + +### 有向图 + +这里有一个很重要的问题,有环怎么办? +有环的情况下,求最长链是没有意义的。要么保证无环,要么是求连接到环上的链的长度。 +例如求连接到环上的链的长度,需要从入度为0的节点开始,递推计算,于是采用拓扑序。 + + + +```c++ +int TopologicalSort(vector& favorite) { + int n = favorite.size(); + vector vis(n, false); + vectorin(n, 0); + vectordp(n, 1); + queue q; + for(int i = 0;i < n;i++) in[favorite[i]]++; + for(int i = 0;i < n;i++) { + if(in[i] == 0) q.push(i); + } + while(!q.empty()) { + int cur = q.front(); + q.pop(); + // cout << cur << " "; + dp[favorite[cur]] = max(dp[favorite[cur]], dp[cur] + 1); + if(--in[favorite[cur]] == 0) q.push(favorite[cur]); + } + // dp[i] 表示到达i的最长链的长度 + int two_point_sum = 0; // 题目相关部分 + for(int i = 0;i < n;i++) { + if(i == favorite[favorite[i]]) two_point_sum += dp[i]; + } + return two_point_sum; +} +``` + +### 无向无环图 + +- 也可以和有向图一样,拓扑序+dp +- 还有一种有趣的方法,两次dfs。可以证明,从任一点出发,dfs能走到的最远点一定是"直径"的一个端点,然后从这个端点出发,dfs得到另一个端点。 参考下面【路径最长的两个叶子节点】 + +例如[Leetcode310最小树高度](https://leetcode.cn/problems/minimum-height-trees/solution/zui-xiao-gao-du-shu-by-leetcode-solution-6v6f/),等价于求树的直径 +第一次dfs找到一个端点,再从这个端点出发dfs找到另一个端点,最后在写个dfs得到路径 + + + +### DFS/BFS 求最长链 + +> [参考](https://leetcode.cn/problems/minimum-height-trees/solution/zui-xiao-gao-du-shu-by-leetcode-solution-6v6f/) + +可以利用以下算法找到图中距离最远的两个节点与它们之间的路径: + +以任意节点 pp 出现,利用广度优先搜索或者深度优先搜索找到以 pp 为起点的最长路径的终点 xx; + +以节点 xx 出发,找到以 xx 为起点的最长路径的终点 yy; + +xx 到 yy 之间的路径即为图中的最长路径,找到路径的中间节点即为根节点。 + +上述算法的证明可以参考「[算法导论习题解答 9-1](http://courses.csail.mit.edu/6.046/fall01/handouts/ps9sol.pdf)」。 + + + +## 参考 + +- [图的最大环最长链](https://www.cnblogs.com/lfri/p/15758120.html) diff --git "a/_posts/Tech/Algorithms/2022-08-19-\346\225\260\344\275\215DP.md" "b/_posts/Tech/Algorithms/2022-08-19-\346\225\260\344\275\215DP.md" new file mode 100644 index 0000000000..ffdd366c48 --- /dev/null +++ "b/_posts/Tech/Algorithms/2022-08-19-\346\225\260\344\275\215DP.md" @@ -0,0 +1,91 @@ +--- +layout: post +category: Algorithms +title: 数位DP +tags: Algorithms +--- + +## 数位DP + +数位:把一个数字按照个、十、百、千等等一位一位地拆开,关注它每一位上的数字。如果拆的是十进制数,那么每一位数字都是 0~9,其他进制可类比十进制。 + +数位 DP:用来解决一类特定问题,这种问题比较好辨认,一般具有这几个特征: + +1. 要求统计满足一定条件的数的数量(即,最终目的为计数); +2. 这些条件经过转化后可以使用「数位」的思想去理解和判断; +3. 输入会提供一个数字区间(有时也只提供上界)来作为统计的限制; +4. 上界很大(比如 ),暴力枚举验证会超时。 + + + +比如统计[a,b] 之间的满足某个条件的数,这个b可能是1e15这样。 注定无法枚举。此类就可以数位DP, 然后a到b形式也可以变成f(b) -f(a)差分来统一处理。记得a这个本身单个可能要额外减去。 + +## 模板 + +1. 记忆化搜索 +2. 关键参数: 数位i, 用过数字mask, is_limit是否是受限,is_num前面是否填了数字.后面两个参数可适用于其它数位 DP 题目。 + +该模板对应[视频](https://www.bilibili.com/video/BV1rS4y1s721?vd_source=9d3646ab1738010f91f766880db9c1c6) + +题目: [2376. 统计特殊整数](https://leetcode.cn/problems/count-special-integers/) + +> 如果一个正整数每一个数位都是 **互不相同** 的,我们称它是 **特殊整数** 。 +> +> 给你一个 **正** 整数 `n` ,请你返回区间 `[1, n]` 之间特殊整数的数目。 + +```python +class Solution: + def countSpecialNumbers(self, n: int) -> int: + # 数位dp + s = str(n) + import functools + ''' + 记忆化搜索,i表示计算数位i, mark表示用过的数字 + 返回从数位i开始填数字,前面填数字的集合为mask, 能构造出整数的数量 + is_limit表示是否前i-1位是s对应位上的,即最大受限值max了,如果是则当前数字上线是s[i],而不是'9' + is_num表示前面是否填了数字,如果为True,则当前可从0开始,否则只能从1开始,后续会有个数位变成True, 从该数位开始是真实数字 + ''' + @functools.lru_cache(None) + def f(i: int, mask: int, is_limit: bool, is_num: bool): + if i == len(s): + # 最后一位了,合法则return 1 + return int(is_num) + res = 0 + if not is_num: + # 此时已经有前缀0了,因此不受限 + res += f(i + 1, mask, False, False) + # 数位i的上限 + up = int(s[i]) if is_limit else 9 + # 如果是is_num, 则可以从0开始,否则只能从1开始,毕竟是第一位数字 + for d in range(1 - int(is_num), up + 1): + if (mask >> d) & 1 == 0: + # 没用过该数字 + res += f(i + 1, mask | (1 << d), is_limit and d == up, True) + return res + + # 第一位就受限,因此is_limit = True + return f(0, 0, True, False) + +``` + +## 应用 + +- [902. 最大为 N 的数字组合](https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/) 求1-N的使用了digits里数字的数量,没有0, f(i: int, is_limit: bool, is_num: bool) + +- [233. 数字 1 的个数](https://leetcode.cn/problems/number-of-digit-one/) 给定一个整数 `n`,计算所有小于等于 `n` 的非负整数中数字 `1` 出现的个数。def f(i: int, ones: int, is_limit: bool, is_num: bool): ones是当前已用ones的数量。 + +- [面试题 17.06. 2出现的次数](https://leetcode.cn/problems/number-of-2s-in-range-lcci/) 和数字1的个数类似。 + +- [600. 不含连续1的非负整数](https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/) 给定一个正整数 `n` ,返回范围在 `[0, n]` 都非负整数中,其二进制表示不包含 **连续的 1** 的个数。def f(i: int, last_is_one: bool, is_limit: bool) + +- [1012. 至少有 1 位重复的数字](https://leetcode.cn/problems/numbers-with-repeated-digits/) 等价为n-完全不重复个数 + +- [1397. 找到所有好字符串](https://leetcode.cn/problems/find-all-good-strings/) + +- 给你两个长度为 n 的字符串 s1 和 s2 ,以及一个字符串 evil 。请你返回 好字符串 的数目。好字符串 的定义为:它的长度为 n ,字典序大于等于 s1 ,字典序小于等于 s2 ,且不包含 evil 为子字符串。 难点在于要动态维护kmp,先求evil的next数组,然后把匹配j当dp参数传递下去。 + + + +## 参考 + +https://leetcode.cn/problems/count-special-integers/solution/shu-wei-dp-mo-ban-by-endlesscheng-xtgx/ diff --git "a/_posts/Tech/Algorithms/2022-10-10-\345\255\227\347\254\246\344\270\262hash.md" "b/_posts/Tech/Algorithms/2022-10-10-\345\255\227\347\254\246\344\270\262hash.md" new file mode 100644 index 0000000000..83d09a4b43 --- /dev/null +++ "b/_posts/Tech/Algorithms/2022-10-10-\345\255\227\347\254\246\344\270\262hash.md" @@ -0,0 +1,73 @@ +--- +layout: post +category: Algorithms +title: 字符串hash +tags: Algorithms +--- + +## 字符串hash + +字符串hash得到hash值,用于o(1)时间复杂度判断是否相等。在提前计算好整个字符串的预处理情况下,可快速比较某两子串是否相等。 + + + +[oi wiki参考](https://oi-wiki.org/string/hash/) + + + +![image-20221010130458789](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202210101304818.png) + + + +## 模板 + +```python +class StringHash: + def __init__(self, s=""): + self.MOD = 998244353 + self.BASE = 131 + # 计算前缀哈希值 + n = len(s) + P = [0] * (n + 1) + P[0] = 1 + for i in range(1, n + 1, 1): + P[i] = P[i - 1] * self.BASE % self.MOD + H = [0] * (n + 1) + for i in range(1, n + 1, 1): + H[i] = (H[i - 1] * self.BASE + ord(s[i - 1])) % self.MOD + self.H = H + self.P = P + + ''' + s的[l,r]区间的hash值,闭区间, l从1开始 + ''' + + def get_hash(self, l=0, r=0): + l, r = l + 1, r + 1 + return (self.H[r] - self.H[l - 1] * self.P[r - l + 1] % self.MOD + self.MOD) % self.MOD + +示例: https://leetcode.cn/problems/maximum-deletions-on-a-string/submissions/ +class Solution: + def deleteString(self, s: str) -> int: + n = len(s) + dp = [1] * n + hash = StringHash(s) + for i in range(n - 1, -1, -1): + for j in range(i + 1, n): + if (j - i + 1) % 2 == 0: + mid = (i + j) // 2 + if hash.get_hash(i, mid) == hash.get_hash(mid+1, j): + # print(i, j, mid) + dp[i] = max(dp[i], dp[mid+1]+1) + # print(dp[i]) + # print(dp) + return dp[0] +``` + + + +## 题目 + + + +题目: https://leetcode.cn/problems/maximum-deletions-on-a-string/solution/by-tsreaper-9xkh/ diff --git "a/_posts/Tech/Algorithms/2022-10-16-\345\217\214\346\214\207\351\222\210.md" "b/_posts/Tech/Algorithms/2022-10-16-\345\217\214\346\214\207\351\222\210.md" new file mode 100644 index 0000000000..66fdad41f9 --- /dev/null +++ "b/_posts/Tech/Algorithms/2022-10-16-\345\217\214\346\214\207\351\222\210.md" @@ -0,0 +1,39 @@ +--- +layout: post +category: Algorithms +title: 双指针 +tags: Algorithms +--- + +## 双指针 + + + +1. 统计子数组数目,枚举右边界,左边界视情况而定。比如求子数组数目,要求子数组包含两个值,那枚举i,j就等于有两个值的最后最小坐标,每次结果可加i-j+1, 实例题目 [6207. 统计定界子数组的数目](https://leetcode.cn/problems/count-subarrays-with-fixed-bounds/) [题解](https://leetcode.cn/problems/count-subarrays-with-fixed-bounds/solution/hua-dong-chuang-by-yi-wei-8-c7h7/) + +2. [3Sum](https://leetcode-cn.com/problems/3sum/) 排序+双指针 + +3. [Longest Repeating Character Replacement](https://leetcode-cn.com/problems/longest-repeating-character-replacement/) 给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。 + + 解答: **双指针。这个双指针属于left每次只移动一次,记得看下。** + +4. [11. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) 多个柱子,挑两个柱子,统计最多能接多少水,双指针,不断缩小两边矮的那个 + +5. [1438. 绝对差不超过限制的最长连续子数组](https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) 给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。 维护max, min的队列,同时双指针,不断移动左指针。 + +6. [6270. 每种字符至少取 K 个](https://mafulong.github.io/2022/12/25/6270.-%E6%AF%8F%E7%A7%8D%E5%AD%97%E7%AC%A6%E8%87%B3%E5%B0%91%E5%8F%96-K-%E4%B8%AA/) 给你一个由字符 `'a'`、`'b'`、`'c'` 组成的字符串 `s` 和一个非负整数 `k` 。每分钟,你可以选择取走 `s` **最左侧** 还是 **最右侧** 的那个字符。 + + 你必须取走每种字符 **至少** `k` 个,返回需要的 **最少** 分钟数 + +7. [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) + +8. 给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。 + + 如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。 + + 反向思考,找最大子数组。 + + + + + diff --git "a/_posts/Tech/Algorithms/2023-01-15-\346\240\221\345\275\242DP\345\222\214\346\215\242\346\240\271DP.md" "b/_posts/Tech/Algorithms/2023-01-15-\346\240\221\345\275\242DP\345\222\214\346\215\242\346\240\271DP.md" new file mode 100644 index 0000000000..e6bbb31406 --- /dev/null +++ "b/_posts/Tech/Algorithms/2023-01-15-\346\240\221\345\275\242DP\345\222\214\346\215\242\346\240\271DP.md" @@ -0,0 +1,227 @@ +--- +layout: post +category: Algorithms +title: 树形DP和换根DP +tags: Algorithms +--- + +## 树形DP和换根DP + +[oi wiki](https://oi-wiki.org/dp/tree/) + +## 树形DP + +固定一个根节点。在树上做DP. 往往是树dfs遍历,先子树再基于子树结果对当前节点DP递推到根节点。 + + + +对于图转树的DFS遍历,其实就是无向树。 + +```python +class DoubleTree: + def tree_dfs(graph, root): + ''' + 这种树上dfs不需要维护visit记录,只需要记录遍历刀当前节点的parent就可以了。时间复杂度o(n) + ''' + import collections + p = collections.defaultdict(lambda: -1) + + def dfs(u): + if len(graph[u]) == 1 and u in p: + print("is leaf") + for v in graph[u]: + if p[u] != v: # 非访问过 + # 多次生效,针对多个v + p[v] = u + dfs(v) + + def dfs_p(u, p): + if len(graph[u]) == 1 and u != root: + # 判断叶子节点时要用和这个双重判断 + + print("is leaf") + for v in graph[u]: + if v != p: + dfs_p(v, u) + + +``` + + + +也可以通过 g[0].append(-1) # 防止根节点被认作叶子 + + + + + +经典树形DP + +- [124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) 给你一个二叉树的根节点 `root` ,返回其 **最大路径和** 。 + + ```python + class Solution: + ans = float('-inf') + + def maxPathSum(self, root: TreeNode) -> int: + def dfs(node): + if not node: + return 0 + l = dfs(node.left) + r = dfs(node.right) + self.ans = max(self.ans, max(l, 0) + max(r, 0) + node.val) + return max(l, r, 0) + node.val + dfs(root) + return self.ans + ``` + + + +- [310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) +- [6294. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) 求最长直径,随便固定一个点为root。 树形DP +- [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) 和最大路径和一样 + + + + + +## 换根DP + +- [参考](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/huan-gen-dong-tai-gui-hua-jie-fa-by-vcli-gaii/) + +树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。 + +通常需要两次 DFS,第一次 DFS 预处理诸如深度,点权和之类的信息,在第二次 DFS 开始运行换根动态规划。 + +第二次dfs时,把父节点作为子树。来调整。比如 [参考](https://zhuanlan.zhihu.com/p/437753260) + +![image-20230115160137183](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301151601264.png) + +需要计算出换根后的值的变化。 然后枚举根。 + + + +每次换根只能换相邻的。否则影响其它节点计算。只有相邻时只影响相邻的。而且枚举的一定是根,而不是第三个点为根的情况的相邻递推。 + +换根后不用恢复数据,因为是邻接点递推的。其它的没用。 + + + + +记住每次改变只有u 和 v的dp值改变。[详细](https://leetcode.cn/problems/sum-of-distances-in-tree/solution/shu-zhong-ju-chi-zhi-he-by-leetcode-solution/) + + +- [834. 树中距离之和](https://leetcode.cn/problems/sum-of-distances-in-tree/) 给定一个无向、连通的树。树中有 `n` 个标记为 `0...n-1` 的节点以及 `n-1` 条边 。返回长度为 `n` 的数组 `answer` ,其中 `answer[i]` 是树中第 `i` 个节点与所有其他节点之间的距离之和。 +- [310. 最小高度树](https://leetcode.cn/problems/minimum-height-trees/) [参考题解](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/huan-gen-dong-tai-gui-hua-jie-fa-by-vcli-gaii/) + +```python +class Solution: + def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: + graph = collections.defaultdict(list) + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + h0 = [0 for _ in range(n)] + + def dfs(u, p): + nonlocal h0 + l = 0 + for v in graph[u]: + if v == p: continue + l = max(l, dfs(v, u) + 1) + h0[u] = l + return l + + dfs(0, -1) + # print(h0) + + dp = [0] * n + + def dfs2(u, p): + nonlocal dp + # 此时就是u为root,每次进入函数都是已经换根为u了。此时孩子的h0是准的,h0[u]是不准的,需要根据孩子重新计算 + first, second = -1, -1 + for v in graph[u]: + if h0[v] >= first: + second = first + first = h0[v] + elif h0[v] >= second: + second = h0[v] + dp[u] = first + 1 + # print(u, dp[u]) + for v in graph[u]: + if v == p: continue + # 假设u与v换根 + h0[u] = first if h0[v] != first else second + h0[u] += 1 + dfs2(v, u) + + dfs2(0, -1) + # print(dp) + minv = min(dp) + ans = [] + for i in range(n): + if dp[i] == minv: + ans.append(i) + return ans + +``` + + + +- [6294. 最大价值和与最小价值和的差值](https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/) 换根DP + +```python +class Solution: + def maxOutput(self, n: int, edges: List[List[int]], price: List[int]) -> int: + graph = collections.defaultdict(list) + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + # 求最大价值和 + h0 = [0 for _ in range(n)] + + + # 第一次遍历,统计以0为根的结果 + def dfs(u, p): + nonlocal h0 + l = price[u] + for v in graph[u]: + if v == p: continue + dfs(v, u) + l = max(l, price[u] + h0[v]) + h0[u] = l + return l + + dfs(0, -1) + # print(h0) + + dp = [0] * n + + def dfs2(u, p): + nonlocal dp + # 此时就是u为root,每次进入函数都是已经换根为u了。此时孩子的h0是准的,h0[u]是不准的,需要根据孩子重新计算下h0[u] + first, second = 0, 0 + dp[u] = 0 + for v in graph[u]: + # 此时不能continue,因为p是有用的,u为root,无p + # if v == p: continue + if h0[v] >= first: + second = first + first = h0[v] + elif h0[v] >= second: + second = h0[v] + # 以u为根情况下 h0和dp的值 + dp[u] = first + h0[u] = first + price[u] + # print(u, dp[u], first, second) + for v in graph[u]: + if v == p: continue + # 假设u与v换根 + h0[u] = price[u] + (first if (h0[v] != first) else second) + dfs2(v, u) + + dfs2(0, -1) + return max(dp) +``` + diff --git "a/_posts/Tech/Algorithms/2023-01-29-\347\273\237\350\256\241\345\233\233\345\205\203\347\273\204\344\271\213\344\270\255\351\227\264\346\236\232\344\270\276.md" "b/_posts/Tech/Algorithms/2023-01-29-\347\273\237\350\256\241\345\233\233\345\205\203\347\273\204\344\271\213\344\270\255\351\227\264\346\236\232\344\270\276.md" new file mode 100644 index 0000000000..087462b364 --- /dev/null +++ "b/_posts/Tech/Algorithms/2023-01-29-\347\273\237\350\256\241\345\233\233\345\205\203\347\273\204\344\271\213\344\270\255\351\227\264\346\236\232\344\270\276.md" @@ -0,0 +1,23 @@ +--- +layout: post +category: Algorithms +title: 统计四元组之中间枚举 +tags: Algorithms +--- + +## 统计四元组之技巧 中间枚举 + +有些题目是让统计四元组之类的数量。 + +此时就可以枚举中间的。 + +通常可以应用些预处理来降低时间复杂度。 + + + +应用 + +- [6340. 统计上升四元组](https://leetcode.cn/contest/weekly-contest-330/problems/count-increasing-quadruplets/) [参考](https://leetcode.cn/circle/discuss/LWLEFc/view/4dIQzE/) + +- [2242. 节点序列的最大得分](https://leetcode.cn/problems/maximum-score-of-a-node-sequence/) + diff --git "a/_posts/Tech/Database/2021-01-09-\346\225\260\346\215\256\345\272\223\345\257\271\346\257\224.md" "b/_posts/Tech/Database/2021-01-09-\346\225\260\346\215\256\345\272\223\345\257\271\346\257\224.md" index 0f45d5872d..1d12cfacdb 100644 --- "a/_posts/Tech/Database/2021-01-09-\346\225\260\346\215\256\345\272\223\345\257\271\346\257\224.md" +++ "b/_posts/Tech/Database/2021-01-09-\346\225\260\346\215\256\345\272\223\345\257\271\346\257\224.md" @@ -270,4 +270,19 @@ Google 发布了第一个列型存储数据库 [Bigtable](http://www.read.seas.h - 排行榜或者得分数据 - 临时数据,如购物车 - 频繁访问的(“热”)表 -- 元数据/查找表 \ No newline at end of file +- 元数据/查找表 + + + +## NoSQL + +> [参考](https://aws.amazon.com/cn/nosql/) + +### 为什么应该使用 NoSQL 数据库? + +NoSQL 数据库非常适合许多现代应用程序,例如移动、Web 和游戏等应用程序,它们需要灵活、可扩展、高性能和功能强大的数据库以提供卓越的用户体验。 + +- **灵活性:**NoSQL 数据库通常提供灵活的架构,以实现更快速、更多的迭代开发。灵活的数据模型使 NoSQL 数据库成为半结构化和非结构化数据的理想之选。 +- **可扩展性:**NoSQL 数据库通常被设计为通过使用分布式硬件集群来横向扩展,而不是通过添加昂贵和强大的服务器来纵向扩展。一些云提供商在后台将这些操作处理为完全托管服务。 +- **高性能:**NoSQL 数据库针对特定的数据模型和访问模式进行了优化,这与尝试使用关系数据库完成类似功能相比可实现更高的性能。 +- **强大的功能:**NoSQL 数据库提供功能强大的 API 和数据类型,专门针对其各自的数据模型而构建。 \ No newline at end of file diff --git a/_posts/Tech/DistributedSystem/2020-12-15-SOA.md b/_posts/Tech/DistributedSystem/2020-12-15-SOA.md index 9242fbf352..eda984b5d1 100644 --- a/_posts/Tech/DistributedSystem/2020-12-15-SOA.md +++ b/_posts/Tech/DistributedSystem/2020-12-15-SOA.md @@ -63,3 +63,55 @@ SOA粗暴理解:把系统按照实际业务,拆分成刚刚好大小的、 ## SOA和微服务microservice的区别 +我们应该将SOA视为微服务的超集。 + + + +| **SOA架构** | **微服务架构** | +| ---------------------------------------- | ---------------------------------------- | +| 最大化应用服务的可重用性 | 关注于解耦 | +| 系统的变化需要修改整体结构 | 系统的变化是创造一种新的服务 | +| DevOps和持续交付正在变得流行,但不是主流 | 专注于DevOps和持续交付 | +| 专注于业务功能的可重用性 | 更加重视“边界上下文”的概念 | +| 对于通信,它使用企业服务总线(ESB) | 对于通信使用不那么复杂和简单的消息系统 | +| 支持多种消息协议 | 使用轻量级协议,如HTTP,REST或Thrift API | +| 为部署到它的所有服务使用通用平台 | 应用服务器并未真正使用,通常使用云平台 | +| 使用容器(如Docker)不太受欢迎 | 容器与微服务一起工作得很好 | +| SOA服务共享数据存储 | 每个微服务可以具有独立的数据存储 | +| 共同治理和标准 | 轻松治理,更加注重团队协作和选择自由度 | + +我将在上表中显示的某些方面进一步详细说明,并进一步解释其中的差异: + +- + + 开发 - 在这两种体系结构中,可以使用不同的编程语言和工具开发服务,从而为开发团队带来技术多样性。可以在多个团队中组织开发,但是,在SOA中,每个团队都需要了解常见的通信机制。另一方面,通过微服务,服务可以独立于其他服务运行和部署。因此,更容易经常部署新版本的微服务或独立扩展服务。您可以在此处进一步了解微服务的这些优点。 + +- + + “绑定上下文” - SOA鼓励共享组件,而微服务试图通过“绑定上下文”最小化共享。绑定上下文指的是将组件及其数据作为单个单元耦合,具有最小的依赖性。由于SOA依赖于多种服务来满足业务请求,因此基于SOA构建的系统可能比微服务慢。 + +- + + 通信 - 在SOA中,ESB可能成为影响整个系统的单点故障。由于每个服务都通过ESB进行通信,如果其中一个服务速度变慢,它可能会阻塞ESB请求该服务。另一方面,微服务在容错方面要好得多。例如,如果一个微服务有内存故障,那么只有那个微服务会受到影响。所有其他微服务将继续定期处理请求。 + +- + + 互操作性 - SOA通过其消息传递中间件组件促进多个异构协议的使用。微服务试图通过减少集成选择的数量来简化架构模式。**因此,如果要在异构环境中使用不同协议集成多个系统,则需要考虑SOA。如果可以通过相同的远程访问协议访问所有服务,那么微服务对您来说是更好的选择。** + +- + + 大小 - 最后但并非最不重要的是,**SOA和微服务之间的主要区别在于大小和范围。微服务中的前缀“微”指的是内部组件的粒度,这意味着它们必须比SOA趋向于小得多。微服务中的服务组件通常只有一个目的,他们做得很好。另一方面,在SOA中,服务通常包含更多的业务功能,并且它们通常作为完整的子系统实现。** + + + +人们不能简单地说一个架构比另一个架构好。它主要取决于您正在构建的应用程序的目的。**SOA更适合需要与许多其他应用程序集成的大型复杂企业应用程序环境。** 话虽这么说,**较小的应用程序不适合SOA,因为它们不需要消息传递中间件组件。** 另一方面,**微服务更适合于较小且分区良好的基于Web的系统。此外,如果您正在开发移动或Web应用程序,那么微服务可以让您作为开发人员获得更大的控制权。** 最后,我们可以得出结论,因为它们用于不同的目的 - **微服务和SOA确实是不同类型的架构。** + + + + + +SOA范围大,着眼于整个企业,而微服务范围小,着眼于应用。 + +**SOA是与企业服务的公开性密切相关的,关注的范围更大,是应用与应用之间的通信、服务公开。** + +**微服务是与应用架构紧密相关的,关注的范围小,只关注应用本身的范围。** diff --git "a/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" "b/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" index 451b6f264b..cde54a6f67 100644 --- "a/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" +++ "b/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" @@ -96,6 +96,11 @@ __两阶段提交协议解决的是分布式数据库数据强一致性问题__ 针对两阶段提交存在的问题,三阶段提交协议通过引入一个 **预询盘** 阶段,以及超时策略来减少整个集群的阻塞时间,提升系统性能。三阶段提交的三个阶段分别为:预询盘(can_commit)、预提交(pre_commit),以及事务提交(do_commit)。 + + +1. 引入canCommit阶段减少**同步阻塞**。 +2. 引入超时减少**单点故障**,没有协调者消息并超时了,直接 commit + #### 第一阶段:预询盘 该阶段协调者会去询问各个参与者是否能够正常执行事务,参与者根据自身情况回复一个预估值,相对于真正的执行事务,这个过程是轻量的,具体步骤如下: diff --git "a/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\347\256\227\346\263\225\345\222\214\345\215\217\350\256\256.md" "b/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\347\256\227\346\263\225\345\222\214\345\215\217\350\256\256.md" index 98418ccab5..6b202f985b 100644 --- "a/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\347\256\227\346\263\225\345\222\214\345\215\217\350\256\256.md" +++ "b/_posts/Tech/DistributedSystem/2021-01-09-\345\210\206\345\270\203\345\274\217\347\256\227\346\263\225\345\222\214\345\215\217\350\256\256.md" @@ -3,6 +3,7 @@ layout: post category: DistributedSystem title: 分布式算法和协议 tags: DistributedSystem +recent_update: true --- ## 分布式算法和协议-思维导图 @@ -37,6 +38,14 @@ tags: DistributedSystem image-20210109160824094 +这里的ZAB为何是最终一致性而不是强一致性?[参考](https://www.zhihu.com/question/455703356/answer/1847949827) + +- 写是强一致性,单领导者模型,写需要majority保证,脑裂情况下也可以写强一致 +- 读两种接口,1:读单个机器的,2:只读主的。但脑裂时多个主还是不能强一致性。 +- etcd做了读的优化,读时也需要majority,需要大伙同意认为它是主,但牺牲了效率。 + +这里的一致性是从读写方面,写一致性,都写成功,读一致性,从提供的读接口任意时刻咋读都一样。和事务一致性不太一样。 + ## 分布式互斥方法(集中,民主协商,轮值ceo(令牌)) ![4210e133d9d94ea22917db55458c11c6](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv2/v2/34.png) diff --git "a/_posts/Tech/DistributedSystem/2021-12-26-etcd\345\222\214raft.md" "b/_posts/Tech/DistributedSystem/2021-12-26-etcd\345\222\214raft.md" index 3fd12cdff4..0bf8fbfdf5 100644 --- "a/_posts/Tech/DistributedSystem/2021-12-26-etcd\345\222\214raft.md" +++ "b/_posts/Tech/DistributedSystem/2021-12-26-etcd\345\222\214raft.md" @@ -3,6 +3,7 @@ layout: post category: DistributedSystem title: etcd和raft tags: DistributedSystem +recent_update: true --- # etcd diff --git "a/_posts/Tech/DistributedSystem/2022-11-26-\346\227\240\344\270\273\345\244\215\345\210\266\347\263\273\347\273\237.md" "b/_posts/Tech/DistributedSystem/2022-11-26-\346\227\240\344\270\273\345\244\215\345\210\266\347\263\273\347\273\237.md" new file mode 100644 index 0000000000..ee018bc3b1 --- /dev/null +++ "b/_posts/Tech/DistributedSystem/2022-11-26-\346\227\240\344\270\273\345\244\215\345\210\266\347\263\273\347\273\237.md" @@ -0,0 +1,63 @@ +--- +layout: post +category: DistributedSystem +title: 无主复制系统 +tags: DistributedSystem +--- + +为什么需要复制数据? - 允许系统在部分节点出现故障后继续工作(增加可用性) - 地理上保持数据离用户更近(减少延迟) - 扩展可以提供查询的机器数量(增加读吞吐) + +复制算法: 1. 单主复制:所有客户端都将写入操作发送到主节点上,该节点负责将数据更改事件发送到其它副本。每个副本都可以接受读请求,但内容可能是过期值。 2. 多主复制:系统中存在多个主节点,每个都可以接受请求,客户端将写请求发送到其中一个主节点上,该节点负责将数据更改事件同步到其它主节点和自己的从节点。 3. 无主复制:客户端将写请求发送多个节点上,读取时从多个节点上并行读取,以此检测和纠正某些过期数据。 + +主从复制可参考 [link](https://iswade.github.io/database/replication/) + +## 无主复制 + +单主、多主复制思路都是:客户端向一个主节点发写请求,而DB系统负责将写请求复制到其他副本。主节点决定写顺序,从节点按相同顺序应用主节点发送的写日志。 + +某些数据存储系统采用不同设计:放弃主节点,允许任何副本直接接受客户端的写。最早的复制数据系统就是无主节点的(或称之为去中心复制、无中心复制),但后来在关系数据库主导时代,这个想法几乎被忘却。在亚马逊将其用于其内部的Dynamo系统后,它再一次成为流行的DB架构。 Riak,Cassandra和Voldemort都是由Dynamo启发的无主复制模型的开源数据存储,所以这类数据库也被称为*Dynamo风格*。 + + + +在一些无主实现中,客户端直接将写请求发到多副本,而另一些实现中,有一个协调者(coordinator)节点代表客户端进行写入,但与主节点的数据库不同,协调者不负责维护写入顺序。这种设计差异对DB使用方式有深远影响。 + + + +失效节点重新上线,而客户端开始读取它。节点失效期间发生的任何写入在该节点都尚未同步,因此读取可能得到过期数据。 + +为解决该问题,当一个客户端从DB读数据时,它不是向1个副本发送请求,而是并行发送到多副本。客户端可能会从不同节点获得不同响应,即来自一个节点的最新值和来自另一个节点的旧值。可利用版本号确定哪个值更新。 + +### 读修复和反熵 + +复制模型应确保所有数据最终复制到所有副本。在一个失效节点重新上线后,它如何赶上错过的写入呢? + +Dynamo风格的数据存储系统常机制: + +#### 读修复(Read repair) + +当客户端并行读取多副本时,可检测到过期的返回值。客户端可判断副本3是过期值,然后将新值写入该副本。适用于读密集场景 + +#### 反熵过程(Anti-entropy process) + +一些数据存储有后台进程,不断查找副本之间的数据差异,将任何缺少的数据从一个副本复制到另一个副本。和基于主节点复制的复制日志不同,此反熵过程不保证任何特定的顺序复制写入,并且会引入明显的同步滞后 + + + +并非所有系统都实现这俩方案。如Voldemort目前无反熵过程。若无反熵过程,由于【读修复】只在发生读取时才可能执行修复,那些很少访问的数据有可能在某些副本中已丢失而无法再检测到,从而降低了写的持久性。 + + + +反熵就是树形的hash,用于快速比较数据是否一致的。 + + + +## 参考 + +- [**无主复制系统(1)-节点故障时写DB**](https://blog.51cto.com/u_11440114/5550577) +- [**无主复制系统(2)-读写quorum**](https://blog.51cto.com/u_11440114/5550582) + + + + + +TODO: 需要读下ddia. diff --git a/_posts/Tech/ElasticSearch/2020-12-08-ElasticSearch.md b/_posts/Tech/ElasticSearch/2020-12-08-ElasticSearch.md index de4939df98..5eda1601ec 100644 --- a/_posts/Tech/ElasticSearch/2020-12-08-ElasticSearch.md +++ b/_posts/Tech/ElasticSearch/2020-12-08-ElasticSearch.md @@ -3,6 +3,7 @@ layout: post category: ElasticSearch title: ElasticSearch(ES)原理 tags: ElasticSearch +recent_update: true --- ## Part1: ES介绍及核心概念 diff --git "a/_posts/Tech/FrontEnd/2017-12-03-css\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2017-12-03-css\345\255\246\344\271\240\347\254\224\350\256\260.md" index 1df5eb98e6..a76bfdfb2d 100644 --- "a/_posts/Tech/FrontEnd/2017-12-03-css\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ "b/_posts/Tech/FrontEnd/2017-12-03-css\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -4,7 +4,39 @@ category: FrontEnd title: CSS笔记 tags: FrontEnd --- +# 概述 + +CSS:Cascading Style Sheet,层叠样式表。CSS 的作用就是给 HTML 页面标签添加各种样式,**定义网页的显示效果**。简单一句话:CSS 将网页**内容和显示样式进行分离**,提高了显示功能。 + +**CSS 优点:** + +1. 使数据和显示分开 +2. 降低网络流量 +3. 使整个网站视觉效果一致 +4. 使开发效率提高了(耦合性降低,一个人负责写 html,一个人负责写 css) + +比如说,有一个样式需要在一百个页面上显示,如果是 html 来实现,那要写一百遍,现在有了 css,只要写一遍。现在,html 只提供数据和一些控件,完全交给 css 提供各种各样的样式。 + +重点:盒子模型、浮动、定位 + + + +CSS 的书写方式,实就是问你 CSS 的代码放在哪个位置。CSS 代码理论上的位置是任意的,**但通常写在` + + +
    +
    + AAAA +
    +
    + + + + +``` + diff --git "a/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..84ad1c0710 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -0,0 +1,457 @@ +--- +layout: post +category: FrontEnd +title: html笔记 +tags: FrontEnd +--- + +# 如何理解HTML? + +**Web标准包括三个方面**: + +- 结构标准(HTML):用于对网页元素进行整理和分类。 +- 表现标准(CSS):用于设置网页元素的版式、颜色、大小等外观样式。 +- 行为标准(JS):用于定义网页的交互和行为。 + + + +HTML 不是一种编程语言,是一种描述性的**标记语言**。 + +**作用**:HTML是负责描述文档**语义**的语言。 + +HTML 格式的文件是一个纯本文文件(就是用txt文件改名而成),用一些标签来描述语义,这些标签在浏览器页面上是无法直观看到的,所以称之为“超文本标记语言”。 + +比如,面试的时候问你,`

    ` 标签有什么作用? + +- 正确答案:给文本增加主标题的语义。 +- 错误答案:给文字加粗、加黑、变大。 + +之前的HTMl很混乱,还有些样式的标签,比如粗体`` 等,新一代的html都废弃这些了,使之专门负责语义。 + + + + + +**语义化的意义是什么** + +- 开发者容易理解,便于维护。 +- 机器(搜索引擎、读屏软件等)容易理解结构 +- 有助于 SEO + + + +HTML 相当于人的身体组织结构 + +# 笔记 + + + +- [文档声明头](https://web.qianguyihao.com/01-HTML/03-%E5%88%9D%E8%AF%86HTML.html#_1%E3%80%81%E6%96%87%E6%A1%A3%E5%A3%B0%E6%98%8E%E5%A4%B4) +- [计算机编码介绍](https://web.qianguyihao.com/01-HTML/03-%E5%88%9D%E8%AF%86HTML.html#_4%E3%80%81-body-%E6%A0%87%E7%AD%BE) unicode是4字节表示,ansi是2字节,utf-8是变长。中文除utf8外还可以gbk仅包含常用中文。追求速度就后者,字节少,快,但不全。 +- HTML中所有的**文字之间**,如果有空格、换行、tab都将被折叠为一个空格显示。 + +- [iframe](https://web.qianguyihao.com/01-HTML/07-html%E6%A0%87%E7%AD%BE%E5%9B%BE%E6%96%87%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%BA%8C%EF%BC%89.html#frame-%EF%BC%9A%E6%A1%86%E6%9E%B6) + +```html + + + + + + Title + + + + + + + + + + +

    mafulong

    + +
    + + Baidu + +
    + + + +

    mafulong

    + + +

    mafulong

    +

    mafulong

    + + + mafulong + + mafulong + 引用 +
    自动缩进的长引用
    + 数学变量,斜体表示 + +
    斜体表示
    + +
    +        
    +            保持代码格式,不删除空格
    +        
    +    
    + + + + + + + + + + + + + + + + + + + + +
    NameTelephone
    12323
    fjdf
    + + +
      +
    1. 1
    2. +
    + +
      +
    • +
    + +
    +
    Coffee
    +
    - black hot drink
    +
    Milk
    +
    - white cold drink
    +
    + + Pulpit rock + + +

    无边框的图片链接: + + HTML 教程

    + + + + + + +``` + + + +```html +fieldset,legend 是表单的语义化。 +``` + +- [label标签](https://web.qianguyihao.com/01-HTML/07-html%E6%A0%87%E7%AD%BE%E5%9B%BE%E6%96%87%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%BA%8C%EF%BC%89.html#%E8%A1%A8%E5%8D%95%E7%9A%84%E8%AF%AD%E4%B9%89%E5%8C%96) 将文字绑定到一个input上,避免点文字和点radio等分离,使其作为一个整体。 + +```html + + + + + Title + + + + + + +

    我的母亲有 蓝色 的眼睛。

    + + + +
    +

    这是一个在 div 元素中的标题。

    +

    这是一个在 div 元素中的文本。

    +
    + + + + + + +

    人生啊

    +
    + +
    + + +
    + Personal information: + Name:
    + E-mail:
    + Date of birth: +
    + First name
    + + female + male + + + + + + + + + + + + + +
    + + + +``` + + + +# 速查 + +## 参考手册 + +- [HTML 参考手册](https://www.runoob.com/tags/html-reference.html) + + + +## HTML基本文档 + +```html + + + +文档标题 + + +可见文本... + + +``` + +## 基本标签(Basic Tags) + +```html +

    最大的标题

    +

    . . .

    +

    . . .

    +

    . . .

    +
    . . .
    +
    最小的标题
    + +

    这是一个段落。

    +
    (换行) +
    (水平线) + +``` + +## 文本格式化(Formatting) + +```html +粗体文本 +计算机代码 +强调文本 +斜体文本 +键盘输入 + H5已废弃 +
    预格式化文本
    +更小的文本 +重要的文本 + + (缩写) + TheWHO was founded in 1948. +
    (联系信息) + 就是斜体 + (文字方向) +
    (从另一个源引用的部分) + (工作的名称) + (删除的文本) + (插入的文本) + (下标文本) + (上标文本) +``` + +## 链接(Links) + +```html +普通的链接:链接文本 +图像链接: 替换文本 +邮件链接: 发送e-mail +书签: +提示部分 +跳到提示部分 +``` + + + +> [参考](https://www.runoob.com/html/html-links.html) + +可以超链接可以设置html锚点到某个元素。 + + + +## 图片(Images) + +必须有src和alt属性 + +```html +替换文本 +``` + +## 样式/区块(Styles/Sections) + +```html + +
    文档中的块级元素
    +文档中的内联元素 +``` + +## 无序列表 + +```html +
      +
    • 项目
    • +
    • 项目
    • +
    +``` + +## 有序列表 + +```html +
      +
    1. 第一项
    2. +
    3. 第二项
    4. +
    +``` + +## 定义列表 + +```html +
    +
    项目 1
    +
    描述项目 1
    +
    项目 2
    +
    描述项目 2
    +
    +``` + +## 表格(Tables) + +```html + + + + + + + + + +
    表格标题表格标题
    表格数据表格数据
    +``` + +## 框架(Iframe) + +```html + +``` + +## 表单(Forms) + +每个输入元素必须有一个name属性 + +在form规定action, submit提交就执行,method规定post还是get, 默认是get + +```html +
    + + + + + + + + + + +
    +``` + +> [参考](https://www.runoob.com/html/html-forms.html) + +表单是一个包含表单元素的区域。 + +表单元素是允许用户在表单中输入内容,比如:文本域(textarea)、下拉列表(select)、单选框(radio-buttons)、复选框(checkbox) 等等。 + +我们可以使用 **form** 标签来创建表单 + + + +input也可以脱离form使用,不过可能需要结合ajax等。 + + + +## 实体(Entities) + +```html +< 等同于 < +> 等同于 > +© 等同于 © +``` + + + +## 事件 + +[参考](https://www.runoob.com/tags/ref-eventattributes.html) + +- `onclick`:用户点击 HTML 元素。 +- `onchange`:HTML 元素改变. `onchange` 事件会在域的内容改变时发生。`onchange` 事件也可用于单选框与复选框改变后触发的事件。 +- `onload`:浏览器已完成页面的加载。 +- `onmouseover`:用户在一个HTML元素上移动鼠标。 +- `onmouseout`:用户从一个HTML元素上移开鼠标。 +- `onkeydown`:用户按下键盘按键。 \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\212.md" "b/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\212.md" deleted file mode 100644 index fa20c72437..0000000000 --- "a/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\212.md" +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: post -category: FrontEnd -title: html笔记上 -tags: FrontEnd ---- - -## html - -```html - - - - - - Title - - - - - - - - - - -

    mafulong

    - -
    - - Baidu - -
    - - - -

    mafulong

    - - -

    mafulong

    -

    mafulong

    - - - mafulong - - mafulong - 引用 -
    自动缩进的长引用
    - 数学变量,斜体表示 - -
    斜体表示
    - -
    -        
    -            保持代码格式,不删除空格
    -        
    -    
    - - - - - - - - - - - - - - - -
    12323
    fjdf
    - - -
      -
    1. 1
    2. -
    - -
      -
    • -
    - - - - - -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\213.md" "b/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\213.md" deleted file mode 100644 index c2b3556ae5..0000000000 --- "a/_posts/Tech/FrontEnd/2017-12-03-html\345\255\246\344\271\240\347\254\224\350\256\260\344\270\213.md" +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: post -category: FrontEnd -title: html笔记下 -tags: FrontEnd ---- - -## html - -```html - - - - - Title - - - - - - - - - - - - - -

    人生啊

    -
    - -
    - - - First name
    - - female - male - - - - - - - - - -
    -
    - - -``` diff --git "a/_posts/Tech/FrontEnd/2017-12-03-javascript\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2017-12-03-javascript\345\255\246\344\271\240\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..0ba482f850 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2017-12-03-javascript\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -0,0 +1,2378 @@ +--- +layout: post +category: FrontEnd +title: javascript笔记 +tags: FrontEnd +recent_update: true +--- + +# 语法 + +## 页面如何添加js + +外部脚本, 可以插入任何位置。 需要js后缀 + +```javascript + +``` + +内部脚本 + +```javascript + +``` + +脚本可被放置在 HTML 页面的 body和head部分中。 + +内联,在html里。 + +```scala + +``` + + + +## Js调用策略 + +调用顺序:HTML 元素是按其在页面中出现的次序调用的,如果用 JavaScript 来管理页面上的元素(更精确的说法是使用 [文档对象模型](https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model) DOM),若 JavaScript 加载于欲操作的 HTML 元素之前,则代码将出错。 + +JavaScript 调用于文档头处,解析 HTML 文档体之前。这样做是有隐患的,需要使用一些结构来避免错误发生。 + + + +“内部”示例使用了以下结构: + +```js +document.addEventListener("DOMContentLoaded", function() { + . . . +}); +``` + +这是一个事件监听器,它监听浏览器的 "`DOMContentLoaded`" 事件,即 HTML 文档体加载、解释完毕事件。事件触发时将调用 " `. . .`" 处的代码,从而避免了错误发生([事件](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Events) 的概念稍后学习)。 + +“外部”示例中使用了 JavaScript 的一项现代技术(`async` “异步”属性)来解决这一问题,它告知浏览器在遇到 ` +``` + +上述情况下,脚本和 HTML 将一并加载,代码将顺利运行。 + +**备注:** “外部”示例中 `async` 属性可以解决调用顺序问题,因此无需使用 `DOMContentLoaded` 事件。而 `async` 只能用于外部脚本,因此不适用于“内部”示例。 + + + +另外用了async后 多个js顺序就保证不了了,需要用defer + +解决这一问题可使用 `defer` 属性,脚本将按照在页面中出现的顺序加载和运行: + +```js + + + + + +``` + + + +脚本调用策略小结: + +- 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 `async`。 +- 如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 `defer`,将关联的脚本按所需顺序置于 HTML 中。 + + + +## 注释 + +``` +// 我是一条注释 +``` + +``` +/* + 我也是 + 一条注释 +*/ +``` + +## JavaScript 输入输出 + +JavaScript 没有任何打印或者输出的函数。 + +document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。 + + JavaScript 显示数据 + JavaScript 可以通过不同的方式来输出数据: + + 使用 window.alert() 弹出警告框。 + 使用 document.write() 方法将内容写到 HTML 文档中。 + 使用 innerHTML 写入到 HTML 元素。 + 使用 console.log() 写入到浏览器的控制台。 + +输入可以prompt + +```js +function updateName() { + let name = prompt('输入一个新的名字:'); + para.textContent = '玩家 1:' + name; +} +``` + +```scala +console.log('name:' + name + ',age:' + age); //传统写法 +console.log(`我是${name},age:${age}`); //ES6 写法。注意语法格式 +``` + + + +## 操作 HTML 元素 + +如需从 JavaScript 访问某个 HTML 元素,您可以使用 document.getElementById(id) 方法。 + +```javascript +//通过标签找html元素 +var x=document.getElementById("main");//id: main +var y=x.getElementsByTagName("p"); + +var element=document.getElementById("header"); +element.innerHTML="New Header"; + +document.getElementById(id).attribute=new value; + +document.getElementById("image").src="landscape.jpg"; + +document.getElementById("p2").style.color="blue"; + +``` + +## 变量 + +在 ES6 语法之前,统一使用`var`关键字来声明一个变量。比如: + +```javascript +var name; // 定义一个名为 name 的变量。name是变量名。 +``` + +在 ES6 语法及之后的版本里,可以使用 `const`、`let`关键字来定义一个变量 + +```js +const name; // 定义一个常量 + +let age; // 定义一个变量 +``` + + + +**变量初始化** + +一旦你定义了一个变量,你就能够初始化它。方法如下,在变量名之后跟上一个“=”,然后是数值: + +```scala +myName = 'Chris'; +myAge = 37; + +// 你可以像这样在声明变量的时候给变量初始化: + +let myName = 'Chris'; +``` + +变量不声明,直接赋值:(正常) ;只声明,不赋值:(注意,打印 undefined);不声明,不赋值,直接使用:(会报错) + + + +## 数据类型 + +### 数据类型 + +#### JS 中一共有八种数据类型 + +- **基本数据类型(值类型)**:String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。 +- **引用数据类型(引用类型)**:Object 对象。 + +注意:内置对象 Function、Array、Date、RegExp、Error 等都是属于 Object 类型。也就是说,除了那七种基本数据类型之外,其他的,都称之为 Object 类型。 + + + +**数据类型之间最大的区别**: + +- 基本数据类型:参数赋值的时候,传数值。 +- 引用数据类型:参数赋值的时候,传地址。 + +```javascript +// 声明一个变量的语法是在 var 或 let 关键字之后加上这个变量的名字: + +let myName; +let myAge; + +//JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型: + +//声明变量时可以确定其类型,如 +var carname=new String; +var x= new Number; +var y= new Boolean; +var cars= new Array; +var person= new Object; +``` + + + +### String + +字符串是由若干个字符组成的,这些字符的数量就是字符串的长度。我们可以通过字符串的 length 属性可以获取整个字符串的长度。 + +字符串型可以是引号中的任意文本,其语法为:双引号 `""` 或者单引号 `''`。 + +js中常用单引号,html中用双引号 + + + +有了 ES6 语法,字符串拼接可以这样写: + +```javascript +var name = 'qianguyihao'; +var age = '26'; + +console.log('我是' + name + ',age:' + age); //传统写法 +console.log(`我是${name},age:${age}`); //ES6 写法。注意语法格式 + + +// 模板字符串支持换行 +const html = `
    + ${result.name} + ${result.age} + ${result.sex} +
    `; + +// 模板字符串中可以调用函数。字符串中调用函数的位置,将会显示函数执行后的返回值。 +function getName() { + return 'qianguyihao'; +} + +console.log(`www.${getName()}.com`); // 打印结果:www.qianguyihao.com + + +var str = 'smyhvae'; +console.log(str.length); // 获取字符串的长度 +console.log(str[2]); // 获取字符串中的第3个字符(下标为2的字符) + +``` + +### Number + +在 JS 中,只要是数,就是 Number 数值型的。无论整浮、浮点数(即小数)、无论大小、无论正负,都是 Number 类型的。 + +#### 数值范围 + +由于内存的限制,ECMAScript 并不能保存世界上所有的数值。 + +- 最大值:`Number.MAX_VALUE`,这个值为: 1.7976931348623157e+308 +- 最小值:`Number.MIN_VALUE`,这个值为: 5e-324 + +如果使用 Number 表示的变量超过了最大值,则会返回 Infinity。 + +- 无穷大(正无穷):Infinity +- 无穷小(负无穷):-Infinity + +注意:`typeof Infinity`的返回结果是 number。 + +#### NaN + +**NaN**:是一个特殊的数字,表示 Not a Number,非数值。在进行数值运算时,如果得不到正常结果,就会返回 NaN。 + +比如: + +```javascript +console.log('abc' / 18); //结果是NaN +``` + +**Undefined 和任何数值计算的结果为 NaN。NaN 与任何值都不相等,包括 NaN 本身。** + + + + + + + +### 数组 + +开头为0 + +- 获取字符串或者数组的长度,用arr.length属性 +- 数组添加头部元素: arr.unshift("a","b"....); 结尾用push()方法 +- 将数组元素连接成字符串: arr.join("连接符") +- 当数组的存储空间不够时,数组会自动扩容。其它编程语言中数组的大小是固定的,不会自动扩容。 +- 如果访问数组中不存在的索引时,不会报错,会返回undefined。 +- 数组可以存储不同类型数据,其它编程语言中数组只能存储相同类型数据。 +- 数组分配的存储空间不一定是连续的。其它语言数组分配的存储空间是连续的。 + +```javascript +let arr1 = []; // 创建一个空的数组 + +let arr2 = [1, 2, 3]; // 创建带初始值的数组 + +var cars=new Array(); +cars[0]="Audi"; +cars[1]="BMW"; +cars[2]="Volvo"; +// 或者 +var cars=new Array("Audi","BMW","Volvo"); +// 或者 +var cars=["Audi","BMW","Volvo"]; + +let sequence = [1, 1, 2, 3, 5, 8, 13]; +for (let i = 0; i < sequence.length; i++) { + console.log(sequence[i]); +} +sequence.length; +myArray.push('Cardiff'); +let removedItem = myArray.pop(); +``` + + + +数组解构赋值,代码举例: + +```js +let [a, b, c] = [1, 2, [3, 4]]; +``` + +判断是否为数组 + +```javascript +布尔值 = Array.isArray(被检测的数组); +``` + + + +```scala +const name = 'qianguyihao'; +console.log(Array.from(name)); // 打印结果是数组:["q","i","a","n","g","u","y","i","h","a","o"] +``` + + + +```scala +// ES5语法 +arr.forEach(function (currentItem, currentIndex, currentArray) { + console.log(currentValue); +}); + +// ES6语法 +arr.forEach((currentItem, currentIndex, currentArray) => { + console.log(currentValue); +}); + +参数1:当前正在遍历的元素 + +参数2:当前正在遍历的元素的索引 + +参数3:正在遍历的数组 + +注意,forEach() 没有返回值。也可以理解成:forEach() 的返回值是 undefined。 +forEach() 通过参数 2、参数 3 修改原数组:(标准做法。 如果你想在遍历数组的同时,去改变数组里的元素内容,那么,最好是用 map() 方法来做,不要用 forEach()方法,避免出现一些低级错误。 + +// ES6语法 +const newArr = arr.map((currentItem, currentIndex, currentArray) => { + return newItem; +}); +``` + + + +### 对象 + +见面向对象 + +### Undefined 和 Null + +Undefined 这个值表示变量不含有值。 + +- case1:变量已声明,未赋值时 + +- case2:变量未声明(未定义)时, 如果用 `typeof` 检查这个变量时,会返回 `undefined` + +- case3:函数无返回值时, 如果一个函数没有返回值,那么,这个函数的返回值就是 undefined。 + + 或者,也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 `return undefined`。 + +- case4:调用函数时,未传参。调用函数时,如果没有传参,那么,这个参数的值就是 undefined。 + + + +可以通过将变量的值设置为 null 来清空变量。**null 虽然是一个单独的数据类型,但null 相当于是一个 object,只不过地址为空(空指针)而已**。 + + + +undefined 实际上是由 null 衍生出来的,所以`null == undefined`的结果为 true。 + +- 任何值和 null 运算,null 可看做 0 运算。 +- 任何数据类型和 undefined 运算都是 NaN。 + + + +### 动态类型 + +JavaScript 是一种“动态类型语言”,这意味着不同于其他一些语言 (译者注:如 C、JAVA),您不需要指定变量将包含什么数据类型(例如 number 或 string) + +例如,如果你声明一个变量并给它一个带引号的值,浏览器就会知道它是一个字符串: + +``` +let myString = 'Hello'; +``` + +### 基本包装类型 + +我们都知道,js 中的数据类型包括以下几种。 + +- 基本数据类型:String、Number、Boolean、Null、Undefined +- 引用数据类型:Object + +JS 为我们提供了三个**基本包装类**: + +- String():将基本数据类型字符串,转换为 String 对象。 +- Number():将基本数据类型的数字,转换为 Number 对象。 +- Boolean():将基本数据类型的布尔值,转换为 Boolean 对象。 + +通过上面这这三个包装类,我们可以**将基本数据类型的数据转换为对象**。 + + + +```javascript +let str1 = 'qianguyihao'; +let str2 = new String('qianguyihao'); + +let num = new Number(3); + +let bool = new Boolean(true); + +console.log(typeof str1); // 打印结果:string +console.log(typeof str2); // 注意,打印结果:object +``` + +**需要注意的是**:我们在实际应用中一般不会使用基本数据类型的**对象**。如果使用基本数据类型的对象,在做一些比较时可能会带来一些**不可预期**的结果。 + + + +当我们对一些基本数据类型的值去调用属性和方法时,JS引擎会**临时使用包装类将基本数据类型转换为引用数据类型**(即“隐式类型转换”),这样的话,基本数据类型就有了属性和方法,然后再调用对象的属性和方法;调用完以后,再将其转换为基本数据类型。 + +比如str.length + +## 运算符 + +### typeof 操作符 + +你可以使用 typeof 操作符来查看 JavaScript 变量的数据类型。 +请注意: + + NaN 的数据类型是 number + 数组(Array)的数据类型是 object + 日期(Date)的数据类型为 object + null 的数据类型是 object + 未定义变量的数据类型为 undefined + +实例 + +```javascript +typeof "John" // 返回 string +typeof 3.14 // 返回 number +typeof NaN // 返回 number +typeof false // 返回 boolean +typeof [1,2,3,4] // 返回 object +typeof {name:'John', age:34} // 返回 object +typeof new Date() // 返回 object +typeof function () {} // 返回 function +typeof myCar // 返回 undefined (如果 myCar 没有声明) +typeof null // 返回 object +``` + +### constructor 属性 + +constructor 属性返回所有 JavaScript 变量的构造函数。 + +实例 + +```javascript +"John".constructor // 返回函数 String() { [native code] } +(3.14).constructor // 返回函数 Number() { [native code] } +false.constructor // 返回函数 Boolean() { [native code] } +[1,2,3,4].constructor // 返回函数 Array() { [native code] } +{name:'John', age:34}.constructor // 返回函数 Object() { [native code] } +new Date().constructor // 返回函数 Date() { [native code] } +function () {}.constructor // 返回函数 Function(){ [native code] } + +function isArray(myArray) { + return myArray.constructor.toString().indexOf("Array") > -1; +} + +function isDate(myDate) { + return myDate.constructor.toString().indexOf("Date") > -1; +} +``` + +### 比较 + +| 运算符 | 名称 | 作用 | 示例 | +| :----- | :--------- | :----------------------- | :------------ | +| `===` | 严格等于 | 测试左右值是否相同 | `5 === 2 + 4` | +| `!==` | 严格不等于 | 测试左右值是否**不**相同 | `5 !== 2 + 3` | + +```text +== 等于 +=== 全等于 +``` + + + +`==`这个符号并不严谨,会做隐式转换,将不同的数据类型,**转为相同类型**进行比较。例如: + +```javascript +console.log('6' == 6); // 打印结果:true。这里的字符串"6"会先转换为数字6,然后再进行比较 +console.log(true == '1'); // 打印结果:true +console.log(0 == -0); // 打印结果:true + +console.log(null == 0); // 打印结果:false +``` + +```javascript +console.log(undefined == null); //打印结果:true。 +``` + + + +**全等在比较时,不会做类型转换**。如果要保证**完全等于**(即:不仅要判断取值相等,还要判断数据类型相同),我们就要用三个等号`===`。例如: + +```javascript +console.log('6' === 6); //false +console.log(6 === 6); //true +``` + +### 逻辑运算符 + +- `&&` — 逻辑与; 使得并列两个或者更多的表达式成为可能,只有当这些表达式每一个都返回`true`时,整个表达式才会返回`true.` +- `||` — 逻辑或; 当两个或者更多表达式当中的任何一个返回 `true` 则整个表达式将会返回 `true`. +- ! — 逻辑非; 对一个布尔值取反,非 true 返回 false,非 false 返回 true. + +## 类型转换 + +### 显式类型转换 + +- toString() +- String() +- Number() +- parseInt(string) +- parseFloat(string) +- Boolean() + +### 隐式类型转换 + +- isNaN () +- 自增/自减运算符:`++`、`—-` +- 正号/负号:`+a`、`-a` +- 加号:`+` +- 运算符:`-`、`*`、`/` + + + +### 将数字转换为字符串 + +```javascript +全局方法 String() 可以将数字转换为字符串。 + +String(x) // 将变量 x 转换为字符串并返回 +String(123) // 将数字 123 转换为字符串并返回 +String(100 + 23) // 将数字表达式转换为字符串并返回 + +Number 方法 toString() 也是有同样的效果。 + +实例 +x.toString() +(123).toString() +(100 + 23).toString() + +``` + +### 将布尔值转换为字符串 + +```javascript +全局方法 String() 可以将布尔值转换为字符串。 + +String(false) // 返回 "false" +String(true) // 返回 "true" +Boolean 方法 toString() 也有相同的效果。 + +false.toString() // 返回 "false" +true.toString() // 返回 "true" +``` + +### 将日期转换为字符串 + +```javascript +Date() 返回字符串。 + +Date() // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) +全局方法 String() 可以将日期对象转换为字符串。 + +String(new Date()) // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) +Date 方法 toString() 也有相同的效果。 + +实例 +obj = new Date() +obj.toString() // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) +``` + +### 将字符串转换为数字 + +```javascript +全局方法 Number() 可以将字符串转换为数字。 + +字符串包含数字(如 "3.14") 转换为数字 (如 3.14). + +空字符串转换为 0。 + +其他的字符串会转换为 NaN (不是个数字)。 + +Number("3.14") // 返回 3.14 +Number(" ") // 返回 0 +Number("") // 返回 0 +Number("99 88") // 返回 NaN + +一元运算符 + +Operator + 可用于将变量转换为数字: +``` + +### 将布尔值转换为数字 + +```javascript +全局方法 Number() 可将布尔值转换为数字。 + +Number(false) // 返回 0 +Number(true) // 返回 1 +``` + +### 将日期转换为数字 + +```javascript +全局方法 Number() 可将日期转换为数字。 + +d = new Date(); +Number(d) // 返回 1404568027739 +日期方法 getTime() 也有相同的效果。 + +d = new Date(); +d.getTime() // 返回 1404568027739 +``` + + + +### 布尔值情况列举【重要】 + +其他的数据类型都可以转换为 Boolean 类型。无论是隐式转换,还是显示转换,转换结果都是一样的。有下面几种情况: + +(1)情况一:数字 --> 布尔。 0 和 NaN的转换结果 false,其余的都是 true。比如 `Boolean(NaN)`的结果是 false。 + +(2)情况二:字符串 ---> 布尔。空串的转换结果是false,其余的都是 true。全是空格的字符串,转换结果也是 true。字符串`'0'`的转换结果也是 true。 + +(3)情况三:null 和 undefined 都会转换为 false。 + +(4)情况四:引用数据类型会转换为 true。注意,空数组`[]`和空对象`{}`,**转换结果也是 true**,这一点,很多人都不知道。 + + + + + + + +## 面向对象 + +### 对象 + +对象由花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔 + +### 对象创建 + +```javascript +// 方式1:字面量 对象的字面量就是一个{}。里面的属性和方法均是键值对. key可以也可以没有引号 +var person={ + firstname : "Bill", + lastname : "Gates", + id : 5566 +}; + +// 方式2:工厂模式 new Object()。弊端: 使用工厂方法创建的对象,使用的构造函数都是 Object。所以创建的对象都是 Object 这个类型,就导致我们无法区分出多种不同类型的对象。 + +person=new Object(); +person.firstname="Bill"; +person.lastname="Gates"; +person.age=56; +person.eyecolor="blue"; +//或者 +person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"}; +//或者对象构造器 构造函数 推荐 +function person(firstname,lastname,age,eyecolor) +{ + this.firstname=firstname; + this.lastname=lastname; + this.age=age; + this.eyecolor=eyecolor; +} +var myFather=new person("Bill","Gates",56,"blue"); +``` + +**构造函数**:是一种特殊的函数,主要用来创建和初始化对象,也就是为对象的成员变量赋初始值。它与 `new` 一起使用才有意义。 + +- 构造函数的创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。 +- 构造函数和普通函数的区别就是**调用方式**的不同:普通函数是直接调用,而构造函数需要使用 new 关键字来调用。 + +**this 的指向也有所不同**: + +- 1.以函数的形式调用时,this 永远都是 window。比如`fun();`相当于`window.fun();` +- 2.以方法的形式调用时,this 是调用方法的那个对象 +- 3.以构造函数的形式调用时,this 是新创建的实例对象 + + + + + +### 访问对象属性 + +对象属性有两种寻址方式: + +```javascript + name=person.lastname; + name=person["lastname"]; +``` + +### 对象方法 + +```javascript +var person = { + firstName: "John", + lastName : "Doe", + id : 5566, + fullName : function() + { + return this.firstName + " " + this.lastName; + } +}; +document.getElementById("demo").innerHTML = person.fullName(); +``` + +### 使用对象方法 + +对象方法通过添加 () 调用 (作为一个函数)。 + +```javascript +name = person.fullName(); +``` + +### instanceof + +使用 instanceof 可以检查**一个对象是否为一个类的实例**。 + +**语法如下**: + +```javascript +对象 instanceof 构造函数; +``` + +### 浅拷贝 + +ES6 给我们提供了新的语法糖,通过 `Object.assgin()` 可以实现**浅拷贝**。 + +`Object.assgin()` 在日常开发中,使用得相当频繁,非掌握不可。 + +**语法**: + +```js +// 语法1 +obj2 = Object.assgin(obj2, obj1); + +// 语法2 +Object.assign(目标对象, 源对象1, 源对象2...); +``` + +**解释**:将`obj1` 拷贝给 `obj2`。执行完毕后,obj2 的值会被更新。 + +**作用**:将 obj1 的值追加到 obj2 中。如果对象里的属性名相同,会被覆盖。 + +从语法2中可以看出,Object.assign() 可以将多个“源对象”拷贝到“目标对象”中。 + + + +深拷贝其实就是将浅拷贝进行递归。 + +### 原型 + +原型就是父类 继承的父类。 + +## 函数 + +### 定义 + +```javascript +// 传统定义函数方式 +function myFunction(a,b) +{ + if (a>b) + { + return; + } + x=a+b +} + +function Test () { + // +} + +const Test = function () { + // +} + +// 使用箭头函数定义函数时可以省略 function 关键字 +const Test = (...params) => { + // +} + +// 该函数只有一个参数时可以简写成: +const Test = param => { + return param; +} + +console.log(Test('hello')); // hello + +// 很少用, +const 变量名/函数名 = new Function('形参1', '形参2', '函数体'); +const fun3 = new Function('a', 'b', 'console.log("我是函数内部的内容"); console.log(a + b);'); +``` + +### 实际参数和形式参数的个数,可以不同 + +实际参数和形式参数的个数,可以不同。调用函数时,解析器不会检查实参的数量。 + +- 如果实参个数 > 形参个数,则末尾的实参是多余的,不会被赋值,因为没有形参能接收它。 +- 如果实参个数 < 形参个数,则末尾的形参是多余的,值是 undefined,因为它没有接收到实参。(undefined参与运算时,表达式的运算结果为NaN) + +函数的实参可以是任意的数据类型。调用函数时,解析器不会检查实参类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型检查。 + +函数体内可以没有返回值,也可以根据需要加返回值。语法格式:`return 函数的返回值`。 + + + +### 类数组对象 arguments + +在调用函数时,浏览器每次都会传递进两个隐含的参数: + +- 1.函数的上下文对象 this +- 2.**封装实参的对象** arguments + +```javascript +function foo() { + console.log(arguments); + console.log(typeof arguments); +} + +foo('a', 'b'); +``` + +函数内的 arguments 是一个**类数组对象**,里面存储的是它接收到的**实参列表**。所有函数都内置了一个 arguments 对象,有个讲究的地方是:只有函数才有arguments。 + +具体来说,在调用函数时,我们所传递的实参都会在 arguments 中保存。**arguments 代表的是所有实参**。 + +arguments 的展示形式是一个**伪数组**。意思是,它和数组有点像,但它并不是数组。它具有以下特点: + +- 可以进行遍历;具有数组的 length 属性,可以获取长度。 +- 可以通过索引(从0开始计数)存储数据、获取和操作数据。比如,我们可以通过索引访问某个实参。 +- 不能调用数组的方法。比如push()、pop() 等方法都没有。 +- 即使我们不定义形参,也可以通过 arguments 来获取实参:arguments[0] 表示第一个实参、arguments[1] 表示第二个实参,以此类推。 +- 当我们不确定有多少个参数传递的时候,可以用 **arguments** 来获取。 + +### 立即执行函数 + +```scala +(function() { + // 函数体 +})(a, b); +``` + +即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。 + + + +### This指向 + +> - [this指向](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/25-this%E6%8C%87%E5%90%91.html#%E6%89%A7%E8%A1%8C%E6%9C%9F%E4%B8%8A%E4%B8%8B%E6%96%87) + +解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是 this,this 指向的是一个对象,这个对象我们称为函数执行的 上下文对象。 + + + +在ES5语法中,根据函数的调用方式的不同,this 会指向不同的对象: + +1、以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如`fun();`相当于`window.fun();` + +2、以方法的形式调用时,this 指向调用方法的那个对象 + +3、以构造函数的形式调用时,this 指向实例对象 + +4、以事件绑定函数的形式调用时,this 指向**绑定事件的对象** + +5、使用 call 和 apply 调用时,this 指向指定的那个对象 + + + +```scala +//以函数形式调用,this是window +fun(); //可以理解成 window.fun() + +function fun() { + console.log(this); + console.log(this.name); +} + +打印结果: + Window + 全局的name属性 + + + +//以方法的形式调用,this是调用方法的对象 +obj2.sayName(); +``` + + + +ES6 中的箭头函数并不使用上面的准则,而是会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。 + + + +#### call() 方法的作用 + +可以通过函数.call方法调用函数。 + +call() 方法的作用:可以**调用**一个函数,与此同时,它还可以改变这个函数内部的 this 指向。 + +call() 方法的另一个应用:**可以实现继承**。之所以能实现继承,其实是利用了上面的作用。 + +语法: + +```js +fn1.call(想要将this指向哪里, 函数实参1, 函数实参2); +``` + +备注:第一个参数中,如果不需要改变 this 指向,则传 null。 + + + +```js +fn1.call(this); // this的指向并没有被改变,此时相当于 fn1(); +``` + + + +通过 call() 实现继承: + +```js +// 给 Father 增加 name 和 age 属性 +function Father(myName, myAge) { + this.name = myName; + this.age = myAge; +} + +function Son(myName, myAge) { + // 【下面这一行,重要代码】 + // 通过这一步,将 father 里面的 this 修改为 Son 里面的 this;另外,给 Son 加上相应的参数,让 Son 自动拥有 Father 里的属性。最终实现继承 + Father.call(this, myName, myAge); +} +``` + + + + + +#### apply() 方法的作用 + +apply() 方法的作用:可以**调用**一个函数,与此同时,它还可以改变这个函数内部的 this 指向。这一点,和 call()类似。 + +apply() 方法的应用: 由于 apply()需要传递数组 + + + +语法: + +```js +fn1.apply(想要将this指向哪里, [函数实参1, 函数实参2]); +``` + + + +备注:第一个参数中,如果不需要改变 this 指向,则传 null。 + +到这里可以看出, call() 和 apply() 方法的作用是相同的。唯一的区别在于,apply() 里面传入的**实参,必须是数组(或者伪数组)**。 + +主要是个Math.max这样用的。使用场景较少,知道即可。 + + + +#### bind() 方法的作用【常用】 + +bind() 方法**不会调用函数**,但是可以改变函数内部的 this 指向。 + +把call()、apply()、bind()这三个方法做一下对比,你会发现:实际开发中, bind() 方法使用得最为频繁。如果有些函数,我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用 bind() 是最为合适的。 + +语法: + +```js +新函数 = fn1.bind(想要将this指向哪里, 函数实参1, 函数实参2); +``` + + + +参数: + +- 第一个参数:在 fn1 函数运行时,指定 fn1 函数的this 指向。如果不需要改变 this 指向,则传 null。 +- 其他参数:fn1 函数的实参。 + +解释:它不会调用 fn1 函数,但会返回 由指定this 和指定实参的**原函数拷贝**。可以看出, bind() 方法是有返回值的。 + + + +### 闭包(closure) + +**闭包**:如果**外部作用域**有权访问另外一个**函数内部**的**局部变量**时,那就产生了闭包。这个内部函数称之为闭包函数。注意,这里强调的是访问**局部变量**。 + +闭包代码举例: + +```js +function fun1() { + const a = 10; + return function fun2() { + console.log(a); + }; +} +fun1(); +// 调用外部函数,就能得到内部函数,并用 变量 result 接收 +const result = fun1(); +// 在 fun1函数的外部,执行了内部函数 fun2,并访问到了 fun2的内部变量a +result(); // 10 +``` + +## if和循环 + +### if else + +```javascript +if (time<10) +{ + x="Good morning"; +} +else if (time<20) +{ + x="Good day"; +} +else +{ + x="Good evening"; +} +``` + +### swith语法 + +```javascript +var day=new Date().getDay(); +switch (day) +{ + case 6: + x="Today it's Saturday"; + break; + case 0: + x="Today it's Sunday"; + break; + default: + x="Looking forward to the Weekend"; +} +``` + +### for循环 + +```javascript +for (var i=0;i"); +} + +for (var i=0,len=cars.length; i"); +} +//循环遍历对象的属性 +var person={fname:"John",lname:"Doe",age:25}; +for (x in person) +{ + txt=txt + person[x]; +} +forin不推荐用在数组上。数组应该用forof +for(let value of arr) { + console.log(value); +} +``` + +### while循环 + +```javascript +while (i<5) +{ + x=x + "The number is " + i + "
    "; + i++; +} + +do +{ + x=x + "The number is " + i + "
    "; + i++; +} +while (i<5); +//break; continue; +``` + +## 作用域 + +> [参考](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/23-%E4%BD%9C%E7%94%A8%E5%9F%9F%E3%80%81%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87%E3%80%81%E5%87%BD%E6%95%B0%E6%8F%90%E5%8D%87.html#%E4%BD%9C%E7%94%A8%E5%9F%9F%EF%BC%88scope%EF%BC%89%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E5%88%86%E7%B1%BB) + +直接编写在 script 标签中的 JS 代码,都在全局作用域。全局作用域在页面打开时创建,在页面关闭时销毁。 + +在全局作用域中有一个全局对象 window,它代表的是浏览器的窗口,由浏览器创建,我们可以直接使用。相关知识点如下: + +- 创建的**变量**都会作为 window 对象的属性保存。比如在全局作用域内写 `const a = 100`,这里的 `a` 等价于 `window.a`。 +- 创建的**函数**都会作为 window 对象的方法保存。 +- 无论是在函数外还是函数内,变量如果未经声明就赋值(意思是,如果不加var/let/const),这个变量是**全局变量**。 + +JS在解析代码之前,有一个“**预处理**(预解析)”阶段,将当前 JS 代码中所有变量的定义和函数的定义,放到所有代码的最前面。 + +- 使用 var 关键字声明的变量( 比如 `var a = 1`),**会在所有的代码执行之前被声明**(但是不会赋值)。但是如果声明变量时不是用 var 关键字(比如直接写`a = 1`),则变量不会被声明提前。 +- 使用`函数声明`的形式创建的函数`function foo(){}`,**会被声明提前**。 +- 在JS的规则中,函数提升优先于变量提升。 + + + +在函数作用域中,也有声明提前的现象: + +- 函数中,使用 var 关键字声明的变量,会在函数中所有代码执行之前被提前声明。 +- 函数中,没有 var 声明的变量都是**全局变量**,且并不会被提前声明。 + + + +在 ES5 中没有块级作用域 + +## try catch + +```javascript +try +{ + throw exception; + adddlert("Welcome guest!"); +} +catch(err) +{ + txt="There was an error on this page.\n\n"; + txt+="Error description: " + err.message + "\n\n"; + txt+="Click OK to continue.\n\n"; + alert(txt); +} +``` + +## 事件 + +```javascript +//DOM事件 +//

    请点击该文本

    this就是自己的id +//

    请点击该文本

    +document.getElementById("myBtn").onclick=function(){displayDate()}; +// +//分配事件 +document.getElementById("myBtn").onclick=function(){displayDate()}; + +//创建元素 +var para=document.createElement("p"); +var node=document.createTextNode("这是新段落。"); +para.appendChild(node); +var element=document.getElementById("div1"); +element.appendChild(para); +//删除元素 +var parent=document.getElementById("div1"); +var child=document.getElementById("p1"); +parent.removeChild(child); +//正则表达式 RegExp +``` + + + + + +## 原型和原型链 + +## 其他 小结 + +- 字符串转换为数字用Number(str)或者parseInt(str)/parseFloat(str)方法 +- 数字转换为字符串用var.toString()方法 +- NaN: not a number +- 可以直接使用Math.方法名,如max(...), +- +- ByName()只用于表单元素,一般是单选和复选框 +- 两个特殊方法,document.title, document.body +- html中onclick="f()", js中obj.click=f,前者是调用属性,后者是给属性赋值 +- 只执行最后一次window.onload=function(){ } +- 事件绑定: obj.addEventListener("click",funcion,false); + + + +# jquery教程 + +## 语法 +```javacript + $(document).ready(function(){ + + // 开始写 jQuery 代码... + + }); + + 简洁写法(与以上写法效果相同): + $(function(){ + + // 开始写 jQuery 代码... + + }); + +#id 选择器 +$("#test") +.class 选择器 +$(".test") +$("p").css("background-color","red"); +$(this) 选取当前 HTML 元素 +``` + +## 事件 +```javascript +$("p").click(function(){ + // action goes here!! +}); +$("p").click(); +$("p").dblclick(function(){ + $(this).hide(); +}); + +jQuery hide() 和 show() +通过 jQuery,您可以使用 toggle() 方法来切换 hide() 和 show() 方法。 + +$(selector).toggle(speed,callback); +可选的 speed 参数规定隐藏/显示的速度,可以取以下值:"slow"、"fast" 或毫秒。 +可选的 callback 参数是 toggle() 方法完成后所执行的函数名称。 +可选的 callback 参数,具有以下三点说明: +$(selector)选中的元素的个数为n个,则callback函数会执行n次 +callback函数名后加括号,会立刻执行函数体,而不是等到显示/隐藏完成后才执行 +callback既可以是函数名,也可以是匿名函数 + +Callback 函数在当前动画 100% 完成之后执行。 + +通过 jQuery,可以把动作/方法链接在一起。 +Chaining 允许我们在一条语句中运行多个 jQuery 方法(在相同的元素上)。 +$("#p1").css("color","red").slideUp(2000).slideDown(2000); +$("#p1").css("color","red") + .slideUp(2000) + .slideDown(2000); + +三个简单实用的用于 DOM 操作的 jQuery 方法: +text() - 设置或返回所选元素的文本内容 +html() - 设置或返回所选元素的内容(包括 HTML 标记) +val() - 设置或返回表单字段的值 + +$("#btn1").click(function(){ + alert("Text: " + $("#test").text()); +}); +$("#btn2").click(function(){ + alert("HTML: " + $("#test").html()); +}); +$("#btn1").click(function(){ + alert("Value: " + $("#test").val()); +}); + +下面的例子演示如何通过 jQuery val() 方法获得输入字段的值: + + + + +

    名称:

    + + + + +$("button").click(function(){ + alert($("#w3s").attr("href")); +}); + +下面的例子演示如何通过 text()、html() 以及 val() 方法来设置内容: +$("#btn1").click(function(){ + $("#test1").text("Hello world!"); +}); +$("#btn2").click(function(){ + $("#test2").html("Hello world!"); +}); +$("#btn3").click(function(){ + $("#test3").val("Dolly Duck"); +}); +text()、html() 以及 val() 的回调函数 + +上面的三个 jQuery 方法:text()、html() 以及 val(),同样拥有回调函数。 +回调函数由两个参数:被选元素列表中当前元素的下标,以及原始(旧的)值。 +然后以函数新值返回您希望使用的字符串。 +$("#btn1").click(function(){ + $("#test1").text(function(i,origText){ + return "Old text: " + origText + " New text: Hello world! + (index: " + i + ")"; + }); +}); + +$("button").click(function(){ + $("#w3s").attr("href","//www.w3cschool.cn/jquery"); +}); + +append() - 在被选元素内部的结尾插入指定内容 +prepend() - 在被选元素内部的开头插入指定内容 +after() - 在被选元素之后插入内容 +before() - 在被选元素之前插入内容 + +$("p").append("Some appended text."); + +$("#div1").remove(); + +$("#div1").empty(); + +addClass() - 向被选元素添加一个或多个类 +removeClass() - 从被选元素删除一个或多个类 +toggleClass() - 对被选元素进行添加/删除类的切换操作 +css() - 设置或返回样式属性 + +$("button").click(function(){ + $("h1,h2,p").addClass("blue"); + $("div").addClass("important"); +}); + +您也可以在 addClass() 方法中规定多个类: +$("button").click(function(){ + $("#div1").addClass("important blue"); +}); + +下面的例子将返回首个匹配元素的 background-color 值: +$("p").css("background-color"); + +$("p").css("background-color","yellow"); + +$("p").css({"background-color":"yellow","font-size":"200%"}); + + + +旧版本 +$("").hide() +//必须 +$(document).ready(function(){ + +--- jQuery functions go here ---- + +}); + +jQuery 语法实例 +$(this).hide() +演示 jQuery hide() 函数,隐藏当前的 HTML 元素。 +$("#test").hide() +演示 jQuery hide() 函数,隐藏 id="test" 的元素。 +$("p").hide() +演示 jQuery hide() 函数,隐藏所有

    元素。 +$(".test").hide() +演示 jQuery hide() 函数,隐藏所有 class="test" 的元素。 + +jQuery 元素选择器 +jQuery 使用 CSS 选择器来选取 HTML 元素。 +$("p") 选取

    元素。 +$("p.intro") 选取所有 class="intro" 的

    元素。 +$("p#demo") 选取所有 id="demo" 的

    元素。 +jQuery 属性选择器 +jQuery 使用 XPath 表达式来选择带有给定属性的元素。 +$("[href]") 选取所有带有 href 属性的元素。 +$("[href='#']") 选取所有带有 href 值等于 "#" 的元素。 +$("[href!='#']") 选取所有带有 href 值不等于 "#" 的元素。 +$("[href$='.jpg']") 选取所有 href 值以 ".jpg" 结尾的元素。 +jQuery CSS 选择器 +jQuery CSS 选择器可用于改变 HTML 元素的 CSS 属性。 +下面的例子把所有 p 元素的背景颜色更改为红色: +$("p").css("background-color","red"); + +$(document).ready(function) 将函数绑定到文档的就绪事件(当文档完成加载时) +$(selector).click(function) 触发或将函数绑定到被选元素的点击事件 +$(selector).dblclick(function) 触发或将函数绑定到被选元素的双击事件 +$(selector).focus(function) 触发或将函数绑定到被选元素的获得焦点事件 +$(selector).mouseover(function) 触发或将函数绑定到被选元素的鼠标悬停事件 + +$("#btn1").click(function(){ + $("#test1").text("Hello world!"); +}); +$("#btn2").click(function(){ + $("#test2").html("Hello world!"); +}); +$("#btn3").click(function(){ + $("#test3").val("Dolly Duck"); +}); +//回调函数 +$("#btn1").click(function(){ + $("#test1").text(function(i,origText){ + return "Old text: " + origText + " New text: Hello world! + (index: " + i + ")"; + }); +}); + +$("button").click(function(){ + $("#w3s").attr("href","http://www.w3school.com.cn/jquery"); +}); +//同时设置多个属性 +$("button").click(function(){ + $("#w3s").attr({ + "href" : "http://www.w3school.com.cn/jquery", + "title" : "W3School jQuery Tutorial" + }); +}); + +$("p").append("Some appended text."); +$("p").prepend("Some prepended text."); + +function appendText() +{ +var txt1="

    Text.

    "; // 以 HTML 创建新元素 +var txt2=$("

    ").text("Text."); // 以 jQuery 创建新元素 +var txt3=document.createElement("p"); // 以 DOM 创建新元素 +txt3.innerHTML="Text."; +$("p").append(txt1,txt2,txt3); // 追加新元素 +} + +$("img").after("Some text after"); +$("img").before("Some text before"); + +$("#div1").remove(); +$("#div1").empty(); +$("p").remove(".italic"); + +$("button").click(function(){ + $("h1,h2,p").addClass("blue"); + $("div").addClass("important"); +}); + +$("button").click(function(){ + $("h1,h2,p").removeClass("blue"); +}); + +$("p").css("background-color","yellow"); +$("p").css({"background-color":"yellow","font-size":"200%"}); +``` + +# Jquery淘汰 + +[参考](https://www.xiejiahe.com/blog/detail/59b35ad615c192bd11b90469) + + + +# ajax教程 + +## ajax请求数据 +### get +```javascript + var xmlhttp=new XMLHttpRequest(); + xmlhttp.open("GET","/test/GetSearchTips?sear="+thisnode.value,true); + xmlhttp.send(); +``` + +### post +```javascript + xmlhttp.open("POST","/myservlet",true); + xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); + xmlhttp.send("name=mafulong&age=14"); +``` + +## 后台处理数据 +```java + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { +// super.doGet(req, resp); +// resp.setContentType("text/html"); + String name=req.getParameter("name"); + System.out.println(name); + resp.setContentType("application/json; charset=UTF-8"); + PrintWriter out=resp.getWriter(); +// out.print("fjdkfjdk"); +// out.println("

    abc

    "); + JSONObject jsonObject=new JSONObject(); + JSONArray jsonArray=new JSONArray(); + jsonArray.put(jsonObject); + jsonArray.put(jsonObject); + try{ + jsonObject.put("name","mafulong"); + jsonObject.put("age",18); + }catch (Exception e){ + e.printStackTrace(); + } +// out.print(jsonObject.toString()); + out.print(jsonArray.toString()); + + } +``` + +## 前端处理后端接收得数据 +```javascript + xmlhttp.onreadystatechange=function() + { + if (xmlhttp.readyState==4 && xmlhttp.status==200) + { + // document.getElementById("myDiv").innerHTML=xmlhttp.responseText; + alert("success "); + var data=xmlhttp.responseText; + var djson=JSON.parse(data); + var str=""; + for(var i=0;i"; + str+=djson[i].age+"
    "; + } + document.getElementById("myDiv").innerHTML=str; + } + } +``` + +# 千古 笔记 + +> [参考](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/03-%E5%B8%B8%E9%87%8F%E5%92%8C%E5%8F%98%E9%87%8F.html#%E5%8F%98%E9%87%8F%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E3%80%90%E9%87%8D%E8%A6%81%E3%80%91) + +变量不声明,直接赋值:(正常) ;只声明,不赋值:(注意,打印 undefined);不声明,不赋值,直接使用:(会报错) + + + +## 事件 + +### 事件 绑定 + +- [事件](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/35-%E4%BA%8B%E4%BB%B6%E7%AE%80%E4%BB%8B.html#%E4%BA%8B%E4%BB%B6%E7%AE%80%E4%BB%8B) + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212150003045.png) + + + +```scala + //这种事件绑定的方式,如果绑定多个,则后面的会覆盖掉前面的 + btn.onclick = function () { + console.log("事件1"); + } + // 方式2 + element.addEventListener('click', function () { + + }, false); + 参数解释: + + 参数1:事件名的字符串(注意,没有on) + + 参数2:回调函数:当事件触发时,该函数会被执行 + + 参数3:true表示捕获阶段触发,false表示冒泡阶段触发(默认)。如果不写,则默认为false。【重要】 +``` + +也可以传入event参数。 + +```html +small.onmousemove = function (event) {} +``` + +### 事件的传播和事件冒泡 + +- [事件的传播和事件冒泡](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/42-%E4%BA%8B%E4%BB%B6%E7%9A%84%E4%BC%A0%E6%92%AD%E5%92%8C%E4%BA%8B%E4%BB%B6%E5%86%92%E6%B3%A1.html#dom%E4%BA%8B%E4%BB%B6%E6%B5%81) + +事件传播的三个阶段是:事件捕获、事件冒泡和目标。 + +- 事件捕获阶段:事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。在这个过程中,默认情况下,事件相应的监听函数是不会被触发的。 +- 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。 +- 事件冒泡阶段:事件从事件目标 target 开始,从子元素往冒泡祖先元素冒泡,直到页面的最上一级标签。 + + + +捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素。 + + + +**事件冒泡**: 当一个元素上的事件被触发的时候(比如说鼠标点击了一个按钮),同样的事件将会在那个元素的所有**祖先元素**中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。 + +通俗来讲,冒泡指的是:**子元素的事件被触发时,父元素的同样的事件也会被触发**。取消冒泡就是取消这种机制。 + + + +以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。意思是,事件不会往父元素那里传递。 + +大部分情况下,冒泡都是有益的。当然,如果你想阻止冒泡,也是可以的。可以按下面的方法阻止冒泡。 + +```javascript + event.stopPropagation(); +``` + + + +可以用这个冒泡做一些[事件委托](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/43-%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98.html) 为父节点注册 click 事件,当子节点被点击的时候,click事件会从子节点开始**向父节点冒泡**。**父节点捕获到事件**之后,开始执行方法体里的内容:通过判断 event.target 拿到了被点击的子节点``。从而可以获取到相应的信息,并作处理。 + +## Dom + +> [DOM](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/36-DOM%E7%AE%80%E4%BB%8B%E5%92%8CDOM%E6%93%8D%E4%BD%9C.html#%E5%B8%B8%E8%A7%81%E6%A6%82%E5%BF%B5) + +**节点**(Node):构成 HTML 网页的最基本单元。网页中的每一个部分都可以称为是一个节点,比如:html标签、属性、文本、注释、整个文档等都是一个节点。 + +虽然都是节点,但是实际上他们的具体类型是不同的。常见节点分为四类: + +- 文档节点(文档):整个 HTML 文档。整个 HTML 文档就是一个文档节点。 +- 元素节点(标签):HTML标签。 +- 属性节点(属性):元素的属性。 +- 文本节点(文本):HTML标签中的文本内容(包括标签之间的空格、换行)。 + +节点的类型不同,属性和方法也都不尽相同。所有的节点都是Object。 + +**解析过程**: HTML加载完毕,渲染引擎会在内存中把HTML文档,生成一个DOM树,getElementById是获取内中DOM上的元素节点。然后操作的时候修改的是该元素的**属性**。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212151021206.png) + + + +### 有三种方式可以获取DOM节点 + +```javascript +var div1 = document.getElementById("box1"); //方式一:通过 id 获取 一个 元素节点(为什么是一个呢?因为 id 是唯一的) + +var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获取 元素节点数组,所以有s + +var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获取 元素节点数组,所以有s +``` + +document.querySelector()方法,querySelectorAll()方法选择器写法和css选择器写法一样,但效率低 + + + +DOM的节点并不是孤立的,因此可以通过DOM节点之间的相对关系对它们进行访问。 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212151022606.png) + + + +获取父节点 + +```javascript + 节点.parentNode +``` + +获取所有的子节点 + +(1)childNodes:标准属性。返回的是指定元素的子节点的集合(包括元素节点、所有属性、文本节点) +(2)children:非标准属性。返回的是指定元素的子元素节点的集合。【重要】它只返回HTML节点,甚至不返回文本节点。 + + + +创建节点的流程:createElement(), createTextNode(),把文本节点插入元素节点 appendChild() + + + +### 获取节点的属性值 + +**方式1**: + +```javascript + 元素节点.属性名; + 元素节点[属性名]; +``` + +**方式2**: + +```javascript + 元素节点.getAttribute("属性名称"); +``` + +### 设置节点的属性值 + +方式1举例:(设置节点的属性值) + +```javascript + myNode.src = "images/2.jpg" //修改src的属性值 + myNode.className = "image2-box"; //修改class的name +``` + +方式2: + +```javascript + 元素节点.setAttribute("属性名", "新的属性值"); +``` + +方式2举例:(设置节点的属性值) + +```javascript + myNode.setAttribute("src","images/3.jpg"); +``` + + + +**如果是节点的“原始属性”**(比如 普通标签的`class/className`属性、普通标签的`style`属性、普通标签的 title属性、img 标签的`src`属性、超链接的`href`属性等),**方式1和方式2是等价的**,可以混用。怎么理解混用呢?比如说:用 `div.title = '我是标题'`设置属性,用 `div.getAttribute('title')`获取属性,就是混用。 + +但如果是节点的“非原始属性”,**这两种方式不能交换使用**,get值和set值必须使用同一种方法。 + + + +### nodeType属性 + +这里讲一下nodeType属性。 + +- **nodeType == 1 表示的是元素节点**(标签) 。记住:在这里,元素就是标签。 +- nodeType == 2 表示是属性节点。 +- nodeType == 3 是文本节点。 + + + +## style属性的获取和修改 + +在DOM当中,如果想设置样式,有两种形式: + +- className(针对内嵌样式) +- style(针对行内样式) + +需要注意的是:style是一个对象,只能获取**行内样式**,不能获取内嵌的样式和外链的样式。 + +### 通过 js 读取/设置元素的样式 只能行内 + +```javascript + 元素.style["属性"]; //格式 + + box.style["width"]; //举例 + +box1.style.width = "300px"; +box1.style.backgroundColor = "red"; // 驼峰命名法 +``` + +备注:我们通过style属性设置的样式都是**行内样式**,而行内样式有较高的优先级。但是如果在样式中的其他地方写了`!important`,则此时`!important`会有更高的优先级。 + + + +### 通过 js 获取元素当前显示的样式 不只行内 + +(1)w3c的做法: + +```javascript + window.getComputedStyle("要获取样式的元素", "伪元素"); +``` + +两个参数都是必须要有的。参数二中,如果没有伪元素就用 null 代替(一般都传null)。 + +(2)IE和opera的做法: + +```javascript + obj.currentStyle; +``` + +注意: + +- 如果当前元素没有设置该样式,则获取它的默认值。 +- 该方法会返回一个**对象**,对象中封装了当前元素对应的样式,可以通过`对象.样式名`来读取具体的某一个样式。 +- 通过currentStyle和getComputedStyle()读取到的样式都是只读的,不能修改,如果要修改必须通过style属性。 + + + +```js + var div1 = document.getElementsByTagName("div")[0]; + + console.log(getStyle(div1, "width")); + console.log(getStyle(div1, "padding")); + console.log(getStyle(div1, "background-color")); + + /* + * 兼容方法,获取元素当前正在显示的样式。 + * 参数: + * obj 要获取样式的元素 + *. name 要获取的样式名 + */ + function getStyle(ele, attr) { + if (window.getComputedStyle) { + return window.getComputedStyle(ele, null)[attr]; + } + return ele.currentStyle[attr]; + } +``` + + + +## BOM + +- [BOM](https://web.qianguyihao.com/04-JavaScript%E5%9F%BA%E7%A1%80/45-BOM%E7%AE%80%E4%BB%8B%E5%92%8Cnavigator.userAgent&History&Location.html#%E5%B8%B8%E8%A7%81%E6%A6%82%E5%BF%B5) + +- **BOM**:浏览器对象模型(Browser Object Model),操作**浏览器部分功能**的API。比如让浏览器自动滚动。 + +```text +location.href = 'https://xxx'; +``` + +解释:获取当前页面的 url 路径(或者设置 url 路径);或者跳转到指定路径。 + +需要特别注意的是:window.location.href的赋值,并不会中断Javascript的执行立即进行页面跳转。因为 LocationChange 行为在浏览器内核中是起定时器异步执行的。异步执行的好处是为了防止代码调用过深,导致栈溢出,另外也是为了防止递归进入加载逻辑,导致状态紊乱,保证导航请求是顺序执行的。 + +解决办法:在 location.href 的下一行,加上 return 即可。意思是,执行了 location.href 之后,就不要再继续往下执行了。 + + + +```javascript + location.reload(); +``` + +解释:用于重新加载当前页面,作用和刷新按钮一样。 + +## 定时器 + +- setInterval():循环调用。将一段代码,**每隔一段时间**执行一次。(循环执行) +- setTimeout():延时调用。将一段代码,等待一段时间之后**再执行**。(只执行一次) + +每间隔一秒,将 数字 加1: + +```javascript + let num = 1; + setInterval(function () { + num ++; + console.log(num); + }, 1000); +``` + + + +定时器的返回值是作为这个定时器的**唯一标识**,可以用来清除定时器。具体方法是:假设定时器setInterval()的返回值是`参数1`,那么`clearInterval(参数1)`就可以清除定时器。 + +setTimeout()的道理是一样的。 + + + + + +## ES5严格模式 + +> [ES5中的严格模式](https://web.qianguyihao.com/05-JavaScript%E5%9F%BA%E7%A1%80%EF%BC%9AES6%E8%AF%AD%E6%B3%95/02-ES5%E4%B8%AD%E7%9A%84%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F.html#es%E7%9A%84%E5%87%A0%E4%B8%AA%E9%87%8D%E8%A6%81%E7%89%88%E6%9C%AC) + +使用 + +- 针对整个文件:将`use strict`放在文件的第一行,则整个文件将以严格模式运行。 +- 针对单个函数:将`use strict`放在函数体的第一行,则整个函数以严格模式运行。 + +语法和行为改变 + +- 必须用var声明变量 + +- 禁止自定义的函数中的this指向window + +- 创建eval作用域 + +- 对象不能有重名的属性 + +- 为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。 + + + + + +在 ES6 语法及之后的版本里,可以使用 `const`、`let`关键字来定义一个变量 + +```js +const name; // 定义一个常量 + +let age; // 定义一个变量 +``` + +如果你想定义一个常量,就用 const;如果你想定义一个变量,就用 let。 + + + +## Json + +```scala +var a = { + b: 3, + c: "dd", +}; +console.log(a); +b = JSON.stringify(a); +console.log(b); + +console.log(JSON.parse(b)); +``` + +1、js对象(数组) --> json对象(数组): + +```javascript + JSON.stringify(obj/arr) +``` + +2、json对象(数组) --> js对象(数组): + +```javascript + JSON.parse(json)1 +``` + +上面这两个方法是ES5中提供的。不需要Import新的包,js就支持 + +## 同源和跨域 + +> [同源和跨域](https://web.qianguyihao.com/06-JavaScript%E5%9F%BA%E7%A1%80%EF%BC%9A%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/04-%E5%90%8C%E6%BA%90%E5%92%8C%E8%B7%A8%E5%9F%9F.html#%E5%90%8C%E6%BA%90%E5%92%8C%E8%B7%A8%E5%9F%9F) + +同源策略是浏览器的一种安全策略,所谓同源是指,域名,协议,端口完全相同。 + +从我自己的网站访问别人网站的内容,就叫跨域。 + + + +  同源策略限制以下几种行为: + +- Cookie、LocalStorage 和 IndexDB 无法读取 +- DOM和JS对象无法获得 +- AJAX 请求不能发送 + + + +指当前域名不能请求其它域名的内容。解决跨域常见几种方式 + +- iframe:处于安全性考虑,浏览器的开发厂商已经禁止了这种方式。 +- JSONP:script 标签的 src 属性传递数据。 +- 跨域资源共享(CORS) [参考](https://juejin.cn/post/6844903882083024910#heading-5) + - CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 + - 请求里有Origin, 服务端会校验。 + - 请求header里有**Access-Control-Request-**xx + - 预检请求可以只有一次 + +## Promise + +> [Promise](https://web.qianguyihao.com/06-JavaScript%E5%9F%BA%E7%A1%80%EF%BC%9A%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/05-Promise%E5%85%A5%E9%97%A8%E8%AF%A6%E8%A7%A3.html) + +**回调的缺点** + +回调的写法比较直观,不需要 return,层层嵌套即可。但也存在两个问题: + +- 如果嵌套过深,则会出现**回调地狱**的问题。 +- 不同的函数,回调的参数,在写法上可能不一致,导致不规范、且需要**单独记忆**。 + +在 ES5 中,当进行多层嵌套回调时,会导致代码层次过多,很难进行后续维护和二次开发;而且会导致**回调地狱**的问题。ES6 中的 Promise 就可以解决这些问题。 + + + +ES6 中的 Promise 是异步编程的一种方案。从语法上讲,Promise 是一个对象,它可以获取异步操作的消息。 + +Promise 对象, 可以**用同步的表现形式来书写异步代码**(也就是说,代码看起来是同步的,但本质上的运行过程是异步的)。使用 Promise 主要有以下好处: + +- 1、可以很好地解决**回调地狱**的问题(避免了层层嵌套的回调函数)。 +- 2、语法简洁、可读性强,便于后期维护。 +- 3、Promise 对象提供了简洁的 API,使得管理异步操作更加容易。比如**多任务等待合并**。 + + + +- [使用 Promise](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises) + +Promise 很棒的一点就是**链式调用**(**chaining**)。 + + + +有可能会在一个回调失败之后继续使用链式操作,即,使用一个 `catch`,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。请阅读下面的例子: + +```js +new Promise((resolve, reject) => { + console.log('初始化'); + + resolve(); +}) +.then(() => { + throw new Error('有哪里不对了'); + + console.log('执行「这个」”'); +}) +.catch(() => { + console.log('执行「那个」'); +}) +.then(() => { + console.log('执行「这个」,无论前面发生了什么'); +}); +``` + + + + + +# MDN + +> [参考](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises) + +## 模块module + +> [Kingdom](https://en.wikipedia.org/wiki/%2B44_(band)) + +可命名.mjs文件,和.js文件实际一样,但便于理解 + +需要导出。类似 + +```js +export const name = 'square'; + +export function draw(ctx, length, x, y, color) {} +``` + +需要导入。类似 + +```scala +import { name, draw, reportArea, reportPerimeter } from '/js-examples/modules/basic-modules/modules/square.mjs'; +``` + +现在我们只需要将 main.mjs 模块应用到我们的 HTML 页面。这与我们将常规脚本应用于页面的方式非常相似,但有一些显着的差异。首先,你需要把 `type="module"` 放到 [``](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script) 标签中,来声明这个脚本是一个模块: + +``` + +``` + + + +支持重命名 + +```js +// inside module.mjs +export { + function1 as newFunctionName, + function2 as anotherNewFunctionName +}; + +// inside main.mjs +import { newFunctionName, anotherNewFunctionName } from '/modules/module.mjs'; +``` + + + +```js +// inside module.mjs +export { function1, function2 }; + +// inside main.mjs +import { function1 as newFunctionName, + function2 as anotherNewFunctionName } from '/modules/module.mjs'; +``` + + + + + +支持模块对象创建 + +一个更好的解决方是,导入每一个模块功能到一个模块功能对象上。可以使用以下语法形式: + +```js +import * as Module from '/modules/module.mjs'; +``` + +这将获取 `module.mjs` 中所有可用的导出,并使它们可以作为对象模块的成员使用,从而有效地为其提供自己的命名空间。例如: + +```js +Module.function1() +Module.function2() +etc. +``` + +## 原型 + +> [原型(prototype)](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes#%E4%BD%BF%E7%94%A8_javascript_%E4%B8%AD%E7%9A%84%E5%8E%9F%E5%9E%8B) + +在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的`prototype`属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。 + +理解对象的原型(可以通过 [`Object.getPrototypeOf(obj)`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf)或者已被弃用的[`__proto__`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)属性获得)与构造函数的 `prototype` 属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,`Object.getPrototypeOf(new Foobar())` 和 `Foobar.prototype` 指向着同一个对象。 + + + +在 javascript 中,函数可以有属性。每个函数都有一个特殊的属性叫作**原型(prototype)**。 + +prototype是个对象。`prototype` 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。 + +原型对象是一个内部对象,应当使用 `__proto__` 访问)。`prototype` 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。 + +原型链中的方法和属性**没有**被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。这是和构造器里直接声明定义方法的区别,构造器里的是每个实例都有一份。原型链里全部只有一份。 + + + +每个实例对象都从原型中继承了一个 constructor 属性,该属性指向了用于构造此实例对象的构造函数。 + + + +事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 `prototype` 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如: + +```js +// 构造器及其属性定义 + +function Test(a,b,c,d) { + // 属性定义 +}; + +// 定义第一个方法 + +Test.prototype.x = function () { ... } + +// 定义第二个方法 + +Test.prototype.y = function () { ... } + +// 等等…… +``` + + + +Object.prototype的**proto**属性是一个访问器属性(一个getter函数和一个setter函数),它公开访问它的对象的内部[[Prototype]](对象或null)。 + + + +> [轻松理解JS 原型原型链](https://juejin.cn/post/6844903989088092174) + +1. js分为**函数对**象和**普通对象**,每个对象都有__proto__属性,但是只有函数对象才有prototype属性 +2. Object、Function都是js内置的**函数**, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String +3. 属性__proto__是一个对象,它有两个属性,constructor和__proto__; +4. 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建; + + + +```scala + function Person(name, age){ + this.name = name; + this.age = age; + } + + Person.prototype.motherland = 'China' + let person01 = new Person('小明', 18); +``` + + + + +js之父在设计js原型、原型链的时候遵从以下两个准则 +1. Person.prototype.constructor == Person // **准则1:原型对象(即Person.prototype)的constructor指向构造函数本身** +2. person01.__proto__ == Person.prototype // **准则2:实例(即person01)的__proto__和原型对象指向同一个地方** + + + + + +构造函数和原型可支持面向对象的特性。但不容易实现。下面是es6里的class笔记。 + + + + + +## 类 + +> [JavaScript 中的类](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Classes_in_JavaScript) + +### 类 + +下面的是es6里提供的 + +这里描述的特性并不是一种继承对象的新方式:在引擎的底层,这一特性使用的仍是原型。这只是一种更容易的创建原型链的方法。 + + + +```scala +class Person { + + name; + + constructor(name) { + this.name = name; + } + + introduceSelf() { + console.log(`Hi! I'm ${this.name}`); + } + +} + +const giles = new Person('Giles'); + +giles.introduceSelf(); // Hi! I'm Giles +``` + +构造函数使用 [`constructor`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/constructor) 关键字来声明。就像[在类声明外的构造函数](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Basics)一样,它会: + +- 创建一个新的对象 +- 将 `this` 绑定到这个新的对象,你可以在构造函数代码中使用 `this` 来引用它 +- 执行构造函数中的代码 +- 返回这个新的对象 + +如果你不需要任何特殊的初始化内容,你可以省略构造函数,默认的构造函数会被自动生成 + + + +### **继承** + +对于上文给出的 Person 类,我们声明一个它的子类 Professor。 + +注意extends, constructor里的super + +```scala + + +class Professor extends Person { + + teaches; + + constructor(name, teaches) { + super(name); + this.teaches = teaches; + } + + introduceSelf() { + console.log(`My name is ${this.name}, and I will be your ${this.teaches} professor.`); + } + + grade(paper) { + const grade = Math.floor(Math.random() * (5 - 1) + 1); + console.log(grade); + } + +} +``` + + + +### 私有 + +想要某些字段私有。可以用字段前面加#. 私有数据属性必须在类的声明中声明,而且其名称需以 `#` 开头。 + + + +与私有数据属性一样,你也可以声明私有方法。而且名称也是以 `#` 开头,只能在类自己的方法中调用 + + + +```scala +class Student extends Person { + + #year; + + constructor(name, year) { + super(name); + this.#year = year; + } + somePublicMethod() { + this.#somePrivateMethod(); + } + + #somePrivateMethod() { + console.log('You called me?'); + } +} +``` + +### 注意点 + +1. 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。 + +2. 类里面的共有属性和方法一定要加this调用。 + +3. 类里面的this指向问题。 constructor里面的this指向的是 创建的实例对象,方法里面的this指向这个方法的调用者 +4. 属性只能放到构造函数里,`this.xxx` 来定义。没有静态属性 + + + + + +### 静态方法 + +使用 `static` 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用: + +```js +class Animal { + static isAnimal(a) { + return a instanceof Animal; + } +} + +let a = new Animal('Jack'); +Animal.isAnimal(a); // true +a.isAnimal(a); // TypeError: a.isAnimal is not a function +``` + + + +# ES6 + +> [参考](https://web.qianguyihao.com/05-JavaScript%E5%9F%BA%E7%A1%80%EF%BC%9AES6%E8%AF%AD%E6%B3%95/01-ES5%E5%92%8CES6%E7%9A%84%E4%BB%8B%E7%BB%8D.html#%E5%89%8D%E8%A8%80) + +简单来说,ECMAScript 是 JS 的语言标准。当然,ECMAScript 还包括其他脚本语言的语言标准。 + +ES6 是新的 JS 语法标准。**ES6 实际上是一个泛指,泛指 ES 2015 及后续的版本**。 + +ES6 的改进如下: + +- ES6 之前的变量提升,会导致程序在运行时有一些不可预测性。而 ES6 中通过 let、const 变量优化了这一点。 +- ES6 增加了很多功能,比如:**常量、作用域、对象代理、异步处理、类、继承**等。这些在 ES5 中想实现,比较复杂,但是 ES6 对它们进行了封装。 +- ES6 之前的语法过于松散,实现相同的功能,不同的人可能会写出不同的代码。 + +ES6 的目标是:让 JS 语言可以编写复杂的大型应用程序,成为企业级开发语言。 + + + + + +现在,有了 ES6 之后,我们可以使用对象解构的方式进行赋值。举例如下: + +```js +const person = { name: 'qianguyihao', age: 28, sex: '男' }; +let { name, age, sex } = person; // 对象的结构赋值 +``` + + + + + +ES6 在**函数扩展**方面,新增了很多特性。例如: + +- 箭头函数 +- 参数默认值 +- 参数结构赋值 +- 剩余参数 +- 扩展运算符 +- this 绑定 +- 尾调用 + + + +```scala +const fn2 = (a, b) => { + console.log('haha'); + return a + b; +}; + +console.log(fn2(1, 2)); //输出结果:3 +``` + +ES6 的箭头函数中:**箭头函数本身不绑定 this**,this 指向的是**箭头函数定义位置的 this**(也就是说,箭头函数在哪个位置定义的,this 就跟这个位置的 this 指向相同)。 + +```scala +function fn(param = 'hello') { + console.log(param); +} +``` + +```scala +const fn = (...args) => { + //当不确定方法的参数时,可以使用剩余参数 + console.log(args[0]); + console.log(args[1]); + console.log(args[2]); + console.log(args[3]); +}; + +``` + diff --git "a/_posts/Tech/FrontEnd/2017-12-03-jquery\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2017-12-03-jquery\345\255\246\344\271\240\347\254\224\350\256\260.md" deleted file mode 100644 index bf0d9b09ad..0000000000 --- "a/_posts/Tech/FrontEnd/2017-12-03-jquery\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ /dev/null @@ -1,110 +0,0 @@ ---- -layout: post -category: FrontEnd -title: jquery笔记 -tags: FrontEnd ---- -class用. ,id用# - -## jquery - -```javascript -$("").hide() -//必须 -$(document).ready(function(){ - ---- jQuery functions go here ---- - -}); - -jQuery 语法实例 -$(this).hide() -演示 jQuery hide() 函数,隐藏当前的 HTML 元素。 -$("#test").hide() -演示 jQuery hide() 函数,隐藏 id="test" 的元素。 -$("p").hide() -演示 jQuery hide() 函数,隐藏所有

    元素。 -$(".test").hide() -演示 jQuery hide() 函数,隐藏所有 class="test" 的元素。 - -jQuery 元素选择器 -jQuery 使用 CSS 选择器来选取 HTML 元素。 -$("p") 选取

    元素。 -$("p.intro") 选取所有 class="intro" 的

    元素。 -$("p#demo") 选取所有 id="demo" 的

    元素。 -jQuery 属性选择器 -jQuery 使用 XPath 表达式来选择带有给定属性的元素。 -$("[href]") 选取所有带有 href 属性的元素。 -$("[href='#']") 选取所有带有 href 值等于 "#" 的元素。 -$("[href!='#']") 选取所有带有 href 值不等于 "#" 的元素。 -$("[href$='.jpg']") 选取所有 href 值以 ".jpg" 结尾的元素。 -jQuery CSS 选择器 -jQuery CSS 选择器可用于改变 HTML 元素的 CSS 属性。 -下面的例子把所有 p 元素的背景颜色更改为红色: -$("p").css("background-color","red"); - -$(document).ready(function) 将函数绑定到文档的就绪事件(当文档完成加载时) -$(selector).click(function) 触发或将函数绑定到被选元素的点击事件 -$(selector).dblclick(function) 触发或将函数绑定到被选元素的双击事件 -$(selector).focus(function) 触发或将函数绑定到被选元素的获得焦点事件 -$(selector).mouseover(function) 触发或将函数绑定到被选元素的鼠标悬停事件 - -$("#btn1").click(function(){ - $("#test1").text("Hello world!"); -}); -$("#btn2").click(function(){ - $("#test2").html("Hello world!"); -}); -$("#btn3").click(function(){ - $("#test3").val("Dolly Duck"); -}); -//回调函数 -$("#btn1").click(function(){ - $("#test1").text(function(i,origText){ - return "Old text: " + origText + " New text: Hello world! - (index: " + i + ")"; - }); -}); - -$("button").click(function(){ - $("#w3s").attr("href","http://www.w3school.com.cn/jquery"); -}); -//同时设置多个属性 -$("button").click(function(){ - $("#w3s").attr({ - "href" : "http://www.w3school.com.cn/jquery", - "title" : "W3School jQuery Tutorial" - }); -}); - -$("p").append("Some appended text."); -$("p").prepend("Some prepended text."); - -function appendText() -{ -var txt1="

    Text.

    "; // 以 HTML 创建新元素 -var txt2=$("

    ").text("Text."); // 以 jQuery 创建新元素 -var txt3=document.createElement("p"); // 以 DOM 创建新元素 -txt3.innerHTML="Text."; -$("p").append(txt1,txt2,txt3); // 追加新元素 -} - -$("img").after("Some text after"); -$("img").before("Some text before"); - -$("#div1").remove(); -$("#div1").empty(); -$("p").remove(".italic"); - -$("button").click(function(){ - $("h1,h2,p").addClass("blue"); - $("div").addClass("important"); -}); - -$("button").click(function(){ - $("h1,h2,p").removeClass("blue"); -}); - -$("p").css("background-color","yellow"); -$("p").css({"background-color":"yellow","font-size":"200%"}); -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2017-12-03-js\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2017-12-03-js\345\255\246\344\271\240\347\254\224\350\256\260.md" deleted file mode 100644 index 9d528c3cec..0000000000 --- "a/_posts/Tech/FrontEnd/2017-12-03-js\345\255\246\344\271\240\347\254\224\350\256\260.md" +++ /dev/null @@ -1,378 +0,0 @@ ---- -layout: post -category: FrontEnd -title: javascript笔记 -tags: FrontEnd ---- - -## javascript -- 字符串转换为数字用Number(str)或则和parseInt(str)/parseFloat(str)方法 -- 数字转换为字符串用var.toString()方法 -- 获取字符串或者数组的长度,用arr.length属性 -- 数组添加头部元素: arr.unshift("a","b"....); 结尾用push()方法 -- NaN: not a number -- 将数组元素连接成字符串: arr.join("连接符") -- 可以直接使用Math.方法名,如max(...), -- document.querySelector()方法,querySelectorAll()方法选择器写法和css选择器写法一样,但效率低 -- ByName()只用于表单元素,一般是单选和复选框 -- 两个特殊方法,document.title, document.body -- 节点有三种:元素节点,属性节点,文本节点 -- 创建节点的流程:createElement(), createTextNode(),把文本节点插入元素节点 appendChild(),把组装好的节点插入到已有元素中:appendChild() -- obj.style.属性名只可以获得行内样式,是没办法获得内部样式和外部样式的。一般用getComputedStyle.属性名或者obj.style.cssText()="width:3px"等来写,后者可以写多个,css写法,前者驼峰样式,没有-了 -- html中onclick="f()", js中obj.click=f,前者是调用属性,后者是给属性赋值 -- 只执行最后一次window.onload=function(){ } -- 事件绑定: obj.addEventListener("click",funcion,false); - -# javascript基础教程 - -## 插入js用法 -1. 可以插入到html的任何位置 -2. 头部 -3. 头部> - -```javascript -// 内部脚本 -/*使用标签*/ -//外部脚本使用 可以插入任何位置 -//document.write()加载后使用,将覆盖,如函数中 - -//JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型: -document.getElementById("demo").innerHTML="我的第一段 JavaScript"; -document.write("

    我的第一段 JavaScript

    "); -``` - -## 变量声明用法 -```javascript -对大小写敏感 -拆行用\ -//声明变量 -var a; -var name="Gates", age=56, job="CEO"; - -// 数组 开头为0 -var cars=new Array(); -cars[0]="Audi"; -cars[1]="BMW"; -cars[2]="Volvo"; -// 或者 -var cars=new Array("Audi","BMW","Volvo"); -// 或者 -var cars=["Audi","BMW","Volvo"]; - -// 对象 -var person={ - firstname : "Bill", - lastname : "Gates", - id : 5566 -}; - -//声明变量时可以确定其类型,如 -var carname=new String; -var x= new Number; -var y= new Boolean; -var cars= new Array; -var person= new Object; - -//创建对象 -person=new Object(); -person.firstname="Bill"; -person.lastname="Gates"; -person.age=56; -person.eyecolor="blue"; -//或者 -person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"}; -//或者对象构造器 -function person(firstname,lastname,age,eyecolor) -{ - this.firstname=firstname; - this.lastname=lastname; - this.age=age; - this.eyecolor=eyecolor; -} -var myFather=new person("Bill","Gates",56,"blue"); -//定义对象方法 -function person(firstname,lastname,age,eyecolor) -{ - this.firstname=firstname; - this.lastname=lastname; - this.age=age; - this.eyecolor=eyecolor; - - this.changeName=changeName; - function changeName(name) - { - this.lastname=name; - } -} - -``` - -## if else用法,循环 - -```javascript - -if (time<10) -{ - x="Good morning"; -} -else if (time<20) -{ - x="Good day"; -} -else -{ - x="Good evening"; -} - -var day=new Date().getDay(); -switch (day) -{ - case 6: - x="Today it's Saturday"; - break; - case 0: - x="Today it's Sunday"; - break; - default: - x="Looking forward to the Weekend"; -} - -for (var i=0;i"); -} -for (var i=0,len=cars.length; i"); -} -//循环遍历对象的属性 -var person={fname:"John",lname:"Doe",age:25}; -for (x in person) -{ - txt=txt + person[x]; -} - -while (i<5) -{ - x=x + "The number is " + i + "
    "; - i++; -} -do -{ - x=x + "The number is " + i + "
    "; - i++; -} -while (i<5); -//break; continue; -``` - -## 函数 -```javascript -function myFunction(var1,var2) -{ - // 这里是要执行的代码 - return 3;//可以没有 -} - -``` -## try/catch -```javacript -try -{ - throw exception; - adddlert("Welcome guest!"); -} -catch(err) -{ - txt="There was an error on this page.\n\n"; - txt+="Error description: " + err.message + "\n\n"; - txt+="Click OK to continue.\n\n"; - alert(txt); -} -``` - -## 引用元素 -```javascript -//通过标签找html元素 -var x=document.getElementById("main");//id: main -var y=x.getElementsByTagName("p"); - -var element=document.getElementById("header"); -element.innerHTML="New Header"; - -document.getElementById(id).attribute=new value; - -document.getElementById("image").src="landscape.jpg"; - -document.getElementById("p2").style.color="blue"; - -//DOM事件 -//

    请点击该文本

    this就是自己的id -//

    请点击该文本

    -document.getElementById("myBtn").onclick=function(){displayDate()}; -// -//分配事件 -document.getElementById("myBtn").onclick=function(){displayDate()}; - -//创建元素 -var para=document.createElement("p"); -var node=document.createTextNode("这是新段落。"); -para.appendChild(node); -var element=document.getElementById("div1"); -element.appendChild(para); -//删除元素 -var parent=document.getElementById("div1"); -var child=document.getElementById("p1"); -parent.removeChild(child); -//正则表达式 RegExp -``` - -# jquery教程 - -## 语法 -```javacript - $(document).ready(function(){ - - // 开始写 jQuery 代码... - - }); - - 简洁写法(与以上写法效果相同): - $(function(){ - - // 开始写 jQuery 代码... - - }); - -#id 选择器 -$("#test") -.class 选择器 -$(".test") -$("p").css("background-color","red"); -$(this) 选取当前 HTML 元素 - ``` - -## 事件 -```javascript -$("p").click(function(){ - // action goes here!! -}); -$("p").click(); -$("p").dblclick(function(){ - $(this).hide(); -}); - -jQuery hide() 和 show() -通过 jQuery,您可以使用 toggle() 方法来切换 hide() 和 show() 方法。 - -$(selector).toggle(speed,callback); -可选的 speed 参数规定隐藏/显示的速度,可以取以下值:"slow"、"fast" 或毫秒。 -可选的 callback 参数是 toggle() 方法完成后所执行的函数名称。 -可选的 callback 参数,具有以下三点说明: -$(selector)选中的元素的个数为n个,则callback函数会执行n次 -callback函数名后加括号,会立刻执行函数体,而不是等到显示/隐藏完成后才执行 -callback既可以是函数名,也可以是匿名函数 - -Callback 函数在当前动画 100% 完成之后执行。 - -通过 jQuery,可以把动作/方法链接在一起。 -Chaining 允许我们在一条语句中运行多个 jQuery 方法(在相同的元素上)。 -$("#p1").css("color","red").slideUp(2000).slideDown(2000); -$("#p1").css("color","red") - .slideUp(2000) - .slideDown(2000); - -三个简单实用的用于 DOM 操作的 jQuery 方法: -text() - 设置或返回所选元素的文本内容 -html() - 设置或返回所选元素的内容(包括 HTML 标记) -val() - 设置或返回表单字段的值 - -$("#btn1").click(function(){ - alert("Text: " + $("#test").text()); -}); -$("#btn2").click(function(){ - alert("HTML: " + $("#test").html()); -}); -$("#btn1").click(function(){ - alert("Value: " + $("#test").val()); -}); - -下面的例子演示如何通过 jQuery val() 方法获得输入字段的值: - - - - -

    名称:

    - - - - -$("button").click(function(){ - alert($("#w3s").attr("href")); -}); - -下面的例子演示如何通过 text()、html() 以及 val() 方法来设置内容: -$("#btn1").click(function(){ - $("#test1").text("Hello world!"); -}); -$("#btn2").click(function(){ - $("#test2").html("Hello world!"); -}); -$("#btn3").click(function(){ - $("#test3").val("Dolly Duck"); -}); -text()、html() 以及 val() 的回调函数 - -上面的三个 jQuery 方法:text()、html() 以及 val(),同样拥有回调函数。 -回调函数由两个参数:被选元素列表中当前元素的下标,以及原始(旧的)值。 -然后以函数新值返回您希望使用的字符串。 -$("#btn1").click(function(){ - $("#test1").text(function(i,origText){ - return "Old text: " + origText + " New text: Hello world! - (index: " + i + ")"; - }); -}); - -$("button").click(function(){ - $("#w3s").attr("href","//www.w3cschool.cn/jquery"); -}); - -append() - 在被选元素内部的结尾插入指定内容 -prepend() - 在被选元素内部的开头插入指定内容 -after() - 在被选元素之后插入内容 -before() - 在被选元素之前插入内容 - -$("p").append("Some appended text."); - -$("#div1").remove(); - -$("#div1").empty(); - -addClass() - 向被选元素添加一个或多个类 -removeClass() - 从被选元素删除一个或多个类 -toggleClass() - 对被选元素进行添加/删除类的切换操作 -css() - 设置或返回样式属性 - -$("button").click(function(){ - $("h1,h2,p").addClass("blue"); - $("div").addClass("important"); -}); - -您也可以在 addClass() 方法中规定多个类: -$("button").click(function(){ - $("#div1").addClass("important blue"); -}); - -下面的例子将返回首个匹配元素的 background-color 值: -$("p").css("background-color"); - -$("p").css("background-color","yellow"); - -$("p").css({"background-color":"yellow","font-size":"200%"}); -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2018-02-11-html\351\200\237\346\237\245.md" "b/_posts/Tech/FrontEnd/2018-02-11-html\351\200\237\346\237\245.md" deleted file mode 100644 index 1db8f68d5c..0000000000 --- "a/_posts/Tech/FrontEnd/2018-02-11-html\351\200\237\346\237\245.md" +++ /dev/null @@ -1,160 +0,0 @@ ---- -layout: post -category: FrontEnd -title: html速查 -tags: FrontEnd ---- - -## HTML基本文档 -```html - - - -文档标题 - - -可见文本... - - -``` - -## 基本标签(Basic Tags) -```html -

    最大的标题

    -

    . . .

    -

    . . .

    -

    . . .

    -
    . . .
    -
    最小的标题
    - -

    这是一个段落。

    -
    (换行) -
    (水平线) - -``` - -## 文本格式化(Formatting) -```html -粗体文本 -计算机代码 -强调文本 -斜体文本 -键盘输入 - H5已废弃 -
    预格式化文本
    -更小的文本 -重要的文本 - - (缩写) - TheWHO was founded in 1948. -
    (联系信息) - 就是斜体 - (文字方向) -
    (从另一个源引用的部分) - (工作的名称) - (删除的文本) - (插入的文本) - (下标文本) - (上标文本) -``` - -## 链接(Links) -```html -普通的链接:链接文本 -图像链接: 替换文本 -邮件链接: 发送e-mail -书签: -提示部分 -跳到提示部分 -``` - -## 图片(Images) -必须有src和alt属性 -```html -替换文本 -``` - -## 样式/区块(Styles/Sections) -```html - -
    文档中的块级元素
    -文档中的内联元素 -``` - -## 无序列表 -```html -
      -
    • 项目
    • -
    • 项目
    • -
    -``` - -## 有序列表 -```html -
      -
    1. 第一项
    2. -
    3. 第二项
    4. -
    -``` - -## 定义列表 -```html -
    -
    项目 1
    -
    描述项目 1
    -
    项目 2
    -
    描述项目 2
    -
    -``` - -## 表格(Tables) -```html - - - - - - - - - -
    表格标题表格标题
    表格数据表格数据
    -``` - -## 框架(Iframe) -```html - -``` - -## 表单(Forms) -每个输入元素必须有一个name属性 - -在form规定action, submit提交就,规定post还是get -```html -
    - - - - - - - - - - -
    -``` - -## 实体(Entities) -```html -< 等同于 < -> 等同于 > -© 等同于 © -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2018-02-12-javascript\351\200\237\346\237\245.md" "b/_posts/Tech/FrontEnd/2018-02-12-javascript\351\200\237\346\237\245.md" deleted file mode 100644 index a10a1b8753..0000000000 --- "a/_posts/Tech/FrontEnd/2018-02-12-javascript\351\200\237\346\237\245.md" +++ /dev/null @@ -1,408 +0,0 @@ ---- -layout: post -category: FrontEnd -title: javascript速查 -tags: FrontEnd ---- - -## js用法 -外部脚本, 可以插入任何位置 -```javascript - -``` -内部脚本 -```javascript - -``` -脚本可被放置在 HTML 页面的 body和head部分中。 - -## JavaScript 输出 -JavaScript 没有任何打印或者输出的函数。 - -document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。 - - JavaScript 显示数据 - JavaScript 可以通过不同的方式来输出数据: - - 使用 window.alert() 弹出警告框。 - 使用 document.write() 方法将内容写到 HTML 文档中。 - 使用 innerHTML 写入到 HTML 元素。 - 使用 console.log() 写入到浏览器的控制台。 - -## 操作 HTML 元素 -如需从 JavaScript 访问某个 HTML 元素,您可以使用 document.getElementById(id) 方法。 -```javascript -//通过标签找html元素 -var x=document.getElementById("main");//id: main -var y=x.getElementsByTagName("p"); - -var element=document.getElementById("header"); -element.innerHTML="New Header"; - -document.getElementById(id).attribute=new value; - -document.getElementById("image").src="landscape.jpg"; - -document.getElementById("p2").style.color="blue"; - -``` -## 变量 -字符串(String)、数字(Number)、布尔(Boolean)、数组(Array)、对象(Object)、空(Null)、未定义(Undefined)。 -```javascript -//声明变量 -var a; -var name="Gates", age=56, job="CEO"; -//JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型: - -//声明变量时可以确定其类型,如 -var carname=new String; -var x= new Number; -var y= new Boolean; -var cars= new Array; -var person= new Object; -``` - -## 数组 -开头为0 -```javascript -var cars=new Array(); -cars[0]="Audi"; -cars[1]="BMW"; -cars[2]="Volvo"; -// 或者 -var cars=new Array("Audi","BMW","Volvo"); -// 或者 -var cars=["Audi","BMW","Volvo"]; -``` - -## 对象 -对象由花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔 - -### 对象创建 -```javascript -var person={ - firstname : "Bill", - lastname : "Gates", - id : 5566 -}; - -//创建对象 -person=new Object(); -person.firstname="Bill"; -person.lastname="Gates"; -person.age=56; -person.eyecolor="blue"; -//或者 -person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"}; -//或者对象构造器 -function person(firstname,lastname,age,eyecolor) -{ - this.firstname=firstname; - this.lastname=lastname; - this.age=age; - this.eyecolor=eyecolor; -} -var myFather=new person("Bill","Gates",56,"blue"); -``` - -### 访问对象属性 -对象属性有两种寻址方式: -```javascript - name=person.lastname; - name=person["lastname"]; -``` - -### 对象方法 -```javascript -var person = { - firstName: "John", - lastName : "Doe", - id : 5566, - fullName : function() - { - return this.firstName + " " + this.lastName; - } -}; -document.getElementById("demo").innerHTML = person.fullName(); -``` - -### 使用对象方法 -对象方法通过添加 () 调用 (作为一个函数)。 -```javascript -name = person.fullName(); -``` - -## Undefined 和 Null -Undefined 这个值表示变量不含有值。 - -可以通过将变量的值设置为 null 来清空变量。 - -## 函数 -```javascript -// 传统定义函数方式 -function myFunction(a,b) -{ - if (a>b) - { - return; - } - x=a+b -} - -function Test () { - // -} - -const Test = function () { - // -} - -// 使用箭头函数定义函数时可以省略 function 关键字 -const Test = (...params) => { - // -} - -// 该函数只有一个参数时可以简写成: -const Test = param => { - return param; -} - -console.log(Test('hello')); // hello -``` - -## 语法 -### if else -```javascript -if (time<10) -{ - x="Good morning"; -} -else if (time<20) -{ - x="Good day"; -} -else -{ - x="Good evening"; -} -``` - -### swith语法 -```javascript -var day=new Date().getDay(); -switch (day) -{ - case 6: - x="Today it's Saturday"; - break; - case 0: - x="Today it's Sunday"; - break; - default: - x="Looking forward to the Weekend"; -} -``` - -### for循环 -```javascript -for (var i=0;i"); -} -//循环遍历对象的属性 -var person={fname:"John",lname:"Doe",age:25}; -for (x in person) -{ - txt=txt + person[x]; -} -``` - -### while循环 -```javascript -while (i<5) -{ - x=x + "The number is " + i + "
    "; - i++; -} - -do -{ - x=x + "The number is " + i + "
    "; - i++; -} -while (i<5); -//break; continue; -``` - -## 类型转化 - -### typeof 操作符 -你可以使用 typeof 操作符来查看 JavaScript 变量的数据类型。 -请注意: - - NaN 的数据类型是 number - 数组(Array)的数据类型是 object - 日期(Date)的数据类型为 object - null 的数据类型是 object - 未定义变量的数据类型为 undefined -实例 - -```javascript -typeof "John" // 返回 string -typeof 3.14 // 返回 number -typeof NaN // 返回 number -typeof false // 返回 boolean -typeof [1,2,3,4] // 返回 object -typeof {name:'John', age:34} // 返回 object -typeof new Date() // 返回 object -typeof function () {} // 返回 function -typeof myCar // 返回 undefined (如果 myCar 没有声明) -typeof null // 返回 object -``` - -### constructor 属性 -constructor 属性返回所有 JavaScript 变量的构造函数。 - -实例 -```javascript -"John".constructor // 返回函数 String() { [native code] } -(3.14).constructor // 返回函数 Number() { [native code] } -false.constructor // 返回函数 Boolean() { [native code] } -[1,2,3,4].constructor // 返回函数 Array() { [native code] } -{name:'John', age:34}.constructor // 返回函数 Object() { [native code] } -new Date().constructor // 返回函数 Date() { [native code] } -function () {}.constructor // 返回函数 Function(){ [native code] } - -function isArray(myArray) { - return myArray.constructor.toString().indexOf("Array") > -1; -} - -function isDate(myDate) { - return myDate.constructor.toString().indexOf("Date") > -1; -} -``` - -### 将数字转换为字符串 -```javascript -全局方法 String() 可以将数字转换为字符串。 - -String(x) // 将变量 x 转换为字符串并返回 -String(123) // 将数字 123 转换为字符串并返回 -String(100 + 23) // 将数字表达式转换为字符串并返回 - -Number 方法 toString() 也是有同样的效果。 - -实例 -x.toString() -(123).toString() -(100 + 23).toString() - -``` - -### 将布尔值转换为字符串 -```javascript -全局方法 String() 可以将布尔值转换为字符串。 - -String(false) // 返回 "false" -String(true) // 返回 "true" -Boolean 方法 toString() 也有相同的效果。 - -false.toString() // 返回 "false" -true.toString() // 返回 "true" -``` - -### 将日期转换为字符串 -```javascript -Date() 返回字符串。 - -Date() // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) -全局方法 String() 可以将日期对象转换为字符串。 - -String(new Date()) // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) -Date 方法 toString() 也有相同的效果。 - -实例 -obj = new Date() -obj.toString() // 返回 Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time) -``` - -### 将字符串转换为数字 -```javascript -全局方法 Number() 可以将字符串转换为数字。 - -字符串包含数字(如 "3.14") 转换为数字 (如 3.14). - -空字符串转换为 0。 - -其他的字符串会转换为 NaN (不是个数字)。 - -Number("3.14") // 返回 3.14 -Number(" ") // 返回 0 -Number("") // 返回 0 -Number("99 88") // 返回 NaN - -一元运算符 + -Operator + 可用于将变量转换为数字: -``` -### 将布尔值转换为数字 -```javascript - -全局方法 Number() 可将布尔值转换为数字。 - -Number(false) // 返回 0 -Number(true) // 返回 1 -``` - -### 将日期转换为数字 -```javascript - -全局方法 Number() 可将日期转换为数字。 - -d = new Date(); -Number(d) // 返回 1404568027739 -日期方法 getTime() 也有相同的效果。 - -d = new Date(); -d.getTime() // 返回 1404568027739 -``` - -## try catch -```javascript -try -{ - throw exception; - adddlert("Welcome guest!"); -} -catch(err) -{ - txt="There was an error on this page.\n\n"; - txt+="Error description: " + err.message + "\n\n"; - txt+="Click OK to continue.\n\n"; - alert(txt); -} -``` - -## 其他 -```javascript -//DOM事件 -//

    请点击该文本

    this就是自己的id -//

    请点击该文本

    -document.getElementById("myBtn").onclick=function(){displayDate()}; -// -//分配事件 -document.getElementById("myBtn").onclick=function(){displayDate()}; - -//创建元素 -var para=document.createElement("p"); -var node=document.createTextNode("这是新段落。"); -para.appendChild(node); -var element=document.getElementById("div1"); -element.appendChild(para); -//删除元素 -var parent=document.getElementById("div1"); -var child=document.getElementById("p1"); -parent.removeChild(child); -//正则表达式 RegExp -``` \ No newline at end of file diff --git a/_posts/Tech/FrontEnd/2018-06-15-ajax.md b/_posts/Tech/FrontEnd/2018-06-15-ajax.md deleted file mode 100644 index 388f2c44ae..0000000000 --- a/_posts/Tech/FrontEnd/2018-06-15-ajax.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: post -category: FrontEnd -title: ajax -tags: FrontEnd ---- -代码链接库[my github](https://github.com/mafulong/ajaxLearning) - - -## ajax请求数据 -### get -```javascript - var xmlhttp=new XMLHttpRequest(); - xmlhttp.open("GET","/test/GetSearchTips?sear="+thisnode.value,true); - xmlhttp.send(); -``` - -### post -```javascript - xmlhttp.open("POST","/myservlet",true); - xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); - xmlhttp.send("name=mafulong&age=14"); -``` - -## 后台处理数据 -```java - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { -// super.doGet(req, resp); -// resp.setContentType("text/html"); - String name=req.getParameter("name"); - System.out.println(name); - resp.setContentType("application/json; charset=UTF-8"); - PrintWriter out=resp.getWriter(); -// out.print("fjdkfjdk"); -// out.println("

    abc

    "); - JSONObject jsonObject=new JSONObject(); - JSONArray jsonArray=new JSONArray(); - jsonArray.put(jsonObject); - jsonArray.put(jsonObject); - try{ - jsonObject.put("name","mafulong"); - jsonObject.put("age",18); - }catch (Exception e){ - e.printStackTrace(); - } -// out.print(jsonObject.toString()); - out.print(jsonArray.toString()); - - } -``` - -## 前端处理后端接收得数据 -```javascript - xmlhttp.onreadystatechange=function() - { - if (xmlhttp.readyState==4 && xmlhttp.status==200) - { - // document.getElementById("myDiv").innerHTML=xmlhttp.responseText; - alert("success "); - var data=xmlhttp.responseText; - var djson=JSON.parse(data); - var str=""; - for(var i=0;i"; - str+=djson[i].age+"
    "; - } - document.getElementById("myDiv").innerHTML=str; - } - } -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2018-06-20-css\346\265\256\345\212\250.md" "b/_posts/Tech/FrontEnd/2018-06-20-css\346\265\256\345\212\250.md" deleted file mode 100644 index 3dd40c8e32..0000000000 --- "a/_posts/Tech/FrontEnd/2018-06-20-css\346\265\256\345\212\250.md" +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -category: FrontEnd -title: css浮动 -tags: FrontEnd ---- - - -[参考链接](https://www.cnblogs.com/smyhvae/p/7297736.html) - -[参考链接2](https://blog.csdn.net/u010297791/article/details/76718589) - diff --git "a/_posts/Tech/FrontEnd/2020-11-08-leetcode\347\224\237\346\210\220\345\215\232\345\256\242\350\204\232\346\234\254.md" "b/_posts/Tech/FrontEnd/2020-11-08-leetcode\347\224\237\346\210\220\345\215\232\345\256\242\350\204\232\346\234\254.md" deleted file mode 100644 index 45f344b8a2..0000000000 --- "a/_posts/Tech/FrontEnd/2020-11-08-leetcode\347\224\237\346\210\220\345\215\232\345\256\242\350\204\232\346\234\254.md" +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: post -category: FrontEnd -title: leetcode生成博客脚本 -tags: FrontEnd ---- - -## leetcode生成博客脚本 - - -通过leetcode网站下运行,爬取标题、题目,生成基于jekyll的博客创建命令,并自动复制到剪贴板,在shell运行后便可以生成博客文件。 - -chrome控制台下开source即可运行 - -```javascript - -let date = new Date() -var dateStr = dateFormat("YYYY-mm-dd-", date) - -// let date = new Date() -// var dateStr = dateFormat("YYYY-mm-dd", date) - -// var title = document.getElementsByClassName("css-v3d350")[0].innerHTML -// console.log("title:", title) -// // var clipBoardContent =dateStr + title + ".md" -// // console.log(clipBoardContent) -// // window.copy(clipBoardContent); - -function dateFormat(fmt, date) { - let ret; - const opt = { - "Y+": date.getFullYear().toString(), // 年 - "m+": (date.getMonth() + 1).toString(), // 月 - "d+": date.getDate().toString(), // 日 - "H+": date.getHours().toString(), // 时 - "M+": date.getMinutes().toString(), // 分 - "S+": date.getSeconds().toString() // 秒 - // 有其他格式化字符需求可以继续添加,必须转化成字符串 - }; - for (let k in opt) { - ret = new RegExp("(" + k + ")").exec(fmt); - if (ret) { - fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) - }; - }; - return fmt; -} - - - -var href = window.location.href; - - -var title = document.getElementsByClassName("css-v3d350")[0].innerHTML -// console.log("title:", title) - -var content = document.getElementsByClassName("content__u3I1 question-content__JfgR")[0] -// console.log(content) - -console.log(content.innerHTML) -String.prototype.myReplace=function(f,e){//吧f替换成e - var reg=new RegExp(f,"g"); //创建正则RegExp对象 - return this.replace(reg,e); -} -html = content.innerHTML -var regex = /(<([^>]+)>)/ig -// html = html.myReplace("
    ", "
    '''\n")
    -// html = html.myReplace("
    ", "
    '''\n") -html = html.myReplace("<", "<"); - -html = html.myReplace(">", ">"); -html = html.myReplace(" ", ""); -html = html.replace(regex, ""); -console.log(html) -// let date = new Date() -// var dateStr = dateFormat("YYYY-mm-dd", date) - -// var code = document.getElementsByClassName("CodeMirror-code")[0].innerHTML -// console.log(code) -// code = code.replace(regex,""); -// console.log(code) - -var data =`--- -layout: post -category: leetcode -title: `+title+` -tags: leetcode ---- - -## title -[problem link](`+href+`) -`+html+` - -## solution - -\`\`\`python - -\`\`\` -` - -console.log(data) - - -var fileName = dateStr + title + ".md"; -var path = "/Users/mafulong/mafulong.github.io/_posts/Algorithms/leetcode/"+fileName; -// console.log(path) - -pathStr = "\""+path+"\"" -var shell = "touch "+pathStr +";" -+ "echo \'"+data+"\' >"+pathStr+" ; code "+pathStr; - -// ```; echo \'``` + ```fdfd``` + ```> ```+path + ```; cat ```+path; -// console.log(shell) -window.copy(shell); -``` \ No newline at end of file diff --git "a/_posts/Tech/FrontEnd/2021-09-10-\345\211\215\347\253\257\345\274\200\345\217\221\345\267\245\345\205\267.md" "b/_posts/Tech/FrontEnd/2021-09-10-\345\211\215\347\253\257\345\274\200\345\217\221\345\267\245\345\205\267.md" new file mode 100644 index 0000000000..f9658ae3b2 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2021-09-10-\345\211\215\347\253\257\345\274\200\345\217\221\345\267\245\345\205\267.md" @@ -0,0 +1,37 @@ +--- +layout: post +category: FrontEnd +title: 前端开发工具 +tags: FrontEnd +--- + +# 前端开发工具 + +# vscode + +## 必备插件 + +#### Auto Close Tag + +自动闭合 HTML/XML 标签 + +### Auto Rename Tag + +自动重命名 HTML/XML 标签 + +### Bracket Pair Colorizer + +用于着色匹配括号,让你的代码分层更清晰 + +### Code Runner + +运行代码片段或多种语言的代码文件 + +### Code Spell Checker + +源代码拼写检查器,提示代码中单词拼写错误 + +### CSS Peek + +允许查看css,并从HTML文件定位到css文件,文件定义跳转 + diff --git "a/_posts/Tech/FrontEnd/2022-07-16-NodeJS\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2022-07-16-NodeJS\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..404638ca86 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2022-07-16-NodeJS\347\254\224\350\256\260.md" @@ -0,0 +1,361 @@ +--- +layout: post +category: FrontEnd +title: NodeJS笔记 +tags: FrontEnd +--- + +# NodeJS笔记 + +> [Node.js介绍](https://web.qianguyihao.com/11-Node.js/01-Node.js%E4%BB%8B%E7%BB%8D.html#todo) + +[Node.js](https://nodejs.org/zh-cn/) 是一个基于 **Chrome V8 引擎**的 JavaScript 运行环境。Node.js 使用了一个**事件驱动**、**非阻塞式 I/O**的模型,使其轻量又高效。Node.js 的包管理工具 npm 是全球最大的开源库生态系统。 + +Node.js 不是一门语言,也不是 JavaScript 的框架,也不是像Nginx一样的Web服务器 ,**Node.js 是 JavaScript 在服务器端的运行环境(平台)**。 + + + +在 Node.js 里运行 JavaScript,跟在 Chrome 里运行 JavaScript 有什么不同? + +二者采用的是同样的 JS 引擎。在 Node.js 里写 JS,和在前端写 JS,几乎没有不同。在写法上的区别在于:Node.js 没有浏览器、页面标签相关的 API,但是新增了一些 Node.js 相关的 API。通俗来说,对于开发者而言,在前端写 JS 是用于控制浏览器;而 Node.js 环境写 JS 可以控制整个计算机。 + + + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212242058356.png) + + + +历史回顾: + +(1)一开始,页面很简单,html 是后端渲染的(比如PHP、ASP、JSP等方式)。后端发现页面中的 js 好麻烦(虽然简单,但是坑多),于是让公司招聘专门写 js 的人,简称「前端切图仔」。 + +(2)随着 Node.js 和前端 MVC 的兴起,以及前端越来越复杂,慢慢演变成了「前后端分离」。 + +(3)前端的 SPA 应用流行之后,发现 SEO 问题很大,而且首屏渲染速度很慢,但是自己选的路再难走也要走下去,于是用 Node.js 在服务端渲染被看成是一条出路。 + +(4)以前在一起的时候,是后端做部分前端的工作;现在在一起的时候,是前端做部分后端的工作。 + + + +还可以做客户端软件 + +Electron 框架就是基于 Node.js 的,可以用来开发客户端软件。 + +Electron 原名为 Atom Shell,是由 GitHub 开发的一个开源框架。Electron 以 Node.js 作为运行时(runtime),以 chromium 作为渲染引擎,使开发者可以使用 JS 这种前端技术栈开来发跨平台的桌面GUI应用程序。 + +有一点你可能会感到惊讶:程序员们都在用的代码编辑器 VS Code 软件, 就是基于 Electron 框架来开发的。其他使用 Electron 进行开发的知名应用还有:Skype、GitHub Desktop、Slack、WhatsApp等。 + + + +## Node.js 的特点 + +- 异步、非阻塞 IO 模型 +- 事件循环 +- 单线程 +- 总结:轻量和高效 + +Node.js 的性能和效率非常高。 + +传统的 Java 语言是一个请求开启一个线程,当请求处理完毕后就关闭这个线程。而 Node.js 则完全没有采用这种模型,它本质上就是一个单线程。 + +你可能会疑问:一个线程如何服务于大量的请求、如何处理高并发的呢?这是因为,Node.js 采用的是异步的、非阻塞的模型。 + +这里所谓的“单线程”,指的是 Node 的主线程只有一个。为了确保主线程不被阻塞,主线程是用于接收客户端请求。但不会处理具体的任务。而 Node 的背后还有一个线程池,线程池会处理长时间运行的任务(比如 IO 操作、网络操作)。线程池里的任务是通过队列和事件循环的机制来执行。 + + + +node默认的是单线程,一个nodejs应用无法利用多核资源,能够采用事件驱动和异步“I/O”的方式,实现一个单线程、高并发的运行时环境。 + +nodejs**实际上只有js执行是单线程的**,I/O显然是其他线程。 + +Node.js没有在运行Javascript时创建新线程的能力。 + +js执行线程是单线程的,只要将所需的I/O传递给libuv,自己马上回来做别的事情,然后libuv在指定的时间回调即可。 + + + + + +### 使用 Node.js 时的劣势 + +- 程序运行不稳定,可能会出现服务不可用的情况 +- 程序运行效率较低,每秒的请求数维持在一个较低的水平 +- 前端同学对服务器端的技术不太熟悉。 + +## Hello world + +```js +var http = require('http'); + +http.createServer(function (request, response) { + + // 发送 HTTP 头部 + // HTTP 状态值: 200 : OK + // 内容类型: text/plain + response.writeHead(200, {'Content-Type': 'text/plain'}); + + // 发送响应数据 "Hello World" + response.end('Hello World\n'); +}).listen(8888); + +// 终端打印如下信息 +console.log('Server running at http://127.0.0.1:8888/'); +``` + +## npm使用 + +```shell +npm -v +npm install express # 本地,项目的node_modules目录,会自动创建 +npm install express -g # 全局 +npm list -g # 看全局模块列表 +npm list grunt # 看版本号 +npm uninstall express # 卸载模块 +npm update express +npm search express + +# 创建模块 +npm init # 会创建package.json +npm publish # 发布到资源库 +``` + +使用package.json描述POM + +### 版本号 + +使用NPM下载和发布代码时都会接触到版本号。NPM使用语义版本号来管理代码,这里简单介绍一下。 + +语义版本号分为X.Y.Z三位,分别代表主版本号、次版本号和补丁版本号。当代码变更时,版本号按以下原则更新。 + +- 如果只是修复bug,需要更新Z位。 +- 如果是新增了功能,但是向下兼容,需要更新Y位。 +- 如果有大变动,向下不兼容,需要更新X位。 + +版本号有了这个保证后,在申明第三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如"argv": "0.0.x"表示依赖于0.0.x系列的最新版argv。 + +## 回调函数 + +Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。 + +通过回调来实现异步编程。回调函数一般作为函数的最后一个参数出现 + + + +非阻塞代码实例 + +```js +var fs = require("fs"); + +fs.readFile('input.txt', function (err, data) { + if (err) return console.error(err); + console.log(data.toString()); +}); + +console.log("程序执行结束!"); +``` + +## 事件循环 + +类似非阻塞IO, 观察者模式。 + +Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例: + +```js +// 引入 events 模块 +var events = require('events'); +// 创建 eventEmitter 对象 +var eventEmitter = new events.EventEmitter(); + +// 创建事件处理程序 +var connectHandler = function connected() { + console.log('连接成功。'); + + // 触发 data_received 事件 + eventEmitter.emit('data_received'); +} + +// 绑定 connection 事件处理程序 +eventEmitter.on('connection', connectHandler); + +// 使用匿名函数绑定 data_received 事件 +eventEmitter.on('data_received', function(){ + console.log('数据接收成功。'); +}); + +// 触发 connection 事件 +eventEmitter.emit('connection'); + +console.log("程序执行完毕。"); +``` + +emit是触发 + +on是绑定对应处理 + + + +在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。 + + + +## EventEmitter + +Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。 + +这些产生事件的对象都是 events.EventEmitter 的实例。 + +events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。 + + + + + +EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。 + +当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。 + +```js +//event.js 文件 +var events = require('events'); +var emitter = new events.EventEmitter(); +emitter.on('someEvent', function(arg1, arg2) { + console.log('listener1', arg1, arg2); +}); +emitter.on('someEvent', function(arg1, arg2) { + console.log('listener2', arg1, arg2); +}); +emitter.emit('someEvent', 'arg1 参数', 'arg2 参数'); +``` + +大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。 + + + +## Stream + +Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。 + +Node.js,Stream 有四种流类型: + +- **Readable** - 可读操作。 +- **Writable** - 可写操作。 +- **Duplex** - 可读可写操作. +- **Transform** - 操作被写入数据,然后读出结果。 + +所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有: + +- **data** - 当有数据可读时触发。 +- **end** - 没有更多的数据可读时触发。 +- **error** - 在接收和写入过程中发生错误时触发。 +- **finish** - 所有数据已被写入到底层系统时触发。 + +从流中读取数据 + +```js +var fs = require("fs"); +var data = ''; + +// 创建可读流 +var readerStream = fs.createReadStream('input.txt'); + +// 设置编码为 utf8。 +readerStream.setEncoding('UTF8'); + +// 处理流事件 --> data, end, and error +readerStream.on('data', function(chunk) { + data += chunk; +}); + +readerStream.on('end',function(){ + console.log(data); +}); + +readerStream.on('error', function(err){ + console.log(err.stack); +}); + +console.log("程序执行完毕"); +``` + + + +写入流 + +```js +var fs = require("fs"); +var data = '菜鸟教程官网地址:www.runoob.com'; + +// 创建一个可以写入的流,写入到文件 output.txt 中 +var writerStream = fs.createWriteStream('output.txt'); + +// 使用 utf8 编码写入数据 +writerStream.write(data,'UTF8'); + +// 标记文件末尾 +writerStream.end(); + +// 处理流事件 --> finish、error +writerStream.on('finish', function() { + console.log("写入完成。"); +}); + +writerStream.on('error', function(err){ + console.log(err.stack); +}); + +console.log("程序执行完毕"); +``` + + + +管道流 + +可一对多 链式流 + +```js +readerStream.pipe(writerStream); + +fs.createReadStream('input.txt') + .pipe(zlib.createGzip()) + .pipe(fs.createWriteStream('input.txt.gz')); +``` + + + +## 模块系统 + +## 引入模块 + +在 Node.js 中,引入一个模块非常简单,如下我们创建一个 **main.js** 文件并引入 hello 模块,代码如下: + +```js +var hello = require('./hello'); +hello.world(); +``` + +以上实例中,代码 require('./hello') 引入了当前目录下的 hello.js 文件(./ 为当前目录,node.js 默认后缀为 js)。 + +Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。 + +接下来我们就来创建 hello.js 文件,代码如下: + +```js +exports.world = function() { + console.log('Hello World'); +} +``` + +在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访问接口,在 main.js 中通过 require('./hello') 加载这个模块,然后就可以直接访 问 hello.js 中 exports 对象的成员函数了。 + + + +require方法接受以下几种参数的传递: + +- http、fs、path等,原生模块。 +- ./mod或../mod,相对路径的文件模块。 +- /pathtomodule/mod,绝对路径的文件模块。 +- mod,非原生模块的文件模块。 + +## 参考 + +- [Node.js](https://www.runoob.com/nodejs/nodejs-router.html) diff --git "a/_posts/Tech/FrontEnd/2022-08-26-chrome\350\260\203\350\257\225.md" "b/_posts/Tech/FrontEnd/2022-08-26-chrome\350\260\203\350\257\225.md" new file mode 100644 index 0000000000..0f71c76863 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2022-08-26-chrome\350\260\203\350\257\225.md" @@ -0,0 +1,54 @@ +--- +layout: post +category: FrontEnd +title: chrome调试 +tags: FrontEnd +--- + +## chrome调试 + + + +## 控制台打印对象 + +console.log + +console.table: 如果你要打印的变量是一个数组,每一个元素都是一个对象。我建议你使用`console.table`来打印,其表格化的呈现更加美观易读。 + + + +## 选择元素 + +**\$ 和 \$\$** + +如果你在 console 下没有任何库使用`$`和`$$`,那么你可以使用它们分别作为`document.querySelector()` 和 `document.querySelectorAll()`的快捷键。 + +**$0** + +如果你想引用某个 DOM 元素,使用`$0`。`$0`指向你当前在 Element 中选中的元素。如果指定了`$0`,`$1`指向之前选中的元素。以此类推,直到`$4`都可以使用。 + +## **getEventListeners()** + +`getEventListeners(domElement)` 返回在 DOM 元素上注册的所有的事件 + + + +## 函数断点**debug(fn)** + +如果你想在点击按钮后的执行过程中暂停,你可以使用`debug`函数。`debug(fn)`接收一个函数作为参数,当每次该函数被调用时,Debugger 就会在该函数的第一行中断执行。 + +想象一下你要 debug 一个按钮的问题,但是你不知道这个按钮对应的事件函数在代码中什么位置。除了去大量的源代码中慢慢寻找之外,还有一个巧妙的方法。使用`getEventListeners`函数,然后将`debug`方法注入进去。这样,当你点击按钮的时候,就会在该函数的第一行停下来。 + + + +## 监听dom元素断点 + +可以找到改dom元素属性的js位置。 + +elements上右键break on, 选attribute. + + + +## 参考 + +[参考](https://blog.fundebug.com/2018/08/22/art-of-debugging-with-chrome-devtools/) diff --git "a/_posts/Tech/FrontEnd/2022-12-01-typescript\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2022-12-01-typescript\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..e4744f19f0 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2022-12-01-typescript\347\254\224\350\256\260.md" @@ -0,0 +1,958 @@ +--- +layout: post +category: FrontEnd +title: typescript笔记 +tags: FrontEnd +recent_update: true +--- + +# typescript笔记 + +## JavaScript 与 TypeScript 的区别 + +TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。 + + + +TypeScript 是一种给 JavaScript 添加特性的语言扩展。增加的功能包括: + +- 类型批注和编译时类型检查 +- 类型推断 +- 类型擦除 +- 接口 +- 枚举 +- Mixin +- 泛型编程 +- 名字空间 +- 元组 +- Await + + + +支持es6标准。 + + + +从 TypeScript 的名字就可以看出来,「类型」是其最核心的特性。 + +我们知道,JavaScript 是一门非常灵活的编程语言: + +- 它没有类型约束,一个变量可能初始化时是字符串,过一会儿又被赋值为数字。 +- 由于隐式类型转换的存在,有的变量的类型很难在运行前就确定。 +- 基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。 +- 函数是 JavaScript 中的一等公民[[2\]](https://ts.xcatliu.com/introduction/what-is-typescript.html#link-2),可以赋值给变量,也可以当作参数或返回值。 + + + + + +**TypeScript 是静态类型** + +TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 **TypeScript 是静态类型** + + + +什么是 TypeScript? + +- TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。 +- TypeScript 是一门静态类型、弱类型的语言。 +- TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。 +- TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。 +- TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。 +- TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。 +- TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。 +- TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。 +- TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。 + +## 安装 + +如果你的本地环境已经安装了 npm 工具,可以使用以下命令来安装。 + +使用国内镜像: + +``` +npm config set registry https://registry.npmmirror.com +``` + +安装 typescript: + +``` +npm install -g typescript +``` + +安装完成后我们可以使用 **tsc** 命令来执行 TypeScript 的相关代码,以下是查看版本号: + +``` +$ tsc -v +Version 3.2.2 +``` + + + +编译一个 TypeScript 文件很简单: + +```bash +tsc hello.ts +``` + +我们约定使用 TypeScript 编写的文件以 `.ts` 为后缀,用 TypeScript 编写 React 时,以 `.tsx` 为后缀。 + + + +为了vscode可以运行ts. 需要下面安装 + +```scala +npm install -g ts-node +``` + + + +## helloworld + +```typescript +const hello : string = "Hello World!" +console.log(hello) +``` + +# 基础 + +## 原始数据类型 + +布尔值是最基础的数据类型,在 TypeScript 中,使用 `boolean` 定义布尔值类型: + +```ts +let isDone: boolean = false; + +// 编译通过 +// 后面约定,未强调编译错误的代码片段,默认为编译通过 +``` + +注意,使用构造函数 `Boolean` 创造的对象**不是**布尔值: + +```ts +let createdByNewBoolean: boolean = new Boolean(1); +``` + + + +使用 `number` 定义数值类型: + +```ts +let decLiteral: number = 6; +``` + + + +使用 `string` 定义字符串类型: + +```ts +let myName: string = 'Tom'; +``` + +```ts +// 模板字符串 +let sentence: string = `Hello, my name is ${myName}. +I'll be ${myAge + 1} years old next month.`; +``` + + + +JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 `void` 表示没有任何返回值的函数: + +```ts +function alertName(): void { + alert('My name is Tom'); +} +``` + +声明一个 `void` 类型的变量没有什么用,因为你只能将它赋值为 `undefined` 和 `null`(只在 --strictNullChecks 未指定时): + +```ts +let unusable: void = undefined; +``` + + + +在 TypeScript 中,可以使用 `null` 和 `undefined` 来定义这两个原始数据类型: + +```ts +let u: undefined = undefined; +let n: null = null; +``` + +与 `void` 的区别是,`undefined` 和 `null` 是所有类型的子类型。也就是说 `undefined` 类型的变量,可以赋值给 `number` 类型的变量: + +```ts +// 这样不会报错 +let num: number = undefined; +``` + +而 `void` 类型的变量不能赋值给 `number` 类型的变量: + + + +## 任意值 + + `any` 类型,则允许被赋值为任意类型。 + +```ts +let myFavoriteNumber: any = 'seven'; +myFavoriteNumber = 7; +``` + + + +在任意值上访问任何属性都是允许的: + +```ts +let anyThing: any = 'hello'; +console.log(anyThing.myName); +console.log(anyThing.myName.firstName); +``` + +可以认为,**声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值**。 + + + +**变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型**: + +```ts +let something; +``` + + + +## 类型推论 + +以下代码虽然没有指定类型,但是会在编译的时候报错: + +```ts +let myFavoriteNumber = 'seven'; +myFavoriteNumber = 7; + +// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. +``` + +事实上,它等价于: + +```ts +let myFavoriteNumber: string = 'seven'; +myFavoriteNumber = 7; + +// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. +``` + +TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。 + +**如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 `any` 类型而完全不被类型检查**: + +```ts +let myFavoriteNumber; +myFavoriteNumber = 'seven'; +myFavoriteNumber = 7; +``` + + + +## 联合类型 + +联合类型(Union Types)表示取值可以为多种类型中的一种。 + +```ts +let myFavoriteNumber: string | number; +myFavoriteNumber = 'seven'; +myFavoriteNumber = 7; +``` + + + +当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们**只能访问此联合类型的所有类型里共有的属性或方法** + + + +联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型: + +```ts +let myFavoriteNumber: string | number; +myFavoriteNumber = 'seven'; +``` + + + +## 接口 + +在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。 + + + +```ts +interface Person { + name: string; + age: number; +} + +let tom: Person = { + name: 'Tom', + age: 25 +}; +``` + + + +- 定义的变量比接口少了一些属性是不允许的。会编译报错 +- 多一些属性也是不允许的。 可见,**赋值的时候,变量的形状必须和接口的形状保持一致**。 + + + +可选 + +- 有时我们希望不要完全匹配一个形状,那么可以用可选属性. 可选属性的含义是该属性可以不存在。这时**仍然不允许添加未定义的属性**: + + ```ts + interface Person { + age?: number; + } + + ``` + + + +任意类型 + +- 有时候我们希望一个接口允许有任意的属性,可以使用如下方式。 使用 `[propName: string]` 定义了任意属性取 `string` 类型的值。需要注意的是,**一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集**。 + +```ts +interface Person { + name: string; + age?: number; + [propName: string]: any; +} +``` + +- 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型 + + + +只读属性 + +- 有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 `readonly` 定义只读属性。 **注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候**: + +```ts +interface Person { + readonly id: number; +} +``` + + + + + +## 数组的类型 + +最简单的方法是使用「类型 + 方括号」来表示数组: + +```ts +let fibonacci: number[] = [1, 1, 2, 3, 5]; +``` + +数组的项中**不允许**出现其他的类型 + + + +我们也可以使用数组泛型(Array Generic) `Array` 来表示数组: + +```ts +let fibonacci: Array = [1, 1, 2, 3, 5]; +``` + + + +## 类数组 + +类数组(Array-like Object)不是数组类型 + +```ts +function sum() { + let args: { + [index: number]: number; + length: number; + callee: Function; + } = arguments; +} +``` + +在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 `length` 和 `callee` 两个属性。 + + + +## 函数的类型 + +```ts +function sum(x: number, y: number): number { + return x + y; +} +``` + +注意,**输入多余的(或者少于要求的)参数,是不被允许的**: + + + +### 函数表达式 + +如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样: + +```ts +let mySum = function (x: number, y: number): number { + return x + y; +}; +``` + +这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 `mySum`,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 `mySum` 添加类型,则应该是这样: + +```ts +let mySum: (x: number, y: number) => number = function (x: number, y: number): number { + return x + y; +}; +``` + +注意不要混淆了 TypeScript 中的 `=>` 和 ES6 中的 `=>`。 + +在 TypeScript 的类型定义中,`=>` 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 + +在 ES6 中,`=>` 叫做箭头函数,应用十分广泛,可以参考 [ES6 中的箭头函数](http://es6.ruanyifeng.com/#docs/function#箭头函数)。 + + + +### 用接口定义函数 + +我们也可以使用接口的方式来定义一个函数需要符合的形状: + +```ts +interface SearchFunc { + (source: string, subString: string): boolean; +} + +let mySearch: SearchFunc; +mySearch = function(source: string, subString: string) { + return source.search(subString) !== -1; +} +``` + +采用函数表达式`|`接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。 + + + + + +### 可选参数和默认值、剩余参数 + +```ts +function buildName(firstName: string, lastName?: string) {} + +function buildName(firstName: string, lastName: string = 'Cat') { + return firstName + ' ' + lastName; +} + +function push(array, ...items) { + items.forEach(function(item) { + array.push(item); + }); +} +``` + +需要注意的是,可选参数必须接在必需参数后面。换句话说,**可选参数后面不允许再出现必需参数了** + + + + + +### 重载 + +联合类型**缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。** + + + +以下重载可避免。支持重载。 + +```ts +function reverse(x: number): number; +function reverse(x: string): string; +function reverse(x: number | string): number | string | void { + if (typeof x === 'number') { + return Number(x.toString().split('').reverse().join('')); + } else if (typeof x === 'string') { + return x.split('').reverse().join(''); + } +} +``` + + + +### 类型断言 + +类型断言(Type Assertion)可以用来手动指定一个值的类型。 + + + +```ts +值 as 类型 + +animal as Fish +``` + + + +用途 + +- 联合类型可以被断言为其中一个类型 +- 父类可以被断言为子类 +- 任何类型都可以被断言为 any +- any 可以被断言为任何类型 + + + +- 若 `A` 兼容 `B`,那么 `A` 能够被断言为 `B`,`B` 也能被断言为 `A`。 + +- 类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除。所以类型断言不是类型转换,它不会真的影响到变量的类型。只是让编译器正确地编译,骗过编译器。 + +## 声明文件 + +> [声明文件](https://ts.xcatliu.com/basics/declaration-files.html) + +参考引用即可。 + + + +- [`declare var`](https://ts.xcatliu.com/basics/declaration-files.html#declare-var) 声明全局变量 +- [`declare function`](https://ts.xcatliu.com/basics/declaration-files.html#declare-function) 声明全局方法 +- [`declare class`](https://ts.xcatliu.com/basics/declaration-files.html#declare-class) 声明全局类 +- [`declare enum`](https://ts.xcatliu.com/basics/declaration-files.html#declare-enum) 声明全局枚举类型 +- [`declare namespace`](https://ts.xcatliu.com/basics/declaration-files.html#declare-namespace) 声明(含有子属性的)全局对象 +- [`interface` 和 `type`](https://ts.xcatliu.com/basics/declaration-files.html#interface-和-type) 声明全局类型 +- [`export`](https://ts.xcatliu.com/basics/declaration-files.html#export) 导出变量 +- [`export namespace`](https://ts.xcatliu.com/basics/declaration-files.html#export-namespace) 导出(含有子属性的)对象 +- [`export default`](https://ts.xcatliu.com/basics/declaration-files.html#export-default) ES6 默认导出 +- [`export =`](https://ts.xcatliu.com/basics/declaration-files.html#export-1) commonjs 导出模块 +- [`export as namespace`](https://ts.xcatliu.com/basics/declaration-files.html#export-as-namespace) UMD 库声明全局变量 +- [`declare global`](https://ts.xcatliu.com/basics/declaration-files.html#declare-global) 扩展全局变量 +- [`declare module`](https://ts.xcatliu.com/basics/declaration-files.html#declare-module) 扩展模块 +- [`/// `](https://ts.xcatliu.com/basics/declaration-files.html#san-xie-xian-zhi-ling) 三斜线指令 + + + +# 进阶 + +## 类型别名 + +类型别名用来给一个类型起个新名字。 + + + +```ts +type Name = string; +type NameResolver = () => string; +type NameOrResolver = Name | NameResolver; +function getName(n: NameOrResolver): Name { + if (typeof n === 'string') { + return n; + } else { + return n(); + } +} +``` + +上例中,我们使用 `type` 创建类型别名。 + +类型别名常用于联合类型。 + + + +## 字符串字面量类型 + +字符串字面量类型用来约束取值只能是某几个字符串中的一个。 + + + +```ts +type EventNames = 'click' | 'scroll' | 'mousemove'; +function handleEvent(ele: Element, event: EventNames) { + // do something +} + +handleEvent(document.getElementById('hello'), 'scroll'); // 没问题 +handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick' + +// index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'. +``` + +上例中,我们使用 `type` 定了一个字符串字面量类型 `EventNames`,它只能取三种字符串中的一种。 + +注意,**类型别名与字符串字面量类型都是使用 `type` 进行定义。** + + + +## 元组 + +数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。 + +元组起源于函数编程语言(如 F#),这些语言中会频繁使用元组。 + +定义一对值分别为 `string` 和 `number` 的元组: + +```ts +let tom: [string, number] = ['Tom', 25]; +``` + +当赋值或访问一个已知索引的元素时,会得到正确的类型: + +```ts +let tom: [string, number]; +tom[0] = 'Tom'; +tom[1] = 25; +``` + +也可以只赋值其中一项 + + + +当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型: + +```ts +let tom: [string, number]; +tom = ['Tom', 25]; +tom.push('male'); +tom.push(true); + +// Argument of type 'true' is not assignable to parameter of type 'string | number'. +``` + + + +## 枚举 + +枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。 + +枚举使用 `enum` 关键字来定义: + +```ts +enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; +``` + +枚举成员会被赋值为从 `0` 开始递增的数字,同时也会对枚举值到枚举名进行反向映射: + +```ts +enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; + +console.log(Days["Sun"] === 0); // true +console.log(Days["Mon"] === 1); // true +console.log(Days["Tue"] === 2); // true +console.log(Days["Sat"] === 6); // true + +console.log(Days[0] === "Sun"); // true +console.log(Days[1] === "Mon"); // true +console.log(Days[2] === "Tue"); // true +console.log(Days[6] === "Sat"); // true +``` + + + +我们也可以给枚举项手动赋值: + +```ts +enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; + +console.log(Days["Sun"] === 7); // true +console.log(Days["Mon"] === 1); // true +``` + +未手动赋值的枚举项会接着上一个枚举项递增。如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的。所以使用的时候需要注意,最好不要出现这种覆盖的情况。 + + + +## 类 + +### 实例属性 + +ES6 中实例的属性只能通过构造函数中的 `this.xxx` 来定义,ES7 提案中可以直接在类里面定义: + +```js +class Animal { + name = 'Jack'; + + constructor() { + // ... + } +} + +let a = new Animal(); +console.log(a.name); // Jack +``` + +### 静态属性 + +ES7 提案中,可以使用 `static` 定义一个静态属性: + +```js +class Animal { + static num = 42; + + constructor() { + // ... + } +} + +console.log(Animal.num); // 42 +``` + + + +### public private 和 protected + +TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 `public`、`private` 和 `protected`。 + + 需要注意的是,TypeScript 编译之后的代码中,并没有限制 `private` 属性在外部的可访问性。 + + + +### 构造函数参数可带访问属性 + +修饰符和`readonly`还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。 + +```ts +class Animal { + // public name: string; + public constructor(public name) { + // this.name = name; + } +} +``` + + + +注意如果 `readonly` 和其他访问修饰符同时存在的话,需要写在其后面。 + +```ts +class Animal { + // public readonly name; + public constructor(public readonly name) { + // this.name = name; + } +} +``` + + + +### 抽象类 + +`abstract` 用于定义抽象类和其中的抽象方法。 + +首先,抽象类是不允许被实例化的: + +```ts +abstract class Animal { + public name; + public constructor(name) { + this.name = name; + } + public abstract sayHi(); +} +``` + +其次,抽象类中的抽象方法必须被子类实现 + + + +## 类与接口 + +实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 `implements` 关键字来实现。这个特性大大提高了面向对象的灵活性。 + +```ts +interface Alarm { + alert(): void; +} + +class Door { +} + +class SecurityDoor extends Door implements Alarm { + alert() { + console.log('SecurityDoor alert'); + } +} +``` + +一个类可以实现多个接口: + +```ts +interface Alarm { + alert(): void; +} + +interface Light { + lightOn(): void; + lightOff(): void; +} + +class Car implements Alarm, Light { + alert() { + console.log('Car alert'); + } + lightOn() { + console.log('Car light on'); + } + lightOff() { + console.log('Car light off'); + } +} +``` + + + +接口与接口之间可以是继承关系: + +```ts +interface Alarm { + alert(): void; +} + +interface LightableAlarm extends Alarm { + lightOn(): void; + lightOff(): void; +} +``` + + + +常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的: + +```ts +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +interface Point3d extends Point { + z: number; +} +``` + + + +## 泛型 + +泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 + +泛型函数、泛型类、泛型接口、泛型类默认泛型 + +```ts +function createArray(length: number, value: T): Array { + let result: T[] = []; + for (let i = 0; i < length; i++) { + result[i] = value; + } + return result; +} + +createArray(3, 'x'); // ['x', 'x', 'x'] + +// or 自动推算 +createArray(3, 'x'); // ['x', 'x', 'x'] + +``` + + + +定义泛型的时候,可以一次定义多个类型参数: + +```ts +function swap(tuple: [T, U]): [U, T] { + return [tuple[1], tuple[0]]; +} + +swap([7, 'seven']); // ['seven', 7] +``` + + + +当然也可以使用含有泛型的接口来定义函数的形状: + +```ts +interface CreateArrayFunc { + (length: number, value: T): Array; +} +``` + + + +与泛型接口类似,泛型也可以用于类的类型定义中: + +```ts +class GenericNumber { + zeroValue: T; + add: (x: T, y: T) => T; +} + +let myGenericNumber = new GenericNumber(); +myGenericNumber.zeroValue = 0; +myGenericNumber.add = function(x, y) { return x + y; }; +``` + + + +在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。 + +```ts +function createArray(length: number, value: T): Array { + let result: T[] = []; + for (let i = 0; i < length; i++) { + result[i] = value; + } + return result; +} +``` + + + +## 声明合并 + +如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型: + +函数合并就是函数类型里提到的**重载** + + + +接口中的属性在合并时会简单的合并到一个接口中: + +```ts +interface Alarm { + price: number; +} +interface Alarm { + weight: number; +} +``` + +相当于: + +```ts +interface Alarm { + price: number; + weight: number; +} +``` + +注意,**合并的属性的类型必须是唯一的** + + + + + +类的合并与接口的合并规则一致。 + +# 参考 + +- [TypeScript 入门教程](https://ts.xcatliu.com/) diff --git "a/_posts/Tech/FrontEnd/2022-12-24-react\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2022-12-24-react\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..81b12c26db --- /dev/null +++ "b/_posts/Tech/FrontEnd/2022-12-24-react\347\254\224\350\256\260.md" @@ -0,0 +1,67 @@ +--- +layout: post +category: FrontEnd +title: react笔记 +tags: FrontEnd +--- + +# react笔记 + +> [React](https://web.qianguyihao.com/13-React%E5%9F%BA%E7%A1%80/01-React%E4%BB%8B%E7%BB%8D.html) + +## React 介绍 + +### React 是什么 + +- Facebook 开源的一个JS库。 +- 一个用于动态构建用户界面的JS库。 + +### React 的特点 + +- Declarative(声明式编码) +- Component-Based(组件化编码) +- Learn Once, Write Anywhere(支持客户端、服务器端渲染) +- 高效的DOM Diff算法,最小化页面重绘 +- 单向数据流 + +### React高效的原因 + +- 虚拟(virtual)DOM,不总是直接操作DOM +- 高效的DOM Diff算法,最小化页面重绘(即“局部渲染”)。 + +虚拟DOM指的是:在真实DOM的上一层**映射**一层虚拟DOM。我们操作的是映射关系,而不是真实的DOM。假设页面的样式做了修改(比如新增了一个标签),此时修改的是虚拟DOM的样式,真实的DOM并未发生变化。那什么时候,真实的DOM会发生变化呢? 当我把所有的内容操作完之后,转化为真实的DOM,此时要打包统一的渲染页面,于是真实的DOM发生变化,然后渲染一次。 这样做的话,可以减少页面的渲染次数。 + +## Hello world + +```html + + + + + + + Document + + + + + + + +
    + + + + + +``` diff --git "a/_posts/Tech/FrontEnd/2022-12-24-\346\250\241\346\235\277\345\274\225\346\223\216.md" "b/_posts/Tech/FrontEnd/2022-12-24-\346\250\241\346\235\277\345\274\225\346\223\216.md" new file mode 100644 index 0000000000..6bbaff032c --- /dev/null +++ "b/_posts/Tech/FrontEnd/2022-12-24-\346\250\241\346\235\277\345\274\225\346\223\216.md" @@ -0,0 +1,34 @@ +--- +layout: post +category: FrontEnd +title: 模板引擎 +tags: FrontEnd +--- + +## 模板引擎 + +## 什么是模板引擎? + +JavaScript 的模板引擎是什么,求解释,有例子 ? - 记得的回答 - 知乎 https://www.zhihu.com/question/53133191/answer/133637554 + + + +简单来讲,[模板引擎](https://www.zhihu.com/search?q=模板引擎&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A133811281})就是为了让动态页面渲染的时候,可以简化字符串的拼接操作的东西。 + +比如jsp或者go template那样的。 + + + +模板引擎和html无绑定关系,比如go的go template. + +jsp除外, jsp绑定了servlet容器。 + + + +## Freemarker + +FreeMarker 是一款 *模板引擎*: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。 + + + +模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, *不是* 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。 diff --git "a/_posts/Tech/FrontEnd/2023-01-30-sass\347\254\224\350\256\260.md" "b/_posts/Tech/FrontEnd/2023-01-30-sass\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..71f9123738 --- /dev/null +++ "b/_posts/Tech/FrontEnd/2023-01-30-sass\347\254\224\350\256\260.md" @@ -0,0 +1,204 @@ +--- +layout: post +category: FrontEnd +title: sass笔记 +tags: FrontEnd +--- + +## sass笔记 + +Sass (英文全称:Syntactically Awesome Stylesheets) 是一个最初由 Hampton Catlin 设计并由 Natalie Weizenbaum 开发的层叠样式表语言。 + +Sass 是一个 CSS 预处理器。 + +Sass 是 CSS 扩展语言,可以帮助我们减少 CSS 重复的代码,节省开发时间。 + +Sass 完全兼容所有版本的 CSS。 + +Sass 扩展了 CSS3,增加了规则、变量、混入、选择器、继承、内置函数等等特性。 + +Sass 生成良好格式化的 CSS 代码,易于组织和维护。 + +Sass 文件后缀为 **.scss**。 + +## Sass 实例 + +```scss +/* 定义变量与值 */ +$bgcolor: lightblue; +$textcolor: darkblue; +$fontsize: 18px; + +/* 使用变量 */ +body { + background-color: $bgcolor; + color: $textcolor; + font-size: $fontsize; +} +``` + +## 为什么使用 Sass? + +CSS 本身语法不够强大,导致重复编写一些代码,无法实现复用,而且在代码也不方便维护。 + +Sass 引入合理的样式复用机制,增加了规则、变量、混入、选择器、继承、内置函数等等特性。 + +## Sass 是如何工作的? + +浏览器并不支持 Sass 代码。因此,你需要使用一个 Sass 预处理器将 Sass 代码转换为 CSS 代码。 + +在命令行输入下面命令,即将 .scss 文件转化的 css 代码: + +``` +$ sass runoob-test.scss +``` + +我们可以在后面再跟一个 .css 文件名,将代码保存到文件中: + +``` +$ sass runoob-test.scss runoob-test.css +``` + +这是会在当前目录下生成 runoob-test.css 文件,代码如下: + +```css +@charset "UTF-8"; +/* 定义变量与值 */ +/* 使用变量 */ +body { + background-color: lightblue; + color: darkblue; + font-size: 18px; +} + +/*# sourceMappingURL=runoob-test.css.map */ +``` + +其实就是变量进行了替换。 + + + +## Sass 变量 + +变量用于存储一些信息,它可以重复使用。 + +Sass 变量可以存储以下信息: + +- 字符串 +- 数字 +- 颜色值 +- 布尔值 +- 列表 +- null 值 + +Sass 变量使用 **$** 符号: + +``` +$variablename: value; +``` + +Sass 变量的作用域只能在当前的层级上有效果,如下所示 h1 的样式为它内部定义的 green,p 标签则是为 red。 + +```scss +$myColor: red; + +h1 { + $myColor: green; // 只在 h1 里头有用,局部作用域 + color: $myColor; +} + +p { + color: $myColor; +} +``` + +当然 Sass 中我们可以使用 **!global** 关键词来设置变量是全局的: + +```scss +$myColor: red; + +h1 { + $myColor: green !global; // 全局作用域 + color: $myColor; +} +``` + +**注意:**所有的全局变量我们一般定义在同一个文件,如:**_globals.scss**,然后我们使用 **[@include](https://www.runoob.com/sass/sass-mixin-include.html)** 来包含该文件。 + + + +## Sass 嵌套规则与属性 + +Sass 嵌套 CSS 选择器类似于 HTML 的嵌套规则。 + +```scss +nav { + ul { + margin: 0; + padding: 0; + list-style: none; + } + li { + display: inline-block; + } + a { + display: block; + padding: 6px 12px; + text-decoration: none; + } +} +``` + + + +实例中,ul, li, 和 a 选择器都嵌套在 nav 选择器中 + +将以上代码转换为 CSS 代码,如下所示: + +```css +nav ul { + margin: 0; + padding: 0; + list-style: none; +} +nav li { + display: inline-block; +} +nav a { + display: block; + padding: 6px 12px; + text-decoration: none; +} + +``` + + + +属性也能嵌套 + +```scss +font: { + family: Helvetica, sans-serif; + size: 18px; + weight: bold; +} + +``` + + + +## 指令 + +### @import + +引入其它scss文件 + +### @minin和@include + +@mixin定义混入,作用在选择器上,混入上会有各种css属性定义,定义后,其它地方@include可以把混入的属性继承过来。 + +混入还支持参数,像函数一样。 + +### @extend + +继承另一个选择器的属性。 \ No newline at end of file diff --git "a/_posts/Tech/Go/2018-11-01-go\345\237\272\347\241\200\350\257\255\346\263\225.md" "b/_posts/Tech/Go/2018-11-01-go\345\237\272\347\241\200\350\257\255\346\263\225.md" index d567385d78..5517e02805 100644 --- "a/_posts/Tech/Go/2018-11-01-go\345\237\272\347\241\200\350\257\255\346\263\225.md" +++ "b/_posts/Tech/Go/2018-11-01-go\345\237\272\347\241\200\350\257\255\346\263\225.md" @@ -17,7 +17,7 @@ import ( 在 Printf 方法中,使用 %T 格式说明符(Format Specifier) -#### 包管理 +### 包管理 所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。 @@ -57,7 +57,7 @@ func main() { ``` -#### 运行Go程序 +### 运行Go程序 使用go run a.go @@ -68,7 +68,7 @@ go run *.go go install命令会安装程序,在工作区嗯bin目录下创建二进制文件,比如kitool,比如lift -#### init 函数 +### init 函数 所有包都可以包含一个 init 函数。init 函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它。 包的初始化顺序如下: @@ -106,11 +106,11 @@ Go 有着非常严格的强类型特征。Go 没有自动类型提升或默认 func variable() { - //集体定义 - var{ - a=3 - b=4 - } + //集体定义 + var{ + a=3 + b=4 + } //1. 指定类型 // 有默认值 var a int @@ -123,7 +123,7 @@ func variable() { var b=3 println(b) - //3. 省略var + //3. 省略var 就有:了 c:=3 println(c) } @@ -132,7 +132,7 @@ func variable() { 常量 */ func constVariable(){ - const a int=3; + const a int=3; //多类型简写 const c,d,e=1,false,4 //可以做枚举 @@ -174,7 +174,7 @@ struct String }; ``` -一个汉字3个字节。。。不知道为啥,本应该是4个字节的,这个到时再看下 +一个汉字3个字节,这是因为在UTF-8编码规则中,一个中文占3个字节 ```go func printBytes(s string) { @@ -184,9 +184,7 @@ func printBytes(s string) { } ``` -我们尝试输出 Señor 的字符,但却输出了错误的 S e à ± o r。 为什么程序分割 Hello World 时表现完美,但分割 Señor 就出现了错误呢?这是因为 ñ 的 Unicode 代码点(Code Point)是 U+00F1。它的 UTF-8 编码占用了 c3 和 b1 两个字节。它的 UTF-8 编码占用了两个字节 c3 和 b1。而我们打印字符时,却假定每个字符的编码只会占用一个字节,这是错误的。在 UTF-8 编码中,一个代码点可能会占用超过一个字节的空间。那么我们该怎么办呢?rune 能帮我们解决这个难题。 - -rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。让我们修改一下上面的程序,用 rune 来打印字符。 +rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。让我们修改一下上面的程序,用 rune 来打印字符。 rune就对应了utf8编码中的一个单位,utf8是变长编码,和unicode等定长不同 ```go func printChars(s string) { @@ -219,6 +217,7 @@ func main() { utf8 package 包中的 func RuneCountInString(s string) (n int) 方法用来获取字符串的长度。这个方法传入一个字符串参数然后返回字符串中的 rune 的数量。 ## 条件语句、循环语句 + ```go /* 运算符 @@ -395,7 +394,7 @@ func function() { /* 闭包 */ - //func getSequence() func() int { + //func getSequence() func() int { // return值是func() int // i:=0 // return func() int { // i+=1 @@ -461,7 +460,7 @@ func array() { 指针的零值是 nil。 -**不要向函数传递数组的指针,而应该使用切片** +**不要向函数传递数组的指针,而应该使用切片** 因为数组是数组拷贝。 假如我们想要在函数内修改一个数组,并希望调用函数的地方也能得到修改后的数组,一种解决方案是把一个指向数组的指针传递给这个函数。 @@ -614,9 +613,9 @@ func structStatement() { //func printBook( book Books ) //结构体指针 - //var struct_pointer *Books + var struct_pointer *Books //使用结构体指针访问结构体成员,使用 "." 操作符: - //struct_pointer.title; + struct_pointer.title; } @@ -643,7 +642,7 @@ func structStatement() { 在其他的所有情况,值接收器都可以被使用。 -注意给值接收器传递指针,那也是更改不可见的,只有指针接收器才可以更改可见 +**注意给值接收器传递指针,那也是更改不可见的,只有指针接收器才可以更改可见**。能不能更改可见以接收器类型为准。 ```go type Employee struct { @@ -682,10 +681,11 @@ func append(slice []Type, elems ...Type) []Type 传入后,当成一个数组,range访问就好了 -## 切片 +## 切片slice slice类型的底层同样是一个C struct。 +``` struct Slice { // must not move anything byte* array; // actual data @@ -693,13 +693,16 @@ struct Slice uintgo cap; // allocated number of elements }; -append 函数的定义是 func append(s[]T,x ... T)[]T。 + +``` + +append 函数的定义是` func append(s[]T,x ... T)[]T`。 我们可以认为,切片在内部可由一个结构体类型表示。这是它的表现形式, -是引用传递,要同步内容修改的 +**是引用传递,要同步内容修改的** -如果切片由数组支持,并且数组本身的长度是固定的,那么切片如何具有动态长度。以及内部发生了什么,当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片的两倍。 +当新的元素被添加到切片时,如果容量不够,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片的两倍。 ```go type slice struct { @@ -752,7 +755,7 @@ func slice() { } /* -范围 range +遍历 range */ func rangeStatement() { @@ -764,10 +767,20 @@ func rangeStatement() { //这是我们使用range去求一个slice的和。使用数组跟这个很类似 nums := []int{2, 3, 4} sum := 0 + // only value for _, num := range nums { sum += num } + // index, value + nums := []string{"a", "b", "c"} + for k,v := range nums{ + println(k,v) // index, value + } + // only index + for k := range nums{ + println(k) // only index + } } ``` @@ -776,7 +789,7 @@ func rangeStatement() { map 的零值是 nil。如果你想添加元素到 nil map 中,会触发运行时 panic。因此 map 必须使用 make 函数初始化。 -也就是说必须使用Make +也就是说必须使用`make` 获取 map 的长度使用 len 函数。 @@ -791,7 +804,7 @@ map func mapStatement() { /* 声明变量,默认 map 是 nil */ //var map_variable map[key_data_type]value_data_type - //nil map 不能用来存放键值对 + //nil map 不能用来存放键值对,会panic /* 使用 make 函数 */ //map_variable := make(map[key_data_type]value_data_type) @@ -825,7 +838,7 @@ func mapStatement() { -## 接口 +## 接口interface 接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。 @@ -849,14 +862,14 @@ func assert(i interface{}) { */ func interfaceStatement() { /* - type Phone interface { - call() + type Phone interface { + call() //接口的方法声明 } type NokiaPhone struct { } - func (nokiaPhone NokiaPhone) call() { + func (nokiaPhone NokiaPhone) call() { //实现了这个接口 fmt.Println("I am Nokia, I can call you!") } @@ -881,7 +894,9 @@ func interfaceStatement() { } ``` -注意接口赋值问题 +注意接口赋值问题。 + +如果是A的指针类型了实现了接口B,那A的非指针对象是不能赋值给非指针对象B的,只能A的指针对象复制给非指针对象B。看是否实现时是区分是指针版本实现的还是非指针实现的,只有对应实现的才能子类转父类。如下。 ```go type Describer interface { @@ -926,7 +941,7 @@ func main() { //d2 = a d2 = &a // 这是合法的 - // 因为在第 22 行,Address 类型的指针实现了 Describer 接口 + // 因为是Address 类型的指针实现了 Describer 接口 d2.Describe() } @@ -981,7 +996,5 @@ func errorHandle() { */ } -func main() { -} ``` diff --git "a/_posts/Tech/Go/2019-09-11-go\344\271\213map.md" "b/_posts/Tech/Go/2019-09-11-go\344\271\213map.md" index 77cad12bdd..c2e4b64395 100644 --- "a/_posts/Tech/Go/2019-09-11-go\344\271\213map.md" +++ "b/_posts/Tech/Go/2019-09-11-go\344\271\213map.md" @@ -15,7 +15,7 @@ Go中的map在底层是用哈希表实现的,你可以在 $GOROOT/src/pkg/runt map由结构体hmap构成 -**hmap** +**hmap** 基本的map ```go type hmap struct { @@ -35,7 +35,7 @@ type hmap struct { } ``` -**bmap结构体** +**bmap结构体** 是一个bucket ```go // A bucket for a Go map. @@ -63,21 +63,47 @@ map 的一个关键点在于,哈希函数的选择。在程序启动时,会 扩容 ---- -### 负载因子引起:增量扩容 +### 负载因子过大引起增量扩容 + +扩容hash表的时候每次都增大2倍,hash表大小始终为2的整数倍,有(hash mod 2^B) == (hash & (2^B-1)),方便于简化运算,避免取余操作 + +扩容后需要将old pair重新hash到新的hash表上(就是一个evacuate的过程)。这个过程不是一次性完成的,**在每次insert、remove的时候会搬移1-2个pair**。就是使用的是增量扩容 + + 负载因子:填入表中的元素个数 / 散列表的长度。表示hash冲突机会大小。如果负载因子越大,则说明空间使用率越高,但产生哈希冲突的可能性更高。而负载因子越小,说明空间使用率低,产生哈希冲突的可能性更低 -在go语言中如果载荷因子大于6.5就需要扩容了。每次扩容2倍 +在go语言中如果载荷因子大于**6.5**就需要扩容了。每次扩容2倍 + + + +> 为什么要使用增量扩容 + +主要是**缩短map容器的响应时间**。如果不用增量扩容,当一个map存储很多元素后进行扩容,会阻塞很长时间无法响应请求。增量扩容的本质其实就是**将总的扩容时间分摊到了每一次hash操作上** + +在搬数据的时候,**并不会把旧的bucket从oldbucket中删除,只是加上了一个已删除的标记** + +扩容期间一部分数据在oldbucket中,一部分在bucket中,会对hash表的insert,remove,lookup操作的处理逻辑产生影响,如耗时更长等 + +只有当oldbucket中所有bucket移动到新表后,才会将oldbucket释放掉 + + + +### 溢出桶 `overflow buckets` 过多引起等量扩容 + +如果**负载因子没有超标,但是使用的溢出桶较多,也会触发扩容**。但是是**等量扩容** + + + +判断溢出桶是否太多,当桶总数 < 2 ^ 15 时,如果溢出桶总数 >= 桶总数,则认为溢出桶过多。当桶总数 >= 2 ^ 15 时,直接与 2 ^ 15 比较,当溢出桶总数 >= 2 ^ 15 时,即认为溢出桶太多了。buckets数量维持不变,该方法我们称之为**等量扩容**。 -### 溢出桶 `overflow buckets` 过多引起扩容 -判断溢出桶是否太多,当桶总数 < 2 ^ 15 时,如果溢出桶总数 >= 桶总数,则认为溢出桶过多。当桶总数 >= 2 ^ 15 时,直接与 2 ^ 15 比较,当溢出桶总数 >= 2 ^ 15 时,即认为溢出桶太多了。buckets数量维持不变,将长度过长的溢出桶搬运到[]bmap的其他桶上,该方法我们称之为**等量扩容**。 -其实应该叫缩容 +原因是原桶中有太多的键值对被删除,等量扩容可以使得剩余的键值对排列更加紧凑,节省空间 ### 渐进式扩容 -由于 map 扩容需要将原有的 key/value 重新搬迁到新的内存地址,如果有大量的 key/value 需要搬迁,会非常影响性能。因此 Go map 的扩容采取了一种称为“渐进式”地方式,原有的 key 并不会一次性搬迁完毕,每次最多只会搬迁 2 个 bucket。 +由于 map 扩容需要将原有的 key/value 重新搬迁到新的内存地址,如果有大量的 key/value 需要搬迁,会非常影响性能。因此 Go map 的扩容采取了一种称为“渐进式”地方式,原有的 key 并不会一次性搬迁完毕,每次最多只会搬迁 2 个 bucket。1-2个每次 @@ -87,15 +113,15 @@ map 的一个关键点在于,哈希函数的选择。在程序启动时,会 map 遍历的核心在于理解 2 倍扩容时,老 bucket 会分裂到 2 个新 bucket 中去。而遍历操作,会按照新 bucket 的序号顺序进行,碰到老 bucket 未搬迁的情况时,要在老 bucket 中找到将来要搬迁到新 bucket 来的 key。 -## 缩容 +## 缩容 释放删除标志位对应空间 缩容是伪缩容。缩容时hash表容量不变。因为缩容仅仅针对溢出桶太多的情况,触发缩容时hash数组的大小不变,即hash数组所占用的空间只增不减。也就是说,如果我们把一个已经增长到很大的map的元素挨个全部删除掉,hash表所占用的内存空间也不会被释放。 触发缩容:溢出的桶数量noverflow>=32768(1<<15)或者>=hash数组大小。 -做了啥:delete操作只置删除标志位,释放溢出桶的空间依靠触发缩容来实现。 +何时发生:delete操作只置删除标志位,释放溢出桶的空间依靠触发缩容来实现。 -如果要实现“真缩容”,需自己实现缩容搬迁,即创建一个较小的map,将需要缩容的map的元素挨个搬迁过来: +如果要实现“真缩容”,需自己实现缩容搬迁,即创建一个较小的map,将需要缩容的map的元素挨个搬迁过来。 目前没有。 @@ -126,7 +152,7 @@ do { //对每个桶b ``` -插入过程分析 +插入过程 ------ 1. 根据key算出hash值,进而得出对应的bucket。 diff --git "a/_posts/Tech/Go/2019-12-25-go\344\271\213slice.md" "b/_posts/Tech/Go/2019-12-25-go\344\271\213slice.md" index f98784cf3f..7be70f48c1 100644 --- "a/_posts/Tech/Go/2019-12-25-go\344\271\213slice.md" +++ "b/_posts/Tech/Go/2019-12-25-go\344\271\213slice.md" @@ -100,6 +100,7 @@ sliceHeader.Len = length sliceHeader.Data = uintptr(ptr) ``` +## QA - +- 非线程安全,但不会像map那样检查写标志位而panic 并发append会索引覆盖 丢增加的数据。 diff --git "a/_posts/Tech/Go/2020-12-27-golang\351\224\201\345\217\212\345\271\266\345\217\221\345\256\211\345\205\250\347\273\223\346\236\204.md" "b/_posts/Tech/Go/2020-12-27-golang\351\224\201\345\217\212\345\271\266\345\217\221\345\256\211\345\205\250\347\273\223\346\236\204.md" index 3dfea27ea1..f3c74c3f83 100644 --- "a/_posts/Tech/Go/2020-12-27-golang\351\224\201\345\217\212\345\271\266\345\217\221\345\256\211\345\205\250\347\273\223\346\236\204.md" +++ "b/_posts/Tech/Go/2020-12-27-golang\351\224\201\345\217\212\345\271\266\345\217\221\345\256\211\345\205\250\347\273\223\346\236\204.md" @@ -44,6 +44,16 @@ CAS即Compare And Swap(比较与交换),是一种有名的无锁算法。 > > [go设计与原理](https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-sync-primitives/) +```go + a := sync.Mutex{} + a.Lock() + a.Unlock() +``` + + + + + Golang Mutex其实是不断改进的,截止到目前为至主要改进了4版: - V1: 简单实现,按照FIFO的方式来加锁、解锁的,cpu切换效率低 @@ -55,6 +65,20 @@ Golang Mutex其实是不断改进的,截止到目前为至主要改进了4版 +```scala +type Mutex struct { + state int32 + sema int32 +} + +``` + +sema是信号量,这是真正的导致goroutine被阻塞和唤醒的原因。 + +基于atomic CAS来操作state + + + 在默认情况下,互斥锁的所有状态位都是 0,`int32` 中的不同位分别表示了不同的状态: - `mutexLocked` — 表示互斥锁的锁定状态; @@ -106,6 +130,16 @@ Goroutine 进入自旋的条件非常苛刻: ## Sync.RWMutex +```go + b := sync.RWMutex{} + b.Lock() + b.Unlock() + b.RLock() + b.RUnlock() +``` + + + [`sync.RWMutex`](https://draveness.me/golang/tree/sync.RWMutex) 中总共包含以下 5 个字段: ```go @@ -138,7 +172,7 @@ type RWMutex struct { func (rw *RWMutex) Lock() { // mutex获取, 其他goroutine只能自旋或者休眠 rw.w.Lock() - // readercount 变成负数,阻塞后续读操作 + // readercount 变成负数,阻塞后续读操作。让读写公平!! r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 如果仍然有其他 Goroutine 持有互斥锁的读锁,该 Goroutine 会调用 runtime.sync_runtime_SemacquireMutex 进入休眠状态等待所有读锁所有者执行结束后释放 writerSem 信号量将当前协程唤醒; if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { @@ -204,6 +238,19 @@ func (rw *RWMutex) RUnlock() { ## Sync.WaitGroup +```go + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(1 * time.Second) + }() + wg.Wait() + println("end") +``` + + + - [`sync.WaitGroup`](https://draveness.me/golang/tree/sync.WaitGroup) 必须在 [`sync.WaitGroup.Wait`](https://draveness.me/golang/tree/sync.WaitGroup.Wait) 方法返回之后才能被重新使用; - [`sync.WaitGroup.Done`](https://draveness.me/golang/tree/sync.WaitGroup.Done) 只是对 [`sync.WaitGroup.Add`](https://draveness.me/golang/tree/sync.WaitGroup.Add) 方法的简单封装,我们可以向 [`sync.WaitGroup.Add`](https://draveness.me/golang/tree/sync.WaitGroup.Add) 方法传入任意负数(需要保证计数器非负)快速将计数器归零以唤醒等待的 Goroutine; - 可以同时有多个 Goroutine 等待当前 [`sync.WaitGroup`](https://draveness.me/golang/tree/sync.WaitGroup) 计数器的归零,这些 Goroutine 会被同时唤醒; @@ -218,6 +265,16 @@ func (rw *RWMutex) RUnlock() { ## Sync.Map +```go + a := sync.Map{} + a.Store("key", "value") + val, ok := a.Load("key") + println(ok, val) + println(val.(string)) +``` + + + sync.Map的原理很简单,使用了空间换时间策略,通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。 通过引入两个map将读写分离到不同的map,其中read map提供并发读和已存元素原子写,而dirty map则负责读写。 diff --git "a/_posts/Tech/Go/2021-05-07-go\345\271\266\345\217\221\347\274\226\347\250\213\351\242\230\347\233\256.md" "b/_posts/Tech/Go/2021-05-07-go\345\271\266\345\217\221\347\274\226\347\250\213\351\242\230\347\233\256.md" index 8bf05d3b5b..1a3595c004 100644 --- "a/_posts/Tech/Go/2021-05-07-go\345\271\266\345\217\221\347\274\226\347\250\213\351\242\230\347\233\256.md" +++ "b/_posts/Tech/Go/2021-05-07-go\345\271\266\345\217\221\347\274\226\347\250\213\351\242\230\347\233\256.md" @@ -12,27 +12,31 @@ tags: Go [参考](https://www.jishuchi.com/read/go-interview/3439) ```go -func groutine1(p chan int) { - for i := 1; i <= POOL; i++ { - p <- i - if i%2 == 1 { - fmt.Println("groutine-1:", i) - } - } +var ( + POOL = 100 +) + +func groutine1(dep, nxt chan int) { + for i := 1; i <= POOL; i += 2 { + <-dep + fmt.Println("groutine-1:", i) + nxt <- 1 + } } -func groutine2(p chan int) { - for i := 1; i <= POOL; i++ { - <-p - if i%2 == 0 { - fmt.Println("groutine-2:", i) - } - } +func groutine2(dep, nxt chan int) { + for i := 2; i <= POOL; i += 2 { + <-dep + fmt.Println("groutine-2:", i) + nxt <- 1 + } } func main() { - msg := make(chan int) - go groutine1(msg) - go groutine2(msg) - time.Sleep(time.Second * 1) + c1 := make(chan int) + c2 := make(chan int) + go groutine1(c1, c2) + go groutine2(c2, c1) + c1 <- 1 + time.Sleep(time.Second * 1) } ``` @@ -179,6 +183,8 @@ select { [参考](https://segmentfault.com/a/1190000040092635) +重入锁就是一个user可以多次Lock 最后UnLock和Lock数量一致就解锁。 + 实现一个可重入锁需要这两点: - 记住持有锁的线程,比如user diff --git "a/_posts/Tech/Go/2021-05-23-go\344\271\213channel.md" "b/_posts/Tech/Go/2021-05-23-go\344\271\213channel.md" index 8b8797ec6f..126bc32184 100644 --- "a/_posts/Tech/Go/2021-05-23-go\344\271\213channel.md" +++ "b/_posts/Tech/Go/2021-05-23-go\344\271\213channel.md" @@ -9,6 +9,37 @@ tags: Go [Go 语言问题集(Go Questions)](https://www.bookstack.cn/books/qcrao-Go-Questions) + + + + +```go + // 记法 <-chan<- 箭头一直朝左, 读取<-c1<-赋值 + var c chan int + var c1 <-chan int + var c2 chan<- int + c = make(chan int, 1) + c1 = make(<-chan int, 2) + + // 写入 + c <- 3 + // 读取 + d := <-c1 + <-c1 + + select { + case c <- 3: + return + case e := <-c: + println(e) + return + default: + return + } +``` + + + ## Channel - CSP,Communicating Sequential Processes, 通信顺序处理。不要通过共享内存来通信,而要通过通信来实现内存共享。相比于内存共享(如mutex),不需要考虑数据所有权,不需要保护结构的内部状态,不需要协调多个逻辑。 diff --git "a/_posts/Tech/Go/2022-04-24-go\344\271\213QA.md" "b/_posts/Tech/Go/2022-04-24-go\344\271\213QA.md" index d7782534d0..00fc1747ef 100644 --- "a/_posts/Tech/Go/2022-04-24-go\344\271\213QA.md" +++ "b/_posts/Tech/Go/2022-04-24-go\344\271\213QA.md" @@ -42,7 +42,11 @@ reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind() reflect.DeepEqual(a, b interface{}) ``` -- 将一个interface{}赋值给另一个interface{} + + + + +其他:将一个interface{}赋值给另一个interface{} ``` reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b)) @@ -112,9 +116,9 @@ func main() { 逃逸场景: -- 指针逃逸 +- 指针逃逸 返回局部变量指针 - 栈空间不足逃逸 -- 动态类型逃逸 +- 动态类型逃逸 函数参数为 interface{},编译期间很难确定其参数的具体类型,也会发生逃逸。 - 闭包引用对象逃逸 小结: @@ -173,3 +177,11 @@ type Context interface { - context是并发安全的 + +### 自定义M/P/G上限数量? + +M默认limit 10000 可自定义,但一般不需要。 + +P就是GOMAXPROCS, 可自定义,默认cpu核数 + +G数量,无限制。不可自定义。但受内存影响,可监控。 diff --git a/_posts/Tech/IDE/2022-12-05-Intellij idea Project Structure.md b/_posts/Tech/IDE/2022-12-05-Intellij idea Project Structure.md new file mode 100644 index 0000000000..a25c51a7cc --- /dev/null +++ b/_posts/Tech/IDE/2022-12-05-Intellij idea Project Structure.md @@ -0,0 +1,92 @@ +--- +layout: post +category: IDE +title: Intellij idea Project Structure +tags: IDE +--- + +## Intellij idea Project Structure + +mac上cmd+; 快捷键唤醒。File菜单栏也可以唤醒。 + +## Project和Module区别 + +一个Project代表一个完整的APP,Module表示APP中的一些依赖库或独立开发的模块。比如可以新建一个library做为module。 + +一个模块为一个Module。 + + + + + +## Language level和sdk区别 + +> 限定项目编译检查时最低要求的 JDK 特性。 Language level <= sdk。 + +当我们使用 JDK 8 的时候,我们只能向下兼容 JDK 8 及其以下的特性,所以只能选择 8 及其以下的 `language level`。所以当我们项目使用的是 JDK 8,但是代码却没有使用 JDK 8 的新特性,最多使用了 JDK 7 的特性的时候我们可以选择 `7 - Diamonds,ARM,multi-catch etc.`。 + +对此我们总结 `language level`:限定项目编译检查时最低要求的 JDK 特性。 + +现在假设我们有一个项目代码使用的 JDK 8 新特性:`lambda` 语法,但是 JDK 选择的却是 JDK 7,即使 `language level` 选择了 `8 - Lambdas,type annotation etc.`,也是没有多大意义的,一样会编译报错。 + + + +## Project Structure设置 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212052232428.png) + +Project 项目级别的,下面每个Module还可以自己有 + +- Project Name +- SDK +- Language level +- 编译输出路径 + + + +Modules + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212052235310.png) + +- Souces:这里对Module的开发目录进行文件夹分类,就是说这个module里有什么内容,说明了不同性质的内容放在哪里。 + 注意,这些不同内容的标记代表了一个标准Java工程的各项内容,IntelliJ就是根据这些标记来识别一个Java工程的各项内容的,比如,它会用javac去编译标记为Sources的源码,打包的时候会把标记为Resources的资源拷贝到jar包中,并且忽略标记为Exluded的内容。左边显示的是在选中内容的预览。 +- Paths:为模块配置编译器输出路径,还可以指定与模块关联的外部JavaDocs和外部注释的位置。 +- Dependencies:在此选项卡上,您可以定义模块的SDK并形成模块依赖关系列表。要将项目SDK与模块相关联,请选择Project SDK。请注意,如果稍后更改了项目SDK,模块SDK将相应更改。**需要保证Dependencies下Add Library时为空,如果有就都Add selected.** + + + +Library + +- 项目级别的类库,这里先加好,然后Module里加jar的话就可以直接选了,相当于统一管理。 + + + +Facets + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212052245804.png) + +- 表示这个 module 有什么特征,比如 Web,Spring 和 Hibernate 等; + + + + + +Artifact + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212052248695.png) + +- Artifact 是 maven 中的一个概念,表示某个 module 要如何打包,例如 war exploded、war、jar、ear 等等这种打包形式; + 一个 module 有了 Artifacts 就可以部署到应用服务器中了! + 在给项目配置 Artifacts 的时候有好多个 type 的选项,exploed 是什么意思? + explode 在这里你可以理解为展开,不压缩的意思。也就是 war、jar 等产出物没压缩前的目录结构。建议在开发的时候使用这种模式,便于修改了文件的效果立刻显现出来。默认情况下,IDEA 的 Modules 和 Artifacts 的 output 目录 已经设置好了,不需要更改, + 打成 war 包 的时候会自动在 WEB-INF 目录 下生产 classes 目录 ,然后把编译后的文件放进去。 + +**最上面的截图下面Platform Settings和Project, Module都无关!** + + + +添加一个Lib方法 + +1. 方法1: 更推荐。在project structure的Libraries加入lib,然后再走方法2,选择Project Lib即可。 +2. 方法2: 在Modules的Dependencies下添加lib + diff --git "a/_posts/Tech/Java/2018-02-02-java\350\277\236\346\216\245mysql.md" "b/_posts/Tech/Java/2018-02-02-java\350\277\236\346\216\245mysql.md" deleted file mode 100644 index 8fb9a72bc6..0000000000 --- "a/_posts/Tech/Java/2018-02-02-java\350\277\236\346\216\245mysql.md" +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: post -category: Java -title: java连接Mysql例子 -tags: Database ---- - -intellij idea添加jar,参考学习中blog - -## 连接 -```java -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -public class DBHelper { - public static final String url = "jdbc:mysql://127.0.0.1/testdb"; - public static final String name = "com.mysql.jdbc.Driver"; - public static final String user = "root"; - public static final String password = "123456"; - - public Connection conn = null; - public PreparedStatement pst = null; - - public DBHelper(String sql) { - try { - Class.forName(name);//指定连接类型 - conn = DriverManager.getConnection(url, user, password);//获取连接 - pst = conn.prepareStatement(sql);//准备执行语句 - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void close() { - try { - this.conn.close(); - this.pst.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } -} -``` - -## 查询(主类) -```java -import java.sql.ResultSet; -import java.sql.SQLException; - -public class Demo { - - static String sql = null; - static DBHelper db1 = null; - static ResultSet ret = null; - - public static void main(String[] args) { - sql = "select * from employee";//SQL语句 - db1 = new DBHelper(sql);//创建DBHelper对象 - - try { - ret = db1.pst.executeQuery();//执行语句,得到结果集 - while (ret.next()) { - String uid=ret.getString(1); - String ufname = ret.getString(2); - String ulname = ret.getString(3); - String udate = ret.getString(4); - System.out.println(uid + "\t" + ufname + "\t" + ulname + "\t" + udate ); - }//显示数据 - ret.close(); - db1.close();//关闭连接 - } catch (SQLException e) { - e.printStackTrace(); - } - } - -} -``` \ No newline at end of file diff --git "a/_posts/Tech/java/2018-02-04-java\345\217\215\345\260\204.md" "b/_posts/Tech/Java/2018-02-04-java Class\345\257\271\350\261\241\345\217\212\345\217\215\345\260\204.md" similarity index 70% rename from "_posts/Tech/java/2018-02-04-java\345\217\215\345\260\204.md" rename to "_posts/Tech/Java/2018-02-04-java Class\345\257\271\350\261\241\345\217\212\345\217\215\345\260\204.md" index 4e1c7c5aba..0c3cbfee90 100644 --- "a/_posts/Tech/java/2018-02-04-java\345\217\215\345\260\204.md" +++ "b/_posts/Tech/Java/2018-02-04-java Class\345\257\271\350\261\241\345\217\212\345\217\215\345\260\204.md" @@ -1,13 +1,11 @@ --- layout: post category: Java -title: java反射 +title: java Class对象及反射 tags: Java --- -[链接](http://blog.csdn.net/javazejian/article/details/70768369?utm_source=gold_browser_extension) - -## 反射 +# 反射 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 @@ -39,11 +37,90 @@ Reflection is powerful, but should not be used indiscriminately. If it is possib > [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
    [深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/) -## Class对象 +# Class对象 + +获取Class的方法有三种: + +1.Class.forName("类名"); 通过类名字符串获取Class对象。 + +2.通过类的对象调用getClass() 获取该类型的Class对象 + +3.通过类型直接获取Class对象。 类名.class (类字面常量) + + + +Example: + +```java +// Class支持泛型 +Class integerClass = Integer.class; +// 注意这里能拿到的是Class, 和Class.forName返回的一样 +Class c1 = Integer.valueOf(3).getClass(); +``` + + + +Class类是什么? + +1. Class类也是类的一种,与class关键字是不一样的。 +2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。 +3. 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。 +4. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载 +5. Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。 + + + +类加载的过程,如下: + +1. 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象 + +2. 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例1. 成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。 + +3. 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。 + + + +Note: + +- 使用字面常量的方式获取Class对象的引用不会触发类的初始化。 我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。 +- 实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段 + + + +关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化: + +1. 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。 +2. 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。 +3. 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。 +4. 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类 [参考我的另一个博客:深入理解Class对象](https://mafulong.github.io/java/2018/10/23/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Class%E5%AF%B9%E8%B1%A1.html) + + +# 类型转换 + +在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型 + +之所可以强制转换,这得归功于RTTI(Run-Time Type Identification)运行时类型识别,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常ClassCastException。 + + + +关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)。 注意子类对象也是父类类型的一个实例,返回true. + +```java +//判断这个对象是不是这种类型 +obj.instanceof(class) +//判断这个对象能不能被转化为这个类 +class.inInstance(obj) +``` + +功能上它们是等价的。 + +# 反射用法 + ## Constructor类及其用法 + ```java //获取Class对象的引用 Class clazz = Class.forName("reflect.User"); diff --git "a/_posts/Tech/Java/2018-03-26-jdbc\346\227\266\351\227\264\351\224\231\350\257\257.md" "b/_posts/Tech/Java/2018-03-26-jdbc\346\227\266\351\227\264\351\224\231\350\257\257.md" deleted file mode 100644 index df3fdc5bf1..0000000000 --- "a/_posts/Tech/Java/2018-03-26-jdbc\346\227\266\351\227\264\351\224\231\350\257\257.md" +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -category: Java -title: jdbc时间错误 -tags: Database ---- - -## 错误 -com.mysql.cj.core.exceptions.InvalidConnectionAttributeException: The server time zone value '�й���׼ - -## 解决 -```java - public static final String URL="jdbc:mysql://localhost:3306/new?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"; - -``` \ No newline at end of file diff --git "a/_posts/Tech/java/2018-05-28-Object\351\200\232\347\224\250\346\226\271\346\263\225.md" b/_posts/Tech/Java/2018-05-28-Object.md similarity index 73% rename from "_posts/Tech/java/2018-05-28-Object\351\200\232\347\224\250\346\226\271\346\263\225.md" rename to _posts/Tech/Java/2018-05-28-Object.md index 62ffbce160..7bd9a9924a 100644 --- "a/_posts/Tech/java/2018-05-28-Object\351\200\232\347\224\250\346\226\271\346\263\225.md" +++ b/_posts/Tech/Java/2018-05-28-Object.md @@ -1,7 +1,7 @@ --- layout: post category: Java -title: Object通用方法 +title: Object tags: Java --- @@ -363,4 +363,95 @@ CloneConstructorExample e1 = new CloneConstructorExample(); CloneConstructorExample e2 = new CloneConstructorExample(e1); e1.set(2, 222); System.out.println(e2.get(2)); // 2 +``` + +# Ojbects util + + + +## Objects 与 Object 区别 + +Object 是 Java 中所有类的基类,位于java.lang包。 + +Objects 是 Object 的工具类,位于java.util包。它从jdk1.7开始才出现,被final修饰不能被继承,拥有私有的构造函数。 它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。 + +## Objects 各方法介绍与分析 + +### equals + +equals方法是判断两个对象是否相等。 + +在比较两个对象的时候,Object.equals方法容易抛出空指针异常。 + +——我刚上班的时候,有位老员工教我“字符串常量与变量对象比较的时候,常量要写在equals外边,变量放在equals()括号里边。” 就是这个原因。 + +如果是两个变量比较的时候,就都需要加非空判断。 + +Object.equals方法内调用的是return (this == obj)。String类中是依据字符串内容是否相等来重定义了equals方法。 + +现在,Objects.equals方法中已经做了非空判断,所以不会抛出空指针异常,它是null-save空指针安全的,而且也可以简化代码。 + +``` + public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } +``` + +### deepEquals + +顾名思义,深度比较两个对象。 + +当参数是数组对象,其方法内部采用的是Arrays.deepEquals()方法的算法。 + +使用Objects.deepEquals方法有个好处,当我们在写业务代码时,可以直接使用此方法来判断两个复杂类型, + +比如使用了泛型的列表对象`List`、或者通过反射得到的对象,不清楚对象的具体类型。 + +``` +public static boolean deepEquals(Object a, Object b) { + if (a == b) + return true; + else if (a == null || b == null) + return false; + else + return Arrays.deepEquals0(a, b); + } +``` + +简短的说明下Arrays.deepEquals0方法: + +- 如果参数是Object类型的数组,则调用Arrays.deepEquals方法,在参数数组的循环中,递归调用deepEquals0,直到出现不相同的元素,或者循环结束; +- 如果参数是基本类型的数组,则根据该类型调用Arrays.equals方法。Arrays工具类依照八种基本类型对equals方法做了重载。 + +### hashCode + +``` + public static int hashCode(Object o) { + return o != null ? o.hashCode() : 0; + } +``` + +### hash + +为一系列的输入值生成哈希码,该方法的参数是可变参数。 源码如下: + +``` + public static int hash(Object... values) { + return Arrays.hashCode(values); + } +``` + +它是将所有的输入值都放到一个数组,然后调用Arrays.hashCode(Object[])方法来实现哈希码的生成。 + +对于当一个对象包含多个成员,重写Object.hashCode方法时,hash方法非常有用。 举个Java源码中的例子: + +java.lang.invoke.MemberName 类,该类有Class clazz、String name、Object type、int flags、Object resoulution这几个成员变量, + +该类的hashCode方法如下: + +``` + @Override + public int hashCode() { + return Objects.hash(clazz, getReferenceKind(), name, getType()); + } ``` \ No newline at end of file diff --git a/_posts/Tech/java/2018-05-31-java String.md b/_posts/Tech/Java/2018-05-31-java String.md similarity index 77% rename from _posts/Tech/java/2018-05-31-java String.md rename to _posts/Tech/Java/2018-05-31-java String.md index e5899f4ae4..8c46969be5 100644 --- a/_posts/Tech/java/2018-05-31-java String.md +++ b/_posts/Tech/Java/2018-05-31-java String.md @@ -12,6 +12,52 @@ String fs; fs=String.format("%d is 3",3); ``` +String format()方法的语法为: + +``` +String.format(String format, Object... args) +``` + +### 格式说明符 + +以下是常用的格式说明符: + +| 说明符 | 描述 | +| :----- | :-------------------------- | +| %b, %B | 根据参数为“ true”或“ false” | +| %s, %S | 一个字符串, 也可以是其他,万能匹配 | +| %c, %C | Unicode字符 | +| %d | 十进制整数(仅用于整数) | +| %f | 用于十进制数字(用于浮点数) | + +不知道用啥就用%s + + + +### 十进制数的格式 + +示例 + +```java +class Main { + public static void main(String[] args) { + + float n1 = -452.534f; + double n2 = -345.766d; + + //按原样格式化浮点数 + System.out.println(String.format("n1 = %f", n1)); // -452.533997 + System.out.println(String.format("n2 = %f", n2)); // -345.766000 + + //显示到小数点后两位 + System.out.println(String.format("n1 = %.2f", n1)); // -452.53 + System.out.println(String.format("n2 = %.2f", n2)); // -345.77 + } +} +``` + + + ## String, StringBuffer and StringBuilder **1. 是否可变** diff --git "a/_posts/Tech/Java/2018-08-13-java\347\232\204redis\346\223\215\344\275\234.md" "b/_posts/Tech/Java/2018-08-13-java\347\232\204redis\346\223\215\344\275\234.md" deleted file mode 100644 index 68ade915ff..0000000000 --- "a/_posts/Tech/Java/2018-08-13-java\347\232\204redis\346\223\215\344\275\234.md" +++ /dev/null @@ -1,251 +0,0 @@ ---- -layout: post -category: Java -title: java的redis操作 ---- - -## 安装 -### win10 -[下载地址](https://github.com/MicrosoftArchive/redis/releases) - -安装目录下运行 redis-server.exe redis.windows.conf 。 - -redis-cli.exe -h 127.0.0.1 -p 6379 - -设置键值对 set myKey abc - -取出键值对 get myKey - - -server是服务器启动,cli是客户端 - -## Jedis - -### Related jars -```xml - - - junit - junit - 4.12 - - - redis.clients - jedis - 2.9.0 - - - org.apache.commons - commons-pool2 - 2.6.0 - - -``` - -### connection and test -```java -package com.mfl; - -import org.junit.Test; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; - -/** - * jedis - */ -public class Demo1 { - @Test - /** - * connection test1 - */ - public void t1(){ - //1. 设置ip和端口 - Jedis jedis=new Jedis("127.0.0.1",6379); - //2. 保存数据 - jedis.set("name","mafulong"); - //3. get the data - String value=jedis.get("name"); - System.out.println(value); - jedis.close(); - } - @Test - /** - * pool test - */ - public void t2(){ - JedisPoolConfig config=new JedisPoolConfig(); - //设置最大连接数 - config.setMaxTotal(30); - //最大空闲连接数 - config.setMaxIdle(10); - //获得连接池 - JedisPool jedisPool=new JedisPool(config,"127.0.0.1",6379); - //获得核心对象 - Jedis jedis=null; - try{ - jedis=jedisPool.getResource(); - jedis.set("name","mafulong2"); - System.out.println(jedis.get("name")); - }catch (Exception e){ - e.printStackTrace(); - }finally { - if(jedis!=null){ - jedis.close(); - } - if(jedisPool!=null){ - jedisPool.close(); - } - } - } - -} - -``` - -## Data structure - -1. String -2. hash -3. list -4. set -5. sorted list - -**Note for Key definition:** - -1. not too long -2. not too short -3. unifed naming - -### String -Assignment and get: - - set key value - get key - getset key newvalue //get value and reassign - del key - -nil: it means value doesn't exist - -incr num: if no value exists, create 0 and incr; Or add 1 - -decr num: initial value is 0 - -incrby num 5: add 5 every time - -decrby num 3: reduce 3 every time - -append num 5: append 5 if num exists, or create a key 0 and assigned to 5 - --1: represents the last element in the list. Like this,-2 represents the second to last. - -### hash - - //set - hset hashname field-key value - hmset myhash2 name mafulong age 18 - hget myhash name - - //get - hmget myhash2 name age - hgetall myhash - //get all the values - - //delete - hdel myhash2 name - del myhash2 - - //incr - hincrby myhash age 5 - - //if exist - hexists myhash - - hgetall myhash - - hlen myhash - - hkeys myhash - - hvals myhash - -### list -Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边) - -左边插入,就是从左边头插,因此a在最右边 - - lpush mylist a b c - //add a b c - -右边插入 - - rpush mylist a b c - -查看链表 - - lrange mylist - -弹出 - - lpop mylist - - rop mylist - -获取元素个数 - - llen mylist - -### set - - sadd set-key item - sismember set-key item4 - smembers set-key - //del - srem myset 1 2 - //diff - sdiff myset1 myset2 - //inter - sinter myset1 myset2 - //union - sunion myset1 myset2 - //get the size - scard myset - - -``` - -redis re 127.0.0.1:6379> sadd runoob redis -(integer) 1 -redis 127.0.0.1:6379> sadd runoob mongodb -(integer) 1 -redis 127.0.0.1:6379> sadd runoob rabitmq -(integer) 1 -redis 127.0.0.1:6379> sadd runoob rabitmq -(integer) 0 -redis 127.0.0.1:6379> smembers runoob -1) "redis" -2) "rabitmq" -3) "mongodb" -``` - -### sorted set -Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。 - -不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。 - -zset的成员是唯一的,但分数(score)却可以重复。 - -``` -redis 127.0.0.1:6379> zadd runoob 0 redis -(integer) 1 -redis 127.0.0.1:6379> zadd runoob 0 mongodb -(integer) 1 -redis 127.0.0.1:6379> zadd runoob 0 rabitmq -(integer) 1 -redis 127.0.0.1:6379> zadd runoob 0 rabitmq -(integer) 0 -redis 127.0.0.1:6379> > ZRANGEBYSCORE runoob 0 1000 -1) "mongodb" -2) "rabitmq" -3) "redis" -``` diff --git "a/_posts/Tech/java/2018-10-11-java\346\263\250\350\247\243\345\216\237\347\220\206.md" "b/_posts/Tech/Java/2018-10-11-java\346\263\250\350\247\243 Annotation.md" similarity index 86% rename from "_posts/Tech/java/2018-10-11-java\346\263\250\350\247\243\345\216\237\347\220\206.md" rename to "_posts/Tech/Java/2018-10-11-java\346\263\250\350\247\243 Annotation.md" index 7fdd3c1fa9..394ac022d3 100644 --- "a/_posts/Tech/java/2018-10-11-java\346\263\250\350\247\243\345\216\237\347\220\206.md" +++ "b/_posts/Tech/Java/2018-10-11-java\346\263\250\350\247\243 Annotation.md" @@ -1,13 +1,13 @@ --- layout: post category: Java -title: java注解原理 +title: java注解 Annotation tags: Java --- ## 注解 -Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。 +Annotation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。 Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。 diff --git "a/_posts/Tech/Java/2021-04-12-\345\206\205\345\255\230\345\261\217\351\232\234.md" "b/_posts/Tech/Java/2021-04-12-\345\206\205\345\255\230\345\261\217\351\232\234.md" deleted file mode 100644 index 3ffd9003a5..0000000000 --- "a/_posts/Tech/Java/2021-04-12-\345\206\205\345\255\230\345\261\217\351\232\234.md" +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: post -category: Java -title: Java内存屏障 -tags: SystemDesign ---- - -## 内存屏障 - -[参考](https://monkeysayhi.github.io/2017/12/28/%E4%B8%80%E6%96%87%E8%A7%A3%E5%86%B3%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/) - -内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。再向下是是硬件提供的支持;向上是操作系统或JVM对内存屏障作出的各种封装。内存屏障是一种标准,各厂商可能采用不同的实现。 - - - -内存屏障的实现涉及大量硬件架构层面的知识,又需要操作系统或JVM的配合才能发挥威力,单纯从任何一个层面都无法理解。 - - - -volatile变量规则:**对volatile变量的写入操作必须在对该变量的读操作之前执行**。 - -volatile变量规则只是一种标准,要求JVM实现保证volatile变量的偏序语义。**结合程序顺序规则、传递性**,该偏序语义通常表现为两个作用: - -- 保持可见性 -- 禁用重排序(读操作禁止重排序之后的操作,写操作禁止重排序之前的操作) - -通过**volatile标记,可以解决编译器层面的可见性与重排序问题**。而**内存屏障则解决了硬件层面的可见性与重排序问题**。 - diff --git "a/_posts/Tech/Java/2022-07-17-Junit\345\222\214Mockito.md" "b/_posts/Tech/Java/2022-07-17-Junit\345\222\214Mockito.md" new file mode 100644 index 0000000000..070a53d912 --- /dev/null +++ "b/_posts/Tech/Java/2022-07-17-Junit\345\222\214Mockito.md" @@ -0,0 +1,725 @@ +--- +layout: post +category: Java +title: Junit和Mockito +tags: Java +--- + +# Junit4 + +## 例子+常用注解 + +需要使用junit.jar这个包。 + + @Test 注解,表示这个方法是一个测试方法。 + +常用注解 + +- @Test:将一个普通方法修饰成一个测试方法 @Test(excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test(timeout = 毫秒数) :测试方法执行时间是否符合预期 +- @BeforeClass: 会在所有的方法执行前被执行,static 方法 (全局只会执行一次,而且是第一个运行) +- @AfterClass:会在所有的方法执行之后进行执行,static 方法 (全局只会执行一次,而且是最后一个运行) +- @Before:会在每一个测试方法被运行前执行一次 +- @After:会在每一个测试方法运行后被执行一次 +- @Ignore:所修饰的测试方法会被测试运行器忽略 +- @RunWith:可以更改测试运行器 org.junit.runner.Runner +- Parameters:参数化注解 + +例子 + +```java +import static org.junit.Assert.*; + +import org.junit.*; + +/** + * 了解一个测试类单元测试的执行顺序为: + * @BeforeClass –> @Before –> @Test –> @After –> @AfterClass + * @author hao + * + */ +public class TestJunit1 { + @BeforeClass + public static void setUpBeforeClass() throws Exception { + System.out.println("in BeforeClass================"); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + System.out.println("in AfterClass================="); + } + + @Before + public void before() { + System.out.println("in Before"); + } + + @After + public void after() { + System.out.println("in After"); + } + + @Test(timeout = 10000) + public void testadd() { + TestJunit2 a = new TestJunit2(); + assertEquals(6, a.add(3, 3)); + System.out.println("in Test ----Add"); + } + + @Test + public void testdivision() { + TestJunit2 a = new TestJunit2(); + assertEquals(3, a.division(6, 2)); + System.out.println("in Test ----Division"); + } + + @Ignore + @Test + public void test_ignore() { + TestJunit2 a = new TestJunit2(); + assertEquals(6, a.add(1, 5)); + System.out.println("in test_ignore"); + } + + @Test + public void teest_fail() { + fail(); + } +} +class TestJunit2 extends Thread { + + int result; + + public int add(int a, int b) { + try { + sleep(1000); + result = a + b; + } catch (InterruptedException e) { + } + return result; + } + + public int division(int a, int b) { + return result = a / b; + } +} +``` + +一个**测试类单元测试的执行顺序**为: + +**@BeforeClass –> @Before –> @Test –> @After –> @AfterClass** + +每一个**测试方法的调用顺序**为: + +@Before –> @Test –> @After + + + +## JUnit的一些注意事项 + +- 测试方法必须使用 @Test 修饰 +- 测试方法必须使用 public void 进行修饰,不能带参数 +- 一般使用单元测试会新建一个 test 目录存放测试代码,在生产部署的时候只需要将 test 目录下代码删除即可 +- 测试代码的包应该和被测试代码包结构保持一致 +- 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖 +- 测试类一般使用 Test 作为类名的后缀 +- 测试方法使一般用 test 作为方法名的前缀 + +## **Assert的用法** + +Assert就是断言,断言是编写测试用例的核心实现方式,即期望值是多少,测试的结果是多少,以此来判断测试是否通过。 + +Assert的核心方法: + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202207171631342.jpeg) + +## TestSuite测试套件 + +测试套件,一下执行多个测试类。 + +需要遵循以下规则: + +1. 创建一个空类作为测试套件的入口。 +2. 使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。 +3. 将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。 +4. 将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。 +5. 保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数 + +## 测试隔离 + +JUnit中每个测试方法都是在独立的类实例中执行的,因此只要没有使用全局变量和外部资源(如数据库和API),这些测试都将是彼此隔离的,即不管一个测试做什么,都不会影响其他测试。 + +[参考](https://www.letianbiji.com/java/java-test-junit-isolate.html) + + + +### 测试运行器 + +JUnit 中所有的测试方法都是由测试运行器负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并没有限制您必须使用默认的运行器。相反,您不仅可以定制自己的运行器(所有的运行器都继承自 org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。指定方法也很简单,使用注解 `org.junit.runner.RunWith` 在测试类上显式的声明要使用的运行器即可: + +```java +@RunWith(CustomTestRunner.class) + public class TestWordDealUtil { +…… + } +``` + +显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit 会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。 + +## 异常测试、参数测试 + +[参考](https://www.w3cschool.cn/junit/1h4e1hva.html) + +# Junit5 + +区别: + +注解不同: + +- @Before变成了@BeforeEach。 +- @After变成了@AfterEach。 +- @BeforeClass变成了@BeforeAll。 +- @AfterClass变成了@AfterAll。 +- @Ignore变成了@Disabled。 +- @Category变成了@Tag。 +- @Rule和@ClassRule没有了,用@ExtendWith和@RegisterExtension代替。 + + + +扩展不同: + +在JUnit 4中,使用Spring框架构建测试看起来是这样的: + +```java +@RunWith(SpringJUnit4ClassRunner.class) +public class MyControllerTest { + // ... +} +``` + +在JUnit 5中,你可以用Spring扩展来代替: + +```java +@ExtendWith(SpringExtension.class) +class MyControllerTest { + // ... +} +``` + +@ExtendWith 注解是可重复的,这意味着多个扩展可以很容易地组合在一起。 + +你也可以通过创建一个类来实现org.junit.jupiter.api.extendWith中的一个或多个接口,然后用@ExtendWith将其添加到你的测试中,从而轻松定义你自己的自定义扩展。 + +# Mockito + +[Tutorial](https://www.baeldung.com/mockito-series) + +## 添加mockito + + + +```xml + + org.mockito + mockito-all + 1.9.5 + test + + + junit + junit + 4.11 + test + + +``` + + + +```java +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; +``` + + + +指定包名 Mockito + + + +测试类加相关注解 + +Junit5: + +```java +@ExtendWith(MockitoExtension.class) +public class UserServiceUnitTest { + +``` + +Junit4: + +```java +@RunWith(MockitoJUnitRunner.class) + +@RunWith(PowerMockRunner.class) +``` + +## 创建 Mock 对象 + +```reasonml +@Test +public void createMockObject() { + // 使用 mock 静态方法创建 Mock 对象. + List mockedList = mock(List.class); + Assert.assertTrue(mockedList instanceof List); + + // mock 方法不仅可以 Mock 接口类, 还可以 Mock 具体的类型. + ArrayList mockedArrayList = mock(ArrayList.class); + Assert.assertTrue(mockedArrayList instanceof List); + Assert.assertTrue(mockedArrayList instanceof ArrayList); +} +``` + +如上代码所示, 我们调用了 **mock** 静态方法来创建一个 Mock 对象. mock 方法接收一个 class 类型, 即我们需要 mock 的类型. + + + +when需要在mock对象上操作!! + +包名冲突的可以用Mockito.when来操作 + +只有调用mock对象的方法才是走的mock逻辑,实际对象还不是mock的。 + +## 配置 Mock 对象 + +当我们有了一个 Mock 对象后, 我们可以定制它的具体的行为. 例如: + +```java +@Test +public void configMockObject() { + List mockedList = mock(List.class); + + // 我们定制了当调用 mockedList.add("one") 时, 返回 true + when(mockedList.add("one")).thenReturn(true); + // 当调用 mockedList.size() 时, 返回 1 + when(mockedList.size()).thenReturn(1); + + Assert.assertTrue(mockedList.add("one")); + // 因为我们没有定制 add("two"), 因此返回默认值, 即 false. + Assert.assertFalse(mockedList.add("two")); + Assert.assertEquals(mockedList.size(), 1); + + Iterator i = mock(Iterator.class); + when(i.next()).thenReturn("Hello,").thenReturn("Mockito!"); + String result = i.next() + " " + i.next(); + //assert + Assert.assertEquals("Hello, Mockito!", result); +} +``` + +我们使用 **when(...).thenReturn(...)** 方法链来定义一个行为, 例如 "when(mockedList.add("one")).thenReturn(true)" 表示: **当调用了mockedList.add("one"), 那么返回 true.**. 并且要注意的是, **when(...).thenReturn(...)** 方法链不仅仅要匹配方法的调用, 而且要方法的参数一样才行. + +而且有趣的是, **when(​...).thenReturn(​...)** 方法链可以指定多个返回值, 当这样做后, 如果多次调用指定的方法, 那么这个方法会依次返回这些值. 例如 "when(i.next()).thenReturn("Hello,").thenReturn("Mockito!");", 这句代码表示: 第一次调用 i.next() 时返回 "Hello,", 第二次调用 i.next() 时返回 "Mockito!". + +上面的例子我们展示了方法调用返回值的定制, 那么我们可以指定一个抛出异常吗? 当然可以的, 例如: + +```reasonml +@Test(expected = NoSuchElementException.class) +public void testForIOException() throws Exception { + Iterator i = mock(Iterator.class); + when(i.next()).thenReturn("Hello,").thenReturn("Mockito!"); // 1 + String result = i.next() + " " + i.next(); // 2 + Assert.assertEquals("Hello, Mockito!", result); + + doThrow(new NoSuchElementException()).when(i).next(); // 3 + i.next(); // 4 +} +``` + +上面代码的第一第二步我们已经很熟悉了, 接下来第三部我们使用了一个新语法: `doThrow(ExceptionX).when(x).methodCall`, 它的含义是: 当调用了 x.methodCall 方法后, 抛出异常 ExceptionX. +因此 doThrow(new NoSuchElementException()).when(i).next() 的含义就是: 当第三次调用 i.next() 后, 抛出异常 NoSuchElementException.(因为 i 这个迭代器只有两个元素) + + + +## 误解 注意事项 + +Mockito.mock() 并不是mock一整个类,而是根据传进去的一个类,mock出属于这个类的一个对象,并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变! + + + +mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面 + + + +mock后直接生效 默认是无限次数 + + + +## 对于手动new而不是依赖注入的Mock + +[参考](https://stackoverflow.com/questions/5920153/test-class-with-a-new-call-in-it-with-mockito) + +```java +public class TestedClass { + public LoginContext login(String user, String password) { + LoginContext lc = new LoginContext("login", callbackHandler); + } +} +``` + +1. Mock 生成这个动作new, 改成用工厂生成,这样就可以Mock这个工厂了 + + You can use a factory to create the login context. Then you can mock the factory and return whatever you want for your test. + +```java +// 新方法 + public LoginContext login(String user, String password) { + LoginContext lc = loginContextFactory.createLoginContext(); + } + + +public interface LoginContextFactory { + public LoginContext createLoginContext(); +} +``` + + + +1. mock部分方法,改写有new的方法逻辑, 用spy + + ```java + TestedClass tc = spy(new TestedClass()); + LoginContext lcMock = mock(LoginContext.class); + when(tc.login(anyString(), anyString())).thenReturn(lcMock); + ``` + +## 校验 Mock 对象的方法调用 + +Mockito 会追踪 Mock 对象的所用方法调用和调用方法时所传递的参数. 我们可以通过 verify() 静态方法来来校验指定的方法调用是否满足断言. 语言描述有一点抽象, 下面我们仍然以代码来说明一下. + +```routeros +@Test +public void testVerify() { + List mockedList = mock(List.class); + mockedList.add("one"); + mockedList.add("two"); + mockedList.add("three times"); + mockedList.add("three times"); + mockedList.add("three times"); + when(mockedList.size()).thenReturn(5); + Assert.assertEquals(mockedList.size(), 5); + + verify(mockedList, atLeastOnce()).add("one"); + verify(mockedList, times(1)).add("two"); + verify(mockedList, times(3)).add("three times"); + verify(mockedList, never()).isEmpty(); +} +``` + +上面的例子前半部份没有什么特别的, 我们关注后面的: + +```scss +verify(mockedList, atLeastOnce()).add("one"); +verify(mockedList, times(1)).add("two"); +verify(mockedList, times(3)).add("three times"); +verify(mockedList, never()).isEmpty(); +``` + +读者根据代码也应该可以猜测出它的含义了, 很简单: + +- 第一句校验 mockedList.add("one") 至少被调用了 1 次(atLeastOnce) +- 第二句校验 mockedList.add("two") 被调用了 1 次(times(1)) +- 第三句校验 mockedList.add("three times") 被调用了 3 次(times(3)) +- 第四句校验 mockedList.isEmpty() 从未被调用(never) + + + +Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password"); + +这句话的作用是,验证 mockUserManager 的 performLogin() 得到了调用,同时参数是“xiaochuang”和"xiaochuang password"。其实更准确的说法是,这行代码验证的是, mockUserManager 的 performLogin() 方法得到了 一次 调用。因为这行代码其实是: + +Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("xiaochuang", "xiaochuang password"); + +的简写,或者说重载方法,注意其中的 Mockito.times(1) 。 + +## 使用 spy() 部分模拟对象 + +对于一个mock对象,我们可以指定返回值和执行特定的动作,当然,也可以不指定,如果不指定的话,一个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。 + +如果你想实现这样的效果:指定时执行指定的动作,不指定时调用这个对象的默认实现,同时又能拥有验证方法调用的功能。那你可以使用Mockito.spy()来创建对象。 + + + +Mockito 提供的 spy 方法可以包装一个真实的 Java 对象, 并返回一个包装后的新对象. 若没有特别配置的话, 对这个新对象的所有方法调用, 都会委派给实际的 Java 对象. 例如: + +```java +@Test +public void testSpy() { + List list = new LinkedList(); + List spy = spy(list); + + // 对 spy.size() 进行定制. + when(spy.size()).thenReturn(100); + + spy.add("one"); + spy.add("two"); + + // 因为我们没有对 get(0), get(1) 方法进行定制, + // 因此这些调用其实是调用的真实对象的方法. + Assert.assertEquals(spy.get(0), "one"); + Assert.assertEquals(spy.get(1), "two"); + + Assert.assertEquals(spy.size(), 100); +} +``` + +这个例子中我们实例化了一个 LinkedList 对象, 然后使用 spy() 方法对 list 对象进行部分模拟. 接着我们使用 **when(...).thenReturn(...)** 方法链来规定 spy.size() 方法返回值是 100. 随后我们给 spy 添加了两个元素, 然后再 调用 spy.get(0) 获取第一个元素. +这里有意思的地方是: 因为我们没有定制 add("one"), add("two"), get(0), get(1), 因此通过 spy 调用这些方法时, 实际上是委派给 list 对象来调用的. +然而我们 定义了 spy.size() 的返回值, 因此当调用 spy.size() 时, 返回 100. + +## Spy和mock 对比,Stub + +spy 和 mock不同,不同点是: + +- spy 的参数是对象示例,mock 的参数是 class。 +- 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。 + +mock默认是返回默认值的。 + + + +**假定**调用`dao`层的某方法时它的返回值是什么,这个过程就是**Stub** + +## 执行特定动作 (替换成另一个方法) + +```java +public class LoginPresenterTest { +   LoginPresenter loginPresenter; +   @Test +   public void testLogin() { +       UserManager mockUserManager = Mockito.mock(UserManager.class); +       PasswordValidator mPasswordValidator = Mockito.mock(PasswordValidator.class); +       Mockito.when(mPasswordValidator.verifyPassword(Mockito.anyString())).thenReturn(true); +​ +       loginPresenter = new LoginPresenter(mockUserManager, mPasswordValidator); +​ +       Mockito.doAnswer(new Answer() { +           @Override +           public Object answer(InvocationOnMock invocation) throws Throwable { +               //这里可以获得传给performLogin的参数 +               Object[] arguments = invocation.getArguments(); + +               //callback是第三个参数 +               UserManager.NetCallback callback = (UserManager.NetCallback) arguments[2]; +               callback.onFailure("404 Not found"); +               return 404; +          } +      }).when(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString(), Mockito.any(UserManager.NetCallback.class)); + +       loginPresenter.login("aya", "123456"); +  } +} +``` + +通过doAnswer方法,生成替换,里面取Arguments手动转化来做某些事情。 + +## 注解 + +@Mock用于代替Mockito.mock创建mock对象。 + + + +spy 对应注解 @Spy,和 @Mock 是一样用的。 + + + +对于@Spy,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。 + +所以下面两种写法是等价的: + +```java +// 写法1 +@Spy +private ExampleService spyExampleService; + +// 写法2 +@Spy +private ExampleService spyExampleService = new ExampleService(); +``` + +## 测试隔离 + +根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。 + +比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。 + +## 实现原理-继承 + +Mockito使用继承的方式实现mock的,用CGLIB生成mock对象代替真实的对象进行执行,为了mock实例的方法,你可以在subclass中覆盖它。 + +因此无法mock静态方法。 + +**不能mock静态、final、私有方法** + +## 参考 + +[手把手教你 Mockito 的使用](https://segmentfault.com/a/1190000006746409) + + + +# Mockito 高版本特性 + +依赖上,一般是说要用 `mockito-inline` 替换 `mockito-core` 依赖。 实质上 `mockito-inline` 就是给 mockito-core 添加了两个插件配置 + + + +使用了什么技术? bytebuddy, 运行时生成Java class + + + +注意mockito mock静态只对当前线程有效, 这点不如powermock + +## Mock静态无参方法支持 + +```java +@Test +void givenStaticMethodWithNoArgs_whenMocked_thenReturnsMockSuccessfully() { + assertThat(StaticUtils.name()).isEqualTo("Baeldung"); + + try (MockedStatic utilities = Mockito.mockStatic(StaticUtils.class)) { + utilities.when(StaticUtils::name).thenReturn("Eugen"); + assertThat(StaticUtils.name()).isEqualTo("Eugen"); + } + + assertThat(StaticUtils.name()).isEqualTo("Baeldung"); +} +``` + +从 Mockito 3.4.0 开始,我们可以使用*Mockito.mockStatic(Class classToMock)*方法来模拟对静态方法调用的调用。**此方法为我们的类型返回一个\*MockedStatic\*对象,它是一个作用域模拟对象。** + +因此,在我们上面的单元测试中,*实用程序*变量表示具有线程局部显式范围的模拟。**请务必注意,作用域模拟必须由激活模拟的实体关闭。**这就是为什么我们在 try-with-resources 构造中定义我们的模拟,以便在我们完成作用域块时自动关闭模拟。 + +## Mock带参数的静态方法 + +我们通常用的 Mockito.when(T methodCall) 的参数是一个方法调用的返回值,所以当 Mock 带参数的静态方法时与 Mockito.when(obj.foo(1, 2)).thenReturn(34)) 的用法是不一样的,MockedStatic.when() 的参数需要放一个 () -> 对象.of(anyInt(), anyInt(), anyInt()) 这样的 Lambda. + + + +比如原来是函数A(b int) + +```java +theMock.when(() -> 类.A(any())).thenReturn(xxx); +``` + +theMock就是MockStatic返回的。 + +对静态方法调用的 verify 也要用 theMock 的 verify() 方法,而不是 Mockito.verify()。 + +## Mock final类和方法 + +和正常无区别 + +[参考](https://www.baeldung.com/mockito-final) + +## Mock 自己new的对象 + +``` + //mock代码中自己new的实例及“该实例的方法” + MockedConstruction newObjectMocked = Mockito.mockConstruction(NewObject.class); Mockito.when(obj.haha()).thenReturn("who am i ?"); + +``` + +[参考](https://cloud.tencent.com/developer/article/1877722) + +# PowerMockito + +## 和mockito比较 + +**Mockito、EasyMock、JMock等比较流行Mock框架有个共同的缺点,都不能mock静态、final、私有方法等,而PowerMock可以完美解决以上框架的不足** + + + +## 使用 + +[参考](https://juejin.cn/post/6850418110105649166) + +开头包是PowerMockito + +### mock对Redis的静态调用 + +我们使用PowerMock来mock对静态方法的调用,注意需要将`RedisUtils`类,加入`@PrepareForTest`注解中,我们既mock了`getArray`方法,也mock了`setArray`方法,其实`setArray`不需要mock这里显式的mock了 + +mock静态类注意 + +1. 加到PrepareForTest +2. mockStatic下 + +```java +PowerMockito.mockStatic(RedisUtils.class); +// mock掉对Redis的静态调用 +PowerMockito.when(RedisUtils.getArray(eq("tom"), eq(StudentBo.class))).thenReturn(Collections.emptyList()); +// 显式的mock掉静态的void的方法(可以不mock) +PowerMockito.doNothing().when(RedisUtils.class, "setArray", anyString(), anyList(), anyInt()); +复制代码 +``` + +### mock单例类 + +mock单例类相对来说复杂一点,逻辑上先用Powermock mock出单例类,然后再给单例类的`getInstance`方法打桩,返回之前mock,再对mock类实际调用的方法打桩即可,代码如下 + +```scss +PowerMockito.mockStatic(SchoolManageProxy.class); +// Powermock mock出单例类 +SchoolManageProxy mockSchoolManageProxy = PowerMockito.mock(SchoolManageProxy.class); +// 给单例类的getInstance方法打桩 + +PowerMockito.when(SchoolManageProxy.getInstance()).thenReturn(mockSchoolManageProxy); +// 对mock类queryPerson的方法打桩 +when(mockSchoolManageProxy.queryPerson(anyList())).thenReturn(Collections.emptyList()); +``` + +单例类就是构造方法为private无法实例化, + + + +### mock私有方法 + +可以看到`queryStudentScoreByKeyword`方法调用了该类的私有方法`processKeyword`,如果该方法耗时过长,使用powermock也可以mock该私有方法。 这里用spy演示。 + +```csharp +// mock 实例 +// spy的标准是:如果不打桩,默认执行真实的方法,如果打桩则返回桩实现。 +studentServiceUnderTest = PowerMockito.spy(studentServiceUnderTest); +// mock私有方法processKeyword +// doReturn(...) when(...)不做真实调用,但是when(...) thenReturn(...)还是会真实调用原方法,只是返回了指定的结果 +PowerMockito.doReturn("tom").when(studentServiceUnderTest, "processKeyword", anyString()); +``` + +注意: + +1. when不能调用private方法了,所以是用字符串表示。 + + + +## 注解 @PrepareForTest + +当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。 + + +当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。 + + +当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。 + + +当需要mock私有方法的时候, 只是需要加注解@PrepareForTest,注解里写的类是私有方法所在的类 + + +当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解里写的类是需要调用系统方法所在的类 + + + + + + +# diff --git "a/_posts/Tech/Java/2022-07-28-\344\276\235\350\265\226\346\263\250\345\205\245\346\241\206\346\236\266Guice.md" "b/_posts/Tech/Java/2022-07-28-\344\276\235\350\265\226\346\263\250\345\205\245\346\241\206\346\236\266Guice.md" new file mode 100644 index 0000000000..b6ab56a573 --- /dev/null +++ "b/_posts/Tech/Java/2022-07-28-\344\276\235\350\265\226\346\263\250\345\205\245\346\241\206\346\236\266Guice.md" @@ -0,0 +1,505 @@ +--- +layout: post +category: Java +title: 依赖注入框架Guice +tags: Java +--- + + + +## 依赖注入Guice + +Spring框架的依赖注入是家喻户晓的,但是在实际的开发中我们想使用便捷的依赖注入功能, 可以使用Google Guice,首先在你的maven项目里引入 + +```text + + com.google.inject + guice + 4.0 + +``` + + + +Guice是一个非常干净的依赖注入框架,框架除了依赖注入功能之外,没有任何其它非相关模块功能。 + + + +## 注解 @Inject,@Singleton,@Implementedby + +最常用的两个注解就是@Singleton和@Inject,Singleton表示构建的对象是单例的,Inject表示被标注的字段将使用Guice自动注入。在一般的项目中这两个注解一般可以完成90%以上的装配工作。 + +Guice需要实例化对象,请确保相应被实例化的对象有默认构造器。 + + + +当某个接口有多个实现时,我们使用@ImplementedBy注解在接口定义上,指定接口的具体实现类 + + + +```java +import javax.inject.Singleton; + +import com.google.inject.Guice; +import com.google.inject.ImplementedBy; +import com.google.inject.Inject; +import com.google.inject.Injector; + +@ImplementedBy(SimpleHelloPrinter.class) +interface IHelloPrinter { + void print(); +} + +@Singleton +class SimpleHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Simple World"); + } + +} + +@Singleton +class ComplexHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Complex World"); + } + +} + +@Singleton +public class Sample { + + @Inject + private IHelloPrinter printer; + + public void hello() { + printer.print(); + } + + public static void main(String[] args) { + Injector injector = Guice.createInjector(); + Sample sample = injector.getInstance(Sample.class); + sample.hello(); + } + +} +``` + + + +如果我们不用Singleton标注,每次获取实例时,Guice会重新构造一个,这个会有反射构造器的性能损耗,在高性能场景下,请谨慎。 + + + +## 用Module定义装配规则 + +我们可以不使用@ImplementedBy注解,因为这样不优雅,谁会在定义接口的时候就能预知实现类的名称呢。我们可以使用Guice Module定义装配规则,它相当于Spring的XML文件,只不过它的装配规则都是使用代码定义的。你可能会辩解说代码定义怎么能比得上XML定义呢,其实Guice Module在一个大型项目中也是非常的简洁,一般只会占用几十行代码,Module里面配置的仅仅是特殊的专配规则。能规则的可读性而言,代码要比XML舒服的多。 + + + +要点: + +1. 继承AbstractModule +2. 在覆盖的configure()方法里手动bind(接口).to(实现类) 效果和在实现类上@ImplementedBy一样 + + + +```java +import javax.inject.Singleton; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; + +interface IHelloPrinter { + void print(); +} + +@Singleton +class SimpleHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Simple World"); + } + +} + +@Singleton +class ComplexHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Complex World"); + } + +} + +class SampleModule extends AbstractModule { + + @Override + protected void configure() { + bind(IHelloPrinter.class).to(SimpleHelloPrinter.class); + } + +} + +@Singleton +public class Sample { + + @Inject + private IHelloPrinter printer; + + public void hello() { + printer.print(); + } + + public static void main(String[] args) { + Injector injector = Guice.createInjector(new SampleModule()); + Sample sample = injector.getInstance(Sample.class); + sample.hello(); + } + +} +``` + +## @Name给实现类起别名指定bind + +我们还可以使用@Named名称指令来指定依赖注入实现 + +要点 + +- 给要注入的实现类起别名,在configre里bind设置 +- @Name 辅助@Inject 引入别名对应的注入,还是需要@Inject + +```java +import javax.inject.Named; +import javax.inject.Singleton; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.name.Names; + +interface IHelloPrinter { + void print(); +} + +@Singleton +class SimpleHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Simple World"); + } + +} + +@Singleton +class ComplexHelloPrinter implements IHelloPrinter { + + public void print() { + System.out.println("Hello, Complex World"); + } + +} + +class SampleModule extends AbstractModule { + + @Override + protected void configure() { + bind(IHelloPrinter.class).annotatedWith(Names.named("simple")).to(SimpleHelloPrinter.class); + bind(IHelloPrinter.class).annotatedWith(Names.named("complex")).to(ComplexHelloPrinter.class); + } + +} + +@Singleton +public class Sample { + + @Inject + @Named("simple") + private IHelloPrinter simplePrinter; + + @Inject + @Named("complex") + private IHelloPrinter complexPrinter; + + public void hello() { + simplePrinter.print(); + complexPrinter.print(); + } + + public static void main(String[] args) { + Injector injector = Guice.createInjector(new SampleModule()); + Sample sample = injector.getInstance(Sample.class); + sample.hello(); + } + +} +``` + +## @Inject放构造函数/set方法上注入 + +我们不使用字段注入,改用构造器注入,如果我们需要在构造器里做一些特别的初始化工作 + +其实是自动根据入参来注入, 单参数多参数都可以。 也可以根据set方法注入。 + +```java +package io.edurt.lc.guice; +import com.google.inject.Guice; +import com.google.inject.Inject; +public class TestGuiceConstructor +{ + private GuiceConstructorService service; + @Inject + public TestGuiceConstructor(GuiceConstructorService service) + { + this.service = service; + } + public GuiceConstructorService getService() + { + return service; + } + public static void main(String[] args) + { + TestGuiceConstructor test = Guice.createInjector().getInstance(TestGuiceConstructor.class); + test.getService().print("Test Case 1"); + } +} +``` + + + +## Guice 获取实例 + +```java +@Test +public void test_service() +{ + Injector injector = Guice.createInjector(new GuiceBasicModule()); + GuiceBasicService service = injector.getInstance(GuiceBasicService.class); + service.print("Hello Guice"); +} +``` + + + +## 静态方法注入 + +关键代码和方法 + +```java +Guice.createInjector(binder -> binder.requestStaticInjection(TestGuiceStatic.class)); + +``` + +其实是手动创建注入。 + +```java + +package io.edurt.lc.guice; +import com.google.inject.Guice; +import com.google.inject.Inject; +public class TestGuiceStatic +{ + @Inject + private static GuiceConstructorService service; + public static void main(String[] args) + { + Guice.createInjector(binder -> binder.requestStaticInjection(TestGuiceStatic.class)); + TestGuiceStatic.service.print("Static"); + } +} +``` + + + +## @Provides 类似Spring的@bean + +Guice provides a way to create bindings with complex objects using @Provides annotation. + +@Provides放函数上 return值就是可注入的Bean. Provides需要放在继承AbstrctModule的类函数上。 + +```java +//Binding Module +class TextEditorModule extends AbstractModule { + + @Override + protected void configure() {} + + @Provides + public SpellChecker provideSpellChecker(){ + + String dbUrl = "jdbc:mysql://localhost:5326/emp"; + String user = "user"; + int timeout = 100; + + SpellChecker SpellChecker = new SpellCheckerImpl(dbUrl, user, timeout); + return SpellChecker; + } +} +``` + + + +## @BindingAnnotation 指定bind + +[参考](https://www.tutorialspoint.com/guice/guice_binding_annotations.htm) + +通过自定义一个注解,然后注解上再加一个注解@BindingAnnotation就可以让@Inject注入时再指定这个自定义注解对应的实现类了,方便一个接口对应多个实现类,通过自定义注解区分对应到哪个实现类,和@Name类似。 + +As we can bind a type with its implementation. In case we want to map a type with multiple implmentations, we can create custom annotation as well. See the below example to understand the concept. + +**Create a binding annotation** + +```java +@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) +@interface WinWord {} +``` + +- **@BindingAnnotation** - Marks annotation as binding annotation. + +**Mapping using binding annotation** + +```java +bind(SpellChecker.class).annotatedWith(WinWord.class).to(WinWordSpellCheckerImpl.class); +``` + +以上代码写在binding module里,对一个接口通过一直注解关联到对应实现类。 + +```java +//Binding Module +class TextEditorModule extends AbstractModule { + + @Override + protected void configure() { + bind(SpellChecker.class).annotatedWith(WinWord.class) + .to(WinWordSpellCheckerImpl.class); + } +} +``` + + + +**Inject using binding annotation** + +用的时候和@Name类似,指定注解来指定了实现类。 + +```java +@Inject +public TextEditor(@WinWord SpellChecker spellChecker) { + this.spellChecker = spellChecker; +} +``` + + + +## Guice AOP + +[details](https://www.tutorialspoint.com/guice/guice_aop.htm) + +**Important Classes** + +- **Matcher** - Matcher is an interface to either accept or reject a value. In Guice AOP, we need two matchers: one to define which classes participate, and another for the methods of those classes. +- **MethodInterceptor** - MethodInterceptors are executed when a matching method is called. They can inspect the call: the method, its arguments, and the receiving instance. We can perform cross-cutting logic and then delegate to the underlying method. Finally, we may inspect the return value or exception and return. + + + +指定类和指定方法,再加个拦截器。 + +- 重要方法: bindInterceptor, 写在Module里。 + +- 拦截器需继承MethodInterceptor + + + +```java +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.matcher.Matchers; + +public class GuiceTester { + public static void main(String[] args) { + Injector injector = Guice.createInjector(new TextEditorModule()); + TextEditor editor = injector.getInstance(TextEditor.class); + editor.makeSpellCheck(); + } +} + +class TextEditor { + private SpellChecker spellChecker; + + @Inject + public TextEditor(SpellChecker spellChecker) { + this.spellChecker = spellChecker; + } + + public void makeSpellCheck(){ + spellChecker.checkSpelling(); + } +} + +//Binding Module +class TextEditorModule extends AbstractModule { + + @Override + protected void configure() { + bind(SpellChecker.class).to(SpellCheckerImpl.class); + bindInterceptor(Matchers.any(), + Matchers.annotatedWith(CallTracker.class), + new CallTrackerService()); + } +} + +//spell checker interface +interface SpellChecker { + public void checkSpelling(); +} + +//spell checker implementation +class SpellCheckerImpl implements SpellChecker { + + @Override @CallTracker + public void checkSpelling() { + System.out.println("Inside checkSpelling." ); + } +} + +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@interface CallTracker {} + +class CallTrackerService implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + System.out.println("Before " + invocation.getMethod().getName()); + Object result = invocation.proceed(); + System.out.println("After " + invocation.getMethod().getName()); + return result; + } +} +``` + +## Spring vs Guice + +[参考](https://www.zditect.com/main-advanced/java/guice-spring-dependency-injection.html) + + + +## 参考 + +https://zhuanlan.zhihu.com/p/32299568 + +[参考](https://qianmoq.com/google/guice/binder-scope.html) diff --git a/_posts/Tech/Java/2022-08-07-java Lombok.md b/_posts/Tech/Java/2022-08-07-java Lombok.md new file mode 100644 index 0000000000..320af03505 --- /dev/null +++ b/_posts/Tech/Java/2022-08-07-java Lombok.md @@ -0,0 +1,12 @@ +--- +layout: post +category: Java +title: java Lombok +tags: Java +--- + +## java Lombok + + + +[参考](https://juejin.cn/post/6844903557016076302) diff --git "a/_posts/Tech/Java/2022-08-07-\346\263\250\350\247\243NotNull NonNull\345\222\214Nonnull.md" "b/_posts/Tech/Java/2022-08-07-\346\263\250\350\247\243NotNull NonNull\345\222\214Nonnull.md" new file mode 100644 index 0000000000..54972a97a9 --- /dev/null +++ "b/_posts/Tech/Java/2022-08-07-\346\263\250\350\247\243NotNull NonNull\345\222\214Nonnull.md" @@ -0,0 +1,38 @@ +--- +layout: post +category: Java +title: 注解NotNull NonNull和Nonnull +tags: Java +--- + +## @NotNull @NonNull和@Nonnull + + + +[参考](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/which-notnull-java-annotation-should-i-use.md) + + + +lombok.NonNull + +``` +适用Lombok项目中代码生成器。不是一个标准的占位符注解. +``` + +会生成检查是否为空的code, 如果为空就抛出NPE异常。 + + + +javax.annotation.Nonnull + +``` +只适用FindBugs,JSR-305不适用 +``` + +一个占位符,不生成code,但是代码分析里可以用到,如果是可空然后又直接引用属性,会发现这样。 + +其中@Nullable也是这样的,只是一个占位符,不生成code。 + + + +NotNull倒没用过这个注解。 diff --git "a/_posts/Tech/Linux/2022-05-05-Linux\345\270\270\347\224\250\345\221\275\344\273\244.md" "b/_posts/Tech/Linux/2022-05-05-Linux\345\270\270\347\224\250\345\221\275\344\273\244.md" index 48a301db4e..f7a2b69fec 100644 --- "a/_posts/Tech/Linux/2022-05-05-Linux\345\270\270\347\224\250\345\221\275\344\273\244.md" +++ "b/_posts/Tech/Linux/2022-05-05-Linux\345\270\270\347\224\250\345\221\275\344\273\244.md" @@ -9,9 +9,11 @@ tags: Linux +## 进程端口 + ### 查看进程 -#### 1. ps +#### 1. ps 非实时查看当前时间点进程 查看某个时间点的进程信息。ps命令是最基本同时也是非常强大的进程查看命令,使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等 @@ -33,17 +35,23 @@ tags: Linux ## ps aux | grep threadx ``` -#### 2. pstree +#### 2. pstree 查看进程树 -查看进程树。 +显示当前所有进程的进程号和进程id -示例:查看所有进程树 +```shell +pstree -p +``` -```sh -## pstree -A +显示所有进程的所有详细信息,遇到相同的进程名可以压缩显示。 + +```shell +pstree -a ``` -#### 3. top + + +#### 3. top 实时查看进程及cpu/mem 实时显示进程信息。是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 @@ -53,36 +61,284 @@ tags: Linux ## top -d 2 ``` -#### 4. netstat - -查看占用端口的进程 +### kill 进程 -示例:查看特定端口的进程 +kill / killall -```sh -## netstat -anp | grep port +``` +kill -9 process_id +for more process: +sudo kill -9 process_id_1 process_id_2 process_id_3 ``` -### kill 进程 +If you know the name of the program, you can use the magnificent killall command and kill all the processes of that program in one single command. -#### kill / killall +``` +killall program_name +``` ### 查看IP #### 1. ifconfig 或者 ip +先inet,再inet6,再127 + +```sh +getip () { + if $ismac + then + ifconfig | grep inet | grep -v inet6 | grep -v 127 | cut -d ' ' -f2 + elif $islinux + then + ip a | grep inet | grep -v inet6 | grep -v 127 | sed 's/^[ \t]*//g' | cut -d ' ' -f2 + fi +} +``` + +### 通过pid查看端口/通过端口查看pid + +不知道pid可先查看进程 + +```sh +pid: ps -ef | grep 进程名 +netstat -anp | grep port +netstat -nap | grep 进程pid +``` + + + +## 大日志查看 + +### grep + +- [grep参考](https://mafulong.github.io/2019/05/24/grep%E4%BD%BF%E7%94%A8/) + +### sed + +``` +Syntax: $ sed -n -e Xp -e Yp FILENAME +``` + +- sed : sed command, which will print all the lines by default. +- -n : Suppresses output. +- -e CMD : Command to be executed +- Xp: Print line number X +- Yp: Print line number Y +- FILENAME : name of the file to be processed. + + + +In the following example, you can view the content of var/log/cron from line number 101 to 110. + +- M – Starting line number +- N – Ending line number + ``` +Syntax: sed -n M,Np FILENAME + +$ sed -n 101,110p /var/log/cron +``` + +### more + +和cat不同是不会一次性加载大文件。 + +Linux more 命令类似 cat ,不过会以一页一页的形式显示,更方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能(与 vi 相似),使用中的说明文件,请按 h 。 + +从第 20 行开始显示 testfile 之文档内容。 + +``` +more -d +20 testfile +``` + +- Ctrl+F 向下滚动一屏 +- 空格键 向下滚动一屏 +- Ctrl+B 返回上一屏 + +d 提示使用者,在画面下方显示 [Press space to continue, 'q' to quit.] ,如果使用者按错键,则会显示 [Press 'h' for instructions.] 而不是 '哔' 声 + + + +### Less + +和more类似,扩展更多。 + + + ``` +less [参数] 文件 +``` + +- -N 显示每行的行号 + +- /字符串:向下搜索"字符串"的功能 + +- ?字符串:向上搜索"字符串"的功能 + +- n:重复前一个搜索(与 / 或 ? 有关) + +- N:反向重复前一个搜索(与 / 或 ? 有关) + +- back,front, up, down和vim类似,比如u是向后半页。也可以加ctrl效果一样 + + - ctrl + F - 向前移动一屏 + - ctrl + B - 向后移动一屏 + - ctrl + D - 向前移动半屏 + - ctrl + U - 向后移动半屏 + - + j - 下一行 + - k - 上一行 + - G - 移动到最后一行 + - g - 移动到第一行 + - q / ZZ - 退出 less 命令 + +- v - 使用配置的编辑器编辑当前文件 -### 查看端口 +- **标记导航** -#### 1. netstat + 当使用 less 查看大文件时,可以在任何一个位置作标记,可以通过命令导航到标有特定标记的文本位置: + + - ma - 使用 a 标记文本的当前位置 + - 'a - 导航到标记 a 处 + +**直接定位到某个位置** + +``` +# 直接定位到第100行 +less +100g xx.log +``` + +**浏览多个文件** + +``` +less log2013.log log2014.log +``` + +说明: +输入 :n后,切换到 log2014.log +输入 :p 后,切换到log2013.log + + + +可以按 **:e** 查看下一个文件, 用 **:n** 和 **:p** 来回切换。 + + + +ps查看进程信息并通过less分页显示 + +``` +ps -ef |less +``` + + + +### tail + +``` +tail -f xxx +``` + + + +### 定位某行请用less/more + +``` +# 直接定位到第100行 +less +100g xx.log + +more -d +20 testfile +``` + + + +## 压缩gz日志查看 + +linux或者unix天然支持一些z开头的命令,可以应用于.gz后缀的压缩文件。 + +注意对.gz压缩文件使用本grep是搜不到的,因为是二进制。 -查看占用端口的进程 -示例:查看特定端口的进程 ```sh -## netstat -anp | grep port +zcat * | grep xx # 不推荐,不知道哪个文件 记住后面的grep可以加参数 但不能加r,r没法这样用管道。 +zgrep xxx * #推荐,会显示哪个文件,但此时无法应用color=auto只能用iterm查找高亮了。 + +zgrep --color=auto -i -n xxx * +find -type f | xargs zgrep xxx # 文件夹下所有文件都zgrep搜索,这种会显示文件名 +find -type f | xargs zcat | grep xxx # 文件夹下所有文件都zgrep搜索,这种不会显示文件名 +``` + +还有zless等命令也可以使用。 + + + +grep后面只能接文件,可以接*, 但不能接目录名。 如果要目录搜索,只能搭配find + + + +## 文件查找 find + +``` +find . -name "*.gz" +``` + +输出一行一行的文件名/目录列表。每个文件名后都带着个换行\n + +```scala +find -type f # 所有文件 +find . -name "*.gz" -type f # 所有.gz文件 +``` + + + +## 管道 + +顺序执行多条命令:command1;command2;command3; + 简单的顺序指令可以通过 `;`来实现 + +有条件的执行多条命令:which command1 && command2 || command3 + +&& : 如果前一条命令执行成功则执行下一条命令,如果command1执行成功(*返回0*),则执行command2 + +|| :与&&命令相反,执行不成功时执行这个命令 + +`$?`: 存储上一次命令的返回结果 + + + +管道命令使用`|`作为界定符号,管道命令与上面说的连续执行命令不一样。 + +- 管道命令仅能处理**standard output**,对于**standard error output**会予以忽略。 + `less,more,head,tail...都是可以接受standard input的命令,所以他们是管道命令` + `ls,cp,mv并不会接受standard input的命令,所以他们就不是管道命令了。` +- 管道命令必须要能够接受来自前一个命令的数据成为**standard input**继续处理才行。 + +### + + + + 管道命令使用`|`作为界定符号,管道命令与上面说的连续执行命令不一样。 + +- 管道命令仅能处理**standard output**,对于**standard error output**会予以忽略。 + `less,more,head,tail...都是可以接受standard input的命令,所以他们是管道命令` + `ls,cp,mv并不会接受standard input的命令,所以他们就不是管道命令了。` +- 管道命令必须要能够接受来自前一个命令的数据成为**standard input**继续处理才行。 + + + +```scala +ls -al /etc | less +``` + + + + + +xargs + +xargs 使用该输入作为我们指定的命令的参数。如果我们没有给xargs指定特定的命令,则默认使用 echo。xargs 始终生成单行输出,即使输入的数据是多行的。 + +```scala +ls | xargs cat # 对每个文件进行cat,如果没有xargs就是对ls列表cat那cat没任何效果。 ``` diff --git "a/_posts/Tech/Mq/2020-12-08-Kafka\345\216\237\347\220\2061-\345\237\272\347\241\200\346\236\266\346\236\204.md" "b/_posts/Tech/Mq/2020-12-08-Kafka\345\216\237\347\220\2061-\345\237\272\347\241\200\346\236\266\346\236\204.md" index c8ced3f763..0113e37049 100644 --- "a/_posts/Tech/Mq/2020-12-08-Kafka\345\216\237\347\220\2061-\345\237\272\347\241\200\346\236\266\346\236\204.md" +++ "b/_posts/Tech/Mq/2020-12-08-Kafka\345\216\237\347\220\2061-\345\237\272\347\241\200\346\236\266\346\236\204.md" @@ -3,6 +3,7 @@ layout: post category: Mq title: kafka原理1-基础架构 tags: Mq +recent_update: true --- ## KafkaServer diff --git "a/_posts/Tech/Mybatis/2018-08-02-Mybatis\344\271\213typeAliases.md" "b/_posts/Tech/Mybatis/2018-08-02-Mybatis\344\271\213typeAliases.md" index 28c43afb04..9651ec30f5 100644 --- "a/_posts/Tech/Mybatis/2018-08-02-Mybatis\344\271\213typeAliases.md" +++ "b/_posts/Tech/Mybatis/2018-08-02-Mybatis\344\271\213typeAliases.md" @@ -3,6 +3,7 @@ layout: post category: Mybatis title: Mybatis之typeAliases tags: Mybatis +draft: true --- - 自定义别名 diff --git "a/_posts/Tech/Mybatis/2018-08-03-Mybatis\344\271\213@param.md" "b/_posts/Tech/Mybatis/2018-08-03-Mybatis\344\271\213@param.md" index 2fbc287f11..2e6c5cede5 100644 --- "a/_posts/Tech/Mybatis/2018-08-03-Mybatis\344\271\213@param.md" +++ "b/_posts/Tech/Mybatis/2018-08-03-Mybatis\344\271\213@param.md" @@ -3,6 +3,7 @@ layout: post category: Mybatis title: Mybatis之@param tags: Mybatis +draft: true --- 实现多参数传入 diff --git "a/_posts/Tech/Mybatis/2022-07-09-Mybatis\347\254\224\350\256\260.md" "b/_posts/Tech/Mybatis/2022-07-09-Mybatis\347\254\224\350\256\260.md" index ad34d35b04..579965d84b 100644 --- "a/_posts/Tech/Mybatis/2022-07-09-Mybatis\347\254\224\350\256\260.md" +++ "b/_posts/Tech/Mybatis/2022-07-09-Mybatis\347\254\224\350\256\260.md" @@ -3,6 +3,7 @@ layout: post category: Mybatis title: Mybatis笔记 tags: Mybatis +draft: true --- ## Mybatis笔记 diff --git "a/_posts/Tech/Mysql/2021-01-07-mysql\345\216\237\347\220\2065-\351\224\201.md" "b/_posts/Tech/Mysql/2021-01-07-mysql\345\216\237\347\220\2065-\351\224\201.md" index b8a7011d43..617af295da 100644 --- "a/_posts/Tech/Mysql/2021-01-07-mysql\345\216\237\347\220\2065-\351\224\201.md" +++ "b/_posts/Tech/Mysql/2021-01-07-mysql\345\216\237\347\220\2065-\351\224\201.md" @@ -70,7 +70,7 @@ RR下,select默认无锁,通过MVCC实现,除非自己select语句上加 相关知识点: -- +- todo ## 快照读和当前读 @@ -245,3 +245,30 @@ lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B) lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C) ``` +## 事务里咋用锁 + +### 锁何时加的 + +insert、update、delete,innodb会自动给那一行加行级排他锁 + +RR下,select默认无锁,通过MVCC实现,除非自己select语句上加锁。 + +主动加共享锁:select * from table where id=1 lock in share mode + +主动加排他锁:select * from table where id=1 for update + + innodb存储引擎的默认锁模式: 一行数据,同一个时刻只能一个人在修改,但是别人修改,你可以随便读,读是读某个版本的,走mvcc机制。 + +### 锁何时释放的 + +- RR下。 事务提交时释放锁,因此如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 + +其他隔离级别的见上文。 + + + +### 先读再更新正确用法 + +因此RR下,如果事务里是先读A,再更新A为xx数据,读A是不加锁的普通select,更新A前有可能A已经被更新了。这种情况最好就是事务开始时就读锁锁住,之后再更新。如果期间不想可见就读时加写锁。 + +否则正常情况下,RR用了MVCC普通select没用锁 diff --git "a/_posts/Tech/Mysql/2022-12-03-mysql\346\225\260\346\215\256\345\255\230\345\202\250.md" "b/_posts/Tech/Mysql/2022-12-03-mysql\346\225\260\346\215\256\345\255\230\345\202\250.md" new file mode 100644 index 0000000000..716d7a58e7 --- /dev/null +++ "b/_posts/Tech/Mysql/2022-12-03-mysql\346\225\260\346\215\256\345\255\230\345\202\250.md" @@ -0,0 +1,18 @@ +--- +layout: post +category: Mysql +title: mysql数据存储 +tags: Mysql +published: false +--- + +## mysql 数据存储 + +- [文件系统参考](https://www.cnblogs.com/xiaolincoding/p/13499209.html) +- [MySQL 的数据到底是怎么存的(下)|MySQL 系列(5)](https://ost.51cto.com/posts/11646) + +之后更新。 + +mysql 用的页是自己实现的,不是文件系统特有的。但文件系统可以把文件抽象成多个数据块,也就是说应用可以直接和数据块交互,直接记录数据库指针。此时磁盘类似一个数据空间。 + +不能把所有数据块指针放到内存里。 diff --git "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" "b/_posts/Tech/OOP/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" similarity index 60% rename from "_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" rename to "_posts/Tech/OOP/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" index 9144caef3e..8b2d36831d 100644 --- "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" +++ "b/_posts/Tech/OOP/2018-02-02-UML\347\261\273\345\233\276\345\205\263\347\263\273.md" @@ -1,38 +1,39 @@ --- layout: post -category: 面向对象 +category: OOP title: UML类图关系 tags: Others --- -## UML类图 -[UML教程](http://blog.csdn.net/badobad/article/details/50914624) +## UML 类图 -矩形图表示类,斜体是抽象类,+表示public,-表示private,#表示protected,第二层变量,第三层方法 +[UML 教程](http://blog.csdn.net/badobad/article/details/50914624) -![image](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/45dda629a143e54b6dda0e864f68a08c.png) +矩形图表示类,斜体是抽象类,+表示 public,-表示 private,#表示 protected,第二层变量,第三层方法 +![image](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/45dda629a143e54b6dda0e864f68a08c.png) -1. 继承:空三角箭头实线表示继承。B继承A,B指向A +1. 继承:空三角箭头实线表示继承。B 继承 A,B 指向 A 2. 实现:空三角箭头虚线表示实现 -3. 关联:关联用实线箭头表示,B知道A +3. 关联:关联用实线箭头表示,B 知道 A 4. 依赖:动物的几大特征,比如有新陈代谢,能繁殖。而动物要有生命力,需要氧气、水以及食物等。也就是说,动物依赖于氧气和水。他们之间是依赖关系(Dependency),用虚线箭头来表示。 -5. 聚合:聚合表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。聚合关系用空心的菱形 + 实线箭头来表示。 +5. 聚合:聚合表示一种弱的拥有关系,体现的是 A 对象可以包含 B 对象,但 B 对象不是 A 对象的一部分。聚合关系用空心的菱形 + 实线箭头来表示。 6. 组合:组合(Composition)是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。组合关系用实心的菱形 + 实线箭头来表示。 - - ## 继承、实现、依赖、关联、聚合、组合的联系与区别 分别介绍这几种关系: ### 继承 -指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性; + +指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在 Java 中此类关系通过关键字 extends 明确标识,在设计时一般没有争议性; ### 实现 + 用来实现一个接口,在 Java 中使用 implements 关键字。 ### 依赖 + 和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式: - A 类是 B 类方法的局部变量; @@ -40,22 +41,22 @@ tags: Others - A 类向 B 类发送消息,从而影响 B 类发生变化。 ### 关联 + 表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。 -他体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的;表现在代码层面,为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量; +他体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的;表现在代码层面,为被关联类 B 以类属性的形式出现在关联类 A 中,也可能是关联类 A 引用了一个类型为被关联类 B 的全局变量; -ER图的那种关联关系 +ER 图的那种关联关系 ### 聚合 表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。 -聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分; +聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即 has-a 的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与 CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分; ### 组合 -和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。 - +和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。 但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖; @@ -71,7 +72,7 @@ A 类和 B 类是依赖关系主要有三种形式: - A 类是 B 类方法的参数; - A 类向 B 类发送消息,从而影响 B 类发生变化。 -## is-a,has-a,contains-a怎样应用 +## is-a,has-a,contains-a 怎样应用 is-a = 继承 @@ -79,10 +80,8 @@ has a = 聚合 contains-a = 组合 +假设你确定两件对象之间是 is-a 的关系,那么此时你应该使用继承。比方菱形、圆形和方形都是形状的一种。那么他们都应该从形状类继承。 +假设你确定两件对象之间是 has-a 的关系,那么此时你应该使用聚合。比方电脑是由显示器、CPU、硬盘等组成的。那么你应该把显示器、CPU、硬盘这些类聚合成电脑类。 -假设你确定两件对象之间是is-a的关系,那么此时你应该使用继承。比方菱形、圆形和方形都是形状的一种。那么他们都应该从形状类继承。 - -假设你确定两件对象之间是has-a的关系,那么此时你应该使用聚合。比方电脑是由显示器、CPU、硬盘等组成的。那么你应该把显示器、CPU、硬盘这些类聚合成电脑类。 - -假设你确定两件对象之间是contains-a的关系,那么此时你应该使用组合。比方空调继承于制冷机,但它同一时候有加热功能。那么你应该把让空调继承制冷机类,并实现接口。 \ No newline at end of file +假设你确定两件对象之间是 contains-a 的关系,那么此时你应该使用组合。比方空调继承于制冷机,但它同一时候有加热功能。那么你应该把让空调继承制冷机类,并实现接口。 diff --git "a/_posts/Tech/OOP/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/_posts/Tech/OOP/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 0000000000..aa3722a64c --- /dev/null +++ "b/_posts/Tech/OOP/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,42 @@ +--- +layout: post +category: OOP +title: 设计模式 +tags: 面向对象 +--- + +## 设计模式 + +## 创建型 + +- 单例 Singleton: 饿汉式(预先加载), 懒汉式(用到再加载), 双重校验锁 +- 工厂 Factory:统一方法创建对象,有工厂方法(子类创建)、抽象工厂(创建家族)、简单工厂(单独一个工具类创建) +- 生成器 Buildere: 类似 string builder +- 原型 ProtoType:.copy()方法, 深拷贝 + +## 行为型 + +- 责任链 Responsibility Chain: 类似中间件 middleware, 挨个处理,可提前返回 +- 命令 Command:swing 的 action,有回调 +- 解释器 Interpreter: 比如 formater, 有词法分析 +- 迭代器 Iterator: 遍历 +- 中介者 Mediator: 集中通知和控制 +- 备忘录 Memento:拷贝一份 Origin +- 观察者 Observer: 回调更新 +- 状态 State:状态机 +- 策略 Strategy: 内置几种算法,可以选择,比如 filter +- 模板方法 Template Method: 新子类自定义实现某函数,比如 sort + +## 结构型 + +- 适配器 Adapter: 比如 Array 的 asList +- 桥接 Bridge: 将抽象与实现分离开来 +- 组合 Composite: 整体/部分 +- 装饰 Decorator: 给类动态添加功能 +- 外观 Facade: 统一接口来访问子系统 +- 享元 FlyWeight: 利用共享的方式来支持大量细粒度的对象 +- 代理 Proxy: 控制其他对象的访问 + +## Reference + +- [Java 设计模式](http://www.cyc2018.xyz/%E5%85%B6%E5%AE%83/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20%E7%9B%AE%E5%BD%95.html#%E4%B8%80%E3%80%81%E5%89%8D%E8%A8%80) diff --git "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/_posts/Tech/OOP/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" similarity index 92% rename from "_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" rename to "_posts/Tech/OOP/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" index 71e9f81c69..39526ee50f 100644 --- "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" +++ "b/_posts/Tech/OOP/2020-12-26-\351\235\242\345\220\221\345\257\271\350\261\241.md" @@ -1,6 +1,6 @@ --- layout: post -category: 面向对象 +category: OOP title: 面向对象 tags: 面向对象 --- @@ -48,7 +48,7 @@ Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat ## 四个基本特征 -面向对象程序设计具有4个共同特征:抽象性、封装性、继承性和多态性。 +面向对象程序设计具有 4 个共同特征:抽象性、封装性、继承性和多态性。 ## 重写和重载的区别 @@ -56,17 +56,15 @@ Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 重写: 子类实现父类方法 -## Go是面向对象语言嘛 +## Go 是面向对象语言嘛 -golang这个语言的设计理念和java不同,它更信奉组合优于继承。go不是面向对象语言,但允许OOP的编程风格。 +golang 这个语言的设计理念和 java 不同,它更信奉组合优于继承。go 不是面向对象语言,但允许 OOP 的编程风格。 +封装:首字母大小写为区分,大写的暴露出来,小写的只在 package 内部用。 +继承:一个 struct 可以包含另外一个 struct,同时拥有了它的 methods 以及属性。也叫匿名字段 -封装:首字母大小写为区分,大写的暴露出来,小写的只在package内部用。 - -继承:一个struct可以包含另外一个struct,同时拥有了它的methods以及属性。也叫匿名字段 - -多态:接口的使用,注意不是struct,struct必须是相等的。 +多态:接口的使用,注意不是 struct,struct 必须是相等的。 ```go type AnimalSounder interface { @@ -74,8 +72,6 @@ type AnimalSounder interface { } ``` - - ## 设计原则 ### S.O.L.I.D @@ -177,4 +173,4 @@ type AnimalSounder interface { ## 参考 -- [面向对象思想](http://www.cyc2018.xyz/%E5%85%B6%E5%AE%83/%E7%BC%96%E7%A0%81%E5%AE%9E%E8%B7%B5/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.html#%E4%B8%80%E3%80%81%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7) \ No newline at end of file +- [面向对象思想](http://www.cyc2018.xyz/%E5%85%B6%E5%AE%83/%E7%BC%96%E7%A0%81%E5%AE%9E%E8%B7%B5/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.html#%E4%B8%80%E3%80%81%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7) diff --git "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" "b/_posts/Tech/OOP/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" similarity index 61% rename from "_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" rename to "_posts/Tech/OOP/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" index 39377dc504..74735404ab 100644 --- "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" +++ "b/_posts/Tech/OOP/2022-06-01-\351\235\242\345\220\221\345\257\271\350\261\241\344\271\213OODesign.md" @@ -1,56 +1,48 @@ --- layout: post -category: 面向对象 +category: OOP title: 面向对象之OODesign tags: ObjectOrientedProgramming --- -## 面向对象之OODesign +## 面向对象之 OODesign 常用表达: -- 假设key是int,简化问题 +- 假设 key 是 int,简化问题 - 假设适合存储 -- 假设输入是合法的,不需要check输入合法性 - - +- 假设输入是合法的,不需要 check 输入合法性 ## 应用 参考: [常见问题](https://github.com/donnemartin/system-design-primer#object-oriented-design-interview-questions-with-solutions) - - -| Question | | -| -------------------------------------- | ------------------------------------------------------------ | -| Design a hash map | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/hash_table/hash_map.ipynb) | -| Design a least recently used cache | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/lru_cache/lru_cache.ipynb) | -| Design a call center | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/call_center/call_center.ipynb) | +| Question | | +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Design a hash map | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/hash_table/hash_map.ipynb) | +| Design a least recently used cache | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/lru_cache/lru_cache.ipynb) | +| Design a call center | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/call_center/call_center.ipynb) | | Design a deck of cards | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/deck_of_cards/deck_of_cards.ipynb) | -| Design a parking lot | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/parking_lot/parking_lot.ipynb) | -| Design a chat server | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/online_chat/online_chat.ipynb) | -| Design a circular array | [Contribute](https://github.com/donnemartin/system-design-primer#contributing) | -| Add an object-oriented design question | [Contribute](https://github.com/donnemartin/system-design-primer#contributing) | +| Design a parking lot | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/parking_lot/parking_lot.ipynb) | +| Design a chat server | [Solution](https://github.com/donnemartin/system-design-primer/blob/master/solutions/object_oriented_design/online_chat/online_chat.ipynb) | +| Design a circular array | [Contribute](https://github.com/donnemartin/system-design-primer#contributing) | +| Add an object-oriented design question | [Contribute](https://github.com/donnemartin/system-design-primer#contributing) | -## +## 呼叫中心: -- 定义操作员,上级,上上级。 这3个都是人,继承于一个对象,每次Init时先super init +- 定义操作员,上级,上上级。 这 3 个都是人,继承于一个对象,每次 Init 时先 super init - 定义呼叫中心,进行流转 - 定义电话实体,有状态机 - - 一部牌 -- 定义红桃还是黑桃这样suit -- 定义卡片,组合suit, 有属性分数 +- 定义红桃还是黑桃这样 suit +- 定义卡片,组合 suit, 有属性分数 - 定义手,内部有卡片集合,支持添加卡片,支持输出当前分数 - 定义桌子,用来发牌、洗牌 - - 停车场 - 定义车,车有多种类型 @@ -59,15 +51,13 @@ tags: ObjectOrientedProgramming - 停车场有多层,定义层,层组合了停车位 list - 定义停车场,停车场组合了层 list - - 聊天室 -- 定义User -- 定义UserService +- 定义 User +- 定义 UserService - 定义聊天基类,chat_id, message list, user list - 定义私人聊天 - 定义群聊天 -- 定义消息message +- 定义消息 message 也可以看:Grokking the Object Oriented Design Interview diff --git "a/_posts/Tech/OperationSystem/2022-10-06-\345\235\227\345\255\230\345\202\250\345\222\214\345\257\271\350\261\241\345\255\230\345\202\250\345\222\214\346\226\207\344\273\266\345\255\230\345\202\250.md" "b/_posts/Tech/OperationSystem/2022-10-06-\345\235\227\345\255\230\345\202\250\345\222\214\345\257\271\350\261\241\345\255\230\345\202\250\345\222\214\346\226\207\344\273\266\345\255\230\345\202\250.md" new file mode 100644 index 0000000000..4c78a7f612 --- /dev/null +++ "b/_posts/Tech/OperationSystem/2022-10-06-\345\235\227\345\255\230\345\202\250\345\222\214\345\257\271\350\261\241\345\255\230\345\202\250\345\222\214\346\226\207\344\273\266\345\255\230\345\202\250.md" @@ -0,0 +1,102 @@ +--- +layout: post +category: OperationSystem +title: 块存储和对象存储和文件存储 +tags: OperationSystem +--- + +# 块存储和对象存储和文件存储 + +> [参考](https://www.zhihu.com/question/21536660) + +## 块存储 + +### 它是什么? + +块存储通常意义上,可以理解为一个不带文件系统的裸磁盘,是将裸磁盘空间映射给主机使用的。比如我们日常使用的硬盘、U盘,这是最常见的形式。当然不仅仅可以直接使用物理设备,也通过虚拟化间接使用物理设备(比如VMware、VirtualBox可以创建[虚拟磁盘](https://www.zhihu.com/search?q=虚拟磁盘&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492}))。 + +### 怎么用它? + +我在这里举一个实际例子,假设我们拥有3块1T的硬盘,我们可以直接将裸设备给操作系统使用;也可以通过raid、[逻辑卷](https://www.zhihu.com/search?q=逻辑卷&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})等方式将其映射为一个或多个逻辑盘提供给系统使用。注意前面提到块存储是不带文件系统的,我们是使用客户端的文件系统接口来访问的块存储里的数据。所以客户端的文件系统或操作系统是无法区分映射上来的磁盘是真正的物理磁盘还是[逻辑磁盘](https://www.zhihu.com/search?q=逻辑磁盘&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492}),操作系统就只是对其进行正常分区、格式化、访问等操作。 + +### 为啥选它? + +所以块存储并不是直接提供给用户(或者说自然人)使用,而是提供给专门的文件系统以及专业的备份管理软件、分区软件以及数据库使用的。针对这些特定软件而言,通过块存储直接访问存储设备,会比通过文件系统访问数据效率高。但同时,块存储并没有支持共享协议,所以无法进行共享操作,只能独占。 + +## 文件存储 + +### 它是什么? + +通常意义上,[文件存储](https://www.zhihu.com/search?q=文件存储&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})就是提供文件系统的存储。这里通常意义上是指支持了Posix接口,并且是文件目录管理结构的存储系统。但是现在也把GFS、HDFS这种非标准Posix接口的文件存储系统算为文件存储。 + +### 怎么用它? + +由于自带文件系统,用户可以轻松的访问存储资源。[文件系统](https://www.zhihu.com/search?q=文件系统&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})中最基本的体现形式是目录和文件,数据以文件的方式存储和访问,按照目录结构进行组织和管理。同时与偏向底层的块存储不同,文件存储上升到了应用层,可以提供更加高级的管理服务,可以很方便的共享。常见的NFS、CIFS、FTP等这些都是基于文件存储的基础上提供的[文件共享协议](https://www.zhihu.com/search?q=文件共享协议&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})。 + +### 为啥选它? + +文件系统是非常简单易用的。不论是普通用户要查找读写文件,或者绝大部分应用软件需要存取数据文件通过文件系统操作都是非常方便的。同时文件系统对共享传输的支持也非常好。但是文件存储需要管理维护其庞大复杂的[文件目录树](https://www.zhihu.com/search?q=文件目录树&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492}),对于一个深层文件的访问需要逐层目录访问才能最终进行操作。所以在处理极其海量数据时,性能会有一定影响。 + +## 对象存储 + +### 它是什么? + +从本质上来说,大家可以把对象存储理解成为一个简单的键值访问的存储。所有被存储的文件都可以理解成为一个独立的对象,整体是一种扁平化的结构。 + +### 怎么用它? + +通常对象存储会提供一套易用的HTTP协议的API,供大多数[客户端](https://www.zhihu.com/search?q=客户端&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})访问(不受客户端语言的限制)。对象存储会提供全局唯一的UUID来代表某一个对象文件(比如一个固定的网址https://****.***.com/***/***/***/xxx.jpg),用户可以直接对这个地址进行GET、PUT、DELETE等操作来管理这个文件。 + +### 为啥选它? + +由于对象存储是类似于键值管理的方式,所以利于客户端直接定位并操作数据,非常简单易用。同时对象存储天然适合网络存储的场景,所以非常便于在网络上共享和传输。而且因为不像文件系统拥有[目录结构](https://www.zhihu.com/search?q=目录结构&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492}),不用目录检索以及维护目录结构,在海量文件场景访问时性能和效率表现会更加优秀。 + + + +### 特点 + +[参考](https://www.zhihu.com/question/21536660) + +对象存储其实介于块存储和文件存储之间。文件存储的树状结构以及路径访问方式虽然方便人类理解、记忆和访问,但计算机需要把路径进行分解,然后逐级向下查找,最后才能查找到需要的文件,对于应用程序来说既没必要,也很浪费性能。 + + + +而块存储是排它的,服务器上的某个逻辑块被一台客户端挂载后,其它客户端就无法访问上面的数据了。而且挂载了块存储的客户端上的一个程序要访问里面的数据,不算类似数据库直接访问裸设备这种方式外,通常也需要对其进行分区、安装文件系统后才能使用。除了在网络上传输的数据包效率更高以外,并不比使用文件存储好多少,客户端的文件系统依然需要对[路径分解](https://www.zhihu.com/search?q=路径分解&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A1159036357}),然后逐级查找才能定位到某一个具体的文件。 + + + +是否可以用不排它但又类似块设备访问的方式呢?理论上是可以的,但对块设备的访问方式虽然比文件存储快,其实也很麻烦——一个文件往往是由多个块组成,并且很可能是不连续的。例如要读取一个文件,可能需要发出这样的指令: + +- 读取从编号A₁开始的N₁个块; +- 读取从编号A₂开始的N₂个块; +- 读取从编号A₃开始的N₃个块; +- ………… +- 读取从[编号Ai](https://www.zhihu.com/search?q=编号Ai&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A1159036357})开始的Ni个块。 + +最后自行把这i个连续的块自行拼接成一个文件,这才完成了一个文件的读取操作。为了发出这些指令,访问文件的软件系统需要记录下这个文件分成多少个部分,每个部分的起始块编号是多少,有多少块,顺序如何。不单是读取操作,删除、写入、修改操作也是如此,非常麻烦复杂。而且往往一个文件可能需要被多个系统访问使用,这就更麻烦了。 + + + +为了解决这中麻烦,使用一个统一的底层存储系统,管理这些文件和底层介质的组织结构,然后给每个文件一个唯一的标识,其它系统需要访问某个文件,直接提供文件的标识就可以了。存储系统可以用更高效的数据组织方式来管理这些标识以及其对应的存储介质上的块。 + +## 总结 + +简单来说,块存储直接与硬盘打交道,读写效率最高,但是不利于共享。文件存储访问简单,利于共享,但是海量数据场景下访问性能可能存在瓶颈。对象存储既天然支持网络共享,同时也适用于海量数据的场景。当然现如今,随着文件存储和对象存储的发展,涌现了越来越多的[分布式文件](https://www.zhihu.com/search?q=分布式文件&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A2552351492})或对象存储,在海量数据、高并发、性能上做的越来越好。块存储、文件存储和对象存储都存在各自不同的适用场景,并不存在绝对意义上的优劣之差。 + + + +# 总结 + +这三者的本质差别是使用数据的“用户”不同:块存储的用户是可以读写块设备的软件系统,例如传统的文件系统、数据库;文件存储的用户是自然人;对象存储的用户则是一些其他软件。 + +访问协议也不同 + +先说一下文件存储,主要操作对象是文件和文件夹。以 NFS 为例,文件相关的接口包括:LOOKUP/ACCESS/READ/WRITE/CREATE/REMOVE/RENAME 等等,文件夹相关的接口包括:MKDIR/RMDIR/READDIR 等等。同时也会有 FSSTAT/FSINFO 等接口用于提供文件系统级别的信息。POSIX,SAMBA 等也是文件存储协议。协议更注重接口的灵活,以及访问权限控制。 + +块存储,主要操作对象是磁盘。以 SCSI 为例,主要接口有 Read/Write/Read Capacity/Inquiry 等等。FC,iSCSI,也是块存储协议。和文件存储相比,没有文件和目录树的概念,一般协议也不会定义磁盘的创建和删除操作。协议更注重传输控制。 + +对象存储,主要操作对象是对象(Object)。以 S3 为例,主要接口有 PUT/GET/DELETE 等。和文件和对象存储相比,没有随机读写的接口。和文件存储相比,没有目录树的概念。协议更注重简洁。 + + + +因此对象存储适合一些比如TOS, AWS S3这样的,如果要改只能重新上传。 diff --git "a/_posts/Tech/Python/2021-01-22-python\344\276\235\350\265\226requirements.md" "b/_posts/Tech/Python/2021-01-22-python\344\276\235\350\265\226requirements.md" deleted file mode 100644 index c93bd66299..0000000000 --- "a/_posts/Tech/Python/2021-01-22-python\344\276\235\350\265\226requirements.md" +++ /dev/null @@ -1,56 +0,0 @@ ---- -layout: post -category: Python -title: 依赖requirements -tags: Python ---- - -## python依赖requirements - -在虚拟环境中使用pip生成: - -``` -pip freeze >requirements.txt -``` - - - -示例: - -``` -alembic==0.8.6 -bleach==1.4.3 -click==6.6 -dominate==2.2.1 -Flask==0.11.1 -Flask-Bootstrap==3.3.6.0 -Flask-Login==0.3.2 -Flask-Migrate==1.8.1 -Flask-Moment==0.5.1 -Flask-PageDown==0.2.1 -Flask-Script==2.0.5 -Flask-SQLAlchemy==2.1 -Flask-WTF==0.12 -html5lib==0.9999999 -itsdangerous==0.24 -Jinja2==2.8 -Mako==1.0.4 -Markdown==2.6.6 -MarkupSafe==0.23 -PyMySQL==0.7.5 -python-editor==1.0.1 -six==1.10.0 -SQLAlchemy==1.0.14 -visitor==0.1.3 -Werkzeug==0.11.10 -WTForms==2.1 -``` - - - -当需要创建这个虚拟环境的完全副本,可以创建一个新的虚拟环境,并在其上运行以下命令: - -``` -pip install -r requirements.txt -``` - diff --git "a/_posts/Tech/Python/2022-10-04-python\351\241\271\347\233\256\347\216\257\345\242\203.md" "b/_posts/Tech/Python/2022-10-04-python\351\241\271\347\233\256\347\216\257\345\242\203.md" new file mode 100644 index 0000000000..7dad6b1899 --- /dev/null +++ "b/_posts/Tech/Python/2022-10-04-python\351\241\271\347\233\256\347\216\257\345\242\203.md" @@ -0,0 +1,120 @@ +--- +layout: post +category: Python +title: python项目环境 +tags: Python +--- + +## python项目环境 + +## pip + +> [参考](https://www.runoob.com/w3cnote/python-pip-install-usage.html) +> +> 以下是对系统python环境修改,如果是项目级别的,记住先venv后再venv里进行pip3的操作 + +你可以通过以下命令来判断是否已安装: + +```shell +pip3 --version # Python3.x 版本命令 +# 可以alias pip=pip3 +pip install -U pip # pip升级 +``` + +指定pip也可以 + +```sh +sudo pip2 install 模块名 +``` + +pip源修改 阿里源即可 + +```sh +# 清华源 +pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + +# 或: +# 阿里源 +pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ +# 腾讯源 +pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple +# 豆瓣源 +pip config set global.index-url http://pypi.douban.com/simple/ +``` + +### requirements + +> 适用于 **单虚拟环境的情况:为什么只适用于单虚拟环境?因为这种方式,会将环境中的依赖包全都加入,如果使用的全局环境,则下载的所有包都会在里面,不管是不时当前项目依赖的** +> +> 非venv请不要用这个 + + + +查看当前pip install的 + +```sh +pip3 list # 输出当前列表 +``` + +更新requirements文件,每次pip install新的后都需要执行一次 + +``` +pip freeze > requirements.txt +``` + +**使用requirements.txt安装依赖** + +``` +pip install ``-``r requirements.txt +``` + +## venv 虚拟环境 + +> [参考](https://docs.python.org/zh-cn/3/library/venv.html#creating-virtual-environments) +> +> 虚拟环境,和系统Python环境隔离,好处是互不影响,项目级别推荐。 + +创建虚拟环境 (后面venv是名字,一个项目可以多个venv,这里就叫venv,不用起其他名字了) + +```sh +python3 -m venv venv +``` + +要使用虚拟环境就必须激活 + +```bash + ➜ source venv/bin/activate +``` + +然后在命令行中就会出现 + +```scss +(venv) ➜ +``` + +说明虚拟环境已经激活,进入到venv shell了 + + + +venv里有pip和Pip3提供, python3也是venv自己的,相当于一个新的shell,然后覆盖了一些python相关命令 + + + +要取消虚拟环境,使用 + +```scss +(venv) ➜ deactivate +``` + +## pycharm结合使用 + +可通过venv命令行创建好python环境,然后使用pycharm设置里设置interpreter 来切换到venv。 + + + +## example + +1. 创建venv, 如果有就激活 +2. venv shell里pip requirement操作,安装和更新requirement文件 +3. pycharm设置interpreter + diff --git "a/_posts/Tech/Scala/2022-08-16-scala\350\257\255\346\263\225.md" "b/_posts/Tech/Scala/2022-08-16-scala\350\257\255\346\263\225.md" new file mode 100644 index 0000000000..b3863d9683 --- /dev/null +++ "b/_posts/Tech/Scala/2022-08-16-scala\350\257\255\346\263\225.md" @@ -0,0 +1,1246 @@ +--- +layout: post +category: Scala +title: scala语法 +tags: Scala +recent_update: true +--- + +# scala语法 + +## hello world + +创建一个 HelloWorld.scala 的文件 + +```scala +object HelloWorld { + /* 这是我的第一个 Scala 程序 + * 以下程序将输出'Hello World!' + */ + def main(args: Array[String]) { + println("Hello, world!") // 输出 Hello World + } +} +``` + +运行 + +```scala +$ scalac HelloWorld.scala +$ ls +HelloWorld$.class HelloWorld.scala +HelloWorld.class + +运行 +$ scala HelloWorld +Hello, world! +``` + +- **类名** - 对于所有的类名的第一个字母要大写。如果需要使用几个单词来构成一个类的名称,每个单词的第一个字母要大写。 + +- **方法名称** - 所有的方法名称的第一个字母用小写。如果若干单词被用于构成方法的名称,则每个单词的第一个字母应大写。 + +- **def main(args: Array[String])** - Scala程序从main()方法开始处理,这是每一个Scala程序的强制程序入口部分。 + +## Scala 包 + +### 定义包 + +Scala 使用 package 关键字定义包,在Scala将代码定义到某个包中有两种方式: + +第一种方法和 Java 一样,在文件的头定义包名,这种方法就后续所有代码都放在该包中。 比如: + +``` +package com.runoob +class HelloWorld +``` + +第二种方法有些类似 C#,如: + +``` +package com.runoob { + class HelloWorld +} +``` + +第二种方法,可以在一个文件中定义多个包。 + +### 引用 + +Scala 使用 import 关键字引用包。 + +```scala +import java.awt.Color // 引入Color + +import java.awt._ // 引入包内所有成员 + +def handler(evt: event.ActionEvent) { // java.awt.event.ActionEvent + ... // 因为引入了java.awt,所以可以省去前面的部分 +} +``` + +import语句可以出现在任何地方,而不是只能在文件顶部。import的效果从开始延伸到语句块的结束。这可以大幅减少名称冲突的可能性。 + +如果想要引入包中的几个成员,可以使用selector(选取器): + +```scala +import java.awt.{Color, Font} + +// 重命名成员 +import java.util.{HashMap => JavaHashMap} + +// 隐藏成员 +import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了 +``` + +> **注意:**默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。 + + + +## 数据类型 + +### 数据类型 + +和Java一致 + +| 数据类型 | 描述 | +| :------- | :----------------------------------------------------------- | +| Byte | 8位有符号补码整数。数值区间为 -128 到 127 | +| Short | 16位有符号补码整数。数值区间为 -32768 到 32767 | +| Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 | +| Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 | +| Float | 32 位, IEEE 754 标准的单精度浮点数 | +| Double | 64 位 IEEE 754 标准的双精度浮点数 | +| Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF | +| String | 字符序列 | +| Boolean | true或false | +| Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 | +| Null | null 或空引用 | +| Nothing | Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 | +| Any | Any是所有其他类的超类 | +| AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 | + + + +### 字符字面量 + +在 Scala 字符变量使用单引号 **'** 来定义,如下: + +``` +'a' +'\u0041' +'\n' +'\t' +``` + +其中 **\** 表示转义字符,其后可以跟 **u0041** 数字或者 **\r\n** 等固定的转义字符。 + +### 字符串字面量 + +在 Scala 字符串字面量使用双引号 **"** 来定义,如下: + +``` +"Hello,\nWorld!" +"菜鸟教程官网:www.runoob.com" +``` + +## 变量声明 + +声明变量实例如下: + +```scala +var myVar : String = "Foo" +var myVar : String = "Too" +``` + +在 Scala 中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的。 + +所以,如果在没有指明数据类型的情况下声明变量或常量必须要给出其初始值,否则将会报错。 + +```scala +var myVar = 10; +val myVal = "Hello, Scala!"; +``` + +## 访问修饰符 + +Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。 + +如果没有指定访问修饰符,默认情况下,Scala 对象的访问级别都是 public。 + +Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。 + + + +```scala +class Outer{ + class Inner{ + private def f(){ + println("f") + } + class InnerMost{ + f() // 正确 + } + } + (new Inner).f() //错误 +} +``` + + + +## if else 循环 + +```scala +object Test { + def main(args: Array[String]) { + var x = 30; + + if( x < 20 ){ + println("x 小于 20"); + }else{ + println("x 大于等于 20"); + } + } +} +``` + + + +```scala +object Test { + def main(args: Array[String]) { + var a = 10; + // 无限循环 + while( true ){ + println( "a 的值为 : " + a ); + } + } +} +``` + +Scala 语言中默认是没有 break 语句,但是你在 Scala 2.8 版本后可以使用另外一种方式来实现 *break* 语句。当在循环中使用 **break** 语句,在执行到该语句时,就会中断循环并执行循环体之后的代码块。 + +```scala +// 导入以下包 +import scala.util.control._ + +// 创建 Breaks 对象 +val loop = new Breaks; + +// 在 breakable 中循环 +loop.breakable{ + // 循环 + for(...){ + .... + // 循环中断 + loop.break; + } +} +``` + +## implicit + +这个关键字可以方法前面,可以变量前面,它的含义:隐式参数,类似**参数默认值** + +``` +implicit +``` + +方法可以具有 *隐式* 参数列表,由参数列表开头的 *implicit* 关键字标记。 如果参数列表中的参数没有像往常一样传递, Scala 将查看它是否可以获得正确类型的隐式值,如果可以,则自动传递。 + +Scala 将查找这些参数的位置分为两类: + +- Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。 +- 然后,它在所有伴生对象中查找与隐式候选类型相关的有隐式标记的成员。 + + + +比如[参考](https://docs.scala-lang.org/zh-cn/tour/implicit-parameters.html) 里的例子, + +定了一个隐式定义stringMonoid 它类型是Monoid泛型String + +```scala + +object ImplicitTest { + // 隐式变量 + implicit val stringMonoid: Monoid[String] = new Monoid[String] { + def add(x: String, y: String): String = x concat y + def unit: String = "" + } + + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + def main(args: Array[String]): Unit = { + println(sum(List(1, 2, 3))) // uses IntMonoid implicitly + println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly + } +} +``` + +然后sum里定义了隐式参数 implicit m ,当你调用该函数时可以不手动指定这个m,它会自动找对应的变量值。其实就是按类型自动匹配。 + + + +更全[参考](http://icejoywoo.github.io/2018/12/29/scala-implicit.html) + +- 还可以隐式函数,类(只能局部类) + +## 方法和函数 + +cala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。 + +```scala +object add{ + def addInt( a:Int, b:Int ) : Int = { + var sum:Int = 0 + sum = a + b + + return sum + } +} +``` + +如果方法没有返回值,可以返回为 **Unit**,这个类似于 Java 的 **void**, 实例如下: + +```scala +object Hello{ + def printMe( ) : Unit = { + println("Hello, Scala!") + } +} +``` + + + +### 函数传名调用(call-by-name) + +- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部 + +在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。 + +这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。 + +```scala +object Test { + def main(args: Array[String]) { + delayed(time()); + } + + def time() = { + println("获取时间,单位为纳秒") + System.nanoTime + } + def delayed( t: => Long ) = { + println("在 delayed 方法内") + println("参数: " + t) + t + } +} +``` + +以上实例中我们声明了 delayed 方法, 该方法在变量名和变量类型使用 => 符号来设置传名调用。执行以上代码,输出结果如下: + +``` +$ scalac Test.scala +$ scala Test +在 delayed 方法内 +获取时间,单位为纳秒 +参数: 241550840475831 +获取时间,单位为纳秒 +``` + +实例中 delay 方法打印了一条信息表示进入了该方法,接着 delay 方法打印接收到的值,最后再返回 t。 + + + + + +函数也可以传递 + +```scala +object Test { + def main(args: Array[String]) { + + println( apply( layout, 10) ) + + } + // 函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v + def apply(f: Int => String, v: Int) = f(v) + + def layout[A](x: A) = "[" + x.toString() + "]" + +} +``` + +也像python支持内嵌。 + +### 指定函数参数名 + +一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数,实例如下: + +```scala +object Test { + def main(args: Array[String]) { + printInt(b=5, a=7); + } + def printInt( a:Int, b:Int ) = { + println("Value of a : " + a ); + println("Value of b : " + b ); + } +} +``` + +### 可变参数 + +```scala +object Test { + def main(args: Array[String]) { + printStrings("Runoob", "Scala", "Python"); + } + def printStrings( args:String* ) = { + var i : Int = 0; + for( arg <- args ){ + println("Arg value[" + i + "] = " + arg ); + i = i + 1; + } + } +} +``` + +### 默认参数值 + +```scala +object Test { + def main(args: Array[String]) { + println( "返回值 : " + addInt() ); + } + def addInt( a:Int=5, b:Int=7 ) : Int = { + var sum:Int = 0 + sum = a + b + + return sum + } +} +``` + + + +### 匿名函数 类似lambda + +下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数: + +``` +var inc = (x:Int) => x+1 +``` + +上述定义的匿名函数,其实是下面这种写法的简写: + +```scala +def add2 = new Function1[Int,Int]{ + def apply(x:Int):Int = x+1; +} +``` + +以上实例的 inc 现在可作为一个函数,使用方式如下: + +``` +var x = inc(7)-1 +``` + + + +### 柯里化 + +柯里化的函数被应用于多个参数列表,而不仅仅是一个。 例如 + +``` +def plainOldSum(x: Int, y: Int) = x + y +``` + + + +被柯里化为 + +``` +def curriedSum(x: Int)(y: Int) = x + y +``` + + + +也可以这样用 + +``` +val onePlus = curriedSum(1)_ +``` + + + +curriedSum(1)_里的下划线是第二个参数列表的占位符,结果就是指向一个函数的参考,这函数在被调用时,对它唯一的Int参数加1并返回结果。 + + + +### 花括号和小括号 + +[参考](https://juejin.cn/post/7083041458558599176) + +对于小括号来说,必须的场景是,多参数函数调用的时候。举个例子: + +```scala +def foo(n: Int) = n * 2 +def foo1(a: Int, b: Int) = (a + b) * 2 +``` + +那么以下三种方式调用`foo`是等效的: + +```scala +foo(1) +foo{1} +foo 1 +``` + +但是,调用`foo1`就必须使用`()`,即 + +```scala +foo1(1, 2) +``` + +也就是说,单参数函数可以使用花括号代替小括号。其实,这里本质上 + +一个经典的例子: + +```scala +package example + +object MyExample { + + def greet(name: => String): Unit = { + println(s"Hello $name") + } + + def main(args: Array[String]): Unit = { + val foo = "Foo" + greet(foo) + + greet { + println("In greet args pass") + "foo" + } + } +} +``` + +代码输出: + +```python +Hello Foo +In greet args pass +Hello foo +``` + +其实,这里本上是第二个`greet`传入了一个表达式,表达式返回了一个`String`类型,这相当于是表达式是一个参数,本质上是无括号调用,然后跟了一个表达式作为参数。当然,单纯的一个`String`本身也是表达式。 + +### 内建控制结构 + +让客户代码看上去更像内建控制结构的一种方式是使用花括号代替小括号包围参数列表。Scala的任何方法调用,如果你确实只传入一个参数,就能可选地使用花括号替代小括号包围参数。例如如果一个函数有两个参数,你可以使用柯里化把第一个参数,拖入分离的参数列表,这将使函数仅剩下列表的第二个参数作为唯一的参数,在调用方法时可将第二个参数用花括号括起来。 例如 + +```scala +def withPrintWriter(file: File)(op: PrintWriter => Unit): Unit = { + val writer = new PrintWriter(file) + try { + op(writer) + } finally { + writer.close() + } + } + + val file = new File("date.txt") + withPrintWriter(file){ + writer => writer.println(new java.util.Date) + } +``` + +### 偏应用函数 + +这种一个下划线代表全部参数列表的表达式的另一种用途,就是把它当做转换def为函数值的方式。例如, + +```scala +val b = sum(1, _:Int, 3) //调用b(2)生成6 +``` + +也叫偏函数,这个名字来源于函数未被应用于它的所有参数。 如果你正在写一个省略所有参数的偏程序表达式,如println _ 或sum _,而且在代码的那个地方正好需要一个函数,你可以去掉下环线从而表达的更简明,例如 + +```scala +examples.foreach(println) +``` + + + +### 无参方法 + +Scala里,方法调用的空括号可以省掉,惯例是如果方法带有副作用就加上括号,如println();如果没有副作用就去掉括号,如String的toLowerCase方法。 + +## 数组 + +以下是 Scala 数组声明的语法格式: + +```scala +var z:Array[String] = new Array[String](3) + +或 + +var z = new Array[String](3) +``` + +以上语法中,z 声明一个字符串类型的数组,数组长度为 3 ,可存储 3 个元素。我们可以为每个元素设置值,并通过索引来访问每个元素,如下所示: + +```scala +z(0) = "Runoob"; z(1) = "Baidu"; z(4/2) = "Google" +``` + +最后一个元素的索引使用了表达式 **4/2** 作为索引,类似于 **z(2) = "Google"**。 + +我们也可以使用以下方式来定义一个数组: + +```scala +var z = Array("Runoob", "Baidu", "Google") +``` + + + + + +```scala + // 输出所有数组元素 + for ( x <- myList ) { + println( x ) + } + + // 计算数组所有元素的总和 + var total = 0.0; + for ( i <- 0 to (myList.length - 1)) { + total += myList(i); + } + println("总和为 " + total); +``` + +## 集合Collection + +```scala +// 定义整型 List +val x = List(1,2,3,4) + +// 定义 Set +val x = Set(1,3,5,7) + +// 定义 Map +val x = Map("one" -> 1, "two" -> 2, "three" -> 3) + +// 创建两个不同类型元素的元组 +val x = (10, "Runoob") + +// 定义 Option +val x:Option[Int] = Some(5) +``` + +| 序号 | 集合及描述 | +| :--- | :----------------------------------------------------------- | +| 1 | [Scala List(列表)](https://www.runoob.com/scala/scala-lists.html)List的特征是其元素以线性方式存储,集合中可以存放重复对象。参考 [API文档](http://www.scala-lang.org/api/current/scala/collection/immutable/List.html) | +| 2 | [Scala Set(集合)](https://www.runoob.com/scala/scala-sets.html)Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。参考 [API文档](http://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) | +| 3 | [Scala Map(映射)](https://www.runoob.com/scala/scala-maps.html)Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。参考 [API文档](http://www.scala-lang.org/api/current/scala/collection/immutable/Map.html) | +| 4 | [Scala 元组](https://www.runoob.com/scala/scala-tuples.html)元组是不同类型的值的集合 | +| 5 | [Scala Option](https://www.runoob.com/scala/scala-options.html)Option[T] 表示有可能包含值的容器,也可能不包含值。 | +| 6 | [Scala Iterator(迭代器)](https://www.runoob.com/scala/scala-iterators.html)迭代器不是一个容器,更确切的说是逐一访问容器内元素的方法。 | + + + +## 语法符号对比 + +<- 类似go的:= + +=》函数返回值 + +scala 可以不用显示return,最后的变量就默认return. + +## 类和对象 + +```scala +class Point(xc: Int, yc: Int) { + var x: Int = xc + var y: Int = yc + + def move(dx: Int, dy: Int) { + x = x + dx + y = y + dy + println ("x 的坐标点: " + x); + println ("y 的坐标点: " + y); + } +} + +val pt = new Point(10, 20); + +// 移到一个新的位置 +pt.move(10, 10); +``` + + + +### 继承 + +Scala继承一个基类跟Java很相似, 但我们需要注意以下几点: + +1、重写一个非抽象方法必须使用override修饰符。 + +2、只有主构造函数才可以往基类的构造函数里写参数。 + +3、在子类中重写超类的抽象方法时,你不需要使用override关键字。 + + + +Scala 使用 extends 关键字来继承一个类。实例中 Location 类继承了 Point 类。Point 称为父类(基类),Location 称为子类。 + +**override val xc** 为重写了父类的字段。 + +继承会继承父类的所有属性和方法,Scala 只允许继承一个父类。 + + + + + +```scala +import java.io._ + +class Point(val xc: Int, val yc: Int) { + var x: Int = xc + var y: Int = yc + def move(dx: Int, dy: Int) { + x = x + dx + y = y + dy + println ("x 的坐标点 : " + x); + println ("y 的坐标点 : " + y); + } +} + +class Location(override val xc: Int, override val yc: Int, + val zc :Int) extends Point(xc, yc){ + var z: Int = zc + + def move(dx: Int, dy: Int, dz: Int) { + x = x + dx + y = y + dy + z = z + dz + println ("x 的坐标点 : " + x); + println ("y 的坐标点 : " + y); + println ("z 的坐标点 : " + z); + } +} + +object Test { + def main(args: Array[String]) { + val loc = new Location(10, 20, 15); + + // 移到一个新的位置 + loc.move(10, 10, 5); + } +} + +``` + +### 构造函数 + +[参考](https://www.cnblogs.com/zsql/p/10952565.html) + +1、主构造器的声明直接放在类名后面 + +2、主构造器会执行所有的代码,方法定义除外 + +3、如果主构造无参数,后面的小括号可以省略(简单,不作证明) + +4、如果想让主构造器私有化,则可以在(参数)前面添加private关键字 + +5、从上面代码中我们在Person类中有三个变量 + +被val修饰,则为只读属性,会生成一个相当于get的方法(反编译看) + +被var修饰,则为读写属性,会生成一个相当于get和set的方法 + +没有被修饰,则是一个局部变量,则不会生成任何方法 + +### 抽象方法 + +Scala里一个方法只要没有没有实现(即没有括号或方法体),它就是抽象的,并不需要像Java里的abstract修饰符。 + +### Scala 单例对象 伴生 + +在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object。 + +Scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object对象不能带参数。 + +当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。 + + + +```scala +/ 私有构造方法 +class Marker private(val color:String) { + + println("创建" + this) + + override def toString(): String = "颜色标记:"+ color + +} + +// 伴生对象,与类名字相同,可以访问类的私有属性和方法 +object Marker{ + + private val markers: Map[String, Marker] = Map( + "red" -> new Marker("red"), + "blue" -> new Marker("blue"), + "green" -> new Marker("green") + ) + + def apply(color:String) = { + if(markers.contains(color)) markers(color) else null + } + + + def getMarker(color:String) = { + if(markers.contains(color)) markers(color) else null + } + def main(args: Array[String]) { + println(Marker("red")) + // 单例函数调用,省略了.(点)符号 + println(Marker getMarker "blue") + } +} + +``` + + + +### apply函数 + +可以把对象当函数使用。 + +```scala +class MyAdder(x: Int) { + def apply(y: Int) = x + y +} + +val adder = new MyAdder(2) +val result = adder(4) // equivalent to x.apply(4) +``` + +对象当factory. 只要apply = new XXX. + +``` +scala> object FooMaker { + | def apply() = new Foo + | } +defined module FooMaker + +scala> val newFoo = FooMaker() +newFoo: Foo = Foo@5b83f762 +``` + +### Trait(特征) + +Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。 + +与接口不同的是,它还可以定义属性和方法的实现。 + +一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。 + +Trait(特征) 定义的方式与类类似,但它使用的关键字是 **trait**,如下所示: + +```scala +trait Equal { + def isEqual(x: Any): Boolean + def isNotEqual(x: Any): Boolean = !isEqual(x) +}trait Equal { + def isEqual(x: Any): Boolean + def isNotEqual(x: Any): Boolean = !isEqual(x) +} + +class Point(xc: Int, yc: Int) extends Equal { + var x: Int = xc + var y: Int = yc + def isEqual(obj: Any) = + obj.isInstanceOf[Point] && + obj.asInstanceOf[Point].x == x +} + +object Test { + def main(args: Array[String]) { + val p1 = new Point(2, 3) + val p2 = new Point(2, 4) + val p3 = new Point(3, 3) + + println(p1.isNotEqual(p2)) + println(p1.isNotEqual(p3)) + println(p1.isNotEqual(2)) + } +} +``` + + + +**特征构造顺序** + +特征也可以有构造器,由字段的初始化和其他特征体中的语句构成。这些语句在任何混入该特征的对象在构造时都会被执行。 + +构造器的执行顺序: + +- 调用超类的构造器; +- 特征构造器在超类构造器之后、类构造器之前执行; +- 特征由左到右被构造; +- 每个特征当中,父特征先被构造; +- 如果多个特征共有一个父特征,父特征不会被重复构造 +- 所有特征被构造完毕,子类被构造。 + +构造器的顺序是类的线性化的反向。线性化是描述某个类型的所有超类型的一种技术规格。 + + + +### extends 和 with + +[参考](https://stackoverflow.com/questions/41031166/scala-extends-vs-with) + +If you have multiple `class`es or `trait`s to inherit, the first one is always `extends`, and the following >=0 `class`/`trait` to be `with`s. + +But remember that you can only *inherit* <=1 (abstract) class, which means if you need to inherit a parent class (`Parent`), it should always comes at first of the form `... extends Parent ...`, and no more classes can be inherited to the derived class. + +scala一个类最多可以继承一个实体/抽象类,但对trait继承数量没有限制。 在java中,trait叫interface,对应叫实现implements. + +Extends 表示第一个集成的class/abstract class/trait, 如果trait和class都有,那就extends class with trait. 当只有trait就可以extends trait. + +### 命名空间 + +Scala一般来说仅有两个命名空间 + +- 值(字段,方法,包,单例对象) +- 类型(类,特质名) + +所以字段可以重写无参数方法,而且Scala里禁止在同一个类里用同样的名称定义字段和方法。 + +### override + +Scala要求,若子类成员重写了父类的具体成员则必须带有override修饰符,若成员实现的是同名的抽象成员时,则这个修饰符是可选的。 + +### 样本类case class + +case class最重要的功能,支持模式匹配,这也是定义case class的重要原因。 + +- 它会添加与类名一致的工厂方法。 +- 样本类参数列表的所有参数隐式获得了val前缀,因此它被当做字段维护。 +- 编译器为你的类添加了方法toString、hashcode、equals的“自然”实现。 +- 样本类支持模式匹配。 +- 自动创建伴生对象,同时在里面给我们实现子apply方法,使得我们在使用的时候可以不直接显示地new对象 +- 伴生对象中同样会帮我们实现unapply方法,从而可以将case class应用于模式匹配,关于unapply方法我们在后面的“提取器”那一节会重点讲解 + + + +比如这样 + +```scala +object Test { + def main(args: Array[String]) { + val alice = new Person("Alice", 25) + val bob = new Person("Bob", 32) + val charlie = new Person("Charlie", 32) + + for (person <- List(alice, bob, charlie)) { + person match { + case Person("Alice", 25) => println("Hi Alice!") + case Person("Bob", 32) => println("Hi Bob!") + case Person(name, age) => + println("Age: " + age + " year, name: " + name + "?") + } + } + } + // 样例类 + case class Person(name: String, age: Int) +} +``` + + + +case class 和 case object 区别:类中有参和无参,当类有参数的时候,用case class ,当类没有参数的时候那么用case object。 + + + + + +### 类实例当函数 + +类也可以扩展Function,这些类的实例可以使用()调用。 + +``` +scala> class AddOne extends Function1[Int, Int] { + | def apply(m: Int): Int = m + 1 + | } +defined class AddOne + +scala> val plusOne = new AddOne() +plusOne: AddOne = + +scala> plusOne(1) +res0: Int = 2 +``` + +可以使用更直观快捷的`extends (Int => Int)`代替`extends Function1[Int, Int]` + +``` +class AddOne extends (Int => Int) { + def apply(m: Int): Int = m + 1 +``` + +## 模式匹配case match + +模式匹配是检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。它是Java中的`switch`语句的升级版,同样可以用于替代一系列的 if/else 语句。 + +### 语法 + +Scala 提供了强大的模式匹配机制,应用也非常广泛。 + +一个模式匹配包含了一系列备选项,每个都开始于关键字 **case**。每个备选项都包含了一个模式及一到多个表达式。箭头符号 **=>** 隔开了模式和表达式。 + +以下是一个简单的整型值模式匹配实例: + + + +```scala +object Test { + def main(args: Array[String]) { + println(matchTest(3)) + + } + def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" + // 其中最后一个case _表示匹配其余所有情况,在这里就是其他可能的整型值。 + } +} + + +object Test { + def main(args: Array[String]) { + println(matchTest("two")) + println(matchTest("test")) + println(matchTest(1)) + println(matchTest(6)) + + } + def matchTest(x: Any): Any = x match { + case 1 => "one" + case "two" => 2 + case y: Int => "scala.Int" + case _ => "many" + } +} +``` + +### 样例类(case classes)的匹配 + +样例类非常适合用于模式匹配。 + +```scala +abstract class Notification + +case class Email(sender: String, title: String, body: String) extends Notification + +case class SMS(caller: String, message: String) extends Notification + +case class VoiceRecording(contactName: String, link: String) extends Notification +``` + +`Notification` 是一个虚基类,它有三个具体的子类`Email`, `SMS`和`VoiceRecording`,我们可以在这些样例类(Case Class)上像这样使用模式匹配: + +```scala +def showNotification(notification: Notification): String = { + notification match { + case Email(sender, title, _) => + s"You got an email from $sender with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" + case VoiceRecording(name, link) => + s"you received a Voice Recording from $name! Click the link to hear it: $link" + } +} +val someSms = SMS("12345", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") + +println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there? + +println(showNotification(someVoiceRecording)) // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +``` + +`showNotification`函数接受一个抽象类`Notification`对象作为输入参数,然后匹配其具体类型。(也就是判断它是一个`Email`,`SMS`,还是`VoiceRecording`)。在`case Email(sender, title, _)`中,对象的`sender`和`title`属性在返回值中被使用,而`body`属性则被忽略,故使用`_`代替。 + +### 模式守卫(Pattern guards) + +为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上`if `。 + +```scala +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { + notification match { + case Email(sender, _, _) if importantPeopleInfo.contains(sender) => + "You got an email from special someone!" + case SMS(number, _) if importantPeopleInfo.contains(number) => + "You got an SMS from special someone!" + case other => + showNotification(other) // nothing special, delegate to our original showNotification function + } +} + +val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") + +val someSms = SMS("867-5309", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") +val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") +val importantSms = SMS("867-5309", "I'm here! Where are you?") + +println(showImportantNotification(someSms, importantPeopleInfo)) +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) +println(showImportantNotification(importantEmail, importantPeopleInfo)) +println(showImportantNotification(importantSms, importantPeopleInfo)) +``` + +在`case Email(sender, _, _) if importantPeopleInfo.contains(sender)`中,除了要求`notification`是`Email`类型外,还需要`sender`在重要人物列表`importantPeopleInfo`中,才会匹配到该模式。 + +## 异常处理 + +```scala +import java.io.FileReader +import java.io.FileNotFoundException +import java.io.IOException + +object Test { + def main(args: Array[String]) { + try { + val f = new FileReader("input.txt") + } catch { + case ex: FileNotFoundException => { + println("Missing file exception") + } + case ex: IOException => { + println("IO Exception") + } + } finally { + println("Exiting finally...") + } + } +} +``` + +## _ 下划线的一些魔法 + +[参考](https://liam.page/2019/10/24/underscore-magic-in-Scala/) + +在 Scala 导入模块包时,`_` 的作用类似于 Java 导入模块包时的 `*`。 + +在 Scala 中的模式匹配中,下划线 `_` 是匹配任意内容的通配符。最基本的用法时,`_` 相当于 C/C++ 中的 `default`。 + + + +**匿名函数的参数** + +Scala 和 Python、C++ 等语言一样,也有匿名函数的设定。下划线 `_` 可用作是匿名函数的参数的占位符,但对于每一个参数,只能用下划线占位一次。例如,在 Scala 中 `2 * _` 相当于 Python 中的 `lambda x: 2 * x` 或者 C++ 中的 `[](auto x) { return 2 * x; }`;但对于 Python 中的 `lambda x: x * x` 不能写成 Scala 中的 `_ * _`——因为在 Scala 中,`_ * _` 表示匿名函数接受 2 个参数,函数返回值是两个参数的乘积。又例如,下列 Scala 代码中的 `print(_)` 相当于 `x => print(x)`: + +``` +List(1, 2, 3, 4, 5).foreach(print(_)) +``` + +下列 Scala 代码中的 `_ + _` 相当于 `(x, y) => x + y`: 记住这里是匿名函数里只使用一次情况下才可以,否则就乱了 + +``` +List(1, 2, 3, 4, 5).reduceLeft(_ + _) +``` + + + +**阻止函数意外调用** + +众所周知,Scala 是函数式语言。在 Scala 中,函数是一等公民,和普通变量一样可以赋值。但由于在 Scala 中函数调用时可省略括号,如果你打算将一个函数赋值给一个新的变量,则函数可能会被意外地调用而后将函数的返回值赋值。这种时候,我们需要在函数名之后加上 `_` 来阻止函数调用——类似 TeX 中的 `\relax` 阻止继续执行的作用。 + +``` +class Test { + def foo = { + // some code + } + val bar = foo _ +} +``` + + + + + +# Scala Option和Some + +### Option + +Option[A] (sealed trait) 有两个取值: + + 1. Some[A] 有类型A的值 + + 2. None 没有值 + + + +Option一般有两种用法: + +1. 模式匹配 + +```scala +Option[A] option + +option match { + case Some(a) => a + case None => "?" +} + +``` + + + +2. map + +```scala +option map( o => "?" ).getOrElse("默认值") +``` + + + +### Some + +Some是一个继承了Option的case class。 而None是一个继承了Option[Nothing]的case object。 + +**Some的解释**: Class `Some[A]` represents existing values of type `A`. + +Some[A] some是一定有值的, some.get获取值,如果没有值, 会报异常. Predef.NoSuchElementException if the option is empty. + + + +### 应用例子 + +Option 类型的值通常作为 Scala 集合类型(List, Map 等)操作的返回类型。比如 Map 的 get 方法: + +```scala +scala> val capitals = Map("France"->"Paris", "Japan"->"Tokyo", "China"->"Beijing") +capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing) + +scala> capitals get "France" +res0: Option[String] = Some(Paris) + +scala> capitals get "North Pole" +res1: Option[String] = None +``` + + + +# 参考教程 + +[参考1](https://docs.scala-lang.org/zh-cn/tour/generic-classes.html) diff --git "a/_posts/Tech/SystemDesign/2022-11-26-\350\256\276\350\256\241\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250.md" "b/_posts/Tech/SystemDesign/2022-11-26-\350\256\276\350\256\241\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250.md" new file mode 100644 index 0000000000..c1b0320db6 --- /dev/null +++ "b/_posts/Tech/SystemDesign/2022-11-26-\350\256\276\350\256\241\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250.md" @@ -0,0 +1,429 @@ +--- +layout: post +category: SystemDesign +title: 设计数据密集型应用 +tags: SystemDesign +--- + +# 设计数据密集型应用 + +相关读书笔记。 + +# 第一部分 数据系统的基石 + +现今很多应用程序都是 **数据密集型(data-intensive)** 的,而非 **计算密集型(compute-intensive)** 的。 + +数据密集型应用通常由标准组件构建而成,标准组件提供了很多通用的功能;例如,许多应用程序都需要: + +- 存储数据,以便自己或其他应用程序之后能再次找到 (数据库(database)) + +- 记住开销昂贵操作的结果,加快读取速度(缓存(cache)) +- 允许用户按关键字搜索数据,或以各种方式对数据进行过滤(搜索索引(search indexes)) +- 向其他进程发送消息,进行异步处理(流处理(stream processing)) +- 定期处理累积的大批量数据(批处理(batch processing)) + + + + + +可靠性(Reliability) + +- 系统在困境(adversity)(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。 + + +可扩展性(Scalability) + +- 有合理的办法应对系统的增长(数据量、流量、复杂性) + + +可维护性(Maintainability) + +- 许多不同的人(工程师、运维)在不同的生命周期,都能高效地在系统上工作(使系统保持现有行为,并适应新的应用场景)。 + + + + + + +硬件故障: + +在拥有10000个磁盘的存储集群上,平均每天会有1个磁盘出故障。通常都是增加单个硬件的冗余度。 + + + + 人们经常讨论**纵向扩展(scaling up)**(**垂直扩展(vertical scaling)**,转向更强大的机器)和**横向扩展(scaling out)** (**水平扩展(horizontal scaling)**,将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“**无共享(shared-nothing)**”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向扩展。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。 + + + + 跨多台机器部署**无状态服务(stateless services)**非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向扩展),直到扩展成本或可用性需求迫使其改为分布式。 + + + +新的非关系型“NoSQL”数据存储在两个主要方向上存在分歧: + +1. **文档数据库**的应用场景是:数据通常是自我包含的,而且文档之间的关系非常稀少。 +2. **图形数据库**用于相反的场景:任意事物都可能与任何事物相关联。 + + + +两大类存储引擎:**日志结构(log-structured)** 的存储引擎,以及**面向页面(page-oriented)** 的存储引擎(例如B树)。 + + + +日志一般是直接追加,读记录时读最新最后的一个值,前面的可后续压缩合并。 + + + +因为数据落到磁盘里,基本就两种方法,1. sstable,先内存后追加再不断合并; 2. 类似B树面向页,直接更新。 除此之外如果更新某个文件某一行,可能会导致后面的都变,因为offset变了,所以目前基本就这两种。为啥没hash, 磁盘哈希映射很难表现优秀。它需要大量的随机访问I/O,当它变满时增长是很昂贵的,并且散列冲突需要很多的逻辑 + + + +**SSTable并发控制** + +由于写操作是以严格顺序的顺序附加到日志中的,所以常见的实现选择是只有一个写入器线程。数据文件段是附加的,或者是不可变的,所以它们可以被多个线程同时读取。 + +乍一看,只有追加日志看起来很浪费:为什么不更新文件,用新值覆盖旧值?但是只能追加设计的原因有几个: + +- 追加和分段合并是顺序写入操作,通常比随机写入快得多,尤其是在磁盘旋转硬盘上。在某种程度上,顺序写入在基于闪存的 **固态硬盘(SSD)** 上也是优选的【4】。我们将在第83页的“[比较B-树和LSM-树](https://vonng.gitbooks.io/ddia-cn/content/ch3.html#比较B-树和LSM-树)”中进一步讨论这个问题。 +- 如果段文件是附加的或不可变的,并发和崩溃恢复就简单多了。例如,您不必担心在覆盖值时发生崩溃的情况,而将包含旧值和新值的一部分的文件保留在一起。 +- 合并旧段可以避免数据文件随着时间的推移而分散的问题。 + + + +**B树** + +B树存储是面向页的,文件就是页组成的,磁盘是按页划分的,指针都是页的指针,页位置不会变。 + +通常,页面可以放置在磁盘上的任何位置;没有什么要求附近的键范围页面附近的磁盘上。如果查询需要按照排序顺序扫描大部分关键字范围,那么每个页面的布局可能会非常不方便,因为每个读取的页面都可能需要磁盘查找。因此,许多B树实现尝试布局树,使得叶子页面按顺序出现在磁盘上。但是,随着树的增长,维持这个顺序是很困难的。相比之下,由于LSM树在合并过程中一次又一次地重写存储的大部分,所以它们更容易使顺序键在磁盘上彼此靠近。 + +额外的指针已添加到树中。例如,每个叶子页面可以在左边和右边具有对其兄弟页面的引用,这允许不跳回父页面就能顺序扫描。 + + + +**比较B树和LSM树** + +尽管B树实现通常比LSM树实现更成熟,但LSM树由于其性能特点也非常有趣。根据经验,通常LSM树的写入速度更快,而B树的读取速度更快【23】。 LSM树上的读取通常比较慢,因为它们必须在压缩的不同阶段检查几个不同的数据结构和SSTables。 + + + +B树索引必须至少两次写入每一段数据:一次写入预先写入日志,一次写入树页面本身(也许再次分页)。即使在该页面中只有几个字节发生了变化,也需要一次编写整个页面的开销。 + + + +LSM树可以被压缩得更好,因此经常比B树在磁盘上产生更小的文件。 B树存储引擎会由于分割而留下一些未使用的磁盘空间:当页面被拆分或某行不能放入现有页面时,页面中的某些空间仍未被使用。由于LSM树不是面向页面的,并且定期重写SSTables以去除碎片,所以它们具有较低的存储开销,特别是当使用平坦压缩时 + + + +1. LSM写入快,B读快。 +2. B树写,一次要写log, 一次要更新页,页只能替换。 +3. LSM碎片少,压缩的好,存储开销小。 +4. LSM写入吞吐量大,需要合理配置压缩。 +5. LSM压缩可能会干扰读写。 +6. B树每个键只有一个位置,方便加锁。 + + + +在高层次上,我们看到存储引擎分为两大类:优化 **事务处理(OLTP)** 或 **在线分析(OLAP)** 。这些用例的访问模式之间有很大的区别: + +- OLTP系统通常面向用户,这意味着系统可能会收到大量的请求。为了处理负载,应用程序通常只访问每个查询中的少部分记录。应用程序使用某种键来请求记录,存储引擎使用索引来查找所请求的键的数据。磁盘寻道时间往往是这里的瓶颈。 +- 数据仓库和类似的分析系统会低调一些,因为它们主要由业务分析人员使用,而不是由最终用户使用。它们的查询量要比OLTP系统少得多,但通常每个查询开销高昂,需要在短时间内扫描数百万条记录。磁盘带宽(而不是查找时间)往往是瓶颈,列式存储是这种工作负载越来越流行的解决方案。 + +在OLTP方面,我们能看到两派主流的存储引擎: + +**日志结构学派** + +只允许附加到文件和删除过时的文件,但不会更新已经写入的文件。 Bitcask,SSTables,LSM树,LevelDB,Cassandra,HBase,Lucene等都属于这个类别。 + +**就地更新学派** + +将磁盘视为一组可以覆写的固定大小的页面。 B树是这种哲学的典范,用在所有主要的关系数据库中和许多非关系型数据库。 + +日志结构的存储引擎是相对较新的发展。他们的主要想法是,他们系统地将随机访问写入顺序写入磁盘,由于硬盘驱动器和固态硬盘的性能特点,可以实现更高的写入吞吐量。在完成OLTP方面,我们通过一些更复杂的索引结构和为保留所有数据而优化的数据库做了一个简短的介绍。 + +然后,我们从存储引擎的内部绕开,看看典型数据仓库的高级架构。这一背景说明了为什么分析工作负载与OLTP差别很大:当您的查询需要在大量行中顺序扫描时,索引的相关性就会降低很多。相反,非常紧凑地编码数据变得非常重要,以最大限度地减少查询需要从磁盘读取的数据量。我们讨论了列式存储如何帮助实现这一目标。 + + + +***向后兼容 (backward compatibility)\*** + + 新代码可以读旧数据。 + +***向前兼容 (forward compatibility)\*** + + 旧代码可以读新数据。 + + + +服务器本身可以是另一个服务的客户端(例如,典型的Web应用服务器充当数据库的客户端)。这种方法通常用于将大型应用程序按照功能区域分解为较小的服务,这样当一个服务需要来自另一个服务的某些功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为 **面向服务的体系结构(service-oriented architecture,SOA)** ,最近被改进和更名为 **微服务架构** + + + +REST不是一个协议,而是一个基于HTTP原则的设计哲学【34,35】。它强调简单的数据格式,使用URL来标识资源,并使用HTTP功能进行缓存控制,身份验证和内容类型协商 + + + +# 第二部分 分布式数据 + +## 复制 + +三种流行的变更复制算法:**单领导者(single leader)**,**多领导者(multi leader)**和**无领导者(leaderless)**。几乎所有分布式数据库都使用这三种方法之一。 + + + +对于异步复制系统而言,主库故障时有可能丢失数据。这可能是一个严重的问题,因此研究人员仍在研究不丢数据但仍能提供良好性能和可用性的复制方法。 例如,**链式复制**【8,9】]是同步复制的一种变体,已经在一些系统(如Microsoft Azure存储【10,11】)中成功实现。 + +### 同步复制与异步复制 + + 复制系统的一个重要细节是:复制是**同步(synchronously)**发生还是**异步(asynchronously)**发生。 (在关系型数据库中这通常是一个配置项,其他系统通常硬编码为其中一个)。 + +同步复制的优点是,从库保证有与主库一致的最新数据副本。如果主库突然失效,我们可以确信这些数据仍然能在从库上上找到。缺点是,如果同步从库没有响应(比如它已经崩溃,或者出现网络故障,或其它任何原因),主库就无法处理写入操作。主库必须阻止所有写入,并等待同步副本再次可用。 + +通常情况下,基于领导者的复制都配置为完全异步。 在这种情况下,如果主库失效且不可恢复,则任何尚未复制给从库的写入都会丢失。 这意味着即使已经向客户端确认成功,写入也不能保证 **持久(Durable)** 。 然而,一个完全异步的配置也有优点:即使所有的从库都落后了,主库也可以继续处理写入。 + + + +### 复制日志的实现 + +基于主库的复制底层是如何工作的?实践中有好几种不同的复制方式 + +1. 基于语句的复制,比如原始的sql。 + +2. 物理日志, WAL 非常底层:包含哪些磁盘块中的哪些字节发生了更改。这使复制与存储引擎紧密耦合 +3. 逻辑日志复制(基于行)复制改动的行的数据,删除传id, insert传所有,update传更新内容 +4. 基于触发器 + + + +### 处理节点宕机 + +#### 从库失效:追赶恢复 + + 在其本地磁盘上,每个从库记录从主库收到的数据变更。如果从库崩溃并重新启动,或者,如果主库和从库之间的网络暂时中断,则比较容易恢复:从库可以从日志中知道,在发生故障之前处理的最后一个事务。因此,从库可以连接到主库,并请求在从库断开连接时发生的所有数据变更。 + +#### 主库失效:故障切换 + + 主库失效处理起来相当棘手:其中一个从库需要被提升为新的主库,需要重新配置客户端,以将它们的写操作发送给新的主库,其他从库需要开始拉取来自新主库的数据变更。这个过程被称为**故障切换** + + + +问题 + +1. 异步复制可能会丢老主库最后写入 +2. 主键重用什么的很麻烦 +3. 老主库恢复可能有两个主 ,要强行杀死老的 + + + +### 复制延迟问题 + +1. 读己之写: 让用户提交一些数据,然后查看他们提交的内容。 写后立即读 +2. 单调读: 从异步从库读取第二个异常例子是,用户可能会遇到 **时光倒流(moving backward in time)**。如果用户从不同从库进行多次读取,就可能发生这种情况。同步延迟太大造成的。 +3. 一致前缀读(consistent prefix reads):如果一系列写入按某个顺序发生,那么任何人读取这些写入时,也会看见它们以同样的顺序出现。 + +### 多主复制的应用场景 + + 在单个数据中心内部使用多个主库很少是有意义的,因为好处很少超过复杂性的代价。 但在一些情况下,多活配置是也合理的。 + +1. 运维多个数据中心 +2. 需要离线操作的客户端 +3. 协同编辑 + +#### 自定义冲突解决逻辑 + +1. **写时执行** +2. **读时执行** 读修复 + + + +自动解决由于数据修改引起的冲突。有几行研究值得一提: + +- **无冲突复制数据类型(Conflict-free replicated datatypes)**(CRDT)【32,38】是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现【39,40】。 +- **可合并的持久数据结构(Mergeable persistent data structures)**【41】显式跟踪历史记录,类似于Git版本控制系统,并使用三向合并功能(而CRDT使用双向合并)。 +- **可执行的转换(operational transformation)**[42]是Etherpad 【30】和Google Docs 【31】等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。 + +### 无主复制 + +参考本人另一个博客。 尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单。 Dynamo风格的数据库通常针对可以忍受最终一致性的用例进行优化。允许通过参数w和r来调整读取陈旧值的概率,但把它们当成绝对的保证是不明智的。 + +## 分区 + + 分区主要是为了**可扩展性**。不同的分区可以放在不共享集群中的不同节点上 + +因此,大数据集可以分布在多个磁盘上,并且查询负载可以分布在多个处理器上。 + + 对于在单个分区上运行的查询,每个节点可以独立执行对自己的查询,因此可以通过添加更多的节点来扩大查询吞吐量。大型,复杂的查询可能会跨越多个节点并行处理,尽管这也带来了新的困难。 + +大多数情况下,分区方案的选择与复制方案的选择是独立的 + + + +Key Range分区的缺点是某些特定的访问模式会导致热点。 由于偏斜和热点的风险,许多分布式数据存储使用散列函数来确定给定键的分区。 + + + + 出于分区的目的,散列函数不需要多么强壮的加密算法:例如,Cassandra和MongoDB使用MD5,Voldemort使用Fowler-Noll-Vo函数。许多编程语言都有内置的简单哈希函数(它们用于哈希表),但是它们可能不适合分区:例如,在Java的`Object.hashCode()`和Ruby的`Object#hash`,同一个键可能在不同的进程中有不同的哈希值 + + + + + + 如今,大多数数据系统无法自动补偿这种高度偏斜的负载,因此应用程序有责任减少偏斜。例如,如果一个主键被认为是非常火爆的,一个简单的方法是在主键的开始或结尾添加一个随机数。只要一个两位数的十进制随机数就可以将主键分散为100种不同的主键,从而存储在不同的分区中。 + + 然而,将主键进行分割之后,任何读取都必须要做额外的工作,因为他们必须从所有100个主键分布中读取数据并将其合并。此技术还需要额外的记录:只需要对少量热点附加随机数;对于写入吞吐量低的绝大多数主键来是不必要的开销。因此,您还需要一些方法来跟踪哪些键需要被分割。 + + + +### 分区和二级索引之间的相互作用 + +#### 同分区上的本地索引 + +**文档分区索引**也被称为**本地索引(local index)**。 在这种索引方法中,每个分区是完全独立的:每个分区维护自己的二级索引,仅覆盖该分区中的文档。它不关心存储在其他分区的数据。无论何时您需要写入数据库(添加,删除或更新文档),只需处理包含您正在编写的文档ID的分区即可。 + +这种查询某个调节的id可能需要将查询发送到所有分区,并合并所有返回的结果。 这种查询分区数据库的方法有时被称为**分散/聚集(scatter/gather)**,并且可能会使二级索引上的读取查询相当昂贵。即使并行查询分区,分散/聚集也容易导致尾部延迟放大(参阅“[实践中的百分位点](https://vonng.gitbooks.io/ddia-cn/content/ch1.html#实践中的百分位点)”)。然而,它被广泛使用:MongoDB,Riak 【15】,Cassandra 【16】,Elasticsearch 【17】,SolrCloud 【18】和VoltDB 【19】都使用文档分区二级索引。 + + + +#### 覆盖所有分区数据的**全局索引** + +**全局索引** 我们可以构建一个覆盖所有分区数据的**全局索引**,而不是给每个分区创建自己的次级索引(本地索引)。但是,我们不能只把这个索引存储在一个节点上,因为它可能会成为瓶颈,违背了分区的目的。全局索引也必须进行分区,但可以采用与主键不同的分区方式。 我们将这种索引称为**关键词分区(term-partitioned)**,因为我们寻找的关键词决定了索引的分区方式。例如,一个关键词可能是:`颜色:红色`。**关键词(Term)** 来源于来自全文搜索索引(一种特殊的次级索引),指文档中出现的所有单词。 + +关键词分区的全局索引优于文档分区索引的地方点是它可以使读取更有效率:不需要**分散/收集**所有分区,客户端只需要向包含关键词的分区发出请求。全局索引的缺点在于写入速度较慢且较为复杂,因为写入单个文档现在可能会影响索引的多个分区(文档中的每个关键词可能位于不同的分区或者不同的节点上) 。 + + 在实践中,对全局二级索引的更新通常是**异步**的(也就是说,如果在写入之后不久读取索引,刚才所做的更改可能尚未反映在索引中)。例如,Amazon DynamoDB声称在正常情况下,其全局次级索引会在不到一秒的时间内更新,但在基础架构出现故障的情况下可能会有延迟【20】。 + + + +## 分区再平衡 + +随着时间的推移,数据库会有各种变化。 + +- 查询吞吐量增加,所以您想要添加更多的CPU来处理负载。 +- 数据集大小增加,所以您想添加更多的磁盘和RAM来存储它。 +- 机器出现故障,其他机器需要接管故障机器的责任。 + +所有这些更改都需要数据和请求从一个节点移动到另一个节点。 将负载从集群中的一个节点向另一个节点移动的过程称为**再平衡(reblancing)**。 + +#### 反面教材:hash mod N + + 模$N$方法的问题是,如果节点数量N发生变化,大多数密钥将需要从一个节点移动到另一个节点。例如,假设$hash(key)=123456$。如果最初有10个节点,那么这个键一开始放在节点6上 + + + +#### 键值 固定数量的分区 hash slot + +类似hash slot. + +创建比节点更多的分区,并为每个节点分配多个分区。 + +如果一个节点被添加到集群中,新节点可以从当前每个节点中**窃取**一些分区,直到分区再次公平分配。如果从集群中删除一个节点,则会发生相反的情况。 + + 原则上,您甚至可以解决集群中的硬件不匹配问题:通过为更强大的节点分配更多的分区,可以强制这些节点承载更多的负载。在Riak 【15】,Elasticsearch 【24】,Couchbase 【10】和Voldemort 【25】中使用了这种再平衡的方法。 + + + +#### 范围,动态分区 Hbase + +按键范围分区。一个节点对应多个分区,当分区大了就拆分,当小了就和相邻分区合并, 类似B树。 + + + +大型分区拆分后,可以将其中的一半转移到另一个节点,以平衡负载。在HBase中,分区文件的传输通过HDFS(底层分布式文件系统)来实现【3】。**可以是不复制数据而直接挂载存储的** + + 动态分区的一个优点是分区数量适应总数据量。如果只有少量的数据,少量的分区就足够了,所以开销很小;如果有大量的数据,每个分区的大小被限制在一个可配置的最大值 + + 动态分区不仅适用于数据的范围分区,而且也适用于散列分区。从版本2.4开始,MongoDB同时支持范围和哈希分区,并且都是进行动态分割分区。 + + + +#### 按节点比例分区 不常用 + +每个节点有固定的分区数量,当新节点加入,随机选择某个分区拆一半给新节点。 + + + + + +## 请求路由 服务发现 + +当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。 + + 这个问题可以概括为 **服务发现(service discovery)** ,它不仅限于数据库。任何可通过网络访问的软件都有这个问题,特别是如果它的目标是高可用性(在多台机器上运行冗余配置)。 + + + +概括来说,这个问题有几种不同的方案(如图6-7所示): + +1. **请求任意节点做协调。**允许客户联系任何节点(例如,通过**循环策略的负载均衡(Round-Robin Load Balancer)**)。如果该节点恰巧拥有请求的分区,则它可以直接处理该请求;否则,它将请求转发到适当的节点,接收回复并传递给客户端。 +2. **加个路由层做负载均衡。** 首先将所有来自客户端的请求发送到路由层,它决定了应该处理请求的节点,并相应地转发。此路由层本身不处理任何请求;它仅负责分区的负载均衡。 +3. **把路由信息放到客户端,客户端订阅。**要求客户端知道分区和节点的分配。在这种情况下,客户端可以直接连接到适当的节点,而不需要任何中介。 + + + + 许多分布式数据系统都依赖于一个独立的协调服务,比如ZooKeeper来跟踪集群元数据。 每个节点在ZooKeeper中注册自己,ZooKeeper维护分区到节点的可靠映射。 其他参与者(如路由层或分区感知客户端)可以在ZooKeeper中订阅此信息。 只要分区分配发生的改变,或者集群中添加或删除了一个节点,ZooKeeper就会通知路由层使路由信息保持最新状态。 + + + + 例如,LinkedIn的Espresso使用Helix 【31】进行集群管理(依靠ZooKeeper),实现了如[图6-8](https://vonng.gitbooks.io/ddia-cn/content/img/fig6-8.png)所示的路由层。 HBase,SolrCloud和Kafka也使用ZooKeeper来跟踪分区分配。 MongoDB具有类似的体系结构,但它依赖于自己的**配置服务器(config server)** 实现和mongos守护进程作为路由层。 + + Cassandra和Riak采取不同的方法:他们在节点之间使用**流言协议(gossip protocol)** 来传播群集状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点([图6-7](https://vonng.gitbooks.io/ddia-cn/content/ch6.html)中的方法1)。这个模型在数据库节点中增加了更多的复杂性,但是避免了对像ZooKeeper这样的外部协调服务的依赖。 + + Couchbase不会自动重新平衡,这简化了设计。通常情况下,它配置了一个名为moxi的路由层,它会从集群节点了解路由变化【32】。 + + **当使用路由层或向随机节点发送请求时,客户端仍然需要找到要连接的IP地址。这些地址并不像分区的节点分布变化的那么快,所以使用DNS通常就足够了。** + + + +## 分布式系统的麻烦 + + + + 单调钟适用于测量持续时间(时间间隔),例如超时或服务的响应时间:Linux上的`clock_gettime(CLOCK_MONOTONIC)`,和Java中的`System.nanoTime()`都是单调时钟。这个名字来源于他们保证总是前进的事实(而时钟可以及时跳回)。 + + 你可以在某个时间点检查单调钟的值,做一些事情,且稍后再次检查它。这两个值之间的差异告诉你两次检查之间经过了多长时间。但单调钟的绝对值是毫无意义的:它可能是计算机启动以来的纳秒数,或类似的任意值。特别是比较来自两台不同计算机的单调钟的值是没有意义的,因为它们并不是一回事。 + +## 一致性与共识 + +强一致性也叫线性一致性。 + +### dynamoDB不能强一致性 + +因为是无主复制系统,可能写时只有一个节点写入成功了,然后读的节点正好没有它,造成读不到。 + +除非所有节点都写,读所有节点,才可以强一致性。 + + + +### 一致性 + +强一致性和性能、可用性往往冲突。 + + + +#### 时代编号 + +> 用于区分谁是新领导,谁是旧的,新领导说的算。投票赞成的一个有效依据就是没有其他更高时代编号 + +**时代编号(epoch number)**(在Paxos中称为**投票编号(ballot number)**,视图戳复制中的**视图编号(view number)**,以及Raft中的**任期号码(term number)**),并确保在每个时代中,领导者都是唯一的。 + + + + 每次当现任领导被认为挂掉的时候,节点间就会开始一场投票,以选出一个新领导。这次选举被赋予一个递增的时代编号,因此时代编号是全序且单调递增的。如果两个不同的时代的领导者之间出现冲突(也许是因为前任领导者实际上并未死亡),那么带有更高时代编号的领导说了算。 + + + +在任何领导者被允许决定任何事情之前,必须先检查是否存在其他带有更高时代编号的领导者,它们可能会做出相互冲突的决定。 + + + +它必须从**法定人数(quorum)**的节点中获取选票(参阅“[读写的法定人数](https://vonng.gitbooks.io/ddia-cn/content/ch5.html#读写的法定人数)”)。对领导者想要做出的每一个决定,都必须将提议值发送给其他节点,并等待法定人数的节点响应并赞成提案。法定人数通常(但不总是)由多数节点组成【105】。只有在没有意识到任何带有更高时代编号的领导者的情况下,一个节点才会投票赞成提议。 + + + +因此,我们有两轮投票:第一次是为了选出一位领导者,第二次是对领导者的提议进行表决。关键在于,这两次投票的**法定人群**必须相互**重叠(overlap)** 如果一个提案的表决通过,则至少得有一个参与投票的节点也必须参加过最近的领导者选举【105】。因此,如果在一个提案的表决过程中没有出现更高的时代编号。那么现任领导者就可以得出这样的结论:没有发生过更高时代的领导选举,因此可以确定自己仍然在领导。然后它就可以安全地对提议值做出决定。 + + + +# 参考 + +https://vonng.gitbooks.io/ddia-cn/content/part-i.html + diff --git "a/_posts/Tech/TOOLS/2017-12-07-matlab\345\277\253\346\215\267\351\224\256.md" "b/_posts/Tech/TOOLS/2017-12-07-matlab\345\277\253\346\215\267\351\224\256.md" index d4ea689f3b..8328587eb7 100644 --- "a/_posts/Tech/TOOLS/2017-12-07-matlab\345\277\253\346\215\267\351\224\256.md" +++ "b/_posts/Tech/TOOLS/2017-12-07-matlab\345\277\253\346\215\267\351\224\256.md" @@ -1,9 +1,10 @@ --- layout: post -category: Tools +category: Tools title: matlab快捷键 tags: Tools --- + 最大化及还原 ctrl+shift+m @@ -18,67 +19,62 @@ tags: Tools 设置标签 - ctrl+F2 f2和shift+f2跳转 - -快捷键ctrl+pageup和ctrl+pagedown之间可以实现在editor中所打开文件之间的切换 + ctrl+F2 f2和shift+f2跳转 -F1帮助 +快捷键 ctrl+pageup 和 ctrl+pagedown 之间可以实现在 editor 中所打开文件之间的切换 -F2改名 +F1 帮助 -F3搜索 +F2 改名 -F4地址 +F3 搜索 -F5刷新 +F4 地址 -F6切换 +F5 刷新 -F10菜单 +F6 切换 -CTRL+A全选 +F10 菜单 -CTRL+C复制 +CTRL+A 全选 -CTRL+X剪切 +CTRL+C 复制 -CTRL+V粘贴 +CTRL+X 剪切 -CTRL+Z撤消 +CTRL+V 粘贴 -CTRL+O打开 +CTRL+Z 撤消 +CTRL+O 打开 -SHIFT+DELETE永久删除 +SHIFT+DELETE 永久删除 -DELETE删除 +DELETE 删除 -ALT+ENTER属性 +ALT+ENTER 属性 -ALT+F4关闭 +ALT+F4 关闭 -CTRL+F4关闭 +CTRL+F4 关闭 -ALT+TAB切换 - -ALT+ESC切换 +ALT+TAB 切换 +ALT+ESC 切换 ALT+空格键窗口菜单 -CTRL+ESC开始菜单 - +CTRL+ESC 开始菜单 -拖动某一项时按CTRL复制所选项目 +拖动某一项时按 CTRL 复制所选项目 +拖动某一项时按 CTRL+SHIFT 创建快捷方式 -拖动某一项时按CTRL+SHIFT创建快捷方式 - - -将光盘插入到CD-ROM驱动器时按SHIFT键阻止光盘自动播放 +将光盘插入到 CD-ROM 驱动器时按 SHIFT 键阻止光盘自动播放 Ctrl+1,2,3... -切换到从左边数起第1,2,3...个标签 +切换到从左边数起第 1,2,3...个标签 Ctrl+A 全部选中当前页面内容 @@ -86,7 +82,6 @@ Ctrl+A Ctrl+C 复制当前选中内容 - Ctrl+D 打开“添加收藏”面版(把当前页面添加到收藏夹中) @@ -96,57 +91,48 @@ Ctrl+E Ctrl+F 打开“查找”面版 - Ctrl+G 打开或关闭“简易收集”面板 Ctrl+H 打开“历史”侧边栏 - Ctrl+I 打开“收藏夹”侧边栏/另:将所有垂直平铺或水平平铺或层叠的窗口恢复 Ctrl+K 关闭除当前和锁定标签外的所有标签 - Ctrl+L -打开“打开”面版(可以在当前页面打开Iternet地址或其他文件...) +打开“打开”面版(可以在当前页面打开 Iternet 地址或其他文件...) Ctrl+N -新建一个空白窗口(可更改,Maxthon选项→标签→新建) - +新建一个空白窗口(可更改,Maxthon 选项 → 标签 → 新建) Ctrl+O -打开“打开”面版(可以在当前页面打开Iternet地址或其他文件...) +打开“打开”面版(可以在当前页面打开 Iternet 地址或其他文件...) Ctrl+P 打开“打印”面板(可以打印网页,图片什么的...) - Ctrl+Q 打开“添加到过滤列表”面板(将当前页面地址发送到过滤列表) Ctrl+R 刷新当前页面 - Ctrl+S 打开“保存网页”面板(可以将当前页面所有内容保存下来) Ctrl+T 垂直平铺所有窗口 - Ctrl+V 粘贴当前剪贴板内的内容 - Ctrl+W 关闭当前标签(窗口) - Ctrl+X 剪切当前选中内容(一般只用于文本操作) @@ -162,45 +148,38 @@ Ctrl+F4 Ctrl+F5 刷新当前页面 - Ctrl+F6 按页面打开的先后时间顺序向前切换标签(窗口) Ctrl+F11 隐藏或显示菜单栏 - Ctrl+Tab 以小菜单方式向下切换标签(窗口) - Ctrl+Enter 域名自动完成[url=]http://www.**.com[/url](内容可更改,Maxthon选项→地址栏→常规)/另:当输入焦点在搜索栏中时,为高亮关键字 - Ctrl+拖曳 -保存该链接的地址或已选中的文本或指定的图片到一个文件夹中(保存目录可更改,Maxthon选项→保存) - +保存该链接的地址或已选中的文本或指定的图片到一个文件夹中(保存目录可更改,Maxthon 选项 → 保存) Ctrl+小键盘'+' -当前页面放大20% +当前页面放大 20% Ctrl+小键盘'-' -当前页面缩小20% - +当前页面缩小 20% -Ctrl+小键盘'*' +Ctrl+小键盘'\*' 恢复当前页面的缩放为原始大小 - Ctrl+Alt+S -自动保存当前页面所有内容到指定文件夹(保存路径可更改,Maxthon选项→保存) +自动保存当前页面所有内容到指定文件夹(保存路径可更改,Maxthon 选项 → 保存) Ctrl+Shift+小键盘'+' -所有页面放大20% +所有页面放大 20% Ctrl+Shift+小键盘'-' -所有页面缩小20% +所有页面缩小 20% Ctrl+Shift+F 输入焦点移到搜索栏 @@ -208,23 +187,18 @@ Ctrl+Shift+F Ctrl+Shift+G 关闭“简易收集”面板 - Ctrl+Shift+H 打开并激活到你设置的主页 - Ctrl+Shift+N -在新窗口中打开剪贴板中的地址,如果剪贴板中为文字,则调用搜索引擎搜索该文字(搜索引擎可选择,Maxthon选项→搜索) - +在新窗口中打开剪贴板中的地址,如果剪贴板中为文字,则调用搜索引擎搜索该文字(搜索引擎可选择,Maxthon 选项 → 搜索) Ctrl+Shift+S -打开“保存网页”面板(可以将当前页面所有内容保存下来,等同于Ctrl+S) - +打开“保存网页”面板(可以将当前页面所有内容保存下来,等同于 Ctrl+S) Ctrl+Shift+W 关闭除锁定标签外的全部标签(窗口) - Ctrl+Shift+F6 按页面打开的先后时间顺序向后切换标签(窗口) @@ -245,19 +219,15 @@ Alt+A 资源管理器 +END 显示当前窗口的底端 -END显示当前窗口的底端 - -HOME显示当前窗口的顶端 - +HOME 显示当前窗口的顶端 NUMLOCK+数字键盘的减号(-)折叠所选的文件夹 - NUMLOCK+数字键盘的加号(+)显示所选文件夹的内容 - -NUMLOCK+数字键盘的星号(*)显示所选文件夹的所有子文件夹 +NUMLOCK+数字键盘的星号(\*)显示所选文件夹的所有子文件夹 向左键当前所选项处于展开状态时折叠该项,或选定其父文件夹 diff --git "a/_posts/Tech/TOOLS/2017-12-22-win10\345\277\253\346\215\267\351\224\256.md" "b/_posts/Tech/TOOLS/2017-12-22-win10\345\277\253\346\215\267\351\224\256.md" index 6a9c0d580f..f920ad24d1 100644 --- "a/_posts/Tech/TOOLS/2017-12-22-win10\345\277\253\346\215\267\351\224\256.md" +++ "b/_posts/Tech/TOOLS/2017-12-22-win10\345\277\253\346\215\267\351\224\256.md" @@ -1,17 +1,19 @@ --- layout: post -category: Tools +category: Tools title: win10快捷键 tags: Tools --- -## win10快捷键 + +## win10 快捷键 + win+shift+s 划矩形截图!!! Win + I:打开设置应用; Win + M:最小化所有窗口 -Win + S:打开Cortana +Win + S:打开 Cortana Win + T:将焦点切换到任务栏图标,并在图标之间进行切换 @@ -21,6 +23,6 @@ Win + 数字键:打开或切换到任务栏上对应位置的应用或程序 Win + Pause:打开系统属性 -Win + Home:相当于拖动活动窗口摇晃,即Aero Shake,除了当前窗口,将其他窗口最小化 +Win + Home:相当于拖动活动窗口摇晃,即 Aero Shake,除了当前窗口,将其他窗口最小化 -Win + W:打开Windows Ink \ No newline at end of file +Win + W:打开 Windows Ink diff --git "a/_posts/Tech/TOOLS/2018-01-15-word\345\205\254\345\274\217\350\276\223\345\205\245.md" "b/_posts/Tech/TOOLS/2018-01-15-word\345\205\254\345\274\217\350\276\223\345\205\245.md" deleted file mode 100644 index 28e84c1d2d..0000000000 --- "a/_posts/Tech/TOOLS/2018-01-15-word\345\205\254\345\274\217\350\276\223\345\205\245.md" +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: post -category: Tools -title: word数学符号输入 -tags: Tools ---- diff --git a/_posts/Tech/TOOLS/2018-03-17-markdown.md b/_posts/Tech/TOOLS/2018-03-17-markdown.md deleted file mode 100644 index 75c14c432a..0000000000 --- a/_posts/Tech/TOOLS/2018-03-17-markdown.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -category: Tools -title: markdown安装 -tags: Tools ---- - -## 邮箱 -Soar360@live.com - -## 密钥 -GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGImDHzWdD6xhMNLGVpbP2M5SN6bnxn2kSE8qHqNY5QaaRxmO3YSMHxlv2EYpjdwLcPwfeTG7kUdnhKE0vVy4RidP6Y2wZ0q74f47fzsZo45JE2hfQBFi2O9Jldjp1mW8HUpTtLA2a5/sQytXJUQl/QKO0jUQY4pa5CCx20sV1ClOTZtAGngSOJtIOFXK599sBr5aIEFyH0K7H4BoNMiiDMnxt1rD8Vb/ikJdhGMMQr0R4B+L3nWU97eaVPTRKfWGDE8/eAgKzpGwrQQoDh+nzX1xoVQ8NAuH+s4UcSeQ== \ No newline at end of file diff --git "a/_posts/Tech/TOOLS/2018-03-24-excel\346\212\200\345\267\247.md" "b/_posts/Tech/TOOLS/2018-03-24-excel\346\212\200\345\267\247.md" index 854817679e..3db3f22dd1 100644 --- "a/_posts/Tech/TOOLS/2018-03-24-excel\346\212\200\345\267\247.md" +++ "b/_posts/Tech/TOOLS/2018-03-24-excel\346\212\200\345\267\247.md" @@ -1,26 +1,29 @@ --- layout: post -category: Tools +category: Tools title: excel技巧 tags: Tools --- -## vlookup函数使用 +## vlookup 函数使用 ### 用处 + 用来根据某些信息匹配自动输入,免得自己查找 ### 使用方法 -在要输入信息的框中输入=vlook,然后tab会输入=vlookup(,此时按ctrl+a,弹出输入参数框,第一个参数选择匹配字段,一个就行, -第二个参数选择数据来源,自己画方框就行,其中这个方框的首列是匹配字段列,然后按F4,让每个参数前面都有个$,改成绝对地址, -第三个参数输入要输入的字段在以匹配字段为首列的第二个参数选择框中的第几列,比如是2,第一列就是匹配字段,第二列就是查找字段 -第三个参数输入0就好了 + +在要输入信息的框中输入=vlook,然后 tab 会输入=vlookup(,此时按 ctrl+a,弹出输入参数框,第一个参数选择匹配字段,一个就行, +第二个参数选择数据来源,自己画方框就行,其中这个方框的首列是匹配字段列,然后按 F4,让每个参数前面都有个$,改成绝对地址, +第三个参数输入要输入的字段在以匹配字段为首列的第二个参数选择框中的第几列,比如是 2,第一列就是匹配字段,第二列就是查找字段 +第三个参数输入 0 就好了 这是一个框的,下面的框,直接双击的+下拉就好了 [参考视频](http://haokan.baidu.com/v?pd=wisenatural&vid=8614654180070298635) ## 除法 + 输入=,选择参数框,输入符号,回车,双击+下来,挺好用的 -除法就是输入=。。。。。然后下来,下面的会智能的选择同一行 \ No newline at end of file +除法就是输入=。。。。。然后下来,下面的会智能的选择同一行 diff --git "a/_posts/Tech/TOOLS/2018-07-01-git\347\232\204\351\205\215\347\275\256.md" "b/_posts/Tech/TOOLS/2018-07-01-git\347\232\204\351\205\215\347\275\256.md" index 057eb66834..97edc91dfe 100644 --- "a/_posts/Tech/TOOLS/2018-07-01-git\347\232\204\351\205\215\347\275\256.md" +++ "b/_posts/Tech/TOOLS/2018-07-01-git\347\232\204\351\205\215\347\275\256.md" @@ -6,17 +6,19 @@ tags: Tools --- ## 本地配置 + ``` git config --global user.email "846897373@qq.com" (该邮箱地址必须和github上邮箱一样) -git config --global user.name "mafulong" +git config --global user.name "mafulong" (名字和github上的用户名一样) ``` 查看命令: + ``` git config -global user.name git config -global user.email @@ -25,29 +27,35 @@ git config -global user.email ## 远程配置 ### 生成密钥 + ``` ssh-keygen -t rsa -C "846897373@qq.com" ``` ### 提交密钥 -文件,win10的在用户目录下的.ssh下 + +文件,win10 的在用户目录下的.ssh 下 + ``` id_rsa.pub ``` -使用命令(win10及linux公用) +使用命令(win10 及 linux 公用) + ``` -cat ~/.ssh/id_rsa.pub +cat ~/.ssh/id_rsa.pub ``` -### 检验是否链接上github +### 检验是否链接上 github + ``` ssh git@github.com ``` result: + ``` PTY allocation request failed on channel 0 Hi mafulong! You've successfully authenticated, but GitHub does not provide shell access. Connection to github.com closed. -``` \ No newline at end of file +``` diff --git "a/_posts/Tech/TOOLS/2018-07-22-\344\273\243\347\240\201\345\217\257\350\257\273\346\200\247.md" "b/_posts/Tech/TOOLS/2018-07-22-\344\273\243\347\240\201\345\217\257\350\257\273\346\200\247.md" index 6d9be9c817..7f16cf446c 100644 --- "a/_posts/Tech/TOOLS/2018-07-22-\344\273\243\347\240\201\345\217\257\350\257\273\346\200\247.md" +++ "b/_posts/Tech/TOOLS/2018-07-22-\344\273\243\347\240\201\345\217\257\350\257\273\346\200\247.md" @@ -6,22 +6,22 @@ tags: Tools --- -* [一、可读性的重要性](#一可读性的重要性) -* [二、用名字表达代码含义](#二用名字表达代码含义) -* [三、名字不能带来歧义](#三名字不能带来歧义) -* [四、良好的代码风格](#四良好的代码风格) -* [五、为何编写注释](#五为何编写注释) -* [六、如何编写注释](#六如何编写注释) -* [七、提高控制流的可读性](#七提高控制流的可读性) -* [八、拆分长表达式](#八拆分长表达式) -* [九、变量与可读性](#九变量与可读性) -* [十、抽取函数](#十抽取函数) -* [十一、一次只做一件事](#十一一次只做一件事) -* [十二、用自然语言表述代码](#十二用自然语言表述代码) -* [十三、减少代码量](#十三减少代码量) -* [参考资料](#参考资料) - +- [一、可读性的重要性](#一可读性的重要性) +- [二、用名字表达代码含义](#二用名字表达代码含义) +- [三、名字不能带来歧义](#三名字不能带来歧义) +- [四、良好的代码风格](#四良好的代码风格) +- [五、为何编写注释](#五为何编写注释) +- [六、如何编写注释](#六如何编写注释) +- [七、提高控制流的可读性](#七提高控制流的可读性) +- [八、拆分长表达式](#八拆分长表达式) +- [九、变量与可读性](#九变量与可读性) +- [十、抽取函数](#十抽取函数) +- [十一、一次只做一件事](#十一一次只做一件事) +- [十二、用自然语言表述代码](#十二用自然语言表述代码) +- [十三、减少代码量](#十三减少代码量) +- [参考资料](#参考资料) + ## 一、可读性的重要性 @@ -35,12 +35,12 @@ tags: Tools 一些比较有表达力的单词: -| 单词 | 可替代单词 | -| :---: | --- | -| send | deliver、dispatch、announce、distribute、route | -| find | search、extract、locate、recover | -| start| launch、create、begin、open| -| make | create、set up、build、generate、compose、add、new | +| 单词 | 可替代单词 | +| :---: | -------------------------------------------------- | +| send | deliver、dispatch、announce、distribute、route | +| find | search、extract、locate、recover | +| start | launch、create、begin、open | +| make | create、set up、build、generate、compose、add、new | 使用 i、j、k 作为循环迭代器的名字过于简单,user_i、member_i 这种名字会更有表达力。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高。 @@ -56,7 +56,6 @@ tags: Tools - 用 first、last 表示访问空间的包含范围; - begin、end 表示访问空间的排除范围,即 end 不包含尾部。 - ## 四、良好的代码风格 适当的空行和缩进。 @@ -83,12 +82,12 @@ int c = 111; // 注释 用 TODO 等做标记: -| 标记 | 用法 | -|---|---| -|TODO| 待做 | -|FIXME| 待修复 | -|HACK| 粗糙的解决方案 | -|XXX| 危险!这里有重要的问题 | +| 标记 | 用法 | +| ----- | ---------------------- | +| TODO | 待做 | +| FIXME | 待修复 | +| HACK | 粗糙的解决方案 | +| XXX | 危险!这里有重要的问题 | ## 六、如何编写注释 @@ -142,6 +141,7 @@ do / while 的条件放在后面,不够简单明了,并且会有一些迷惑 if line.split(':')[0].strip() == "root": ... ``` + ```python username = line.split(':')[0].strip() if username == "root": @@ -155,6 +155,7 @@ if (!a && !b) { ... } ``` + ```java if (!(a || b)) { ... @@ -191,59 +192,59 @@ JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函 ```js submitted = false; -var submit_form = function(form_name) { - if (submitted) { - return; - } - submitted = true; +var submit_form = function (form_name) { + if (submitted) { + return; + } + submitted = true; }; ``` ```js -var submit_form = (function() { - var submitted = false; - return function(form_name) { - if(submitted) { - return; - } - submitted = true; +var submit_form = (function () { + var submitted = false; + return function (form_name) { + if (submitted) { + return; } -}()); // () 使得外层匿名函数立即执行 + submitted = true; + }; +})(); // () 使得外层匿名函数立即执行 ``` JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。 变量定义的位置应当离它使用的位置最近。 -**实例解析** +**实例解析** 在一个网页中有以下文本输入字段: ```html - - - - + + + + ``` 现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下: ```js -var setFirstEmptyInput = function(new_alue) { - var found = false; - var i = 1; - var elem = document.getElementById('input' + i); - while (elem != null) { - if (elem.value === '') { - found = true; - break; - } - i++; - elem = document.getElementById('input' + i); +var setFirstEmptyInput = function (new_alue) { + var found = false; + var i = 1; + var elem = document.getElementById("input" + i); + while (elem != null) { + if (elem.value === "") { + found = true; + break; } - if (found) elem.value = new_value; - return elem; -} + i++; + elem = document.getElementById("input" + i); + } + if (found) elem.value = new_value; + return elem; +}; ``` 以上实现有以下问题: @@ -253,17 +254,17 @@ var setFirstEmptyInput = function(new_alue) { - 可以用 for 循环代替 while 循环; ```js -var setFirstEmptyInput = function(new_value) { - for (var i = 1; true; i++) { - var elem = document.getElementById('input' + i); - if (elem === null) { - return null; - } - if (elem.value === '') { - elem.value = new_value; - return elem; - } +var setFirstEmptyInput = function (new_value) { + for (var i = 1; true; i++) { + var elem = document.getElementById("input" + i); + if (elem === null) { + return null; + } + if (elem.value === "") { + elem.value = new_value; + return elem; } + } }; ``` diff --git "a/_posts/Tech/TOOLS/2018-07-22-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/_posts/Tech/TOOLS/2018-07-22-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" index 7c55d762d5..80f2d405ce 100644 --- "a/_posts/Tech/TOOLS/2018-07-22-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ "b/_posts/Tech/TOOLS/2018-07-22-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" @@ -6,19 +6,19 @@ tags: Tools --- -* [一、概述](#一概述) -* [二、匹配单个字符](#二匹配单个字符) -* [三、匹配一组字符](#三匹配一组字符) -* [四、使用元字符](#四使用元字符) -* [五、重复匹配](#五重复匹配) -* [六、位置匹配](#六位置匹配) -* [七、使用子表达式](#七使用子表达式) -* [八、回溯引用](#八回溯引用) -* [九、前后查找](#九前后查找) -* [十、嵌入条件](#十嵌入条件) -* [参考资料](#参考资料) - +- [一、概述](#一概述) +- [二、匹配单个字符](#二匹配单个字符) +- [三、匹配一组字符](#三匹配一组字符) +- [四、使用元字符](#四使用元字符) +- [五、重复匹配](#五重复匹配) +- [六、位置匹配](#六位置匹配) +- [七、使用子表达式](#七使用子表达式) +- [八、回溯引用](#八回溯引用) +- [九、前后查找](#九前后查找) +- [十、嵌入条件](#十嵌入条件) +- [参考资料](#参考资料) + # 一、概述 @@ -32,58 +32,58 @@ tags: Tools 正则表达式一般是区分大小写的,但是也有些实现是不区分。 -**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符; +**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符; -**\\** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。 +**\\** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。 -**正则表达式** +**正则表达式** ``` nam. ``` -**匹配结果** +**匹配结果** -My **name** is Zheng. +My **name** is Zheng. # 三、匹配一组字符 -**[ ]** 定义一个字符集合; +**[ ]** 定义一个字符集合; 0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定,字符区间只能用在 [ ] 之间。 -**-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符; +**-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符; -**^** 在 [ ] 字符集合中是取非操作。 +**^** 在 [ ] 字符集合中是取非操作。 -**应用** +**应用** 匹配以 abc 为开头,并且最后一个字母不为数字的字符串: -**正则表达式** +**正则表达式** ``` abc[^0-9] ``` -**匹配结果** +**匹配结果** -1. **abcd** -2. abc1 -3. abc2 +1. **abcd** +2. abc1 +3. abc2 # 四、使用元字符 ## 匹配空白字符 -| 元字符 | 说明 | -| :---: | :---: | -| [\b] | 回退(删除)一个字符 | -| \f | 换页符 | -| \n | 换行符 | -| \r | 回车符 | -| \t | 制表符 | -| \v | 垂直制表符 | +| 元字符 | 说明 | +| :----: | :------------------: | +| [\b] | 回退(删除)一个字符 | +| \f | 换页符 | +| \n | 换行符 | +| \r | 回车符 | +| \t | 制表符 | +| \v | 垂直制表符 | \r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n ;\r\n\r\n 可以匹配 Windows 下的空白行,因为它将匹配两个连续的行尾标签,而这正是两条记录之间的空白行; @@ -93,36 +93,36 @@ abc[^0-9] ### 1. 数字元字符 -| 元字符 | 说明 | -| :---: | :---: | -| \d | 数字字符,等价于 [0-9] | -| \D | 非数字字符,等价于 [^0-9] | +| 元字符 | 说明 | +| :----: | :-----------------------: | +| \d | 数字字符,等价于 [0-9] | +| \D | 非数字字符,等价于 [^0-9] | ### 2. 字母数字元字符 -| 元字符 | 说明 | -| :---: | :---: | -| \w | 大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] | -| \W | 对 \w 取非 | +| 元字符 | 说明 | +| :----: | :--------------------------------------------: | +| \w | 大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] | +| \W | 对 \w 取非 | ### 3. 空白字符元字符 -| 元字符 | 说明 | -| :---: | :---: | -| \s | 任何一个空白字符,等价于 [\f\n\r\t\v] | -| \S | 对 \s 取非 | +| 元字符 | 说明 | +| :----: | :-----------------------------------: | +| \s | 任何一个空白字符,等价于 [\f\n\r\t\v] | +| \S | 对 \s 取非 | \x 匹配十六进制字符,\0 匹配八进制,例如 \x0A 对应 ASCII 字符 10 ,等价于 \n,也就是它会匹配 \n 。 # 五、重复匹配 -**\+** 匹配 1 个或者多个字符, **\*** 匹配 0 个或者多个,**?** 匹配 0 个或者 1 个。 +**\+** 匹配 1 个或者多个字符, **\*** 匹配 0 个或者多个,**?** 匹配 0 个或者 1 个。 -**应用** +**应用** 匹配邮箱地址。 -**正则表达式** +**正则表达式** ``` [\w.]+@\w+\.\w+ @@ -130,9 +130,9 @@ abc[^0-9] [\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符; -**匹配结果** +**匹配结果** -**abc.def@qq.com** +**abc.def@qq.com** 为了可读性,常常把转义的字符放到字符集合 [ ] 中,但是含义是相同的。 @@ -141,11 +141,11 @@ abc[^0-9] [\w.]+@[\w]+[\.][\w]+ ``` -**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符; +**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符; \* 和 + 都是贪婪型元字符,会匹配最多的内容,在元字符后面加 ? 可以转换为懒惰型元字符,例如 \*?、+? 和 {m, n}? 。 -**正则表达式** +**正则表达式** ``` a.+c @@ -153,77 +153,77 @@ a.+c 由于 + 是贪婪型的,因此 .+ 会匹配更可能多的内容,所以会把整个 abcabcabc 文本都匹配,而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。 -**匹配结果** +**匹配结果** -**abcabcabc** +**abcabcabc** # 六、位置匹配 ## 单词边界 -**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。 +**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。 \b 只匹配位置,不匹配字符,因此 \babc\b 匹配出来的结果为 3 个字符。 ## 字符串边界 -**^** 匹配整个字符串的开头,**$** 匹配结尾。 +**^** 匹配整个字符串的开头,**$** 匹配结尾。 ^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。 分行匹配模式(multiline)下,换行被当做字符串的边界。 -**应用** +**应用** 匹配代码中以 // 开始的注释行 -**正则表达式** +**正则表达式** ``` ^\s*\/\/.*$ ``` -**匹配结果** +**匹配结果** 1. public void fun() { -2.      **// 注释 1** -3.      int a = 1; -4.      int b = 2; -5.      **// 注释 2** -6.      int c = a + b; +2.      **// 注释 1** +3.      int a = 1; +4.      int b = 2; +5.      **// 注释 2** +6.      int c = a + b; 7. } # 七、使用子表达式 -使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 * 等元字符。 +使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 \* 等元字符。 子表达式可以嵌套,但是嵌套层次过深会变得很难理解。 -**正则表达式** +**正则表达式** ``` (ab){2,} ``` -**匹配结果** +**匹配结果** -**ababab** +**ababab** -**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。 +**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。 -**正则表达式** +**正则表达式** ``` (19|20)\d{2} ``` -**匹配结果** +**匹配结果** -1. **1900** -2. **2010** -3. 1020 +1. **1900** +2. **2010** +3. 1020 -**应用** +**应用** 匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的: @@ -233,27 +233,27 @@ a.+c - 2 开头,第 2 位是 0-4 的三位数 - 25 开头,第 3 位是 0-5 的三位数 -**正则表达式** +**正则表达式** ``` ((25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))\.){3}(25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d)) ``` -**匹配结果** +**匹配结果** -1. **192.168.0.1** -2. 00.00.00.00 -3. 555.555.555.555 +1. **192.168.0.1** +2. 00.00.00.00 +3. 555.555.555.555 # 八、回溯引用 -回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc,那么回溯引用部分也需要匹配 abc 。 +回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc,那么回溯引用部分也需要匹配 abc 。 -**应用** +**应用** 匹配 HTML 中合法的标题元素。 -**正则表达式** +**正则表达式** \1 将回溯引用子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。 @@ -261,31 +261,31 @@ a.+c <(h[1-6])>\w*?<\/\1> ``` -**匹配结果** +**匹配结果** -1. **<h1>x</h1>** -2. **<h2>x</h2>** -3. <h3>x</h1> +1. **<h1>x</h1>** +2. **<h2>x</h2>** +3. <h3>x</h1> ## 替换 需要用到两个正则表达式。 -**应用** +**应用** 修改电话号码格式。 -**文本** +**文本** 313-555-1234 -**查找正则表达式** +**查找正则表达式** ``` (\d{3})(-)(\d{3})(-)(\d{4}) ``` -**替换正则表达式** +**替换正则表达式** 在第一个子表达式查找的结果加上 () ,然后加一个空格,在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。 @@ -293,59 +293,59 @@ a.+c ($1) $3-$5 ``` -**结果** +**结果** (313) 555-1234 ## 大小写转换 -| 元字符 | 说明 | -| :---: | :---: | -| \l | 把下个字符转换为小写 | -| \u| 把下个字符转换为大写 | -| \L | 把\L 和\E 之间的字符全部转换为小写 | -| \U | 把\U 和\E 之间的字符全部转换为大写 | -| \E | 结束\L 或者\U | +| 元字符 | 说明 | +| :----: | :--------------------------------: | +| \l | 把下个字符转换为小写 | +| \u | 把下个字符转换为大写 | +| \L | 把\L 和\E 之间的字符全部转换为小写 | +| \U | 把\U 和\E 之间的字符全部转换为大写 | +| \E | 结束\L 或者\U | -**应用** +**应用** 把文本的第二个和第三个字符转换为大写。 -**文本** +**文本** abcd -**查找** +**查找** ``` (\w)(\w{2})(\w) ``` -**替换** +**替换** ``` $1\U$2\E$3 ``` -**结果** +**结果** aBCd # 九、前后查找 -前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义(注: javaScript 不支持向后匹配, java 对其支持也不完善)。 +前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义(注: javaScript 不支持向后匹配, java 对其支持也不完善)。 -**应用** +**应用** 查找出邮件地址 @ 字符前面的部分。 -**正则表达式** +**正则表达式** ``` \w+(?=@) ``` -**结果** +**结果** **abc** @qq.com @@ -357,7 +357,7 @@ aBCd 条件判断为某个子表达式是否匹配,如果匹配则需要继续匹配条件表达式后面的内容。 -**正则表达式** +**正则表达式** 子表达式 (\\() 匹配一个左括号,其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件,当子表达式 1 匹配时条件成立,需要执行 \) 匹配,也就是匹配右括号。 @@ -365,29 +365,29 @@ aBCd (\()?abc(?(1)\)) ``` -**结果** +**结果** -1. **(abc)** -2. **abc** -3. (abc +1. **(abc)** +2. **abc** +3. (abc ## 前后查找条件 条件为定义的首尾是否匹配,如果匹配,则继续执行后面的匹配。注意,首尾不包含在匹配的内容中。 -**正则表达式** +**正则表达式** - ?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。 +?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。 ``` \d{5}(?(?=-)-\d{4}) ``` -**结果** +**结果** -1. **11111** -2. 22222- -3. **33333-4444** +1. **11111** +2. 22222- +3. **33333-4444** # 参考资料 diff --git "a/_posts/Tech/TOOLS/2018-08-26-\345\215\232\345\256\242\346\267\273\345\212\240Live2d.md" "b/_posts/Tech/TOOLS/2018-08-26-\345\215\232\345\256\242\346\267\273\345\212\240Live2d.md" index c0e3e9a26c..9fb111c824 100644 --- "a/_posts/Tech/TOOLS/2018-08-26-\345\215\232\345\256\242\346\267\273\345\212\240Live2d.md" +++ "b/_posts/Tech/TOOLS/2018-08-26-\345\215\232\345\256\242\346\267\273\345\212\240Live2d.md" @@ -10,54 +10,66 @@ tags: Tools [下载及参考链接](https://github.com/galnetwen/Live2D) ## 正式开工 + 在你博客程序头部文件(header)引入界面样式,在 head 标签内插入如下代码: + ```html ``` 在 body 标签内合适的位置插入 Live2D 看板娘的元素,按照 Html 书写规范写 ~ + ```html
    -
    - -
    隐藏
    +
    + +
    隐藏
    ``` 在你博客程序页脚文件(footer)引入脚本,在 body 标签结束前插入如下代码: + ```html ``` 鼠标放在页面某个元素上时,需要 Live2D 看板娘提示的请修改 message.json 文件。 **示例:** + ```json { - "mouseover": [ - { - "selector": ".title a", //此处修改为你页面元素的标签名 - "text": ["要看看 {text} 么?"] //此处修改为你需要提示的文字 - }, - { - "selector": "#searchbox", - "text": ["在找什么东西呢,需要帮忙吗?"] - } - ], - "click": [ //此处是 Live2D 看板娘的触摸事件提示 - { - "selector": "#landlord #live2d", - "text": ["不要动手动脚的!快把手拿开~~", "真…真的是不知羞耻!","Hentai!", "再摸的话我可要报警了!⌇●﹏●⌇", "110吗,这里有个变态一直在摸我(ó﹏ò。)"] - } - ] + "mouseover": [ + { + "selector": ".title a", //此处修改为你页面元素的标签名 + "text": ["要看看 {text} 么?"] //此处修改为你需要提示的文字 + }, + { + "selector": "#searchbox", + "text": ["在找什么东西呢,需要帮忙吗?"] + } + ], + "click": [ + //此处是 Live2D 看板娘的触摸事件提示 + { + "selector": "#landlord #live2d", + "text": [ + "不要动手动脚的!快把手拿开~~", + "真…真的是不知羞耻!", + "Hentai!", + "再摸的话我可要报警了!⌇●﹏●⌇", + "110吗,这里有个变态一直在摸我(ó﹏ò。)" + ] + } + ] } ``` @@ -68,18 +80,20 @@ PHP 程序推荐使用主题函数获取绝对路径。 **问:“为什么这个 Live2D 没有换装功能哎?”** **答:“自己研究去。”** - + ~~其实,就是动态改变 model.json 内的服装字段,达到随机服装的效果……~~ ## 模型欣赏 + ![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/9fe2b98062e87d05a66daf0565115b4b.png) ![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/c38bf3e8cc64d6cb4780d049527d3464.png) ## 模型说明 + > Live2D 并不是一种先进的技术,它产生的效果,都是用基本的平移、旋转、透明、曲面变形等操作实现的。 -最终的效果与贴图关系很大,而每一个动作,都需要制作师的精细调整。 -这是一个需要消耗大量时间精力的过程,因此质量好的模型并不多,质量好的也一般是在游戏中,版权受到保护,**不能随意使用**。 +> 最终的效果与贴图关系很大,而每一个动作,都需要制作师的精细调整。 +> 这是一个需要消耗大量时间精力的过程,因此质量好的模型并不多,质量好的也一般是在游戏中,版权受到保护,**不能随意使用**。 本文章中所用模型解包自 [药水制作师](https://play.google.com/store/apps/details?id=com.sinsiroad.potionmaker&hl=zh_CN "药水制作师") 手机游戏,版权归该官方所有。 (没错,我也安利下这款 ~~萝莉控~~ 游戏。啪!) -![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/6f33e886a13443de172516f03d7f61a0.png) \ No newline at end of file +![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/6f33e886a13443de172516f03d7f61a0.png) diff --git "a/_posts/Tech/TOOLS/2018-09-27-pdf reader\346\212\244\347\234\274\346\250\241\345\274\217.md" "b/_posts/Tech/TOOLS/2018-09-27-pdf reader\346\212\244\347\234\274\346\250\241\345\274\217.md" index 6820a56d33..2804459ad3 100644 --- "a/_posts/Tech/TOOLS/2018-09-27-pdf reader\346\212\244\347\234\274\346\250\241\345\274\217.md" +++ "b/_posts/Tech/TOOLS/2018-09-27-pdf reader\346\212\244\347\234\274\346\250\241\345\274\217.md" @@ -5,7 +5,6 @@ title: pdf reader护眼模式 tags: Tools --- -主菜单->编辑->首选项->辅助工具->替换文档颜色->自定义颜色->页面背景->其它颜色->按下列设置更改:色调:85;饱和度:123;亮度:205->添加到自定义颜色->在自定义颜色选定点确定->确定。这样PDF文档不再是刺眼的白底黑字,而是非常柔和的豆沙绿色,这个色调是眼科专家配置的,长时间使用会很有效的缓解眼睛疲劳保护眼睛。 - -改变其他文档颜色的方法:桌面->右键->属性->外观->高级->项目选择(窗口)、颜色1(L)选择(其它)将色调改为:85。饱和度:123。亮度:205->添加到自定义颜色->在自定义颜色选定点确定->确定这样所有的文档都不再是刺眼的白底黑字,而是非常柔和的豆沙绿色。 +主菜单->编辑->首选项->辅助工具->替换文档颜色->自定义颜色->页面背景->其它颜色->按下列设置更改:色调:85;饱和度:123;亮度:205->添加到自定义颜色->在自定义颜色选定点确定->确定。这样 PDF 文档不再是刺眼的白底黑字,而是非常柔和的豆沙绿色,这个色调是眼科专家配置的,长时间使用会很有效的缓解眼睛疲劳保护眼睛。 +改变其他文档颜色的方法:桌面->右键->属性->外观->高级->项目选择(窗口)、颜色 1(L)选择(其它)将色调改为:85。饱和度:123。亮度:205->添加到自定义颜色->在自定义颜色选定点确定->确定这样所有的文档都不再是刺眼的白底黑字,而是非常柔和的豆沙绿色。 diff --git "a/_posts/Tech/TOOLS/2018-11-25-git\344\275\277\347\224\250.md" "b/_posts/Tech/TOOLS/2018-11-25-git\344\275\277\347\224\250.md" index ff5f280723..9b09b11de1 100644 --- "a/_posts/Tech/TOOLS/2018-11-25-git\344\275\277\347\224\250.md" +++ "b/_posts/Tech/TOOLS/2018-11-25-git\344\275\277\347\224\250.md" @@ -5,6 +5,60 @@ title: git使用 tags: Tools --- +## 首先要搞清楚下面几个概念 + +- `HEAD`: `HEAD`就是指向当前分支当前版本的游标 +- Index/stage: Index即为暂存区,当你修改了你的git仓库里的一个文件时,这些变化一开始是unstaged状态,为了提交这些修改,你需要使用`git add`把它加入到index,使它成为staged状态。当你提交一个commit时,index里面的修改被提交。 +- working tree: 即当前的工作目录。 + +## 工作区域转换 + +![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208292149111) + +```python +from index to working directory: git restore --staged xx or git reset commit/HEAD. +from head to index: git reset commit --soft 把去除的commit的changes恢复到stage了 +modifed files from index to working directory: git co +``` + +文件的四种状态 + +版本控制就是对文件的版本控制,要对文件进行修改、提交等操作,首先要知道文件当前在什么状态,不然可能会提交了现在还不想提交的文件,或者要提交的文件没提交上。 + +GIT不关心文件两个版本之间的具体差别,而是关心文件的整体是否有改变,若文件被改变,在添加提交时就生成文件新版本的快照,而判断文件整体是否改变的方法就是用 + +SHA-1算法计算文件的校验和。 + +![image-20220830093905242](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208300939275.png) + +**Untracked:**未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged. + +**Unmodify:**文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. + +​ 如果使用git rm移出版本库, 则成为Untracked文件 + + **Modified:** 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用**git checkout** 则丢弃修改, 返回到unmodify状态, 这个git checkout即从stage中取出文件, 覆盖当前修改 + + **Staged:** 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, + +​ 文件状态为Modified + + 下面的图很好的解释了这四种状态的转变: + +![image-20220830093929218](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208300939259.png) + +新建文件--->Untracked + +使用add命令将新建的文件加入到暂存区--->Staged + +使用commit命令将暂存区的文件提交到本地仓库--->Unmodified + +如果对Unmodified状态的文件进行修改---> modified + +如果对Unmodified状态的文件进行remove操作--->Untracked + + + ## 最基本的提交代码 ``` @@ -33,10 +87,9 @@ git checkout -b dev_mfl_test origin/dev_mafulong [参考](https://www.cnblogs.com/luosongchao/p/3408365.html) - ``` 删除分支:git branch -d -查看远程分支: +查看远程分支: git branch -a 使用命令,git branch -d Chapater8 可以删除本地分支(在主分支中) @@ -45,6 +98,7 @@ git branch -a ``` ## 分支更新 + ``` gl = git pull @@ -63,7 +117,7 @@ git pull origin next:master git push --set-upstream origin dev_mafulong1 ``` -以后就可以push自己这个分支了 +以后就可以 push 自己这个分支了 ``` Git push origin 分支名 @@ -71,274 +125,282 @@ git push origin item_pack_mfl ``` 当我想从远程仓库里拉取一条本地不存在的分支时: + ``` git checkout -b 本地分支名 origin/远程分支名 ``` 这个将会自动创建一个新的本地分支,并与指定的远程分支关联起来。 -git show显示修改了啥 +git show 显示修改了啥 + ``` git show ``` ## 一、新建代码库 -在当前目录新建一个Git代码库 - $ git init -新建一个目录,将其初始化为Git代码库 - $ git init [project-name] +在当前目录新建一个 Git 代码库 +$ git init + +新建一个目录,将其初始化为 Git 代码库 +$ git init [project-name] 下载一个项目和它的整个代码历史 - $ git clone [url] +$ git clone [url] ## 二、配置 -Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。 -显示当前的Git配置 - $ git config --list +Git 的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。 + +显示当前的 Git 配置 +$ git config --list -编辑Git配置文 - $ git config -e [--global] +编辑 Git 配置文 +$ git config -e [--global] 设置提交代码时的用户信息 - $ git config [--global] user.name "[name]" - $ git config [--global] user.email "[email address]" +$ git config [--global] user.name "[name]" +$ git config [--global] user.email "[email address]" ## 三、增加/删除文件 + 添加指定文件到暂存区 - $ git add [file1] [file2] ... +$ git add [file1] [file2] ... 添加指定目录到暂存区,包括子目录 - $ git add [dir] +$ git add [dir] 添加当前目录的所有文件到暂存区 - $ git add . +$ git add . 添加每个变化前,都会要求确认 对于同一个文件的多处变化,可以实现分次提交 - $ git add -p +$ git add -p 删除工作区文件,并且将这次删除放入暂存区 - $ git rm [file1] [file2] ... +$ git rm [file1] [file2] ... 停止追踪指定文件,但该文件会保留在工作区 - $ git rm --cached [file] +$ git rm --cached [file] 改名文件,并且将这个改名放入暂存区 - $ git mv [file-original] [file-renamed] +$ git mv [file-original] [file-renamed] ## 四、代码提交 + 提交暂存区到仓库区 - $ git commit -m [message] +$ git commit -m [message] 提交暂存区的指定文件到仓库区 - $ git commit [file1] [file2] ... -m [message] +$ git commit [file1] [file2] ... -m [message] -提交工作区自上次commit之后的变化,直接到仓库区 - $ git commit -a +提交工作区自上次 commit 之后的变化,直接到仓库区 +$ git commit -a -提交时显示所有diff信息 - $ git commit -v +提交时显示所有 diff 信息 +$ git commit -v -使用一次新的commit,替代上一次提交 如果代码没有任何新变化,则用来改写上一次commit的提交信息 - $ git commit --amend -m [message] +使用一次新的 commit,替代上一次提交 如果代码没有任何新变化,则用来改写上一次 commit 的提交信息 +$ git commit --amend -m [message] -重做上一次commit,并包括指定文件的新变化 - $ git commit --amend [file1] [file2] ... +重做上一次 commit,并包括指定文件的新变化 +$ git commit --amend [file1] [file2] ... ## 五、分支 + 列出所有本地分支 - $ git branch +$ git branch 列出所有远程分支 - $ git branch -r +$ git branch -r 列出所有本地分支和远程分支 - $ git branch -a +$ git branch -a 新建一个分支,但依然停留在当前分支 - $ git branch [branch-name] +$ git branch [branch-name] 新建一个分支,并切换到该分支 - $ git checkout -b [branch] +$ git checkout -b [branch] -新建一个分支,指向指定commit - $ git branch [branch] [commit] +新建一个分支,指向指定 commit +$ git branch [branch] [commit] 新建一个分支,与指定的远程分支建立追踪关系 - $ git branch --track [branch] [remote-branch] +$ git branch --track [branch] [remote-branch] 切换到指定分支,并更新工作区 - $ git checkout [branch-name] +$ git checkout [branch-name] 切换到上一个分支 - $ git checkout - +$ git checkout - 建立追踪关系,在现有分支与指定的远程分支之间 - $ git branch --set-upstream [branch] [remote-branch] +$ git branch --set-upstream [branch] [remote-branch] 合并指定分支到当前分支 - $ git merge [branch] +$ git merge [branch] -选择一个commit,合并进当前分支 - $ git cherry-pick [commit] +选择一个 commit,合并进当前分支 +$ git cherry-pick [commit] 删除分支 - $ git branch -d [branch-name] +$ git branch -d [branch-name] 删除远程分支 - $ git push origin --delete [branch-name] - $ git branch -dr [remote/branch] +$ git push origin --delete [branch-name] +$ git branch -dr [remote/branch] ## 六、标签 -列出所有tag + +列出所有 tag $ git tag -新建一个tag在当前commit - $ git tag [tag] +新建一个 tag 在当前 commit +$ git tag [tag] -新建一个tag在指定commit - $ git tag [tag] [commit] +新建一个 tag 在指定 commit +$ git tag [tag] [commit] -删除本地tag - $ git tag -d [tag] +删除本地 tag +$ git tag -d [tag] -删除远程tag - $ git push origin :refs/tags/[tagName] +删除远程 tag +$ git push origin :refs/tags/[tagName] -查看tag信息 - $ git show [tag] +查看 tag 信息 +$ git show [tag] -提交指定tag - $ git push [remote] [tag] +提交指定 tag +$ git push [remote] [tag] -提交所有tag - $ git push [remote] --tags +提交所有 tag +$ git push [remote] --tags -新建一个分支,指向某个tag - $ git checkout -b [branch] [tag] +新建一个分支,指向某个 tag +$ git checkout -b [branch] [tag] ## 七、查看信息 + 显示有变更的文件 - $ git status +$ git status 显示当前分支的版本历史 - $ git log +$ git log -显示commit历史,以及每次commit发生变更的文件 - $ git log --stat +显示 commit 历史,以及每次 commit 发生变更的文件 +$ git log --stat 搜索提交历史,根据关键词 - $ git log -S [keyword] +$ git log -S [keyword] -显示某个commit之后的所有变动,每个commit占据一行 - $ git log [tag] HEAD --pretty=format:%s +显示某个 commit 之后的所有变动,每个 commit 占据一行 +$ git log [tag] HEAD --pretty=format:%s -显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 - $ git log [tag] HEAD --grep feature +显示某个 commit 之后的所有变动,其"提交说明"必须符合搜索条件 +$ git log [tag] HEAD --grep feature 显示某个文件的版本历史,包括文件改名 - $ git log --follow [file] - $ git whatchanged [file] +$ git log --follow [file] +$ git whatchanged [file] -显示指定文件相关的每一次diff - $ git log -p [file] +显示指定文件相关的每一次 diff +$ git log -p [file] -显示过去5次提交 - $ git log -5 --pretty --oneline +显示过去 5 次提交 +$ git log -5 --pretty --oneline 显示所有提交过的用户,按提交次数排序 - $ git shortlog -sn +$ git shortlog -sn 显示指定文件是什么人在什么时间修改过 - $ git blame [file] +$ git blame [file] 显示暂存区和工作区的差异 - $ git diff +$ git diff -显示暂存区和上一个commit的差异 - $ git diff --cached [file] +显示暂存区和上一个 commit 的差异 +$ git diff --cached [file] -显示工作区与当前分支最新commit之间的差异 - $ git diff HEAD +显示工作区与当前分支最新 commit 之间的差异 +$ git diff HEAD 显示两次提交之间的差异 - $ git diff [first-branch]...[second-branch] +$ git diff [first-branch]...[second-branch] 显示今天你写了多少行代码 - $ git diff --shortstat "@{0 day ago}" +$ git diff --shortstat "@{0 day ago}" 显示某次提交的元数据和内容变化 - $ git show [commit] +$ git show [commit] 显示某次提交发生变化的文件 - $ git show --name-only [commit] +$ git show --name-only [commit] 显示某次提交时,某个文件的内容 - $ git show [commit]:[filename] +$ git show [commit]:[filename] 显示当前分支的最近几次提交 - $ git reflog +$ git reflog 八、远程同步 下载远程仓库的所有变动 - $ git fetch [remote] +$ git fetch [remote] 显示所有远程仓库 - $ git remote -v +$ git remote -v 显示某个远程仓库的信息 - $ git remote show [remote] +$ git remote show [remote] 增加一个新的远程仓库,并命名 - $ git remote add [shortname] [url] +$ git remote add [shortname] [url] 取回远程仓库的变化,并与本地分支合并 - $ git pull [remote] [branch] +$ git pull [remote] [branch] 上传本地指定分支到远程仓库 - $ git push [remote] [branch] +$ git push [remote] [branch] 强行推送当前分支到远程仓库,即使有冲突 - $ git push [remote] --force +$ git push [remote] --force 推送所有分支到远程仓库 - $ git push [remote] --all +$ git push [remote] --all 九、撤销 恢复暂存区的指定文件到工作区 - $ git checkout [file] +$ git checkout [file] -恢复某个commit的指定文件到暂存区和工作区 - $ git checkout [commit] [file] +恢复某个 commit 的指定文件到暂存区和工作区 +$ git checkout [commit] [file] 恢复暂存区的所有文件到工作区 - $ git checkout . +$ git checkout . + +重置暂存区的指定文件,与上一次 commit 保持一致,但工作区不变 +$ git reset [file] -重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 - $ git reset [file] +重置暂存区与工作区,与上一次 commit 保持一致 +$ git reset --hard -重置暂存区与工作区,与上一次commit保持一致 - $ git reset --hard +重置当前分支的指针为指定 commit,同时重置暂存区,但工作区不变 +$ git reset [commit] -重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 - $ git reset [commit] +重置当前分支的 HEAD 为指定 commit,同时重置暂存区和工作区,与指定 commit 一致 +$ git reset --hard [commit] -重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 - $ git reset --hard [commit] +重置当前 HEAD 为指定 commit,但保持暂存区和工作区不变 +$ git reset --keep [commit] -重置当前HEAD为指定commit,但保持暂存区和工作区不变 - $ git reset --keep [commit] +新建一个 commit,用来撤销指定 commit 后者的所有变化都将被前者抵消,并且应用到当前分支 +$ git revert [commit] -新建一个commit,用来撤销指定commit 后者的所有变化都将被前者抵消,并且应用到当前分支 - $ git revert [commit] - 暂时将未提交的变化移除,稍后再移入 - $ git stash - $ git stash pop - +$ git stash +$ git stash pop + -## 其他 -**commit合并** +## commit rebase多合一 ``` git rebase -i head~2 //合并最近的2个commit @@ -371,16 +433,136 @@ H = D^2 = B^^2 = A^^^2 = A~2^2 I = F^ = B^3^ = A^^3^ J = F^2 = B^3^2 = A^^3^2 ``` -G-D-B-A可以认为是主干,其他都是merge进来的其他分支节点。 -A^表示A的第一个父提交,A^2表示A的第二个父提交 +G-D-B-A 可以认为是主干,其他都是 merge 进来的其他分支节点。 -A~1表示A的父提交,A~2表示A的父提交的父提交,相当于A^^和A^1^1 +A^表示 A 的第一个父提交,A^2 表示 A 的第二个父提交 -A~2^2表示A的父提交的父提交的第二个父提交,即为H +A~1 表示 A 的父提交,A~2 表示 A 的父提交的父提交,相当于 A^^和 A^1^1 +A~2^2 表示 A 的父提交的父提交的第二个父提交,即为 H + +1. “^”代表父提交,当一个提交有多个父提交时,可以通过在”^”后面跟上一个数字,表示第几个父提交,”^”相当于”^1”. + +2. `~n` + + 相当于连续的n个”^”. + + + +## git reset + +Reset一词是指撤销修改。git reset 命令被用来重置更改。git reset 命令有三种核心调用形式。这些形式如下。 + +- Soft +- Mixed +- Hard + +![Git Reset](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202208292119556.png) + +``` +git reset [--soft | --mixed | --hard] [HEAD] +``` -1.“^”代表父提交,当一个提交有多个父提交时,可以通过在”^”后面跟上一个数字,表示第几个父提交,”^”相当于”^1”. +**--mixed** 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。 -2. ~相当于连续的个”^”. +``` +git reset [HEAD] +``` + + + + --mixed reset HEAD and index 被抹去的commit的changes以及work directory的changes都会保留到work directory. 不会丢,只不过commit信息没了,修改都在。 + --soft reset only HEAD 被抹去的commit的changes会保留到stage, unstage的还是unstage,不会丢,commit信息没了 + --hard reset HEAD, index and working tree 被抹去的commit的changes没了,stage没看,unstage的还在,还是unstage + +Soft: 会把head reset到commit, 然后 + + + + + +## git 合并操作总结 + +[参考](https://sevody.github.io/2017/02/16/git-merge-command-summary/) + + + +### git merge + +`git merge` 用来合并分支 + +把 feature 分支合到 master 分支 + +``` +// 如果不设置第二个分支,默认是当前分支 +$ git merge feature [master]` +``` + +有时候`merge` 后面会加上 `--no-ff` 或者 `--ff-only` 参数 + +#### -ff + +`ff` 意思是 fast-forward, 使用 merge 时,**默认会使用 fast-forward 的方式合并代码** + +如果合并的分支(master)是被合并分支(feature)的上游分支,则合并成功,不会产生 merge log, + +如果合并的分支(master)不是被合并分支(feature)的直接上游分支(比如 master 在 checkout 出 feature 分支后,又进行了几次提交),不能使用 fast-forward 的方式合并代码,git 会进行一次三方合并(magic),如果合并成功,就会产生一个 merge log, 如果有冲突产生,则合并失败,需要解决冲突并 commit 后才能合并. + +#### –no-ff + +如果加上 `--no-ff` 参数,就是默认使用三方合并的方式合并,就算合并的分支(master)是被合并分支(feature)的上游分支,也会产生一个 merge log +这种做法的好处是,忠实地记录了实际发生过什么,关注点在真实的提交历史上面 + +#### –ff-only + +与 `--no-ff` 相反,`--ff-only` 表示只接受 fast-forward 方式的合并,如果不能直接使用 fast-forward 合并,会合并失败并报错。 + + + +可以设置每次git pull 都rebase + +```sh +git config pull.rebase true +全局: +git config --global pull.rebase true +``` + +## git pull和push默认行为 + +[参考](https://juejin.cn/post/6844903844212637709) + +设置git pull和git push默认分支,也就是设置本地分支要跟进的分支。 建立当前分支的 upstream + +```sh +$ git branch -u origin/dev +=> Branch 'dev' set up to track remote branch 'dev' from 'origin'. +``` + +也可以 + +``` +git branch --set-upstream-to=origin/one-branch one-branch` + 例如想把本地分支的 `feature-983` 跟进远程仓库的 `feature-983` 分支, 可以进行如下操作: + `git branch --set-upstream-to=origin/feature-983 feature-983 +``` + + + +查看对应关系 + +```sh +$ git branch -vv +=> dev 32cf90b [origin/dev] e23rw + master 9b04659 [origin/master] dadfa + +$ cat .git/config +=> [branch "master"] + remote = origin + merge = refs/heads/master + [branch "dev"] + remote = origin + merge = refs/heads/dev + +``` diff --git "a/_posts/Tech/TOOLS/2019-02-04-jekyll\345\215\232\345\256\242\351\207\215\346\236\204.md" "b/_posts/Tech/TOOLS/2019-02-04-jekyll\345\215\232\345\256\242\351\207\215\346\236\204.md" index f873e68b3f..2b10d5ec71 100644 --- "a/_posts/Tech/TOOLS/2019-02-04-jekyll\345\215\232\345\256\242\351\207\215\346\236\204.md" +++ "b/_posts/Tech/TOOLS/2019-02-04-jekyll\345\215\232\345\256\242\351\207\215\346\236\204.md" @@ -7,65 +7,73 @@ tags: Tools ## 前言 -本想迁移到hugo的,是一个go的静态生成框架,正好也了解go,同时编译速度更快,问题更少 +本想迁移到 hugo 的,是一个 go 的静态生成框架,正好也了解 go,同时编译速度更快,问题更少 -奈何一时之间没有找到好的theme,遂重构jekyll,以此留作日后theme迁移 +奈何一时之间没有找到好的 theme,遂重构 jekyll,以此留作日后 theme 迁移 -## 搭建jekyll本地调试环境 +## 搭建 jekyll 本地调试环境 -**以下是mac上搭建环境流程** +**以下是 mac 上搭建环境流程** -需要事先有ruby和python环境,而这恰恰mac已经有了,windows就糟糕了 +[参考](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/testing-your-github-pages-site-locally-with-jekyll) -``` -~ $ gem install jekyll bundler -~ $ jekyll new my-awesome-site -~ $ cd my-awesome-site -~/my-awesome-site $ bundle install -~/my-awesome-site $ bundle exec jekyll serve -# => 打开浏览器 http://localhost:4000 -``` -进入你的 Blog 所在目录,然后创建本地服务器 +```scala +brew install chruby ruby-install xz +ruby-install ruby 3.1.3 + +echo "source $(brew --prefix)/opt/chruby/share/chruby/chruby.sh" >> ~/.zshrc +echo "source $(brew --prefix)/opt/chruby/share/chruby/auto.sh" >> ~/.zshrc +echo "chruby ruby-3.1.3" >> ~/.zshrc # run 'chruby' to see actual version + +source ~/.zshrc + +ruby -v # + +gem install jekyll + + +bundle install + +# 预览 +bundle exec jekyll serve +# => 打开浏览器 http://localhost:4000 ``` -$ jekyll s -``` -这步就出错了,遗留bug。。。 + 你就可以在 http://127.0.0.1:4000/ 看到你的博客 -## jekyll目录 - -我们来看看Jekyll 网站的基础结构,当然我们的网站比这个复杂。 -├── _config.yml -├── _drafts -| ├── begin-with-the-crazy-ideas.textile -| └── on-simplicity-in-technology.markdown -├── _includes -| ├── footer.html -| └── header.html -├── _layouts -| ├── default.html -| └── post.html -├── _posts -| ├── 2007-10-29-why-every-programmer-should-play-nethack.textile -| └── 2009-04-26-barcamp-boston-4-roundup.textile -├── _data -| └── members.yml -├── _site +## jekyll 目录 + +我们来看看 Jekyll 网站的基础结构,当然我们的网站比这个复杂。 +├── \_config.yml +├── \_drafts +| ├── begin-with-the-crazy-ideas.textile +| └── on-simplicity-in-technology.markdown +├── \_includes +| ├── footer.html +| └── header.html +├── \_layouts +| ├── default.html +| └── post.html +├── \_posts +| ├── 2007-10-29-why-every-programmer-should-play-nethack.textile +| └── 2009-04-26-barcamp-boston-4-roundup.textile +├── \_data +| └── members.yml +├── \_site ├── img └── index.html -很复杂看不懂是不是,不要紧,你只要记住其中几个OK了 +很复杂看不懂是不是,不要紧,你只要记住其中几个 OK 了 -_config.yml 全局配置文件 +\_config.yml 全局配置文件 -_posts 放置博客文章的文件夹 +\_posts 放置博客文章的文件夹 img 存放图片的文件夹 具体参考这里https://www.jekyll.com.cn/docs/structure/ - diff --git a/_posts/Tech/TOOLS/2019-03-29-zsh.md b/_posts/Tech/TOOLS/2019-03-29-zsh.md index e0080b988d..8cd06924b4 100644 --- a/_posts/Tech/TOOLS/2019-03-29-zsh.md +++ b/_posts/Tech/TOOLS/2019-03-29-zsh.md @@ -5,16 +5,17 @@ title: zsh tags: Tools --- -## 安装vim -apt安装vim以及git +## 安装 vim -Bash替代工具Zsh +apt 安装 vim 以及 git + +Bash 替代工具 Zsh ``` -sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)" +sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/Tools/install.sh -O -)" ``` -## 设置默认zsh +## 设置默认 zsh [参考](https://askubuntu.com/questions/131823/how-to-make-zsh-the-default-shell) @@ -27,4 +28,3 @@ open your bashrc file in your favourite editor then add the line below top of the file exec zsh - diff --git "a/_posts/Tech/TOOLS/2020-11-09-\346\227\266\345\272\217\345\233\276\347\224\273\346\263\225.md" "b/_posts/Tech/TOOLS/2020-11-09-\346\227\266\345\272\217\345\233\276\347\224\273\346\263\225.md" index 8412039221..77a9b411f0 100644 --- "a/_posts/Tech/TOOLS/2020-11-09-\346\227\266\345\272\217\345\233\276\347\224\273\346\263\225.md" +++ "b/_posts/Tech/TOOLS/2020-11-09-\346\227\266\345\272\217\345\233\276\347\224\273\346\263\225.md" @@ -7,14 +7,13 @@ tags: Tools ## 时序图画法 -### markdown画时序图 -本地上可以使用typora, typora是一个markdown软件. [typora.io](https://typora.io/) +## typora 画时序图 -typora简单语法 [Draw-Diagrams-With-Markdown](https://support.typora.io/Draw-Diagrams-With-Markdown/) - -typora画时序图时机上使用的是mermaid,一个js引擎。meramaid高级语法 [https://mermaid-js.github.io/mermaid/#/sequenceDiagram](https://mermaid-js.github.io/mermaid/#/sequenceDiagram) +本地上可以使用 typora, typora 是一个 markdown 软件. [typora.io](https://typora.io/) +typora 简单语法 [Draw-Diagrams-With-Markdown](https://support.typora.io/Draw-Diagrams-With-Markdown/) +typora 画时序图时机上使用的是 mermaid,一个 js 引擎。meramaid 高级语法 [https://mermaid-js.github.io/mermaid/#/sequenceDiagram](https://mermaid-js.github.io/mermaid/#/sequenceDiagram) `->>`是实线 @@ -22,8 +21,6 @@ typora画时序图时机上使用的是mermaid,一个js引擎。meramaid高级 +是箭头,灰色长条 表示个一个过程 - - 举例: ```mermaid @@ -47,39 +44,102 @@ typora画时序图时机上使用的是mermaid,一个js引擎。meramaid高级 复杂一点的 - ```mermaid +```mermaid + +sequenceDiagram + Title: md时序图练习 - sequenceDiagram - Title: md时序图练习 + participant 客户端 + participant 控制器 + participant 业务 + participant 数据库 - participant 客户端 - participant 控制器 - participant 业务 - participant 数据库 + 客户端->>数据库:提交数据店铺 + Note right of 客户端:提交数据进行验证 + 控制器->>控制器:验证数据完整性 + Note left of 控制器:返回错误的字段信息 + 控制器-->>客户端:数据不完整 + Note over 客户端: 用户输入通行证的账号、密码 + 控制器->>业务:保存店铺到数据库 + 业务->>业务:save店铺数据 + 业务-->>控制器:保存出现异常 + 控制器-->>客户端:保存成功 + 数据库-->>业务:success + 业务-->>控制器:success + 控制器-->>客户端:success 客户端 - 客户端->>数据库:提交数据店铺 - Note right of 客户端:提交数据进行验证 - 控制器->>控制器:验证数据完整性 - Note left of 控制器:返回错误的字段信息 - 控制器-->>客户端:数据不完整 - Note over 客户端: 用户输入通行证的账号、密码 - 控制器->>业务:保存店铺到数据库 - 业务->>业务:save店铺数据 - 业务-->>控制器:保存出现异常 - 控制器-->>客户端:保存成功 - 数据库-->>业务:success - 业务-->>控制器:success - 控制器-->>客户端:success 客户端 +``` - ``` +效果图: -效果图: +![image-20220215174704294](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220215174709.png) -![image-20220215174704294](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220215174709.png)planUML画时序图 -[参考](https://plantuml.com/zh/sequence-diagram) -### 人工拖拽画时序图 +## Draw.io 人工拖拽画时序图 + [draw.io](https://app.diagrams.net/) +## plantUML 画时序图 + + + +可以用vscode插件[PlantUML](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml) + +- 创建文件a.plantuml 注意后缀 + +- mac本地需安装 + +- ```sh + brew install --cask temurin + brew install graphviz + ``` + +- Mac: option+D 快捷键预览 + +```scala + +@startuml +control a as "X" +participant c as "Client" +participant s as "Server" +a -> c: 例子1 +activate c + +c -> s: fetch +activate s + +c -> c: render + +s --> c: callback +deactivate s + +c -> c: render +return "ebd" +deactivate c + +@enduml + +``` + + + +![image-20221213144758433](/Users/mafulong/Library/Application%20Support/typora-user-images/image-20221213144758433.png) + + + +- participant可以重命名 + +- return是虚线 + +- activate和deactivate应该成对出现,表示一个竖条。有了return会自动deactivate,所以deactivate可以用return替代,return的是虚线而不是实线。下面的就会在b上有长条了。 + +- ```scala + a->b: xxx + activate b + return "xxx" + ``` + + + diff --git a/_posts/Tech/TOOLS/2020-11-29-vpn.md b/_posts/Tech/TOOLS/2020-11-29-vpn.md index 8b9e44dd95..b79ec4c426 100644 --- a/_posts/Tech/TOOLS/2020-11-29-vpn.md +++ b/_posts/Tech/TOOLS/2020-11-29-vpn.md @@ -9,21 +9,13 @@ tags: Tools [http://instar.me/archives/84807ed.html](http://instar.me/archives/84807ed.html) - - -[忍者云:](https://renzhe.cloud/user) - - +[忍者云:](https://renzhe.cloud/user) clash +[clash 配置教程](https://lancellc.gitbook.io/clash/clash-config-file/an-example-configuration-file) - -[clash配置教程](https://lancellc.gitbook.io/clash/clash-config-file/an-example-configuration-file) - - - -clash配置url-test +clash 配置 url-test ``` proxy-groups: @@ -45,6 +37,4 @@ proxy-groups: interval: 30 ``` - - -[安卓上的clash](https://10101.io/2020/02/05/how-to-use-clash-for-android) \ No newline at end of file +[安卓上的 clash](https://10101.io/2020/02/05/how-to-use-clash-for-android) diff --git "a/_posts/Tech/TOOLS/2020-11-30-\344\277\256\346\224\271host\346\217\220\351\253\230\347\275\221\347\273\234\351\200\237\345\272\246.md" "b/_posts/Tech/TOOLS/2020-11-30-\344\277\256\346\224\271host\346\217\220\351\253\230\347\275\221\347\273\234\351\200\237\345\272\246.md" index 86915921e8..78c1173e0b 100644 --- "a/_posts/Tech/TOOLS/2020-11-30-\344\277\256\346\224\271host\346\217\220\351\253\230\347\275\221\347\273\234\351\200\237\345\272\246.md" +++ "b/_posts/Tech/TOOLS/2020-11-30-\344\277\256\346\224\271host\346\217\220\351\253\230\347\275\221\347\273\234\351\200\237\345\272\246.md" @@ -5,11 +5,11 @@ title: 修改host提高网络速度 tags: Tools --- -### **Hosts的概念** +### **Hosts 的概念** -Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。Hosts 的请求级别比 DNS 高。 +Hosts 是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的 IP 地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从 Hosts 文件中寻找对应的 IP 地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交 DNS 域名解析服务器进行 IP 地址的解析。Hosts 的请求级别比 DNS 高。 -### **Hosts文件格式** +### **Hosts 文件格式** 127.0.0.1 localhost 127.0.0.1 [http://www.baidu.com](https://link.zhihu.com/?target=http%3A//www.baidu.com) @@ -19,28 +19,25 @@ Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开 ![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/d1ea9f4f3d02f4006ea83b9dee39921b.jpeg) +根据以上流程,我们就可以从 hosts 文件上做手脚,将广告的相关域名解析为 127.0.0.1,这个地址是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机**进程间通信**,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,**不进行任何网络传输** 。也就使用了 127.0.0.1 广告域名 的配置 hosts 文件,请求广告的网络请求发送,从而不能展示广告,这样就达到去广告的目的。关于去广告方案留在下篇展开。 -根据以上流程,我们就可以从hosts文件上做手脚,将广告的相关域名解析为127.0.0.1,这个地址是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机**进程间通信**,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,**不进行任何网络传输** 。也就使用了 127.0.0.1 广告域名 的配置hosts文件,请求广告的网络请求发送,从而不能展示广告,这样就达到去广告的目的。关于去广告方案留在下篇展开。 +除了去广告的应用,还应用在**科学上网、断开特定连接**。修改 hosts 文件就可以避免 DNS 污染。经常看一下破解提示就是需要在 hosts 文件添加 127.0.0.1 域名来使软件无法连接网络。 -除了去广告的应用,还应用在**科学上网、断开特定连接**。修改hosts文件就可以避免DNS污染。经常看一下破解提示就是需要在hosts文件添加127.0.0.1 域名来使软件无法连接网络。 +### **修改 hosts 文件** -### **修改hosts文件** - -接下来关键问题就是如何修改hosts文件,hosts文件存放目录: - -- Window目录 C:WindowsSystem32driversetc -- Linux及其他类Unix操作系统:/etc -- Android目录/system/etc/ - -编辑修改以上目录下的hosts文件。目前实现方式有许多形式,最直接的就是使用文件管理器将获取的hosts文件覆盖系统的hosts文件。 +接下来关键问题就是如何修改 hosts 文件,hosts 文件存放目录: +- Window 目录 C:WindowsSystem32driversetc +- Linux 及其他类 Unix 操作系统:/etc +- Android 目录/system/etc/ +编辑修改以上目录下的 hosts 文件。目前实现方式有许多形式,最直接的就是使用文件管理器将获取的 hosts 文件覆盖系统的 hosts 文件。 ### 刷新 DNS 缓存 【Windows】 按下 Windows R 键,运行 cmd ,在命令提示符运行命令 ipconfig /flushdns -【OS X 10.9】【OS X 10.10.4 】在[应用程序][实用工具][终端]运行命令 sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder +【OS X 10.9】【OS X 10.10.4 】在[应用程序][实用工具][终端]运行命令 sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder 【OS X 10.10】 在[应用程序][实用工具][终端]运行命令 sudo discoveryutil udnsflushcaches @@ -50,16 +47,12 @@ Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开 【Android、iOS】 重新启动设备 - - -### 最新hosts文件的获取渠道 +### 最新 hosts 文件的获取渠道 github:https://github.com/racaljk/hosts或https://coding.net/u/scaffrey/p/hosts/git https://github.com/lennylxx/ipv6-hosts(ipv6) +### 找到最快的 IP - -### 找到最快的IP - -http://tool.chinaz.com/speedworld/github.com \ No newline at end of file +http://tool.chinaz.com/speedworld/github.com diff --git a/_posts/Github/2021-03-16-github action.md b/_posts/Tech/TOOLS/2021-03-16-github action.md similarity index 99% rename from _posts/Github/2021-03-16-github action.md rename to _posts/Tech/TOOLS/2021-03-16-github action.md index 689ebab1cd..8afcc27342 100644 --- a/_posts/Github/2021-03-16-github action.md +++ b/_posts/Tech/TOOLS/2021-03-16-github action.md @@ -1,6 +1,6 @@ --- layout: post -category: Github +category: Tools title: github action tags: Tools --- diff --git "a/_posts/Tech/TOOLS/2021-03-22-\345\217\221\345\270\203gomodule.md" "b/_posts/Tech/TOOLS/2021-03-22-\345\217\221\345\270\203gomodule.md" deleted file mode 100644 index 0866fb1df9..0000000000 --- "a/_posts/Tech/TOOLS/2021-03-22-\345\217\221\345\270\203gomodule.md" +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -category: Tools -title: 发布gomodule -tags: Tools ---- - -## 发布gomodule - - - -[参考](https://gocn.vip/topics/9829) \ No newline at end of file diff --git "a/_posts/Tech/TOOLS/2021-09-20-\346\234\254\345\234\260\346\265\217\350\247\210\345\231\250\346\211\223\345\274\200\345\274\200\345\217\221\346\234\272\346\226\207\344\273\266.md" "b/_posts/Tech/TOOLS/2021-09-20-\346\234\254\345\234\260\346\265\217\350\247\210\345\231\250\346\211\223\345\274\200\345\274\200\345\217\221\346\234\272\346\226\207\344\273\266.md" new file mode 100644 index 0000000000..1c869b3366 --- /dev/null +++ "b/_posts/Tech/TOOLS/2021-09-20-\346\234\254\345\234\260\346\265\217\350\247\210\345\231\250\346\211\223\345\274\200\345\274\200\345\217\221\346\234\272\346\226\207\344\273\266.md" @@ -0,0 +1,18 @@ +--- +layout: post +category: Tools +title: 本地浏览器打开开发机文件 +tags: Tools +--- + +## 本地浏览器打开开发机文件 + +可以使用如下工具实现。 + +```sh +python -m http.server +``` + +它就会启动一个端口,然后浏览器就可以访问了。 + +![image-20220926202323383](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202209262023839.png) diff --git a/_posts/Github/2021-10-20-github codecov.md b/_posts/Tech/TOOLS/2021-10-20-github codecov.md similarity index 96% rename from _posts/Github/2021-10-20-github codecov.md rename to _posts/Tech/TOOLS/2021-10-20-github codecov.md index 086a4418f6..c10c1eb5fd 100644 --- a/_posts/Github/2021-10-20-github codecov.md +++ b/_posts/Tech/TOOLS/2021-10-20-github codecov.md @@ -1,6 +1,6 @@ --- layout: post -category: Github +category: Tools title: github codecov tags: Github --- diff --git "a/_posts/Tech/TOOLS/2022-02-28-chrome\346\217\222\344\273\266vimium.md" "b/_posts/Tech/TOOLS/2022-02-28-chrome\346\217\222\344\273\266vimium.md" index 34d1c723b3..7b296ddef1 100644 --- "a/_posts/Tech/TOOLS/2022-02-28-chrome\346\217\222\344\273\266vimium.md" +++ "b/_posts/Tech/TOOLS/2022-02-28-chrome\346\217\222\344\273\266vimium.md" @@ -5,9 +5,9 @@ title: chrome插件vimium tags: Tools --- -## chrome插件vimium +## chrome 插件 vimium -安装后 键盘? 可打开如下帮助页 +安装后 键盘? 可打开如下帮助页 ![image-20220228134807860](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv3/v3/20220228134807.png) @@ -21,4 +21,3 @@ map << closeTabsOnLeft map >> closeTabsOnRight map cc closeOtherTabs ``` - diff --git "a/_posts/Tech/TOOLS/2022-04-09-mac\345\274\200\346\234\272\345\257\206\347\240\201\350\256\276\347\275\2561\344\275\215.md" "b/_posts/Tech/TOOLS/2022-04-09-mac\345\274\200\346\234\272\345\257\206\347\240\201\350\256\276\347\275\2561\344\275\215.md" index e5606f5b5d..59ceac3dcb 100644 --- "a/_posts/Tech/TOOLS/2022-04-09-mac\345\274\200\346\234\272\345\257\206\347\240\201\350\256\276\347\275\2561\344\275\215.md" +++ "b/_posts/Tech/TOOLS/2022-04-09-mac\345\274\200\346\234\272\345\257\206\347\240\201\350\256\276\347\275\2561\344\275\215.md" @@ -5,7 +5,7 @@ title: mac开机密码设置1位 tags: Tools --- -## mac开机密码设置1位 +## mac 开机密码设置 1 位 - 打开终端 - 输入以下命令 `pwpolicy -clearaccountpolicies` @@ -14,8 +14,4 @@ tags: Tools - 直接命令行修改 `passwd`,或者打开系统偏好设置 > 用户与群组 - 点击更改密码,输入旧密码,输入新密码,验证新密码,点击更改密码 - - - - 这样就可以输入任意位数的密码了 diff --git "a/_posts/Tech/TOOLS/2022-04-27-git\345\233\275\345\206\205\351\225\234\345\203\217\345\212\240\351\200\237.md" "b/_posts/Tech/TOOLS/2022-04-27-git\345\233\275\345\206\205\351\225\234\345\203\217\345\212\240\351\200\237.md" index 28535fb660..833ea4d6e6 100644 --- "a/_posts/Tech/TOOLS/2022-04-27-git\345\233\275\345\206\205\351\225\234\345\203\217\345\212\240\351\200\237.md" +++ "b/_posts/Tech/TOOLS/2022-04-27-git\345\233\275\345\206\205\351\225\234\345\203\217\345\212\240\351\200\237.md" @@ -5,27 +5,19 @@ title: git国内镜像加速 tags: Tools --- -## git国内镜像加速 +## git 国内镜像加速 +## 安装 cgit +cgit 可直接代替 git 命令,cgit 内部会自动把 github.com 域名替换成想要的国内镜像域名。 -## 安装cgit +[安装 cgit](https://gitee.com/killf/cgit) -cgit可直接代替git命令,cgit内部会自动把github.com域名替换成想要的国内镜像域名。 +想要的国内镜像域名可以通过 bash_profile 里加如下配置 - - -[安装cgit](https://gitee.com/killf/cgit) - - - -想要的国内镜像域名可以通过bash_profile里加如下配置 - -```` +``` export CGIT_MIRROR=https://hub.xn--gzu630h.xn--kpry57d/ -```` - - +``` -然后把脚本里的git改成cgit,或者alias直接改git -> cgit +然后把脚本里的 git 改成 cgit,或者 alias 直接改 git -> cgit diff --git "a/_posts/Tech/TOOLS/2022-07-16-\346\254\247\351\231\206\350\257\215\345\205\270\351\205\215\347\275\256.md" "b/_posts/Tech/TOOLS/2022-07-16-\346\254\247\351\231\206\350\257\215\345\205\270\351\205\215\347\275\256.md" new file mode 100644 index 0000000000..2f668a13ea --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-07-16-\346\254\247\351\231\206\350\257\215\345\205\270\351\205\215\347\275\256.md" @@ -0,0 +1,26 @@ +--- +layout: post +category: Tools +title: 欧陆词典配置 +tags: Tools +--- + +## 欧陆词典配置 + +app store 里下载 + +登录 vip 账号 + +下载词库, [下载网址](https://www.eudic.net/v4/en/home/dictionaryresource) + +- [词根词缀](https://static.frdic.com/extra_eudb/cigen_en_new.eudic?v=20210709) + +另一个下载网址:https://mdict.org/ 都是离线版 + +[更全的下载网址](https://www.mrfan.org/dicts) + +推荐 + +- 牛津 +- 柯斯林 +- 朗文 diff --git "a/_posts/Tech/TOOLS/2022-07-22-ssh\346\216\245\345\217\243\350\275\254\345\217\221.md" "b/_posts/Tech/TOOLS/2022-07-22-ssh\346\216\245\345\217\243\350\275\254\345\217\221.md" new file mode 100644 index 0000000000..9bf97b5c12 --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-07-22-ssh\346\216\245\345\217\243\350\275\254\345\217\221.md" @@ -0,0 +1,16 @@ +--- +layout: post +category: Tools +title: ssh接口转发 +tags: Tools +--- + +## ssh 接口转发 + +[参考](https://zhuanlan.zhihu.com/p/148825449) + +``` +ssh -v -N -L ... +``` + +如何关闭,ssh 关闭则这个也关闭,开发机 exit 后即可。 diff --git "a/_posts/Tech/TOOLS/2022-07-30-chrome\345\277\205\350\243\205\346\217\222\344\273\266.md" "b/_posts/Tech/TOOLS/2022-07-30-chrome\345\277\205\350\243\205\346\217\222\344\273\266.md" new file mode 100644 index 0000000000..f7395e5e62 --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-07-30-chrome\345\277\205\350\243\205\346\217\222\344\273\266.md" @@ -0,0 +1,26 @@ +--- +layout: post +category: Tools +title: chrome必装插件 +tags: Tools +--- + +## chrome 必装插件 + +### Tampermonkey: 油猴 + +安装脚本 + +1. **Open the F\*\*king URL Right Now** 知乎自动跳转 + +### uBlacklist:google, bing 屏蔽 + +参考个人博客 搜索引擎屏蔽 rubbish 网站 + +### Dark Reader: 护眼 + +护眼,背景颜色改变 + +## 主题背景 + +- [Game of Thrones: Stark](https://chrome.google.com/webstore/category/themes) diff --git "a/_posts/Tech/TOOLS/2022-07-30-\346\220\234\347\264\242\345\274\225\346\223\216\345\261\217\350\224\275rubbish\347\275\221\347\253\231.md" "b/_posts/Tech/TOOLS/2022-07-30-\346\220\234\347\264\242\345\274\225\346\223\216\345\261\217\350\224\275rubbish\347\275\221\347\253\231.md" new file mode 100644 index 0000000000..963005dd8c --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-07-30-\346\220\234\347\264\242\345\274\225\346\223\216\345\261\217\350\224\275rubbish\347\275\221\347\253\231.md" @@ -0,0 +1,18 @@ +--- +layout: post +category: Tools +title: 搜索引擎屏蔽rubbish网站 +tags: Tools +--- + +## 搜索引擎屏蔽 rubbish 网站 + +实在想屏蔽一些网站, + +找到个 google search 屏蔽插件 PersonalBlockList,但只能屏蔽 google 的一些结果。 + +后来发现了 uBlackList,支持更多搜索引擎,包括 bing 等,就是没有百度。 推荐直接安装这个 + +[uBlackList 主页](https://github.com/iorate/uBlacklist/blob/master/README.md) + +[订阅的垃圾网址](https://raw.githubusercontent.com/cobaltdisco/Google-Chinese-Results-Blocklist/master/uBlacklist_subscription.txt) diff --git "a/_posts/Tech/TOOLS/2022-09-03-\345\244\232\346\234\272\345\231\250\345\220\214\346\255\245rsync.md" "b/_posts/Tech/TOOLS/2022-09-03-\345\244\232\346\234\272\345\231\250\345\220\214\346\255\245rsync.md" new file mode 100644 index 0000000000..2d42b2a3ee --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-09-03-\345\244\232\346\234\272\345\231\250\345\220\214\346\255\245rsync.md" @@ -0,0 +1,50 @@ +--- +layout: post +category: Tools +title: 多机器同步rsync +tags: Tools +--- + +## 多机器同步rsync + + + + + +```sh +tb () { + x=$(pwd) + echo $x + rsync -avz --delete --exclude='build' $x/ ${DEV_A}:/home/${x:6}/ +} + +get () { + x=$(pwd) + echo $x + rsync -avz --delete --exclude='' ${DEV_A}:/home/${x:6}/ $x/ +} +``` + + + +检测文件变化可以 + +- macOS: fswatch +- Linux: ionotify + + + +ssh可以远程执行另一台机器的命令 + +```sh +ssh ${DEV_A} "echo 'hhh'" +``` + + + +mac可以触发mac系统通知 + +```sh +osascript -e 'display notification "Notification text" with title "Title"' +``` + diff --git "a/_posts/Tech/TOOLS/2022-10-04-popclip\346\217\222\344\273\266\345\274\200\345\217\221.md" "b/_posts/Tech/TOOLS/2022-10-04-popclip\346\217\222\344\273\266\345\274\200\345\217\221.md" new file mode 100644 index 0000000000..a0cdb339c9 --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-10-04-popclip\346\217\222\344\273\266\345\274\200\345\217\221.md" @@ -0,0 +1,170 @@ +--- +layout: post +category: Tools +title: popclip插件开发 +tags: Tools +--- + +## popclip插件开发 + +[js开发插件](https://sspai.com/post/73404) + +[官方教程](https://github.com/pilotmoon/PopClip-Extensions) + +现有插件和其代码: https://github.com/pilotmoon/PopClip-Extensions + +### Extension Snippets + +```sh +# popclip js + multi action example +name: Markdown Formatting +requirements: [text, paste] +actions: +- title: MdInlineCode + icon: IC + javascript: popclip.pasteText('`' + popclip.input.text + '`') +- title: Markdown Bold # note: actions have a `title`, not a `name` + icon: circle filled B + javascript: popclip.pasteText('**' + popclip.input.text + '**') +- title: Markdown Italic + icon: circle filled I + javascript: popclip.pasteText('*' + popclip.input.text + '*') +``` + + + +### Extension Snippets with Python + +```sh +# popclip shellscript nested in an applescript +name: CopyNotes +applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' H2" + +"osascript -e 'display notification \"开发机通知\" with title \"开发机消息\"'" + +/Users/mafulong/github/scripts/py/copy_notes/popclip_using.py + +# popclip shellscript nested in an applescript +name: Say +applescript: do shell script "echo $PATH > ~/abxx1" + + +# popclip shellscript nested in an applescript +name: CopyNotes +actions: +- title: Markdown Note + icon: circle filled H2 + applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' H2" +- title: Markdown Note + icon: circle filled H3 + applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' H3" +- title: Markdown Note + icon: circle filled T + applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' TXT" +- title: Markdown Note + icon: circle filled H + applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' HREF" +- title: Markdown Note + icon: circle filled C + applescript: do shell script "python3 ~/github/scripts/py/copy_notes/popclip_using.py '{popclip text}' CODE" + +``` + +可以直接选择复制。 + + + +对应python 脚本 + +```python +# coding: utf8 +# MIT License + +import json +import uuid +import os +import subprocess +import time +import sys + +filename = "/Users/mafulong/Documents/copyNotes.md" +try: + + maction = [ + ("H2", lambda x: "\n## " + x + "\n"), + ("H3", lambda x: "\n### " + x + "\n"), + ("TXT", lambda x: x+"\n\n"), + ("HREF", lambda x: f"[{x}]({x})\n"), + ("CODE", lambda x: f'''```scala\n{x}\n```\n\n''') + ] + + with open(filename, "a+") as f: + timenow = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + if len(sys.argv) < 3: + f.write(timenow + "------" + "参数不全\n\n") + else: + selecttext = sys.argv[1] + action = sys.argv[2] + res = None + for (a, b) in maction: + if a == action: + res = b(selecttext) + break + if res: + f.write(res) +except Exception as ex: + with open(filename, "a+") as f: + f.write(ex) + + +``` + + + +但注意这种方式对长度有限制,因此长文本不可行。 + + + + + +### Extension Package + + + +多段一起复制时会丢换行,手动复制却没啥问题,可能是typora做的。 + +那就继续维持复制模式吧。 + +临时文件 + +```sh +{ + "identifier": "com.mfl.popclip.extension.md_notes", + "name": "MdNotes", + "icon": "quiver.png", + "popclipVersion": 3785, + "description": "Send the selection to Python.", + "note": "7 May 2022: Updated to clip as code block when holding ⌥.", + "captureHtml": true, + "actions":[ + { + "title": "Md note", + "icon": "circle filled H1", + "scriptInterpreter": "python3", + "shellScriptFile": "quiver.py" + } + ] +} + +``` + + + +```python +filename = "/Users/mafulong/Documents/copyNotes.md" +import os +with open(filename, "a+") as f: + text = os.environ['POPCLIP_TEXT'] + f.write(text) +``` + diff --git "a/_posts/Tech/TOOLS/2022-10-08-\345\257\206\347\240\201\347\256\241\347\220\206\345\231\250.md" "b/_posts/Tech/TOOLS/2022-10-08-\345\257\206\347\240\201\347\256\241\347\220\206\345\231\250.md" new file mode 100644 index 0000000000..805edd37a6 --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-10-08-\345\257\206\347\240\201\347\256\241\347\220\206\345\231\250.md" @@ -0,0 +1,62 @@ +--- +layout: post +category: Tools +title: 密码管理器 +tags: Tools +--- + +## 密码管理器 + + + +一般使用chrome密码管理器即可。这里的网站密码通常认为不咋重要,重要的都会放到另个更安全地方。 + + + +但公司的chrome可能受组织管理,禁用了chrome密码管理器,这时就需要其他方法了。 + + + +## 使用keeweb + +keeweb是个开源免费秘密管理器。 可保存文件到onedrive上等。 + + + +首先将chrome密码导出csv,然后用keeweb打开,会让你选择导入到哪里,这时候导入到你建好或者打开的密码文件即可。 + + + +使用上,在chrome输入框,可通过右键选择keyweb plugin选择账号和密码进行填充。 + + + +keeweb 有桌面版和浏览器版,哪个都行,前者不用一直开始keeweb标签页。但需要个叫啥connector的插件。 + + + +ui很丑。 + +## 使用**KeePassXC** + +另一个keepass开源版本,mac推荐。 + + + +ui更好看。 + + + +## firefox csv密码导入 + +chrome可以以csv形式导出密码。 + + + +[细节请参考](https://websetnet.net/zh-CN/%E5%A6%82%E4%BD%95%E5%B0%86%E5%AF%86%E7%A0%81%E4%BB%8Ecsv%E6%96%87%E4%BB%B6%E5%AF%BC%E5%85%A5%E5%88%B0firefox/) + + + +## 微软 authenticator + +首推,还可以跨平台,微软有保障。 diff --git "a/_posts/Tech/TOOLS/2022-11-26-chrome\345\217\263\351\224\256\350\217\234\345\215\225\345\274\200\345\217\221.md" "b/_posts/Tech/TOOLS/2022-11-26-chrome\345\217\263\351\224\256\350\217\234\345\215\225\345\274\200\345\217\221.md" new file mode 100644 index 0000000000..797fa26276 --- /dev/null +++ "b/_posts/Tech/TOOLS/2022-11-26-chrome\345\217\263\351\224\256\350\217\234\345\215\225\345\274\200\345\217\221.md" @@ -0,0 +1,32 @@ +--- +layout: post +category: Tools +title: chrome右键菜单开发 +tags: Tools +--- + +## chrome右键菜单开发 + +请参考: + +- [扩展浏览器右键菜单,加个谷歌搜索](https://juejin.cn/post/6952384957360635911) + +- [Chrome开发自定义右键菜单实现快速跳转到指定页面,一次多个菜单项目](https://zhuanlan.zhihu.com/p/550500152) + +- [Chrome 插件如何完成剪切板的操作](https://blog.51cto.com/xingag/5847203) + + + + + +主要改manifest.json, index.js, content.js。 + + + +content.js运行在每个页面上,可选,除非有需要页面交互的才需要。 + +background.js是监听click事件的。 + + + +Demo code: 见个人github。 diff --git "a/_posts/Tech/Tools/2021-01-02-\344\275\277\347\224\250github+picGo+typora\345\233\276\345\272\212.md" "b/_posts/Tech/Tools/2021-01-02-\344\275\277\347\224\250github+picGo+typora\345\233\276\345\272\212.md" index c067af34b9..b3729c4577 100644 --- "a/_posts/Tech/Tools/2021-01-02-\344\275\277\347\224\250github+picGo+typora\345\233\276\345\272\212.md" +++ "b/_posts/Tech/Tools/2021-01-02-\344\275\277\347\224\250github+picGo+typora\345\233\276\345\272\212.md" @@ -5,7 +5,46 @@ title: 使用github+picGo+typora图床 tags: Tools --- -## 使用github+picGo+typora图床 +## 使用 github+picGo+typora 图床 [参考](https://blog.csdn.net/qq_36376089/article/details/107429913) +### token 生成 + +https://github.com/settings/tokens + +第一个 repo 勾选。 + +### picgo 配置 + +仓库名: mafulong/mdPic + +分支 v7,v8 递增,需要提前创建。 + +制定存储路径 v7/, 这样 + +自定义域名如下,不用包含目录,仅包含分支名即可。 + +``` +https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6 +``` + +开启『时间戳重命名』 + +不要开启:『上传前重命名』 + +## Typora 免费版本下载 + +### Windows 用户 + +下载地址: https://github.com/iuxt/src/releases/download/2.0/typora-0-11-18.exe + +### Mac 用户 + +下载地址: https://github.com/iuxt/src/releases/download/2.0/typora-0-11-18.dmg + +### Ubuntu 用户 + +下载地址:https://github.com/iuxt/src/releases/download/2.0/Typora_Linux_0.11.18_amd64.deb + +[参考](https://zahui.fan/posts/64b52e0d/) diff --git "a/_posts/Tech/Tools/2021-01-24-\345\270\270\347\224\250\346\225\260\345\255\246\345\205\254\345\274\217.md" "b/_posts/Tech/Tools/2021-01-24-\345\270\270\347\224\250\346\225\260\345\255\246\345\205\254\345\274\217.md" deleted file mode 100644 index 1d91106c81..0000000000 --- "a/_posts/Tech/Tools/2021-01-24-\345\270\270\347\224\250\346\225\260\345\255\246\345\205\254\345\274\217.md" +++ /dev/null @@ -1,25 +0,0 @@ ---- -layout: post -category: Tools -title: 常用数学公式 -tags: Tools ---- - -## 常用数学公式 - -### 等比数列前n项和 - -![image-20210124135003088](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv1/v1/1.png) - - - -### 组合数公式 - -![img](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv2/v2/21.png) - -(r 已翻车,很多广告 -μTorrent是BitTorrent Inc发布的一款有名的BT下载软件,支持Windows 和Mac、Linux、Android操作系统。当然,和迅雷不一样,μTorrent无视版权,敏感资源。阿虚这里提供两个版本: +μTorrent 是 BitTorrent Inc 发布的一款有名的 BT 下载软件,支持 Windows 和 Mac、Linux、Android 操作系统。当然,和迅雷不一样,μTorrent 无视版权,敏感资源。阿虚这里提供两个版本: -uTorrent.v2.21 已优化配置版(大小仅有1mb多,无广告,绿色版,没有乱七八糟的功能(可以类比迅雷精简版)) +uTorrent.v2.21 已优化配置版(大小仅有 1mb 多,无广告,绿色版,没有乱七八糟的功能(可以类比迅雷精简版)) ### Transmission -照例先介绍一下Transmission,这是一款开源的BT下载软件,支持BT下载和磁力下载,主要支持Linux和Mac OS 操作系统(很遗憾没有windows版),这款软件最大的特点就是在保证功能的同时做到了对资源占用的极小化。大多带USB的路由器挂PT就是用的他,我之前在网件6300V2上用他下PT跑满全速没压力。 +照例先介绍一下 Transmission,这是一款开源的 BT 下载软件,支持 BT 下载和磁力下载,主要支持 Linux 和 Mac OS 操作系统(很遗憾没有 windows 版),这款软件最大的特点就是在保证功能的同时做到了对资源占用的极小化。大多带 USB 的路由器挂 PT 就是用的他,我之前在网件 6300V2 上用他下 PT 跑满全速没压力。 ### FDM -FDM是Free Download Manager的名称缩写。它的两大特点:完全免费,纯净无广告。 +FDM 是 Free Download Manager 的名称缩写。它的两大特点:完全免费,纯净无广告。 > [官网](https://www.freedownloadmanager.org/zh/) -- 支持Windows及Mac OSX两大操作系统; +- 支持 Windows 及 Mac OSX 两大操作系统; - 现代化设计的友好界面; - 从热门网站下载视频; -- 支持HTTP/HTTPS/FTP/BitTorrent协议; +- 支持 HTTP/HTTPS/FTP/BitTorrent 协议; - 支持代理; - 快速、安全、高效下载。 ### persepolisdm -基于aria2 套了个外客,支持bt,磁力。 +基于 aria2 套了个外客,支持 bt,磁力。 > [官网](https://persepolisdm.github.io/) ### 其他 -Motrix:https://motrix.app/ 需要流量,流量要买,抵制 +Motrix:https://motrix.app/ 需要流量,流量要买,抵制 qBittorrent:https://www.qbittorrent.com uTorrent:https://www.utorrent.com/intl/zh_cn/ Transmission:https://www.transmissionbt.com/ - - - diff --git "a/_posts/Tech/Tools/2021-03-09-mac\345\210\240\351\231\244pdf\347\251\272\347\231\275\351\241\265.md" "b/_posts/Tech/Tools/2021-03-09-mac\345\210\240\351\231\244pdf\347\251\272\347\231\275\351\241\265.md" index 68e2e985b9..6ab36e7461 100644 --- "a/_posts/Tech/Tools/2021-03-09-mac\345\210\240\351\231\244pdf\347\251\272\347\231\275\351\241\265.md" +++ "b/_posts/Tech/Tools/2021-03-09-mac\345\210\240\351\231\244pdf\347\251\272\347\231\275\351\241\265.md" @@ -5,12 +5,8 @@ title: mac删除pdf空白页 tags: Tools --- -## mac删除pdf空白页 +## mac 删除 pdf 空白页 +使用 pdf 语言,缩略图模式,delete 就行 - -使用pdf语言,缩略图模式,delete就行 - - - -[参考](https://www.jianshu.com/p/61e83c599bf7) \ No newline at end of file +[参考](https://www.jianshu.com/p/61e83c599bf7) diff --git "a/_posts/Tech/algorithms/2018-02-19-\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215KMP\347\256\227\346\263\225.md" "b/_posts/Tech/algorithms/2018-02-19-\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215KMP\347\256\227\346\263\225.md" index 5fad5af33a..f1117bc18c 100644 --- "a/_posts/Tech/algorithms/2018-02-19-\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215KMP\347\256\227\346\263\225.md" +++ "b/_posts/Tech/algorithms/2018-02-19-\345\255\227\347\254\246\344\270\262\345\214\271\351\205\215KMP\347\256\227\346\263\225.md" @@ -16,6 +16,10 @@ tags: Algorithms next数组的意思就是 next[i] = len; 长度为i的数组的前缀和后缀相等的最大长度。 例如abcdabc就是 next[7] = 3; 相等的前缀和后缀最长是abc长度为3 + + +next理解: KMP中的next数组,有的也称fail失败数组,存的到底是什么。是首子串和尾子串能匹配的最大长度 + ## kmp 特意设置next[0] = -1, 因为当next[0]=0地时候会死循环,无限地j=next[j], j=0. diff --git "a/_posts/Tech/algorithms/2018-06-01-\344\272\214\345\217\211\346\240\221.md" "b/_posts/Tech/algorithms/2018-06-01-\344\272\214\345\217\211\346\240\221.md" index 4a1a7b12cc..fdd84f7174 100644 --- "a/_posts/Tech/algorithms/2018-06-01-\344\272\214\345\217\211\346\240\221.md" +++ "b/_posts/Tech/algorithms/2018-06-01-\344\272\214\345\217\211\346\240\221.md" @@ -822,3 +822,61 @@ public List postorderTraversal(TreeNode root) { return node; } ``` + +## 找最近公共祖先 + +- [236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) + + + +方法1: 遍历,时间复杂度o(N) + +```python +class Solution: + + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if not root or root == p or root == q: + return root + left = self.lowestCommonAncestor(root.left, p, q) + right = self.lowestCommonAncestor(root.right, p, q) + if left and right: + return root + return left if left else right +``` + +方法2: 存储父节点o(N), 父节点遍历o(log(N)) + +```python +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + parent = {} + + def dfs(r): + nonlocal parent + if r.left: + parent[r.left] = r + dfs(r.left) + if r.right: + parent[r.right] = r + dfs(r.right) + + dfs(root) # o(N) + p_path = [p] + while p in parent: + p = parent[p] + p_path.append(p) + q_path = [q] + while q in parent: + q = parent[q] + q_path.append(q) + p_path = p_path[::-1] + q_path = q_path[::-1] + # print(p_path) + # print(q_path) + r = None + for i in range(min(len(p_path), len(q_path))): + if p_path[i] == q_path[i]: + r = p_path[i] + return r +``` + diff --git "a/_posts/Tech/algorithms/2018-06-01-\346\216\222\345\272\217.md" "b/_posts/Tech/algorithms/2018-06-01-\346\216\222\345\272\217.md" index 5d9940df0d..1de4bc1b79 100644 --- "a/_posts/Tech/algorithms/2018-06-01-\346\216\222\345\272\217.md" +++ "b/_posts/Tech/algorithms/2018-06-01-\346\216\222\345\272\217.md" @@ -801,4 +801,68 @@ class Solution: * 基数排序和桶排序可以看成是计数排序的泛化版本,使用了某些措施优化排序过程。 * 在桶排序中当桶的个数取最大值(max-min+1)的时候,就变成了计数排序,所以计数排序时桶排序的一种特例。 * 基数排序可以看做是多轮桶排序,基数排序以有效位的角度,每个有效位都进行一轮桶排序。 - * 当用最大值作为基数时,基数排序就退化成了计数排序。 \ No newline at end of file + * 当用最大值作为基数时,基数排序就退化成了计数排序。 + + + + + +## 排序问题 + +### 得到数组排序需要的最小交换次数 + +给一个序列,序列两两元素可以任意交换,求最少的交换次数使得序列有序 + +这是一个经典问题,一般有两种做法 + +![image-20221113155634380](/Users/mafulong/Library/Application Support/typora-user-images/image-20221113155634380.png) + +非置换环方法。 + +```python + def min_swaps(nums): + mp = {} + sort_nums = sorted(nums) + for i in range(len(sort_nums)): + mp[sort_nums[i]] = i + ans = 0 + for i in range(len(nums)): + while nums[i] != sort_nums[i]: + t = mp[nums[i]] + nums[i], nums[t] = nums[t], nums[i] + ans += 1 + return ans +``` + + + + + +**置换环的思想为** : 对每个节点,将其指向其排序后应该放到的位置,直到首位相接形成了一个环。 + +> [参考](https://leetcode.cn/problems/minimum-number-of-operations-to-sort-a-binary-tree-by-level/solution/by-liu-wan-qing-zjlj/) + +![QQ图片20221113152139.jpg](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202211131558611.jpg) + +```python + def min_swaps(nums): + mp = {} + sort_nums = sorted(nums) + for i in range(len(sort_nums)): + mp[sort_nums[i]] = i + lops = 0 + flags = [] + for i in range(len(nums)): + flags.append(False) + for i in range(len(nums)): + if not flags[i]: + j = i + while not flags[j]: + flags[j] = True + j = mp[nums[j]] + lops += 1 + return len(nums) - lops +``` + + + diff --git "a/_posts/Tech/algorithms/2018-09-29-trie\345\255\227\345\205\270\346\240\221.md" "b/_posts/Tech/algorithms/2018-09-29-trie\345\255\227\345\205\270\346\240\221.md" index a86cbc7b09..0920a27670 100644 --- "a/_posts/Tech/algorithms/2018-09-29-trie\345\255\227\345\205\270\346\240\221.md" +++ "b/_posts/Tech/algorithms/2018-09-29-trie\345\255\227\345\205\270\346\240\221.md" @@ -45,7 +45,7 @@ Trie的核心思想是空间换时间。利用字符串的公共前缀来降低 ## 后缀字典树 -#### 简介 +### 简介 后缀树,它描述了给定字符串的所有后缀,许多重要的字符串操作都能够在后缀树上快速地实现。 @@ -59,7 +59,7 @@ Trie的核心思想是空间换时间。利用字符串的公共前缀来降低 ![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/cbc0942c49766bad09ba134b41e81295.gif) -#### 优点 +### 优点 匹配快。对于长度为m的模式串,只需花费至多O(m)的时间进行匹配。 空间省。Suffix tree的空间耗费要低于Suffix trie,因为Suffix tree除根节点外不允许其内部节点只含单个子节点,因此它是Suffix trie的压缩表示。 @@ -72,7 +72,7 @@ Trie的核心思想是空间换时间。利用字符串的公共前缀来降低 -#### 应用 +### 应用 后缀树(Suffix tree)是一种树形数据结构,能快速解决很多关于字符串的问题。后缀樹的概念最早由Weiner 於1973年提出,既而由McCreight 在1976年和Ukkonen在1992年和1995年加以改進完善。 diff --git "a/_posts/Tech/algorithms/2019-01-12-Tarjan\347\256\227\346\263\225\357\274\232\346\261\202\350\247\243\345\233\276\347\232\204\345\211\262\347\202\271\344\270\216\346\241\245\357\274\210\345\211\262\350\276\271\357\274\211.md" "b/_posts/Tech/algorithms/2019-01-12-Tarjan\347\256\227\346\263\225\357\274\232\346\261\202\350\247\243\345\233\276\347\232\204\345\211\262\347\202\271\344\270\216\346\241\245\357\274\210\345\211\262\350\276\271\357\274\211.md" index d5c0f9947e..56aee555d0 100644 --- "a/_posts/Tech/algorithms/2019-01-12-Tarjan\347\256\227\346\263\225\357\274\232\346\261\202\350\247\243\345\233\276\347\232\204\345\211\262\347\202\271\344\270\216\346\241\245\357\274\210\345\211\262\350\276\271\357\274\211.md" +++ "b/_posts/Tech/algorithms/2019-01-12-Tarjan\347\256\227\346\263\225\357\274\232\346\261\202\350\247\243\345\233\276\347\232\204\345\211\262\347\202\271\344\270\216\346\241\245\357\274\210\345\211\262\350\276\271\357\274\211.md" @@ -86,10 +86,5 @@ def tarjan(u): if cur[-1] == u: break scc.append(cur) - -作者:承志 -链接:https://juejin.cn/post/6875498612537851918 -来源:掘金 -著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ``` diff --git "a/_posts/Tech/algorithms/2020-11-12-\347\272\277\346\256\265\346\240\221.md" "b/_posts/Tech/algorithms/2020-11-12-\347\272\277\346\256\265\346\240\221.md" index 5608004595..e7e1cb57f6 100644 --- "a/_posts/Tech/algorithms/2020-11-12-\347\272\277\346\256\265\346\240\221.md" +++ "b/_posts/Tech/algorithms/2020-11-12-\347\272\277\346\256\265\346\240\221.md" @@ -3,6 +3,7 @@ layout: post category: Algorithms title: 线段树 tags: Algorithms +recent_update: true --- # 定义 diff --git "a/_posts/Tech/algorithms/2020-11-29-\345\212\250\346\200\201\350\247\204\345\210\222\346\200\273\347\273\223.md" "b/_posts/Tech/algorithms/2020-11-29-\345\212\250\346\200\201\350\247\204\345\210\222\346\200\273\347\273\223.md" index acf7011660..aeb3b0f195 100644 --- "a/_posts/Tech/algorithms/2020-11-29-\345\212\250\346\200\201\350\247\204\345\210\222\346\200\273\347\273\223.md" +++ "b/_posts/Tech/algorithms/2020-11-29-\345\212\250\346\200\201\350\247\204\345\210\222\346\200\273\347\273\223.md" @@ -3,6 +3,7 @@ layout: post category: Algorithms title: 动态规划总结 tags: Algorithms +recent_update: true --- [参考](https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns) diff --git "a/_posts/Tech/java/2018-02-01-java\346\263\233\345\236\213.md" "b/_posts/Tech/java/2018-02-01-java\346\263\233\345\236\213.md" index f58cea1463..cf4d44df76 100644 --- "a/_posts/Tech/java/2018-02-01-java\346\263\233\345\236\213.md" +++ "b/_posts/Tech/java/2018-02-01-java\346\263\233\345\236\213.md" @@ -4,18 +4,43 @@ category: Java title: java泛型 tags: Java --- + + + +## 泛型 + Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 +其实就是一种编译检查的工具,类似某些注解一样, 如Override。 + + + +为什么说编译检查,例子 + +```java +class A { + public E f(E a) { + return a; + } +} + +public class Main { + public static void main(String[] args) { + System.out.println(new A().f(123)); // compile error + } +} +``` + ## 泛型的使用 ### 泛型方法 -```java +```java public static < E > void printArray( E[] inputArray ) { - // 输出数组元素 - for ( E element : inputArray ){ + // 输出数组元素 + for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); @@ -23,6 +48,7 @@ Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了 ``` ### 泛型类 + 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。 ```java @@ -36,6 +62,7 @@ public class Box { ``` ### 泛型接口 + ```java public interface Generator { public T next(); @@ -44,310 +71,220 @@ public interface Generator { 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型 -### 泛型的好处 +## 泛型的好处 -①类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作java.lang.ClassCastException展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性。 +① 类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作 java.lang.ClassCastException 展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性。 -②消除了代码中许多的强制类型转换,增强了代码的可读性。 +② 消除了代码中许多的强制类型转换,增强了代码的可读性。 -③为较大的优化带来了可能。 +③ 为较大的优化带来了可能。 ## 类型通配符 -类型通配符一般是使用?代替具体的类型参数。 - -**``````和``````的区别** - -前者表示该通配符所代表的类型是T类型的子类。 -后者表示该通配符所代表的类型是T类型的父类。 +### 为什么需要类型通配符 -### PECS原则 -“Producer Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends +java 数组具有协变性: -“Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super +- class Circle implements Shape, 如果给 某方法(Shape[] arr) 传递一个 Circle[] 类型的数组,这是可以的,编译通过,也能正常运行。也就是说:Circle[] IS-A Shape[] -如果阅读过一些Java集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样: - -```java -public class Collections { - public static void copy(List dest, List src) { - for (int i=0; i arr)`传递一个 `Collection`类型的集合,这是不可以的。编译器就会报如下的错误: -记住:Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。 + - `The method totalArea(Collection) in the type Demo is not applicable for the arguments (Collection)` -不能给形参为```List```的函数传入实参```List```类型,会编译出错,在编译期间就出现错误,它们是不同的类型。 +- 也就是说,形参是`List`,你无法传入`List`,`List`,`ArrayList`等,在编译期间就出现错误,它们是不同的类型。 -正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。比如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。 +有了类型通配符后,就可以约束泛型的父类或者子类,然后就可以让带有类型通配符泛型的 List 有数组一样的协变性,比如 ```java - ArrayList a1 = new ArrayList<>(); - ArrayList a2 =new ArrayList<>(); +//假如B继承A +List可以赋值给List +List也可以赋值给List +``` - Class c1 =a1.getClass(); - Class c2 = a2.getClass(); +注意如下是不可以的 - System.out.println(c1.equals(c2)); //Output: true +``` +The type Listis assignable to List. ``` -### 一,数组的协变性(covariant array type)及集合的非协变性 -设有Circle类和Square类继承自Shape类。 +### 类型通配符 -关于数组的协变性,看代码: -```java -public static double totalArea(Shape[] arr){ - double total = 0; - for (Shape shape : arr) { - if(shape != null) - total += shape.area(); - } - return total; - } -``` +类型通配符一般是使用?代替具体的类型参数。 -如果给 totalArray(Shape[] arr) 传递一个Circle[] 类型的数组,这是可以的,编译通过,也能正常运行。也就是说:Circle[] IS-A Shape[] +**``和``的区别** -关于集合的协变性,看代码: -```java -public static double totalArea(Collection arr){ - double total = 0; - for (Shape shape : arr) { - if(shape != null) - total += shape.area(); - } - return total; - } -``` +前者表示该通配符所代表的类型是 T 类型的子类。 -给```totalArea(Collection arr)```传递一个 ```Collection```类型的集合,这是不可以的。编译器就会报如下的错误: +后者表示该通配符所代表的类型是 T 类型的父类。 -```The method totalArea(Collection) in the type Demo is not applicable for the arguments (Collection)``` + 是无界通配符。 -也就是说,形参是```List```,你无法传入```List```,```List```,```ArrayList```等 -### 二,如果解决集合的非协变性带来的不灵活? -```java -public static double totalArea(Collection arr){ - double total = 0; - for (Shape shape : arr) { - if(shape != null) - total += shape.area(); - } - return total; - } -``` -这样,就可以给totalArea(Collection arr) -传递Collection、Collection、Collection类型的参数了。 -假如B继承A +注意不能能对`List`的List进行Add. 会报错。 对于这个问题我们不妨从编译器的角度去考虑。`List` 可能有多种含义,比如是`ArrayList` 编译器无法识别所以会报错。 所以对于实现了``的集合类只能将它视为`Producer`向外提供(`get`)元素,而不能作为`Consumer`来对外获取(`add`)元素。 ```java -List与List一样 -List可以赋值给List -//假如B继承A -List可以赋值给List -List也可以赋值给List +class Fruit { +} +class Apple extends Fruit { +} +List flist = new ArrayList(); +flist.add(new Fruit()); // compile error: capture of ? extends Fruit ``` -链接:https://www.nowcoder.com/questionTerminal/9bc2d446173147b3b28b31568a6c4706?toCommentId=2215648 -对的只有A C D G -``` -class A {} -class B extends A {} -class C extends A {} -class D extends B {} -Which four statements are true ? -The type Listis assignable to List. -The type Listis assignable to List. -The type Listis assignable to List. -The type Listis assignable to List. -The type Listis assignable to List. -The type Listis assignable to any List reference. -The type Listis assignable to List. -``` +#### PECS 原则 -### 三,泛型的类型擦除及类型擦除带来的ClassCastException异常 -JAVA的泛型只存在于编译层,到了运行时,是看不到泛型的。 +规律 -还是拿数组来做对比: +- “Producer Extends” – 如果你需要一个只读 List,用它来 produce T,那么使用? extends 。 如果写会编译错误 + +- “Consumer Super” – 如果你需要一个只写 List,用它来 consume T,那么使用? super 。只允许写T,可读。 +- 如果需要同时读取以及写入,那么我们就不能使用通配符了。 + +如果阅读过一些 Java 集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样: ```java - String[] str = new String[10]; - Object[] obj = str;//向上转型 - - //Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer - obj[0] = new Integer(2); +public class Collections { + public static void copy(List dest, List src) { + for (int i=0; i intList = new ArrayList(); - intList.add(2); - Object obj = intList; - - //Type safety: Unchecked cast from Object to ArrayList - ArrayList strList = (ArrayList)obj; - - //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String - String str = strList.get(0); - str.trim();//do something with str -``` -编译器会对第6行提示第5行所示的警告。程序运行到第9行时抛出ClassCastException异常。因为ArrayList存储的本质上是一个Integer。 +泛型限定符有一描述:上界不存下界不取。 + +上界不存的原因:例如 List,编译器只知道容器内是 Father 及其子类,具体是什么类型并不知道,编译器在看到 extends 后面的 Father 类,只是标上一个 `CAP#1` 作为占位符,无论往里面插什么,编译器都不知道能不能和 `CAP#1` 匹配,所以就不允许插入。 + +extends的作用:可以在初始化的时候存入一个值,并且能保证数据的稳定性,只能取不能存。读取出来的数据可以存在父类或者基类里。 -现在分析下第6行代码: +下界不取的原因:下界限定了元素的最小粒度,实际上是放松了容器元素的类型控制。例如 List, 元素是 Father 的基类,可以存入 Father 及其子类。但编译器并不知道哪个是 Father 的超类,如 Human。读取的时候,自然不知道是什么类型,只能返回 Object,这样元素信息就全部丢失了。 -obj是Object类型的引用,strList是一个ArrayList类型的引用,因此,向下转型时编译器给出了警告,在运行时,由于类型擦除,相当于 +### List -ArrayList strList = (ArrayList)obj; -因此,代码运行到第6行也能通过。 +> [参考](https://stackoverflow.com/questions/1844770/what-does-list-mean-in-java-generics) -对于第9行代码: +The `?`, or unbounded wildcard, means that the type of the object is not specified. It could be unknown, could be meant for multiple possible values or might be just plain irrelevant. `List`, is pronounced "List of unknown." -strList但是一个ArrayList类型的引用,当然可以调用 ArrayList的get方法。因此,编译时没问题。在运行时, +Unbounded Wildcards ? -由于,String str = strList.get(0);会编译成String str = (String)strList.get(0); +> The unbounded wildcard type is specified using the wildcard character (`?`), for example, `List`. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach: +> +> - If you are writing a method that can be implemented using functionality provided in the Object class. +> - When the code is using methods in the generic class that don't depend on the type parameter. For example, `List.size` or `List.clear`. In fact, `Class` is so often used because most of the methods in `Class` do not depend on `T`. -而strList.get(0)得到 的是一个Integer对象,然后把它赋值给 String str,由于Integer IS-NOT-A String。故抛出ClassCastException。 +? 表示一种特定的未知类型,所以 List 不能 add 因为会类型绑定错误而出现错误 capture xx of ?之类的。 -### 四,为什么不支持泛型数组 -现在我们假设在 Java 中可以创建泛型数组,看看可能会发生什么情况: +- `List`: There is no type restriction and assignment restriction at all. +- `List`: It seems to be used the same as `List`, but a compilation error will occur when accepting other generic assignments. 不能`List list = a, where a is List` +- `List`: It is a generic type. Before assignment, it means that it can accept any type of set assignment, but after assignment, you can't `add` elements to it, but you can `remove` and `clear`, not an `immutable set`. `List` is generally used as a parameter to receive an external collection, or return a collection of specific element types, also known as a `wildcard collection`. ```java -// 假设可以创建泛型数组 -List[] stringLists = new ArrayList[1]; -List intList = Arrays.asList(42); -// 泛型擦除,List 继承自 Object,所以可以如此赋值 -// 在数组中,子类数组 是 父类数组 的子类,Object[] o = new ArrayList[1]; -Object[] objects = stringLists; -// 同理,泛型擦除后,List 类型变量赋值给 Object 类型变量 -// 但此时出现问题了,**** List 实例添加到了声明为 List[] 类型的数组中了 ****** -objects[0] = intList; -String s = stringLists[0].get(0); +The type Listis assignable to List. +The type Listis assignable to List. ``` -Java对数组运行时是会检查其类型并强制类型转换的,如果用了泛型,因为被擦除后可以塞各种数据,运行时系统无法对数组中存储的类型做检查。 -而这类问题在编译时无法发现,只能在运行时出现问题 -所以如果禁止创建泛型数组,就可以避免此类问题 -### 解决方案 +注意,**优先使用带?的泛型**,这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,并且**在编译器检查时不会产生警告信息。** -#### 泛型数组包装器 -用ArrayList收集泛型数组对象的对象元素,如ArrayList、ArrayList> +## 类型擦除 -将获得数组的行为,以及由泛型提供的编译期的类型安全 +Java 的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。到了运行时,是看不到泛型的。Java的设计者在JDK 1.5时才引入了泛型,但为了照顾以前设计上的缺陷,同时兼容非泛型的代码,不得不做出了一个折中的策略:编译时对泛型要求严格,运行时却把泛型擦除了——要兼容以前的版本,还要升级扩展新的功能,真的很不容易! -#### 反射 -通过反射在运行时构出实际类型为type[]的对象数组,避免了类型擦除,从而转换成功,无ClassCastException +泛型擦除到底是什么意思呢?我们先来看一下下面这个简单的例子: ```java -import java.lang.reflect.*; - -public class GenericArrayWithTypeToken { - private T[] array; - @SuppressWarning("unchecked") - public GenericArrayWithTypeToken(Class type, int sz) { - array = (T[]) Array.newInstance(type, sz);//通过反射在运行时构出实际类型为type[]的对象数组,避免了类型擦除,从而转换成功,无ClassCastException - } - public void put(int index, T item){ - array[index] = item; +public class Node { + private T data; + private Node next; + public Node(T data, Node next) { + this.data = data; + this.next = next; } - public T get(int index) { return array[index]; } - public T[] rep() { return array; } //能成功返回了~ - public static void main(String[] args) { - GenericArrayWithTypeToken gawtt = new GenericArrayWithTypeToken<>(Integer.class, 10); - Integer[] ia = gawtt.rep(); //能成功返回了! + public T getData() { return data; } + // ... +} +``` + +编译器做完相应的类型检查之后,实际上到了运行期间上面这段代码实际上将转换成: + +```java +public class Node { + private Object data; + private Node next; + public Node(Object data, Node next) { + this.data = data; + this.next = next; } + public Object getData() { return data; } + // ... } ``` -#### 通配符 -[The Java™ Tutorials: Generics](https://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html) -给出的解决方案如下: +这意味着不管我们声明`Node`还是`Node`,到了运行期间,`JVM`统统视为`Node`。有没有什么办法可以解决这个问题呢?这就需要我们自己重新设置`bounds`了,将上面的代码修改成下面这样: ```java -// OK, array of unbounded wildcard type. -List[] lsa = new List[10]; -Object o = lsa; -Object[] oa = (Object[]) o; -List li = new ArrayList(); -li.add(new Integer(3)); -// Correct. -oa[1] = li; -// Run time error, but cast is explicit. -String s = (String) lsa[1].get(0); +public class Node> { + private T data; + private Node next; + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + public T getData() { return data; } + // ... +} ``` -一定要用List或者List后面那块,前面得无所谓了 +这样编译器就会将`T`出现的地方替换成`Comparable`而不再是默认的`Object`了: -比如 ```java - List[] graphic = new List[numCourses]; -for (int i = 0; i < numCourses; i++) { - graphic[i] = new ArrayList<>(); +public class Node { + private Comparable data; + private Node next; + public Node(Comparable data, Node next) { + this.data = data; + this.next = next; + } + public Comparable getData() { return data; } + // ... } ``` -## 泛型的约束和限制 -### 原始类型变量的赋值分析 -[参考网址](https://blog.csdn.net/never_blue/article/details/70652659) + +因此可能会带来一些问题,比如如下代码运行到最后一行时会产生异常 ClassCastException ```java + ArrayList intList = new ArrayList(); + intList.add(2); + Object obj = intList; - public static void main(String[] args) { - List list=new ArrayList(); - list.add("abc"); - list.add(33); - List list1=list; - for(int i=0;iInteger的ClassCastException异常 - //是运行错误,编译通过哦 - System.out.println(t); - } - - for (int i = 0; i < list1.size(); i++) { - String t=list1.get(i); - //编译通过不了,因为编译器认为list1里都是Integer,按照声明时的类型来, - // 因此直接编译错误,不管实际类型,即使都是String - System.out.println(t); - } + //just warnning: Type safety: Unchecked cast from Object to ArrayList + ArrayList strList = (ArrayList) obj; + // 这里会ClassCastException,因为类型擦除 + //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String + String str = strList.get(0); ``` -总结: - -- 当程序把一个原始类型的变量赋给一个带泛型信息的变量时,总是可以通过编译,只是会提示一些警告信息。 -- 当程序试图访问带有泛型声明的集合的集合元素时,编译器总是把集合元素当成泛型类型处理,并不关心集合里元素的实际类型。 -- 当程序试图访问带有泛型声明的集合的集合元素时,JVM会遍历每个集合元素自动执行强制类型转化,如果集合元素的实际类型与集合所带的泛型信息不匹配,运行时将引发ClassCastException异常。 - -### 泛型的约束和限制 -**不能使用8个基本类型实例化类型参数** +## 泛型的约束和限制 -**类型检查不可使用泛型** +### 类型检查不可使用泛型 ```java -if(aaa instanceof Pair){}//error +if(aaa instanceof Pair){}//compile error: Illegal generic type for instanceof Pair p = (Pair) a;//warn @@ -372,7 +309,8 @@ public static void rtti(List list) { } ``` -**不能创建泛型对象数组** +### 不能创建泛型对象数组 + ```java GenericMethod[] o=null;//ok o=new GenericMethod[10];//error @@ -386,25 +324,9 @@ o=new GenericMethod[10];//error o=(GenericMethod[]) new GenericMethod[10]; ``` -不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此jdk强制不允许。 - -有一个特例是方法的可变参数,虽然本质上是数组,却可以使用泛型。 - -安全的方法是使用List。 - -**Varargs警告** - -java不支持泛型类型的对象数组,可变参数是可以的。它也正是利用了强制类型转换,因此同样是类型不安全的。所以这种代码编译器会给一个警告。 - -```java -public static T getMiddle(T... a){ - return a[a.length/2]; -} -``` - -去除警告有两种途径:一种是在定义可变参数方法上(本例中的getMiddle())加上@SafeVarargs注解,另一种是在调用该方法时添加@SuppressWarnings("unchecked")注解。 +不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此 jdk 强制不允许。java对数组是有类型检查的,不论是写还是读。 -**不能实例化泛型对象** +### 不能实例化泛型对象 ```java T t= new T();//error @@ -426,7 +348,8 @@ append(ls, String.class); ``` -**不能在泛型类的静态域中使用泛型类型** +### 不能在泛型类的静态域中使用类泛型类型 + ```java public class Singleton{ private static T singleton; //error @@ -436,6 +359,7 @@ public class Singleton{ ``` 但是,静态的泛型方法可以使用泛型类型: + ```java public static T getInstance(){return null;} //ok public static void print(T t){} //ok @@ -443,13 +367,14 @@ public static void print(T t){} //ok 这个原因很多资料中都没说的太明白,说一下个人理解,仅供参考: -1. 泛型类中,称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:`private T t;`,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的。 +1. 泛型类中,``称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:`private T t;`,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的。 +2. 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T 所指的具体类型已经明确了。 -2. 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T所指的具体类型已经明确了。 +### 继承泛型类时自动重写override -**擦除的冲突 重载与重写** +对于泛型代码,Java 编译器实际上还会偷偷帮我们实现一个 Bridge method。 -对于泛型代码,Java编译器实际上还会偷偷帮我们实现一个Bridge method。 +在继承一个泛型类时,会自动加对泛型类的方法的调用时的类型强制转换。 ```java public class Node { @@ -469,7 +394,7 @@ public class MyNode extends Node { } ``` -看完上面的分析之后,你可能会认为在类型擦除后,编译器会将Node和MyNode变成下面这样: +类型擦除后: ```java public class Node { @@ -480,33 +405,10 @@ public class Node { this.data = data; } } -public class MyNode extends Node { - public MyNode(Integer data) { super(data); } - public void setData(Integer data) { - System.out.println("MyNode.setData"); - super.setData(data); - } -} -``` - -实际上不是这样的,我们先来看一下下面这段代码,这段代码运行的时候会抛出ClassCastException异常,提示String无法转换成Integer: - -```java -MyNode mn = new MyNode(5); -Node n = mn; // A raw type - compiler throws an unchecked warning -n.setData("Hello"); // Causes a ClassCastException to be thrown. -// Integer x = mn.data; -``` - - -如果按照我们上面生成的代码,运行到第3行的时候不应该报错(注意我注释掉了第4行),因为MyNode中不存在setData(String data)方法,所以只能调用父类Node的setData(Object data)方法,既然这样上面的第3行代码不应该报错,因为String当然可以转换成Object了,那ClassCastException到底是怎么抛出的? - -实际上Java编译器对上面代码自动还做了一个处理: - -```java class MyNode extends Node { // Bridge method generated by the compiler - public void setData(Object data) { + public void setData(Object data) {、 + // 这个是关键!!!!,有个强制类型转化。 setData((Integer) data); } public void setData(Integer data) { @@ -517,5 +419,10 @@ class MyNode extends Node { } ``` -这也就是为什么上面会报错的原因了,setData((Integer) data);的时候String无法转换成Integer。所以上面第2行编译器提示unchecked warning的时候,我们不能选择忽略,不然要等到运行期间才能发现异常。如果我们一开始加上Node n = mn就好了,这样编译器就可以提前帮我们发现错误。 + + + +## 参考 + +- [参考1](https://github.com/SigalHu/MyBlog/blob/master/Java/Java%E6%B3%9B%E5%9E%8B%E8%AF%A6%E8%A7%A3%5B%E8%BD%AC%5D.md) diff --git "a/_posts/Tech/java/2018-02-03-java\345\233\236\350\260\203\345\207\275\346\225\260.md" "b/_posts/Tech/java/2018-02-03-java\345\233\236\350\260\203\345\207\275\346\225\260.md" deleted file mode 100644 index 3667a3a472..0000000000 --- "a/_posts/Tech/java/2018-02-03-java\345\233\236\350\260\203\345\207\275\346\225\260.md" +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: post -category: Java -title: java回调函数 -tags: Java ---- - -[回调函数](https://www.cnblogs.com/snowbook/p/5802804.html) \ No newline at end of file diff --git "a/_posts/Tech/java/2018-02-03-java\345\244\232\347\272\277\347\250\213.md" "b/_posts/Tech/java/2018-02-03-java\345\244\232\347\272\277\347\250\213.md" index c31699e393..315b992637 100644 --- "a/_posts/Tech/java/2018-02-03-java\345\244\232\347\272\277\347\250\213.md" +++ "b/_posts/Tech/java/2018-02-03-java\345\244\232\347\272\277\347\250\213.md" @@ -4,18 +4,18 @@ category: Java title: java多线程 tags: Java --- -[多线程](https://www.cnblogs.com/GarfieldEr007/p/5746362.html) - +[多线程](https://www.cnblogs.com/GarfieldEr007/p/5746362.html) 先看:http://www.cyc2018.xyz/Java/Java%20%E5%B9%B6%E5%8F%91.html#%E4%B8%80%E3%80%81%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B ## 概念 + 饥饿状态:某几个进程长久得不到运行 活锁:几个进程同时谦让对方,使得没有进程得到资源 -Java中的Long是非原子性的,前32和后32不同写入,int操作是原子性的 +Java 中的 Long 是非原子性的,前 32 和后 32 不同写入,int 操作是原子性的 编译器为了减少中断流水线的次数,所以会进行指令重排,串行中不会发生的问题并行中就出现了 @@ -23,21 +23,19 @@ Java中的Long是非原子性的,前32和后32不同写入,int操作是原 ![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/c7cca93e6dbb47e8ab3cb83bf091e2bf.jpeg) - 1、新建状态(New):新创建了一个线程对象。 -2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 - -3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 +2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。 -4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: +3、运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。 -(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) +4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: -(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 +(一)、等待阻塞:运行的线程执行 wait()方法,JVM 会把该线程放入等待池中。(wait 会释放持有的锁) -(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁) +(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。 +(三)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。(注意,sleep 是不会释放持有的锁) 阻塞三种状态: @@ -45,7 +43,7 @@ Java中的Long是非原子性的,前32和后32不同写入,int操作是原 2. WAITING: 无限等待 3. TIMED_WAITING: 有限时间的等待 -5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 +5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。 ## 使用线程 @@ -101,23 +99,24 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc } ``` -#### Callable接口和Runnable接口的不同之处: +#### Callable 接口和 Runnable 接口的不同之处: -1. Callable规定的方法是call,而Runnable是run -2. call方法可以抛出异常,但是run方法不行 -3. Callable对象执行后可以有返回值,运行Callable任务可以得到一个Future对象,通过Future对象可以了解任务执行情况,可以取消任务的执行,而Runnable不可有返回值 +1. Callable 规定的方法是 call,而 Runnable 是 run +2. call 方法可以抛出异常,但是 run 方法不行 +3. Callable 对象执行后可以有返回值,运行 Callable 任务可以得到一个 Future 对象,通过 Future 对象可以了解任务执行情况,可以取消任务的执行,而 Runnable 不可有返回值 -get方法会一直阻塞,直到结束,或者可以规定时间 +get 方法会一直阻塞,直到结束,或者可以规定时间 -#### 多线程的实现有以下4个步骤: +#### 多线程的实现有以下 4 个步骤: + +1.创建一个线程,创建 Callable 的实现类 Race,并且重写 call 方法 -1.创建一个线程,创建Callable的实现类Race,并且重写call方法 ```java ExecutorService ser=Executors.newFixedThreadPool(线程数目); Race tortoise = new Race(); ``` -2.得到Future对象 +2.得到 Future 对象 Future result=ser.submit(tortoise); @@ -155,48 +154,54 @@ public static void main(String[] args) { - Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口; - 类可能只要求可执行就行,继承整个 Thread 类开销过大。 - ## 守护线程 + 守护线程(后台线程)和非守护线程(前台线程) 垃圾回收,内存管理都是后台线程 主线程默认是前台线程,前台线程创建的子线程默认是前台线程,守护线程创建的子线程默认是守护线程。当程序中只有守护线程运行时该程序便可以结束运行。 -setDaemon(true)必须在start()前设置 +setDaemon(true)必须在 start()前设置 ## 线程的加塞运行 -t.join()使t线程加塞到当前线程之前获得CPU,当前线程进入等待状态,直到线程t结束为止,当前线程恢复为就绪状态,等待线程调度 -可以通过传入线程t2,并t2.join()使得按照一定顺序的运行t2->t +t.join()使 t 线程加塞到当前线程之前获得 CPU,当前线程进入等待状态,直到线程 t 结束为止,当前线程恢复为就绪状态,等待线程调度 + +可以通过传入线程 t2,并 t2.join()使得按照一定顺序的运行 t2->t -thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 +thread.Join 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续执行线程 B。 ## 线程的礼让 -yield()让当前线程落选,让出CPU回到就绪状态,让线程调试器重新调度一次。让给优先级高或相等的线程获得执行的机会,没有相同优先级的线程是就绪状态,yield()就什么也不做,继续运行。 + +yield()让当前线程落选,让出 CPU 回到就绪状态,让线程调试器重新调度一次。让给优先级高或相等的线程获得执行的机会,没有相同优先级的线程是就绪状态,yield()就什么也不做,继续运行。 ## 线程的定时执行 -1. Timer类 -定时工具类 -cancel(),schedule(TimerTask task,Date time,long period),time时开始,period是周期 + +1. Timer 类 + 定时工具类 + cancel(),schedule(TimerTask task,Date time,long period),time 时开始,period 是周期 2. TimerTask -此类implements Runable,就是个线程 + 此类 implements Runable,就是个线程 -需要用户extends此类 +需要用户 extends 此类 ## 线程的中断 -Thread.interrupt()通知线程中断,设置中断标志位,但不是立即中断,不会发生作用,需要在run()中设置中断处理代码。比如run()中的while{},增加if(Thread.currentThread().isInterrupted()),,break,退出线程。线程的正常终止也应是这样的。当在sleep()期间中断会抛出异常,所以在catch块中重新设置中断标志位,来中断线程 + +Thread.interrupt()通知线程中断,设置中断标志位,但不是立即中断,不会发生作用,需要在 run()中设置中断处理代码。比如 run()中的 while{},增加 if(Thread.currentThread().isInterrupted()),,break,退出线程。线程的正常终止也应是这样的。当在 sleep()期间中断会抛出异常,所以在 catch 块中重新设置中断标志位,来中断线程 ## 线程的终止 + 1. 无限循环中使用退出标志 2. stop(),不推荐 -3. 如处于运行状态,对象赋值null, +3. 如处于运行状态,对象赋值 null, 4. 非运行状态,interrupt(),抛出异常,可以捕获,然后退出,也可以捕获后不退出 stop()方法回立即释放所持有的锁,会将正在写入的数据写坏,比如写坏一半 ## 线程的优先级 + 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。 Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。 @@ -205,132 +210,138 @@ Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIO 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。 - ## 常见问题 ### 错误的加锁 -对于Integer,给Integer加锁是经常出错的,因为Integer是不变对象,当对其增加时,是另外创建了一个对象并赋值 + +对于 Integer,给 Integer 加锁是经常出错的,因为 Integer 是不变对象,当对其增加时,是另外创建了一个对象并赋值 ## 线程的挂起和解挂 -不推荐使用suspend()和resume()函数,会出现状态错误,使用自定义标志位,然后在线程中检查标志,实现挂起和解挂 -挂起:while:标志为真,wait(); +不推荐使用 suspend()和 resume()函数,会出现状态错误,使用自定义标志位,然后在线程中检查标志,实现挂起和解挂 + +挂起:while:标志为真,wait(); 解挂:设置标志,notify() ## volatile + 并不能代替锁,只能让其他线程看到线程的修改,也无法保证一些复合操作的原子性。 -另外,不能真正的保证线程安全,当两个线程同时修改一个数据时,依然会发生冲突,比如两个i++,其实只加了一次 +另外,不能真正的保证线程安全,当两个线程同时修改一个数据时,依然会发生冲突,比如两个 i++,其实只加了一次 -volatile两大作用 +volatile 两大作用 -1、保证内存可见性 2、防止指令重排 +1、保证内存可见性 2、防止指令重排 -此外需注意volatile并不保证操作的原子性。保证的是load,assign,store +此外需注意 volatile 并不保证操作的原子性。保证的是 load,assign,store -volatile关键字可以保证变量的可见性,因为对volatile的操作都在Main Memory中,而Main Memory是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或Cache,因为它们都不是全局的,无法保证可见性,可能产生脏读。 +volatile 关键字可以保证变量的可见性,因为对 volatile 的操作都在 Main Memory 中,而 Main Memory 是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或 Cache,因为它们都不是全局的,无法保证可见性,可能产生脏读。 -volatile还有一个作用就是局部阻止重排序的发生,对volatile变量的操作指令都不会被重排序,因为如果重排序,又可能产生可见性问题。 -在保证可见性方面,锁(包括显式锁、对象锁)以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同,例如同步锁保证得到锁时从内存里重新读入数据刷新缓存,释放锁时将数据写回内存以保数据可见,而volatile变量干脆都是读写内存。 +volatile 还有一个作用就是局部阻止重排序的发生,对 volatile 变量的操作指令都不会被重排序,因为如果重排序,又可能产生可见性问题。 +在保证可见性方面,锁(包括显式锁、对象锁)以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同,例如同步锁保证得到锁时从内存里重新读入数据刷新缓存,释放锁时将数据写回内存以保数据可见,而 volatile 变量干脆都是读写内存。 注意点: 1. 不能当计数器,因为计数器要保证读取-修改-存取是一个原子性操作 -2. 可以当标志位boolean +2. 可以当标志位 boolean 3. 可以用于一次性发布,就是发布后不再修改了,比如单例模式 ## 线程的同步和互斥 -### 用synchronized实现线程的互斥 +### 用 synchronized 实现线程的互斥 + 1. 用在语句块前 2. 用在方法声明 -### 用ThreadLocal实现线程局部变量 +### 用 ThreadLocal 实现线程局部变量 + 就是线程的私有变量,线程之间互不干扰,在线程类中声明定义即可 + ```java public ThreadLocal sum=new ThreadLocal(); sum.set(0); sum.get(); ``` -### 用Object类的wait()和notify()实现线程的同步 -wait()和notify()必须与synchronized联合使用 +### 用 Object 类的 wait()和 notify()实现线程的同步 + +wait()和 notify()必须与 synchronized 联合使用 1. Synchronized 加在方法上, (同步方法,锁定类实例) 2. Synchronized 加在对象上, (同步块,锁定类实例) -3. Synchronized 锁定的是 类变量 ,即static 变量(可能是属性,可能是方法)(锁定类对象) -4. 类的方法中访问了多线程共同的资源, 且该资源是可变的,这种情况下也是需要进行同步的,比如static字符串 +3. Synchronized 锁定的是 类变量 ,即 static 变量(可能是属性,可能是方法)(锁定类对象) +4. 类的方法中访问了多线程共同的资源, 且该资源是可变的,这种情况下也是需要进行同步的,比如 static 字符串 -在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在JAVA中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。 +在 JAVA 中,是没有类似于 PV 操作、进程互斥等相关的方法的。JAVA 的进程同步是通过 synchronized()来实现的,需要说明的是,JAVA 的 synchronized()方法类似于操作系统概念中的互斥内存块,在 JAVA 中的 Object 类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现 JAVA 中简单的同步、互斥操作。明白这个原理,就能理解为什么 synchronized(this)与 synchronized(static XXX)的区别了,synchronized 就是针对内存区块申请内存锁,this 关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而 static 成员属于类专有,其内存空间为该类所有成员共有,这就导致 synchronized()对 static 成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在 JAVA 中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助 Object.wait(), Object.nofity()了。 -Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。 +Obj.wait(),与 Obj.notify()必须要与 synchronized(Obj)一起使用,也就是 wait,与 notify 是针对已经获取了 Obj 锁进行操作,从语法角度来说就是 Obj.wait(),Obj.notify 必须在 synchronized(Obj){...}语句块内。从功能上来说 wait 就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的 notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的 notify()就是对对象锁的唤醒操作。但有一点需要注意的是 notify()调用后,并不是马上就释放对象锁的,而是在相应的 synchronized(){}语句块执行结束,自动释放锁后,JVM 会在 wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与 Object.wait()二者都可以暂停当前线程,释放 CPU 控制权,主要的区别在于 Object.wait()在释放 CPU 同时,释放了对象锁的控制。 [更好的理解在这呢](https://blog.csdn.net/genww/article/details/6096232) -单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下: +单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对 Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印 ABC 的问题了吧,这是一道比较经典的面试题,题目要求如下: -建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下: +建立三个线程,A 线程打印 10 次 A,B 线程打印 10 次 B,C 线程打印 10 次 C,要求线程同时运行,交替打印 10 次 ABC。这个问题用 Object 的 wait(),notify()就可以很方便的解决。代码如下: ```java -public class MyThreadPrinter2 implements Runnable { - - private String name; - private Object prev; - private Object self; - - private MyThreadPrinter2(String name, Object prev, Object self) { - this.name = name; - this.prev = prev; - this.self = self; - } - - @Override - public void run() { - int count = 10; - while (count > 0) { - synchronized (prev) { - synchronized (self) { - System.out.print(name); - count--; - +public class MyThreadPrinter2 implements Runnable { + + private String name; + private Object prev; + private Object self; + + private MyThreadPrinter2(String name, Object prev, Object self) { + this.name = name; + this.prev = prev; + this.self = self; + } + + @Override + public void run() { + int count = 10; + while (count > 0) { + synchronized (prev) { + synchronized (self) { + System.out.print(name); + count--; + self.notify(); //释放对象锁,后面的可以拿到锁 - } - try { + } + try { prev.wait();//阻塞,等待prev的notify,才能继续运行 //也就是释放了这个对象锁,暂停这个线程 //必须有个notify才可以继续运行 - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - } - } - - public static void main(String[] args) throws Exception { - Object a = new Object(); - Object b = new Object(); - Object c = new Object(); - MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a); - MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b); - MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c); - - - new Thread(pa).start(); - Thread.sleep(100); //确保按顺序A、B、C执行 - new Thread(pb).start(); - Thread.sleep(100); - new Thread(pc).start(); - Thread.sleep(100); - } -} + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + } + + public static void main(String[] args) throws Exception { + Object a = new Object(); + Object b = new Object(); + Object c = new Object(); + MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a); + MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b); + MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c); + + + new Thread(pa).start(); + Thread.sleep(100); //确保按顺序A、B、C执行 + new Thread(pb).start(); + Thread.sleep(100); + new Thread(pc).start(); + Thread.sleep(100); + } +} ``` -自己理解来看,就是设置额外变量设置条件,当条件不满足时,this.wait()阻塞,可以if/while块中 +自己理解来看,就是设置额外变量设置条件,当条件不满足时,this.wait()阻塞,可以 if/while 块中 -以下代码是子进程5次,主线程3,然后循环10次的,通过一个boolean设置条件,每当条件不满足时,就阻塞下。 +以下代码是子进程 5 次,主线程 3,然后循环 10 次的,通过一个 boolean 设置条件,每当条件不满足时,就阻塞下。 ```java @@ -392,9 +403,10 @@ class T{ } ``` -## JDK并发包 +## JDK 并发包 ### 重入锁 + 几个重要方法 re-entrance-lock @@ -411,17 +423,20 @@ unlock(); //释放锁 ``` -### 重入锁的好搭档:Condition条件 -condition.await()和wait()类似 +### 重入锁的好搭档:Condition 条件 + +condition.await()和 wait()类似 -condition.signal()和notify()类似 +condition.signal()和 notify()类似 -要在lock块内 +要在 lock 块内 ### Semaphore + 信号量机制 允许多个线程同时访问 + ```java Semaphore semaphore=new semaphore(int permits);//permits是个许可证 @@ -460,13 +475,16 @@ public class SemaphoreExample { } } ``` + resutl: - 2 1 2 2 2 2 2 1 2 2 +2 1 2 2 2 2 2 1 2 2 ### ReadWriteLock + 读写分离锁,可以减少锁竞争,提升性能 ### CountdownLatch + 就是倒计数的锁存期,可以让线程等待直到倒计时结束,再开始执行 用来控制一个线程等待多个线程。 @@ -498,13 +516,14 @@ result: run..run..run..run..run..run..run..run..run..run..end ### CyclicBarrier + 允许一组线程互相等待,直到都到达某个公共屏障点 用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。 和 CountdownLatch 相似,都是通过维护计数器来实现的。但是它的计数器是递增的,每次执行 await() 方法之后,计数器会加 1,直到计数器的值和设置的值相等,等待的所有线程才会继续执行。和 CountdownLatch 的另一个区别是,CyclicBarrier 的计数器可以循环使用,所以它才叫做循环屏障。 -这个是规定多少个线程,这一定数量的线程都到达await()时才开始都唤醒,继续执行 +这个是规定多少个线程,这一定数量的线程都到达 await()时才开始都唤醒,继续执行 ```java public class CyclicBarrierExample { @@ -531,11 +550,168 @@ public class CyclicBarrierExample { ``` result: + ``` before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after.. ``` ### Exchanger + 用于两个线程之间数据的同步交换,都准备好才交换 ## 线程池 + +[线程池的粗浅使用](https://www.jianshu.com/p/edd7cb4eafa0) + +[线程池的分析](http://ifeve.com/java-threadpool/) + +[线程池分析更好些](https://www.cnblogs.com/absfree/p/5357118.html) + +合理利用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。 + +### 创建线程池 + +一般通过工具类 Executors 的静态方法来获取线程池或静态方法。介绍四种常用创建方法 + +单例线程,表示在任意的时间段内,线程池中只有一个线程在工作 + +```java +ExecutorService service1 = Executors.newSingleThreadExecutor(); +``` + +缓存线程池,先查看线程池中是否有当前执行线程的缓存,如果有就 resue(复用),如果没有,那么需要创建一个线程来完成当前的调用.并且这类线程池只能完成一些生存期很短的一些任务.并且这类线程池内部规定能 resue(复用)的线程,空闲的时间不能超过 60s,一旦超过了 60s,就会被移出线程池 + +```java +ExecutorService service2 = Executors.newCacheThreadPool(); +``` + +固定型线程池,和 newCacheThreadPool()差不多,也能够实现 resue(复用),但是这个池子规定了线程的最大数量,也就是说当池子有空闲时,那么新的任务将会在空闲线程中被执行,一旦线程池内的线程都在进行工作,那么新的任务就必须等待线程池有空闲的时候才能够进入线程池,其他的任务继续排队等待.这类池子没有规定其空闲的时间到底有多长.这一类的池子更适用于服务器. + +```java +ExecutorService service3 = Executors.newFixedThreadPool(10); +``` + +调度型线程池,调度型线程池会根据 Scheduled(任务列表)进行延迟执行,或者是进行周期性的执行.适用于一些周期性的工作. + +```java +public class Test { + public static void main(String[] args) { + ExecutorService service = Executors.newCachedThreadPool(); + service.submit(new Runnable() { + @Override + public void run() { + while(true){ + System.out.println("hello world !"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + System.out.println(" ===> main Thread execute here ! " ); + } +} + +``` + +```java +ExecutorService service4 = Executors.newScheduledThreadPool(10); +``` + +### 线程池任务创建与提交 + +任务分为两种:一种是有返回值的( callable ),一种是没有返回值的( runnable ). Callable 与 Future 两功能是 Java 在后续版本中为了适应多并法才加入的,Callable 是类似于 Runnable 的接口,实现 Callable 接口的类和实现 Runnable 的类都是可被其他线程执行的任务。 + +1. 无返回值的任务就是一个实现了 runnable 接口的类.使用 run 方法. +1. 有返回值的任务是一个实现了 callable 接口的类.使用 call 方法. + +Callable 和 Runnable 的区别如下: + +1. Callable 定义的方法是 call,而 Runnable 定义的方法是 run。 +1. Callable 的 call 方法可以有返回值,而 Runnable 的 run 方法不能有返回值。 +1. Callable 的 call 方法可抛出异常,而 Runnable 的 run 方法不能抛出异常。 + +execute 与 submit 区别: + +1. 接收的参数不一样 +1. submit 有返回值,而 execute 没有 +1. submit 方便 Exception 处理 +1. execute 是 Executor 接口中唯一定义的方法;submit 是 ExecutorService(该接口继承 Executor)中定义的方法 + +### 线程池的关闭 + +我们可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池,但是它们的实现原理不同,shutdown 的原理是只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。shutdownNow 的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow 会首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。 + +只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow。 + +### 线程池的分析 + +#### 线程池创建分析 + +```java +new ThreadPoolExecutor(corePoolSize, maximumPoolSize, +keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler); +``` + +创建一个线程池需要输入几个参数: + +- corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads 方法,线程池会提前创建并启动所有基本线程。默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中; +- runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。 + +1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 +1. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。 +1. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。 +1. PriorityBlockingQueue:一个具有优先级得无限阻塞队列。 + +- maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。也就是说 corePoolSize 就是线程池大小,maximumPoolSize 在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。 +- ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug 和定位问题时非常又帮助。 +- RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。以下是 JDK1.5 提供的四种策略。 + +1. AbortPolicy:直接抛出异常 +1. CallerRunsPolicy:只用调用者所在线程来运行任务。 +1. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 +1. DiscardPolicy:不处理,丢弃掉。 + +当然也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。如记录日志或持久化不能处理的任务。 + +- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 +- TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。 + +![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/9a70374b792298b693aeafa95e1bd07d.jpeg) + +从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下: + +1. 如果当前线程池中的线程数目小于 corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务; +1. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务; +1. 如果当前线程池中的线程数目达到 maximumPoolSize,则会采取任务拒绝策略进行处理; + +如果线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime,线程将被终止,直至线程池中的线程数目不大于 corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过 keepAliveTime,线程也会被终止 + +从它们的具体实现来看,它们实际上也是调用了 ThreadPoolExecutor,只不过参数都已配置好了。 + +newFixedThreadPool 创建的线程池 corePoolSize 和 maximumPoolSize 值是相等的,它使用的 LinkedBlockingQueue; + +newSingleThreadExecutor 将 corePoolSize 和 maximumPoolSize 都设置为 1,也使用的 LinkedBlockingQueue; + +newCachedThreadPool 将 corePoolSize 设置为 0,将 maximumPoolSize 设置为 Integer.MAX_VALUE,使用的 SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过 60 秒,就销毁线程。 + +实际中,如果 Executors 提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置 ThreadPoolExecutor 的参数有点麻烦,要根据实际任务的类型和数量来进行配置。另外,如果 ThreadPoolExecutor 达不到要求,可以自己继承 ThreadPoolExecutor 类进行重写。 + +## 内存屏障 和 volatile + +[参考](https://monkeysayhi.github.io/2017/12/28/一文解决内存屏障/) + +内存屏障是硬件之上、操作系统或 JVM 之下,对并发作出的最后一层支持。再向下是是硬件提供的支持;向上是操作系统或 JVM 对内存屏障作出的各种封装。内存屏障是一种标准,各厂商可能采用不同的实现。 + +内存屏障的实现涉及大量硬件架构层面的知识,又需要操作系统或 JVM 的配合才能发挥威力,单纯从任何一个层面都无法理解。 + +volatile 变量规则:**对 volatile 变量的写入操作必须在对该变量的读操作之前执行**。 + +volatile 变量规则只是一种标准,要求 JVM 实现保证 volatile 变量的偏序语义。**结合程序顺序规则、传递性**,该偏序语义通常表现为两个作用: + +- 保持可见性 +- 禁用重排序(读操作禁止重排序之后的操作,写操作禁止重排序之前的操作) + +通过**volatile 标记,可以解决编译器层面的可见性与重排序问题**。而**内存屏障则解决了硬件层面的可见性与重排序问题**。 diff --git "a/_posts/Tech/java/2018-02-05-Collections\345\267\245\345\205\267\347\261\273.md" "b/_posts/Tech/java/2018-02-05-Collections\345\267\245\345\205\267\347\261\273.md" deleted file mode 100644 index ff21394017..0000000000 --- "a/_posts/Tech/java/2018-02-05-Collections\345\267\245\345\205\267\347\261\273.md" +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: post -category: Java -title: Collections工具类 -tags: Java ---- -[链接](https://www.cnblogs.com/fysola/p/6021134.html) - -## 排序 -void reverse(List list):反转 - -void shuffle(List list),随机排序 - -void sort(List list),按自然排序的升序排序 - -void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑 - -void swap(List list, int i , int j),交换两个索引位置的元素 - -void rotate(List list, int distance),旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。 - -## 查找,替换操作 -int binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的 - -int max(Collection coll),根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll) - -int max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c) - -void fill(List list, Object obj),用元素obj填充list中所有元素 - -int frequency(Collection c, Object o),统计元素出现次数 - -int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target). - -boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。 - -## 同步控制 -Collections中几乎对每个集合都定义了同步控制方法,例如 SynchronizedList(), SynchronizedSet()等方法,来将集合包装成线程安全的集合。 - -## 设置不可变(只读)集合 - -Collections提供了三类方法返回一个不可变集合, - -emptyXXX(),返回一个空的只读集合(这不知用意何在?) - -singleXXX(),返回一个只包含指定对象,只有一个元素,只读的集合。 - -unmodifiablleXXX(),返回指定集合对象的只读视图。 diff --git "a/_posts/Tech/java/2018-02-05-java Arrays\345\267\245\345\205\267\347\261\273.md" "b/_posts/Tech/java/2018-02-05-java Arrays\345\267\245\345\205\267\347\261\273.md" deleted file mode 100644 index db7ecc4d20..0000000000 --- "a/_posts/Tech/java/2018-02-05-java Arrays\345\267\245\345\205\267\347\261\273.md" +++ /dev/null @@ -1,118 +0,0 @@ ---- -layout: post -category: Java -title: Arrays工具类 -tags: Java ---- - -java.util.Arrays类:常用方法如下 - -```java -public static: - int binarySearch(Object[] a,Object key); - void fill(int[] a,int val); - void sort(Object[] a); -``` - -## equals比较 -对比两个数组是否相等 -```java - - @Test - public void equals(){ - String[] array2 = new String[]{"a","c","2","1","b"}; - - //1 对比引用是否相同 - //2 对比是否存在null - //3 对比长度是否相同 - //4 挨个元素对比 - System.out.println(Arrays.equals(array,array2)); - } -``` - -## fill -基于目标元素填充数组 -```java - - @Test - public void fill(){ - Arrays.fill(array,"test"); - System.out.println(Arrays.deepToString(array));//[test, test, test, test, test] - } -``` - -## toString -打印数组元素 -```java - - @Test - public void string(){ - System.out.println(Arrays.toString(array));//[a, c, 2, 1, b] - } -``` - -## copyOf -拷贝数组,第一种用法,如果目标长度不够,会使用0进行补位。第二种用法,支持拷贝目标起始位置到结束为止的数组。 -```java - - @Test - public void copyOf(){ - //如果位数不够,需要补位 - Integer[] result = Arrays.copyOf(ints,10); - for(int i : result){ - System.out.println(i); - } - System.out.println("----------------------------------------->"); - //如果位数够,就取最小的数组 - result = Arrays.copyOf(ints,3); - for(int i : result){ - System.out.println(i); - } - System.out.println("----------------------------------------->"); - // - result = Arrays.copyOfRange(ints,2,4); - for(int i : result){ - System.out.println(i); - } - } -``` - -## binarySearch -查找目标元素所在的位置,注意需要先进行排序。 -```java - - @Test - public void binarySearch(){ - //binarySearch需要保证是排好序的 - System.out.println(Arrays.binarySearch(array,"c"));//-6 - Arrays.sort(array); - System.out.println(Arrays.binarySearch(array,"c"));//4 - } -``` - -## asList -这个方法可以把数组转换成List,List提供了很多的操作方法,更便于使用。 -```java - - @Test - public void test1(){ - List lists = Arrays.asList(array); - } -``` - -## sort排序和parallelSort并行排序 -sort比较常用了,根据元素按照自然排序规则排序,也可以设置排序元素的起始位置。 -```java - - @Test - public void sort(){ - /* Arrays.sort(array); - for(String str : array){ - System.out.println(str); - }*/ - Arrays.sort(array,2,5); - System.out.println(Arrays.deepToString(array));//[a, c, 1, 2, b] - } -``` - -parallelSort则采用并行的排序算法排序.但是我自己测试,可能数据量太小,速度上并没有明显的变化。 \ No newline at end of file diff --git "a/_posts/Tech/java/2018-02-09-JUnit\346\265\213\350\257\225.md" "b/_posts/Tech/java/2018-02-09-JUnit\346\265\213\350\257\225.md" deleted file mode 100644 index fbf56f6367..0000000000 --- "a/_posts/Tech/java/2018-02-09-JUnit\346\265\213\350\257\225.md" +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: post -category: Java -title: JUnit单元测试 -tags: Java ---- - -[链接](https://www.yiibai.com/junit/) - -## JUnit教程列表-基础部分 -1. 单元测试介绍 -2. JUnit测试是什么? -3. Eclipse JUnit简单示例 -4. JUnit注解5.JUnit断言 -6. 使用Eclipse的JUnit实例 -7. 使用@Ignore注解 -8. 创建套件测试 -9. 创建参数测试 -10. Junit规则 -11. 在命令行中运行JUnit测试 - -## JUnit 4.x 教程实例 -1. JUnit基本使用 -介绍JUnit 4,支持基本的注解。 -2. 预期异常测试 -测试异常抛出的方法。 -3. 忽略(Ignore)测试实例 -忽略某些方法的单元测试。 -4. 测试(超时返回)时间 -以确保一定时间后,测试方法将返回。 -5. 套件测试实例 -捆绑几个单元测试用例并运行起来。 -6. 参数化测试实例 -如何通过参数值传递到单元测试。 - -## 常用注解 -@Test -@Before -@BeforeClass -@After -@AfterClass -@Ignore - -## 套件测试 -```java -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ PrepareMyBagTest.class, AddPencilsTest.class }) -public class SuitTest { - -} -``` - -## 参数化测试 -要满足下列所有要求: - -- 该类被注解为 @RunWith(Parameterized.class). -- 如前一节中所说明的, @RunWith 注解让JUnit来调用其中的注释来运行测试类,代替使用内置的JUnit运行器,Parameterized 是一个在JUnit内的运行器将运行相同的测试用例组在不同的输入。 -- 这个类有一个构造函数,存储测试数据。 -- 这个类有一个静态方法生成并返回测试数据,并注明@Parameters注解。 -- 这个类有一个测试,它需要注解@Test到方法。 -```java -import static org.junit.Assert.assertEquals; -import java.util.Arrays; -import java.util.Collection; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -public class CalculateTest { - - private int expected; - private int first; - private int second; - - public CalculateTest(int expectedResult, int firstNumber, int secondNumber) { - this.expected = expectedResult; - this.first = firstNumber; - this.second = secondNumber; - } - - @Parameters - public static Collection addedNumbers() { - return Arrays.asList(new Integer[][] { { 3, 1, 2 }, { 5, 2, 3 }, - { 7, 3, 4 }, { 9, 4, 5 }, }); - } - - @Test - public void sum() { - Calculate add = new Calculate(); - System.out.println("Addition with parameters : " + first + " and " - + second); - assertEquals(expected, add.sum(first, second)); - } -} -``` - -结果如下: - - -```java -Addition with parameters : 1 and 2 -Adding values: 1 + 2 -Addition with parameters : 2 and 3 -Adding values: 2 + 3 -Addition with parameters : 3 and 4 -Adding values: 3 + 4 -Addition with parameters : 4 and 5 -Adding values: 4 + 5 -``` \ No newline at end of file diff --git "a/_posts/Tech/java/2018-05-30-\347\256\227\346\263\225\347\253\236\350\265\233\344\270\255\347\232\204JAVA\344\275\277\347\224\250\347\254\224\350\256\260.md" "b/_posts/Tech/java/2018-05-30-\347\256\227\346\263\225\347\253\236\350\265\233\344\270\255\347\232\204JAVA\344\275\277\347\224\250\347\254\224\350\256\260.md" index a9dc28e603..9aabfb75af 100644 --- "a/_posts/Tech/java/2018-05-30-\347\256\227\346\263\225\347\253\236\350\265\233\344\270\255\347\232\204JAVA\344\275\277\347\224\250\347\254\224\350\256\260.md" +++ "b/_posts/Tech/java/2018-05-30-\347\256\227\346\263\225\347\253\236\350\265\233\344\270\255\347\232\204JAVA\344\275\277\347\224\250\347\254\224\350\256\260.md" @@ -5,8 +5,6 @@ title: 算法竞赛中的JAVA使用笔记 tags: Java --- -[算法竞赛中的JAVA使用笔记](https://blog.csdn.net/hnshhslsh/article/details/53159283) - ## 输入输出 #### 常用的包 diff --git "a/_posts/Tech/java/2018-06-01-\345\214\205\350\243\205\347\261\273\345\236\213.md" "b/_posts/Tech/java/2018-06-01-\345\214\205\350\243\205\347\261\273\345\236\213.md" deleted file mode 100644 index 97a8664c8a..0000000000 --- "a/_posts/Tech/java/2018-06-01-\345\214\205\350\243\205\347\261\273\345\236\213.md" +++ /dev/null @@ -1,104 +0,0 @@ ---- -layout: post -category: Java -title: 包装类型 -tags: Java ---- - -## 基本类型与包装类类型转换 -正向:Integer i=new Integer(100); - -反向:int b=i.intValue(); - -## 包装类型 - -八个基本类型: - -- boolean/1 -- byte/8 -- char/16 -- short/16 -- int/32 -- float/32 -- long/64 -- double/64 - -基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。 - -```java -Integer x = 2; // 装箱 -int y = x; // 拆箱 -``` - -new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。 - -```java -Integer x = new Integer(123); -Integer y = new Integer(123); -System.out.println(x == y); // false -Integer z = Integer.valueOf(123); -Integer k = Integer.valueOf(123); -System.out.println(z == k); // true -``` - -编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。 - -```java -Integer m = 123; -Integer n = 123; -System.out.println(m == n); // true -``` - -valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。 - -```java -public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); -} -``` - -在 Java 8 中,Integer 缓存池的大小默认为 -128\~127。 - -```java -static final int low = -128; -static final int high; -static final Integer cache[]; - -static { - // high value may be configured by property - int h = 127; - String integerCacheHighPropValue = - sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); - if (integerCacheHighPropValue != null) { - try { - int i = parseInt(integerCacheHighPropValue); - i = Math.max(i, 127); - // Maximum array size is Integer.MAX_VALUE - h = Math.min(i, Integer.MAX_VALUE - (-low) -1); - } catch( NumberFormatException nfe) { - // If the property cannot be parsed into an int, ignore it. - } - } - high = h; - - cache = new Integer[(high - low) + 1]; - int j = low; - for(int k = 0; k < cache.length; k++) - cache[k] = new Integer(j++); - - // range [-128, 127] must be interned (JLS7 5.1.7) - assert IntegerCache.high >= 127; -} -``` - -Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些: - -- boolean values true and false -- all byte values -- short values between -128 and 127 -- int values between -128 and 127 -- char in the range \u0000 to \u007F - -因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 diff --git "a/_posts/Tech/java/2018-06-02-\346\240\210\345\256\236\347\216\260.md" "b/_posts/Tech/java/2018-06-02-\346\240\210\345\256\236\347\216\260.md" deleted file mode 100644 index 431a76cd06..0000000000 --- "a/_posts/Tech/java/2018-06-02-\346\240\210\345\256\236\347\216\260.md" +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: post -category: Java -title: 栈的实现 -tags: Java ---- - -## 数组实现 -```java -import javafx.beans.binding.ObjectExpression; - -import java.util.Iterator; - -public class myStack implements Iterable{ - private Item[] a=(Item[])new Object[1];//注意,这是泛型数组的唯一方法,不能有<>的泛型数组 - private int N=0; - public void push(Item item){ - check(); - a[N++]=item; - } - public boolean isEmpty(){ - return N==0; - } - public Item pop() throws Exception{ - if(isEmpty()){ - throw new Exception("stack is empty"); - } - Item item=a[--N]; - check(); - a[N]=null; - return item; - } - public int size(){ - return N; - } - public void resize(int size){ - Item[] temp=(Item[])new Object[size]; - for(int i=0;i=a.length){ - resize(2*a.length); - } - else if(N>0&&a.length iterator(){ - return new myIterator(); - } - private class myIterator implements Iterator{ - private int i=N; - @Override - public Item next() { - return (Item) a[--i]; - } - - @Override - public boolean hasNext() { - return i>0; - } - } - - public static void main(String[] args) throws Exception{ - myStack myStack1=new myStack<>(); - myStack1.push("1"); - myStack1.push("2"); - myStack1.push("3"); - System.out.println(myStack1.pop()); - Iterator iterator=myStack1.iterator(); - - while(iterator.hasNext()){ - System.out.println(iterator.next()); - } - } -} -``` - -## 链表实现 - -头插法 -```java -public class ListStack { - - private Node top = null; - private int N = 0; - - private class Node { - Item item; - Node next; - } - - public boolean isEmpty() { - return N == 0; - } - - public int size() { - return N; - } - - public void push(Item item) { - Node newTop = new Node(); - newTop.item = item; - newTop.next = top; - top = newTop; - N++; - } - - public Item pop() throws Exception { - if (isEmpty()) - throw new Exception("stack is empty"); - Item item = top.item; - top = top.next; - N--; - return item; - } -} -``` diff --git "a/_posts/Tech/java/2018-08-10-jvm\350\207\252\345\212\250\345\206\205\345\255\230\347\256\241\347\220\206.md" "b/_posts/Tech/java/2018-08-10-jvm\350\207\252\345\212\250\345\206\205\345\255\230\347\256\241\347\220\206.md" deleted file mode 100644 index 4684576e4b..0000000000 --- "a/_posts/Tech/java/2018-08-10-jvm\350\207\252\345\212\250\345\206\205\345\255\230\347\256\241\347\220\206.md" +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: post -category: Java -title: jvm自动内存管理 -tags: Java ---- - -Java虚拟机在执行Java程序的过程中会把它所管理的内存划分若干个不同的数据区域。这个区域都各自的用途,以及创建的销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立销毁。Java虚拟机所管理的内存将会包括以下几个运行时数据区域。 - -![](https://github.com/changwensir/study_document/raw/master/upload_img/JVM/%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E5%9F%9F.png) - - 其中程序计数器、虚拟机栈、本地方法栈3个区域会随线程线程而生,随线程而灭,所以这一块上的代码是线程安全的;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。因此这几个 区域的内存分配和回收都具备确定性,在这几个区域就不需要过多考虑回收的问题,因为方法结束或者线程时,内存自然就跟随着回收了。 - - -## 虚拟机栈(Virtual Machine Stacks) -与程序计数器一样,也是线程私有的,其生命周期和线程一样,每个Java线程有一个虚拟机栈。平常我们讲的“栈内存”就是虚拟机栈,或者说是虚拟机栈中局部变量表部分。 - -作用: 虚拟机栈描述的是Java方法执行的内存模型,即:每个方法在执行的时候都会创建一个栈帧(Stack Frame),栈帧中存储: - - 1).局部变量表 -  存放了编译期就可知的:各种基本数据类型(8个基本数据类型)、对象引用(reference类型)、returnAddress类型(指向一条字节码指令地址) -  其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存大小在编译期就完成了分配,也就是说当进入一个方法时,此方法需要在栈帧中分配多大的局部变量表空间时完全确定的,运行期不会改变 - - 2).操作数栈 - - 3).动态链接 - - 4).方法出口等 - -  方法从调用到执行完成的过程,就对应了,一个栈帧在虚拟机栈中的入栈和出栈的过程 -有两种异常: - -  1).如果线程请求的栈深度大于JVM所允许的深度,将抛出StackOverflowError异常 - -  2).如果栈扩展时无法申请到足够的内存,将抛出OutOfMemoryError(OOM)异常 - -## Java堆(Java Heap) - -根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只在逻辑上是连续的即可。对于大多数应用来说,Java堆是虚拟机管理的内存中最大的一块。是被所有线程共享的一块区域,在虚拟机启动时创建,通过参数“-Xmx和-Xms”控制。 - -此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(当然也有例外):对象实例以及对应的实例变量、数组。 -Java堆是垃圾回收器管理的主要区域,因此很多时候也被称做“GC堆”。 - -由于现代GC基本都采用分带收集算法,所以Java堆还可以细分为: - -   ①.新生代 -   ②.老年代 - -再细分一下还可分为: - -   ①.Eden空间 -   ②.From Survivor空间 -   ③.To Survivor空间 - -![](https://github.com/changwensir/study_document/raw/master/upload_img/JVM/%E6%96%B0%E8%80%81%E5%B9%B4%E4%BB%A3.png) - -从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。 - -默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ), - -默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ) - -JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。 - -### GC回收动作 - -Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。 - -### Minor GC -Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。 - -当一个对象被判定为 “死亡” 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。 - -当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。 - -但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。 - -### Full GC -Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。 - -现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 - -另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。 - diff --git "a/_posts/Tech/java/2018-08-25-java\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/_posts/Tech/java/2018-08-25-java\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" deleted file mode 100644 index 38ab086cb2..0000000000 --- "a/_posts/Tech/java/2018-08-25-java\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: post -category: Java -title: java正则表达式 -tags: Java ---- - -## 关于反斜杠 -在正则表达式中的“\”表示和后面紧跟着的那个字符构成一个转义字符,代表着特殊的意义;所以如果你要在正则表达式中表示一个反斜杠\,应当写成“\\”。因为在正则要经过两次转义,因此就需要四个反斜杠才可以匹配一个反斜杠。Java先转义成“\”,在由正则进行一次转义,就结果就为“\”。 - -一句话:表示正则表达式里面的斜杠“\”,然后再用字符串表示出来。而这2个斜杠分别需要一个转义符,这样就成了4个斜杠在正则表达式里面表示一个斜杠。 - -也就是说正则表达式用的是转义后的字符串,就像sout输出那样,所以正则表达式使用转义之后的。 - -## 匹配规则 -主要如下 - -``` -[] : 字符集合 -() : 分组 -? : 重复 0 ~ 1 -+ : 重复 1 ~ n -* : 重复 0 ~ n -. : 任意字符 -\\. : 转义后的 . -\\d : 数字 -``` - -.代表任意字符,所以要用.这个本身要进行转义,所以需要两个反斜杠 - -## 比如匹配整数 -```java -public class Solution { - public boolean isNumeric(char[] str) { - return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); - } - - public static void main(String[] args) { - Solution solution=new Solution(); - boolean res= solution.isNumeric("3.14".toCharArray()); - System.out.println(res); - System.out.println(5e+2); - } -} -``` - diff --git "a/_posts/Tech/java/2018-10-12-Java\344\270\255System.arraycopy()\345\222\214Arrays.copyOf().md" "b/_posts/Tech/java/2018-10-12-Java\344\270\255System.arraycopy()\345\222\214Arrays.copyOf().md" deleted file mode 100644 index b5ee67dd35..0000000000 --- "a/_posts/Tech/java/2018-10-12-Java\344\270\255System.arraycopy()\345\222\214Arrays.copyOf().md" +++ /dev/null @@ -1,62 +0,0 @@ ---- -layout: post -category: Java -title: Java中System.arraycopy()和Arrays.copyOf() -tags: Java ---- - -## System.arraycopy() - -```java -public static native void arraycopy(Object src,int srcPos, Object dest, int destPos,int length); - - - int[] ids = {1, 2, 3, 4, 5}; - - // 1、测试复制到别的数组上 - // 将ids数组的索引从0开始其后5个数,复制到ids2数组的索引从0开始 - int[] ids2 = new int[5]; - System.arraycopy(ids, 0, ids2, 0, 5); - - -``` - - src - 源数组。 - srcPos - 源数组中的起始位置。 - dest - 目标数组。 - destPos - 目标数据中的起始位置。 - length - 要复制的数组元素的数量。 - -该方法用了native关键字,说明调用的是其他语言写的底层函数。 - -## Arrays.copyOf() - -```java -//复杂数据类型 -public static T[] copyOf(U[] original, int newLength, Class newType) { - T[] copy = ((Object)newType == (Object)Object[].class) - ? (T[]) new Object[newLength] - : (T[]) Array.newInstance(newType.getComponentType(), newLength); - System.arraycopy(original, 0, copy, 0, - Math.min(original.length, newLength)); - return copy; - } -public static T[] copyOf(T[] original, int newLength) { - return (T[]) copyOf(original, newLength, original.getClass()); -} - -``` - - original - 要复制的数组 - newLength - 要返回的副本的长度 - newType - 要返回的副本的类型 - -仔细观察发现,copyOf()内部调用了System.arraycopy()方法 - - -区别在于: - -1. arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置 -2. copyOf()是系统自动在内部新建一个数组,调用arraycopy()将original内容复制到copy中去,并且长度为newLength。返回copy; 即将原数组拷贝到一个长度为newLength的新数组中,并返回该数组。 - -Array.copyOf()可以看作是受限的System.arraycopy(),它主要是用来将原数组全部拷贝到一个新长度的数组,适用于数组扩容。 \ No newline at end of file diff --git a/_posts/Tech/java/2018-10-14-Java 8 Lambda.md b/_posts/Tech/java/2018-10-14-Java 8 Lambda.md index eece484e98..58a858a386 100644 --- a/_posts/Tech/java/2018-10-14-Java 8 Lambda.md +++ b/_posts/Tech/java/2018-10-14-Java 8 Lambda.md @@ -5,8 +5,8 @@ title: Java 8 Lambda tags: Java --- -## Lambda表达式的组成及使用 -### Lambda表达式是什么? +# Lambda表达式的组成及使用 +## Lambda表达式是什么? 可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。 - 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多! @@ -14,15 +14,18 @@ tags: Java - 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。 - 简洁——无需像匿名类那样写很多模板代码。 -### Lambda表达式的语法与组成 +## Lambda表达式的语法与组成 -Lambda表达式由参数、箭头、主体组成。如下图: +Lambda表达式由参数、箭头、主体组成。 -![](http://img.hao124.net/c25961a4a7f8330c30c9774689d32bce.PNG) +```ruby +(lambda paramters) -> lambda expression; -- 参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。 -- 箭头——箭头->把参数列表与Lambda主体分隔开。 -- Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。 +小括号():代表方法签名,当只有一个参数的时候,()可以省略 +lambda paramters:代表具体形参,参数可以指定类型也可以省略类型,因为Lambda表达式会自动推断出参数类型 +->:代表lambda操作符 +lambda expression:代表lambda表达式body体,具体的函数式接口唯一方法实现逻辑。当body体只有一行代码的时候,{}和return都可以省略 +``` 所以,Lambda表达式的基本语法可以总结为: ```(parameters) -> expression 或 (parameters) -> { statements; }``` @@ -50,6 +53,14 @@ BinaryOperator add = (Long x, Long y) -> x + y;// 4 BinaryOperator addImplicit = (x, y) -> x + y;// 5 类型推断 ``` + + +错误示例 + +- 参数类型要么全部省略,不能省略部分,如 (x, int y) -> x+y; +- 参数不能使用final修饰,如(final a)->a; +- 函数表达式接口不能返回一个Object对象,如Object obj = () -> "lambda"; + ## 1.替代匿名内部类 毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类! @@ -148,4 +159,111 @@ filter也是我们经常使用的一个操作。在操作集合的时候,经 System.out.println("\nLanguage length bigger three: "); filterTest(languages,x -> x.length() > 4); } -``` \ No newline at end of file +``` + + + +# 进阶 + +## 函数式接口 + +有且只有一个抽象方法(可以包含default或static方法,但Object类除外)的接口是函数式接口。@FunctionlInterface就是用来指定某个接口必须是函数式接口。@FunctionalInterface不是必须的,只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。@FunctionalInterface主要是帮助程序员避免一些低级错误,比如多个抽象方法。 + + + +如定义了一个函数式接口如下: + +``` +@FunctionalInterface +interface GreetingService +{ + void sayMessage(String message); +} +``` + +那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的): + +``` +GreetingService greetService1 = message -> System.out.println("Hello " + message); +``` + + + +例子 + +![image.png](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202207282325504.png) + +常用函数式接口 + +![image.png](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202207282324717.png) + +![image.png](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202207282324583.png) + + + +## 方法引用 + +**什么是方法引用?** +Lambda还有一个非常重要的功能,就是方法引用。方法引用可以理解为lambda表达式的简便写法。方法引用是用来直接访问类或者实例的已经存在的方法或构造方法(函数),它比lambda表达式更加的简洁,更高的可读性,更好的复用性。 + + + +**方法引用的语法** + +```autohotkey +类名(或实例)::方法名 +``` + +------ + +**方法引用的分类** + +| 方法引用类型 | 语法 | Lambda表达式 | +| ------------ | -------------------- | --------------------------------------- | +| 静态方法引用 | 类名::staticMethod | (args)->类名.staticMethod(args) | +| 实例方法引用 | instance::instMethod | (args)->instance::instMethod(args) | +| 对象方法引用 | 类名::instMethod | (instance,args)->类名::instMethod(args) | +| 构造方法引用 | 类名::new | (args)->new 类名(args) | + + + +学会了lambda后已经会了如下写法 + +```java + Consumer c1 = (name) -> LambdaStaticMethodTest.setName(name); + Consumer c2 = name -> LambdaStaticMethodTest.setName(name); + Consumer c3 = (name) -> LambdaStaticMethodTest.queryName(name); + Consumer c4 = name -> LambdaStaticMethodTest.queryName(name); + +``` + +但还可以再见过做替代,可用如下替代 + +```java + Consumer c5 = LambdaStaticMethodTest::setName; + Consumer c6 = LambdaStaticMethodTest::queryName; +``` + +原理在于就一个函数,已经知道参数是啥了,反正也是自己新起的名字,这样直接不如不起了。 + +```java + Function f1 = name -> LambdaStaticMethodTest.length(name); + /** + * 将f1改写为静态方法引用,只需要写类名和方法名即可,简洁了很多 + */ + Function f2 = LambdaStaticMethodTest::length; +``` + +有参数的话,可自动识别,不用管。 + +有返回值的话,可自动识别,不用管。 + +[各种情况参考,实际就是不用管](https://segmentfault.com/a/1190000039404435) + +# 参考 + +https://segmentfault.com/a/1190000039393723 + + + +https://segmentfault.com/a/1190000039404435 \ No newline at end of file diff --git "a/_posts/Tech/java/2018-10-16-java\347\272\277\347\250\213\346\261\240.md" "b/_posts/Tech/java/2018-10-16-java\347\272\277\347\250\213\346\261\240.md" deleted file mode 100644 index 772f2306d7..0000000000 --- "a/_posts/Tech/java/2018-10-16-java\347\272\277\347\250\213\346\261\240.md" +++ /dev/null @@ -1,139 +0,0 @@ ---- -layout: post -category: Java -title: java线程池 -tags: Java ---- - -[线程池的粗浅使用](https://www.jianshu.com/p/edd7cb4eafa0) - -[线程池的分析](http://ifeve.com/java-threadpool/) - -[线程池分析更好些](https://www.cnblogs.com/absfree/p/5357118.html) - -合理利用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。 - -## 创建线程池 -一般通过工具类Executors的静态方法来获取线程池或静态方法。介绍四种常用创建方法 - -单例线程,表示在任意的时间段内,线程池中只有一个线程在工作 -```java -ExecutorService service1 = Executors.newSingleThreadExecutor(); -``` - -缓存线程池,先查看线程池中是否有当前执行线程的缓存,如果有就resue(复用),如果没有,那么需要创建一个线程来完成当前的调用.并且这类线程池只能完成一些生存期很短的一些任务.并且这类线程池内部规定能resue(复用)的线程,空闲的时间不能超过60s,一旦超过了60s,就会被移出线程池 - -```java -ExecutorService service2 = Executors.newCacheThreadPool(); -``` - -固定型线程池,和newCacheThreadPool()差不多,也能够实现resue(复用),但是这个池子规定了线程的最大数量,也就是说当池子有空闲时,那么新的任务将会在空闲线程中被执行,一旦线程池内的线程都在进行工作,那么新的任务就必须等待线程池有空闲的时候才能够进入线程池,其他的任务继续排队等待.这类池子没有规定其空闲的时间到底有多长.这一类的池子更适用于服务器. -```java -ExecutorService service3 = Executors.newFixedThreadPool(10); -``` - -调度型线程池,调度型线程池会根据Scheduled(任务列表)进行延迟执行,或者是进行周期性的执行.适用于一些周期性的工作. - -```java -public class Test { - public static void main(String[] args) { - ExecutorService service = Executors.newCachedThreadPool(); - service.submit(new Runnable() { - @Override - public void run() { - while(true){ - System.out.println("hello world !"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - }); - System.out.println(" ===> main Thread execute here ! " ); - } -} - -``` - -```java -ExecutorService service4 = Executors.newScheduledThreadPool(10); -``` - - - -## 线程池任务创建与提交 -任务分为两种:一种是有返回值的( callable ),一种是没有返回值的( runnable ). Callable与 Future 两功能是Java在后续版本中为了适应多并法才加入的,Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。 - -1. 无返回值的任务就是一个实现了runnable接口的类.使用run方法. -1. 有返回值的任务是一个实现了callable接口的类.使用call方法. - -Callable和Runnable的区别如下: - -1. Callable定义的方法是call,而Runnable定义的方法是run。 -1. Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。 -1. Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。 - -execute与submit区别: - -1. 接收的参数不一样 -1. submit有返回值,而execute没有 -1. submit方便Exception处理 -1. execute是Executor接口中唯一定义的方法;submit是ExecutorService(该接口继承Executor)中定义的方法 - -## 线程池的关闭 -我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。 - -只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。 - -## 线程池的分析 - -### 线程池创建分析 - -```java -new ThreadPoolExecutor(corePoolSize, maximumPoolSize, -keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler); -``` - -创建一个线程池需要输入几个参数: - -- corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; -- runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。 -1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 -1. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。 -1. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 -1. PriorityBlockingQueue:一个具有优先级得无限阻塞队列。 -- maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。 -- ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。 -- RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 -1. AbortPolicy:直接抛出异常 -1. CallerRunsPolicy:只用调用者所在线程来运行任务。 -1. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 -1. DiscardPolicy:不处理,丢弃掉。 - -当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。 -- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 -- TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。 - -![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@master/images/9a70374b792298b693aeafa95e1bd07d.jpeg) - -从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下: - -1. 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务; -1. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务; -1. 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理; - -如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止 - - -从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。 - -newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue; - -newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue; - -newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。 - -实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。 - diff --git a/_posts/Tech/java/2018-10-23-java.util.Objects.md b/_posts/Tech/java/2018-10-23-java.util.Objects.md deleted file mode 100644 index e93974e7f9..0000000000 --- a/_posts/Tech/java/2018-10-23-java.util.Objects.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -layout: post -category: Java -title: java.util.Objects -tags: Java ---- - -## Objects 与 Object 区别 -Object 是 Java 中所有类的基类,位于java.lang包。 - -Objects 是 Object 的工具类,位于java.util包。它从jdk1.7开始才出现,被final修饰不能被继承,拥有私有的构造函数。 -它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。 - -## Objects 各方法介绍与分析 -### equals - -equals方法是判断两个对象是否相等。 - -在比较两个对象的时候,Object.equals方法容易抛出空指针异常。 - -——我刚上班的时候,有位老员工教我“字符串常量与变量对象比较的时候,常量要写在equals外边,变量放在equals()括号里边。” 就是这个原因。 - -如果是两个变量比较的时候,就都需要加非空判断。 - -Object.equals方法内调用的是return (this == obj)。String类中是依据字符串内容是否相等来重定义了equals方法。 - -现在,Objects.equals方法中已经做了非空判断,所以不会抛出空指针异常,它是null-save空指针安全的,而且也可以简化代码。 - -```java - public static boolean equals(Object a, Object b) { - return (a == b) || (a != null && a.equals(b)); - } -``` - -### deepEquals -顾名思义,深度比较两个对象。 - -当参数是数组对象,其方法内部采用的是Arrays.deepEquals()方法的算法。 - -使用Objects.deepEquals方法有个好处,当我们在写业务代码时,可以直接使用此方法来判断两个复杂类型, - -比如使用了泛型的列表对象```List```、或者通过反射得到的对象,不清楚对象的具体类型。 - -```java -public static boolean deepEquals(Object a, Object b) { - if (a == b) - return true; - else if (a == null || b == null) - return false; - else - return Arrays.deepEquals0(a, b); - } -``` - -简短的说明下Arrays.deepEquals0方法: - -- 如果参数是Object类型的数组,则调用Arrays.deepEquals方法,在参数数组的循环中,递归调用deepEquals0,直到出现不相同的元素,或者循环结束; -- 如果参数是基本类型的数组,则根据该类型调用Arrays.equals方法。Arrays工具类依照八种基本类型对equals方法做了重载。 - -### hashCode -```java - public static int hashCode(Object o) { - return o != null ? o.hashCode() : 0; - } -``` - -### hash -为一系列的输入值生成哈希码,该方法的参数是可变参数。 -源码如下: -```java - public static int hash(Object... values) { - return Arrays.hashCode(values); - } -``` - -它是将所有的输入值都放到一个数组,然后调用Arrays.hashCode(Object[])方法来实现哈希码的生成。 - -对于当一个对象包含多个成员,重写Object.hashCode方法时,hash方法非常有用。 -举个Java源码中的例子: - -java.lang.invoke.MemberName 类,该类有Class clazz、String name、Object type、int flags、Object resoulution这几个成员变量, - -该类的hashCode方法如下: -```java - @Override - public int hashCode() { - return Objects.hash(clazz, getReferenceKind(), name, getType()); - } -``` - diff --git "a/_posts/Tech/java/2018-10-23-\346\267\261\345\205\245\347\220\206\350\247\243Class\345\257\271\350\261\241.md" "b/_posts/Tech/java/2018-10-23-\346\267\261\345\205\245\347\220\206\350\247\243Class\345\257\271\350\261\241.md" deleted file mode 100644 index 1c9b016ff0..0000000000 --- "a/_posts/Tech/java/2018-10-23-\346\267\261\345\205\245\347\220\206\350\247\243Class\345\257\271\350\261\241.md" +++ /dev/null @@ -1,416 +0,0 @@ ---- -layout: post -category: Java -title: 深入理解Class对象 -tags: Java ---- - -## RRTI的概念以及Class对象作用 -认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下: - -```java -public final class Class implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement { - private static final int ANNOTATION= 0x00002000; - private static final int ENUM = 0x00004000; - private static final int SYNTHETIC = 0x00001000; - - private static native void registerNatives(); - static { - registerNatives(); - } - - /* - * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类) - * This constructor is not used and prevents the default constructor being - * generated. - */ - private Class(ClassLoader loader) { - // Initialize final field for classLoader. The initialization value of non-null - // prevents future JIT optimizations from assuming this final field is null. - classLoader = loader; - } -``` - -Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图): - -到这我们也就可以得出以下几点信息: - -1. Class类也是类的一种,与class关键字是不一样的。 -2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。 -3. 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。 -4. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载 -5. Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。 - -## Class对象的加载及其获取方式 -### Class对象的加载 -前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题(例子引用自Thinking in Java): -```java -package com.zejian; - -class Candy { - static { System.out.println("Loading Candy"); } -} - -class Gum { - static { System.out.println("Loading Gum"); } -} - -class Cookie { - static { System.out.println("Loading Cookie"); } -} - -public class SweetShop { - public static void print(Object obj) { - System.out.println(obj); - } - public static void main(String[] args) { - print("inside main"); - new Candy(); - print("After creating Candy"); - try { - Class.forName("com.zejian.Gum"); - } catch(ClassNotFoundException e) { - print("Couldn't find Gum"); - } - print("After Class.forName(\"com.zejian.Gum\")"); - new Cookie(); - print("After creating Cookie"); - } -} -``` - -在上述代码中,每个类Candy、Gum、Cookie都存在一个static语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果: - -``` -inside main -Loading Candy -After creating Candy -Loading Gum -After Class.forName("com.zejian.Gum") -Loading Cookie -After creating Cookie - -Process finished with exit code 0 -``` - -从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象肯定会被加载,毕竟Candy实例对象的创建依据其Class对象。比较有意思的是 -```java -Class.forName("com.zejian.Gum"); -``` - -其中forName方法是Class类的一个static成员方法,记住所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会导致Gum类被加载(前提是Gum类从来没有被加载过)。 - -### Class.forName方法 -通过上述的案例,我们也就知道Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象,如下的第2种方式是通过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。 - -```java -public static void main(String[] args) { - - try{ - //通过Class.forName获取Gum类的Class对象 - Class clazz=Class.forName("com.zejian.Gum"); - System.out.println("forName=clazz:"+clazz.getName()); - }catch (ClassNotFoundException e){ - e.printStackTrace(); - } - - //通过实例对象获取Gum的Class对象 - Gum gum = new Gum(); - Class clazz2=gum.getClass(); - System.out.println("new=clazz2:"+clazz2.getName()); - - } -``` -注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。 - -### Class字面常量 -在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下: - -```java -//字面常量的方式获取Class对象 -Class clazz = Gum.class; -``` - -这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。 -```java -boolean.class = Boolean.TYPE; -char.class = Character.TYPE; -byte.class = Byte.TYPE; -short.class = Short.TYPE; -int.class = Integer.TYPE; -long.class = Long.TYPE; -float.class = Float.TYPE; -double.class = Double.TYPE; -void.class = Void.TYPE; -``` - -前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下: - -1. 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象 -1. 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例1. 成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。 -1. 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。 - -由此可知,我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程: - -```java -import java.util.*; - -class Initable { - //编译期静态常量 - static final int staticFinal = 47; - //非编期静态常量 - static final int staticFinal2 = - ClassInitialization.rand.nextInt(1000); - static { - System.out.println("Initializing Initable"); - } -} - -class Initable2 { - //静态成员变量 - static int staticNonFinal = 147; - static { - System.out.println("Initializing Initable2"); - } -} - -class Initable3 { - //静态成员变量 - static int staticNonFinal = 74; - static { - System.out.println("Initializing Initable3"); - } -} - -public class ClassInitialization { - public static Random rand = new Random(47); - public static void main(String[] args) throws Exception { - //字面常量获取方式获取Class对象 - Class initable = Initable.class; - System.out.println("After creating Initable ref"); - //不触发类初始化 - System.out.println(Initable.staticFinal); - //会触发类初始化 - System.out.println(Initable.staticFinal2); - //会触发类初始化 - System.out.println(Initable2.staticNonFinal); - //forName方法获取Class对象 - Class initable3 = Class.forName("Initable3"); - System.out.println("After creating Initable3 ref"); - System.out.println(Initable3.staticNonFinal); - } -} -``` -执行结果: - -``` -After creating Initable ref -47 -Initializing Initable -258 -Initializing Initable2 -147 -Initializing Initable3 -After creating Initable3 ref -74 -``` - -从输出结果来看,可以发现,通过字面常量获取方式获取Initable类的Class对象并没有触发Initable类的初始化,这点也验证了前面的分析,同时发现调用Initable.staticFinal变量时也没有触发初始化,这是因为staticFinal属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量staticFinal存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量staticFinal的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因。但在之后调用了Initable.staticFinal2变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能确定,因此staticFinal2并不是编译期常量,使用该变量必须先初始化Initable类。Initable2和Initable3类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于forName方法获取Class对象,肯定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,ok~,到此这里可以得出小结论: - -- 获取Class对象引用的方式3种,通过继承自Object类的getClass方法,Class类的静态方法forName以及字面常量的方式”.class”。 -- 其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。 -- 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。 - -1. 关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化: -1. 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。 -1. 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。 -1. 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。 -1. 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类 - -当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住) - -### 理解泛化的Class对象引用 -由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。如下: - -```java -/** - * Created by zejian on 2017/4/30. - * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] - */ -public class ClazzDemo { - - public static void main(String[] args){ - //没有泛型 - Class intClass = int.class; - - //带泛型的Class对象 - Class integerClass = int.class; - - integerClass = Integer.class; - - //没有泛型的约束,可以随意赋值 - intClass= double.class; - - //编译期错误,无法编译通过 - //integerClass = double.class - } -} -``` - -从代码可以看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上Integer.class就是一个```Class```类的对象。面对下述语句,确实可能令人困惑,但该语句确实是无法编译通过的。 -```java -//编译无法通过 -Class numberClass=Integer.class; -``` - -我们或许会想Integer不就是Number的子类吗?然而事实并非这般简单,毕竟Integer的Class对象并非Number的Class对象的子类,前面提到过,所有的Class对象都只来源于Class类,看来事实确实如此。当然我们可以利用通配符“?”来解决问题: -```java -Class intClass = int.class; -intClass = double.class; -``` - -这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用```Class```呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此```Class```总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题: -```java -//编译通过! -Class clazz = Integer.class; -//赋予其他类型 -clazz = double.class; -clazz = Number.class; -``` - -上述的代码是行得通的,extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用Class是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。 - -### 关于类型转换的问题 -在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型: - -```java -package com.zejian; - -/** - * Created by zejian on 2017/4/30. - * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] - */ -public class ClassCast { - - public void cast(){ - - Animal animal= new Dog(); - //强制转换 - Dog dog = (Dog) animal; - } -} - -interface Animal{ } - -class Dog implements Animal{ } -``` - -之所可以强制转换,这得归功于RRTI,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。除了强制转换外,在Java SE5中新增一种使用Class对象进行类型转换的方式,如下: - -```java -Animal animal= new Dog(); -//这两句等同于Dog dog = (Dog) animal; -Class dogType = Dog.class; -Dog dog = dogType.cast(animal) -``` - -利用Class对象的cast方法,其参数接收一个参数对象并将其转换为Class引用的类型。这种方式似乎比之前的强制转换更麻烦些,确实如此,而且当类型不能正确转换时,仍然会抛出ClassCastException异常。源码如下: - -```java -public T cast(Object obj) { - if (obj != null && !isInstance(obj)) - throw new ClassCastException(cannotCastMsg(obj)); - return (T) obj; - } -``` - -### instanceof 关键字与isInstance方法 -关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException) -```java -public void cast2(Object obj){ - if(obj instanceof Animal){ - Animal animal= (Animal) obj; - } -} -``` - -而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子: -```java - -public void cast2(Object obj){ - //instanceof关键字 - if(obj instanceof Animal){ - Animal animal= (Animal) obj; - } - - //isInstance方法 - if(Animal.class.isInstance(obj)){ - Animal animal= (Animal) obj; - } - } -``` - -事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceOf是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。一般形式: -```java -//判断这个对象是不是这种类型 -obj.instanceof(class) -``` -而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false;一般形式如下: -```java -//判断这个对象能不能被转化为这个类 -class.inInstance(obj) -``` - -最后这里给出一个简单实例,验证isInstance方法与instanceof等价性: - -```java -class A {} - -class B extends A {} - -public class C { - static void test(Object x) { - print("Testing x of type " + x.getClass()); - print("x instanceof A " + (x instanceof A)); - print("x instanceof B "+ (x instanceof B)); - print("A.isInstance(x) "+ A.class.isInstance(x)); - print("B.isInstance(x) " + - B.class.isInstance(x)); - print("x.getClass() == A.class " + - (x.getClass() == A.class)); - print("x.getClass() == B.class " + - (x.getClass() == B.class)); - print("x.getClass().equals(A.class)) "+ - (x.getClass().equals(A.class))); - print("x.getClass().equals(B.class)) " + - (x.getClass().equals(B.class))); - } - public static void main(String[] args) { - test(new A()); - test(new B()); - } -} -``` - -执行结果: -``` -Testing x of type class com.zejian.A -x instanceof A true -x instanceof B false //父类不一定是子类的某个类型 -A.isInstance(x) true -B.isInstance(x) false -x.getClass() == A.class true -x.getClass() == B.class false -x.getClass().equals(A.class)) true -x.getClass().equals(B.class)) false ---------------------------------------------- -Testing x of type class com.zejian.B -x instanceof A true -x instanceof B true -A.isInstance(x) true -B.isInstance(x) true -x.getClass() == A.class false -x.getClass() == B.class true -x.getClass().equals(A.class)) false -x.getClass().equals(B.class)) true -``` diff --git "a/_posts/Tech/python/2018-05-19-python\347\254\224\350\256\260.md" "b/_posts/Tech/python/2018-05-19-python\347\254\224\350\256\260.md" index 5ad4af4e09..19b4723641 100644 --- "a/_posts/Tech/python/2018-05-19-python\347\254\224\350\256\260.md" +++ "b/_posts/Tech/python/2018-05-19-python\347\254\224\350\256\260.md" @@ -88,6 +88,15 @@ s.islower() +字符串变量拼接 f开头, {}引用变量 + +```python + f'My name is {name}, my age next year is {age+1}, my anniversary is {anniversary:%A, %B %d, %Y}.' +'My name is Fred, my age next year is 51, my anniversary is Saturday, October 12, 1991.' +``` + + + ### List(列表) 0开头,-1结尾, 也可以list[start:end:span] @@ -493,11 +502,9 @@ with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f: 因为a//b是向下取整,这块在求%的时候也有问题,因为求模是r=a-n*[a//n] -## global和nonlocal - -https://blog.csdn.net/xCyansun/article/details/79672634?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.pc_relevant_is_cache&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.pc_relevant_is_cache - +### global和nonlocal +https://www.programiz.com/python-programming/global-local-nonlocal-variables 全局变量update需要global @@ -998,6 +1005,23 @@ b | 10 | {:x<4d} | 10xx | 数字补x (填充右边, 宽度为4) | | 1000000 | {:,} | 1,000,000 | 以逗号分隔的数字格式 | +### range + +`Range(10**9) `也没关系,因为range的时间复杂度是1,可以o(1)时间复杂度计算出对应的值。如果是`list(range(xx))`这复杂度就是O(xx)了。 len(range)的时间复杂度也是o(1) + +### 二分bisect + +```python + import bisect + bisect.bisect_left + bisect.bisect_right + # range时间复杂度不用list转化的时间复杂度是o(1) 包括取任意位置的值和len + # 如果想找个某个位置为True,可以传key函数并且找True的值。 + +``` + + + ### python负数补码 > [参考](https://www.runoob.com/w3cnote/python-negative-storage.html) diff --git a/_posts/Tech/python/2019-05-30-pip.md b/_posts/Tech/python/2019-05-30-pip.md deleted file mode 100644 index 6d85259733..0000000000 --- a/_posts/Tech/python/2019-05-30-pip.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: post -category: Python -title: pip -tags: Python ---- - -## pip -[参考](https://www.runoob.com/w3cnote/python-pip-install-usage.html) - -### 使用教程 - - -指定某个模块安装 -``` -sudo pip2 install 模块名 或 python2 -m pip install 模块名 -``` - - -``` -pip --version -pip install -U pip # pip升级 -``` - -安装Pip3: - -```shell -sudo apt install python3-pip -``` - -更新pip3 - -```shell -sudo pip3 install --upgrade pip -``` - -pip源修改 - -```bash -# 清华源 -pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple - -# 或: -# 阿里源 -pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ -# 腾讯源 -pip config set global.index-url http://mirrors.cloud.tencent.com/pypi/simple -# 豆瓣源 -pip config set global.index-url http://pypi.douban.com/simple/ -``` - -### requirements - -#### 导出的的两种方式 - -第一种适用于 **单虚拟环境的情况:为什么只适用于单虚拟环境?因为这种方式,会将环境中的依赖包全都加入,如果使用的全局环境,则下载的所有包都会在里面,不管是不时当前项目依赖的** - -```shell -pip freeze > requirements.txt -``` - -第二种 **(推荐)** 使用 `pipreqs` - -```shell -# 安装 -pip install pipreqs -# 在当前目录生成 -pipreqs . ``-``-``encoding``=``utf8 ``-``-``force -``` - -注意 `--encoding=utf8` 为使用utf8编码,不然可能会报UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 406: illegal multibyte sequence 的错误。 - -`--force` 强制执行,当 生成目录下的requirements.txt存在时覆盖。 - -#### 使用requirements.txt安装依赖 - -``` -pip install ``-``r requirements.txt -``` \ No newline at end of file diff --git "a/_posts/Tech/python/2020-11-11-\350\231\232\346\213\237\347\216\257\345\242\203venv.md" "b/_posts/Tech/python/2020-11-11-\350\231\232\346\213\237\347\216\257\345\242\203venv.md" deleted file mode 100644 index 4f118ca09d..0000000000 --- "a/_posts/Tech/python/2020-11-11-\350\231\232\346\213\237\347\216\257\345\242\203venv.md" +++ /dev/null @@ -1,19 +0,0 @@ ---- -layout: post -category: Python -title: 虚拟环境venv -tags: Python ---- - -## 虚拟环境venv -[参考](https://docs.python.org/zh-cn/3/library/venv.html#creating-virtual-environments) - - -创建虚拟环境 - python3 -m venv abc - -激活虚拟环境: 此时对python相关命令路由了一次 - abc/bin/activate - -退出虚拟环境 - deactivate diff --git "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index c9ebb380ab..0000000000 --- "a/_posts/Tech/\351\235\242\345\220\221\345\257\271\350\261\241/2020-12-26-\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: post -category: 面向对象 -title: 设计模式 -tags: 面向对象 ---- - -## 设计模式 - -## 创建型 - -- 单例Singleton: 饿汉式(预先加载), 懒汉式(用到再加载), 双重校验锁 -- 工厂Factory:统一方法创建对象,有工厂方法(子类创建)、抽象工厂(创建家族)、简单工厂(单独一个工具类创建) -- 生成器Buildere: 类似string builder -- 原型ProtoType:.copy()方法, 深拷贝 - -## 行为型 - -- 责任链Responsibility Chain: 类似中间件middleware, 挨个处理,可提前返回 -- 命令Command:swing的action,有回调 -- 解释器Interpreter: 比如formater, 有词法分析 -- 迭代器Iterator: 遍历 -- 中介者Mediator: 集中通知和控制 -- 备忘录Memento:拷贝一份Origin -- 观察者Observer: 回调更新 -- 状态State:状态机 -- 策略Strategy: 内置几种算法,可以选择,比如filter -- 模板方法Template Method: 新子类自定义实现某函数,比如sort - -## 结构型 - -- 适配器Adapter: 比如Array的asList -- 桥接Bridge: 将抽象与实现分离开来 -- 组合Composite: 整体/部分 -- 装饰Decorator: 给类动态添加功能 -- 外观Facade: 统一接口来访问子系统 -- 享元FlyWeight: 利用共享的方式来支持大量细粒度的对象 -- 代理Proxy: 控制其他对象的访问 - -## Reference - -- [Java设计模式](http://www.cyc2018.xyz/%E5%85%B6%E5%AE%83/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20%E7%9B%AE%E5%BD%95.html#%E4%B8%80%E3%80%81%E5%89%8D%E8%A8%80) - diff --git a/assets/css/sections/repo-list.css b/assets/css/sections/repo-list.css index 9f7277603c..0863ff3574 100755 --- a/assets/css/sections/repo-list.css +++ b/assets/css/sections/repo-list.css @@ -1 +1,2 @@ .repo-list-name,.repo-list-name .prefix,.repo-list-name .slash{font-weight:400}.repo-list{position:relative;padding-left:0}.repo-list .participation-graph{position:absolute;right:0;bottom:0;left:0;z-index:-1}.repo-list .participation-graph.disabled{display:none}.repo-list .participation-graph .bars{position:absolute;bottom:0}.repo-list-item{position:relative;padding-top:30px;padding-bottom:30px;list-style:none;border-bottom:1px solid #eee}.repo-list-name{margin:0 0 8px;font-size:20px;line-height:1.2}.repo-list-name:hover{text-decoration:none;color:#4169E1}.repo-list-name .slash{margin-right:-4px;margin-left:-4px}.repo-list-description{max-width:550px;margin-top:8px;margin-bottom:0;font-size:14px;color:#666}.repo-list-stats{margin-top:6px;float:right;font-size:12px;font-weight:700;color:#888}.repo-list-stats .repo-list-stat-item{margin-left:8px;display:inline-block;color:#888;text-decoration:none}.repo-list-stats .repo-list-stat-item:hover{color:#4183c4}.repo-list-stats .repo-list-stat-item>.octicon{font-size:14px}.repo-list-info{display:inline-block;height:100%;margin-top:0;margin-bottom:0;font-size:12px;color:#888;vertical-align:middle}.repo-list-info .octicon{margin-top:-3px;font-size:12px;vertical-align:middle}.repo-list-meta{display:block;margin-top:8px;margin-bottom:0;font-size:13px;color:#999}.repo-list-meta .avatar{margin-top:-2px}.repo-list-meta a:hover{text-decoration:none}.repo-list-meta .meta-info{margin-right:15px}.repo-list-meta .meta-info a{color:#999}.repo-list-meta .meta-info a:hover,.repo-list-meta .meta-info a:hover{text-decoration: underline} +.font16px{font-size: 15px;line-height: 1;} diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 414c0c7a8a..0000000000 --- a/deploy.sh +++ /dev/null @@ -1 +0,0 @@ -bundle exec jekyll serve diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index c04e20c1f9..0000000000 Binary files a/favicon.ico and /dev/null differ diff --git a/favicon.jpeg b/favicon.jpeg new file mode 100644 index 0000000000..e382816c1d Binary files /dev/null and b/favicon.jpeg differ diff --git a/index.html b/index.html index a127bc49b7..1941b3cf7e 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ {% assign assets_base_url = site.url %} {% if site.cdn.jsdelivr.enabled %} -{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@master' %} +{% assign assets_base_url = "https://cdn.jsdelivr.net/gh/" | append: site.repository | append: '@'| append: site.cdn.jsdelivr.branch %} {% endif %} diff --git a/pages/404.md b/pages/404.md index f01e496cf7..f0a047a0bc 100644 --- a/pages/404.md +++ b/pages/404.md @@ -7,4 +7,4 @@ comments: false permalink: /404.html --- -404了,请重新搜索查找 \ No newline at end of file +404 了,请重新搜索查找 diff --git a/pages/about.md b/pages/about.md index f013d5e7b3..96e5c83c42 100644 --- a/pages/about.md +++ b/pages/about.md @@ -1,7 +1,7 @@ --- layout: page title: About -description: +description: keywords: mafulong comments: true menu: 关于 @@ -22,13 +22,12 @@ permalink: /about/ 🎊 Hello world! -- 🔭 Now:Bytedance as a Back-end Software Engineer +- 🔭 Now:Bytedance as a Back-end Software Engineer - 🌱 **PROFESSIONAL SKILLS**: - Programmer Languages: Golang(frequent), Python/C++/Java (Basic, once used). - Mysql(frequent), Redis(frequent), Kafka(frequent), Elastic-Search(Basic). - Understand common network protocol, such as Http, WebSocket, Quic. - Understand common distributed components and protocols such as etcd. - - 📫 [Blog:mafulong.github.io](https://mafulong.github.io) - 📝 [My Patent List(20+)](https://github.com/mafulong/mafulong/blob/main/files/zhuanli.csv) - 📝 [SCI Articles:《A Time and Location Correlation Incentive Scheme for Deeply Data Gathering in Crowdsourcing Networks》](https://www.hindawi.com/journals/wcmc/2018/8052620/) diff --git a/pages/bookmark.md b/pages/bookmark.md index 832a024a04..40cf6818c4 100644 --- a/pages/bookmark.md +++ b/pages/bookmark.md @@ -9,11 +9,13 @@ permalink: /bookmark/ ## 常用网站 ### 搜索引擎 + - [Bing](https://www.bing.com/?intlF=) - [Baidu](https://www.baidu.com) - [Google](https://www.google.com/ncr) ### 编程学习 + - [PAT](https://www.patest.cn/) - [菜鸟编程](http://www.runoob.com/) - [慕课网](http://www.imooc.com/) @@ -25,26 +27,32 @@ permalink: /bookmark/ - [上乘网站知乎链接](https://www.zhihu.com/question/22525413) ### 英语学习 -- [Google翻译](https://translate.google.cn/) + +- [Google 翻译](https://translate.google.cn/) - [可可英语](http://www.kekenet.com/) - [有道在线词典](http://dict.youdao.com/) ### 邮箱链接 -- [163邮箱](http://mail.163.com/#return) -- [126邮箱](http://mail.126.com/) -- [CSU邮箱](http://mail.csu.edu.cn/) + +- [163 邮箱](http://mail.163.com/#return) +- [126 邮箱](http://mail.126.com/) +- [CSU 邮箱](http://mail.csu.edu.cn/) ### 其他 + - [电影天堂](http://www.dytt8.net/) - [中南大学图书馆](http://122.207.86.18/) - [乔布简历模板](http://cv.qiaobutang.com/tpl/?order=relevance&keyword=%E7%A8%8B%E5%BA%8F%E5%91%98) - [高清壁纸](https://pixabay.com) +- [高清壁纸 2](https://wallhaven.cc/toplist?page=5) ### 工具 -- [html转markdown](https://euangoddard.github.io/clipboard2markdown/) 鼠标选择html的文字,然后copy到网页里复制就行 -- [rgb颜色表](http://www.wahart.com.hk/rgb.htm) rgb颜色表 + +- [html 转 markdown](https://euangoddard.github.io/clipboard2markdown/) 鼠标选择 html 的文字,然后 copy 到网页里复制就行 +- [rgb 颜色表](http://www.wahart.com.hk/rgb.htm) rgb 颜色表 ## 面试八股文 + - [秋招笔记](https://github.com/CyC2018/Interview-Notebook) - 准备秋招学习笔记: 算法,操作系统,网路,面向对象,数据库,Java,分布式,工具 - [LeetCode](https://books.halfrost.com/) - [力扣加加](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/) @@ -52,12 +60,15 @@ permalink: /bookmark/ - [小刀面试笔记](https://wdxtub.com/interview/index.html) 包含面向对象 - [算法通关手册](https://algo.itcharge.cn/07.Tree/03.Segment-Tree/01.Segment-Tree/) - [为什么这么设计](https://draveness.me/whys-the-design/) - 为什么这么设计系列文章 -- [Go语言设计与实现](https://draveness.me/golang/) - Go语言设计与实现 -- [Java全栈知识](https://pdai.tech/md/outline/x-outline.html) +- [Go 语言设计与实现](https://draveness.me/golang/) - Go 语言设计与实现 +- [7 天用 Go 从零实现 Web 框架 Gee 教程](https://geektutu.com/post/gee.html) +- [Java 全栈知识](https://pdai.tech/md/outline/x-outline.html) - [Spring](https://potoyang.gitbook.io/spring-in-action-v4/1/1.3-fu-kan-spring-feng-jing-xian/1.3.1spring-mo-kuai) -- [代码重工java&spring入门](https://www.wolai.com/nnRjHcUSv2mrRbFKZUpBMS) [这个也是,有maven等,都需要看](https://heavy_code_industry.gitee.io/code_heavy_industry/) +- [代码重工 java&spring 入门](https://www.wolai.com/nnRjHcUSv2mrRbFKZUpBMS) [这个也是,有 maven 等,都需要看](https://heavy_code_industry.gitee.io/code_heavy_industry/) +- [前端](https://github.com/qianguyihao/Web) ## 软件使用 + ### win10 - 控制台: cmder @@ -71,12 +82,14 @@ permalink: /bookmark/ ### mac os -- iTerm -- Sequel Pro -- HyperSwitch -- Spectacle -- popclip 选择文字后上面可显示快捷键。 [破解版下载网址](https://xclient.info/s/popclip.html#versions), 还可以安装插件,比如欧陆词典Eudic, 下载后双击即可,[插件下载网址](https://pilotmoon.com/popclip/extensions/) -- betterTouchTool, 免费触控板手势软件。 +- iTerm 控制台必装 +- Sequel Pro 数据库工具 +- HyperSwitch 应用多窗口切换而不是应用切换,可选 +- Spectacle 多窗口必装 +- popclip 选择文字后上面可显示快捷键。 [破解版下载网址](https://xclient.info/s/popclip.html#versions), 还可以安装插件,比如欧陆词典 Eudic, 下载后双击即可,[插件下载网址](https://pilotmoon.com/popclip/extensions/) +- betterTouchTool, 免费触控板手势软件, 可选。 +- [MonitorControl Lite](https://github.com/MonitorControl/MonitorControlLite#readme) mac 亮度条件软件,可以状态栏就开始调屏幕亮度了,亮度下限可以更低点。 +- Alfred 快速搜索 ### android @@ -104,12 +117,18 @@ permalink: /bookmark/ - onenote - keep - 滴滴出行 -- 场库 -- 开眼 -- Ted + +### chrome + +- [主题 Chuck Anderson](https://chrome.google.com/webstore/detail/chuck-anderson/gegkoiakifeoejnjkbnnojkkdoegeofp) +- [主题 Super Color](https://chrome.google.com/webstore/detail/colors/lhbgjlhhonbdjfdoiklbbkejcipkbnac) + +### 其他好用软件 + +- [好用程序员软件列表 包括各平台的](https://github.com/pseudoyu/yu-tools) ## 书签 ### 理念 -- [到底什么是技术](https://mp.weixin.qq.com/s/h8iZcL1FOabjWis4_vQVQQ) - 到底什么是技术 +- [到底什么是技术](https://mp.weixin.qq.com/s/h8iZcL1FOabjWis4_vQVQQ) - 到底什么是技术 diff --git a/pages/gallery.md b/pages/gallery.md new file mode 100644 index 0000000000..bb4955789c --- /dev/null +++ b/pages/gallery.md @@ -0,0 +1,25 @@ +--- +layout: page +title: 私人图库 +description: 图库 +comments: false +menu: 图库 +permalink: /gallery/ +--- + +> 私人收藏 + +![](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202212022241388.jpg) + +![1111672918711_.pic](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301051939882.jpg) + + +![爱心](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301201659485.jpg) + + + +![653771674205284_.pic](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301201702769.jpg) + + + +![653751674205257_.pic](https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv6/v6/202301201702130.jpg) diff --git a/pages/mindmap-viewer.md b/pages/mindmap-viewer.md deleted file mode 100644 index 488e71a46c..0000000000 --- a/pages/mindmap-viewer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: mindmap -title: mindmap -keywords: mindmap -description: 全屏查看脑图 -permalink: /mindmap-viewer/ ---- diff --git a/pages/wiki.md b/pages/wiki.md deleted file mode 100644 index 227b6ae504..0000000000 --- a/pages/wiki.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -layout: page -title: Wiki -description: 人越学越觉得自己无知 -keywords: 维基, Wiki -comments: false -menu: 维基 -permalink: /wiki/ ---- - -> 记多少命令和快捷键会让脑袋爆炸呢? - -