【python測試開發棧】帶你徹底搞明白python3編碼原理

在之前的文章中,我們介紹過編碼格式的發展史:[文章傳送門-todo]。今天我們通過幾個例子,來徹底搞清楚python3中的編碼格式原理,這樣你之後寫python腳本時碰到編碼問題,才能有章可循。

我們先搞清楚幾個概念:

  • 系統默認編碼:指python解釋器默認的編碼格式,在python文件頭部沒有聲明其他編碼格式時,python3默認的編碼格式是utf-8。
  • 本地默認編碼:操作系統默認的編碼,常見的Windows的默認編碼是gbk,Linux的默認編碼是UTF-8。
  • python文件頭部聲明編碼格式:修改的是文件的默認編碼格式,只是會影響python解釋器讀取python文件時的編碼格式,並不會改變系統默認編碼和本地默認編碼。

通過python自帶的庫,可以查看系統默認編碼和本地默認編碼

Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
>>>

注意,因為我在windows系統的電腦上 進行測試,所以系統默認編碼返回“cp936”, 這是代碼頁(是字符編碼集的別名),而936對應的就是gbk。如果你在linux或者mac上執行上面的代碼,應該會返回utf-8編碼。

其實總結來看,容易出現亂碼的場景,基本都與讀寫程序有關,比如:讀取/寫入某個文件,或者從網絡流中讀取數據等,因為這個過程中涉及到了編碼解碼的過程,只要編碼和解碼的編碼格式對應不上,就容易出現亂碼。下面我們舉兩個具體的例子,來驗證下python的編碼原理,幫助你理解這個過程。注意:下面的例子都是在pycharm中寫的。

01默認的編碼格式

我們新建一個encode_demo.py的文件,其文件默認的編碼格式是UTF-8(可以從pycharm右下角看到編碼格式),代碼如下:

"""
    @author: asus
    @time: 2019/11/21
    @function: 驗證編碼格式
"""
import sys, locale


def write_str_default_encode():
    s = "我是一個str"
    print(s)
    print(type(s))
    print(sys.getdefaultencoding())
    print(locale.getdefaultlocale())

    with open("utf_file", "w", encoding="utf-8") as f:
        f.write(s)
    with open("gbk_file", "w", encoding="gbk") as f:
        f.write(s)
    with open("jis_file", "w", encoding="shift-jis") as f:
        f.write(s)


if __name__ == '__main__':
    write_str_default_encode()

我們先來猜測下結果,因為我們沒有聲明編碼格式,所以python解釋器默認用UTF-8去解碼文件,因為文件默認編碼格式就是UTF-8,所以字符串s可以正常打印。同時以UTF-8編碼格式寫文件不會出現亂碼,而以gbk和shift-jis(日文編碼)寫文件會出現亂碼(這裏說明一點,我是用pycharm直接打開生成的文件查看的,編輯器默認編碼是UTF-8,如果在windows上用記事本打開則其默認編碼跟隨系統是GBK,gbk_file和utf_file均不會出現亂碼,只有jis_file是亂碼),我們運行看下結果:

# 運行結果
我是一個str
<class 'str'>
utf-8
('zh_CN', 'cp936')

# 寫文件utf_file、gbk_file、jis_file文件內容分別是:
我是一個str
����һ��str
�䐥�꘢str

和我們猜測的結果一致,下面我們做個改變,在文件頭部聲明個編碼格式,再來看看效果。

02 python頭文件聲明編碼格式

因為上面文件encode_demo.py的格式是UTF-8,那麼我們就將其變為gbk編碼。同樣的我們先來推測下結果,在pycharm中,在python文件頭部聲明編碼為gbk后(頭部加上 # coding=gbk ),文件的編碼格式變成gbk,同時python解釋器會用gbk去解碼encode_demo.py文件,所以運行結果應該和用UTF-8編碼時一樣。運行結果如下:

# 運行結果
我是一個str
<class 'str'>
utf-8
('zh_CN', 'cp936')

# 寫文件utf_file、gbk_file、jis_file文件內容分別是:
我是一個str
����һ��str
�䐥�꘢str

結果確實是一樣的,證明我們推論是正確的。接下來我們再做個嘗試,假如我們將(# coding=gbk)去掉(需要注意,在pycharm中將 # coding=gbk去掉,並不會改變文件的編碼格式,也就是說encode_demo.py還是gbk編碼),我們再運行一次看結果:

  File "D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py", line 4
SyntaxError: Non-UTF-8 code starting with '\xd1' in file D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py on line 5, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

運行直接報錯了,我們加個斷點,看看具體的異常信息:

看錯誤提示是UnicodeDecodeError,python解釋器在對encode_demo.py文件解碼時,使用默認的UTF-8編碼,但是文件本身是gbk編碼,所以當碰到有中文沒辦法識別時,就拋出DecodeError。

03 敲黑板,划重點

python3中的str和bytes

python3的重要特性之一就是對字符串和二進制流做了嚴格的區分,我們聲明的字符串都是str類型,不過Str和bytes是可以相互轉換的:

def str_transfor_bytes():
    s = '我是一個測試Str'
    print(type(s))
    # str 轉bytes
    b = s.encode()
    print(b)
    print(type(b))
    # bytes轉str
    c = b.decode('utf-8')
    print(c)
    print(type(c))


if __name__ == '__main__':
    str_transfor_bytes()

需要注意一點:在調用encode()和decode()方法時,如果不傳參數,則會使用python解釋器默認的編碼格式UTF-8(如果不在python頭文件聲明編碼格式)。但是如果傳參的話,encode和decode使用的編碼格式要能對應上。

python3默認編碼是UTF-8?還是Unicode?

經常在很多文章里看到,python3的默認編碼格式是Unicode,但是我在本文中卻一直在說python3的默認編碼格式是UTF-8,那麼哪種說法是正確的呢?其實兩種說法都對,主要得搞清楚Unicode和UTF-8的區別(之前文章有提到):

  • Unicode是一個字符集,說白了就是把各種編碼的映射關係全都整合起來,不過它是不可變長的,全部都以兩個字節或四個字節來表示,佔用的內存空間比較大。
  • UTF-8是Unicode的一種實現方式,主要對 Unicode 碼的數據進行轉換,方便存儲和網絡傳輸 。它是可變長編碼,比如對於英文字母,它使用一個字節就可以表示。

在python3內存中使用的字符串全都是Unicode碼,當python解釋器解析python文件時,默認使用UTF-8編碼。

open()方法默認使用本地編碼

在上面的例子中,我們往磁盤寫入文件時,都指定了編碼格式。如果不指定編碼格式,那麼默認將使用操作系統本地默認的編碼格式,比如:Linux默認是UTF-8,windows默認是GBK。其實這也好理解,因為和磁盤交互,肯定要考慮操作系統的編碼格式。這有區別於encode()和decode()使用的是python解釋器的默認編碼格式,千萬別搞混淆了。

總結

不知道你看完上面的例子后,是否已經徹底理解了python3的編碼原理。不過所有的編碼問題,都逃不過“編碼”和“解碼”兩個過程,當你碰到編碼問題時,先確定源文件使用的編碼,再確定目標文件需要的編碼格式,只要能匹配,一般就可以解決編碼的問題。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝

人生苦短,我用 Python

前文傳送門:

本篇內容較長,各位同學可以先收藏后再看~~

在開始講爬蟲之前,還是先把環境搞搞好,工欲善其事必先利其器嘛~~~

本篇文章主要介紹 Python 爬蟲所使用到的請求庫和解析庫,請求庫用來請求目標內容,解析庫用來解析請求回來的內容。

開發環境

首先介紹小編本地的開發環境:

  • Python3.7.4
  • win10

差不多就這些,最基礎的環境,其他環境需要我們一個一個安裝,現在開始。

請求庫

雖然 Python 為我們內置了 HTTP 請求庫 urllib ,使用姿勢並不是很優雅,但是很多第三方的提供的 HTTP 庫確實更加的簡潔優雅,我們下面開始。

Requests

Requests 類庫是一個第三方提供的用於發送 HTTP 同步請求的類庫,相比較 Python 自帶的 urllib 類庫更加的方便和簡潔。

Python 為我們提供了包管理工具 pip ,使用 pip 安裝將會非常的方便,安裝命令如下:

pip install requests

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests

首先在 CMD 命令行中輸入 python ,進入 python 的命令行模式,然後輸入 import requests 如果沒有任何錯誤提示,說明我們已經成功安裝 Requests 類庫。

Selenium

Selenium 現在更多的是用來做自動化測試工具,相關的書籍也不少,同時,我們也可以使用它來做爬蟲工具,畢竟是自動化測試么,利用它我們可以讓瀏覽器執行我們想要的動作,比如點擊某個按鈕、滾動滑輪之類的操作,這對我們模擬真實用戶操作是非常方便的。

安裝命令如下:

pip install selenium

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import selenium

這樣沒報錯我們就安裝完成,但是你以為這樣就算好了么?圖樣圖森破啊。

ChromeDriver

我們還需要瀏覽器的支持來配合 selenium 的工作,開發人員嘛,常用的瀏覽器莫非那麼幾種:Chrome、Firefox,那位說 IE 的同學,你給我站起來,小心我跳起來打你膝蓋,還有說 360 瀏覽器的,你們可讓我省省心吧。

接下來,安裝 Chrome 瀏覽器就不用講了吧。。。。

再接下來,我們開始安裝 ChromeDriver ,安裝了 ChromeDriver 后,我們才能通過剛才安裝的 selenium 來驅動 Chrome 來完成各種騷操作。

首先,我們需要查看自己的 Chrome 瀏覽器的版本,在 Chrome 瀏覽器右上角的三個點鐘,點擊 幫助 -> 關於,如下圖:

將這個版本找個小本本記下來,小編這裏的版本為: 版本 78.0.3904.97(正式版本) (64 位)

接下來我們需要去 ChromeDriver 的官網查看當前 Chrome 對應的驅動。

官網地址:

因某些原因,訪問時需某些手段,訪問不了的就看小編為大家準備的版本對應表格吧。。。

ChromeDriver Version Chrome Version
78.0.3904.11 78
77.0.3865.40 77
77.0.3865.10 77
76.0.3809.126 76
76.0.3809.68 76
76.0.3809.25 76
76.0.3809.12 76
75.0.3770.90 75
75.0.3770.8 75
74.0.3729.6 74
73.0.3683.68 73
72.0.3626.69 72
2.46 71-73
2.45 70-72
2.44 69-71
2.43 69-71
2.42 68-70
2.41 67-69
2.40 66-68
2.39 66-68
2.38 65-67
2.37 64-66
2.36 63-65
2.35 62-64

順便小編找到了國內對應的下載的鏡像站,由淘寶提供,如下:

雖然和小編本地的小版本對不上,但是看樣子只要大版本符合應該沒啥問題,so,去鏡像站下載對應的版本即可,小編這裏下載的版本是:78.0.3904.70 ,ChromeDriver 78版本的最後一個小版本。

下載完成后,將可執行文件 chromedriver.exe 移動至 Python 安裝目錄的 Scripts 目錄下。如果使用默認安裝未修改過安裝目錄的話目錄是:%homepath%\AppData\Local\Programs\Python\Python37\Scripts ,如果有過修改,那就自力更生吧。。。

chromedriver.exe 添加后結果如下圖:

驗證:

還是在 CMD 命令行中,輸入以下內容:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from selenium import webdriver
>>> browser = webdriver.Chrome()

如果打開一個空白的 Chrome 頁面說明安裝成功。

GeckoDriver

上面我們通過安裝 Chrome 的驅動完成了 Selenium 與 Chrome 的對接,想要完成 Selenium 與 FireFox 的對接則需要安裝另一個驅動 GeckoDriver 。

FireFox 的安裝小編這裏就不介紹了,大家最好去官網下載安裝,路徑如下:

FireFox 官網地址:

GeckoDriver 的下載需要去 Github (全球最大的同性交友網站),下載路徑小編已經找好了,可以選擇最新的 releases 版本進行下載。

下載地址:

選擇對應自己的環境,小編這裏選擇 win-64 ,版本為 v0.26.0 進行下載。

具體配置方式和上面一樣,將可執行的 .exe 文件放入 %homepath%\AppData\Local\Programs\Python\Python37\Scripts 目錄下即可。

驗證:

還是在 CMD 命令行中,輸入以下內容:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from selenium import webdriver
>>> browser = webdriver.Firefox()

應該是可以正常打開一個空白的 FireFox 頁面的,結果如下:

注意: GeckoDriver 指出一點,當前的版本在 win 下使用有已知 bug ,需要安裝微軟的一個插件才能解決,原文如下:

You must still have the installed on your system for the binary to run. This is a known bug which we weren’t able fix for this release.

插件下載地址:

請各位同學選擇自己對應的系統版本進行下載安裝。

Aiohttp

上面我們介紹了同步的 Http 請求庫 Requests ,而 Aiohttp 則是一個提供異步 Http 請求的類庫。

那麼,問題來了,什麼是同步請求?什麼是異步請求呢?

  • 同步:阻塞式,簡單理解就是當發出一個請求以後,程序會一直等待這個請求響應,直到響應以後,才繼續做下一步。
  • 異步:非阻塞式,還是上面的例子,當發出一個請求以後,程序並不會阻塞在這裏,等待請求響應,而是可以去做其他事情。

從資源消耗和效率上來說,同步請求是肯定比不過異步請求的,這也是為什麼異步請求會比同步請求擁有更大的吞吐量。在抓取數據時使用異步請求,可以大大提升抓取的效率。

如果還想了解跟多有關 aiohttp 的內容,可以訪問官方文檔: 。

aiohttp 安裝如下:

pip install aiohttp

aiohttp 還推薦我們安裝另外兩個庫,一個是字符編碼檢測庫 cchardet ,另一個是加速DNS的解析庫 aiodns 。

安裝 cchardet 庫:

pip install cchardet

安裝 aiodns 庫:

pip install aiodns

aiohttp 十分貼心的為我們準備了整合的安裝命令,無需一個一個鍵入命令,如下:

pip install aiohttp[speedups]

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import aiohttp

沒報錯就安裝成功。

解析庫

lxml

lxml 是 Python 的一個解析庫,支持 HTML 和 XML 的解析,支持 XPath 的解析方式,而且解析效率非常高。

什麼是 XPath ?

XPath即為XML路徑語言(XML Path Language),它是一種用來確定XML文檔中某部分位置的語言。
XPath基於XML的樹狀結構,提供在數據結構樹中找尋節點的能力。起初XPath的提出的初衷是將其作為一個通用的、介於XPointer與XSL間的語法模型。

以上內容來源《百度百科》

好吧,小編說人話,就是可以從 XML 文檔或者 HTML 文檔中快速的定位到所需要的位置的路徑語言。

還沒看懂?emmmmmmmmmmm,我們可以使用 XPath 快速的取出 XML 或者 HTML 文檔中想要的值。用法的話我們放到後面再聊。

安裝 lxml 庫:

pip install lxml

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import lxml

沒報錯就安裝成功。

Beautiful Soup

Beautiful Soup 同樣也是一個 Python 的 HTML 或 XML 的解析庫 。它擁有強大的解析能力,我們可以使用它更方便的從 HTML 文檔中提取數據。

首先,放一下 Beautiful Soup 的官方網址,有各種問題都可以在官網查看文檔,各位同學養成一個好習慣,有問題找官方文檔,雖然是英文的,使用 Chrome 自帶的翻譯功能還是勉強能看的。

官方網站:

安裝方式依然使用 pip 進行安裝:

pip install beautifulsoup4

Beautiful Soup 的 HTML 和 XML 解析器是依賴於 lxml 庫的,所以在此之前請確保已經成功安裝好了 lxml 庫 。

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from bs4 import BeautifulSoup

沒報錯就安裝成功。

pyquery

pyquery 同樣也是一個網頁解析庫,只不過和前面兩個有區別的是它提供了類似 jQuery 的語法來解析 HTML 文檔,前端有經驗的同學應該會非常喜歡這款解析庫。

首先還是放一下 pyquery 的官方文檔地址。

官方文檔:

安裝:

pip install pyquery

驗證:

C:\Users\inwsy>python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyquery

沒報錯就安裝成功。

本篇的內容就先到這裏結束了。請各位同學在自己的電腦上將上面介紹的內容都安裝一遍,以便後續學習使用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

[ASP.NET Core 3框架揭秘] 文件系統[3]:物理文件系統

ASP.NET Core應用中使用得最多的還是具體的物理文件,比如配置文件、View文件以及作為Web資源的靜態文件。物理文件系統由定義在NuGet包“Microsoft.Extensions.FileProviders.Physical”中的PhysicalFileProvider來構建。我們知道System.IO命名空間下定義了一整套針操作物理目錄和文件的API,實際上PhysicalFileProvider最終也是通過調用這些API來完成相關的IO操作。

public class PhysicalFileProvider : IFileProvider, IDisposable
{   
    public PhysicalFileProvider(string root);   
     
    public IFileInfo GetFileInfo(string subpath);  
    public IDirectoryContents GetDirectoryContents(string subpath); 
    public IChangeToken Watch(string filter);

    public void Dispose();   
}

一、PhysicalFileInfo

一個PhysicalFileProvider對象總是映射到某個具體的物理目錄上,被映射的目錄所在的路徑通過構造函數的參數root來提供,該目錄將作為PhysicalFileProvider的根目錄。GetFileInfo方法返回的IFileInfo對象代表指定路徑對應的文件,這是一個類型為PhysicalFileInfo的對象。一個物理文件可以通過一個System.IO.FileInfo對象來表示,一個PhysicalFileInfo對象實際上就是對該對象的封裝,定義在PhysicalFileInfo的所有屬性都來源於這個FileInfo對象。對於創建讀取文件輸出流的CreateReadStream方法來說,它返回的是一個根據物理文件絕對路徑創建的FileStream對象。

public class PhysicalFileInfo : IFileInfo
{
    ...
    public PhysicalFileInfo(FileInfo info);    
}

對於PhysicalFileProvider的GetFileInfo方法來說,即使我們指定的路徑指向一個具體的物理文件,它並不總是會返回一個PhysicalFileInfo對象。PhysicalFileProvider會將一些場景視為“目標文件不存在”,並讓GetFileInfo方法返回一個NotFoundFileInfo對象。具體來說,PhysicalFileProvider的GetFileInfo方法在如下的場景中會返回一個NotFoundFileInfo對象:

  • 確實沒有一個物理文件與指定的路徑相匹配。
  • 如果指定的是一個絕對路徑(比如“c:\foobar”),即Path.IsPathRooted方法返回True。
  • 如果指定的路徑指向一個隱藏文件。

顧名思義,具有如下定義的NotFoundFileInfo類型表示一個“不存在”的文件。NotFoundFileInfo對象的Exists屬性總是返回False,而其他的屬性則變得沒有任何意義。當我們調用它的CreateReadStream試圖讀取一個根本不存在的文件內容時,會拋出一個FileNotFoundException類型的異常。

public class NotFoundFileInfo : IFileInfo
{
    public bool Exists => false;   
    public long Length => throw new NotImplementedException();   
    public string PhysicalPath => null;  
    public string Name { get; }   
    public DateTimeOffset LastModified => DateTimeOffset.MinValue;
    public bool IsDirectory => false; 

    public NotFoundFileInfo(string name) => this.Name = name;
    public Stream CreateReadStream() => throw new FileNotFoundException($"The file {Name} does not exist.");
}

二、PhysicalDirectoryInfo

PhysicalFileProvider利用一個PhysicalFileInfo對象來描述某個具體的物理文件,而一個物理目錄則通過一個PhysicalDirectoryInfo的對象來描述。既然PhysicalFileInfo是對一個FileInfo對象的封裝,那麼我們應該想得到PhysicalDirectoryInfo對象封裝的就是表示目錄的DirectoryInfo對象。如下面的代碼片段所示,我們需要在創建一個PhysicalDirectoryInfo對象時提供這個DirectoryInfo對象,PhysicalDirectoryInfo實現的所有屬性的返回值都來源於這個DirectoryInfo對象。由於CreateReadStream方法的目的總是讀取文件的內容,所以PhysicalDirectoryInfo類型的這個方法會拋出一個InvalidOperationException類型的異常。

public class PhysicalDirectoryInfo : IFileInfo
{   
    ...
    public PhysicalDirectoryInfo(DirectoryInfo info);
}

三、PhysicalDirectoryContents

當我們調用PhysicalFileProvider的GetDirectoryContents方法時,如果指定的路徑指向一個具體的目錄,那麼該方法會返回一個類型為PhysicalDirectoryContents的對象。PhysicalDirectoryContents是一個IFileInfo對象的集合,該集合中包括所有描述子目錄的PhysicalDirectoryInfo對象和描述文件的PhysicalFileInfo對象。PhysicalDirectoryContents的Exists屬性取決於指定的目錄是否存在。

public class PhysicalDirectoryContents : IDirectoryContents
{
    public bool Exists { get; }
    public PhysicalDirectoryContents(string directory);
    public IEnumerator<IFileInfo> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

四、NotFoundDirectoryContents

如果指定的路徑並不指向一個存在的目錄,或者指定的是一個絕對路徑,GetDirectoryContents方法都會返回一個Exsits為False的NotFoundDirectoryContents對象。如下所示的代碼片段展示了NotFoundDirectoryContents類型的定義,如果我們需要使用到這麼一個類型,可以直接利用靜態屬性Singleton得到對應的單例對象。

public class NotFoundDirectoryContents : IDirectoryContents
{    
    public static NotFoundDirectoryContents Singleton { get; }  = new NotFoundDirectoryContents();
    public bool Exists => false;
    public IEnumerator<IFileInfo> GetEnumerator()  => Enumerable.Empty<IFileInfo>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

五、PhysicalFilesWatcher

我們接着來談談PhysicalFileProvider的Watch方法。當我們調用該方法的時候,PhysicalFileProvider會通過解析我們提供的Globbing Pattern表達式來確定我們期望監控的文件或者目錄,並最終利用FileSystemWatcher對象來對這些文件實施監控。這些文件或者目錄的變化(創建、修改、重命名和刪除等)都會實時地反映到Watch方法返回的IChangeToken上。

PhysicalFileProvider的Watch方法中指定的Globbing Pattern表達式必須是針對當前根目錄的相對路徑,我們可以使用“/”或者“./”前綴,也可以不採用任何前綴。一旦我們使用了絕對路徑(比如“c:\test\*.txt”)或者“../”前綴(比如“../test/*.txt”),不論解析出來的文件是否存在於PhysicalFileProvider的根目錄下,這些文件都不會被監控。除此之外,如果我們沒有指定Globbing Pattern表達式,PhysicalFileProvider也不會有任何的文件會被監控。

PhysicalFileProvider針對物理文件系統變化的監控是通過如下這個PhysicalFilesWatcher對象實現的,其Watch方法內部會直接調用PhysicalFileProvider的CreateFileChangeToken方法,並返回得到的IChangeToken對象。這是一個公共類型,如果我們具有監控物理文件系統變化的需要,可以直接使用這個類型。

public class PhysicalFilesWatcher: IDisposable
{
    public PhysicalFilesWatcher(string root, FileSystemWatcher fileSystemWatcher, bool pollForChanges);
    public IChangeToken CreateFileChangeToken(string filter);
    public void Dispose();
}

從PhysicalFilesWatcher構造函數的定義我們不難看出,它最終是利用一個FileSystemWatcher對象(對應於fileSystemWatcher參數)來完成針對指定根目錄下(對應於root參數)所有子目錄和文件的監控。FileSystemWatcher的CreateFileChangeToken方法返回的IChangeToken對象會幫助我們感知到子目錄或者文件的添加、刪除、修改和重命名,但是它會忽略隱藏的目錄和文件。最後需要提醒的是,當我們不再需要對指定目錄實施監控的時候,記得調用PhysicalFileProvider的Dispose方法,該方法會負責將FileSystemWatcher對象關閉。

六、小結

我們藉助下圖所示的UML來對由PhysicalFileProvider構建物理文件系統的整體設計做一個簡單的總結。首先,該文件系統使用PhysicalDirectoryInfo和PhysicalFileInfo對類型來描述目錄和文件,它們分別是對DirectoryInfo和FileInfo(System.IO.FileInfo)對象的封裝。

PhysicalFileProvider的GetDirectoryContents方法返回一個PhysicalDirectoryContents 對象(如果指定的目錄存在),組成該對象的分別是根據其所有子目錄和文件創建的PhysicalDirectoryInfo和PhysicalFileInfo對象。當我們調用PhysicalFileProvider的GetFileInfo方法時,如果指定的文件存在,返回的是描述該文件的PhysicalFileInfo對象。至於PhysicalFileProvider的Watch方法,它最終利用了FileSystemWatcher來監控指定文件或者目錄的變化。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

面向對象和面向過程詳解

1.前言

其實一直對面向過程和面向對象的概念和區別沒有很深入的理解,在自己不斷想完善自己的知識體系中,今天借這個時間,寫一篇博客。來深入的了解面向過程與面向對象!好記性不如爛筆頭!!  

2.面向對象與面向過程的區別

面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了;面向對象是把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。

舉一個下五子棋通俗例子吧 哈哈哈 我感覺這兒例子很容易讓人理解

面向過程的設計思路就是首先分析問題的步驟:

1、開始遊戲,

2、黑子先走,

3、繪製畫面,

4、判斷輸贏,

5、輪到白子,

6、繪製畫面,

7、判斷輸贏,

8、返回步驟2,

9、輸出最後結果。

把上面每個步驟用分別的函數來實現,問題就解決了。

面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為

1、黑白雙方,這兩方的行為是一模一樣的,

2、棋盤系統,負責繪製畫面,

3、規則系統,負責判定諸如犯規、輸贏等。第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子布局的變化,

棋盤對象接收到了棋子的i變化就要負責在屏幕上面显示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。

可以明顯地看出,面向對象是以功能來劃分問題,而不是步驟。同樣是繪製棋局,這樣的行為在面向過程的設計中分散在了總多步驟中,很可能出現不同的繪製版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一。

功能上的統一保證了面向對象設計的可擴展性。比如要加入悔棋的功能,如果要改動面向過程的設計,那麼從輸入到判斷到显示這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤系統保存了黑白雙方的棋譜,簡單回溯就可以了,而显示和規則判斷則不用顧及,同時整個對對象功能的調用順序都沒有變化,改動只是局部的。

再比如我要把這個五子棋遊戲改為圍棋遊戲,如果你是面向過程設計,那麼五子棋的規則就分佈在了你的程序的每一個角落,要改動還不如重寫。但是如果你當初就是面向對象的設計,那麼你只用改動規則對象就可以了,五子棋和圍棋的區別不就是規則嗎?(當然棋盤大小好像也不一樣,但是你會覺得這是一個難題嗎?直接在棋盤對象中進行一番小改動就可以了。)而下棋的大致步驟從面向對象的角度來看沒有任何變化。

當然,要達到改動只是局部的需要設計的人有足夠的經驗,使用對象不能保證你的程序就是面向對象,初學者或者很蹩腳的程序員很可能以面向對象之虛而行面向過程之實,這樣設計出來的所謂面向對象的程序很難有良好的可移植性和可擴展性

三、面向過程與面向對象的優缺點
很多資料上全都是一群很難理解的理論知識,整的我頭都大了,後來發現了一個比較好的文章,寫的真是太棒了,通俗易懂,想要不明白都難!

用面向過程的方法寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什麼菜,你就澆上什麼菜。我覺得這個比喻還是比較貼切的。

蛋炒飯製作是把米飯和雞蛋混在一起炒勻。蓋澆飯呢,則是把米飯和蓋菜分別做好,你如果要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;如果要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。

蛋炒飯的好處就是入味均勻,吃起來香。如果恰巧你不愛吃雞蛋,只愛吃青菜的話,那麼唯一的辦法就是全部倒掉,重新做一份青菜炒飯了。蓋澆飯就沒這麼多麻煩,你只需要把上面的蓋菜撥掉,更換一份蓋菜就可以了。蓋澆飯的缺點是入味不均,可能沒有蛋炒飯那麼香。

到底是蛋炒飯好還是蓋澆飯好呢?其實這類問題都很難回答,非要比個上下高低的話,就必須設定一個場景,否則只能說是各有所長。如果大家都不是美食家,沒那麼多講究,那麼從飯館角度來講的話,做蓋澆飯顯然比蛋炒飯更有優勢,他可以組合出來任意多的組合,而且不會浪費。

蓋澆飯的好處就是”菜”“飯”分離,從而提高了製作蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業術語就是”可維護性“比較好,”飯” 和”菜”的耦合度比較低。蛋炒飯將”蛋”“飯”攪和在一起,想換”蛋”“飯”中任何一種都很困難,耦合度很高,以至於”可維護性”比較差。軟件工程追求的目標之一就是可維護性,可維護性主要表現在3個方面:可理解性、可測試性和可修改性。面向對象的好處之一就是顯著的改善了軟件系統的可維護性。
看了這篇文章,簡單的總結一下!

面向過程

優點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;比如嵌入式開發、 Linux/Unix等一般採用面向過程開發,性能是最重要的因素。
缺點:沒有面向對象易維護、易復用、易擴展
面向對象

優點:易維護、易復用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護

缺點:性能比面向過程低

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

堡壘機的核心武器:WebSSH錄像實現

WebSSH終端錄像的實現終於來了

前邊寫了兩篇文章和深入介紹了終端錄製工具Asciinema,我們已經可以實現在終端下對操作過程的錄製,那麼在WebSSH中的操作該如何記錄並提供後續的回放審計呢?

一種方式是文章最後介紹的自動錄製審計日誌的方法,在主機上添加個腳本,每次連接自動進行錄製,但這樣不僅要在每台遠程主機添加腳本,會很繁瑣,而且錄製的腳本文件都是放在遠程主機上的,後續播放也很麻煩

那該如何更好處理呢?下文介紹一種優雅的方式來實現,核心思想是不通過錄製命令進行錄製,而在Webssh交互執行的過程中直接生成可播放的錄像文件

設計思路

通過上邊兩篇文章的閱讀,我們已經知道了Asciinema錄像文件主要由兩部分組成:header頭和IO流數據

header頭位於文件的第一行,定義了這個錄像的版本、寬高、開始時間、環境變量等參數,我們可以在websocket連接創建時將這些參數按照需要的格式寫入到文件

header頭數據如下,只有開頭一行,是一個字典形式

{"version": 2, "width": 213, "height": 55, "timestamp": 1574155029.1815443, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}, "title": "ops-coffee"}

整個錄像文件除了第一行的header頭部分,剩下的就都是輸入輸出的IO流數據,從websocket連接建立開始,隨着操作的進行,IO流數據是不斷增加的,直到整個websocket長連接的結束,那就需要在整個WebSSH交互的過程中不斷的往錄像文件追加輸入輸出的內容

IO流數據如下,每一行一條,列表形式,分別表示操作時間,輸入或輸出(這裏我們為了方便就寫固定字符串輸出),IO數據

[0.2341010570526123, "o", "Last login: Tue Nov 19 17:11:30 2019 from 192.168.105.91\r\r\n"]

似乎很完美,按照上邊的思路錄像文件就應該沒有問題了,但還有一些細節需要處理

首先是需要歷史連接列表,在這個列表裡可以看到什麼時間,哪個用戶連接了哪台主機,當然也需要提供回放功能,新建一張表來記錄這些信息

class Record(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')

    host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name='主機')
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用戶')

    filename = models.CharField(max_length=128, verbose_name='錄像文件名稱')

    def __str__(self):
        return self.host

其次還需要考慮的一個問題是header和後續IO數據流要寫入同一個文件,這就需要在整個websocket的連接過程中有一個固定的文件名可被讀取,這裏我使用了主機+用戶+當前時間作為文件名,同一用戶在同一時間不能多次連接同一主機,這樣可保證文件名不重複,同時避免操作寫入錯誤的錄像文件,文件名在websocket建立時初始化

def __init__(self, host, user, websocket):
    self.host = host
    self.user = user

    self.time = time.time()
    self.filename = '%s.%s.%d.cast' % (host, user, self.time)

IO流數據會持續不斷的寫入文件,這裏以一個獨立的方法來處理寫入

def record(self, type, data):
    RECORD_DIR = settings.BASE_DIR + '/static/record/'
    if not os.path.isdir(RECORD_DIR):
        os.makedirs(RECORD_DIR)

    if type == 'header':
        Record.objects.create(
            host=Host.objects.get(id=self.host),
            user=self.user,
            filename=self.filename
        )

        with open(RECORD_DIR + self.filename, 'w') as f:
            f.write(json.dumps(data) + '\n')
    else:
        iodata = [time.time() - self.time, 'o', data]
        with open(RECORD_DIR + self.filename, 'a', buffering=1) as f:
            f.write((json.dumps(iodata) + '\n'))

record接收兩個參數type和data,type標識本次寫入的是header頭還是IO流,data則是具體的數據

header只需要執行一次寫入,所以將其放在ssh的connect方法中,只在ssh連接建立時執行一次,在執行header寫入時同時往數據庫插入新的歷史記錄數據

調用record方法寫入header

def connect(self, host, port, username, authtype, password=None, pkey=None,
            term='xterm-256color', cols=80, rows=24):
    ...

    # 構建錄像文件header
    self.record('header', {
        "version": 2,
        "width": cols,
        "height": rows,
        "timestamp": self.time,
        "env": {
            "SHELL": "/bin/bash",
            "TERM": term
        },
        "title": "ops-coffee"
    })

IO流數據則需要與返回給前端的數據保持一致,這樣就能保證前端显示什麼錄像就播放什麼了,所以所有需要返回前端數據的地方都同時寫入錄像文件即可

調用record方法寫入io流數據

def connect(self, host, port, username, authtype, password=None, pkey=None,
            term='xterm-256color', cols=80, rows=24):
    ...

    # 連接建立一次,之後交互數據不會再進入該方法
    for i in range(2):
        recv = self.ssh_channel.recv(65535).decode('utf-8', 'ignore')
        message = json.dumps({'flag': 'success', 'message': recv})
        self.websocket.send(message)

        self.record('iodata', recv)

...

def _ssh_to_ws(self):
    try:
        with self.lock:
            while not self.ssh_channel.exit_status_ready():
                data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
                if len(data) != 0:
                    message = {'flag': 'success', 'message': data}
                    self.websocket.send(json.dumps(message))

                    self.record('iodata', data)
                else:
                    break
    except Exception as e:
        message = {'flag': 'error', 'message': str(e)}
        self.websocket.send(json.dumps(message))
        self.record('iodata', str(e))
        
        self.close()

由於命令執行與返回都是多線程的操作,這就會導致在寫入文件時出現文件亂序影響播放的問題,典型的操作有vim、top等,通過加鎖self.lock可以順利解決

最後歷史記錄頁面,當用戶點擊播放按鈕時,調用js彈出播放窗口

<div class="modal fade" id="modalForm">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-body" id="play">
      </div>
    </div>
  </div>
</div>

// 播放錄像
function play(host,user,time,file) {
  $('#play').html(
    '<asciinema-player id="play" title="WebSSH Record" author="ops-coffee.cn" author-url="https://ops-coffee.cn" author-img-url="/static/img/logo.png" src="/static/record/'+file+'" speed="3" '+
    'idle-time-limit="2" poster="data:text/plain,\x1b[1;32m'+time+
    '\x1b[1;0m用戶\x1b[1;32m'+user+
    '\x1b[1;0m連接主機\x1b[1;32m'+host+
    '\x1b[1;0m的錄像記錄"></asciinema-player>'
  )

  $('#modalForm').modal('show');
}

asciinema-player標籤的詳細參數介紹可以看這篇文章

演示與總結

在寫入文件的方案中,考慮了實時寫入和一次性寫入,實時寫入就像上邊這樣,所有的操作都會實時寫入錄像文件,好處是錄像不丟失,且能在操作的過程中進行實時的播放,缺點也很明顯,就是會頻繁的寫文件,造成IO開銷

一次性寫入可以在用戶操作的過程中將錄像數據寫入內存,在websocket關閉時一次性異步寫入到文件中,這種方案在最終寫入文件時可能因為種種原因而失敗,從而導致錄像丟失,還有個缺點是當你WebSSH操作時間過長時,會導致內存的持續增加

兩種方案一種是對磁盤的消耗另一種是對內存的消耗,各有利弊,當然你也可以考慮批量寫入,例如每分鐘寫一次文件,一分鐘之內的保存在內存中,平衡內存和磁盤的消耗,期待你的實現

相關文章推薦閱讀:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

NetCore3.0 文件上傳與大文件上傳的限制

NetCore文件上傳兩種方式

  NetCore官方給出的兩種文件上傳方式分別為“緩衝”、“流式”。我簡單的說說兩種的區別,

  1.緩衝:通過模型綁定先把整個文件保存到內存,然後我們通過IFormFile得到stream,優點是效率高,缺點對內存要求大。文件不宜過大。

  2.流式處理:直接讀取請求體裝載后的Section 對應的stream 直接操作strem即可。無需把整個請求體讀入內存,

以下為官方微軟說法

緩衝

  整個文件讀入 IFormFile,它是文件的 C# 表示形式,用於處理或保存文件。 文件上傳所用的資源(磁盤、內存)取決於併發文件上傳的數量和大小。 如果應用嘗試緩衝過多上傳,站點就會在內存或磁盤空間不足時崩潰。 如果文件上傳的大小或頻率會消耗應用資源,請使用流式傳輸。

流式處理   

  從多部分請求收到文件,然後應用直接處理或保存它。 流式傳輸無法顯著提高性能。 流式傳輸可降低上傳文件時對內存或磁盤空間的需求。

文件大小限制

  說起大小限制,我們得從兩方面入手,1應用服務器Kestrel 2.應用程序(我們的netcore程序),

1.應用服務器Kestre設置

  應用服務器Kestrel對我們的限制主要是對整個請求體大小的限制通過如下配置可以進行設置(Program -> CreateHostBuilder),超出設置範圍會報 BadHttpRequestException: Request body too large 異常信息

public static IHostBuilder CreateHostBuilder(string[] args) =>
           Host.CreateDefaultBuilder(args)
               .ConfigureWebHostDefaults(webBuilder =>
               {
                   webBuilder.ConfigureKestrel((context, options) =>
                   {
                       //設置應用服務器Kestrel請求體最大為50MB
                       options.Limits.MaxRequestBodySize = 52428800;
                   });
                   webBuilder.UseStartup<Startup>();
});

2.應用程序設置

  應用程序設置 (Startup->  ConfigureServices) 超出設置範圍會報InvalidDataException 異常信息

services.Configure<FormOptions>(options =>
 {
             options.MultipartBodyLengthLimit = long.MaxValue;
 });

通過設置即重置文件上傳的大小限制。

源碼分析

  這裏我主要說一下 MultipartBodyLengthLimit  這個參數他主要限制我們使用“緩衝”形式上傳文件時每個的長度。為什麼說是緩衝形式中,是因為我們緩衝形式在讀取上傳文件用的幫助類為 MultipartReaderStream 類下的 Read 方法,此方法在每讀取一次後會更新下讀入的總byte數量,當超過此數量時會拋出  throw new InvalidDataException($Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.);  主要體現在 UpdatePosition 方法對 _observedLength  的判斷

以下為 MultipartReaderStream 類兩個方法的源代碼,為方便閱讀,我已精簡掉部分代碼

Read

public override int Read(byte[] buffer, int offset, int count)
 {
          
          var bufferedData = _innerStream.BufferedData;
      int read;
      read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
          return UpdatePosition(read);
}

UpdatePosition

private int UpdatePosition(int read)
        {
            _position += read;
            if (_observedLength < _position)
            {
                _observedLength = _position;
                if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
                {
                    throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
                }
            }
            return read;
}

通過代碼我們可以看到 當你做了 MultipartBodyLengthLimit 的限制后,在每次讀取後會累計讀取的總量,當讀取總量超出

 MultipartBodyLengthLimit  設定值會拋出 InvalidDataException 異常,

最終我的文件上傳Controller如下

  需要注意的是我們創建 MultipartReader 時並未設置 BodyLengthLimit  (這參數會傳給 MultipartReaderStream.LengthLimit )也就是我們最終的限制,這裏我未設置值也就無限制,可以通過 UpdatePosition 方法體現出來

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.IO;
using System.Threading.Tasks;
 
namespace BigFilesUpload.Controllers
{
    [Route("api/[controller]")]
    public class FileController : Controller
    {
        private readonly string _targetFilePath = "C:\\files\\TempDir";
 
        /// <summary>
        /// 流式文件上傳
        /// </summary>
        /// <returns></returns>
        [HttpPost("UploadingStream")]
        public async Task<IActionResult> UploadingStream()
        {
 
            //獲取boundary
            var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
            //得到reader
            var reader = new MultipartReader(boundary, HttpContext.Request.Body);
            //{ BodyLengthLimit = 2000 };//
            var section = await reader.ReadNextSectionAsync();
 
            //讀取section
            while (section != null)
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
                if (hasContentDispositionHeader)
                {
                    var trustedFileNameForFileStorage = Path.GetRandomFileName();
                    await WriteFileAsync(section.Body, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
                }
                section = await reader.ReadNextSectionAsync();
            }
            return Created(nameof(FileController), null);
        }
 
        /// <summary>
        /// 緩存式文件上傳
        /// </summary>
        /// <param name=""></param>
        /// <returns></returns>
        [HttpPost("UploadingFormFile")]
        public async Task<IActionResult> UploadingFormFile(IFormFile file)
        {
            using (var stream = file.OpenReadStream())
            {
                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                await WriteFileAsync(stream, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
            }
            return Created(nameof(FileController), null);
        }
 
 
        /// <summary>
        /// 寫文件導到磁盤
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="path">文件保存路徑</param>
        /// <returns></returns>
        public static async Task<int> WriteFileAsync(System.IO.Stream stream, string path)
        {
            const int FILE_WRITE_SIZE = 84975;//寫出緩衝區大小
            int writeCount = 0;
            using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true))
            {
                byte[] byteArr = new byte[FILE_WRITE_SIZE];
                int readCount = 0;
                while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0)
                {
                    await fileStream.WriteAsync(byteArr, 0, readCount);
                    writeCount += readCount;
                }
            }
            return writeCount;
        }
 
    }
}

 

 總結:

如果你部署 在iis上或者Nginx 等其他應用服務器 也是需要注意的事情,因為他們本身也有對請求體的限制,還有值得注意的就是我們在創建文件流對象時 緩衝區的大小盡量不要超過netcore大對象的限制。這樣在併發高的時候很容易觸發二代GC的回收.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

【Stream—5】MemoryStream相關知識分享

一、簡單介紹一下MemoryStream

MemoryStream是內存流,為系統內存提供讀寫操作,由於MemoryStream是通過無符號字節數組組成的,可以說MemoryStream的性能可以算比較出色,所以它擔當起了一些其他流進行數據交互安時的中間工作,同時可降低應用程序中對臨時緩衝區和臨時文件的需求,其實MemoryStream的重要性不亞於FileStream,在很多場合,我們必須使用它來提高性能

二、MemoryStream和FileStream的區別

前文中也提到了,FileStream主要對文件的一系列操作,屬於比較高層的操作,但是MemoryStream卻很不一樣,他更趨向於底層內存的操作,這樣能夠達到更快速度和性能,也是他們的根本區別,很多時候,操作文件都需要MemoryStream來實際進行讀寫,最後放入相應的FileStream中,不僅如此,在諸如XmlWriter的操作中也需要使用MemoryStream提高讀寫速度

三、分析MemoryStream最常見的OutOfMemory異常

先看一下下面一段簡單的代碼

 1             //測試byte數組 假設該數組容量是256M
 2             var testBytes = new byte[256 * 1024 * 1024];
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 for (int i = 0; i < 1000; i++)
 7                 {
 8                     try
 9                     {
10                         ms.Write(testBytes, 0, testBytes.Length);
11                     }
12                     catch
13                     {
14                         Console.WriteLine("該內存流已經使用了{0}M容量的內存,該內存流最大容量為{1}M,溢出時容量為{2}M",
15                             GC.GetTotalMemory(false) / (1024 * 1024),//MemoryStream已經消耗內存量
16                             ms.Capacity / (1024 * 1024), //MemoryStream最大的可用容量
17                             ms.Length / (1024 * 1024));//MemoryStream當前流的長度(容量)
18                         break;
19                     }
20                 }
21             }
22             Console.ReadLine();

輸出結果:

 

 

 從輸出結果來看,MemoryStream默認可用最大容量是1024M,發生異常時正好是其最大容量。

問題來了,假設我們需要操作比較大的文件,該怎麼辦呢?其實有2種方法可以搞定,一種是分段處理,我們將Byte數組分成等份進行處理,還有一種方式便是增加MomoryStream的最大可用容量(字節),我們可以在聲明MomoryStream的構造函數時利用它的重載版本:MemoryStream(int capacity)

到底使用哪種方法比較好呢?其實筆者認為具體項目具體分析,前者分段處理的確能夠解決大數據量操作的問題,但是犧牲了性能和時間(多線程暫時不考慮),後者可以得到性能上的優勢,但是其允許最大容量是 int.Max,所以無法給出一個明確的答案,大家在做項目時,按照需求自己定製即可,最關鍵的還是要取到性能和開銷的最佳點位,還有一種更噁心的溢出方式,往往會讓大家抓狂,就是不定時溢出,就是MemoryStream處理的文件可能只有40M或更小時,也會發生OutOfMemory的異常,關於這個問題,終於在老外的一篇文章中得到了解釋,運氣還不錯,可以看看這篇博文:,由於涉及到windows的內存機制,包括內存也,進程的虛擬地址空間等,比較複雜,所以大家看他的文章前,我先和大家簡單的介紹一下頁和進程的虛擬地址:

內存頁:內存頁分為:文件頁和計算頁

內存中的文件頁是文件緩存區,即文件型的內存頁,用於存放文件數據的內存頁(也稱永久頁),作用在於讀寫文件時可以減少對磁盤的訪問,如果它的大小設置的太小,會引起系統頻繁的訪問磁盤,增加磁盤I/O,設置太大,會浪費內存資源。

內存中的計算頁也稱為計算型的內存頁,主要用於存放程序代碼和臨時使用的數據。

進程的虛擬地址:每一個進程被給予它的非常自由的虛擬地址空間。對於32位的進程,地址空間是4G,因為一個32位指針能夠從0x00000000到0xffffffff之間的任意值,這個範圍允許指針從4294967296個值得一個,覆蓋了一個進程得4G範圍,對於64位進程,地址空間是16eb因為一個64位指針能夠指向18,446,744,073,709,551,616個值中的一個,覆蓋一個進程的16eb範圍,這是十分寬廣的範圍,上述概念都在自windows核心編程這本書,其實這本書對於我們程序員來說很重要,對於內存的操作,本人也是小白。

四、MemoryStream的構造函數

1、MemoryStream()

MemoryStream允許不帶參數的構造

2、MemoryStream(byte[] byte)

Byte數組是包含了一定數據的byte數組,這個構造很重要,初學者或者用的不是很多的程序員會忽略這個構造函數導致後面讀取或寫入數據時發現MemoryStream中沒有byte數據,會導致很鬱悶的感覺,大家注意一下就行,有時候也可能無需這樣,因為很多方法返回值已經是MemoryStream了。

3、MemoryStream(int capacity)

這個是重中之重,為什麼這麼說呢?我在本文探討關於OutMemory異常中也提到了,如果你想額外提高MemoryStream的吞吐量,也只能靠這個方法提升一定的吞吐量,最多也只能到int.Max,這個方法也是解決OutOfMemory的一個可行方案。

4、MemoryStream(byte[] byte,bool writeable)

writeable參數定義該流是否可寫

5、MemoryStream(byte[] byte,int index,int count)

index:參數定義從byte數組中的索引index

count:參數是獲取的數據量的個數

6、MemoryStream(byte[] byte,int index,int count,bool writeable,bool publiclyVisible)

publiclyVisible:參數表示true可以啟用GetBuffer方法,它返回無符號字節數組,流從該數組創建,否則為false。(大家一定覺得這個很難理解,別急,下面的方法中我會詳細的講一下這個東西)

五、MemoryStream的屬性

 Memory的屬性大致都是和其父類很相似,這些功能在我的這篇文章中已經詳細討論過,所以我簡單列舉以下其屬性:

 

 其獨有的屬性:

Capacity:這個前文其實已經提及,它表示該流的可支配容量(字節),非常重要的一個屬性。

六、MemoryStream的方法

對於重寫的方法,這裏不再重複說明,大家可以去看一下

 以下是MemoryStream獨有的方法

1、virtual byte[] GetBuffer()

這個方法使用時需要小心,因為這個方法返回無符號字節數組,也就是說,即使我只輸入幾個字符例如“HellowWorld”我們只希望返回11個數據就行,可是這個方法會把整個緩衝區的數據,包括那些已經分配但是實際上沒有用到的字符數據都返回回來了,如果想啟用這個方法那必須使用上面最後一個構造函數,將publiclyVisible屬性設置成true就行,這也是上面那個構造函數的錯用所在。

2、virtual void WriteTo(Stream stream)

這個方法的目的其實在本文開始的時候討論性能問題時已經指出,MemoryStream常用起中間流的作用,所以讀寫在處理完后將內存吸入其他流中。

七、示例:

1、XmlWriter中使用MemoryStream

 1         public static void UseMemoryStreamInXmlWriter()
 2         {
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 //定義一個XmlWriter
 7                 using (XmlWriter writer= XmlWriter.Create(ms))
 8                 {
 9                     //寫入xml頭
10                     writer.WriteStartDocument(true);
11                     //寫入一個元素
12                     writer.WriteStartElement("Content");
13                     //為這個元素增加一個test屬性
14                     writer.WriteStartAttribute("test");
15                     //設置test屬性的值
16                     writer.WriteValue("萌萌小魔王");
17                     //釋放緩衝,這裏可以不用釋放,但是在實際項目中可能要考慮部分釋放對性能帶來的提升
18                     writer.Flush();
19                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false)/1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length,4)}byte,默認容量為:{ms.Capacity}byte");
20                     Console.WriteLine($"重新定位前MemoryStream所在的位置是{ms.Position}");
21                     //將流中所在當前位置往後移動7位,相當於空格
22                     ms.Seek(7, SeekOrigin.Current);
23                     Console.WriteLine($"重新定位后MemoryStream所存在的位置是{ms.Position}");
24                     //如果將流所在的位置設置位如下示的位置,則XML文件會被打亂
25                     //ms.Position = 0;
26                     writer.WriteStartElement("Content2");
27                     writer.WriteStartAttribute("testInner");
28                     writer.WriteValue("萌萌小魔王2");
29                     writer.WriteEndElement();
30                     writer.WriteEndElement();
31                     //再次釋放
32                     writer.Flush();
33                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false) / 1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length, 4)}byte,默認容量為:{ms.Capacity}byte");
34                     //建立一個FileStream 文件創建目的地是f:\test.xml
35                     var fs=new FileStream(@"f:\test.xml",FileMode.OpenOrCreate);
36                     using (fs)
37                     {
38                         //將內存流注入FileStream
39                         ms.WriteTo(fs);
40                         if (ms.CanWrite)
41                         {
42                             //釋放緩衝區
43                             fs.Flush();
44                         }
45                     }
46                     Console.WriteLine();
47                 }
48             }
49         }

運行結果:

 

 咱看一下XML文本是什麼樣的?

 

 2、自定義處理圖片的HttpHandler

有時項目里我們必須將圖片進行一定的操作,例如:水印,下載等,為了方便和管理我們可以自定義一個HttpHandler來負責這些工作

後台代碼:

 1     public class ImageHandler : IHttpHandler
 2     {
 3         /// <summary>
 4         /// 實現IHttpHandler接口中ProcessRequest方法
 5         /// </summary>
 6         /// <param name="context"></param>
 7         public void ProcessRequest(HttpContext context)
 8         {
 9             context.Response.Clear();
10             //得到圖片名
11             var imageName = context.Request["ImageName"] ?? "小魔王";
12             //得到圖片地址
13             var stringFilePath = context.Server.MapPath($"~/Image/{imageName}.jpg");
14             //聲明一個FileStream用來將圖片暫時放入流中
15             FileStream stream=new FileStream(stringFilePath,FileMode.Open);
16             using (stream)
17             {
18                 //通過GetImageFromStream方法將圖片放入Byte數組中
19                 var imageBytes = GetImageFromStream(stream, context);
20                 //上下文確定寫道客戶端時的文件類型
21                 context.Response.ContentType = "image/jpeg";
22                 //上下文將imageBytes中的數組寫到前端
23                 context.Response.BinaryWrite(imageBytes);
24             }
25         }
26 
27         public bool IsReusable => true;
28 
29         /// <summary>
30         /// 將流中的圖片信息放入byte數組后返回該數組
31         /// </summary>
32         /// <param name="stream">文件流</param>
33         /// <param name="context">上下文</param>
34         /// <returns></returns>
35         private byte[] GetImageFromStream(FileStream stream, HttpContext context)
36         {
37             //通過Stream到Image
38             var image = Image.FromStream(stream);
39             //加上水印
40             image = SetWaterImage(image, context);
41             //得到一個ms對象
42             MemoryStream ms = new MemoryStream();
43             using (ms)
44             {
45                 //將圖片保存至內存流
46                 image.Save(ms,ImageFormat.Jpeg);
47                 byte[] imageBytes = new byte[ms.Length];
48                 ms.Position = 0;
49                 //通過內存流放到imageBytes
50                 ms.Read(imageBytes, 0, imageBytes.Length);
51                 //ms.Close();
52                 //返回imageBytes
53                 return imageBytes;
54             }
55         }
56 
57         private Image SetWaterImage(Image image, HttpContext context)
58         {
59             Graphics graphics = Graphics.FromImage(image);
60             Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/logo.jpg"));
61             graphics.DrawImage(waterImage, new Point { X = image.Size.Width - waterImage.Size.Width, Y = image.Size.Height - waterImage.Size.Height });
62             return image;
63         }
64     }

別忘了,還要再web.config中進行配置,如下:

 

 這樣前台就能使用了

 

 讓我們來看一下輸出結果:

 

 哈哈,還不錯。

好了,MemoryStream相關的知識就先分享到這裏了。同志們,再見!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

fastjason常用方法

什麼是fastjson?

Fastjson是一個Java語言編寫的高性能功能完善的JSON庫。它採用一種“假定有序快速匹配”的算法,把JSON Parse的性能提升到極致,是目前Java語言中最快的JSON庫。Fastjson接口簡單易用,已經被廣泛使用在緩存序列化、協議交互、Web輸出、Android客戶端等多種應用場景。

主要特點:

  • 快速FAST (比其它任何基於Java的解析器和生成器更快,包括jackson)
  • 強大(支持普通JDK類包括任意Java Bean Class、Collection、Map、Date或enum)
  • 零依賴(沒有依賴其它任何類庫除了JDK)

背景

最近關於fastjson的消息,引起了很多人的關注!

fastjson爆出重大漏洞,攻擊者可使整個業務癱瘓

漏洞描述

常用JSON組件FastJson存在遠程代碼執行漏洞,攻擊者可通過精心構建的json報文對目標服務器執行任意命令,從而獲得服務器權限。此次爆發的漏洞為以往漏洞中autoType的繞過。

影響範圍

FastJson < 1.2.48

很多開發者才猛然發現,fastjson已經深入到我們開發工作的方方面面。那麼除了趕快升級你的json外,我們來挖挖fastjson最常用的用法。

fastjson常用方式

1.maven依賴(記得升級到1.2.48以上版本哦)

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.62</version>
        </dependency>    

2.FastJson對於json格式字符串的解析主要用到了一下三個類:

(1)JSON:fastJson的解析器,用於JSON格式字符串與JSON對象及javaBean之間的轉換。

(2)JSONObject:fastJson提供的json對象。

(3)JSONArray:fastJson提供json數組對象。

3.常用方式

3.1 string和java對象

 

實例1:對象轉json字符串

        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);

輸出結果為:

{"code":"0","message":"ok"}

實例2:字符串轉對象

        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);
        
        Map obj=(Map)JSON.parse(json);
        System.out.println("code="+obj.get("code")+",message="+obj.get("message"));

輸出結果

{"code":"0","message":"ok"}
code=0,message=ok

3.2 工具類JSONObject

    public static void main(String[] args) {
        Map<String,String> map=new HashMap<String,String>();
        map.put("code","0");
        map.put("message","ok");
        String json=JSON.toJSONString(map);
        System.out.println(json);
        
        Map obj=(Map)JSON.parse(json);
        System.out.println("code="+obj.get("code")+",message="+obj.get("message"));        
        
        String code=JSON.parseObject(json).getString("code");
        String message=JSON.parseObject(json).getString("message");
        System.out.println("code="+code+",message="+message);
    }

輸出結果

{"code":"0","message":"ok"}
code=0,message=ok
code=0,message=ok

3.3 數組對象

List<user> list=new ArrayList<user>(JSONArray.parseArray(jsonString,user.class)); 

Fastjson 與各種JSON庫的性能比較:

 

json庫 序列化性能 反序列化性能 jar大小
fastjson 1201 1216 fastjson-1.1.26.jar(356k)
fastjson-1.1.25-android.jar(226k)
jackson 1408 1915 jackson-annotations-2.1.1.jar(34k)
jackson-core-2.1.1.jar(206k)
jackson-databind-2.1.1.jar(922k)
總共1162k
gson 7421 5065 gson-2.2.2.jar(189k)
json-lib 27555 87292 json-lib-2.4-jdk15.jar(159k)


本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

Spring Boot2 系列教程(二十五)Spring Boot 整合 Jpa 多數據源

本文是 Spring Boot 整合數據持久化方案的最後一篇,主要和大夥來聊聊 Spring Boot 整合 Jpa 多數據源問題。在 Spring Boot 整合JbdcTemplate 多數據源、Spring Boot 整合 MyBatis 多數據源以及 Spring Boot 整合 Jpa 多數據源這三個知識點中,整合 Jpa 多數據源算是最複雜的一種,也是很多人在配置時最容易出錯的一種。本文大夥就跟着松哥的教程,一步一步整合 Jpa 多數據源。

工程創建

首先是創建一個 Spring Boot 工程,創建時添加基本的 Web、Jpa 以及 MySQL 依賴,如下:

創建完成后,添加 Druid 依賴,這裏和前文的要求一樣,要使用專為 Spring Boot 打造的 Druid,大夥可能發現了,如果整合多數據源一定要使用這個依賴,因為這個依賴中才有 DruidDataSourceBuilder,最後還要記得鎖定數據庫依賴的版本,因為可能大部分人用的還是 5.x 的 MySQL 而不是 8.x。完整依賴如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.28</version>
    <scope>runtime</scope>
</dependency>

如此之後,工程就創建成功了。

基本配置

在基本配置中,我們首先來配置多數據源基本信息以及 DataSource,首先在 application.properties 中添加如下配置信息:

#  數據源一
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.one.url=jdbc:mysql:///test01?useUnicode=true&characterEncoding=UTF-8
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource

#  數據源二
spring.datasource.two.username=root
spring.datasource.two.password=root
spring.datasource.two.url=jdbc:mysql:///test02?useUnicode=true&characterEncoding=UTF-8
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource

# Jpa配置
spring.jpa.properties.database=mysql
spring.jpa.properties.show-sql=true
spring.jpa.properties.database-platform=mysql
spring.jpa.properties.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

這裏 Jpa 的配置和上文相比 key 中多了 properties,多數據源的配置和前文一致,然後接下來配置兩個 DataSource,如下:

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    @Primary
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}

這裏的配置和前文的多數據源配置基本一致,但是注意多了一個在 Spring 中使用較少的註解 @Primary,這個註解一定不能少,否則在項目啟動時會出錯,@Primary 表示當某一個類存在多個實例時,優先使用哪個實例。

好了,這樣,DataSource 就有了。

多數據源配置

接下來配置 Jpa 的基本信息,這裏兩個數據源,我分別在兩個類中來配置,先來看第一個配置:

@Configuration
@EnableJpaRepositories(basePackages = "org.javaboy.jpa.dao",entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanOne",transactionManagerRef = "platformTransactionManagerOne")
public class JpaConfigOne {
    @Autowired
    @Qualifier(value = "dsOne")
    DataSource dsOne;
    @Autowired
    JpaProperties jpaProperties;
    @Bean
    @Primary
    LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsOne)
                .packages("org.javaboy.jpa.model")
                .properties(jpaProperties.getProperties())
                .persistenceUnit("pu1")
                .build();
    }
    @Bean
    PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean factoryBeanOne = localContainerEntityManagerFactoryBeanOne(builder);
        return new JpaTransactionManager(factoryBeanOne.getObject());
    }
}

首先這裏注入 dsOne,再注入 JpaProperties,JpaProperties 是系統提供的一個實例,裡邊的數據就是我們在 application.properties 中配置的 jpa 相關的配置。然後我們提供兩個 Bean,分別是 LocalContainerEntityManagerFactoryBean 和 PlatformTransactionManager 事務管理器,不同於 MyBatis 和 JdbcTemplate,在 Jpa 中,事務一定要配置。在提供 LocalContainerEntityManagerFactoryBean 的時候,需要指定 packages,這裏的 packages 指定的包就是這個數據源對應的實體類所在的位置,另外在這裏配置類上通過 @EnableJpaRepositories 註解指定 dao 所在的位置,以及 LocalContainerEntityManagerFactoryBean 和 PlatformTransactionManager 分別對應的引用的名字。

好了,這樣第一個就配置好了,第二個基本和這個類似,主要有幾個不同點:

  • dao 的位置不同
  • persistenceUnit 不同
  • 相關 bean 的名稱不同

注意實體類可以共用。

代碼如下:

@Configuration
@EnableJpaRepositories(basePackages = "org.javaboy.jpa.dao2",entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanTwo",transactionManagerRef = "platformTransactionManagerTwo")
public class JpaConfigTwo {
    @Autowired
    @Qualifier(value = "dsTwo")
    DataSource dsTwo;
    @Autowired
    JpaProperties jpaProperties;
    @Bean
    LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanTwo(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsTwo)
                .packages("org.javaboy.jpa.model")
                .properties(jpaProperties.getProperties())
                .persistenceUnit("pu2")
                .build();
    }
    @Bean
    PlatformTransactionManager platformTransactionManagerTwo(EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean factoryBeanTwo = localContainerEntityManagerFactoryBeanTwo(builder);
        return new JpaTransactionManager(factoryBeanTwo.getObject());
    }
}

接下來,在對應位置分別提供相關的實體類和 dao 即可,數據源一的 dao 如下:

package org.javaboy.jpa.dao;
public interface UserDao extends JpaRepository<User,Integer> {
    List<User> getUserByAddressEqualsAndIdLessThanEqual(String address, Integer id);
    @Query(value = "select * from t_user where id=(select max(id) from t_user)",nativeQuery = true)
    User maxIdUser();
}

數據源二的 dao 如下:

package org.javaboy.jpa.dao2;
public interface UserDao2 extends JpaRepository<User,Integer> {
    List<User> getUserByAddressEqualsAndIdLessThanEqual(String address, Integer id);

    @Query(value = "select * from t_user where id=(select max(id) from t_user)",nativeQuery = true)
    User maxIdUser();
}

共同的實體類如下:

package org.javaboy.jpa.model;
@Entity(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String address;
    //省略getter/setter
}

到此,所有的配置就算完成了,接下來就可以在 Service 中注入不同的 UserDao,不同的 UserDao 操作不同的數據源。

其實整合 Jpa 多數據源也不算難,就是有幾個細節問題,這些細節問題解決,其實前面介紹的其他多數據源整個都差不多。

好了,本文就先介紹到這裏。

相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

CSS:CSS彈性盒子布局 Flexible Box

一、簡介

flexbox:全稱Flexible Box, 彈性盒子布局。可以簡單實現各種伸縮性的設計,它是由伸縮容器和伸縮項目組成。任何一個元素都可以指定為flexbox布局。這種新的布局方案在2009年是由W3C組織提出來的,在此之前,Web開發一般使用基於盒子模型的傳統頁面布局,依賴定位屬性、流動屬性和显示屬性來解決,參看鏈接:。彈性盒子布局的出現,極大的方便了開發者,在如今的ReactNative開發中,也已經被引入使用。

伸縮流布局結構圖如下:

彈性盒子布局具備的特徵:

1、伸縮容器的子元素稱為伸縮項目,伸縮項目使用伸縮布局來排版。伸縮布局和傳統布局不一樣,它按照伸縮流方向布局。

2、伸縮容器由兩條軸構成,分別為主軸(main axis)和交叉軸(cross axis)。主軸既可以用水平軸,也可以是豎直軸,根據開發者需要來決定。

3、主軸的起點叫main start,終點叫main end,主軸的空間用main size表示。

4、交叉軸的起點叫cross start,終點叫cross end,交叉軸的空間用cross size表示。

5、默認情況下,伸縮項目總是沿着主軸方向排版,從開始位置到終點位置。至於換行显示,則通過設置伸縮屬性來實現。

6、伸縮容器的屬性有:display、flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content

7、伸縮項目的屬性有: order、flex-grow、flex-shrink、flex-basis、flex、align-self

 

二、伸縮容器的屬性,全局設置排版

HTML:[注意:下面的演示截圖項目個數會根據需要選擇性註釋“flex-item”,有時用不到5個]

<!DOCTYPE html>
<html>
<head>
    <title>Flexbox</title>
    <!--  採用外聯方式導入css文件 -->
    <link rel="stylesheet" type="text/css" href="./css_test.css">
</head>
<body>
    <span class="flex-container"> 
        <span class="flex-item" id="item1" style="color:white;font-size:20px">1</span>
        <span class="flex-item" id="item2" style="color:white;font-size:20px">2</span>
        <span class="flex-item" id="item3" style="color:white;font-size:20px">3</span>
        <span class="flex-item" id="item4" style="color:white;font-size:20px">4</span>
        <span class="flex-item" id="item5" style="color:white;font-size:20px">5</span>
    </span>
</body>
</html> 

1、display:決定元素是否為伸縮容器

  • flex:產生塊級伸縮容器
    .flex-container {
         display: flex;
     }
  • inline-flex:產生行內塊級伸縮容器
  •  .flex-container {
         display: inline-flex;
     }

2、flex-direction:指定伸縮容器主軸的方向

  • row:水平方向,從左到右
     .flex-container {
         display: flex;
         flex-direction: row;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; 
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • row-reverse:水平方向,從右到左
     .flex-container {
         display: flex;
         flex-direction: row-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; 
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • column:豎直方向,從上到下
     .flex-container {
         display: flex;
         flex-direction: column;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px; 
         background-color: green;
         margin: 1px;
     }

  • column-reverse:豎直方向,從下到上
     .flex-container {
         display: flex;
         flex-direction: column-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px; 
         background-color: green;
         margin: 1px;
     }

3、flex-wrap:指定伸縮容器主軸方向空間不足時,決定是否換行以及換行方式

  • nowarp:不換行
    .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • warp:換行,若主軸為水平方向,換行方向是從上到下
     .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • wrap-reverse:換行,若主軸為水平方向,換行方向是從下到上
     .flex-container {
         display: flex;
         flex-direction: row;
         flex-wrap: wrap-reverse;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

4、flex-flow:flex-direction和flex-wrap的縮寫,同時指定伸縮容器主軸方向和換行設置

  • row nowrap:默認主軸是水平方向,從左到右,且不換行
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

5、justify-content:決定伸縮項目沿着主軸線的對齊方式

  • flex-start:與主軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與主軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與主軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • space-between:平均分配到主軸線里,第一個項目靠齊起始位置,最後一個項目靠齊終點位置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: space-between;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • sapce-around:平均分配到主軸線里,兩端保留一半的空間
    .flex-container {
         display: flex;
         flex-flow: row wrap;
         justify-content: space-around;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

6、align-items:決定伸縮項目不能換行時沿着交叉軸線的對齊方式

  • flex-start:與交叉軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與交叉軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //下圖單行狀態寬度被重新計算
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與交叉軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • baseline:根據基線對齊
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: baseline;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         padding-top: 25px;
     }
    
     #item2 {
         padding-top: 20px;
     }
      
     #item3 {
         padding-top: 15px;
     }
    
     #item4 {
         padding-top: 10px;
     }
      
     #item5 {
         padding-top: 5px;
     }

  • stretch:沿着交叉軸線拉伸填充整個伸縮容器
     .flex-container {
         display: flex;
         flex-flow: row nowrap;
         align-items: stretch;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;//此時可以設置寬度,但不能設置高度,否則無法拉伸
         background-color: green;
         margin: 1px;
     }

7、align-content:決定伸縮項目可以換行時沿着交叉軸線的對齊方式,flex-warp:warp一定要開啟

  • flex-start:與交叉軸線起始位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:flex-start;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • flex-end:與交叉軸線結束位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:flex-end;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • center:與主軸線中間位置靠齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:center;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • space-between:平均分配到主軸線里,第一行項目靠齊起始位置,最後一行項目靠齊終點位置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:space-between;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • sapce-around:所有行平均分配到主軸線里,兩端保留一半的空間
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:space-around;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }

  • stretch:沿着交叉軸
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         align-content:stretch;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px; //不要設置高度,不然無法拉伸
         background-color: green;
         margin: 1px;
     }

 

三、伸縮項目的屬性,單個設置排版

1、order:定義伸縮項目的排列順序。數值越小,排列越靠前,默認值為0。

  • 表達式 order: integer;
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item4 {
         order: -1;
     }
    
     #item5 {
         order: -2;
     }

2、flex-grow:定義伸縮項目的放大比例,默認值為0,表示即使存在剩餘空間,也不放大。

  • 表達式 flex-grow: number;
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex-grow: 1; //空間不足,item2不會放大
     }
    
     #item4 {
         flex-grow: 1; //item4放大填滿剩餘空間
     }

3、flex-shrink:定義伸縮項目的收縮比例,默認值為1。

  • 表達式 flex-shrink: numer;
    .flex-container {
         display: flex;
         flex-flow: row nowrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item4 {
         flex-shrink:3; //單行,空間有限,item4縮小為原來的1/3
     }
    
     #item5 {
         flex-shrink:4;  //單行,空間有限,item5縮小為原來的1/5
    }

4、flex-basis:定義伸縮項目的基準值,剩餘空間按照比例進行伸縮,默認auto。

  •  auto
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item5 {
         flex-basis:auto;
     }

            

  • flex-basis: length 
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item5 {
         flex-basis:200px;
     }

5、flex:是flex-grow、flex-shrink、flex-basis的縮寫,默認值 0 1 auto。

  • none: 不設置
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex: none; /* 等同於 flex: 0 0 auto */
     }

  • flex-grow flex-shrink flex-basis: 設置放大或縮小或基準線
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item2 {
         flex: 1; /* 等同於 flex: 1 1 auto 或者 等同於 flex: auto*/
     }

6、align-self:用來設置伸縮項目在交叉軸的對齊方式。

  • auto:自動對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: auto;
     }

  • flex-start: 向交叉軸的開始位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: flex-start;
     }

  • flex-end: 向交叉軸的結束位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: flex-end;
     }

  • center: 向交叉軸的中間位置對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item3 {
         align-self: center;
     }

  • baseline:向交叉軸的基準線對齊
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         height: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         align-self: baseline;
         margin-top: 50px;
     }
    
     #item2 {
         align-self: baseline;
     }

  • stretch: 在交叉軸拉伸填滿伸縮容器
     .flex-container {
         display: flex;
         flex-flow: row wrap;
         width: 160px;
         height: 160px;
         background-color: red;
     }
    
     .flex-item {
         width: 50px;
         background-color: green;
         margin: 1px;
     }
    
     #item1 {
         align-self: stretch;
     }
    
     #item2 {
         align-self: stretch;
     }
    
     #item3 {
         align-self: stretch;
     }

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象