文:宋瑞文
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
摘錄自2019年10月4日自由時報報導
據報導,北韓於5月份出現首起非洲豬瘟病例,韓國則努力地避免疫情擴散過來,甚至在邊界建起圍欄。韓國各處養豬場已確認有13起病例,不過,長4公里、佈滿地雷、守衛嚴密的南北韓非軍事區(DMZ)更發現患有非洲豬瘟的野豬,目前推測韓國的疫情可能是被帶原的野豬給傳染過來的。
DMZ向來避免擦槍走火,但韓國軍方已被授權殺死穿越DMZ的野豬。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※新北清潔公司,居家、辦公、裝潢細清專業服務
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
摘錄自2019年10月3日自由時報報導
米塔颱風在週三晚間襲擊南韓南部,南韓南部港口城市釜山發生土石流,南韓行政安全部3日表示,截至下午,全國共有9人喪生,但傷亡人數預計會在攀升,因為還有數人下落不明。
南韓國防部表示,米塔風災造成1000多棟房屋損壞,超過1500人提前被撤離。
米塔離開朝鮮半島後,已往日本西部前進,同樣帶來驚人雨量,日本氣象廳警告,儘管米塔颱風可能在4日減弱為溫帶氣旋,但朝著日本北部前進時,強風豪雨可能引發洪水、土石流等災情。
【動画】高知県内で大雨 道路冠水も
— 高知新聞 (公式)Kochinews (@Kochi_news)
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※新北清潔公司,居家、辦公、裝潢細清專業服務
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
摘錄自2019年10月3日自由時報報導
澳洲詹姆士庫克大學(James Cook University)3日宣布,當地攝影師在凱恩斯(Cairns)的郊區拍到「火焰茸」,經科學家確定是一種又被稱為毒火珊瑚(Poison Fire Coral)的真菌。
外媒稱,火焰茸的原生棲地在日本和南韓的山區,這種鮮紅色的真菌經常被誤認為是可食用的紅珊瑚菌(Clavulinopsis miyabeana)、冬蟲夏草,誤食而中毒甚至死亡。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
摘錄自2019年10月3日自由時報報導
美國太空總署(NASA)探測器「洞察號」(InSight)去年底飛抵火星表面至今,,分別從今年5月22日發生的「Sol 173震盪」及7月25日的「Sol 235震盪」錄下,這兩次火星震若換算成常用單位,分別為芮氏規模3.7與3.3。
「洞察號」火星任務最主要的目標是探測火星內部結構,科學家希望藉此理解,所謂「類地行星」(terrestrial planet,又稱為岩質行星)是如何形成的。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?

項目演示地址
React 框架的優雅不言而喻,組件化的編程思想使得React框架開發的項目代碼簡潔,易懂,但早期 React 類組件的寫法略顯繁瑣。React Hooks 是 React 16.8 發布以來最吸引人的特性之一,她簡化了原有代碼的編寫,是未來 React 應用的主流寫法。
本文通過一個實戰小項目,手把手從零開始帶領大家快速入門React Hooks。
在本項目中,會用到以下知識點:
(1)安裝node.js 官網鏈接
(2)安裝vscode 官網鏈接
(3)安裝 creat-react-app 功能組件,該組件可以用來初始化一個項目, 即按照一定的目錄結構,生成一個新項目。
打開cmd 窗口 輸入:
npm install --g create-react-app
npm install --g yarn
(-g 代表全局安裝)
如果安裝失敗或較慢。需要換源,可以使用淘寶NPM鏡像,設置方法為:
npm config set registry https://registry.npm.taobao.org
設置完成后,重新執行
npm install --g create-react-app
npm install --g yarn
(4)在你想創建項目的目錄下 例如 D:/project/ 打開cmd命令 輸入
create-react-app react-exam
去使用creat-react-app命令創建名字是react-exam的項目
安裝完成后,移至新創建的目錄並啟動項目
cd react-exam
yarn start
一旦運行此命令,localhost:3000新的React應用程序將彈出一個新窗口。
右鍵react-exam目錄,使用vscode打開該目錄。
react-exam項目目錄中有一個/public和/src目錄,以及node_modules,.gitignore,README.md,和package.json。
在目錄/public中,重要文件是index.html,其中一行代碼最重要
<div id="root"></div>
該div做為我們整個應用的掛載點
/src目錄將包含我們所有的React代碼。
要查看環境如何自動編譯和更新您的React代碼,請找到文件/src/App.js:
將其中的
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
修改為
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
和豆約翰 Learn React
</a>
保存文件后,您會注意到localhost:3000編譯並刷新了新數據。
1.安裝項目依賴,在package.json中添加:
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"axios": "^0.19.2",
"bootstrap": "^4.5.0",
"he": "^1.2.0",
"react-loading": "^2.0.3",
"reactstrap": "^8.4.1"
},
執行命令:
yarn install
修改index.js,導入bootstrap樣式
import "bootstrap/dist/css/bootstrap.min.css";
修改App.css代碼
html {
width: 80%;
margin-left: 10%;
margin-top: 2%;
}
.ansButton {
margin-right: 4%;
margin-top: 4%;
}
修改App.js,引入Quiz組件
import React from 'react';
import './App.css'
import { Quiz } from './Exam/Quiz';
function App() {
return (
<div className = 'layout'>
<Quiz></Quiz>
</div>
);
}
export default App;
在項目src目錄下新增Exam目錄,Exam目錄中新建Quiz.js
Quiz組件的定義如下:
Quiz.js,引入開始頁面組件Toggle。
import React, { useState } from "react";
import { Toggle } from "./Toggle";
export const Quiz = () => {
const [questionData, setQuestionData] = useState([]);
const questions = questionData.map(({ question }) => [question]);
const answers = questionData.map(({ incorrect_answers, correct_answer }) =>
[correct_answer, incorrect_answers].flat()
);
return (
<>
<Toggle
setQuestionData={setQuestionData}
/>
</>
);
};
Toggle.js,點擊開始按鈕,通過axios訪問遠程接口,獲得題目及答案。
import React from "react";
import axios from "axios";
import ToggleHeader from "./ToggleHeader";
import {
Button,
Form,
} from "reactstrap";
export const Toggle = ({
setQuestionData,
}) => {
const getData = async () => {
try {
const incomingData = await axios.get(
`https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
);
setQuestionData(incomingData.data.results);
} catch (err) {
console.error(err);
}
};
return (
<>
<ToggleHeader />
<Form
onSubmit={(e) => {
e.preventDefault();
getData();
}}
>
<Button color="primary">開始</Button>
</Form>
</>
);
};
ToggleHeader.js
import React from "react";
import { Jumbotron, Container} from "reactstrap";
export default function ToggleHeader() {
return (
<Jumbotron fluid>
<Container fluid>
<h1 className="display-4">計算機知識小測驗</h1>
</Container>
</Jumbotron>
);
}
https://opentdb.com/api.php接口返回的json數據格式為
{
"response_code": 0,
"results": [{
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "The numbering system with a radix of 16 is more commonly referred to as ",
"correct_answer": "Hexidecimal",
"incorrect_answers": ["Binary", "Duodecimal", "Octal"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "This mobile OS held the largest market share in 2012.",
"correct_answer": "iOS",
"incorrect_answers": ["Android", "BlackBerry", "Symbian"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "How many values can a single byte represent?",
"correct_answer": "256",
"incorrect_answers": ["8", "1", "1024"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "In computing, what does MIDI stand for?",
"correct_answer": "Musical Instrument Digital Interface",
"incorrect_answers": ["Musical Interface of Digital Instruments", "Modular Interface of Digital Instruments", "Musical Instrument Data Interface"]
}, {
"category": "Science: Computers",
"type": "multiple",
"difficulty": "easy",
"question": "In computing, what does LAN stand for?",
"correct_answer": "Local Area Network",
"incorrect_answers": ["Long Antenna Node", "Light Access Node", "Land Address Navigation"]
}]
}
程序運行效果:
當前項目目錄結構為:
Quiz.js,新增toggleView變量用來切換視圖。
const [toggleView, setToggleView] = useState(true);
Quiz.js,其中Question和QuestionHeader 組件,參見後面。
import { Question } from "./Question";
import { Jumbotron } from "reactstrap";
import QuestionHeader from "./QuestionHeader";
...
export const Quiz = () => {
var [index, setIndex] = useState(0);
const [questionData, setQuestionData] = useState([]);
...
return (
<>
{toggleView && (
<Toggle
setIndex={setIndex}
setQuestionData={setQuestionData}
setToggleView={setToggleView}
/>
)}
{!toggleView &&
(
<Jumbotron>
<QuestionHeader
setToggleView={setToggleView}
/>
<Question question={questions[index]} />
</Jumbotron>
)}
</>
);
使用index控制題目索引
var [index, setIndex] = useState(0);
修改Toggle.js
獲取完遠程數據,通過setToggleView(false);切換視圖。
export const Toggle = ({
setQuestionData,
setToggleView,
setIndex,
}) => {
...
return (
<>
<ToggleHeader />
<Form
onSubmit={(e) => {
e.preventDefault();
getData();
setToggleView(false);
setIndex(0);
}}
>
<Button color="primary">開始</Button>
</Form>
</>
);
};
QuestionHeader.js代碼:
同樣的,點擊 返回首頁按鈕 setToggleView(true),切換視圖。
import React from "react";
import { Button } from "reactstrap";
export default function QuestionHeader({ setToggleView, category }) {
return (
<>
<Button color="link" onClick={() => setToggleView(true)}>
返回首頁
</Button>
</>
);
}
Question.js代碼
接受父組件傳過來的question對象,並显示。
其中he.decode是對字符串中的特殊字符進行轉義。
import React from "react";
import he from "he";
export const Question = ({ question }) => {
// he is a oddly named library that decodes html into string values
var decode = he.decode(String(question));
return (
<div>
<hr className="my-2" />
<h1 className="display-5">
{decode}
</h1>
<hr className="my-2" />
<br />
</div>
);
};
程序運行效果:
首頁
點擊開始后,显示問題:
當前項目目錄結構為:
新增LoadingSpin.js
import React from "react";
import { Spinner } from "reactstrap";
export default function LoadingSpin() {
return (
<>
<Spinner type="grow" color="primary" />
<Spinner type="grow" color="secondary" />
<Spinner type="grow" color="success" />
<Spinner type="grow" color="danger" />
</>
);
}
修改Quiz.js
import LoadingSpin from "./LoadingSpin";
export const Quiz = () => {
const [isLoading, setLoading] = useState(false);
return (
<>
{toggleView && (
<Toggle
...
setLoading={setLoading}
/>
)}
{!toggleView &&
(isLoading ? (
<LoadingSpin />
) :
(
...
))}
</>
);
};
修改Toggle.js
export const Toggle = ({
...
setLoading,
}) => {
const getData = async () => {
try {
setLoading(true);
const incomingData = await axios.get(
`https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
);
setQuestionData(incomingData.data.results);
setLoading(false);
} catch (err) {
console.error(err);
}
};
...
};
運行效果:
目前代碼結構:
新增Answer.js,用戶點擊下一題按鈕,修改index,觸發主界面刷新,显示下一題:
import React from "react";
import { Button } from "reactstrap";
export const Answer = ({ setIndex, index }) => {
function answerResult() {
setIndex(index + 1);
}
return (
<Button className="ansButton" onClick={answerResult}>
下一題
</Button>
);
};
修改Quiz.js,添加Answer組件:
import { Answer } from "./Answer";
...
{!toggleView &&
(isLoading ? (
<LoadingSpin />
) :
(
<Jumbotron>
...
<Answer
setIndex={setIndex}
index={index}
/>
</Jumbotron>
))}
運行效果:
點擊下一題:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※新北清潔公司,居家、辦公、裝潢細清專業服務
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案

三月份已經介紹過R2DBC,它是一種異步的、非阻塞的關係式數據庫連接規範。儘管一些NoSQL數據庫供應商為其數據庫提供了反應式數據庫客戶端,但對於大多數項目而言,遷移到NoSQL並不是一個理想的選擇。這促使了一個通用的響應式關係數據庫連接規範的誕生。 作為擁有龐大用戶群的關係式數據庫MySQL也有了反應式驅動,不過並不是官方的。但是Spring官方將其納入了依賴池,說明該類庫的質量並不低。所以今天就嘗嘗鮮,試一下使用R2DBC連接MySQL。
基於Spring Boot 2.3.1和Spring Data R2DBC,還有反應式Web框架Webflux,同時也要依賴r2dbc-mysql庫,所有的Maven依賴為:
<!--r2dbc mysql 庫-->
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
</dependency>
<!--Spring r2dbc 抽象層-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!--自動配置需要引入的一個嵌入式數據庫類型對象-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!--反應式web框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
MySQL版本為5.7,沒有測試其它版本。
所有的R2DBC自動配置都在org.springframework.boot.autoconfigure.data.r2dbc包下,如果要配置MySQL必須針對性的配置對應的連接工廠接口ConnectionFactory,當然也可以通過application.yml配置。個人比較喜歡JavaConfig。
@Bean
ConnectionFactory connectionFactory() {
return MySqlConnectionFactory.from(MySqlConnectionConfiguration.builder()
.host("127.0.0.1")
.port(3306)
.username("root")
.password("123456")
.database("database_name")
// 額外的其它非必選參數省略
.build());
}
詳細配置可參考r2dbc-mysql的官方說明:https://github.com/mirromutth/r2dbc-mysql
當ConnectionFactory配置好后,就會被注入DatabaseClient 對象。該對象是非阻塞的,用於執行數據庫反應性客戶端調用與反應流背壓請求。我們可以通過該接口反應式地操作數據庫。
我們先創建一張表並寫入一些數據:
create table client_user
(
user_id varchar(64) not null comment '用戶唯一標示' primary key,
username varchar(64) null comment '名稱',
phone_number varchar(64) null comment '手機號',
gender tinyint(1) default 0 null comment '0 未知 1 男 2 女 '
)
對應的實體為:
package cn.felord.r2dbc.config;
import lombok.Data;
/**
* @author felord.cn
*/
@Data
public class ClientUser {
private String userId;
private String username;
private String phoneNumber;
private Integer gender;
}
然後我們編寫一個Webflux的反應式接口:
package cn.felord.r2dbc.config;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
/**
* The type User controller.
*
* @author felord.cn
* @since 17 :07
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private DatabaseClient databaseClient;
/**
* 查詢
*
* @return 返回Flux序列 包含所有的ClientUser
*/
@GetMapping("/get")
public Flux<ClientUser> clientUserFlux() {
return databaseClient.execute("select * from client_user").as(ClientUser.class)
.fetch()
.all();
}
/**
* 響應式寫入.
*
* @return Mono對象包含更新成功的條數
*/
@GetMapping("/add")
public Mono<Integer> insert() {
ClientUser clientUser = new ClientUser();
clientUser.setUserId("34345514644");
clientUser.setUsername("felord.cn");
clientUser.setPhoneNumber("3456121");
clientUser.setGender(1);
return databaseClient.insert().into(ClientUser.class)
.using(clientUser)
.fetch().rowsUpdated();
}
}
調用接口就能獲取到期望的數據結果。
乍一看R2DBC並沒有想象中的那麼難,但是間接的需要了解Flux、Mono等抽象概念。同時目前來說如果不和Webflux框架配合也沒有使用場景。就本文的MySQL而言,R2DBC驅動還是社區維護(不得不說PgSQL就做的很好)。
然而需要你看清的是反應式才是未來。如果你要抓住未來就需要現在就了解一些相關的知識。這讓我想起五年前剛剛接觸Spring Boot的感覺。另外這裡有一份Spring官方關於R2DBC的PPT,也是讓你更好了解R2DBC的權威資料。可以關注:碼農小胖哥 回復r2dbc獲取。
關注公眾號:Felordcn 獲取更多資訊
個人博客:https://felord.cn
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※新北清潔公司,居家、辦公、裝潢細清專業服務
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?

目錄
上篇文章我們講到了JIT中的LogCompilation,將編譯的日誌都收集起來,存到日誌文件裏面,並且詳細的解釋了LogCompilation日誌文件中的內容定義。今天我們再和小師妹一起學習LogCompilation的姊妹篇PrintCompilation,看看都有什麼妙用吧。
小師妹:F師兄,上次你給講的LogCompilation實在是太複雜了,生成的日誌文件又多,完全看不了,我其實只是想知道有哪些方法被編譯成了機器碼,有沒有什麼更加簡單的辦法呢?
真理的大海,讓未發現的一切事物躺卧在我的眼前,任我去探尋- 牛頓(英國)
當然有的,那就給你介紹一下LogCompilation的妹妹PrintCompilation,為什麼是妹妹呢?因為PrintCompilation輸出的日誌要比LogCompilation少太多了。
老規矩,上上我們的JMH運行代碼,文章中使用的代碼鏈接都會在文末註明,這裏使用圖片的原因只是為了方便讀者閱讀代碼:
這裏和上次的LogCompilation不同的是,我們使用:-XX:+PrintCompilation參數。
其實我們還可以添加更多的參數,例如:
-Xbatch -XX:-TieredCompilation -XX:+PrintCompilation
先講一下-Xbatch。
一般來說JIT編譯器使用的是和主線程完全不同的新的線程。這樣做的好處就是JIT可以和主線程并行執行,編譯器的運行基本上不會影響到主線程的的運行。
但是有陰就有陽,有利就有弊。多線程在提高的處理速度的同時,帶給我們的就是輸出日誌的混亂。因為是并行執行的,我們主線程的日誌中,穿插了JIT編譯器的線程日誌。
如果使用-Xbatch就可以強迫JIT編譯器使用主線程。這樣我們的輸出日誌就是井然有序的。真棒。
再講一下TieredCompilation。
為了更好的提升編譯效率,JVM在JDK7中引入了分層編譯Tiered compilation的概念。
大概來說分層編譯可以分為三層:
第一層就是禁用C1和C2編譯器,這個時候沒有JIT進行。
第二層就是只開啟C1編譯器,因為C1編譯器只會進行一些簡單的JIT優化,所以這個可以應對常規情況。
第三層就是同時開啟C1和C2編譯器。
在JDK8中,分層編譯是默認開啟的。因為不同的編譯級別處理編譯的時間是不一樣的,後面層級的編譯器啟動的要比前面層級的編譯器要慢,但是優化的程度更高。
這樣我們其實會產生很多中間的優化代碼,這裏我們只是想分析最終的優化代碼,所以我們需要停止分層編譯的功能。
最後是今天的主角:PrintCompilation。
PrintCompilation將會輸出被編譯方法的統計信息,因此使用PrintCompilation可以很方便的看出哪些是熱點代碼。熱點代碼也就意味着存在着被優化的可能性。
小師妹:F師兄,我照着你的例子運行了一下,結果果然清爽了很多。可是我還是看不懂。
沒有一個人能全面把握真理。小師妹,我們始終在未知的路上前行。不懂就問,不會就學。
我們再截個圖看一下生成的日誌吧。
因為日誌太長了,為了節約大家的流量,我只截取了開頭的部分和結尾的部分。
大家可以看到開頭部分基本上都是java自帶的類的優化。只有最後才是我們自己寫的類。
第一列是方法開始編譯的時間。
第二列是簡單的index。
第三列是一系列的flag的組合,有下面幾個flag:
b Blocking compiler (always set for client)
* Generating a native wrapper
% On stack replacement (where the compiled code is running)
! Method has exception handlers
s Method declared as synchronized
n Method declared as native
made non entrant compilation was wrong/incomplete, no future callers will use this version
made zombie code is not in use and ready for GC
如果我們沒有關閉分層編譯的話,在方法名前面還會有数字,表示是使用的那個編譯器。
分層編譯詳細的來說可以分為5個級別。
0表示是使用解釋器,不使用JIT編譯。
1,2,3是使用C1編譯器(client)。
4是使用C2編譯器(server)。
現在讓我們來看一下最後一列。
最後一列包含了方法名和方法的長度。注意這裏的長度指的是字節碼的長度。
如果字節碼被編譯成為機器碼,長度會增加很多倍。
本文介紹了JIT中PrintCompilation的使用,並再次複習了JIT中的分層編譯架構。希望大家能夠喜歡。
本文的例子https://github.com/ddean2009/learn-java-base-9-to-20
本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/jvm-jit-printcompilation/
本文來源:flydean的博客
歡迎關注我的公眾號:程序那些事,更多精彩等着您!
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案

公司有一個數據處理線,上面的數據經過不同環境處理,然後上線到正式庫。其中一個環節需要將數據進行處理然後導入到另外一個庫(Sql Server)。這個處理的程序是老大用python寫的,處理完後進入另外一個庫后某些字段出現了亂碼。
比如這個字符串:1006⁃267X(2020)02⁃0548⁃10
另外一個庫變成:1006?267X(2020)02?0548?10
線上人員反饋回來后老大由於比較忙,一直沒有排查,然後我問了下估計是什麼原因。老大說他python裏面轉了utf8,可能是編碼問題。我當時問了下就沒下文了,因為我不會python,所以這個事情就擱置了。
然後這個問題拖了很久,線上也不斷反饋。同時自己也負責這塊,空閑時間就主動去排查了下原因。當然這個排查過程還是比較曲折的,所以就把這個過程分享下,同時回顧下涉及到的知識點。
先說結果:最後經過排查是由於python處理后insert語句插入到Sql Server數據庫保存字段前沒有加N。
1.SQL Server數據類型
首先由於數據寫進去出現亂碼,所以第一步就是檢查寫入庫的字段是否設置了正確的數據類型。因為有時候對char與varchar的區別或者varchar與nvarchar的區別不是很在意,所以有可能設置了錯誤的數據類型。至於這幾個字符的數據類型區別是什麼,這裏摘抄官方解釋。
字符數據類型 char(大小固定)或 varchar(大小可變) 。 從 SQL Server 2019 (15.x) 起,使用啟用了 UTF-8 的排序規則時,這些數據類型會存儲 Unicode 字符數據的整個範圍,並使用 UTF-8 字符編碼。 若指定了非 UTF-8 排序規則,則這些數據類型僅會存儲該排序規則的相應代碼頁支持的字符子集。
參數
char [ ( n ) ]
固定大小字符串數據 。 n 用於定義字符串大小(以字節為單位),並且它必須為 1 到 8,000 之間的值 。 對於單字節編碼字符集(如拉丁文),存儲大小為 n 個字節,並且可存儲的字符數也為 n。 對於多字節編碼字符集,存儲大小仍為 n 個字節,但可存儲的字符數可能小於 n。 char 的 ISO 同義詞是 character 。
varchar [ ( n | max ) ]
可變大小字符串數據 。 使用 n 定義字符串大小(以字節為單位),可以是介於 1 和 8,000 之間的值;或使用 max 指明列約束大小上限為最大存儲 2^31-1 個字節 (2GB)。 對於單字節編碼字符集(如拉丁文),存儲大小為 n + 2 個字節,並且可存儲的字符數也為 n。 對於多字節編碼字符集,存儲大小仍為 n + 2 個字節,但可存儲的字符數可能小於 n 。
字符數據類型 nchar(大小固定)或 nvarchar(大小可變) 。 從 SQL Server 2012 (11.x) 起,使用啟用了補充字符 (SC) 的排序規則時,這些數據類型會存儲 Unicode 字符數據的整個範圍,並使用 UTF-16 字符編碼。 若指定了非 SC 排序規則,則這些數據類型僅會存儲 UCS-2 字符編碼支持的字符數據子集。
nchar [ ( n ) ]
固定大小字符串數據。 n 用於定義字符串大小(以雙字節為單位),並且它必須為 1 到 4,000 之間的值 。 存儲大小為 n 字節的兩倍。 對於 UCS-2 編碼,存儲大小為 n 個字節的兩倍,並且可存儲的字符數也為 n。 對於 UTF-16 編碼,存儲大小仍為 n 個字節的兩倍,但可存儲的字符數可能小於 n,因為補充字符使用兩個雙字節(也稱為代理項對)。 nchar 的 ISO 同義詞是 national char 和 national character 。
nvarchar [ ( n | max ) ]
可變大小字符串數據。 n 用於定義字符串大小(以雙字節為單位),並且它可能為 1 到 4,000 之間的值 。 max 指示最大存儲大小是 2^30-1 個字符 (2 GB) 。 存儲大小為 n 字節的兩倍 + 2 個字節。 對於 UCS-2 編碼,存儲大小為 n 個字節的兩倍 + 2 個字節,並且可存儲的字符數也為 n。 對於 UTF-16 編碼,存儲大小仍為 n 個字節的兩倍 + 2 個字節,但可存儲的字符數可能小於 n,因為補充字符使用兩個雙字節(也稱為代理項對)。 nvarchar 的 ISO 同義詞是 national char varying 和 national character varying 。
通過上面的描述我們可以總結:這幾種類型都是存儲字符數據,如果存儲單字節的字符串(比如英文)使用char、varchar,節約空間。如果存儲多字節的字符串(比如包含中文)使用nchar、nvarchar,兼容更多的編碼。雙字節比單字節對應的多了一個n。
單字節雙字節中還有一個區別var,表示可變大小字符串數據。可變是指如果某字段插入的值超過了數據頁的長度,該行的字段值將存放到ROW_OVERFLOW_DATA中。但是會造成多餘的I/O,比如一個VARCHAR列經常被修改,而且每次被修改的數據的長度不同,這會引起‘行遷移’(Row Migration)現象。這裏就不展開了,可以去了解下。
所以我們設計數據庫字段的時候需要根據業務設計合理的數據類型,有利於節約空間和時間。而經過我檢查數據庫字段確實設置的nvarchar,所以不存在存儲不了對應編碼問題。而且問了老大他說python裏面他轉了UTF8編碼,所以下一步就是排查是否轉編碼出了問題。
2.編碼
因為我經常寫C#,C#裏面的字符串是Unicode的,當然對於程序員來說這個編碼是透明的,因為是Unicode編碼可以轉換成其它任何編碼,所以我們日常開發的時候並不需要時刻去關注編碼的問題,其底層已經幫我們進行了處理。既然說是python轉了utf8那麼我就去大概看了下python的基礎並試驗了一把。
先找了一條出現亂碼的數據,在原庫取出來然後進行utf8轉碼,然後再解碼。講道理同一個編碼解碼出來存儲應該還是原來的字符串,所以我才會好奇去試驗。試驗后發現果然沒有什麼問題。
關於編碼可以看下這個講解:編碼,因為講的比較形象而且容易理解,所以我這裏就不累述了。
排除python程序編碼問題,那接下來就是要排查從程序插入到數據庫這一段的問題了。
3.SQL Server排序規則
首先插入這一階段我想到的還是編碼問題,所以去查詢了數據庫編碼。使用sql語句查詢數據庫排序規則
SELECT COLLATIONPROPERTY('Chinese_PRC_Stroke_CI_AI_KS_WS', 'CodePage')
對應的字符集編碼
936 :簡體中文GBK
950 :繁體中文BIG5
437 :美國/加拿大英語
932 :日文
949 :韓文
866 :俄文
65001 :unicode UTF-8
查詢了數據排序規則,導入數據庫是默認排序規則,也就是936 GBK編碼。為什麼要看數據庫排序規則,第1點中可見“數據類型僅會存儲該排序規則的相應代碼頁支持的字符子集”。
排序規則微軟解釋:排序規則
SQL Server 中的排序規則可為您的數據提供排序規則、區分大小寫屬性和區分重音屬性。 與諸如 char 和 varchar 等字符數據類型一起使用的排序規則規定可表示該數據類型的代碼頁和對應字符 。
無論你是要安裝 SQL Server 的新實例、還原數據庫備份,還是將服務器連接到客戶端數據庫,都必須了解正在處理的數據的區域設置要求、排序順序以及是否區分大小寫和重音。
所以通過查看排序規則知道,默認編碼是GBK。然後我就猜測到是GBK編碼問題,因為在python3裏面字符串的默認編碼也是Unicode,測試下把1006⁃267X(2020)02⁃0548⁃10轉成GBK。
可以看到是無法轉碼的,gbk識別不了那個短橫杠,然後我編碼成GB18030能夠編碼。說明短橫杠是更高位的編碼,當然unicode是能存儲的。那為什麼在數據庫裏面就成了亂碼呢?而且字段類型是設置的nvarchar啊。
4、大寫字母“N”作為前綴
通過3點的分析,說明了本該存儲成Unicode的編碼被保存成了默認編碼。所以我們只要把保存成Unicode編碼就行了,所以到此已經和python程序沒什麼關係了,帶着懷疑的態度,我將這段字符直接拿到Sql Sever裏面執行,果然也是亂碼。
最後就是在參數前加N執行
這下結果就正常了。細心的你是否發發現v1字段還是亂碼,因為我為了測試varchar單字節,即使我加了N一樣的是亂碼。所以記得存儲中文最好選nvarchar,原因么請看第一點char和varchar的說明中這樣一句話:若指定了非 UTF-8 排序規則,則這些數據類型僅會存儲該排序規則的相應代碼頁支持的字符子集。也就是它只會存儲我當前數據庫的GBK編碼。
最後我還在python裏面插入的sql語句加了N,同樣可以插入成功。
關於加N的解釋,微軟t-sql文檔關於insert說明:鏈接
5.為什麼我們平時很少加N
既然有這樣的問題為什麼我們平時基本沒加過N?原因有幾點:
SqlParameter會自動加N?帶着懷疑的態度測試下。
首先寫一個測試程序,然後開啟SQL server跟蹤來查看執行的sql。
static void Test()
{
string server = "127.0.0.1";
string database = "TestDB";
string user = "sa";
string password = "******";
string connectionString = $"server={server};database={database};User ID={user};Password={password}";
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = connection;
cmd.CommandText = "insert into Test1 values('1006⁃267X(2020)02⁃0548⁃10','1006⁃267X(2020)02⁃0548⁃10')";
cmd.ExecuteNonQuery();
cmd.CommandText = "insert into Test1 values(@v1,@v2)";
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "v1",
Value = "1006⁃267X(2020)02⁃0548⁃10"
});
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "v2",
Value = "1006⁃267X(2020)02⁃0548⁃10"
});
cmd.ExecuteNonQuery();
}
}
}
查看跟蹤執行的sql,一個是直接傳入拼接sql執行,一個是使用參數方式執行。
通過一次排查亂碼問題,又回顧或者學習了關於數據類型和編碼,以及sql存儲如何避免亂碼問題。平時設計的時候如果是帶中文的字段首先考慮帶n的char類型。同時在直接使用sql進行insert、update的時候注意在要保存為Unicode編碼字符串前面加N。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?