本文通过脚本命令获取当前公网 IP,并与 moon.json 中的 IP 进行比对,如果不一致,则更改 moon.json 中的 IP,并重启服务。
本文是在 openwrt 里安装的 Zerotier,luci-app-zerotier
插件自带的 Zerotier 服务。地址为 /etc/config/zero
。
cd /etc/config/zero |
修改刚刚生成的配置文件 moon.json
中的 stableEndpoints
:
{ |
此处的 10.10.0.0
就是公网 IP。
zerotier-idtool genmoon moon.json |
执行之后会生产一个 000000xxxx.moon
的文件
新建 moons.d
文件夹,将生成的 .moon
文件移动到 moons.d
文件夹中,并重启 Zerotier 服务
mkdir moons.d |
将以下脚本放到 /etc/config/zero/update_moon.sh
中
|
使用crontab来定时执行脚本,输入以下命令来编辑当前用户的crontab文件: crontab -e
然后在打开的编辑器中添加一行如下的内容,表示每天凌晨2点执行update_moon.sh脚本:
0 2 * * * /etc/config/zero/update_moon.sh |
保存并退出编辑器。这会将指定的命令添加到用户的crontab中,使其每天凌晨2点自动执行。
]]>对于单个服务器来说,LXD 仍然是我认为最舒服的多人共用服务器的方式。他避免了权限滥用,能够完整利用硬件,缺点就是显卡驱动需要保持一致,升级较为麻烦。
本教程超级详细,是根据我最近操作录屏总结的最佳步骤,并且为批量操作总结了脚本,保证你能够顺利完成安装。
由于 LXD 的容器需要使用 ZFS 文件系统进行管理,因此有两种对硬盘的格式化方式:
第1种方案由于 ZFS 的空间和宿主机共享,可能导致创建的 ZFS Pool 过大导致宿主机硬盘空间过小,届时可能无法通过ssh连接,需要在宿主机手动删除一些文件才可继续使用。
严重警告!!!ZFS 只支持扩容,不支持缩容,否则将带来不可逆的文件丢失。
因此这里推荐使用第2种方式,因为这样宿主机与容器的存储分离开来,存储逻辑更清晰。
宿主机系统推荐使用 Ubuntu Server,即不带桌面的版本,这样作为服务器更稳定。
宿主机安装时硬盘选择了 LVM 分区格式,但是他只分配了 200 GB,其他空间没有利用。因此需要使用以下命令将 LVM 分区扩容,占用整个硬盘空间:
sudo fdisk -l |
使用下面的命令对 硬盘 /dev/sda 进行 ZFS 的分区格式,如果组 raid 可以上网查。 sudo apt install zfsutils-linux
# 创建 ZFS 普通分区
sudo zpool create zfs_lvm sda
# 创建 ZFS mirror(raid1)分区
sudo zpool create zfs_lvm mirror sda sdb
# 创建 ZFS raidz(raid5)分区:
sudo zpool create zfs_lvm raidz sdb sdc sdd sde
其中 zfs_lvm 为 ZFS Pool 的名字。
sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak |
如下为哈工大源: # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb http://mirrors.hit.edu.cn/ubuntu/ focal main restricted universe multiverse
# deb-src http://mirrors.hit.edu.cn/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.hit.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
# deb-src http://mirrors.hit.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.hit.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
# deb-src http://mirrors.hit.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
deb http://mirrors.hit.edu.cn/ubuntu/ focal-security main restricted universe multiverse
# deb-src http://mirrors.hit.edu.cn/ubuntu/ focal-security main restricted universe multiverse
# 预发布软件源,不建议启用
# deb http://mirrors.hit.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
# deb-src http://mirrors.hit.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
更新源: sudo apt update
sudo apt upgrade
sudo apt-mark hold linux-image-generic linux-headers-generic |
防止内核升级导致显卡驱动失效。 因为显卡驱动需要编译内核版本,升级内核后显卡驱动需要重新编译安装。由于 LXD 容器共享内核,升级内核会导致所有显卡驱动都需要重新安装。
需要更改宿主机网络为网桥模式,这样才能使容器和宿主机处于同一网络子层,在同一局域网的计算机可以直接 ssh 链接。
Ubuntu 17.10 以后默认使用 Netplan 管理网络。
进入 /etc/netplan/
目录有一个 yaml 配置文件,下面的命令需要根据自己的 yaml 文件名称自行修改
sudo cp /etc/netplan/01-netcfg.yaml /etc/netplan/01-netcfg.yaml.bak |
如下:
# This file describes the network interfaces available on your system |
addresses: [ 192.168.100.123/24 ]
为任意网络无人占用的 IP 即可。gateway4: 192.168.100.254
为网关地址。eno1
为网卡名称,可以使用 ip a
或 ifconfig
命令查看。应用网络配置: sudo netplan --debug apply
sudo snap install lxd |
我们需要安装 LXD 实现虚拟容器,ZFS 作为 LXD 的存储管理工具,bridge-utils 用于搭建网桥。由于 apt 安装的 LXD 不是最新版本,这里使用 snap 安装工具安装 LXD。
去 NVIDIA 官网 下载最新驱动,这里下载的是 ./NVIDIA-Linux-x86_64-418.56.run
。
由于系统是 ubuntu-server,所以简单很多,如果是安装的 ubuntu-desktop,建议用其他电脑 ssh 远程连接后再安装。如果一定要在有 desktop 的系统安装显卡驱动,可以参考:超详细! Ubuntu 18.04 安装 NVIDIA 显卡驱动
安装依赖: sudo apt install gcc g++ make
安装驱动: sudo bash ./NVIDIA-Linux-x86_64-418.56.run
查看显卡: nvidia-smi
此时发现输入 nvidia-smi
命令后需要 3 秒左右才会出结果,并且显卡功率占用较高,没有程序运行就有一百多瓦的功耗。
为了解决这些问题,需要将显卡模式改为持久模式,该命令需要 root 权限: sudo nvidia-smi -pm 1
持久模式使得输出结果反应迅速,并且功耗得到降低。但是重启后该模式会默认关闭,需要添加自启动命令,在后面会讲到。
sudo lxd init |
在初始化过程中,不要创建新的网桥,已存在的网桥名为 br0
,其他设置默认即可。
当采用提前将整个硬盘作为 ZFS 分区,这时在是否创建新的 ZFS Pool 时选 no,并填写已经存在的 ZFS pool 的名字。如下所示:
Would you like to use LXD clustering? (yes/no) [default=no]: |
当采用所有硬盘作为宿主机硬盘时,需要在 LXD 初始化时创建新的 ZFS Pool,ZFS设置大小要尽量大,如下所示:
Would you like to use LXD clustering? (yes/no) [default=no]: |
创建的容器最好和宿主机系统相同。
sudo lxc launch ubuntu:20.04 |
查看容器列表: sudo lxc list
为了后续方便,我们将容器名进行修改: sudo lxc stop equipped-locust
sudo lxc rename equipped-locust template
sudo lxc start template
sudo lxc config device add template gpu gpu |
先配置一个网络、驱动都正常的容器,制作快照并作为模板,这样需要创建新容器时可以从快照创建,节省时间。
与宿主机更换方法相同。
可以通过容器的 NAME 进入容器: sudo lxc exec template bash
其中 template 为容器名。
进入容器后默认是 root 用户,首先安装 net-tools: apt install net-tools
通过 ifconfig
命令查看网卡名为 eth0
:
和宿主机一样,进入 /etc/netplan/
目录有一个 yaml 配置文件,下面的命令需要根据自己的 yaml 文件名称自行修改:
编辑 yaml 配置文件:
mv /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak |
如下: network:
version: 2
ethernets:
eth0:
dhcp4: no
dhcp6: no
addresses:
- 192.168.100.124/24
gateway4: 192.168.100.254
nameservers:
addresses:
- 114.114.114.114
- 8.8.8.8
应用网络配置: netplan --debug apply
容器默认用户名为 ubuntu
,这里想把他改成 tmp
,命令如下: usermod -l tmp -d /home/tmp -m ubuntu
groupmod -n tmp ubuntu
此时 /home
文件夹下只剩下 tmp
目录。
更改 tmp
用户的密码: passwd tmp
然后输入两次新密码。
apt install openssh-server |
编辑 ssh 配置文件:
vim /etc/ssh/sshd_config |
将 PasswordAuthentication
改为 yes
,退出编辑后重启 ssh 服务: systemctl restart sshd
此时可以用 exit
命令退出到宿主机中,尝试用 ssh 命令远程连接容器: ssh tmp@192.168.100.124
输入密码,能登录则没问题。
为了方便以后初始化容器,我们将网络初始化等命令写入脚本。 在容器的 /root/ 目录执行 vim init_lxd.sh
命令创建脚本,编辑如下: # !/bin/bash
read -p "Enter your last name as the username, such as zhang: " last_name
read -p "Enter the password of the container: " password
read -p "Enter the IP address:" IP
echo "Change username to $last_name"
usermod -l $last_name -d /home/$last_name -m tmp
groupmod -n $last_name tmp
echo "Change IP to $IP"
mv /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
echo "network:
version: 2
ethernets:
eth0:
dhcp4: no
dhcp6: no
addresses:
- $IP/24
gateway4: 192.168.100.254
nameservers:
addresses:
- 114.114.114.114
- 8.8.8.8" > /etc/netplan/50-cloud-init.yaml
netplan --debug apply
echo "$last_name:$password" | sudo chpasswd
reboot
容器和宿主机的显卡驱动必须保持一致,因此需要将宿主机的驱动文件传输到容器中。 因为刚安好了 ssh
,因此可以选择 scp
传输。在宿主机中输入以下命令: scp ./NVIDIA-Linux-x86_64-418.56.run tmp@192.168.100.124:/home/tmp/
也可以通过 lxc
命令传输。在宿主机中输入以下命令: sudo lxc file push ./NVIDIA-Linux-x86_64-418.56.run template/home/tmp/NVIDIA-Linux-x86_64-418.56.run
以上两种方法均可传输文件。
传输后通过 ssh 进入容器,输入以下命令安装显卡驱动: sudo bash ./NVIDIA-Linux-x86_64-418.56.run --no-kernel-module
由于容器和宿主机共享内核,所以在安装容器的显卡驱动时需要添加 --no-kernel-module
参数。
安装好显卡驱动后用 nvidia-smi
命令查看显卡:
在宿主机执行以下命令,对 template
容器创建一个名为 gpu
的快照: sudo lxc snapshot template gpu
现在容器模板已经制作完成,创建新的容器只需要将容器模板的快照进行复制并恢复,即可得到一个新的容器,但是命令比较复杂,因此我将其整理为脚本。 在宿主机执行 vim create_container.sh
创建脚本文件,编辑如下: # !/bin/bash
passwd='xxxx'
read -p "Enter your full name as the container name, such as zhangsan: " name
echo "Create the container $name..."
echo $passwd | sudo -S lxc copy template/gpu $name
echo "Start the container $name..."
echo $passwd | sudo -S lxc start $name
sudo -S lxc exec $name -- /bin/bash
其中 passwd
为容器的默认密码。
此时如果重启宿主机,我们会发现容器中显卡驱动消失。目前找到的解决办法是在宿主机运行一次 pytorch cuda 的程序,并重启容器。 因此我们需要在宿主机安装 PyTorch,并在开机时自动执行 import torch; print(torch.cuda.is_available())
命令。
在官网下载 Anaconda3,也可以用以下命令: wget https://repo.anaconda.com/archive/Anaconda3-2022.05-Linux-x86_64.sh
安装Anaconda(一定不要用 sudo): bash ./Anaconda3-2022.05-Linux-x86_64.sh
Anaconda 换源(哈工大): 创建 .condarc
vim ~/.condarc
编辑如下: channels:
- defaults
show_channel_urls: true
default_channels:
- https://mirrors.hit.edu.cn/anaconda/pkgs/main
- https://mirrors.hit.edu.cn/anaconda/pkgs/r
- https://mirrors.hit.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: https://mirrors.hit.edu.cn/anaconda/cloud
msys2: https://mirrors.hit.edu.cn/anaconda/cloud
bioconda: https://mirrors.hit.edu.cn/anaconda/cloud
menpo: https://mirrors.hit.edu.cn/anaconda/cloud
pytorch: https://mirrors.hit.edu.cn/anaconda/cloud
pytorch-lts: https://mirrors.hit.edu.cn/anaconda/cloud
simpleitk: https://mirrors.hit.edu.cn/anaconda/cloud
运行 conda clean -i
清除索引缓存,保证用的是镜像站提供的索引。
在 PyTorch 官网查看对应版本的安装命令,建议创建一个新的 PyTorch 环境: conda create -n pt1.12 pytorch torchvision torchaudio cudatoolkit=11.6 -c pytorch -c conda-forge
编辑 rc-local.service: sudo vim /lib/systemd/system/rc-local.service
末尾添加以下三行: [Install]
WantedBy=multi-user.target
Alias=rc-local.service
新建 rc.local
sudo vim /etc/rc.local |
编辑如下:
sleep 180s
sudo nvidia-smi -pm 1
/home/j1812/anaconda3/envs/pt1.12/bin/python -c 'import torch; print(torch.cuda.is_available())'
sudo lxc stop template --force
sudo lxc start template
exit 0
增加 rc.local 可执行权限: sudo chmod u+x /etc/rc.local
设置开机启动: sudo systemctl enable rc-local
sudo systemctl start rc-local
检查是否启动成功: sudo systemctl status rc-local
至此,已经完成了所有 LXD 的配置。
当不知道哪个容器正在占用显卡时,使用下面的命令查询:
nvidia-smi | grep -E 'python.*[0-9]{3,4}MiB' | awk '{print $5}' | xargs -I{} sh -c 'echo "PID: {} Cgroup: $(cat /proc/{}/cgroup | grep rdma | cut -d ":" -f 3)"' |
从运行 nvidia-smi
命令结果中获取占用GPU的Python进程的PID,然后通过 xargs
将PID传递给 sh
命令,进而在shell中执行一条命令来查看进程所属的 rdma
类型的cgroup。
具体来说,awk '{print $5}'
的作用是从 nvidia-smi
命令结果中获取占用GPU的Python进程的PID,其中 $5
是因为 nvidia-smi 命令结果中Python进程PID位于第5列。接着,xargs -I{} sh -c
将PID传递给 sh
命令,并在shell中执行一条命令。这条命令通过 cat /proc/{}/cgroup | grep rdma | cut -d ":" -f 3
获取进程所属的 rdma
类型的cgroup路径,并且通过 echo
命令输出PID和对应的cgroup路径。
wmic bios get serialnumber |
wmic diskdrive get serialnumber |
systeminfo |
ipconfig /all |
sudo dmidecode -s system-serial-number |
sudo lsblk |
df -P / |
ifconfig |
因此,使用雷电3协议的外接硬盘是扩展硬盘容量的简单方法,尤其是对于mac mini这种几乎不移动的设备。
外接扩容有两种方法:
一种是仅将大型软件、大型数据、虚拟机等存放到外置硬盘中,可以既享受高速的内置硬盘,也可以满足临时存储大型数据的需求。不需要重装系统,但是割裂了使用体验。
另一种是将整个系统都装在外置硬盘中,这样相当于购买了大容量的硬盘空间版本,保持了完整的系统使用体验,同时内置硬盘也可以用来存储文件。如果后续需要更换电脑,直接在新电脑上接上硬盘即可正常使用,无需备份恢复等操作(前提是内置硬盘系统与外置硬盘的系统大版本一致,之前所有的正版软件需要重新授权)。
这里我选择的是第二种扩容方法,可能是采用了M1芯片的原因,网上的方法大多不好使,踩了一些坑,在此记录一下。
这里硬盘盒选择的牌子是ACASIS,只支持雷电3协议,性价比算是非常高的了,前两年雷电3协议的硬盘盒动辄要600多,今年降价了不少,这款2021双十一大概350元在拼多多买的,在正常运行时外壳微热不烫手,散热非常不错。
硬盘选择的是闪迪1TB(sn550同款),固件21,发热量较小,硬盘盒几乎达到了硬盘的读写速度上限。虽然读写速度相较于内置硬盘有所下降,但是正常使用几乎感受不到区别。
使用 AmorphousDiskMark 软件进行测速结果如下,内置硬盘比外置硬盘速度要快很多,尤其是4k方面。
因此,我建议把平时经常读写硬盘的大型应用放入内置硬盘中,可以充分利用内置硬盘和外置硬盘。
在 实用工具
中找到 磁盘工具
,显示里选择 显示所有设备
:
选择外接硬盘,点击抹掉
,并将名称改为macOS
,格式为APFS
,方案为GUID分区图
:
打开 App Store
,搜索 macos
,就可以找到最新的版本 macos monterey
,下载后便可以在应用程序中找到 安装macOS Monterey.app
。需要注意的是,系统镜像必须从APP Store
进行下载,否则不允许安装。
这时传统的方法会双击这个安装macOS Monterey.app
并选择将系统安装到外接硬盘中,但是我操作后无法进入系统,在系统偏好设置
中点击启动磁盘
,将启动磁盘设为macOS
外接硬盘时会报错:无法设定启动磁盘-将“macOS”设为启动磁盘时出错:未能完成操作。(SDErrorDomain错误108。)
这可能是M1芯片的限制,所以只能进入恢复模式进行安装。
关机后长按电源键进入恢复模式,选择最右侧的选项
在这里如果选择第二项的重新安装macOS Monterey
的话需要从网上重新下载镜像,并且我也没有安装成功,所以这里介绍另一种省时省力的方法。
在上面的实用工具
中选择终端
,或者键盘cmd
+shift
+T
:
在这里输入一行命令,意思就是找到在步骤4中已经下好的安装macOS Monterey.app
并进行安装,命令如下:
cd ../../Volumes/Macintosh\ HD/Applications |
稍微懂一些Linux的小伙伴应该能看懂,路径可以一点点试出来。
回车后就进入了安装界面,这时不用下载即可安装:
一路下一步,注意选择macOS的外接硬盘即可,安装完成后会自动进入外接硬盘中的系统。
最后简单介绍一下自己由黑苹果转白苹果的体会。
之前自己撘过一个黑苹果,由于没有配显卡,Intel的核显的HDMI接口只能支持1080P60帧的显示器,而我希望配两个4K显示器,这就有了两种解决办法:
很明显,我选择了第二种办法。在体验了一周m1芯片的白苹果后,主要感受到了以下区别:
说实话,之前的黑苹果在使用上和白苹果并没有明显区别,要不是因为需要支持双4K显示器,黑苹果可能会接着使用。不过白苹果确实省心,不用为升级系统而担心各种bug,蓝牙wifi连接不稳定也只会认为是设备的问题,光噪声消失这一项我就认为这钱花的值。苹果产品还非常保值,过两年卖了买新款也不心疼。总之,m1的mac mini还是值得购买的。
]]>我们在 Ubuntu20.04 操作系统下进行操作。需要使用 GCC 编译器,使用命令 gcc -v
检查编译器是否可用,若没有使用下述命令进行安装:
sudo apt update |
我们使用 binutils 这个开源C++项目作为例子,下载链接如下 https://ftp.gnu.org/gnu/binutils/,下载 binutils-2.37.tar.gz
到服务器目标路径 ~/project/open_source
下。
进入目标路径:
cd ~/project/open_source |
使用以下命令进行解压:
tar -zxvf binutils-2.37.tar.gz |
创建编译路径并进入:
mkdir binutils-build |
因为服务器是x64的系统,所以默认编译的二进制也是x64,使用以下命令创建 Makefile 文件:
../binutils-2.37/configure CC=gcc CFLAGS=-O0 CPPFLAGS=-O0 |
上述为优化选项 O0 的编译结果,如果想改成其他优化选项,更改 CFLAGS 和 CPPFLAGS 里面的参数即可。
使用make命令进行编译:
make |
编译成功后,在 binutils
文件夹下可以看到编译好的二进制文件,使用 file
命令可以看到其中 addr2line 二进制文件的详细信息:
file binutils/addr2line |
可知该二进制文件为 x86-64 架构的 64-bit 程序。
首先使用下述命令删除 binutils-build
文件夹所有文件:
rm -rf * |
使用下述命令安装MIPS交叉编译选项
sudo apt-get update |
使用下述命令生成MIPS平台的 Makefile 文件:
../binutils-2.37/configure CFLAGS=-O0 CPPFLAGS=-O0 --host=mips-linux-gnu |
其中 --host 表示交叉编译中的目标架构。同样,更改优化选项可直接将O0改为O1等。
使用make命令进行编译:
make |
编译成功后,在 binutils
文件夹下可以看到编译好的二进制文件,使用 file
命令可以看到其中 addr2line 二进制文件的详细信息:
file binutils/addr2line |
可知该二进制文件为 MIPS 架构的 32-bit 程序。
首先使用下述命令删除 binutils-build
文件夹所有文件:
rm -rf * |
使用下述命令安装MIPS交叉编译选项
sudo apt-get update |
使用下述命令生成MIPS平台的 Makefile 文件:
../binutils-2.37/configure CFLAGS=-O0 CPPFLAGS=-O0 --host=arm-linux-gnueabi |
其中 --host 表示交叉编译中的目标架构。同样,更改优化选项可直接将O0改为O1等。
使用make命令进行编译:
make |
编译成功后,在 binutils
文件夹下可以看到编译好的二进制文件,使用 file
命令可以看到其中 addr2line 二进制文件的详细信息:
file binutils/addr2line |
可知该二进制文件为 ARM 架构的 32-bit 程序。
]]>在本教程中,我将向你展示如何使用 BERT 与 huggingface PyTorch 库来快速高效地微调模型,以获得接近句子分类的最先进性能。更广泛地讲,我将描述转移学习在NLP中的实际应用,以最小的努力在一系列NLP任务上创建高性能模型。
2018年是NLP的突破性一年。转移学习,特别是像Allen AI的ELMO、OpenAI的Open-GPT和谷歌的BERT这样的模型,让研究人员用最小的特定任务微调粉碎了多个基准,并为NLP社区的其他成员提供了预训练的模型,这些模型可以轻松地(用更少的数据和更少的计算时间)进行微调和实施,以产生最先进的结果。遗憾的是,对于许多刚开始接触NLP的人,甚至对于一些有经验的实践者来说,这些强大模型的理论和实际应用仍然没有得到很好的理解。
BERT(Bidirectional Encoder Representations from Transformers)于2018年底发布,我们将在本教程中使用该模型,为读者更好地理解和实践指导在NLP中使用转移学习模型。BERT是一种预训练语言表征的方法,它被用来创建模型,然后NLP实践者可以免费下载并使用这些模型。你可以使用这些模型从你的文本数据中提取高质量的语言特征,也可以用你自己的数据在特定的任务(分类、实体识别、问题回答等)上对这些模型进行微调,以产生最先进的预测。
这篇文章将解释如何修改和微调BERT,以创建一个强大的NLP模型,快速给你提供最先进的结果。
在本教程中,我们将使用BERT来训练一个文本分类器。具体来说,我们将把预先训练好的 BERT 模型,在最后添加一层未经训练的神经元,并为我们的分类任务训练新模型。为什么要这样做,而不是训练一个很适合你需要的特定深度学习模型(CNN、BiLSTM等)?
更快的发展
更少的数据
更好的结果
这种向转移学习的转变与几年前计算机视觉领域发生的相同转变并行。为计算机视觉任务创建一个好的深度学习网络可能需要数百万个参数,而且训练成本非常高。研究人员发现,深度网络可以学习分层的特征表示(在最低层有简单的特征,如边缘,在较高层有逐渐复杂的特征)。与其每次从头开始训练一个新的网络,不如将训练好的网络的低层泛化图像特征复制并转移到另一个有不同任务的网络中使用。很快,下载一个预先训练好的深度网络,并迅速对其进行重新训练以适应新的任务,或者在上面添加额外的层,这比从头开始训练网络的昂贵过程要好得多。对于许多人来说,2018年引入的深度预训练语言模型(ELMO、BERT、ULMFIT、Open-GPT等)标志着NLP中向转移学习的转变,就像计算机视觉看到的那样。
让我们开始吧!
为了让 torch 使用 GPU,我们需要识别并指定 GPU 作为设备。稍后,在我们的训练循环中,我们将把数据加载到设备上。
import torch |
接下来,让我们安装 HuggingFace 的transformers包,它将为我们提供一个与BERT一起工作的pytorch接口。(这个库包含了其他预训练语言模型的接口,如OpenAI的GPT和GPT-2)。我们选择了pytorch接口,因为它在高级API(它很容易使用,但不能深入了解事情的工作原理)和tensorflow代码(它包含了很多细节,但经常让我们偏离了关于tensorflow的课程,而这里的目的是BERT!)之间取得了很好的平衡。
目前,Hugging Face库似乎是最被广泛接受的、最强大的与BERT合作的pytorch接口。除了支持各种不同的预先训练好的变换模型外,该库还包含了这些模型的预构建修改,适合你的特定任务。例如,在本教程中,我们将使用BertForSequenceClassification
。
该库还包括用于标记分类、问题回答、下句预测等的特定任务类。使用这些预建的类可以简化为您的目的修改BERT的过程。
本笔记本中的代码其实是 HuggingFace 的run_glue.py示例脚本的简化版。
run_glue.py
是一个很有用的工具,它允许你选择你想运行的GLUE基准任务,以及你想使用的预训练模型(你可以看到可能的模型列表这里)。它还支持使用CPU、单个GPU或多个GPU。如果你想进一步提高速度,它甚至支持使用16位精度。
不幸的是,所有这些可配置性都是以可读性为代价的。在这篇Notebook中,我们已经大大简化了代码,并添加了大量的注释,以使其清楚地了解发生了什么。
我们将使用The Corpus of Linguistic Acceptability (CoLA)数据集进行单句分类。它是一组被标记为语法正确或不正确的句子。它于2018年5月首次发布,是 "GLUE Benchmark "中包含的测试之一,BERT等模型都在此基础上进行比赛。
该数据集托管在GitHub上的这个repo中:https://nyu-mll.github.io/CoLA/,下载链接
我们可以从文件名中看到,"tokenized" 和 "raw"版本的数据都是可用的。
我们不能使用预标记版本,因为为了应用预训练的BERT,我们必须使用模型提供的标记器。这是因为:(1)模型有一个特定的、固定的词汇,(2)BERT tokenizer有一种特殊的方式来处理词汇外的词汇。
我们将使用pandas来解析"域内"训练集,并查看其一些属性和数据点。
import pandas as pd |
我们实际关心的两个属性是"句子"和它的"标签",这个标签被称为"可接受性判断"(0=不可接受,1=可接受)。
下面是五个被标注为语法上不可接受的句子。请注意,这个任务比情感分析之类的工作要难得多!
print(df.loc[df.label == 0].sample(5)[['sentence', 'label']]) |
让我们将训练集的句子和标签提取为 numpy ndarrays。
# Get the lists of sentences and their labels. |
在本节中,我们将把我们的数据集转换为BERT可以训练的格式。
为了将我们的文本输入到 BERT,必须将其分割成 tokens,然后这些 tokens 必须被映射到 tokenizer 词汇表中的索引。
Tokenization 必须由 BERT 中包含的 Tokenizer 来执行--下面的单元格将为我们下载。我们将在这里使用 "uncases "版本。
from transformers import BertTokenizer |
让我们把tokenizer应用到一个句子上,看看输出。
# Print the original sentence. |
当我们实际转换所有的句子时,我们将使用tokenize.encode
函数来处理这两个步骤,而不是分别调用tokenize
和convert_tokens_to_ids
。
不过,在我们这样做之前,我们需要先谈谈BERT的一些格式化要求。
上面的代码遗漏了一些必要的格式化步骤,我们将在这里看看。
我们需要做的是 1. 在每个句子的开头和结尾添加特殊的标记。 2. 将所有句子的长度固定为一个固定的长度。 3. 用"注意力遮盖"明确区分真正的 token 和填充 token。
[SEP]
在每个句子的末尾,我们需要附加特殊的"[SEP]"令牌。
这个标记是双句子任务的产物,即给BERT两个独立的句子,并要求它确定一些事情(例如,句子A中的问题的答案能否在句子B中找到?
我还不确定为什么当我们只有单句输入的时候,还需要 token,但它确实需要!
[CLS]
对于分类任务,我们必须在每个句子的开头加上特殊的"[CLS]"标记。
这个标记具有特殊的意义。BERT 由12个 Transformer 层组成。每个 Transformer 都会接收一个标记嵌入的列表,并在输出中产生相同数量的嵌入(当然是改变了特征值!)。
在最后一个(第12个) Transformer 的输出端,分类器只使用第一个嵌入(对应[CLS]标记)。
"The first token of every sequence is always a special classification token (
[CLS]
). The final hidden state corresponding to this token is used as the aggregate sequence representation for classification tasks." (摘自BERT论文)
你可能会想到在最终的嵌入上尝试一些池化策略,但这并不是必须的。因为 BERT 被训练成只使用这个[CLS]标记进行分类,我们知道模型已经被激励将分类步骤所需的一切编码到那个单一的 768 值嵌入向量中。它已经为我们完成了池化工作!
我们数据集中的句子显然有不同的长度,那么BERT是如何处理的呢?
BERT有两个约束条件。 1. 所有的句子必须被填充或截断成一个固定的长度。 2. 最大的句子长度是512个tokens。
填充是通过一个特殊的"[PAD]"令牌来完成的,它在BERT词汇表中的索引0。下面的插图演示了填充到8个令牌的 "MAX_LEN"。
"注意力遮盖"只是一个1和0的数组,表示哪些标记是padding,哪些不是(看起来有点多余,不是吗!)。这个掩码告诉BERT中的"自我关注"机制不要将这些pad标记纳入它对句子的解释中。
不过,最大长度确实会影响训练和评估速度。
例如,用特斯拉K80。
MAX_LEN = 128 --> 训练一个 epoch 需要 5:28
MAX_LEN = 64 --> 训练一个 epoch 需要 2:57
。
transformers库提供了一个有用的 "encode" 函数,它将为我们处理大部分的解析和数据准备步骤。
在我们准备好对文本进行编码之前,我们需要决定一个最大句子长度来进行填充/截断。
下面的单元格将对数据集进行一次标记化处理,以测量最大句子长度。
max_len = 0 |
为了防止有一些较长的测试句子,我将最大长度设置为64。
现在我们准备好执行真正的 tokenization 了。
tokenizer.encode_plus
函数为我们结合了多个步骤。
[CLS]
和[SEP]
标记。[PAD]
token。前四项功能在tokenizer.encode
中,但我使用tokenizer.encode_plus
来获得第五项(注意力遮盖)。文档在这里.
# Tokenize all of the sentences and map the tokens to thier word IDs. |
把我们的训练集分成 90% 用于训练,10% 用于验证。
from torch.utils.data import TensorDataset, random_split |
我们还将使用 torch DataLoader 类为我们的数据集创建一个迭代器。这有助于在训练过程中节省内存,因为与for循环不同,有了迭代器,整个数据集不需要加载到内存中。
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler |
现在我们的输入数据已经被正确格式化了,是时候微调一下BERT模型了。
对于这个任务,我们首先要修改预先训练好的 BERT 模型,给出分类的输出,然后我们要在我们的数据集上继续训练模型,直到整个模型,端到端都很适合我们的任务。
值得庆幸的是,huggingface pytorch的实现中包含了一套针对各种NLP任务设计的接口。虽然这些接口都是建立在训练好的 BERT 模型之上,但每个接口都有不同的顶层和输出类型,以适应其特定的 NLP 任务。
以下是目前提供的类列表,供微调。
这些文档可以在这里下找到。
我们将使用BertForSequenceClassification。这是普通的BERT模型,上面增加了一个用于分类的单线性层,我们将使用它作为句子分类器。当我们输入数据时,整个预先训练好的BERT模型和额外的未经训练的分类层会根据我们的特定任务进行训练。
OK,让我们加载 BERT 吧! 有几个不同的预训练 BERT 模型可供选择。"bert-base-uncased "指的是只有小写字母("uncased")的版本,是两者中较小的版本("base "vs "large")。
from_pretrained
的文档可以在这里找到,附加参数定义在这里。
from transformers import BertForSequenceClassification, AdamW, BertConfig |
为了好奇,我们可以在这里按名称浏览所有模型的参数。
在下面的单元格中,我打印出了权重的名称和尺寸,分别为。
# Get all of the model's parameters as a list of tuples. |
现在我们已经加载了我们的模型,我们需要从存储的模型中抓取训练超参数。
为了微调的目的,作者建议从以下数值中选择(来自BERT论文的附录A.3)。
- batch大小: 16,32。
- 学习率(Adam): 5e-5、3e-5、2e-5。
- epoch数: 2、3、4。
我们选择的是: * batch大小:32(在创建DataLoaders时设置)。 * 学习率:2e-5 * Epochs: 4 (我们将看到这可能是太多了...)
epsilon 参数eps = 1e-8
是 "一个非常小的数字,以防止在实现中出现任何除以零的情况" (来自这里)。
你可以在run_glue.py
这里中找到AdamW优化器的创建。
# Note: AdamW is a class from the huggingface library (as opposed to pytorch) |
下面是我们的训练循环。有很多事情要做,但从根本上讲,我们的循环中的每一个过程都有一个训练阶段和一个验证阶段。
*感谢Stas Bekman贡献了使用验证损失来检测过度拟合的见解和代码!
训练: - 解开我们的数据输入和标签 - 将数据加载到GPU上进行加速 - 清空上一次计算的梯度。 - 在pytorch中,默认情况下梯度会累积(对RNNs等有用),除非你明确地清除它们。 - 正向传递(通过网络输入数据)。 - 后传(反向传播) - 用optimizer.step()告诉网络更新参数。 - 跟踪监测进展的变量
验证: - 解开我们的数据输入和标签 - 将数据加载到GPU上进行加速 - 正向传递(通过网络输入数据) - 计算我们的验证数据的损失,并跟踪监测进度的变量。
Pytorch 向我们隐藏了所有的详细计算,但我们已经对代码进行了注释,以指出上述步骤中的每一行都在进行。
PyTorch也有一些初学者教程,你可能也会觉得很有帮助。
定义一个用于计算精度的辅助函数。
import numpy as np |
用于格式化 "hh:mm:ss" 的经过时间的辅助函数。
import time |
我们准备开始训练了!
import random |
我们来看看训练过程的总结。
import pandas as pd |
请注意,虽然训练损失随着时间的推移在下降,但验证损失却在增加!这说明我们的模型训练时间过长,对训练数据的拟合过度。
作为参考,我们使用的是7695个训练样本和856个验证样本)。
验证损失是一个比准确率更精确的衡量标准,因为对于准确率,我们并不关心准确的输出值,而只是关心它落在阈值的哪一边。
如果我们预测的答案是正确的,但置信度较低,那么验证损失会抓住这一点,而准确性则不会。
# Commented out IPython magic to ensure Python compatibility. |
现在,我们将加载保持数据集,并准备输入,就像我们对训练集所做的那样。然后我们将使用Matthew's correlation coefficient来评估预测,因为这是广大NLP社区用来评估CoLA性能的度量。通过这个指标,+1是最好的分数,-1是最差的分数。通过这种方式,我们可以看到我们在这个特定任务上与最先进模型的表现。
我们需要应用所有与训练数据相同的步骤来准备我们的测试数据集。
import pandas as pd |
准备好了测试集,我们就可以应用我们的微调模型对测试集产生预测。
# Prediction on test set |
使用"Matthews correlation coefficient"来衡量CoLA基准的准确性。(MCC)。
我们在这里用MCC是因为班级不平衡。
print('Positive samples: %d of %d (%.2f%%)' % (df.label.sum(), len(df.label), (df.label.sum() / len(df.label) * 100.0))) |
最后的分数将基于整个测试集,但我们来看看各个 batch 的分数,以了解各 batch 之间指标的差异性。
每个批次都有 32 个句子,除了最后一个 batch 只有 (516 % 32)=4 个测试句子。
# Create a barplot showing the MCC score for each batch of test samples. |
现在我们将综合所有批次的结果,计算出我们最终的MCC分数。
# Combine the results across all batches. |
酷! 在大约半小时内,在不做任何超参数调整(调整学习率、epochs、批次大小、ADAM属性等)的情况下,我们能够得到一个不错的分数。
*注意:为了最大限度地提高分数,我们应该删除 "验证集"(我们用它来帮助确定要训练多少个epochs),并对整个训练集进行训练。
库中记录了这个基准的预期精度这里为49.23
。
你也可以看看官方的排行榜这里。
请注意,(由于数据集规模较小?)运行之间的准确率可能会有很大差异。
本篇文章演示了利用预先训练好的 BERT 模型,无论你对哪个具体的 NLP 任务感兴趣,你都可以使用 pytorch 接口以最小的努力和训练时间快速有效地创建一个高质量的模型。
]]>显示器是上左右三窄边框设计,窄边框约7mm,虽然比之前用的戴尔显示器边框稍宽一些,但是在28寸的屏幕上看起来很协调。显示器底座是一块厚重的矩形金属板,看起来非常简洁。显示器支持壁挂,支架支持快拆,并且支架可以上下左右俯仰调节,但是不支持旋转为竖屏。此处预售时放了竖屏的宣传图,后来才改了宣传图并添加了竖屏支架需单独购买的小字。说实话28寸显示器竖起来实在是太高了,但是误导宣传实在没必要。
接口方面由一个HDMI 2.0,一个DP 1.2,一个Type-C,两个USB 2.0和一个音频接口组成。其中两个USB2.0接口比较鸡肋,因为没有USB的上行接口,所以只有在Type-C接口接上支持云电脑的手机后才可以使用。Type-C接口好评,对于一些只有Type-C接口的笔记本电脑来说可以不用转接头了,但是看评论好像Type-C只支持30HZ的刷新率。同时还支持15W的反向充电,充手机足够了。显示器还附带了DP线和Type-C线,质量都很不错。不过显示器电源是外置的,大砖头差评。
这块面板还支持HDR10,不过最大亮度只有300 cd/m2,只是支持播放HDR视频,对HDR效果就不要有太多期待了。显示器还有2*2W的音响,音质就不用考虑了,聊胜于无。
博主在网上租了爱色丽i1 pro较色仪对屏幕进行校色,色域和色准表现结果如下。
P3色域只有84%,没有达到宣传的90%。△E结果最大值为1.11,小于宣传的△E<3。以上测试为爱色丽+DisplayCAL的测试结果,由于博主第一次使用较色仪,可能操作有误,不保证结果一定正确。校色文件稍后放出。
很不幸,博主购买的这块显示器有一个坏点,如下图绿点所示。应该是这个绿色像素点无法控制亮暗,在显示灰色的时候尤其明显。不过由于是4K分辨率,坏点很小以至于使用的第二天才发现,和客服聊了聊是允许退换货的,看起来不影响使用也就不退了。
IPS面板难免会有一定的漏光,我对这款显示器的漏光水平还是比较满意的。具体对比如下图,均为黑天关灯后拍摄。
上述所有图片的ISO均是250,与人眼观察较为一致,可见显示器四周发暗,但基本不影响使用。
当将ISO拉高到2500时效果如上图所示,可见四周漏光仍然明显,当然IPS面板多少都会有漏光现象,并不影响正常使用。
进入工厂模式我们可以看到该显示器使用的是群创的 M280DCA-E3B 面板。我们将其与京东方的 MV270QUM-N20 面板进行对比,该面板常用在三千价位以上的27寸4K的IPS显示器(戴尔 U2718Q、明基 PD2700U 等)
基本信息 | 群创 M280DCA-E3B(创维28U1) | 京东方 MV270QUM-N20 |
---|---|---|
面板尺寸 | 28.0(英寸) | 27.0(英寸) |
面板亮度 | 300 cd/m2 | 350 cd/m2 |
NTSC 比值 | 84% NTSC | 79% NTSC |
Adobe 覆盖率 | 82% | 81% |
DCI-P3 覆盖率 | 86% | 82% |
响应时间 | 4 ms | 5 ms |
对比度 | 1000:1 | 1300:1 |
通过比较我们发现,创维28U1 使用的这块群创面板在色域方面比三千价位的4K显示器更广,但是在亮度以及对比度等方面,京东方 MV270QUM-N20 参数更好一些。也就是说作为入门4K显示器,这块面板是及格的,并没有明显的偏科。
同时使用这块群创面板的还可能有 联想 S28u,三星 U28R550,宏碁 VG280K,飞利浦 288E2E 等显示器。之所以是可能是因为博主并没有证据证明他们的面板相同,但是据我所知28寸4K的IPS显示器目前只有群创这一款面板。在使用相同面板的情况下,不同厂家的显示器主要区别主要是驱动板用料、出厂校色、品牌售后等地方下功夫。对比结果如下表所示。
显示器名称 | 京东最低价 | 画中画 | △E | 接口 | 质保 | 亮度 |
---|---|---|---|---|---|---|
创维 28U1 | 1399 | 不支持 | <3 | HDMI+DP+typeC | 3年 | 300 |
联想 S28u | 2499 | 不支持 | ? | HDMI+DP | 1年 | 300 |
三星 U28R550 | 1999 | 支持 | ? | HDMI*2+DP | 1年 | 300 |
宏碁 VG280K | 1999 | 不支持 | ? | HDMI*2+DP | 3年 | 300 |
飞利浦 288E2E | 1799 | 支持 | <2 | HDMI*2+DP | 3年 | 300 |
华硕 VG289Q | 1999 | 不支持 | ? | HDMI*2+DP | 3年 | 350 |
除了华硕亮度达到350cd/m2,其余显示器都是300cd/m2。创维28u1以较低的价格,不错的配置,可以说是性价比非常不错的选择了。
创维在价格上十分具有优势,但是在一些细节上仍需打磨,如可能没有出厂校色,亮屏较慢,屏幕坏点控制等。对于产品来说,创维28U1这款显示器无疑是值得购买的,作为先上车的我来说非常满意。但对于宣传角度来说,无疑是失败的,尤其是在预售时的参数与实际购买的参数差距较大,比如△E由小于2改成了小于3,宣传可以竖屏写代码但支架需单独购买,宣传支持画中画又删除此功能。
这在品牌口碑上尤其不利,希望以后厂商注意。
]]>博主在购买之前在知乎提了一个问题,得到了很有用的帮助。下面是具体配置:
类别 | 品牌 | 数量 | 单价 | 总价 |
---|---|---|---|---|
GPU | 技嘉RTX2080Ti TURBO 11G | 4 | 9999 | 39996 |
CPU | i9-9820X 10/20 | 1 | 6299 | 6299 |
主板 | 技嘉 X299-WU8 | 1 | 4999 | 4999 |
机箱 | 先马掠食者V1 | 1 | 999 | 999 |
内存 | 美商海盗船 复仇者LPX DDR4 3000 16GB | 8 | 579 | 4632 |
电源 | 振华 额定2000W LEADEX P 2000电源 | 1 | 3299 | 3299 |
NVME SSD | 三星 1TB SSD固态硬盘 970 EVO | 1 | 1699 | 1699 |
SATA SSD | 三星 1TB SSD固态硬盘 860 EVO | 1 | 1029 | 1029 |
CPU 散热 | 美商海盗船 H150i PRO | 1 | 1299 | 1299 |
- | - | - | - | 64251 |
其实配置的关键就是 GPU、CPU和主板的选择。
博主是2019年10月选择的配置,目前最具有性价比的显卡依然是 RTX2080Ti,图灵架构可以在混合精度运算时大幅度提升性能,缺点是显存较小,只有 11 GB
GPU 在确定是 RTX2080Ti 后就非常好选择了。注意多卡服务器必须是单风扇的涡轮版显卡,因为涡轮散热空气从头进入,热气从尾排出,多张卡互不影响。但是多风扇轴式散热是从下方吸入空气,侧面排出,如果是多卡排出的热气又被上卡吸进去,导致上卡温度爆炸,降频严重。下图可以理解涡轮风扇的风道。
CPU与主板的选择无非就是Intel和AMD的选择,每家又分为民用级和服务器级,这里我选择的是Intel的民用级i9-9820X。AMD的线程撕裂者性价比更高,但可能坑多一些。服务器级的CPU以及主板一般需要经销商采购,区别主要是支持ECC的内存更稳定,京东自营一般不卖,所以没有考虑。
CPU散热器这里选择的是美商海盗船 H150i PRO 360一体水冷。一开始选择的是猫头鹰的风冷,但是由于个头太大,装上后散热块会和显卡背面金属触点接触(显卡没有背板),怕引起短路等问题,最终更换成一体水冷。
由于显卡满载就需要至少1000W的功率,再加上CPU内存硬盘,1600W按道理是足够了,为了稳妥选择了振华的2000W电源。需要注意的是这块电源的插头是10A的空调插头,需要买个转接的插线板。
服务器已经稳定运行了半年多,散热问题还是有必要提一下。虽然机箱装满了4个机箱风扇以及3个一体水冷的风扇,显卡也是机器学习专供的涡轮版可以及时将热气排到机箱外,但是由于机箱放置在只开着一扇窗户的小机房里,导致机房温度骤升(机箱的铁板烫的不敢用手摸)。最终在窗户上加上排气扇解决。四块显卡满载情况下,最下面的显卡温度控制最好,在76度左右,剩下的三块显卡均达到82度,可能碰到了功耗墙。
本次装机除显卡为天猫旗舰店购买外,其余均在京东自营购买。如果下次还有机会重组一套配置的话,显卡的显存会尽可能换大一些的,CPU会尝试使用AMD,可能的话选择水冷(风险和维护成本有点高)。
]]>Debug 查看是因为 Tensor 的 is_leaf
是 False
,说明无法求梯度
解决办法是 Tensor.detach()
就可以求梯度了
]]>在使用 cv2 读取数据集并将其馈送到 model(data)
时,如果没有对data进行检验,就有可能报下面的错误
RuntimeError: Given groups=1, weight of size 64 3 7 7, expected input[1, 4, 224, 224] to have 3 channels, but got 4 channels instead |
或者
RuntimeError: invalid argument 0: Sizes of tensors must match except in dimension 0. Got 3 and 4 in dimension 1 at /tmp/pip-req-build-58y_cjjl/aten/src/TH/generic/THTensor.cpp:689 |
这是因为训练集中有一个图片是 PNG 图片,强行将后缀名改为了 .JPEG
,但还是保留了 PNG 图像的 4 通道(PNG图像除了 RGB 三通道外还有一个 Alpha 通道,表示图像的透明度)
这个图片就是 n02105855_2933.JPEG
这个图片在读取时没有报错,但会报 warning。
JPEG 图像分两种,一种 RGB,一种 CMYK,下面是 CMYK 的图像列表:
n01739381_1309.JPEG |
当你在读取 ImageNet 2012 训练集遇到问题时,可以先尝试使用验证集进行训练,确认不是程序问题,而是训练集问题时,可以尝试删除或替换上述图像。
]]>类型 | 型号 | 数量 |
---|---|---|
GPU | 技嘉RTX2080Ti TURBO 11G | 4 |
CPU | i9-9820X 10核/20线程 | 1 |
CPU散热 | 美商海盗船 H150i PRO | 1 |
主板 | 技嘉 X299-WU8 | 1 |
机箱 | 先马掠食者V1 | 1 |
内存 | 美商海盗船 复仇者LPX DDR4 3000 16GB | 8 |
电源 | 振华 额定2000W LEADEX P 2000电源 | 1 |
PCIE固态硬盘 | 三星 1TB SSD固态硬盘 970 EVO | 1 |
SATA固态硬盘 | 三星 1TB SSD固态硬盘 860 EVO | 1 |
LXD 就是一个提供了 REST API 的 LXC 容器管理器。 LXD 最主要的目标就是使用 Linux 容器而不是硬件虚拟化向用户提供一种接近虚拟机的使用体验。
其优势是
使用 LXD,相当于每个人都拥有一台独立的服务器,运行环境互相隔离,例如可以安装不同的 CUDA 版本或 cuDNN 版本。(容器的显卡驱动必须和宿主机显卡驱动版本号相同,故不能改变)
目前每一个容器都已经安装好了显卡驱动,并分配了内网 IP,可以直接使用 SSH 进行远程登录。主目录有一个 share
共享目录,用于存放公用数据集、安装包、模型等共享资源。
目前使用 LXDUI 统一管理容器,地址为 http://192.168.100.230:15151 进入后界面如下,可以对自己的容器进行 IP 查询以及快照管理。
找到自己的 IP 后就可以使用 SSH 工具进行远程连接,默认用户名为你的姓氏,例如登录 IP 为 192.168.100.111
,名为 zhangsan
,则输入下述命令进行连接:
ssh zhang@192.168.100.111 |
登录成功后可更改用户名和密码:
# 更改用户名 |
这里介绍使用 Anaconda 进行 Python 环境的配置。
1.输入下述命令安装 Anaconda(不要加 sudo):
bash ~/share/install/Anaconda3-2019.07-Linux-x86_64.sh |
其中最后一步 Init 为 yes,这样会在 ~/.bashrc 文件的最后添加初始化信息。
2.运行下述命令更新 .bashrc
source ~/.bashrc |
更新后命令行前出现 (bash)
说明安装成功。
3.更换 Anaconda 源
由于 Anaconda 服务器在国外,换国内源可以加快下载速度。我在实验室搭建了 Anaconda 的本地镜像,本地 Anaconda 镜像的搭建可参考本人博客 搭建本地 Anaconda 镜像。输入下述命令更换为实验室镜像(更新可能不及时):
conda config --add channels http://192.168.100.188/pkgs/free/ |
也可以使用清华源,参考https://mirror.tuna.tsinghua.edu.cn/help/anaconda/
4.创建 Anaconda 环境
例如,我们想创建名为 “pytorch” 的 PyTorch 环境,则可以运行下述命令:
conda create -n "pytorch" pytorch |
这样将安装 PyTorch 的最新版本。若想安装某个特定的版本,可以在后面加 =1.0
。
注意,使用 Anaconda 安装深度学习框架如 PyTorch 或 TensorFlow 时会自动安装 CUDA 和 cuDNN,并且会对深度学习框架进行优化,如果没有特殊需求则不需要再安装 CUDA 和 cuDNN。当然也可以安装(如编译安装 apex)。
输入下述命令进入刚才创建的 PyTorch 环境:
conda activate pytorch |
之后可以使用 conda install xxx
或 pip install xxx
安装 Python 包。
PyCharm IDE 必须是专业版 (Professional),如果是社区版 (Community) 需到 官网 下载专业版。
学生可通过咱学校的 edu 邮箱免费激活,激活教程在此
PyCharm 远程调试的教程有很多,随便找了一个 使用PyCharm进行远程开发和调试
在训练时,我们需要实时监控硬件的变化,如 GPU 的显存,占用率等。使用 nvidia-smi
命令可查看显存和占用率等使用状态,如下图所示。
一般来说,显存和占用率越高,训练速度越快。
使用下述命令可以每 0.1 秒刷新一次界面:
watch -n 0.1 nvidia-smi |
使用下述网址可通过 NetData 监控硬件信息:
http://192.168.100.230:19999/#menu_nv_submenu_Load;theme=slate;help=true
效果如图:
]]>不知道显卡型号的可以通过此命令查看,但可能有的新显卡无法识别。
lspci | grep VGA |
在 NVIDIA 官网 或 GeForce 官网 下载所需的显卡驱动程序。
需要注意的是显卡驱动需要和 CUDA 版本对应,而 CUDA 版本又要和 PyTorch 或 TensorFlow 的版本对应,所以原则上是越新的版本越好,因为可以支持更多版本的深度学习框架。
1.使用下述命令可以查看 nouveau 驱动是否运行:
lsmod | grep nouveau |
若出现下述结果:
nouveau 1863680 9 |
说明 nouveau 驱动正在运行。
2.运行下述命令禁用该驱动:
sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf" |
检查命令是否正确:
cat /etc/modprobe.d/blacklist-nvidia-nouveau.conf |
若出现下述结果说明命令正确:
blacklist nouveau |
3.更新设置并重启:
sudo update-initramfs -u |
4.重启后重新输入下述命令:
lsmod | grep nouveau |
若没有任何输出说明禁用 nouveau 驱动成功
1.安装依赖:
sudo apt install gcc g++ make |
2.登录时按 ctrl + alt + F2
进入命令行并使用用户名密码登录,并输入 sudo telinit 3
打开一个新的 TTY1 界面。如果是 SSH 远程连接,则不需要做上述步骤。
3.安装驱动:
sudo bash ./NVIDIA-Linux-x86_64-418.56.run |
并按下述选项选择:
4.安装成功后输入 nvidia-smi
,若有类似下述输出证明显卡安装成功:
+-----------------------------------------------------------------------------+ |
20230327更新
其实安装显卡驱动有更简单的方法,不用禁用 nouveau 驱动。
ubuntu-drivers devices |
输出 == /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd000025A2sv00001558sd0000866Dbc03sc00i00
vendor : NVIDIA Corporation
driver : nvidia-driver-515-open - distro non-free
driver : nvidia-driver-515-server - distro non-free
driver : nvidia-driver-470-server - distro non-free
driver : nvidia-driver-525 - distro non-free
driver : nvidia-driver-470 - distro non-free
driver : nvidia-driver-515 - distro non-free
driver : nvidia-driver-525-open - distro non-free recommended
driver : nvidia-driver-510 - distro non-free
driver : nvidia-driver-525-server - distro non-free
driver : xserver-xorg-video-nouveau - distro free builtin
最好选择 distro
专有,non-free
闭源,最好是 -server
结尾的安装包
因此我们选择 nvidia-driver-515-server
进行安装: sudo apt install nvidia-driver-515-server
重启命令: sudo reboot
安装 NVIDIA 配置功能: sudo apt install nvidia-xconfig nvidia-settings nvidia-prime
如果重启后黑屏,可通过以下方法解决。
ctrl + alt + f3 进入tty命令行界面后登录。
查询显示管理器:
cat /etc/X11/default-display-manager |
如果输出/usr/sbin/gdm3
,说明显示管理器是gdm3
,执行以下命令解决:
sudo systemctl restart gdm3 |
同理,如果是ligtdm或者sddm等,将命令中的gdm3改为对应即可。
通常情况下新显卡30系列可能有适配问题会黑屏,老显卡10/20系列问题不大。
]]>数据集选择常用的 ISLVRC2012
(ImageNet Large Scale Visual Recognition Challenge)
下载地址:
预处理:
为了使用 Pytorch 自带的 DataLoader 函数进行数据集加载,我们需要将每一个相同类的图片放到相同的文件夹。
训练集只需要解压缩即可:
mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train |
但是验证集图片都在一个文件夹,需要重新分类:
mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar |
参数设置的方式有很多种,有的人喜欢直接在主文件中进行设置;有的人喜欢用 argparse 这个模块;也有人喜欢用 json 格式的文件,我个人喜欢单独创建个 Python 类,以类属性的形式定义参数,详情见下:
class DefaultConfigs(object): |
当我们需要评价一个模型的准确率时,需要输出 top1、top5 等准确率,使用下面函数进行封装。其中 AverageMeter
类可快速计算多个值的平均值等。
class AverageMeter(object): |
当验证模型和训练模型时都需要使用验证集验证模型准确率,来指导下一步操作。注意需要将 model
切换为 evaluate
模式。其中 torch.no_grad()
表示计算时不会改变模型梯度。
def validate(val_loader, model, criterion): |
注意需要将 model
切换为 train
模式。
def train(train_loader, model, criterion, optimizer): |
注意在数据集加载时,train_loader
的 shuffle
为 True
。
def main(): |
本文使用的 Pytorch 版本为 1.0.1,且暂时只适用于 ImageNet 数据集,其他数据集需要一定地修改,完整代码地址如下:https://gist.github.com/xunge/d7be591bc1b41350273a61722c0d398a
激活函数需要具备以下几点性质:
Sigmoid 型函数是指一类 S 型曲线函数,为两端饱和函数。常用的 Sigmoid 型函数有 Logistic 函数和 Tanh 函数。
公式定义如下:
\[\sigma(x)=\frac{1}{1+\exp (-x)}\]
特点:
软性门
(Soft Gate),用来控制其它神经元输出信息的数量。公式定义如下:
\[\tanh (x)=\frac{\exp (x)-\exp (-x)}{\exp (x)+\exp (-x)}\]
Tanh 函数也可以看作是放大并平移的 Logistic 函数,其值域是 (−1, 1)。
\[\tanh (x)=2 \sigma(2 x)-1\]
Sigmoid 两个函数对比如下图所示。如图可知,Tanh 函数的输出是 零中心化的
(Zero-Centered),而 Logistic 函数的输出恒大于 0。非零中心化的输出会使得其后一层的神经元的输入发生 偏置偏移
(Bias Shift),并进一步使得梯度下降的收敛速度变慢。
线性整流函数 (Rectified Linear Unit, ReLU),又称修正线性单元,通常指代以斜坡函数及其变种为代表的非线性函数。是目前深层神经网络中经常使用的激活函数。公式定义为:
\[\begin{aligned} \operatorname{ReLU}(x) &=\left\{\begin{array}{ll}{x} & {x \geq 0} \\ {0} & {x<0}\end{array}\right.\\ &=\max (0, x) \end{aligned}\]
优点:
缺点:
为了避免上述情况,有几种 ReLU 的变种也会被广泛使用。
带泄露的ReLU (Leaky ReLU) 在输入 \(x < 0\) 时,保持一个很小的梯度 \(\gamma\)。这样当神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活。带泄露的ReLU的定义如下:
\[\begin{aligned} \text { LeakyReLU(x)} &=\left\{\begin{array}{ll}{x} & { x>0} \\ {\gamma x} & { x \leq 0}\end{array}\right.\\ &=\max (0, x)+\gamma \min (0, x) \end{aligned}\]
其中 \(\gamma\) 是一个很小的常数,比如 0.01。当 \(\gamma < 1\) 时,带泄露的 ReLU 也可以写为
\[\text { Leaky ReLU }(x)=\max (x, \gamma x)\]
相当于是一个比较简单的 maxout 单元。
带参数的 ReLU (Parametric ReLU, PReLU) 引入一个可学习的参数,不同神经元可以有不同的参数。对于第 \(i\) 个神经元,其 PReLU 的定义为:
\[\begin{aligned} \operatorname{PReLU}_{i}(x) &=\left\{\begin{array}{ll}{x} & { x>0} \\ {\gamma_{i} x} & { x \leq 0}\end{array}\right.\\ &=\max (0, x)+\gamma_{i} \min (0, x) \end{aligned}\]
其中 \(\gamma_{i}\) 为 \(x \leq 0\) 时函数的斜率。因此,PReLU 是非饱和函数。如果 \(\gamma_{i}=0\),那么 PReLU 就退化为 ReLU。如果 \(\gamma_{i}\) 为一个很小的常数,则 PReLU 可以看作带泄露的 ReLU。PReLU 可以允许不同神经元具有不同的参数,也可以一组神经元共享一个参数。
指数线性单元 (Exponential Linear Unit, ELU)是一个近似的零中心化的非线性函数,其定义为:
\[\begin{aligned} \operatorname{ELU}(x) &=\left\{\begin{array}{ll}{x} & { x>0} \\ {\gamma(\exp (x)-1)} & { x \leq 0}\end{array}\right.\\ &=\max (0, x)+\min (0, \gamma(\exp (x)-1)) \end{aligned}\]
其中 \(\gamma \geq 0\) 是一个超参数,决定 \(x \leq 0\) 时的饱和曲线,并调整输出均值在 0 附近。
Softplus 函数可以看作是 rectifier 函数的平滑版本,其定义为:
\[\operatorname{Softplus}(x)=\log (1+\exp (x))\]
Softplus 函数其导数刚好是 Logistic 函数。Softplus 函数虽然也有具有单侧抑制、宽兴奋边界的特性,却没有稀疏激活性。
下图给出了 ReLU、Leaky ReLU、ELU 以及 Softplus 函数的示例:
然而Anaconda国外源在国内下载速度较慢,虽然国内有清华源可以大大提高下载速度(2019年4月清华源曾因版权原因关闭,但在5月已重新开放),但是肯定没有搭建一个本地源速度快。本文将详细介绍如何将Anaconda镜像安装在本地,以供本机以及局域网内的其他电脑访问。
搭建本地镜像肯定需要将所有镜像文件下载到本地。
这里感谢清华开源下载镜像文件的Python代码,这里进行了一定的修改,代码如下。
可以看到代码中的路径改为了国内的清华源, repos 只选择了 main
和 free
,arches 选择了 linux-64
和 win-64
,当然也可以选择同步注释代码中的更多系统和版本。博主下载了这些文件共189.1 GB(2019年6月),也就是说只需要占用不到 200 GB 的磁盘空间,无需下载即可使用Anaconda安装Python包,还是很实用的。
具体代码如下:
#!/usr/bin/env python3 |
下载的文件会在 pkgs
根目录下,我们需要运行以下命令
conda index pkgs/* |
运行需要较长时间,运行完成后会在 free
和 main
文件夹内生成 noarch
文件夹。
为了使局域网内的用户都可访问本地Anaconda镜像,我们首先搭建一个本地http服务器,参考这篇 博客
在 ubuntu 系统下运行下面命令
sudo apt install apache2 |
apache2 的配置文件是 /etc/apache2/apache2.conf
。
服务器默认的访问路径在 /var/www/html
目录下。
创建软链接,例如我们的镜像 pkgs 文件夹在 /home/ubuntu/mirror/anaconda/pkgs
,在 /var/www/html
目录下通过命令 ln -s /home/ubuntu/mirror/anaconda/pkgs/ anaconda/pkgs
创建一个软连接。就可以通过 http://192.168.1.10/anaconda/pkgs
访问到文件目录。
通过以下命令设置Anaconda的镜像路径:
conda config --add channels http://192.168.1.10/anaconda/pkgs/free/ |
然后编辑配置文件 .condarc
,一般在 ~/.condarc
,去掉最后的 - defaults
至此,本地镜像的配置完成,我们可以离线安装Anaconda管理包了,速度不是一般的快。
为了使得镜像及时更新,我们可以使用 Linux 的 crontab 服务定时更新 anaconda.py 脚本。具体方法如下:
运行 crontab –e
编写一条定时任务:
0 1 * * 6 /usr/bin/python /home/ubuntu/anaconda.py --working-dir /home/ubuntu/anaconda-mirror > /home/ubuntu/auto.log |
意思是每周六的凌晨 1:00 执行 anaconda.py 脚本。
其他关于 crontab 的详细说明见文档说明 19. crontab 定时任务
运行 crontab –l
进行验证。
运行 service cron restart
重启服务。
adversarial example
翻译为 对抗性的例子
而不是 对抗样本
。虽然 Word 可以全局替换,但是一次只能替换一个词组,如果想同时替换多个词组将非常费事。这时使用 VBA 将是一个不错的选择。视图
-> 宏
-> 查看宏
-> 新建宏
命名为 replace。
在打开的 VB 编辑器粘贴下面代码,更改具体的替换词语,即可
Sub replace() |
保存后关闭编辑器即可。
之后只需要运行名为 replace 的宏即可。
也可以将该宏添加到快速访问工具栏中,这样可以更方便的调用宏。
]]>本文采用在宿主机上创建多个 LXD 容器的软件虚拟化方法,使得资源能够更好的利用。
基于上述背景提出以下需求:
本人在 Ubuntu 18.04 的宿主机上使用 LXD 容器中的 Ubuntu 18.04 系统完成了上述所有需求。
sudo snap install lxd |
我们需要安装LXD实现虚拟容器,ZFS作为LXD的存储管理工具,bridge-utils用于搭建网桥。由于apt安装的LXD不是最新版本,这里使用snap安装工具安装LXD。
sudo lxd init |
在初始化过程中,不要创建新的网桥,ZFS设置大小要尽量大,其他设置默认即可。详情如下:
Would you like to use LXD clustering? (yes/no) [default=no]: |
如果网速允许可以尝试:
sudo lxc launch ubuntu:xenial yourContainerName |
如果网速不行可以添加清华大学的镜像:
sudo lxc remote add tuna-images https://mirrors.tuna.tsinghua.edu.cn/lxc-images/ --protocol=simplestreams –public |
使用如下命令查看清华镜像的所有系统:
sudo lxc image list tuna-images: |
选择容器系统并记录第二列的 FINGERPRINT,如 ubuntu/18.04 的FINGERPRINT为 0023c4e9dc6e
然后使用如下命令创建新容器:
sudo lxc launch tuna-images:0023c4e9dc6e yourContainerName |
在实验室的其他电脑访问服务器中的 LXD 容器有两种方法。
很明显,第 2 种方法比第 1 种方法好,不仅登录方便,而且每个容器的所有端口都是开放的,可以拿其他端口做一些事情,如搭建网站、开启服务等。
第 2 种方法的详细步骤参见本人博客 LXD 使用 Netplan 实现在同一网络访问
由于容器默认的用户名为 ubuntu
,我们需要在安装其他程序之前更改用户名即密码,以避免路径冲突。
修改root密码:
passwd root |
修改用户ubuntu密码:
passwd ubuntu |
修改用户名:
usermod -l <newname> -d /home/<newname> -m <oldname> |
sudo apt-get install openssh-server |
至此,已经可以使用实验室的任意电脑通过 ssh 直接访问容器而不需要操作宿主机。
在宿主机中首先需要安装显卡驱动,使用如下命令安装推荐的显卡驱动:
sudo ubuntu-drivers autoinstall |
但是这个方法安装的驱动往往不是最新的,可以去 NVIDIA 官网 下载最新驱动并安装。具体方法参考 Ubuntu 16.04安装NVIDIA驱动 和 超详细! Ubuntu 18.04 安装 NVIDIA 显卡驱动
使用 nvidia-smi 查看驱动版本,并在官网下载对应的驱动,以便于在容器中安装和宿主机相同版本的NVIDIA驱动。
为容器添加所有GPU:
sudo lxc config device add <container> gpu gpu |
添加指定GPU:
sudo lxc config device add <container> gpu0 gpu id=0 |
在容器中安装显卡驱动:
sudo sh ./NVIDIA-Linux-x86_64-xxx.xx.run --no-kernel-module |
因为在容器中显卡驱动不需要安装内核文件,所以后面加上 --no-kernel-module
。
在容器中安装好显卡驱动重启后输入 nvidia-smi
检查驱动是否安装成功
因为 LXD 相当于 LXC 增加了 RESTful API,所以可以通过 WEB 界面管理容器。lxdui 为不错的管理界面。具体安装方法详见 lxdui 的 github
目前 lxdui 版本为 2.1.2,我使用有一些 bug,例如 snapshot、clone 等操作只能在容器表格中的 Actions 进行操作,进入容器后顶部的 snapshot、clone 等按钮是无效的。所以 lxdui 主要还是方便查看和管理,具体的操作还是用命令吧。
以上所有对容器的操作基本完成,现在需要建立快照,以备将来需要恢复快照,或者可以从快照新建一个容器,这样可以避免上面的重复劳动。
创建快照:
sudo lxc snapshot <container> <snapshot name> |
恢复快照:
sudo lxc restore <container> <snapshot name> |
从快照新建一个容器 (新旧容器 MAC 地址不同):
sudo lxc copy <source container>/<snapshot name> <destination container> |
这也是比较好的创建容器的方法。如果直接 clone 容器的话,MAC 地址等关键信息也会同样被复制。
在宿主机输入以下命令
sudo lxc file push <source path> <container>/<path> # 表示从容器中复制文件到宿主机 |
复制文件夹需要在最后加 -r
创建共享文件夹:
sudo lxc config set <container> security.privileged true |
其中 path
为容器路径,source
为宿主机路径。device-name
随意取名字即可。
移除共享文件夹:
sudo lxc config device remove <container> <device-name> |
CUDA 与 cuDNN 的安装建议使用 Anaconda 安装,因为使用 Anaconda 安装 TensorFlow 或 PyTorch 都会自带 CUDA 与 cuDNN,并且据说有一定的优化。具体命令为:
conda create -n tf_gpu1.9 tensorflow-gpu=1.9 |
只要 CUDA 版本与 NVIDIA 驱动版本相对应即可,可以在 Release Notes :: CUDA Toolkit Documentation 查找。
参考文档:How to Connect to a Ubuntu 18.04 Server via Remote Desktop Connection using xRDP
xRDP 是一款非常不错的远程桌面软件,且全平台支持。安装 xRDP 最好在干净的系统上安装。
Microsoft Remote Desktop
mac 版,App Store 搜不到,这里放上 下载链接。FreeRDP
、Rdesktop
等开源软件。sudo apt-get update |
Ubuntu 有很多桌面环境,例如 XFCE, Lubuntu, Xubuntu 和 MATE 等。这里使用 XFCE 为例:
sudo apt-get install xfce4 |
现在就可以使用远程桌面软件,通过 IP 即可访问。
参考文档:docker run hello-world still fails, permission denied - stackoverflow
在 LXD 环境中依然可以使用 Docker,但是需要更改一些配置,否则在 pull 镜像时会有 permission denied
错误,如下:
OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:430: container init caused \"rootfs_linux.go:58: mounting \\\"proc\\\" to rootfs \\\"/var/lib/docker/vfs/dir/c5cedb213621362913c6d950eec507ba91e04f2a933cd6d309f1c74a92c346ec\\\" at \\\"/proc\\\" caused \\\"permission denied\\\"\"": unknown |
我们只需要在宿主机对容器进行以下配置即可:
sudo lxc config set <container> security.nesting true |
重启宿主机后,再使用容器里的环境时会找不到 CUDA,报下面的错误:
RuntimeError: cuda runtime error (30) : unknown error at /tmp/pip-req-build-58y_cjjl/aten/src/THC/THCGeneral.cpp:50 |
或者 torch.cuda.is_available()
是 false
网上其他人报这个错误可能是因为 CUDA 没有安装,但我使用的是 Anaconda 自动安装的 CUDA 和 cuDNN,容器和宿主机都可以运行 nvidia-smi
命令查看显卡驱动版本,并且在重启前是没有错误的,为什么重启后就报错了呢?为此我甚至重装系统,但这个问题依然存在。。。
目前临时的解决办法是:
重启宿主机后,需要使用宿主机的 Python 环境运行一次使用 CUDA 的程序;
例如,如果是pytorch环境,可以运行下面的代码:
python -c 'import torch; print(torch.cuda.is_available())'
重启所有容器。
这样容器里的 CUDA 就可以找到了。这可能是 LXC 的配置问题,如果有人遇到相同问题有更好的解决方案希望可以告知,万分感谢~
重启宿主机后,显卡驱动掉了,所有用到显卡的容器都无法启动,输入nvidia-smi
报错:
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running |
原因可能是宿主机运行了sudo apt upgrade
命令并更新了系统内核,导致安装显卡驱动时的内核与现有内核版本不一致,解决办法参考此解决办法
sudo apt-get install dkms # DKMS,Dynamic Kernel Module Support,可以帮我们维护内核外的这些驱动程序,在内核版本变动之后可以自动重新生成新的模块。 |
DKMS全称是Dynamic Kernel Module Support,它可以帮我们维护内核外的这些驱动程序,在内核版本变动之后可以自动重新生成新的模块。
430.50是安装驱动的版本,可进入/usr/src查看nvidia-430.50的文件夹获取版本号
重启后输入nvidia-smi就应该正常输出了。如果还是不好使,可以重启后重新运行上述命令再重启。
如果仍不好使,说明该内核下无法编译显卡驱动,解决方法是内核降级为原来版本,具体方法参考如何降级/切换 Ubuntu 系统 Linux 内核启动版本
grep menuentry /boot/grub/grub.cfg |
可以看到子选项:
Ubuntu, with Linux 5.3.0-62-generic
使用vim编辑grub文件:
sudo vim /etc/default/grub |
GRUB_DEFAULT=0 // 0表示系统当前启动的内核序号
修改为想要启动的内核版本对应子选项:
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 5.3.0-62-generic"
注意,>
两侧没有空格,这个原博客有错误。
sudo update-grub |
uname -r(或-a) |
LXD 初始化的时候会对 ZFS 进行空间分配,但是随着时间的推移仍有扩容的需求。当容器变得很卡的时候,有可能就是 ZFS 分配的空间已满。
输入下面的命令可以对 ZFS 进行扩容,以扩容512GB为例:
sudo truncate -s +512G /var/snap/lxd/common/lxd/disks/lxd.img |
其中,lxd.img 为初始化时定义的容器名称,不知道容器名称的可以通过下述命令查看 sudo ls /var/snap/lxd/common/lxd/disks/
至此,多人使用的 GPU 服务器就搭建完成了,当需要新建容器时,只需要完成以下几步:
lxc exec <container> bash
进入容器;这在以后可以编写脚本,使得新建操作更容易。
]]>本文使用 Ubuntu 18.04 默认的网络配置工具 Netplan 管理网络,实现 LXD 容器的 IP 与实验室的 IP 域相同。
Ubuntu 17.10 以后默认使用 Netplan 管理网络。进入 /etc/netplan/
目录有一个 yaml 配置文件,下面的命令需要根据自己的 yaml 文件名称自行修改
备份配置文件:
sudo cp /etc/netplan/01-netcfg.yaml /etc/netplan/01-netcfg.yaml.bak |
编辑 yaml 配置文件:
sudo vim /etc/netplan/01-netcfg.yaml |
如下:
# This file describes the network interfaces available on your system |
addresses: [ 192.168.1.2/24 ]
为任意网络无人占用的 IP 即可。gateway4
为网关地址。eno1
为网卡名称,可以使用 ip a
或 ifconfig
命令查看。应用网络配置:
sudo netplan --debug apply |
重启后确认网络可用:
sudo reboot |
进入容器:
sudo lxc exec <container> bash |
编辑 yaml 配置文件:
nano /etc/netplan/50-cloud-init.yaml |
如下:
network: |
应用网络配置:
netplan --debug apply |
同样重启后检查网络连接:
sudo reboot |
不再建议使用转接卡更换苹果电脑固态硬盘,因为在一年后就无法正常工作了。转接卡失效,760p 固态硬盘主控坏掉,只能识别 1 GB 的缓存。虽然数据及时导出,且硬盘有 5 年质保,但仍不推荐使用此方法扩容。可以尝试咸鱼购买拆机硬盘,现在也不贵。
本教程适用于 13/14/15 年 MacBook Pro 升级非官方硬盘
由于 MacBook Pro 的硬盘接口是自己设计的,所以更换硬盘有两种选择:
本文将介绍使用第三方 M.2 接口的固态硬盘和转接卡升级 MacBook Pro 硬盘的方案
SSD 主要是根据广大网友的前车之鉴,目前比较推荐的有三星 SM951,Intel 760P等。博主选择的是 Intel 760P,因为 SM951 目前很难买到新款,基本上都是拆机的二手货,没有质保,而 Intel 760P 有 5 年保修,价格也差不多。
转接卡在淘宝搜索 苹果SSD转接卡 2014 mac book
,博主买的转接卡如下图所示。
因为在 macOS 10.13 High Sierra 版本后系统支持了 NVME 硬盘协议,所以我们安装的 macOS 版本必须是10.13之后的系统。本文选择的是 macOS 10.14 Mojave 版本,具体制作 macOS系统盘的教程可以参考 本人博客
硬盘有价,数据无价!
拆机时还需要 T5 的螺丝刀,这里建议购买 米家wiha螺丝刀
拆机等详细步骤可以参考 IFIXIT 更换 SSD 教程
拆机过程要注意上面两颗螺丝和其他八颗螺丝的长度是不一样的,重新装机的时候要注意。
后机盖和机身有两个卡扣固定,装机要确定卡扣卡紧再上螺丝。
更换完 SSD 是这样的
注意一定要将硬盘接头用力往里塞紧,并保证固定螺丝是可以拧上固定的状态。
插上刚才制作的 macOS 系统盘,按住 option
键开机,选择 install macOS 进入下面界面,选择磁盘工具,继续
将新安装的 SSD 抹掉,格式为 Mac OS 扩展(日志式)
,方案为 GUID 分区图
,如下图所示
若没有检测到新硬盘,则可能是硬盘没有插紧,或者硬盘不是全新的硬盘留有分区,只需要在 Windows 系统下删除卷即可
之后关闭磁盘工具,进入第二项 安装 macOS
即可成功安装。
新旧硬盘测速图如下(都是刚装完系统就测试的):
如果将电脑扣盖休眠 8 小时后(或者电脑休眠至没电关机)再开机发现无法唤醒电脑(电脑屏幕亮,但无反应),那么你可能需要继续往下阅读
Mac 电脑默认模式下休眠时间超过 8 小时后会将内存中的资料存储在硬盘中并将内存断电,以达到更好的省电目的。可能因为转接卡或硬盘不兼容将资料从硬盘导入内存造成无法唤醒的现象。
以下来自苹果官网:
1.如何重置 Mac 笔记本电脑上的 SMC:
如果电池不可拆卸: 选取苹果菜单 >“关机”。 等 Mac 关机后,按下内建键盘左侧的 Shift-Control-Option,然后同时按下电源按钮。按住这些按键和电源按钮 10 秒钟。 如果您的 MacBook Pro 带有触控 ID,则触控 ID 按钮也是电源按钮。 松开所有按键。 再次按下电源按钮以开启 Mac。
2.如何重置 NVRAM
将 Mac 关机,然后开机并立即同时按住以下四个按键:Option、Command、P 和 R。您可以在大约 20 秒后松开这些按键,在此
期间您的 Mac 可能看似在重新启动。
在发出启动声的 Mac 电脑上,您可以在两次启动声之后松开这些按键。 在 iMac Pro 上,您可以在 Apple 标志第二次出现并消失后松开这些按键。 如果您的 Mac 使用了固件密码,这个组合键将不起任何作用或导致您的 Mac 从 macOS 恢复功能启动。要重置 NVRAM,请先关闭固件密码。
在您的 Mac 完成启动后,您可能需要打开“系统偏好设置”并调整已重置的任何设置,例如音量、显示屏分辨率、启动磁盘选择或时区。
如果上述方法依旧存在休眠无法唤醒的情况,那就只能使用下述命令通过更改 hibernatemode,使电脑不进入内存断电的休眠模式。
# 永不休眠 |
详细命令参考博客 mac下pmset的使用方法
虽然更改后可能会略微增加耗电,但是开盖唤醒速度也会加快。需要注意的是更改模式后在电脑快要没电的时候需要关机,否则可能导致数据的丢失。
]]>做项目的时候遇到一个需求,就是如何将网页后端生成的 Matplotlib 图像展示在前端页面上。尝试使用了如下几种方法,在此记录一下。
这是一个相对简单并且改动较小的方法,只需要在后端改一下 import 就可以,具体使用方法参照官方教程。
但是实现的时候遇到了一些问题
该方法易于实现,首先将网页后端的图像保存到后端服务器的 static 目录下,前端再从 static 目录下读取图片进行展示。
但是依然存在一些问题
该方法将图像以请求的方式传到前端,前端只需将 <img>
标签的 src 属性赋值为后端的请求路径即可。
该方法可以在后端生成完图像后再发送给前端,无需设置延时获取图像,但也遇到一些问题
fig.savefig('a.png', bbox_inches='tight', pad_inches=0.0)
这句代码时才能去除白边,而发送请求只能发送 fig ,所以前端显示的图像有白边。该方法也是本人最终采取的方法。原理是在调用 savefig 方法时不存储为图像,而是存储为二进制格式,二进制格式再转化为 Base64 格式,并将其发送给前端,前端只需要将 <img>
标签的 src 属性赋值为后端发送的 Base64 字符串即可。
后端代码如下所示
from io import BytesIO |
将最后一行的 src
传到前端即可展示。
该方法优点如下
所以几乎解决了之前方法的所有痛点
]]>但是因为 TensorFlow 在 1.2 版本后不再支持 macOS 的 GPU 版本,只能通过编译源代码进行安装,过程较为繁杂,所以在此记录
首先确定 Mac 显卡是 NVIDIA 显卡,且compute capabilities >= 3.0,点击这里 查看你的显卡型号是否支持
软件 | 版本号 |
---|---|
macOS High Sierra | 10.13.4 |
TensorFlow | 1.8 |
python | 3.6.4 |
NVIDIA Web-Drivers | 387.10.10.10.30.106 |
CUDA-Drivers | 387.178 |
CUDA Toolkit | 9.1 |
cuDNN | 7.0.5 |
bazel | 0.10.0 |
Xcode | 8.3.2 |
Command Line Tools for Xcode | 8.3.2 |
在终端输入下面命令安装 Homebrew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
brew install coreutils llvm cliutils/apple/libomp |
建议使用 Anaconda 包管理和 Virtualenv 虚拟环境等安装 Python
pip install six numpy wheel |
下载 0.10.0 版本中的 bazel-0.10.0-installer-darwin-x86_64.sh
文件
需要注意,这里必须是 0.10.0 版本,新或旧都能导致编译失败
在下载目录打开终端,输入下面命令进行安装
chmod +x bazel-0.10.0-installer-darwin-x86_64.sh |
下载 Xcode 8.3.2
和 Command Line Tools for Xcode 8.3.2
,Xcode 9 需要降级,因为编译 TensorFlow 只能使用 Xcode 8,官网下载需要登录苹果账号,官网下载链接,按名称排列即可快速找到。
Xcode8.3.2.xip
(4.49GB) 下载后解压,重命名为 Xcode8.3.2
并复制到 应用程序
即可CommandLineToolsforXcode8.3.2.dmg
(166.1MB) 下载后安装即可使用下面的命令在终端激活 Xcode 8.3.2
sudo xcode-select -s /Applications/Xcode8.3.2.app |
换回 Xcode 9 可以用
sudo xcode-select -s /Applications/Xcode.app |
下载 NVIDIA Web-Drivers
驱动,根据不同的 Mac 系统进行下载,点击这里 下载,支持 macOS 10.13.4 的版本为 387.10.10.10.30.106
WebDriver-387.10.10.10.30.106.pkg
(63.9MB) 下载后安装即可下载 CUDA-Drivers
驱动,支持 CUDA 9.1 的版本号为 387.178,官网 下载、 百度云 下载
cudadriver_387.178_macos.dmg
(39.9MB) 下载后安装即可下载 CUDA Toolkit 9.1
,官网 下载和 百度云 下载
cuda_9.1.128_mac.dmg
(1.53GB) 下载后安装即可配置 CUDA 环境,编辑 ~/.bash_profile
文件,如果安装了zsh则编辑 ~/.zshrc
文件,打开终端:
open -e .bash_profile |
然后在弹出的文件中添加:
export CUDA_HOME=/usr/local/cuda |
执行命令重启bash_profile
. ~/.bash_profile |
检测CUDA能否正常运行:
cd /usr/local/cuda/samples |
第一次编译时可能需要同意苹果协议,按照要求填 agree
即可
最终结果为 Result = PASS
则安装正确。
下载 cuDNN 7.0.5
,该版本支持 CUDA 9.1 ,官网下载时需要登录 NVIDIA 账号,官网 下载、 百度云 下载
cudnn-9.1-osx-x64-v7-ga.tgz
(340.3MB) 下载后解压,切换到解压缩的 cuda
目录,输入以下命令sudo cp cuda/include/cudnn.h /usr/local/cuda/include |
git clone https://github.com/tensorflow/tensorflow -b r1.8 |
cd tensorflow |
我还遇到了以下错误
ERROR: /Users/xunge/Desktop/tensorflow/tensorflow/tools/pip_package/BUILD:166:1: error loading package 'tensorflow': Encountered error while reading extension file 'protobuf.bzl': no such package '@protobuf_archive//': java.io.IOException: thread interrupted and referenced by '//tensorflow/tools/pip_package:build_pip_package' |
解决办法 如下:
sed -i '\@https://github.com/google/protobuf/archive/0b059a3d8a8f8aa40dde7bea55edca4ec5dfea66.tar.gz@d' tensorflow/workspace.bzl |
下载 nccl.h,放在 third_party/nccl
文件夹内
tf_http_archive( |
搜索如上替换为如下
tf_http_archive( |
修复 third_party/gpus/cuda/BUILD.tpl 文件 -lgomp 报错
linkopts = ["-lgomp"], |
搜索如上,注释掉
# linkopts = ["-lgomp"], |
在 TensorFlow 目录下输入以下命令进行命令配置
./configure |
配置文件如下
You have bazel 0.10 installed. |
bazel clean --expunge |
如果看到
INFO: Build completed successfully, 9160 total actions |
就说明编译成功
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg |
本人编译完成后的文件为 tensorflow-1.8.0-cp36-cp36m-macosx_10_7_x86_64.whl
百度云下载
最后提供本人 Z270 + i7-7700k 的黑苹果 EFI
参考文章:
]]>TypeError: __init__() missing 1 required positional argument: 'on_delete' |
是因为在 Django 2.0 后,models.ForeignKey() 函数 和 models.OneToOneField() 中的 on_delete 参数不再默认为 CASCADE ,而是必须参数
官方文档:https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.ForeignKey.on_delete
修改方法如下
修改之前
class BlogArticles(models.Model): |
修改之后
class BlogArticles(models.Model): |
需要在 BIOS 中进行更改。我的是微星 BIOS,操作如下
高级
-> 换型事件设置
-> 将 PCIE设备唤醒
和 网络唤醒
设置为 允许
(Enable)
其他 BIOS 也类似,因为网卡也属于 PCIE 设备,所以 PCIE设备唤醒 也需要打开。
在设备驱动管理器中,找到 网络适配器
,在第一个驱动 右键
-> 属性
在 高级
菜单中的属性找到 唤醒魔包
(Wake on Magic Packet) 设置为 启用
在 电源管理
中 勾选
允许此设备唤醒计算机
由于 IPV4 地址紧张,运营商宽带都是使用的动态 IP 地址,这就需要 动态 DNS 服务
进行穿透局域网。
我家的路由器是 网件 NETGEAR R7800
所以这里使用 NETGEAR 的 DDNS 服务,其他路由器基本也有自己的 DDNS 服务,大家可以自己选择。
首先登录路由器控制界面,一般是浏览器输入 192.168.0.1 / 192.168.1.1 / 10.0.0.1 等进入。
找到 DDNS
或者 动态 DNS
,注册 DDNS 服务商。网件提供三个 DDNS 服务商,我选择的是 www.No-IP.com
。
找到 端口映射/端口触发
,在 端口映射
中 添加自定义服务
。
服务名随便填,协议:TCP/UDP,外部端口组
和 内部端口组
一致即可,内部 IP 地址
映射到家里电脑的 IP。
这样,你就可以使用 WOL 软件发送一个数据包唤醒家里的电脑了。
有一个网站就可以使用 https://www.depicus.com/wake-on-lan/woli。
该网站还提供了 windows, mac OS, Android, iOS 等不同平台的应用,有需要的可以自行下载。
mac 地址可以在控制台输入 ipconfig /all
获取;
IP 地址填 域名即可
;
子网掩码为 255.255.255.255
;
端口号为之前设的外部和内部端口号。
]]>原文写作日期:2017年3月15日 译文写作日期:2017年11月29日
本文为cleverhans-blog的第二篇博客,作者为 Ian Goodfellow 和 Nicolas Papernot,主要讲解 对抗性训练 和 防御性蒸馏 两种防御方法之间的优势与不足。
xunge 是一名初学者,论文翻译中用到了 Google,并自己逐句检查过,但还是会有显得晦涩的地方,如有语法/专业名词翻译错误,还请见谅,并欢迎及时指出。
转载请注明出处!
在我们的第一篇文章中,我们提出了几种攻击者可以打破当前机器学习系统的方式,比如通过毒化学习算法使用的数据[BNL12],或者制作对抗性样本迫使模型做出错误的预测[SZS13]。在本文中,我们将以对抗性样本为例说明为什么攻击机器学习似乎比防御更容易。换句话说,我们将详细介绍为什么我们还没有完全有效的防御对抗性样本的一些原因,并推测我们是否能够进行防御。
对抗性样本是机器学习模型的输入,它是由攻击者设计,用来欺骗模型产生不正确的输出。例如,我们给一个熊猫图片添加一个经过计算的小扰动,使图像被认为是一个高可信度的长臂猿[GSS14]:
到目前为止,设计出这种欺骗模型的方法要比设计出不能欺骗模型的方法容易得多。
我们先介绍下两种防御方法:对抗性训练和防御性蒸馏。防御者如何试图使机器学习模型更加强壮并减轻对抗性样本的攻击效果。
对抗性训练旨在训练时主动产生对抗性样本,在测试时提取对抗性样本来改进模型的泛化。这个想法是由Szegedy等人首次提出的[SZS13],但由于产生对抗性样本的成本太高而不实用。 Goodfellow等人展示了如何利用快速梯度符号方法低成本地产生对抗性样本,并且使得在训练过程中高计算效率地产生大批对抗性样本 [GSS14]。然后该模型被训练成将相同的标签分配给相对于原始样本的对抗性样本。例如:我们拍摄一张猫的照片,并对其进行扰动以欺骗模型,使其认为它是秃鹫,然后告诉模型这张照片仍然是一只猫。对抗训练的一个开源实现可以在cleverhans 库中找到,其使用方法在下面的 教程 中有说明。
防御性蒸馏平滑模型的决策表面在对抗方向被攻击者利用。蒸馏是一种训练过程,其中一个模型被训练以预测由先前训练的另一个模型输出的概率。蒸馏最初由Hinton等人提出。在[HVD15]中,其目标是用一个小模型来模拟一个大型的、计算成本很高的模型。防御性蒸馏有一个不同的目标,即简单地使最终模型的反应更加平滑,所以即使两个模型的大小相同也能起作用。训练一个模型来预测另一个具有相同架构的模型输出看起来是违反直觉的。它的工作原理是,第一个模型是用“硬”标签(图像100%概率是狗而不是猫)训练,然后第二个模型用“软”标签(图像95%概率是狗而不是猫)训练。第二个蒸馏模型对于诸如快速梯度符号法[PM16]或基于雅可比行列式显著图法[PMW16]的攻击更为鲁棒。这两种攻击的实现也分别在这里 和这里的cleverhans 上提供。(已经404 。。2333)
大多数对抗性样本构建技术使用模型的梯度来进行攻击。换句话说,一张飞机的照片,他们测试在图像空间中往哪个方向移动使得图片识别为“猫”的概率增加,然后他们往那个方向进行移动(换句话说,扰乱了输入)。这样新修改后的图像被误认为是猫。
但是,如果没有梯度,如果对图像进行微小的修改不会导致模型的输出发生变化呢?这似乎提供了一些防御,因为攻击者不知道怎样去“推”图像。
我们可以很容易想到一些非常微不足道的方法来摆脱梯度。例如,大多数图像分类模型可以以两种模式运行:一种模式是输出最可能类别的标识,另一种模式是输出概率。如果模型的输出是“99.9%的飞机,0.1%的猫”,那么输入的一个微小的变化会给输出带来一个微小的变化,梯度告诉我们哪些变化会增加“猫”类的概率。如果我们在输出模式只是“飞机”的模式下运行模型,那么对输入的一个微小的变化根本不会改变输出,而梯度不会告诉我们任何事情。让我们做一个思考实验,如何通过以“最有可能的类”模式而不是“概率模式”运行它来防御对抗性样本。攻击者再也不能找到分类为猫的扰乱输入,所以我们可能会有一些防御。不幸的是,之前被归类为猫的图片现在仍被归类为猫。如果攻击者可以猜测哪些点是对抗性样本,这些点将仍然会被错误的分类。我们并没有使模型更加鲁棒,我们只是给了攻击者更少的线索来找出模型防御的漏洞。更不幸的是,事实证明攻击者有一个非常好的策略来猜测防御漏洞的位置。攻击者可以训练他们自己的模型,一个具有梯度的光滑模型,为他们的模型制作对抗性样本,然后将这些对抗性样本用于我们的非光滑模型。很多时候,我们的模型也会错误地分类这些样本。最后,我们的思想实验表明,隐藏梯度并没有达到我们的目的。
因此,我们称之为有缺陷的防御策略梯度掩蔽,这个术语在[PMG16]中有介绍。执行梯度掩蔽的防御策略通常导致在特定方向和训练点的邻域中模型变得非常平滑,这使得攻击者难以找到攻击方向的梯度去扰乱输入。然而,攻击者可以训练一个替代模型:一个模仿防御模型的副本,通过观察防御模型分配给攻击者输入的标签。[PMG16]中介绍了执行这种模型提取攻击的过程。然后攻击者可以使用替代模型的梯度来找到被防御模型错误分类的对抗性样本。在下面的图中,我们再现[PMS16]中对梯度掩蔽的讨论,我们用一维ML问题来说明这种攻击策略。对于高维问题,梯度掩蔽现象会加剧,但难以描述。
令人惊讶的是,我们发现对抗性训练和防御性蒸馏都意外地表现出一种梯度掩蔽。如果我们将对抗性样本从一个模型转移到另一个模型,并且用这些防御之一进行训练,即使对第二个模型的直接攻击会失败,攻击通常也会成功[PMG16]。这表明两种训练方法都能使模型变平滑和消除梯度,而不是确保对多个点进行正确地分类。
在“隐藏梯度”的游戏中,我们看到梯度掩蔽并不是很好的防御。它防御使用梯度的攻击者,但是如果攻击者知道我们正在使用这种防御,那么他们只需要切换到移植攻击。在安全术语中,这意味着梯度掩蔽不是一种自适应防御。
迄今为止提出的大多数针对对抗性样本的防御措施根本不起作用,但是有效的那些并不是自适应的。这意味着就像他们在玩一个打地鼠游戏一样:他们关闭了一些漏洞,但是让其他人打开。
对抗训练需要选择算法来产生对抗性样本。通常情况下,这个模型被训练成可以抵抗在一个步骤中产生的低成本对抗性样本,例如快速梯度符号方法一样。经过训练能抵制这些低成本对抗性样本,这个模型通常能成功地抵制同类低成本的新对抗性样本。如果我们使用高成本的、迭代的对抗性样本,就像[SZS13]中的那些例子,那么模型通常就会被愚弄。
保证适应性是具有挑战性的。灵感可以从差异隐私的框架中得到,它提供了随机算法不会暴露个人用户隐私的正式保证。这一保证不会对攻击者的知识或能力做出假设,因此能够面对未来由攻击者设计的假想攻击。
对抗性样本很难防御,因为很难构建对抗性样本制定过程的理论模型。对抗性样本是许多ML模型(包括神经网络)的非线性和非凸的优化问题的解决方案。由于我们没有很好的理论工具去描述这些复杂的优化问题的解决方案,所以很难做出任何一种防御理论来排除一系列对抗性样本。
从另一个角度来看,对抗性样本很难防御,因为它们需要机器学习模型来为每一个可能的输入生成好的输出。大多数情况下,机器学习模型工作得很好,但只能处理遇到所有可能输入中的很少一部分。
由于可能的输入的量非常巨大,设计出真正自适应的防御是非常困难的。
其他几种对机器学习的攻击也是难以防御。在本文中,我们专注于试图混淆机器学习模型测试过程的输入。但是其他类型的攻击是可能的,例如基于暗中修改训练数据的攻击,使得模型学习攻击者希望它进行的行为。
对抗性机器学习的一个亮点是差分隐私,我们实际上有理论上的观点,即某些训练算法可以防止攻击者从训练好的模型中恢复关于训练集的敏感信息。
将机器学习与攻击和防御都可能的其他场景进行比较是有趣的。
在密码学中,防御者似乎有优势。给定一系列合理的假设,例如加密算法的正确实现,防御者可以可靠地发送攻击者无法解密的消息。
在物理冲突中,攻击者似乎有优势。建造核弹比建造一个能够承受核爆的城市要容易得多。热力学的第二定律似乎意味着,如果防御要求将熵维持在某个阈值以下,那么即使没有明确的攻击者有意引起这种熵的增加,防御者也必然随着时间熵增加而最终失去。
监督学习的“没有免费午餐定理”[W96]指出,在所有可能的数据集进行平均,没有任何机器学习算法在测试时间的新点上比其他算法更好。乍一看,这似乎表明,所有的算法都同样容易受到对抗性样本。然而,“没有免费午餐定理”只适用于我们对问题结构不作假设的情况。当我们研究对抗性样本时,我们假设输入的小扰动不应该改变输出类别,所以一般形式的“没有免费午餐定理”并不适用。
正式揭露攻击者的鲁棒性和对清洁数据的模型表现之间的矛盾关系仍然是一个活跃的研究问题。在[PMS16]中,针对机器学习的对抗性样本的第一个“没有免费午餐定理”表明,在从有限的数据中学习时存在这样的矛盾。结果表明,防御者可以通过转向更丰富的假设类别来阻挠对抗性样本。然而,这种矛盾关系是由于没有合适的数据和学习算法来学习高保真模型所面临的挑战。
对抗性样本的研究是令人兴奋的,因为许多最重要的问题在理论和应用方面都是开放的。在理论上,还没有人知道防御对抗性样本是否是一个理论上没有希望的努力(如试图找到一个通用的机器学习算法),或者是否存在一个最优策略会使防御者更有利。(如在密码学和差分隐私)。在应用方面,还没有人设计出真正强大的防御算法,可以抵抗各种对抗性样本的攻击算法。我们希望我们的读者能够得到启发,解决其中的一些问题。
[BNL12] Biggio, B., Nelson, B., & Laskov, P. (2012). Poisoning attacks against support vector machines. arXiv preprint arXiv:1206.6389.
[GSS14] Goodfellow, I. J., Shlens, J., & Szegedy, C. (2014). Explaining and harnessing adversarial examples. arXiv preprint arXiv:1412.6572.
[HVD15] Hinton, Geoffrey, Oriol Vinyals, and Jeff Dean. “Distilling the knowledge in a neural network.” arXiv preprint arXiv:1503.02531 (2015).
[PM16] Papernot, N., & McDaniel, P. (2016). On the effectiveness of defensive distillation. arXiv preprint arXiv:1607.05113.
[PMG16] Papernot, N., McDaniel, P., Goodfellow, I., Jha, S., Berkay Celik, Z., & Swami, A. (2016). Practical Black-Box Attacks against Deep Learning Systems using Adversarial Examples. arXiv preprint arXiv:1602.02697.
[PMS16] Papernot, N., McDaniel, P., Sinha, A., & Wellman, M. (2016). Towards the Science of Security and Privacy in Machine Learning. arXiv preprint arXiv:1611.03814.
[PMW16] Papernot, N., McDaniel, P., Wu, X., Jha, S., & Swami, A. (2016, May). Distillation as a defense to adversarial perturbations against deep neural networks. In the 2016 IEEE Symposium on Security and Privacy (pp. 582-597).
[SZS13] Szegedy, C., Zaremba, W., Sutskever, I., Bruna, J., Erhan, D., Goodfellow, I., & Fergus, R. (2013). Intriguing properties of neural networks. arXiv preprint arXiv:1312.6199.
[W96] Wolpert, David H. (1996). The lack of a priori distinction between learning algorithms. Neural Computation
]]>MySQL 绿色版优点是安装时间短,可在一部电脑兼容多个版本的 MySQL。
下载地址:http://downloads.mysql.com/archives/get/file/mysql-5.7.11-winx64.zip
可以复制链接使用迅雷下载,速度较快。
解压到指定目录,我的是 “C:.11-winx64”
将解压目录中的 my-default.ini
文件重命名为 my.ini
,并将内容替换为以下即可
[mysqld] |
以管理员身份运行 cmd
:: 进入 `C:\MySQL\mysql-5.7.11-winx64\bin` 目录下, |
打开 MySQL 安装目录,打开 data 目录,有一个 .err
后缀名的文件,用编辑器打开
如果每一行都是 [Warning]
,没有 [Error]
,就说明安装正确,并且最后一行应该如下
[Note] A temporary password is generated for root@localhost: oK-R(foa>4by |
后面 12 个字符为默认生成初始密码,复制
打开 cmd ,输入以下命令
mysql -u root -p |
若出现 “Welcome to the MySQL monitor. Commands end with ; or . ...” 则说明密码正确
若出现 “ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)” 则说明密码错误,编辑 MySQL 配置文件 my.ini
,在 [mysqld]
这个条目下加入 skip-grant-tables
,保存退出后重启 MySQL
密码正确后更改默认密码
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'newPassword'; |
newPassword
更改为新密码
进入 C:\MySQL\mysql-5.7.11-winx64\bin
目录下,输入
mysqld -remove |
或者
sc delete mysql |
执行卸载服务。
]]>id() # 查看每个对象的内存地址 |
>>> 4.0 + 2 |
# 9^2 |
>>> 5 / 2 |
浮点数十进制转化为二进制造成误差 >>> 10.0 / 3
3.3333333333333335
解决方法1:使用 decimal 模块(小数)
>>> import decimal |
解决方法1:使用 fractions 模块(分数)
>>> from fractions import Fraction |
>>> 5 % 2 |
>>> round(1.23456, 2) |
浮点数十进制转化为二进制造成误差
>>> round(1.2345, 3) |
>>> import math |
# input() 函数进行输入赋值 |
>>> dos = "c:\news" |
>>> lang = 'study python' |
>>> "py" + "thon" |
>>> str = "python" |
>>> max(str) |
>>> len(str) |
>>> "I love %s" % "gong yu xin" |
# 判断是否全是字母 |
>>> a = ['gong', 831, 'xungejiang.com'] |
>>> lst = ['jiang', 'xun', 'zhi'] |
本文主要讲述群晖的 SHR 、raid1 、raid0 、raid1 、raid5 之间的区别。
SHR 是 Synology Hybrid RAID 的缩写;当NAS里面只有一颗硬盘的时候,磁盘阵列的模式为 basic ,无数据保护。当再添加为一个硬盘的时候磁盘阵列的模式自动转换成类似 raid1 模式,空间大小不变,但是多了个数据保护。当再加入一个硬盘的时候会自动转换成类似 raid5 模式(前提是你的NAS可以放3个及以上的硬盘),容量为 N-1 个硬盘的总容量,假如 3 个 3T 的硬盘,此时的SHR空间总容量为 (3 - 1) * 3T
为 6T
的空间,后期可以慢慢加硬盘数据也不影响的。
SHR 优点:在于方便不熟悉磁盘阵列的玩家,傻瓜简单式的帮你组好磁盘阵列,而且还能合理利用容量大小不一的硬盘,减少浪费,raid 是按照最小的硬盘算,而 SHR 则可以合理利用减少浪费,智能 raid 推荐使用~
附计算网址:https://www.synology.cn/zh-cn/support/RAID_calculator。
basic 是基本模式,一个硬盘一个独立的空间。
raid0:无数据保护,空间最大化利用,当在NAS中运行的时候就和JBOD属性差不多,就不再多介绍,raid0是将多个磁盘合并成一个大的磁盘,不具有冗余,并行 I / O
,速度最快。它是将多个磁盘并列起来,成为一个大磁盘。
raid0 优点:传输速度快且空间最大化利用,传输速度理论数值是一般raid的2倍,实际速度为1.6倍。
raid0 缺点:没有冗余,数据存入都是以拆分打散的方式放到不同的硬盘,所以说当一块硬盘坏掉的时候所以的数据都会丢失!慎用~
raid1 是镜像备份,实际容量为总空间的一半,N/2,如果有2块3T的硬盘,总容量为(3+3)/2。
raid1 优点:有数据保护,让硬盘坏掉一个时,数据还在,硬盘还可以正常读取。
rard1 缺点:空间折一半,放放重要数据资料,照片,放电影就不划算啦!
raid5 是一种既考虑到数据保护又考虑到硬盘运作成本的解决方案,raid5不对数据进行存储,而是把奇偶校检信息存储到不同的磁盘上。损坏后,用奇偶校检信息和对应的数据去恢复损坏的数据,实际空间为 N-1 ,上面有介绍,假如有3块3T的硬盘,实际空间为 ( 3 - 1 ) * 3T
为 6T
,说直白点:就是假如4个硬盘,3个放数据,1个备份,值得强调的一点就是4个硬盘不分主次,可以任意坏一块硬盘。
raid5 优点:数据安全和成本兼顾,是4盘位NAS玩家的首选。
raid5 缺点:只有一个硬容错,当硬盘坏掉一个是要及时更换。
raid6 和 riad5 原理差不多,riad5 是 N - 1
可以任意坏一个硬盘,raid6 则是可以任意坏 2 个硬盘,N - 2
的模式,适合盘位很多的玩家。
还有 raid2、raid3、raid4、raid6、raid7、raid50、raid53,NAS不支持,感兴趣的朋友可自行百度。
]]>群晖(Synology)DS216play
,2017-7-29
京东购买,下午就到了,狗东物流就是快啊。买的是带 两块 希捷 4T 硬盘的套装,一共 3799
元。单买是 (2250 + 1299 * 2) = 4848 元,相当于赠了一块硬盘,还算挺合适的,当然比 618 贵 100+ 。
NAS (Network Attached Storage) 网络附属存储,也叫网络存储器,是专门用来存储数据的服务器,家用的主要功能其实就是私有云、照片电影的存储等。在各大网盘都被封掉的时代,买一个 NAS 存放一些私有文件还是一个比较好的选择。
传说中群晖是买软件送硬件,一是说群晖性价比低,二是说群晖软件做的确实良心,各大平台都有,插件也比较全,可玩性比较高。
DS216play
是双硬盘位,不支持热插拔硬盘位。个人认为双盘位在家庭中使用足够了,8T (4T * 2) 的硬盘也够使好长时间了。
把两个硬盘装好后,拧上螺丝,插上电源和网线(连路由器或交换机的 LAN 口),按下电源键就可以开机啦~
在连接网络的电脑浏览器输入网址 http://find.synology.com 进行初次配置,设置群晖账号密码等。
之后会提示你安装推荐插件,先点取消,因为我们还要更改一下 RAID 格式。
详细的 shr
basic
raid0
raid1
raid5
raid6
类型介绍参照下面的链接 http://xungejiang.com/2017/08/03/shr-raid015/
安装完系统后默认为 shr
格式,双盘位时为 raid1 模式,多了数据备份功能,但是容量只有一半,也就是说 2 块 4T 的硬盘只有 4T 的容量。由于是家庭使用,没有太重要的文件,所以没必要进行数据备份,需要把 shr
模式改为 basic
模式,这样可用容量才是 8T。
具体方法如下。
如果 NAS 里已经有一些重要的资料不想拿另一个硬盘备份,可以参照这篇博客 如何将raid1(SHR)降级为basic。
不过如果你已经把资料都备份了,推荐恢复出厂设置。方法如下:
控制面板
-> 更新和还原
-> 重置
-> 删除所有数据
。
如果你已经安装过插件,不建议你选择 删除存储空间
进行重置,因为插件容易卸载不干净,影响后续使用。所以最好的方法还是恢复出厂设置。
变为新系统后,在 存储空间管理员
-> 存储空间
-> 删除
-> 删除
将系统默认的 shr 删掉。
之后点 新增
-> 自定义
-> 使用所有硬盘容量的存储空间
-> 勾选第一个硬盘
-> 确定
-> Basic
-> 否
-> 下一步
-> 应用
同理,第二块硬盘重复上述操作,只是在第四步勾选第二块硬盘。
群晖的软件可谓是真的良心,很多插件都已经集成,可直接下载,兼容性很强。
存储空间分析器
:可查看文件类型、重复文件等。
Cloud Station Server
:可下载 Cloud Station Drive
和 Cloud Station Backup
两个客户端,区别是 Drive
是 双向同步,保证云端和本地一致;而 Backup
只有新增才会同步,删除本地云端不会删除。
Cloud Sync
:可同步各大网盘,以 百度网盘
为例,配置好后,只要将文件保存至 我的应用数据
-> Cloud Sync
里即可自动下载到 NAS 中,不过速度较慢
Download Station
:远程下载,不过速度较慢。由于迅雷取消了第三方软件的远程下载,只有迅雷的下载包和小米路由器可以使用,所以群晖的远程下载也被取消,远程下东西只能用 Download Station
和 同步云盘。
本文主要介绍一下 jcaptcha
验证码的实现 (IDEA , 附源码)。
项目源码:
https://github.com/xunge/SSM-jcaptcha
参考:
由于 jcaptcha 有个缺陷,就是无法使用 AJAX 进行验证,因为一旦验证就会清除 session,这就导致如果使用 AJAX 验证后,如果输入的验证码错误,就无法重复验证,只有刷新网页才可重新使用。
这里参考 这篇博客,将清除 session 的操作提取出来,便可以使用 AJAX 进行验证了。
jcaptcha
实现了验证码功能,并使用AJAX技术实时验证。<dependency> |
在 resources/spring
下新建 spring-jcaptcha.xml
。
该文件主要控制验证码的样式,可根据官网适当修改。
|
因为是在 resources/spring
下新建 spring-jcaptcha.xml
, 所以 web.xml
无需重新配置。
|
在 controller
下新建 JcaptchaImageCreater.java
,用来生成验证码图片。
import java.awt.image.BufferedImage; |
在 service
下新建 CustomGenericManageableCaptchaService.java
,将 removeCaptcha 方法提出来,便可以使用 AJAX 进行验证。
import com.octo.captcha.engine.CaptchaEngine; |
在 controller
下新建 LoginController
,进行用户注册和检查验证码的方法。
import javax.servlet.http.HttpServletRequest; |
<input type="text" class="form-control input-lg input_size input-captcha" id="captcha" name="captcha" maxlength="4" placeholder="请输入验证码" /> |
前端 AJAX 验证使用 JQuery 的 validate,进行表单的验证更美观。
$("#regform").validate({ |
更详细信息可以参考源码。
]]>在购买前已经做了一年多的功课啦,本来上次双十一就准备买来着,不过因为某种原因没有买成。今天再看当时的配件不是换代就是降价很多,不禁感叹摩尔定律依旧成立。。
买之前一直参照知乎大神的 @毅种循环 的专栏6.18,参考价值非常大,再次感谢毅神。
6.1 福利最大,幸好我当天锁了两单,还是非常优惠的。
下面给出我的配置单
配件 | 品牌 | 价格 |
---|---|---|
CPU | i7-7700k(散) | 2078¥ |
显卡 | 索泰 GTX1060 6G | 1899¥(-200) |
主板 | 微星 Z270 gaming pro carbon | 1375¥(-224) |
内存条 | 英睿达 DDR4 2400 8G | 主板赠(-339) |
固态硬盘 | 三星 PM961 256G | 629¥ |
机械硬盘 | 希捷酷鱼 7200转 2T | 329¥(-80) |
散热器 | 九州风神 大霜塔 | 179¥(-40) |
电源 | 海韵 G-550 | 419¥(-80) |
机箱 | 先马黑洞 | 289¥(-10) |
显示器 | 戴尔 U2417h | 1474(-175) |
总计:8671¥(-1208)
其中除了 CPU 和 固态是从淘宝买的之外,其余全部走的京东,可以说福利还是足够的。
因为 AMD 最近崛起的 Ryzen 处理器表现也相当不俗,也曾经考虑不过是否上 AMD。不过还是感觉英特尔稳妥一点,在游戏体验上英特尔也是略胜一筹,最终决定还是英特尔吧。
M.2 接口的固态硬盘还是非常有必要的,读写速度也比 SATA 接口快了好几倍。
下面列取市场上主流 nvme 协议的固态硬盘,容量为 256G。大家可以根据自己的钱包进行选择。
品牌 | 价钱 | 颗粒 | 读/写(MB/S) | |
---|---|---|---|---|
英特尔 600p | 660¥ | TLC | 1570/540 | |
建兴 T10 | 680¥ | MLC | 2700/1300 | |
三星960 evo | 900¥ | TLC | 3200/1800 | |
SM 961 | 850¥ | MLC | 3200/1400 | |
PM 961 | 680¥ | TLC | 2800/1100 |
注:MLC 好于 TLC,价格来源于 6月12日 淘宝。
京东买的东西就是快,下完单最晚第二天就到了。然而 CPU 和 固态 走的淘宝,第二周才送到。
装机前看了几个装机视频,感觉还不错,推荐给大家 。
并且要仔细看主板的说明书!!!(x3)
装机的时候要注意避免静电。因为静电很容易损坏 CPU 或 主板,可以用手经常摸一下机箱,有条件的可以戴白色手套,戴防静电手环当然更好啦。
这一步算是最容易的啦。将压杆侧压掀起保护盖,将 CPU 放入。注意 Intel 的 CPU 有两个小凹槽的防呆设计,与卡槽对齐后即可将压杆归位,同时保护盖跳起。
内存条的安装按道理也很简单,但是还是花了不少功夫。
内存条也有个凹槽,两边距离不同,按的时候对比一下凹槽对应即可,不容易安错。
不过在按内存条的时候真的是需要很大力气的。这里建议在主板底下垫一个软垫,可以更好地发力。
我买的 Z270 主板有两个 M.2 插槽,其中有一个带有散热装甲,对固态硬盘的加速还是很有帮助的。
先将中间的平齐螺丝放在外侧,再将固态硬盘装好并用固定螺丝固定即可。
散热的安装相对复杂一些。因为买的 CPU 想超频,低端水冷还不如高端风冷,所以买了大霜塔。
说明书讲的还是很清楚的,这里简单说一下注意事项。
1.将四个固定钉插入中间孔,并套上保护胶套进行固定。(不要加矩形胶垫)
2.将 4 个固定钉从主板底下穿出。
3.套上四个胶垫。
4.固定支架,注意螺丝少量多次拧紧,对向螺丝一起拧。
5.将中间的风扇去掉,记住风扇是怎么摘的,一会还要装回去。
6.涂硅脂。往 CPU 上挤出黄豆粒大小的硅脂,用牙签涂匀。注意只需要薄薄一层,太厚了反倒不适合散热。
7.固定散热器本体。注意风扇方向是朝着内存吹的。如果挡内存可以把风扇往上移一些,或者把外侧的风扇放到相反一侧。拧螺丝时一定不要太用力,否则会压坏 CPU 或 主板。
8.将中间的风扇装好。
9.接风扇电源线。中间的风扇(4pin)接到 CPU_FAN1
接口上,旁边的风扇(3pin)接到任意 SYS_FAN
接口即可。(3pin插到4pin接口就行)
我买的是 海韵 G-550
模组电源。首先把电源后面的模组都插好,再把电源用螺丝拧紧在机箱后面底部,注意风扇向下。
每个机箱都不同,照着机箱说明书装就好啦。SATA 接口需要连接两根线:一根数据线,和主板相连;一根电源线,和电源相连。
将主板的 IO 挡板固定在机箱上。
机箱在买回来的时候就有六个铜柱,在如图位置。因为我的主板是大板,所以需要把外面三个红圈的铜柱也拧上,并拧掉绿圈的铜柱(很紧,可能需要扳子)。
注意没用的铜柱一定要拧下来,否则可能导致主板短路。
固定螺丝如图所示。
这里说明一下,如果你用的也是大霜塔等大型散热器,最好提前插上 CPU 供电线,再固定主板。
因为我买的 CPU 散热器大霜塔太大,我又从背板走的线,CPU 的供电线非常难插,只有很小的空间,像我的大手根本进不去,最后没办法求助母上大人。母上大人的纤纤玉手也是勉强才伸进去。为了插这根线花了大约半个小时的时间。所以建议在固定主板前先把 CPU 的供电线插上,再固定主板,会轻松很多。
这一步是对新手来说最不友好的一步。需要往主板上插各种跳线。有电源线,机箱前置面板线,硬盘、风扇线等。
下图为主板用到的几个接口。
接口 | 名称 | 功能 |
---|---|---|
① | AUD1 | 耳机与麦克风接口 |
② | JFP1 | 机箱开机键、指示灯等接口 |
③ | JUSB1 | 机箱前面板USB |
④ | SATA | 接硬盘、光驱 |
⑤ | JUSB3 | 机箱前面板USB |
⑥ | ATX_PWR1 | 主板供电线 |
⑦ | SYS_FAN4 | CPU散热副风扇供电线 |
⑧ | CPU_FAN1 | CPU散热主风扇供电线 |
⑨ | CPU_PWR1 | CPU供电线 |
其中 ② JFP1
接口需要连接多根跳线,下图为具体连接方式。
因为显卡比较大,容易挡住跳线接口,我们选择最后安装它。
需要卸下机箱的两个挡板,将显卡插入 PCIE 接口后用卸下的螺丝固定。
我买的是索泰至尊需要 6+8 pin 供电。模组电源有两个 8 pin 的显卡供电线,需要都连上,其中有一个 8 pin 线只连接 6 pin 接口。
至此,装机已经完成,我们需要连接电源,连接显示器(用显卡的接口连,不是主板),开机看看是否点亮。
如果没有点亮也不用着急,我第一次也没有点亮,原因是机箱开关机键没有插好(可能是电源灯正负极接反了)。把 ② JFP1
接口的跳线拔下来重新插上再试试吧。
装系统速度神快,坦克世界终于开了最高特效!!那效果,爽!!
p.s. 幸亏 6.1 就全买完了。。显卡由于被挖矿的高价买走了,显卡至少贵500。。
]]>Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。
Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。
Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
JDK: Java开发工具包,包含了JRE、编译器和其它工具(如:JavaDOc、Java调试器)
JRE: Java运行环境,包含Java虚拟机和Java程序所需的核心类库。
如果只是想跑Java程序,那么只需安装JRE,如果要写Java程序并且运行,那就需要JDK了。
如果一个类的变量或者方法前面有static修饰,那么表明这个方法或者变量属于这个类,也就是说可以在不创建对象的情况下直接使用
当父类的方法被private修饰时,表明该方法为父类私有,对其他任何类都是不可见的,因此如果子类定了一个与父类一样的方法,这对于子类来说相当于是一个新的私有方法,且如果要进行向上转型,然后去调用该“覆盖方法”,会产生编译错误。
class Parent { |
static方法时编译时静态绑定的,属于类,而覆盖是运行时动态绑定的(动态绑定的多态),因此不能覆盖。
Java支持的基本数据类型有以下9种: byte, shot, int, long, float, double, char, boolean, void 。
自动拆装箱是Java从jdk1.5引用,目的是将原始类型自动的转换为相对应的对象,也可以逆向进行,即拆箱。这也体现Java中一切皆对象的宗旨。
所谓自动装箱就是将原始类型自动的转换为对应的对象,而拆箱就是将对象类型转换为基本类型。Java中的自动拆装箱通常发生在变量赋值的过程中,如:
Integer object = 3; //自动装箱 |
在 Java 中,应该注意自动拆装箱,因为有时可能因为 Java 自动装箱机制,而导致创建了许多对象,对于内存小的平台会造成压力。
覆盖 也叫 重写 ,发生在子类与父类之间,表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。
重载 是指在一个类中,可以有多个相同名称的方法,但是他们的参数列表的个数或类型不同,当调用该方法时,根据传递的参数类型调用对应参数列表的方法。注意!! 当参数列表相同但返回值不同时,将会出现编译错误,这并不是重载,因为jvm无法根据返回值类型来判断应该调用哪个方法。
在 Java 中是单继承的,也就是说一个类只能继承一个父类。
Java 中实现多继承有两种方式,一是接口,二是内部类。
//实现多个接口 如果两个接口的变量相同 那么在调用该变量的时候 编译出错 |
值传递 就是在方法调用的时候,实参是将自己的一份拷贝赋给形参,在方法内,对该参数值的修改不影响原来实参,常见的例子就是刚开始学习c语言的时候那个交换方法的例子了。
引用传递 是在方法调用的时候,实参将自己的地址传递给形参,此时方法内对该参数值的改变,就是对该实参的实际操作。
在java中只有一种传递方式,那就是值传递.可能比较让人迷惑的就是java中的对象传递时,对形参的改变依然会影响到该对象的内容。
下面这个例子来说明Java中是值传递.
public class Test { |
在上面这个例子中,当前输出结果为:hello world
。这并没有什么问题,可能就是大家平常所理解的引用传递,那么当然会改变StringBuffer的内容。
但是如果把上面的注释去掉,那么就会输出:hello
。此时sb的值并没有变成 ha hello
。假如说是引用传递的话,那么形参的 s 也就是 sb 的地址,此时在方法里 new StringBuffer() ,并将该对象赋给 s ,也就是说 s 现在指向了这个新创建的对象.按照引用传递的说法,此时对 s 的改变就是对 sb 的操作,也就是说 sb 应该也指向新创建的对象,那么输出的结果应该为 ha world
。但实际上输出的仅是 hello
。这说明sb指向的还是原来的对象,而形参 s 指向的才是创建的对象,这也就验证了 Java 中的对象传递也是值传递。
不同点在于:
构造方法是不能被子类重写的,但是构造方法可以重载,也就是说一个类可以有多个构造方法。
Math.round(11.5)==12 Math.round(-11.5)==-11
round 方法返回与参数 最接近的长整数,参数加 1/2 后求其 floor.
String 的长度是不可变的;
StringBuffer的长度是可变的,如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用 StringBuffer,如果最后需要 String,那么使用 StringBuffer 的 toString() 方法;线程安全;
StringBuilder 是从 JDK 5 开始,为StringBuffer该类补充了一个单个线程使用的等价类;通常应该优先使用 StringBuilder 类,因>为它支持所有相同的操作,但由于它不执行同步,所以速度更快。 使用字符串的时候要特别小心,如果对一个字符串要经常改变的话,就一定不要用String,否则会创建许多无用的对象出来. 来看一下比较
String s = "hello"+"world"+"i love you"; |
这个时候s有多个字符串进行拼接,按理来说会有多个对象产生,但是jvm会对此进行一个优化,也就是说只创建了一个对象,此时它的执行速度要比 StringBuffer 拼接快。再看下面这个:
String s2 = "hello"; |
上面这种情况,就会多创建出来三个对象,造成了内存空间的浪费。
java虚拟机主要分为以下五个区:
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。
判断一个对象是否存活有两种方法:
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收。
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。
在java中可以作为 GC Roots 的对象有以下几种:
当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记。
如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法或者已被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行 finalize() 方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚拟机会触发一个 Finalize() 线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果 finalize() 执行缓慢或者发生了死锁,那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。 GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除"即将回收"集合,等待回收。
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一半的内存。
于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用 标记-整理 或者 标记-清除。
java内存模型 (JMM) 是线程间通信的控制机制。 JMM 定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。 Java 内存模型的抽象示意图如下:
从上图来看,线程 A 与线程 B 之间如要通信的话,必须要经历下面2个步骤:
java类加载需要经历以下 7 个过程:
加载时类加载的第一个过程,在这个阶段,将完成以下三件事情:
验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机。在该阶段主要完成以下四钟验证:
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
public static int value=123;//在准备阶段value初始值为0 。在初始化阶段才会变为123 。 |
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次 Minor GC。
Minor Gc 通常发生在新生代的 Eden 区,在这个区的对象生存期短,往往发生 Gc 的频率较高,回收速度比较快;
Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在 Full GC 之前进行一次 Minor GC 这样可以加快老年代的回收速度。
HashMap 内部是通过一个数组实现的,只是这个数组比较特殊,数组里存储的元素是一个 Entry 实体 (jdk 8为Node) ,这个 Entry 实体主要包含 key 、 value 以及一个指向自身的 next 指针。HashMap 是基于 hashing 实现的,当我们进行 put 操作时,根据传递的 key 值得到它的 hashcode ,然后再用这个 hashcode 与数组的长度进行模运算,得到一个 int 值,就是 Entry 要存储在数组的位置(下标);当通过 get 方法获取指定 key 的值时,会根据这个 key 算出它的 hash 值(数组下标),根据这个 hash 值获取数组下标对应的 Entry ,然后判断 Entry 里的 key , hash 值或者通过 equals() 比较是否与要查找的相同,如果相同,返回 value ,否则的话,遍历该链表(有可能就只有一个 Entry ,此时直接返回 null ),直到找到为止,否则返回null。
HashMap 之所以在每个数组元素存储的是一个链表,是为了解决 hash 冲突问题,当两个对象的 hash 值相等时,那么一个位置肯定是放不下两个值的,于是 hashmap 采用链表来解决这种冲突, hash 值相等的两个元素会形成一个链表。
jdk 1.6版: ConcurrenHashMap 可以说是 HashMap 的升级版, ConcurrentHashMap 是线程安全的,但是与 Hashtable 相比,实现线程安全的方式不同。 Hashtable 是通过对 hash 表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。 ConcurrentHashMap 是采用分离锁的方式,它并没有对整个 hash 表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对 hash 表其他地方的访问。
具体实现: ConcurrentHashMap 内部有一个 Segment<K,V> 数组,该 Segment 对象可以充当锁。 Segment 对象内部有一个 HashEntry<K,V> 数组,于是每个 Segment 可以守护若干个桶 (HashEntry) ,每个桶又有可能是一个 HashEntry 连接起来的链表,存储发生碰撞的元素。
每个 ConcurrentHashMap 在默认并发级下会创建包含 16 个 Segment 对象的数组,每个数组有若干个桶,当我们进行 put 方法时,通过 hash 方法对 key 进行计算,得到 hash 值,找到对应的 segment ,然后对该 segment 进行加锁,然后调用 segment 的 put 方法进行存储操作,此时其他线程就不能访问当前的 segment ,但可以访问其他的 segment 对象,不会发生阻塞等待。
jdk 1.8版: 在jdk 8中, ConcurrentHashMap 不再使用 Segment 分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是 “数组+链表->红黑树” 的实现。
List<String> strList = new ArrayList<>(); |
Iterator 的 fail-fast 属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。 Java.util 包中的所有集合类都被设计为 fail->fast 的,而 java.util.concurrent 中的集合类都为 fail-safe 的。当检测到正在遍历的集合的结构被改变时, Fail-fast 迭代器抛出 ConcurrentModificationException ,而 fail-safe 迭代器从不抛出 ConcurrentModificationException。
ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。
通过看源码知道 HashSet 的实现是依赖于 HashMap 的,HashSet 的值都是存储在 HashMap 中的。在 HashSet 的构造法中会初始化一个 HashMap 对象, HashSet 不允许值重复,因此, HashSet 的值是作为 HashMap 的 key 存储在 HashMap 中的,当存储的值已经存在时返回 false。
LinkedHashMap 也是基于 HashMap 实现的,不同的是它定义了一个 Entry header ,这个 header 不是放在 Table 里,它是额外独立出来的。 LinkedHashMap 通过继承 hashMap 中的 Entry ,并添加两个属性 Entry before , after ,和 header 结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。 LinkedHashMap 定义了排序模式 accessOrder ,该属性为 boolean 型变量,对于访问顺序,为 true ;对于插入顺序,则为 false 。一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。
线程可定义为进程内的一个执行单位,或者定义为进程内的一个可调度实体。 在具有多线程机制的操作系统中,处理机调度的基本单位不是进程而是线程。一个进程可以有多个线程,而且至少有一个可执行线程。
打个比喻:进程好比工厂(计算机)里的车间,一个工厂里有多个车间(进程)在运转,每个车间里有多个工人(线程)在协同工作,这些工人就可以理解为线程。
线程和进程的关系:
start() 方法被用来启动新创建的线程,而且 start() 内部调用了 run() 方法,这和直接调用 run() 方法的效果不一样。当你调用 run() 方法的时候,只会是在原来的线程中调用,没有新的线程启动,start() 方法才会启动新线程。
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为。
线程安全的核心是 “正确性” ,也就是说当多个线程访问某个类时,能够得到预期的结果,那么就是线程安全的。
自旋锁: 自旋锁在 JDK1.6 之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,所以这里就做了一个处理,让后面请求锁的那个线程在稍等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。
在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定
偏向锁: 目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。
轻量级锁: 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在 Java SE1.6 里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。
java中以synchronize的形式, 为防止资源冲突提供了内置支持。当任务要执行被 synchronize 关键字保护的代码段时, 它将检查锁是否可用, 然后获取锁--执行代码--释放锁。
所有对象都自动含有单一的锁。当一个线程正在访问一个对象的 synchronized 方法,那么其他线程不能访问该对象的其他 synchronized 方法,但可以访问非 synchronized 方法。因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他 synchronized 方法。
synchronized代码块
synchronized(synObject) { |
当在某个线程中执行这段代码块,该线程会获取对象 synObject 的锁,从而使得其他线程无法同时访问该代码块。 synObject 可以是 this ,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。
针对每一个类,也有一个锁,所以 static synchronize 方法可以在类的范围内防止对static数据的并发访问。如果一个线程执行一个对象的非 static synchronized 方法,另外一个线程需要执行这个对象所属类的 static synchronized方法,此时不会发生互斥现象,因为访问 static synchronized 方法占用的是类锁,而访问非 static synchronized方法占用的是对象锁,所以不存在互斥现象。
对于 synchronized 方法或者 synchronized 代码块,当出现异常时,JVM 会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。
ThreadLocal 是一个创建线程局部变量的类。通常情况下我们创建的变量,可以被多个线程访问并修改,通过 ThreadLocal 创建的变量只能被当前线程访问。
ThreadLocal 内部实现:
ThreadLocal 提供了 set 和 get 方法。
set 方法会先获取当前线程,然后用当前线程作为句柄,获取 ThreadLocaMap 对象,并判断该对象是否为空,如果为空则创建一个,并设置值,不为空则直接设置值。
public void set(T value) { |
ThreadLocal 的值是放入了当前线程的一个 ThreadLocalMap 实例中,所以只能在本线程中访问,其他线程无法访问。
ThreadLocal 并不会导致内存泄露,因为 ThreadLocalMap 中的 key 存储的是 ThreadLocal 实例的弱引用,因此如果应用使用了线程池,即便之前的线程实例处理完之后出于复用的目的依然存活,也不会产生内存泄露。
这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。
一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的 wait() 方法就有意义了。如果 wait() 方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait , notify 和 notifyAll 都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
]]>快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法
该方法的基本思想是:
对挖坑填数进行总结
照着这个总结很容易实现挖坑填数的代码:
//快速排序 |
快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另的方法排序以减小递归深度。有兴趣的筒子可以再深入的研究下。
参考资料http://blog.csdn.net/morewindows/article/details/6684558
]]>正则表达式 | 说明 | 正确示例 | 错误示例 |
---|---|---|---|
. | 匹配任何单个符号,包括所有字符 | (“..”, “a%”) – true | (“..”, “a”) – false |
^xxx | 在开头匹配正则xxx | (“^a.c.”, “abcd”) – true | (“^a”, “ac”) – false |
xxx\\(|在结尾匹配正则xxx|(“..cd\\\)”, “abcd”) – true | (“a\$”, “aca”) – false | ||
[abc] | 能够匹配字母a,b或c | (“1d.”, “ad9”) – true | (“[ab]x”, “cx”) – false |
[^abc] | 当^是[]中的第一个字符时代表取反 | (“[^ab][^12].”, “c3#”) – true | (“[^ab][^12]“, “c2″) – false |
[a-e1-8] | 匹配a到e或者1到8之间的字符 | (“[a-e1-3].”, “d#”) – true | (“[a-e1-3]“, “f2″) – false |
xx\ | yy | 匹配正则xx或者yy | (“x.\ |
正则表达式 | 说明 |
---|
任意数字,等同于[0-9] |任意非数字,等同于[^0-9] |任意空白字符,等同于[0B |任意非空白字符,等同于[^\s] |任意英文字符,等同于[a-zA-Z_0-9] |任意非英文字符,等同于[^\w] 单词边界 |非单词边界
有两种方法可以在正则表达式中像一般字符一样使用元字符。
正则表达式 | 说明 |
---|---|
x? | x没有出现或者只出现一次 |
X* | X出现0次或更多 |
X+ | X出现1次或更多 |
X{n} | X正好出现n次 |
X{n,} | X出席n次或更多 |
X{n,m} | X出现至少n次但不多于m次 |
原文链接: journaldev 翻译: ImportNew.com - ImportNew读者 译文链接: http://www.importnew.com/6810.html [ 转载请保留原文出处、译者和译文链接。]
abc↩︎
MIUI 国际版很好的解决了这点不足。MIUI 国际版是自带 Google 全家桶的,不仅 MIUI 的功能大部分都有保留,而且还能享受完整的 Google 服务,岂不完美?
本文将介绍小米手机(米5)如何刷 MIUI 国际版
,以及一些踩坑经历。
MIUI 国际版优点自然不必多说,不仅可以享受完整的 Google 服务,而且所有被人诟病的 MIUI 系统广告全都消失,相当干净,这对广告过敏者是一个非常棒的选择。
当然,在使用了一周后,发现一些在 国行版 很方便的功能在 国际版 进行了阉割,如自动和运营商校准流量(仍能显示已使用流量),小米钱包(MiPay,公交卡)下方方法已解决,小米应用商店(底部有旧版apk) 等都因为国界问题消失了。如果您很依赖上述功能,那么在刷国际版之前一定要三思啊!!
当然你也可以通过在国行版中安装 Google框架服务
使用 Google套件
,但是因为 Android 升级到了7.0 ,博主试了好多种方法都没有成功,无奈只好安装 MIUI 国际版啦...
MIUI 国际版官方其实是有官方教程的, 链接如下, 一共有三种方法,在这里因为安装包更新,recovery 系统更新等原因,前两种方法楼主都没有尝试成功,于是只好使用第三种 线刷法。
首先需要下载线刷包。因为线刷包和卡刷包不同,不太好找,博主找线刷包的链接找了好久,可算被我找到了~~ 链接如下, 该链接附带线刷法教程,这里简单翻译一下。
1.解除 BootLoader 锁
目前,小米全线手机已经加上了 BL 锁,必须解除 BL 锁才能刷机。想要解除 BL 锁可以参考 这个帖子,写的很详细。
2.下载 MiFlash 刷机工具。解压后点击 MiFlash.exe
安装。中间可能会出现提示条,始终点击继续安装就可以正常安装完毕。
3.下载线刷包,链接同 上方教程链接,下载对应手机版本的线刷包并解压。注意 不要 在官网的 Download
里下载。
4.关机。同时按住 音量-
和 电源键
,进入 Fastboot
模式,界面是一个 正在修安卓机器人的米兔。
5.用数据线连结电脑,将解压后的路径复制到 MiFlash
的路径中,点击刷新,列表中会出现你的设备。注意一定要选中底下的 清除所有数据
!!(都刷机了就别抱幻想保留用户数据了。。博主当时就选择的保留用户数据,结果卡在开机界面一个多小时,后来没办法进入 Recovery模式
清除所有数据,马上就开机了)然后点击刷机就成功了。
因为刷完机后只能使用 Google Play 商店,所以建议之前先准备好 VPN 或 Shadowsocks 等翻墙软件。
博主在使用 Google Play 商店时发现下载应用时出现 正在等待连接WLAN
提示。。然而我已经连上 WLAN 了。。
解决办法是 设置
-> 更多应用
-> Google Play 商店
-> 卸载更新
。 之后再打开 Google Play 商店
就可以下载软件了。不过过几天下载就又提示 正在等待连接WLAN
,还需要重复上述操作。这应该是系统 Bug,应该会解决的。
无需 Root 权限,只需要安装两个 apk 就可以啦。 下载链接
旧版小米应用商店 下载链接
不过由于是旧版,风格与 MIUI8 格格不入,所以推荐 酷安
应用商店,很全,不流氓。
这是我总结的两个知识点,对于期末复习的小伙伴很有帮助。
下文主要写的是在笔试或面试中常问的知识点
OSI分层(7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
TCP/IP分层(4层):网络接口层、网际层、运输层、应用层。
五层协议(5层):物理层、数据链路层、网络层、运输层、应用层。
每一层的协议如下:
每一层的作用如下:
物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)
数据链路层:将比特组装成帧和点到点的传递(帧Frame)
网络层:负责数据包从源到宿的传递和网际互连(包Packet)
传输层:提供端到端的可靠报文传递和错误恢复(段Segment)
会话层:建立、管理和终止会话(会话协议数据单元SPDU)
表示层:对数据进行翻译、加密和压缩(表示协议数据单元PPDU)
应用层:允许访问OSI环境的手段(应用协议数据单元APDU)
类别 | 最大网络数 | IP地址范围 | 最大主机数 |
---|---|---|---|
A | 126(2^7-2) | 0.0.0.0-127.255.255.255 | 16777214 |
B | 16384(2^14) | 128.0.0.0-191.255.255.255 | 65534 |
C | 2097152(2^21) | 192.0.0.0-223.255.255.255 | 254 |
A类地址:以0开头;
B类地址:以10开头;
C类地址:以110开头;
10.0.0.0~10.255.255.255, 172.16.0.0~172.31.255.255, 192.168.0.0~192.168.255.255。(Internet上保留地址用于内部)
IP地址与子网掩码相与得到主机号
首先,每个主机都会在自己的ARP缓冲区中建立一个 ARP 列表,以表示 IP 地址和 MAC 地址之间的对应关系。
当源主机要发送数据时,首先检查 ARP 列表中是否有对应 IP 地址的目的主机的 MAC 地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送 ARP 数据包,该数据包包括的内容有:源主机IP地址,源主机 MAC 地址,目的主机的 IP 地址。
当本网络的所有主机收到该 ARP 数据包时,首先检查数据包中的 IP 地址是否是自己的 IP 地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的 IP 和 MAC 地址写入到 ARP 列表中,如果已经存在,则覆盖,然后将自己的 MAC 地址写入 ARP 响应包中,告诉源主机自己是它想要找的 MAC 地址。
源主机收到ARP响应包后。将目的主机的 IP 和 MAC 地址写入 ARP 列表,并利用此信息发送数据。如果源主机一直没有收到 ARP 响应数据包,表示 ARP 查询失败。
广播发送 ARP 请求,单播发送 ARP 响应。
ICMP协议: 因特网控制报文协议。它是 TCP/IP 协议族的一个子协议,用于在 IP 主机、路由器之间传递控制消息。
TFTP协议: 是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
HTTP协议: 超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
DHCP协议: 动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。
NAT协议: 网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,
DHCP协议: 一个局域网的网络协议,使用 UDP 协议工作,用途:给内部网络或网络服务供应商自动分配 IP 地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。
RARP是逆地址解析协议,作用是完成硬件地址到IP地址的映射,主要用于无盘工作站,因为给无盘工作站配置的IP地址不能保存。
工作流程:在网络中配置一台RARP服务器,里面保存着 IP 地址和 MAC 地址的映射关系,当无盘工作站启动后,就封装一个 RARP 数据包,里面有其 MAC 地址,然后广播到网络上去,当服务器收到请求包后,就查找对应的 MAC 地址的 IP 地址装入响应报文中发回给请求者。
因为需要广播请求报文,因此 RARP 只能用于具有广播能力的网络。
三次握手:
第一次握手:客户端发送 syn 包 (syn=x) 到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次握手:服务器收到 syn 包,必须确认客户的 SYN (ack=x+1) ,同时自己也发送一个 SYN 包 (syn=y) ,即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK (ack=y+1) ,此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
四次握手
与建立连接的 三次握手 类似,断开一个 TCP 连接则需要 四次握手 。
第一次挥手:主动关闭方发送一个 FIN ,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。
第二次挥手:被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号 +1 (与 SYN 相同,一个 FIN 占用一个序号)。
第三次挥手:被动关闭方发送一个 FIN ,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号 +1,至此,完成四次挥手。
客户端浏览器通过 DNS 解析到 www.baidu.com 的IP地址 220.181.27.48 ,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个 HTTP 会话到 220.161.27.48 ,然后通过TCP进行封装数据包,输入到网络层。
在客户端的传输层,把 HTTP 会话请求分成报文段,添加源和目的端口,如服务器使用 80 端口监听客户端的请求,客户端由系统随机选择一个端口如 5000 ,与服务器进行交换,服务器把相应的请求返回给客户端的 5000 端口。然后使用IP层的IP地址查找目的端。
客户端的网络层不用关系应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。
客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定 IP 地址的 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的IP数据包现在就可以传输了,然后发送 IP 数据包到达服务器的地址。
-- | TCP | UDP |
---|---|---|
连接性 | 面向连接 | 无连接 |
可靠性 | 可靠 | 不可靠 |
报文 | 面向字节流 | 面向报文 |
应用场合 | 传输大量的数据 | 少量数据 |
速度 | 慢 | 快 |
流量控制 | 有 | 无 |
拥塞控制 | 有 | 无 |
TCP对应的协议:
(1) FTP:定义了文件传输协议,使用 21 端口。
(2) Telnet:一种用于远程登陆的端口,使用 23 端口,用户可以以自己的身份远程连接到计算机上,可提供基于 DOS 模式下的通信服务。
(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是 25 端口。
(4) POP3:它是和SMTP对应, POP3 用于接收邮件。POP3协议所用的是 110 端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议,使用 80 端口。
UDP对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为IP地址。 DNS 用的是 53 端口。
(2) SNMP:简单网络管理协议,使用 161 端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
(3) TFTP (Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口 69 上使用UDP服务。
当DNS客户机需要在程序中使用名称时,它会查询DNS服务器来解析该名称。客户机发送的每条查询信息包括三条信息,包括:指定的DNS域名,指定的查询类型,DNS域名的指定类别。基于 UDP 服务,端口 53 。 该应用一般不直接为用户使用,而是为其他应用服务,如 HTTP, SMTP 等在其中需要完成主机名到IP地址的转换。
面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。
非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
答:建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。
(1)TCP的三次握手过程:主机 A 向 B 发送连接请求;主机 B 对收到的主机 A 的报文段进行确认;主机 A 再次对主机 B 的确认进行确认。
(2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机 B ,因而产生错误。 失效的连接请求报文段是指:主机 A 发出的连接请求没有收到主机 B 的确认,于是经过一段时间后,主机 A 又重新向主机 B 发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机 A 第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机 B ,主机B以为是主机 A 又发起的新连接,于是主机 B 同意连接,并向主机 A 发回确认,但是此时主机 A 根本不会理会,主机 B 就一直在等待主机 A 发送数据,导致主机 B 的资源浪费。
(3)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。
服务 | 端口号 | 服务 | 端口号 |
---|---|---|---|
FTP | 21 | SSH | 22 |
telnet | 23 | SMTP | 25 |
Domain(域名服务器) | 53 | HTTP | 80 |
POP3 | 110 | NTP(网络时间协议) | 123 |
MySQL数据库服务 | 3306 | Shell或 cmd | 514 |
POP-2 | 109 | SQL Server | 1433 |
1)交换机
在计算机网络系统中,交换机是针对共享工作模式的弱点而推出的。交换机拥有一条高带宽的背部总线和内部交换矩阵。交换机的所有的端口都挂接在这条背 部总线上,当控制电路收到数据包以后,处理端口会查找内存中的地址对照表以确定目的 MAC (网卡的硬件地址)的 NIC (网卡)挂接在哪个端口上,通过内部 交换矩阵迅速将数据包传送到目的端口。目的 MAC 若不存在,交换机才广播到所有的端口,接收端口回应后交换机会“学习”新的地址,并把它添加入内部地址表中。
交换机工作于 OSI 参考模型的第二层,即数据链路层。交换机内部的CPU会在每个端口成功连接时,通过 ARP 协议学习它的 MAC 地址,保存成一张 ARP 表。在今后的通讯中,发往该 MAC 地址的数据包将仅送往其对应的端口,而不是所有的端口。因此,交换机可用于划分数据链路层广播,即冲突域;但它不能划分网络层广播,即广播域。
交换机被广泛应用于二层网络交换,俗称 二层交换机 。
交换机的种类有:二层交换机、三层交换机、四层交换机、七层交换机分别工作在OSI七层模型中的第二层、第三层、第四层盒第七层,并因此而得名。
2)路由器
路由器(Router)是一种计算机网络设备,提供了路由与转送两种重要机制,可以决定数据包从来源端到目的端所经过 的路由路径(host到host之间的传输路径),这个过程称为路由;将路由器输入端的数据包移送至适当的路由器输出端(在路由器内部进行),这称为转 送。路由工作在OSI模型的第三层 即网络层,例如网际协议。
路由器的一个作用是连通不同的网络,另一个作用是选择信息传送的线路。 路由器与交换器的差别,路由器是属于OSI第三层的产品,交换器是OSI第二层的产品(这里特指二层交换机)。
3)网关
网关 (Gateway), 网关 顾名思义就是连接两个网络的设备,区别于路由器(由于历史的原因,许多有关TCP/IP 的文献曾经把网络层使用的路由器(Router)称为网关,在今天很多局域网采用都是路由来接入网络,因此现在通常指的网关就是路由器的IP),经常在家 庭中或者小型企业网络中使用,用于连接局域网和Internet。 网关也经常指把一种协议转成另一种协议的设备,比如语音网关。
在传统TCP/IP术语中,网络设备只分成两种,一种为网关(gateway),另一种为主机(host)。网关能在网络间转递数据包,但主机不能转送数据包。在主机(又称终端系统,end system)中,数据包需经过 TCP/IP 四层协议处理,但是在网关(又称中介系 统,intermediate system)只需要到达网际层(Internet layer),决定路径之后就可以转送。在当时,网关 (gateway)与路由器(router)还没有区别。
在现代网络术语中,网关(gateway)与路由器(router)的定义不同。网关(gateway)能在不同协议间移动数据,而路由器(router)是在不同网络间移动数据,相当于传统所说的IP网关(IP gateway)。
网关 是连接两个网络的设备,对于语音网关来说,他可以连接 PSTN 网络和以太网,这就相当于 VOIP ,把不同电话中的模拟信号通过网关而转换成数字信号,而且加入协议再去传输。在到了接收端的时候再通过 网关 还原成模拟的电话信号,最后才能在电话机上听到。
对于以太网中的 网关 只能转发三层以上数据包,这一点和路由是一样的。而不同的是 网关 中并没有路由表,他只能按照预先设定的不同网段来进行转发。网关最重要的一点就是端口映射,子网内用户在外网看来只是外网的IP地址对应着不同的端口,这样看来就会保护子网内的用户。
随着互连网应用的不断扩大,原先的 IPv4 的弊端也逐渐暴露出来,即网络号占位太多,而主机号位太少,所以其能提供的主机地址也越来越稀缺,目前除了使用 NAT 在企业内部利用保留地址自行分配以外,通常都对一个高类别的IP地址进行再划分,以形成多个子网,提供给不同规模的用户群使用。
这里主要是为了在网络分段情况下有效地利用 IP 地址,通过对主机号的高位部分取作为子网号,从通常的网络位界限中扩展或压缩子网掩码,用来创建某类地址的更多子网。但创建更多的子网时,在每个子网上的可用主机地址数目会比原先减少。
子网掩码是标志两个IP地址是否同属于一个子网的,也是32位二进制地址,其每一个为1代表该位是网络位,为0代表主机位。它和IP地址一样也是使用点式十进制来表示的。如果两个 IP 地址在子网掩码的按位与的计算下所得结果相同,即表明它们共属于同一子网中。
在计算子网掩码时,我们要注意 IP 地址中的保留地址,即“0”地址和广播地址,它们是指主机地址或网络地址全为“0”或“1”时的IP地址,它们代表着本网络地址和广播地址,一般是不能被计算在内的。
对于无须再划分成子网的IP地址来说,其子网掩码非常简单,即按照其定义即可写出:如某B类IP地址为 10.12.3.0 ,无须再分割子网,则该IP地址的子网掩码 255.255.0.0 。如果它是一个C类地址,则其子网掩码为 255.255.255.0 。其它类推,不再详述。下面我们关键要介绍的是一个 IP 地址,还需要将其高位主机位再作为划分出的子网网络号,剩下的是每个子网的主机号,这时该如何进行每个子网的掩码计算。
下面总结一下有关子网掩码和网络划分常见的面试考题:
1)利用子网数来计算
在求子网掩码之前必须先搞清楚要划分的子网数目,以及每个子网内的所需主机数目。
如欲将 B 类 IP 地址 168.195.0.0 划分成27个子网:27 = 11011;
该二进制为五位数,N = 5
将 B 类地址的子网掩码 255.255.0.0 的主机地址前 5 位,置 1,得到 255.255.248.0
2)利用主机数来计算
如欲将 B 类IP地址 168.195.0.0 划分成若干子网,每个子网内有主机 700 台:
700 = 1010111100;
该二进制为十位数,N=10;
将该B类地址的子网掩码255.255.0.0的主机地址全部置1,得到255.255.255.255,然后再从后向前将后 10位置0,即为: 11111111.11111111.11111100.00000000 ,即 255.255.252.0 。这就是该欲划分成主机为 700 台的 B 类 IP 地址 168.195.0.0 的子网掩码。
3)还有一种题型,要你根据每个网络的主机数量进行子网地址的规划和计算子网掩码。这也可按上述原则进行计算。
比如一个子网有10台主机,那么对于这个子网需要的IP地址是:
10 + 1 + 1 + 1 = 13
注意:加的第一个 1 是指这个网络连接时所需的网关地址,接着的两个 1 分别是指 网络地址 和 广播地址 。
因为 13 小于 16 ( 16 等于 2 的 4 次方),所以主机位为 4 位。而 256 - 16 = 240,所以该子网掩码为 255.255.255.240 。
如果一个子网有 14 台主机,不少人常犯的错误是:依然分配具有 16 个地址空间的子网,而忘记了给网关分配地址。这样就错误了,因为 14 + 1 + 1 + 1 = 17,17 大于 16,所以我们只能分配具有 32 个地址(32 等于 2 的 5 次方)空间的子网。这时子网掩码为: 255.255.255.224。
]]>它直接使用 Javascript 使用矢量字库或 SVG 文件来显示数学公式。优点是效果好,比如在 Retina 屏幕上也不会变得模糊。并且可以直接把公式写在 Markdown 文章里。本文介绍在 Sublime 中使用 MathJax 在 Markdown 文件里直接插入数学公式。并且附带一个简单的书写数学公式的 LaTex 教程。
使用 Sublime + Markdown Preview 插件来写博客时。需要开启 Markdown Preview 对 MathJax 的支持,这样在预览界面才能正确地显示数学公式。方法是打开在 Markdown Preview 的用户配置文件 (Package Settings -> Markdown Preview -> Setting - User) 里添加如下内容:
"enable_mathjax": true |
如果博客不支持 MathJax 可以在模板中添加如下脚本
<!-- mathjax config similar to math.stackexchange --> |
关于在 Markdown 书写 LaTex 数学公式有几个规则常用规则需要记住:
行内公式 行内公式使用 $
和 $
作为公式的左右边界,如 \(h(x) = \theta_0 + \theta_1 x\) 公式的 LaTex 内容如下
$h(x) = \theta_0 + \theta_1 x$ |
行间公式 公式需要独立显示一行时,使用 $$
来作为公式的左右边界,如
\[\theta_i = \theta_i - \alpha\frac\partial{\partial\theta_i}J(\theta)\]
的 LaTex 代码为:
$$ |
常用 LaTex 代码 需要记住的几个常用的符号,这样书写起来会快一点
编码 | 说明 | 示例 |
---|---|---|
分子分母之间的横线 | \(1x\) | |
_ | 用下划线来表示下标 | \(x_i\) |
^ | 次方运算符来表示上标 | \(x^i\) |
累加器,上下标用上面介绍的编码来书写 | \(\) | |
希腊字母 alpha | \(y := x\) |
要特别注意公式里空格和 {}
的运用规则。基本原则是,空格可加可不加,但如果会引起歧义,最好加上空格。{}
是用来组成群组的。比如写一个分式时,分母是一个复杂公式时,可以用 {}
包含起来,这样整个复杂公式都会变成分母了。
好啦,这样差不多就可以写出优美的数学公式啦。
本文参考 kamidox.com
]]>共三张表:学生表 student,班级表 class,分数表 score
案例为
1.查询 张三 的 数学 成绩 2.查询 三班 全体成员成绩 3.查询 数学 第4,5,6名的 学生姓名
博主使用的是 MySQL 数据库,所以需要导一些 jar 包
新建一个 java project , 在工程下新建文件夹,命名为 lib ,将MySQL 的驱动文件 和 MyBatis 的驱动文件 复制到该文件夹,并右键 Build Path -> Add to Build Path 。这时发现在工程里出现 Referenced Libraries ,里面有和刚才同名的 jar 文件。
建表的 sql 语句如下(为了方便没有外键)
create table class( |
插入数据的 sql 语句如下
INSERT INTO `class` (`classno`, `classname`) VALUES ('1', '一班'); |
首先配置连结数据库文件,在 src
下新建文件,名称为 db.properties
,内容如下,其中 20170214
为数据库名称,需要改成你自己的数据库名字。
jdbc.driver = com.mysql.jdbc.Driver |
在 src
下新建一个 SqlMapConfig.xml
文件,内容为:
|
在 src
下新建一个包,包名为 pojo
,然后根据数据库列名新建 student
, class
, score
三个类,注意列名与对象名应一致,并进行set, get方法
package pojo; |
package pojo; |
package pojo; |
package mapper; |
|
由 SQL 语句得知这是两个表的查询语句 传入参数有 studentname, object, 传出参数有 a.studentname, b.object, b.score。
以上数据因为都是从两个表中获取,所以需要再写两个 pojo 类:FirstParameter.java 和 FirstResult.java
package pojo; |
package pojo; |
package test; |
纯文本,所以兼容性极强,可以用所有文本编辑器打开。 让你专注于文字而不是排版。 格式转换方便,Markdown 的文本你可以轻松转换为 html、电子书等。 Markdown 的标记语法有极好的可读性。
代码如下:
**粗体** |
显示效果:
可以行首加井号表示不同级别的标题 (H1-H6),代码如下:
# H1 |
因为该代码会加入目录里,所以不做演示了。
代码如下:
[本人博客](http://xungejiang.com "xunge的博客") |
显示效果:
链接后的 title 需要用引号括起来,可以选填,效果是鼠标放到链接上会有提示。
如果安装了 MarkdownExtended 插件的话,可以使用 mdl
+ tab 键
需要注意的是,使用 Markdown 方法,默认是在本网页打开新网页,如果想在新的标签页上打开链接,只能使用 HTML
语言实现,代码如下:
<a href="http://xungejiang.com" target="_blank">本人博客</a> |
显示效果:
代码如下:
![小米](http://7xvx4s.com2.z0.glb.qiniucdn.com/mi.jpg "小米") |
显示效果:
如果安装了 MarkdownExtended 插件的话,可以使用 mdi
+ tab 键
需要注意的是,使用 Markdown 方法,图片将不能调整大小,有以下两种方式可以调整大小
使用 HTML
语言实现,代码如下:
<img src="http://7xvx4s.com2.z0.glb.qiniucdn.com/mi.jpg" width="50%"/> |
显示效果:
可以使用支持参数的图床,例如七牛,可参考七牛图片基本处理。
例如代码为:
![小米](http://7xvx4s.com2.z0.glb.qiniucdn.com/mi.jpg "小米") //旧方法 |
显示效果:
imageView2/2/w/200
的意义为 宽度固定为200px,高度等比缩小。
用反引号将短代码框住,代码如下:
这是 `行内代码`
显示效果:
这是 行内代码
多行代码有两种表示方式。
一种是用前后两个 ``` 把代码包围起来,并在第一行后面标注哪种语言,即可实现代码高亮。注意 ` 不是单引号而是左上角的ESC下面~中的 `
代码如下:
CREATE TABLE stu (
stu_no INT(20),
stu_name VARCHAR(20) NOT NULL,
stu_tel VARCHAR(15),
CONSTRAINT pk_stu_no PRIMARY KEY (stu_no),
CONSTRAINT uk_stu_tel UNIQUE KEY (stu_tel)
);
显示效果:
CREATE TABLE stu ( |
另一种是把代码选中后按一下 tab 键,缺点是无法识别代码语言,无法高亮。
使用 *,+,- 任意一种表示无序列表,代码如下:
- 无序列表项 一 |
显示效果:
代码如下:
1. 有序列表项 一 |
显示效果:
代码如下:
> 引用文字 一 |
显示效果:
引用文字 一
第一行为表头,第二行分隔表头和主体部分,默认 -
左对齐, :-:
居中对齐, -:
右对齐,第三行开始每一行为一个表格行,代码如下:
这是第一列 左对齐|这是第二列 中间对齐|这是第三列 右对齐 |
显示效果:
这是第一列 左对齐 | 这是第二列 中间对齐 | 这是第三列 右对齐 |
---|---|---|
小姜 | 男 | 99 |
小宫 | 女 | 100 |
小刘 | 男 | 98 |
三个以上的星号、减号、底线线来建立一个分隔线,效果相同,代码如下:
--- |
显示效果:
<sub>
和 </sub>
中间的为下角标 <sup>
和 </sup>
中间的为上角标
H<sub>2</sub>O |
显示效果: H2O E=mc2
也可以用下面介绍的 LaTex 公式,更方便。
\\(
和 \\)
表示行内公式:代码:
质能守恒方程可以用一个很简洁的方程式 \\(E=mc^2\\) 来表达。 |
显示效果:
质能守恒方程可以用一个很简洁的方程式 \(E=mc^2\) 来表达。
$$
表示整行公式:代码:
$$ |
显示效果:
\[\sum_{i=1}^n a_i=0\]
\[f(x_1,x_x,\ldots,x_n) = x_1^2 + x_2^2 + \cdots + x_n^2\]
\[\sum_{k=0}^{j-1} {\widehat{\gamma}_{kj} z_k}\]
查看 Sublime 如何配置 LaTex 可参考 我写的这篇文章
以上。
]]>推荐一个 HTML 的编辑器 HBuilder , 当然 sublime 也是极好的。
可以通过多种方法在网页中添加 jQuery。 您可以使用以下方法:
将下载的文件放在网页的同一目录下,并用 <script>
标签引用他,就可以使用jQuery。
<head> |
国内网站推荐百度
<head> |
国外网站推荐谷歌
<head> |
------------|window.onload| \((document).ready()** :-|:-|:- 执行时机|必须等待网页中所有的内容加载完毕后(包括图片)才能执行|网页中所有DOM结构绘制完毕后就执行,可能DOM元素关联的东西并没有加载完 编写个数|不能同时编写多个|能同时编写多个 简化写法|无| **\)(document).ready(function(){}); 可以简写成 $(function(){});
jQuery的基本设计和主要用法,就是"选择某个网页元素,然后对其进行某种操作"。这是它区别于其他函数库的根本特点。
使用jQuery的第一步,往往就是将一个选择表达式,放进构造函数jQuery()(简写为$),然后得到被选中的元素。
选择表达式可以是CSS选择器:
$(document)//选择整个文档对象 |
也可以是jQuery特有的表达式:
$('a:first')//选择网页中第一个a元素 |
如果选中多个元素,jQuery提供过滤器,可以缩小结果集:
$('div').has('p'); //选择包含p元素的div元素 |
有时候,我们需要从结果集出发,移动到附近的相关元素,jQuery也提供了在DOM树上的移动方法:
$('div').next('p'); //选择div元素后面的第一个p元素 |
选中网页元素以后,就可以对它进行某种操作。
jQuery允许将所有操作连接在一起,以链条的形式写出来,比如:
$('div').find('h3').eq(2).html('Hello');
分解开来,就是下面这样:
$('div') //找到div元素 |
这是jQuery最令人称道、最方便的特点。它的原理在于每一步的jQuery操作,返回的都是一个jQuery对象,所以不同操作可以连在一起。
jQuery还提供了.end()方法,使得结果集可以后退一步,回到最近的一个"破坏性"操作之前。
如果之前没有破坏性操作,则返回一个空集。所谓的"破坏性"就是指任何改变所匹配的jQuery元素的操作。
$('div') |
示例
描述:选取所有的 p 元素,查找并选取 span 子元素,然后再回过来选取 p 元素
HTML 代码:
<p><span>Hello</span>,how are you?</p> |
jQuery 代码:
$("p").find("span").end() |
结果:
<p><span>Hello</span> how are you?</p> |
操作网页元素,最常见的需求是取得它们的值,或者对它们进行赋值。
jQuery使用同一个函数,来完成取值(getter)和赋值(setter)。到底是取值还是赋值,由函数的参数决定。
$('h1').html(); //html()没有参数,表示取出h1的值 |
常见的取值和赋值函数如下:
.html() 返回或设置被选元素的内容 (inner HTML) |
需要注意的是,如果结果集包含多个元素,那么赋值的时候,将对其中所有的元素赋值;取值的时候,则是只取出第一个元素的值(.text()例外,它取出所有元素的text内容)。
.insertAfter(),把div元素移动p元素后面:
$('div').insertAfter('p');
.after(),把p元素加到div元素前面: $('p').after('div');
使用这种模式的操作方法,一共有四对
.insertAfter()和.after():在现存元素的外部,从后面插入元素 |
描述:
在所有段落中后插入一个jQuery对象(类似于一个DOM元素数组)。
HTML 代码:
<b>Hello</b><p>I would like to say: </p> |
jQuery 代码:
$("p").after( $("b") ); |
结果:
<p>I would like to say: </p><b>Hello</b> |
描述:
把所有段落插入到一个元素之后。与 $("#foo").after("p")
相同
HTML 代码:
<p>I would like to say: </p><div id="foo">Hello</div> |
jQuery 代码:
$("p").insertAfter("#foo"); |
结果:
<div id="foo">Hello</div><p>I would like to say: </p> |
描述:
在所有段落中前插入一个jQuery对象(类似于一个DOM元素数组)。
HTML 代码:
<p>I would like to say: </p><b>Hello</b> |
jQuery 代码:
$("p").before( $("b") ); |
结果:
<b>Hello</b><p>I would like to say: </p> |
描述:向所有段落中追加一些HTML标记。
HTML 代码:
<p>I would like to say: </p> |
jQuery 代码:
$("p").append("<b>Hello</b>"); |
结果:
<p>I would like to say: <b>Hello</b></p> |
描述:新建段落追加div中并加上一个class
HTML 代码:
<div></div><div></div> |
jQuery 代码:
$("<p/>") |
结果:
<div><p class="test test2"></p></div> |
描述:向所有段落中前置一个jQuery对象(类似于一个DOM元素数组)。
HTML 代码:
<p>I would like to say: </p><b>Hello</b> |
jQuery 代码:
$("p").prepend( $("b") ); |
结果:
<p><b>Hello</b>I would like to say: </p> |
描述:把所有段落追加到ID值为foo的元素中。
HTML 代码:
<p>I would like to say: </p><div id="foo"></div> |
jQuery 代码:
$("p").prependTo("#foo"); |
结果:
<div id="foo"><p>I would like to say: </p></div> |
复制元素使用.clone()
删除元素使用.remove()和.detach()。两者的区别在于,前者不保留被删除元素的事件,后者保留,有利于重新插入文档时使用。
清空元素内容(但是不删除该元素)使用.empty()。
创建新元素的方法非常简单,只要把新元素直接传入jQuery的构造函数就行了:
$('<p>Hello</p>'); |
除了对选中的元素进行操作以外,jQuery还提供一些工具方法(utility),不必选中元素,就可以直接使用。
常用的工具方法有以下几种:
$.trim() 去除字符串两端的空格。 |
jQuery可以对网页元素绑定事件。根据不同的事件,运行相应的函数。
$('p').click(function(){ |
目前,jQuery主要支持以下事件:
.blur() 表单元素失去焦点。 |
以上这些事件在jQuery内部,都是.bind()的便捷方式。使用.bind()可以更灵活地控制事件,比如为多个事件绑定同一个函数:
$('input').bind( |
有时,你只想让事件运行一次,这时可以使用.one()方法。
$("p").one("click", function(){ |
unbind()用来解除事件绑定。
$('p').unbind('click'); |
所有的事件处理函数,都可以接受一个事件对象(event object)作为参数,比如下面例子中的e:
$("p").click(function(e){ |
这个事件对象有一些很有用的属性和方法:
event.pageX 事件发生时,鼠标距离网页左上角的水平距离 |
在事件处理函数中,可以用this关键字,返回事件针对的DOM元素:
$('a').click(function(){ |
有两种方法,可以自动触发一个事件。一种是直接使用事件函数,另一种是使用.trigger()或.triggerHandler()。
$('a').click(); |
jQuery允许对象呈现某些特殊效果。
$('h1').show(); //展现一个h1标题 |
常用的特殊效果如下:
.fadeIn() 淡入 |
除了.show()和.hide(),所有其他特效的默认执行时间都是400ms(毫秒),但是你可以改变这个设置。
$('h1').fadeIn(300); // 300毫秒内淡入 |
在特效结束后,可以指定执行某个函数。
$('p').fadeOut(300, function(){$(this).remove(); }); |
更复杂的特效,可以用.animate()自定义。
$('div').animate({ |
.stop()和.delay()用来停止或延缓特效的执行。
$.fx.off
如果设置为 true,则关闭所有网页特效。
几个常见的筛选选择器:
filter():筛选出与指定表达式匹配的元素集合。这个方法用于缩小匹配的范围。用逗号分隔多个表达式
描述:保留子元素中不含有ol的元素。
HTML 代码:
<p><ol><li>Hello</li></ol></p><p>How are you?</p> |
jQuery 代码:
$("p").filter(function(index) { |
结果:
<p>How are you?</p> |
silce():选取一个匹配的子集
描述:选择第一个p元素
HTML 代码:
<p>Hello</p><p>cruel</p><p>World</p> |
jQuery 代码:
$("p").slice(0, 1).wrapInner("<b></b>"); |
结果:
<p><b>Hello</b></p> |
Jekyll是一个开源的博客生成工具,类似WordPress。但与之不同的是,jekyll只生成静态网页,并不需要数据库支持。通常配合第三方评论系统使用,例如 有言, Disqus(由于众所周知的原因上不去), 多说(已倒闭)。GitHub Pages 原生支持 jekyll,而且可以绑定自己的域名。
关于 GithubPages 绑定自定义域名 可以参考这篇文章。
Jekyll是用ruby语言编写的,所以我们首先要在windows上装好ruby环境。
注意选择对应的操作系统版本为 64位 还是 32位。
记得要勾选 Add Ruby executables to your PATH,其作用是绑定ruby环境变量,另外安装目录不可以包含空格。
与RubyInstller同一链接,页面稍下方有“DEVELOPMENT KIT”, 注意:DevKit版本要与上面的ruby版本是匹配的。
解压DevKit完成后打开CMD窗口,回到Devkit根目录,输入:
ruby dk.rb init |
返回的分别是
[INFO] found RubyInstaller v2.3.3 at C:/Ruby23-x64 |
[INFO] Updating convenience notice gem override for 'C:/Ruby23-x64' |
无翻墙软件,可使用国内淘宝提供的源
gem sources --remove https://rubygems.org/ |
有翻墙软件,可以使用如下源
gem sources --remove https://rubygems.org/ |
gem install jekyll |
gem install jekyll-paginate |
网上找个模板好看的 github pages 的博客, Clone 下来。
Clone 有两种方法
第一种是 https 方法,通过直接输入账号密码的格式提交代码; 第二种是 ssh 的方式,需要提前配置 SSH ,之后可直接 push 代码。
Git 的基本操作参考 git介绍 github的基本配置 这篇文章。
git clone https://github.com/[username]/[username].github.io.git |
启动jekyll服务
cd xxxx.github.io.git |
cd {username.github.io} |
域名除了为了装B,给人留下深刻的印象,博主认为最大的好处是能访问的运营商更多了。
博主使用的是 GithubPages 作为博客的平台,带来的问题是 由于 IP 访问限制,只有使用电信运营商时才能访问,而移动运营商不能访问(联通没试过)。而绑定域名后,因为使用 DNS 解析,移动运营商的网络也可以访问了。
域名购买有多种渠道。这里推荐用国外的 Godaddy 进行域名注册。
Godaddy 有很多的优惠码,并且可以使用支付宝付款,非常方便。博主2017年买的一年域名花了55¥。
购买域名很简单,一步步来就行,如果不能使用支付宝付款,说明使用的优惠码不支持支付宝,可以选择使用国际银行卡支付或者换个优惠码。
在 Godaddy 上购买域名后,域名会自动使用 Godaddy 自己的 DNS 解析器进行解析,不过容易被墙,所以建议使用国内的 DNS 解析器。这里推荐免费的 DNSPOD 进行 DNS 解析。
DNSPOD 支持 QQ 账号登录,非常方便。进入 域名解析 -> 添加域名 ,输入你注册的域名,进入后将记录改为如图所示
主机记录 | 记录类型 | 记录值 | TTL |
---|---|---|---|
@ | A | 192.30.252.153 | 600 |
@ | A | 192.30.252.154 | 600 |
@ | NS | f1g1ns1.dnspod.net. | 86400 |
@ | NS | f1g1ns2.dnspod.net. | 86400 |
其中下面两个 NS 记录类型是不能更改的。
这里建议使用 A 记录进行解析。当然也可以使用 CNAME 解析,但是博主使用 CNAME 进行解析有时出现错误。。所以不如把解析地址指向 GitHub,让 Github 进行域名解析。
在 我的账户 中选择 我的产品
在 域名 处点击 管理
点击域名旁边的箭头,选择 设置域名服务器
将 标准 改为 定制 ,并填写 DNSPOD 的解析服务器
f1g1ns1.dnspod.net |
GithubPages 是支持域名绑定的,只需要在主目录里添加一个名字为 CNAME 文件,注意没有后缀名。文件内容为你所购买的域名,注意没有 www 前缀,例如你申请的 xiaoming.com
,那么 CNAME 的内容为 xiaoming.com
。
这样的话当你输入 xiaoming.github.io
时会自动跳转到 xiaoming.com
。
至此 GitHub 域名绑定完毕,你已经通过域名访问你的网站啦~
]]>加装固态硬盘有两种方案:
这个主板后面的 SSD 接口为 7+17pin ,是苹果自己专用的,只有专门为苹果固态硬盘提供配货的厂家才卖,价格贵的惊人。
所以我的解决方案是买一个 msata 转 7+17pin 接口的转换器,再买一个 msata 的固态硬盘就好啦~
需要注意的是2012款ssd接口与之后年份的接口不同,买的时候需要向卖家确认。
推荐一个 iFixit 加装固态硬盘的视频,非常详细,有这个视频基本就不用往下看了,在此只是总结一下注意事项。
这个是 iFixit 加装固态硬盘的文字教程,也非常详细,需要科学上网才能查看。本篇博客的部分图片为该网站原图,侵删。
需要挑选硬实点的卡片,废弃的会员卡、银行卡都是不错的选择。买双面胶淘宝可能会赠送拆屏拨片,不过实际使用体验不如前者好。
一般从屏幕侧面下方开始切入,之后就是力气活啦,暴力出奇迹吧。放心,轻易不会损坏屏幕,注意的是需要跳过上部中间摄像头部分,避免胶蹭到摄像头部分。
以下部分尤其重要!! 因为屏幕有两跟数据线和主板连结,所以当屏幕已经分离后,需要将屏幕翘起15度左右,从主板将两根数据线拔掉后方可把屏幕拿下。
拆解方法如图
之后在清除底部双面胶后便可将屏幕拿下。
在 iMac 下部有5个螺丝,如图,拧下后便可把底部的支架条拿下来。不拿下来的话主板和音频主板都相当难拿下来。。不要问博主是怎么知道的。。
拧下如图所示四颗螺丝。
这时已可以拆下整个机械硬盘,不过本教程选择先拆解音频主板。
首先将2个连接线拔出。
拧下底部固定的两个螺丝。
这时可以将音频主板的一侧拿出,比较困难,一定不要暴力,因为另一侧还有两根线连结在主板和音箱。
然后便可将两根连接线取下,方法如图,也比较费力。
首先取下电源线。
然后拧下如图所示三颗螺丝即可取下。
机械硬盘就非常好拆啦,拆下 SATA 接口连接线即可。
拧下如图螺丝即可取下。
这里需要拧下四颗螺丝,如图,注意该型螺丝型号为 T8,而之前型号为 T10 ,要小一些。
该出风口与主板是连接在一起的。
拧下如图所示两颗螺丝。
扩音器不必要完全拆卸,只需将扩音器往右挪一挪即可。
拧下如图所示四颗螺丝。
即可拿掉主板。比较费力,因为是和 USB 等接口对应的,需要耐心。
在主板背面就能找到我们期待已久的 SSD 接口啦,安装比较容易。
再按之前的步骤倒序依次安装即可。
开机后进入 Windows 系统,系统会自动识别到新安装的固态硬盘,并提醒你分区。至此固态硬盘安装完毕,剩下的就是在固态硬盘上重装系统了。速度飞起~~
]]>首先需要在淘宝上买一条 12V 的 USB升压线。
关键词:12v; usb升压线。
一定要管客服要接口是 4mm 的!! 一定要管客服要接口是 4mm 的!! 一定要管客服要接口是 4mm 的!!(事重三)
因为卖家一般只卖 3.5mm 的和 5.5mm 接口的,当时我拿格尺量感觉差不多 3.5mm 就下单了,不过客服特别负责,问我能不能把插头插入耳机孔,能插入的才是 3.5mm 的,而米家台灯的插头并不能插入,所以是 4mm 的。
最后卖家给我发了一个 5.5mm 的接口配了一个 4mm 的转接头。
转接头如下图
最终完美适配~
米家LED智能台灯的插座标示的是 12V 0.5A ,而我是用的移动电源最高电压输出只有 5V ,所以需要 12V 的升压线。
台灯适配器如下图
作为 Miboy 强推一下这款台灯吧,此款台灯设计简洁,美观,并且护眼,亮度、冷暖色无级调节(有一个旋钮,按下拧调冷暖色,不按下拧调亮度),可以连接手机选择不同模式(没什么用,喜欢自己调),总之非常适合学习和办公使用,配合本文中的移动电源使用方法简直无黑点。
暖色
冷色
]]>如果你是一名小白,又梦想有朝一日成为一代大 PPT 工程师,或许你会需要这篇文章。
如何在 PPT 中插入高亮的代码?少量代码大可以手调,但是当代码多起来就力不从心了。一个显然的办法是用 HTML 对代码高亮,然后粘贴过去,HTML 高亮的方法有很多,一搜一大堆,但是 HTML 格式的代码粘贴到 Word 是高亮的,但是粘贴到 PPT(即使是从 Word 粘贴过去)都会出现问题,至少我这里会出现问题。
怎么办呢?
查了一下,微软自家的产品内部通用 RTF (Rich Text Format) 格式,接下来就是如何得到 RTF 格式的高亮代码了。
我手头用的 Sublime,有一款插件叫做 Highlight,选中代码,右键 -> Copy as RTF,然后粘贴到 PPT 就好了。然后发现代码高亮有坑(诸如左右括号不一样颜色之类的 bug)。
Notepad++ 也有这个功能,下载最新的 Notepad++ 7.0 版本,安装插件,连个 plugin manager 都没有,手动安装出错,遂退回到 6.9 版本,然后 Copy as RTF 粘贴到 PPT,结果连背景色都带上了,RTF 那个语法,简直了,看着就想吐,别说改了,卒。
最后我选择了 Pygments,安装:
pip install Pygments |
执行:
pygmentize -f rtf -O style=paraiso-dark -l c -o code.rtf code.c |
粘贴到 PPT,OK。
结束了 PPT 中代码高亮的噩梦。
参考: How can I embed programming source code in Powerpoint slide and keep code highlighting? Pygments Docs How to add syntax-highlighted code to PowerPoint slides (Mac OS)
]]>转载作者博客:snovey
wordpress 有强大的 Crayon Syntax Highlighter,因为太过强大,很多功能用不上,用这个插件会拖慢网站速度,于是找到了这个插件:highlight.js,如果你只是想给代码添加简单的高亮而不需要添加行号、复制按钮之类的功能,那么这款插件刚好适合你。下面简单的介绍一下: highlight.js 是一款强大的代码高亮插件。官方给出描述如下:
- 支持 166 种语言,有 77 种样式
- 自动识别语言
- 同时支持多种语言
- 支持 node.js 平台
- 支持各种标记
- 兼容任何 js 框架
该项目已在 Github 开源,项目地址:highlight.js 安装的思路非常简单:
- 导入 CSS 文件
- 导入 JS 文件
- 加载 JS
最简单粗暴的方法如下,在 header.php
中加入如下代码:
<link rel="stylesheet" href="/path/to/styles/default.css"> |
注意修改路径! 当然,这个办法非常不可取,JS 应当放在 <body>
中而非 <head>
中,所以改进后的办法是将
<script src="/path/to/highlight.pack.js"></script> |
移至 footer.php
中 </body>
标签之前。 为了插件化我更推荐你这样做:在 function.php
中添加如下代码
function add_highlight_js(){ |
然后在 footer.php
中添加
<script>hljs.initHighlightingOnLoad();</script> |
设置触发函数。
如果是下载至本地,那么在网站的 /wp-content/plugins/
目录下新建 highlight
文件夹,然后将压缩包解压至该文件夹,将上述的
/path/to/styles/default.css |
修改为
echo get_site_url() . '/wp-content/plugins/highlight/default.css'; |
即可。如果想要提高网站的速度,也可以不从本地加载,转而使用第三方提供的 CDN,下面贴几个。
cdnjs
https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/highlight.min.js |
yandex:
https://yandex.st/highlightjs/8.2/styles/default.min.css |
Bootstrap
https://cdn.bootcss.com/highlight.js/9.6.0/styles/default.min.css |
注意 cdnjs
不提供 CSS,而 yandex
貌似没有 8.2 以后的版本,根据自己的情况选择 CDN 吧。如果你想自定义代码高亮,不妨从 CDN 加载 JS,从本地加载 CSS,这里不啰嗦了。
hljs.initHighlightingOnLoad()
会寻找 <pre><code>
标签,所以使用 highlight.js 时应当这样写代码:
<pre><code class="html">...</code></pre> |
记得在 class 中填写语言的类型。啥?介绍时不是说可以自动识别么?即便如此,标记语言类型是一种良好的编码习惯。
参考链接:
]]>表单的验证一般使用 JavaScript 实现,博主在这里简单介绍一下。
表单经常是由输入框组成。这里介绍几个常用的输入框。
<input type="text" /> 文本框 <br /> |
执行后的效果大概如此
这里以注册用户名为例,要求 **6-30位字母、数字或“_”,字母开头**
用户名:<input type="text" onblur="checkName()" id="username" /> <span id="yes"></span><font color="#FF9900">6-30位字母、数字或“_”,字母开头</font> |
标签下,最后执行结果如图所示:
这里需要用到一个 event 对象 onblur
,定义为 onblur 事件会在对象失去焦点时发生,及当我光标离开输入框时触发事件发生,这非常适用于检查输入框内容的格式是否符合要求。当然还有很多 events
,若想了解更多可到 w3school 查询。
<span>
标签被用来组合文档中的行内元素。这里主要用来显示错误信息。
function checkName() |
由代码可以很容易理解 JavaScript 语法需要先用 document.getElementById("")
获取输入框,然后用 .value
得到输入框的内容。
错误分三种情况:
1.如果输入为空,则错误信息为“请输入用户名!” 2.如果输入少于6个字符,则错误信息为“用户名长度不能少于6个字符!” 3.如果输入不满足由 由字母,数字,下划线组成,须以字母开头
这个条件,则错误信息为 用户名只能由字母,数字,下划线组成,须以字母开头
三种错误方式分别如图所示
正确方式如图所示
其中最后的条件限制使用了正则表达式。
placeholder 是 html5 <input>
里的属性,提供可描述输入字段预期值的提示信息(hint)。
为了
具体代码如下,以下代码基于 12306 网上购票用户注册
|
博主是在学习 JavaWeb 的时候接触到 tomcat 的,这里介绍一下在 mac 系统安装 tomcat 的过程
首先到 tomcat 官网 下载 tomcat ,版本既然是新版兼容旧版当然是越新越好啦。
选择 zip
或者 tar.gz
下载即可。下载后解压,更名为 tomcat
,并复制到 /Library
(就是finder中的资源库)。当时博主将文件夹重命名为 tomcat 后居然带后缀名 tomcat.M9
。。文件夹居然带后缀名!!后缀名居然删不掉!!也不知道那个 .M9
是咋出来的。。在表面看是看不到的啊!!
tomcat中的几个运行服务程序都是以*.sh结尾的,在运行之前需要授权。打开终端输入如下命令:
sudo chmod 755 /Library/tomcat/bin/*.sh |
其中 tomcat 为你的文件夹名。(博主当时很无奈的将tomcat换成了tomcat.M9)
回车出现要输入密码:请输入本机账户密码
先使用 cd 命令进入tomcat的bin目录,命令如下:
cd /Library/tomcat/bin/
启动服务命令:
sudo sh startup.sh |
启动成功,会出现如下结果:
Using CATALINA_BASE: /ProgramFile/tomcat |
如果出现如上结果,说明tomcat启动成功。
这个时候输入 http://localhost:8080/ 应该就可以访问了。
]]>命令方式最简单,键入如下两行命令你就可以实现对文件的现实和隐藏功能了。
显示:defaults write com.apple.finder AppleShowAllFiles -bool true |
然后 重启 Finder !!!!很重要!!!!楼主就是试了好多次最后发现栽在这上面了
方法如图
在 Finder 中进入任意文件夹,按快捷键Command + F
调出搜索窗口,点击"种类"选项卡,在下面找到"其他",如图所示
在弹出的窗口里 找到"文件可见性" 选项(可通过搜索快速查找),勾选后面的方框,点击"好"保存设置。
当然!!仍然需要重启 Finder !!方法同上。
]]>超文本标记语言的结构包括 头 部分(英语:Head)、和 主体 部分(英语:Body),其中 头 部提供关于网页的信息,主体 部分提供网页的具体内容。
HTML 标签很多,都记住有一定的困难。所以这里推荐 Dreamweaver ,很强大,标签有很多的提示。当然也有推荐初学者使用记事本编辑html文件的,不过仁者见仁智者见智吧,大家自己选择。
这里推荐两个网站,是专门查询标签的功能,并且可以在线查看效果的
1.http://www.w3school.com.cn/tags/
2.http://www.runoob.com/html/html-tutorial.html
基本 |
##表单
<form action="url" method=get|post></form> 定义表单 |
2016-07-10学校组织来大连东软实训,我选择的是 JavaWeb 方向,经过20天的学习收获还是蛮大的。第一个知识点是 jdbc 。为了留点纪念,所以想把老师讲的知识全部复述一下,可以说是详细版教程。
在东软,老师把 MySQL 和 Oracle 的使用方法都介绍了,但是由于 Oracle 过大,占内存,卸载不干净,所以在这里只介绍 MySQL 数据库的操作。
首先到官网下载 MySQL 的数据库,和MySQL Workbench 的数据库操作界面。Windows 版的在安装时记得在填写密码时一定要记住,并且选择 utf-8
编码。mac 版的安装很简单,但是在创建数据库的时候记得填写密码,否则会生成临时密码,如果当时没有记住临时密码就惨了。
然后在 MySQL 上新建一个数据库,并在空白区域右键 create schema
。
当然 JDBC 是需要掌握 SQL 语句的一般语法。
使用 JDBC 的话一般只进行增加(insert)
,删除(delete)
,修改(update)
,查询(select)
等操作而很少进行 创建(create)
操作,所以现在需要手动创建一张表。
CREATE TABLE stu ( |
这里以学生表为例。
在东软老师推荐用 MyEclipse ,因为将来的 JavaWeb 需要各种插件。但是 MyEclipse 需要花钱买,否则需要破解。破解网上教程太多,就不举例了。
而 eclipse 是开源免费的。如果只用 JDBC 的话 eclipse 完全足够。这里给出官网下载地址。
注意选择 Java EE
版本,并可以选择中国镜像,下载速度更快。
用 eclipse 新建一个 java project 起名为 jdbc
在工程下新建文件夹,命名为 lib ,将MySQL 的驱动文件复制到该文件夹,并右键 Build Path -> Add to Build Path 。这时发现在工程里出现 Referenced Libraries ,里面有和刚才同名的 jar 文件。
在 src 中新建一个包。由于包的名字一般由公司域名倒置构成,所以我起名为 com.xunge.jdbc
在包中新建一个类,类名为 JDBCDemo
, 并录入以下代码。
package com.xunge.jdbc; |
代码中的 try catch
和 导的包是用 eclipse 自动提示添加的。
上面的代码用 executeUpdate 方法,可以进行 增加(insert)
, 删除(delete)
, 修改(update)
操作,返回的是更新数据的行数。而 查询(select)
语句需要用 executeQuery 方法。
然而这个代码如果多次执行的话重复的代码过多,需要进行代码复用。现在可以看到的是 连接数据库 和 删除数据库 代码是不变的,可以进行代码复用。
方法是:
1.在 src 新建一个包,包名为 com.xunge.jdbc.common
。
2.在该包下新建一个文件,文件名为 dbmysql.properties
。文件内容如下
# MySQL |
这个是 MySQL 数据库的配置文件。如果用的是 Oracle 数据库,则配置文件如下
#Oracle |
properties 文件为配置文件。
2.在该包下新建一个类,类名为 JDBCUtil
,作为 JDBC 的工具类,内容如下。
package com.xunge.jdbc.common; |
之后就可以引用工具类里的函数将 JDBC 实现出来。
由上文知 增加(insert)
, 删除(delete)
, 修改(update)
操作用 executeUpdate 方法,返回的是更新数据的行数。
而 查询(select)
操作需要用 executeQuery 方法,需要用 executeQuery 方法,用 ResultSet 接收数据并输出。
package com.xunge.jdbc; |
这里介绍一下 预编译语句。预编译语句 的优点是 如果遇到两条 SQL 语句除了几个参数不同,其他不变的话(尤其是插入语句!!),用 预编译语句 是再合适不过的啦。预编译不仅运行更快,而且写起来也方便很多。
package com.xunge.jdbc; |
以上就是 JDBC 配置的全部过程及简单的应用,相信在将来的 JavaWeb 开发中一定会用到其中的知识。
说说东软学校吧
东软老师真的很不错,很负责,也很耐心,遇到的一些问题向老师提问都会很快得到详细的解释,这样的教学方式非常值得大学老师的学习。
如果你们学校也组织来东软实训的话,我作为过来人也给出几条建议吧
苹果电脑当然还是苹果系统最适合,优势最明显的是省电,当然也有美观、系统流畅等其他优点。但是也有很多劣势,比如 mac 系统学习成本比较大,很多软件不兼容。所以在硬盘空间的允许下装双系统应该是再合适不过的啦。
博主就是因为老爸不习惯 macOS ,而博主又特别喜欢 macOS 的界面,所以权衡利弊借助 bootcamp 装了双系统。
其实想在苹果电脑上运行 mac 还可以用虚拟机,这里推荐 parlldesktop
虚拟机,不过需要花钱,也有破解版。
win10 镜像, USB 2.0 。
首先需要 win10 镜像,推荐 MSN I tell you,镜像都是原版无添加。
然后需要一个 USB 2.0 的U盘。。注意,最好!!一定!!是 2.0 的U盘 因为博主试过好多 3.0 的U盘都不成功,后来看有人建议用 2.0 的U盘试试,果然成功了!!博主用的是 Kingston 的 2.0 U盘。
然后需要用 bootcamp 制作 win10 的启动U盘。
实用工具 -> bootcamp助理 -> 继续 -> 打好三个对勾,然后会自动将镜像拷贝到U盘,并下载一些必要的驱动。之后会自动重启,安装方法和 Windows 就没有什么区别了。
安装好 Windows 会发现没有无线网驱动,分辨率也不是很适配。这时候就需要打开C盘,会有一个 bootcamp 的文件夹,里头有 setup 的驱动程序,安装完成后就发现无线网卡出现了,分辨率也适配到了最佳水准。
2018.10 update
新版 BootCamp(启动转换助理) 不再需要使用 U 盘,系统会自动将硬盘的一部分分区作为系统盘,并在之后的使用中删去这个分区,使用更加方便
]]>Linux
和 MAC
环境下是自带 GIT
的,如果使用 Windows
的话有如下几个解决方案。
Download full
,不仅自带 GIT
,而且是替代 Windows
自带很丑的 cmd
的很好选择。建议大家使用命令行操作,方便快捷容易理解。
$ git config --global user.name "{username}" //用户名替换{username} |
ssh-keygen -t rsa -C"{email}" //邮箱替换{email} |
一路回车到命令完成,win 系统默认在文件夹 C:\Users\{你的用户名}\.ssh
,该文件夹有 id_rsa
(私钥) 和 id_rsa.pub
(公钥) 两个文件。
将id_rsa.pub内容复制到自己的 Github 主页的 Settings -> SSH keys,添加完毕即可。
可以输入以下命令,来测试是否能够正确链接到 github
ssh -T git@github.com |
若返回命令如下
Hi ***! You've successfully authenticated, but GitHub does not provide shell access. |
则说明连接成功。
::GIT 仓库初始化 |
如果你每次push的时候都需要输入github的用户名和密码,就会感到非常的麻烦。原因是我们push的地址使用的是https,把它改成ssh就好啦,因为我们之前已经在github上添加ssh秘钥了。这里介绍一下这个方法。
首先在git bash 输入 $ git remote -v
查看当前推送方法
若如下
origin https://github.com/someaccount/someproject.git (fetch) |
则修改
git remote set-url origin git@github.com:someaccount/someproject.git |
其中将https改为ssh的方式,这样就可以不用输入密码进行push了。
]]>Sublime 作为文本编辑器的同时还可以进行一些轻量级的编程运算,由于其漂亮的界面,深得 ACMer 的喜爱。
虽然 Sublime 自带一个C语言的编译功能,但却无法使用,需要一定的配置才可以运行。
要想编译C语言首先要有编译器,Windows平台主要是 gcc
和 g++
,是通过安装 MinGW 实现的。
MinGW 的官网是 http://www.mingw.org/ ,但是从官网安装很麻烦,在线安装经常龟速容易失败。
博主推荐的方法是借助 codeblocks,选择带有MinGW的版本安装(100M以上)。
博主也提供一个 codeblocks-16.01mingw-setup 的百度云下载,感觉codeblocks官网还是下载慢。
安装后把MinGW文件夹复制出来放到C盘根目录就可以了。
右键计算机->属性->高级系统设置->环境变量
把 C:\MinGW\bin
添加到path变量中,注意前后英文分号。
Tools -> Build System -> New Build System
将下面代码粘贴,并保存为 gcc.sublime-build
。
Windows 系统代码如下
{ |
MAC 系统代码如下
{ |
这样就可以用cmd运行,并且scanf也能读取。
sublime我认为最方便的地方就是可以设置一些模板,比如博主参加ACM竞赛开头会有很多在每道题都出现的语句,头文件等,如果有模板就会非常方便。
<snippet> |
中间代码的位置当然可以修改。然后保存为文件名.sublime-snippet
。例如博主起名为 acm.sublime-snippet
,之后只要用sublime打开 .cpp
文件,输入 acm
-> 回车就可以显示中间代码。是不是很方便啊~
演示效果如下:
]]>博主家里有一台iMac一体机,买回来的时候就是win7(肯定是老爸要求换的),所以也一直当成windows电脑用着。然而博主家里还有台 MacBook Pro,被博主强行换成mac系统,简直不能再漂亮,再好用,所以决定将 iMac 也换成 mac 系统,再装win10,给老爸用。
然而开机长按 option(alt)键发现原来 mac 系统一直没删,这下就好办了。不过 mac 系统有点旧,还是 lion 系统,于是想给 mac 系统做一下彻底的升级。
这里网上也有很多帖子写的很好,这里也简单说一下。
首先准备一个 8G 左右的 U 盘,当然最好 3.0。然后进入 mac 系统(如果没有 mac 系统的话看看有没有同学有用苹果本的借一下。。实在没有安一个mac虚拟机也可以)。进入实用工具 -> 磁盘工具,将U盘抹掉,格式为 OS X 扩展(日志式)
,名字叫做 Capitan
(最好一致,后面代码用得上)。
当然还需要 El Capitan 安装程序,可以从 App Store上 下载(推荐,保证最新版)。也可以百度云下载下载。
确保在应用程序里有 安装OS X El Capitan。
之后在 实用工具
-> 终端
输入下面代码
sudo /Applications/Install\ OS\ X\ El\ Capitan.app/Contents/Resources/createinstallmedia --volume /Volumes/Capitan --applicationpath /Applications/Install\ OS\ X\ El\ Capitan.app --nointeraction |
其中Capitan是U盘名字,如若之前没听话没改名字,这里需要把Capitan改成你的U盘的名字。
做好后是这样滴
如果出现错误则重新执行一次。
这样mac启动U盘就做好啦~
在这之前最好适用 Time Machine(时间机器)备份一下。如果电脑之前安过windows的话可能要费劲一些了,如果你的windows还分了好多区的话那就更麻烦了。。非常建议将windows分成一个区 bootcamp
(C盘),否则无法更新mac系统。或者像博主一样彻底一点将windows删除,更新完系统再重装windows。
插上U盘,重启电脑,并长按 Option键
(alt键),进入选盘界面(如果不成功重启再试一次)
进入后界面如下
可以先进入 安装 OS X
,选择 mac 系统盘,并下一步即可。
下面是可能会出现的问题,如果成功安装则跳过下文。
这个磁盘没有使用GUID分区表方案
如下图的话则说明windows分区破坏了GUID分区,需要将windows分区删掉才能升级mac系统。
无法在此硬盘安装更新
等类似字样的话,则说明只能将原来mac系统盘抹掉。方法是退出安装程序,在 OS X 实用工具
中选择磁盘工具,将 mac 系统盘抹掉,这样就能成功安装了(一个空电脑当然能成功安装系统)。
不能验证这个“安装 OS X El Capitan”应用程序副本。它在下载过程中可能已遭破坏或篡改。
不要真的相信程序坏掉了,解决方法是:先退出安装程序,在上面菜单栏选择 终端
,输入下面代码
date 122014102015.30 |
这应该是系统bug,需要改一下时间,改完之后就应该没问题了。
2018.10 Update
mac 系统新推出的 macOS Mojave
版本依然可以使用上述方法安装系统。
在终端输入代码制作安装 U 盘时可能会报 Warning,但并不影响。
]]>Sublime 可是一款程序猿必知,必用,必会的一款文本编辑器,当然配有适当的插件也可以编译 C, C++, Java, Python 等所有简单程序,并且可以写 Markdown 文档,博主的博客就是用 sublime 写的哦~
下载完成后字体可能太小, Ctrl
+ =
可以将字体调大。同理 Ctrl
+ -
可以调小字体。
Sublime 并不是免费的软件,需要70刀左右。。然而博主还是个学生,比较穷,所以只好选择破解版。。不过等将来有钱了,我一定会来买正版的,现在就当为他的产品使用量做贡献吧。
怎么破解就不说了吧(网上搜该版本的注册码)
既然要用 sublime 那一定是看中他的插件功能啦,然而插件的安装需要先安装 Package Control,方法如下:
Ctrl
+ ~
或者 View
-> Show Console
调出 Console
,并将下面代码粘贴执行。
import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'https://sublime.wbond.net/' + pf.replace(' ','%20')).read()) |
如果安装成功,就可以在 Preferences 菜单下看到 Package Settings
和 Package Control
两个菜单。
若不能通过以上方式成功安装,可尝试以下方式手动安装:
点击 Preferences
-> Browse Packages...
菜单,进入打开的目录的上层目录(即 Sublime Text 3
目录),再打开 Installed Packages
目录
点击下载Package Control.sublime-package并复制到 Installed Packages
目录
,之后就可以尽情的去安装插件啦~
经过上面安装了 Package Control 后,我们就可以通过快捷键 Ctrl
+ Shift
+ P
打开 Package Control 来安装插件了。在打开的输入框中输入 install ,会根据你的输入自动提示,选择 Package Control:Install Package,如下图。
等一会,便会又弹出一个输入框,输入你要安装插件的名字即可。
这里推荐几个必安插件:
文件转码成 UTF-8
,避免中文乱码。
同样是解决中文乱码问题,将 GBK
编码转换成 UTF-8
编码。
插件可以说是使用Sublime Text进行前端开发必不可少的插件
例如输入以下代码
ul#jiang>li.item$*4>a{Item $} |
便可自动生成
<ul id="jiang"> |
详情参考 前端开发必备!Emmet使用手册,Emmet 官方文档。
这是一款 JS 格式化的插件,Ctrl
+ Alt
+ F
对 JS 进行格式化。
是一款很实用的右键菜单增强插件,可以对左侧文件栏进行更多的操作,更多配置请点击标题。
在安装该插件前,在Sublime Text左侧FOLDERS栏中点击右键,只有三个功能。
通过Package Control安装SideBarEnhancements插件后
可见功能增加了不少。
这款插件能高亮显示多余的空格和Tab,并一键去除,是处女座的福音。
一键删除多余空格(需配置) Ctrl
+ Alt
+ T
点击 Preferences
-> Key Bindings – User
加上代码
{ "keys": ["ctrl+alt+t"], "command": "delete_trailing_spaces" } |
"=" 对齐
默认快捷键 Ctrl
+ Alt
+ A
和QQ截屏冲突,可设置其他快捷键如:Ctrl
+ Alt
+ Shift
+ A
:
点击 Preferences
-> Key Bindings – User
加上代码
{ "keys": ["ctrl+alt+shift+a"], "command": "alignment" } |
先选择要对齐的文本,如下图:
为代码补全插件,支持 JavaScript, Mason, XBL, XUL, RHTML, SCSS, Python, HTML, Ruby, Python3, XML, Sass, XSLT, Django, HTML5, Perl, CSS, Twig, Less, Smarty, Node.js, Tcl, TemplateToolkit, PHP 等多种语言。
该插件安装时间可能较长,需要耐心等待。
生成优美的注释,更多配置请点击标题。
调色板,默认 Ctrl
+ Alt
+ C
,但与 ConvertToUTF8
快捷键冲突。
解决方法:更改 ConvertToUTF8
快捷键为 Ctrl
+ Alt
+ Shift
+ C
。
点击 Preferences
-> Browse Packages...
-> ConvertToUTF8
-> Default (Windows).sublime-keymap
(根据你的操作系统,打开相应文件) -> 就不用说了吧~
强大的比较代码不同工具。
右键标签页,出现 FileDiffs Menu
或者 Diff with Tab…
选择对应文件比较即可。
这是HTML/XML标签缩进、补全、排版和校验工具。使用方法如下图:
暂时没有快捷键。
Markdown Preview 可以实时将 markdown 文件在浏览器上显示。
快捷键设置如下
点击 Preferences --> 选择 Key Bindings User,输入:
{ "keys": ["alt+m"], "command": "markdown_preview", "args": {"target": "browser", "parser":"markdown"} } |
Alt + M 为在浏览器显示
Ctrl + B 为转换成 html 文件
记得和之前的快捷键用逗号隔开
我最爱用的 Markdown 主题
输入 "mdi + tab" 会自动插入下面的图片标记
![Alt text](/path/to/img.jpg "Optional title") |
输入 "mdl + tab" 会自动生成下面的链接标记
[](link) |
Sublime自带的英文字体是 Consola
,非常好看,但是中文默认是宋体,不太协调,所以这里可以改成 YaHeiConsola
字体,英文是 Consola
,中文是 微软雅黑
。
下载字体 YaHeiConsola,右键安装。
在Menu 中点击 Preference -> Setting-User, 添加
{ |
注意:参数之间用逗号隔开
Sublime 还有很多功能,例如可以编译很多编程语言。这是博主写的 Sublime Text 3 设置C/C++编译环境 可以参考。
]]>