Hexo的使用

Hexo是一个静态博客框架
可以方便地创建自己的博客
但是hexo的markdown解释引擎并不是很好用……有时候会出现一些奇怪的效果……

基本用法

i其实主要参考Hexo官方文档即可
这里作简单归纳
安装就不说了,看文档

  • 初始化:
    进入要作为blog的文件夹
    hexo init
    npm install
  • 文章(.md):
    放入文件夹sources/_posts
    文章开头可以写入信息,最基本的有title, datetags
    内容要和冒号用空格隔开如:title: aaaa
    每个信息占用一行,tags可以有多个标签,如tags: - aaa - bbbb注意横线和标签内容也要用空格隔开,另外每个标签可以独占一行
    写完信息后,在信息的后面应该加入三个横线---表示信息结束
    总体大致如下:

    title: TITLE
    date: 2015/03/04
    tags: 
    - aaaaa
    - bbbbb
    
  • 生成静态页面
    hexo g

  • 本地查看
    hexo s
    输入网址0.0.0.0:4000查看
  • 部署到github

    • 在github上新建仓库
      仓库名必须是USERNAME.github.ioUSERNAME为github的用户名
    • 安装git-deploy插件:npm install hexo-deployer-git --save
    • 配置_config.yml文件:

      deployer:    
      - type: git
      - repo: http://github.com/USERNAME/USERNAME.github.io`    
      #USERNAME为github用户名     
      
    • 执行部署命令
      hexo d
      随后根据提示输入github帐号密码即可

      • 部署git需要配置好git用户名和邮箱
        每次commit都会记录这两个信息
        git config --global user.name "用户名"
        git config --global user.email "邮箱"
  • 更换主题
    以next主题为例
    clone https://github.com/iissnan/hexo-theme-nextthemes/next目录下
    修改_config.yml文件,将themes修改为next即可
  • 相关配置信息
    在文件_config.yml中,包括博客名,作者名,主题,部署方式等
  • 其他信息
    参照hexo官方文档和相关主题的官方文档

HTML、CSS、JS等资源的添加

  • source目录下新建目录,将资源放入其中即可调用
    hexo将自动在网站根目录下生成相应的目录和文件
  • HTML文件需要在文件内容头部加入layout: FALSE表示不对该文件使用布局
    但是此时HTML不会作为文章直接显示出来,需要自己在_post目录下写md文章来跳转

    比如我要把HTML格式的python的笔记挂上去
    我在source目录下新建了目录Note_for_Python,把笔记内容放到该目录下
    _post写一个md文章作为笔记内容的目录,比如要跳转到目录下的1.html文件——
    [1](/Note_for_Python/1.html)

fedora安装与配置

fedora的安装

烧制启动盘

百度了一下
fedora的安装不同于ubuntu
据说直接烧进U盘是不能正常安装的
因为fedora采用了分轨镜像

  1. 官网提供了烧进U盘的专用工具,但是在我的电脑上并不能成功安装,连界面都进不去
  2. 在linux下用dd命令烧进U盘(注意of=/dev/sdb而不是of=/dev/sdb1
    windows下也可以找到dd的程序,据说跟linux差别不大
  3. 但是,在一些帖子上看到有的人用Utraiso烧进U盘也能用?但在我的电脑上并不可行

最终采用了第2种方法进入了安装的界面

UEFI?

分区的时候安装程序死活不让我过,提示——
For a UEFI installation you must include EFI system partition on a GPT-formatted disk, mounted a /boot/efi
我试着去分出一个挂载在/boot/efi上的分区,但是并不能解决问题
最后查阅资料后,改成传统的legacy模式进入安装程序就没有问题啦

fedora的基本配置

触控板

默认的触控板上滑动只能移动鼠标,单击必须按下按钮,极不舒服
设置,在SettingsMouse&TouchpadTap to click钩上即可

Terminal的快捷键

默认终端是没有快捷键的,不舒服啊!得把快捷键加上
SettingsKeyboardShortcuts选项卡的Custom Shortcuts中加入自定义快捷键,Name随便填,Command设置为/usr/bin/gnome-terminal,并且设置所要的快捷键,一般是Ctrl+Alt+T

vim的markdown语法高亮插件

据说,fedora下的sublime是写不了中文的耶?!?!郁闷了……
那就用vim吧~~但是要手动下载markdon语法高亮插件
嗯……这个插件还是蛮不错的!
在Home目录下新建名为.vim的文件夹,将插件clone到本地即可
https://github.com/plasticboy/vim-markdown

安装fedora的时候不小心把以前的md文件弄丢了,索性曾经转换成html发布了出去,只要把html再转回markdon即可
Html转换为MarkDown样式代码
这个工具还是蛮不错的,转换的结果还行~~

shadowsocks安装与配置

参考资料:
Shadowsocks|Github
科学上网利器 Shadowsocks 使用方法

安装

yum install python-pip
pip install shadowsocks

配置

新建配置文件和相应目录

/etc/shadowsocks/config.json
文件内容:

{
  "server":"服务器地址",
  "server_port":服务器端口,
  "local_port":1080,
  "password":"密码",
  "timeout":60,
  "method":"aes-256-cfb"
}

启动

前台启动:sslocal -c /etc/shadowsocks/config.json
后台启动:在/etc/shadowsocks/目录下执行nohup sslocal > log &

添加服务到systemd

安装完shadowsocks后会发现,systemd中并没有sslocal、ssserver、shadowsocks之类的服务,那么需要我们手动添加进去
= =不过我不会玩systemd哦,只好疯狂了翻资料,然后自己写了个low low的service凑活着用吧,等有空恶补一下systemd的知识再来进一步完善
首先,systemd的服务配置文件是在/etc/systemd/system/目录下
文件名即为服务名.service,比如我新建的一个sslocal.service
文件内容我是这么写哒——

[Service]
Type=simple
ExecStart=/usr/bin/sslocal -c /etc/shadowsocks/config.json

[Intsall]
WantedBy=multi-user.target  

Service字段呢指明了类型为简单,然后执行一条命令
只要有这条字段我们就可以systemctl start sslocal
但是一执行enable他就会报错缺少Install字段
额……还不是很理解这个字段干嘛的,看到各种资料给的例子上都写上这么一句,就直接拔了下来,也能成功地执行systemctl enable sslocal来使这个服务开机自启啦
这个配置写的lowlow的,而且不知道会不会有什么错误的地方……
等我好好学一下systemd的知识再来完善吧……=。=

firefox->foxyproxy插件

添加代理服务器

服务器ip设置为127.0.0.1,端口1080
选择SOCKS proxy->SOCKS v5

添加gwflist规则

让插件判断哪些网址需要代理,哪些不需要
Pattern Subscriptions选项卡,添加规则订阅
URL: https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt
添加刚才加入的代理Add Proxy
Refesh Rate: 600
Format: AutoProxy
ObFuscation: Base64
这个是谷歌的一个规则,所以添加时必须打开代理服务器,否则将获取不到
Select Mode修改为Use proxies based on their pre-defined pattens and priorities即可

apache+php+mysql安装与配置

参考资料:在fedora21 上的php+mysql+apache环境搭建

apache

安装:yum install httpd
控制:
systemctl [start | stop | restart | reload] httpd.service
service httpd [start | stop |restart | reload]
开机自启:systemctl enable httpd.service

php

安装:yum install php php-mysql
重启apache:systemctl restart httpd.service

mysql

安装:yum install mariadb mariadb-server
控制:与apache类似,服务名为“mariadb”
开机自启:与apache类似
配置向导:mysql_secure_installation主要是设置root用户密码,其他设置一般一路回车即可

sudoers

参考资料:Fedora 20下解决用户不在 sudoers 文件中的方法
fedora默认的一般用户并不是sudoer
也就是不能临时使用root权限来执行命令
但是我们可以手动加入到sudoers中
用root打开文件/etc/sudoers
在最下面加入一行USERNAME ALL=(ALL) ALL即可
此处的USERNAME为要加入sudoers的用户名

sublime的安装与中文支持

fcitx安装

参考资料:Fedora20安装fcitx输入法
yum install fcitx fcitx-pinyin fcitx-configtool im-chooser
然后用im-chooser更换输入法为fcitx
如果报错,则执行gsettings set org.gnome.settings-daemon.plugins.keyboard active false
如果实在不行,尝试删除ibus,yum install remove ibus

sublime text安装

参考资料:Fedora 20 sublime text 安装
在官方下载包,解压后复制到/opt/sublime_text/目录下
创建sublime命令:

touch /usr/bin/sublime
chmod 755 /usr/bin/sublime
vim /usr/bin/sublime

贴入一下代码:(假设创建sublime命令)

#!/bin/sh
export SUBLIME_HOME=/opt/sublime_text
$SUBLIME_HOME/sublime_text $*

中文输入支持

参考资料:FEDORA20使用FCITX在SUBLIME_TEXT中文输入
1、新建名为sublime_text_fcitx.c文件,贴入以下代码:

/*

sublime-imfix.c

Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.

By Cjacker Huang <jianzhong.huang at i-soft.com.cn>

gcc -shared -o libsublime-imfix.so sublime_imfix.c `pkg-config –libs –cflags gtk+-2.0` -fPIC

LD_PRELOAD=./libsublime-imfix.so sublime_text

*/

#include <gtk/gtk.h>

#include <gdk/gdkx.h>

typedef GdkSegment GdkRegionBox;

struct _GdkRegion

{

long size;

long numRects;

GdkRegionBox *rects;

GdkRegionBox extents;

};

GtkIMContext *local_context;

void

gdk_region_get_clipbox (const GdkRegion *region,

GdkRectangle *rectangle)

{

g_return_if_fail (region != NULL);

g_return_if_fail (rectangle != NULL);

rectangle->x = region->extents.x1;

rectangle->y = region->extents.y1;

rectangle->width = region->extents.x2 – region->extents.x1;

rectangle->height = region->extents.y2 – region->extents.y1;

GdkRectangle rect;

rect.x = rectangle->x;

rect.y = rectangle->y;

rect.width = 0;

rect.height = rectangle->height;

//The caret width is 2;

//Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.

if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {

gtk_im_context_set_cursor_location(local_context, rectangle);

}

}

//this is needed, for example, if you input something in file dialog and return back the edit area

//context will lost, so here we set it again.

static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)

{

XEvent *xev = (XEvent *)xevent;

if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {

GdkWindow * win = g_object_get_data(G_OBJECT(im_context),”window”);

if(GDK_IS_WINDOW(win))

gtk_im_context_set_client_window(im_context, win);

}

return GDK_FILTER_CONTINUE;

}

void gtk_im_context_set_client_window (GtkIMContext *context,

GdkWindow *window)

{

GtkIMContextClass *klass;

g_return_if_fail (GTK_IS_IM_CONTEXT (context));

klass = GTK_IM_CONTEXT_GET_CLASS (context);

if (klass->set_client_window)

klass->set_client_window (context, window);

if(!GDK_IS_WINDOW (window))

return;

g_object_set_data(G_OBJECT(context),”window”,window);

int width = gdk_window_get_width(window);

int height = gdk_window_get_height(window);

if(width != 0 && height !=0) {

gtk_im_context_focus_in(context);

local_context = context;

}

gdk_window_add_filter (window, event_filter, context);

}

直接用vim贴入有可能会莫名其妙自动注释掉一些代码,要注意检查一下
2、编译为动态链接库
所需工具:gtk2-devel、glib2-devel
执行命令:

gcc -shared -o sublime_text_fcitx.so sublime_text_fcitx.c  `pkg-config –libs –cflags gtk+-2.0` -fPIC  

将该so文件复制到/opt/sublime_text目录下

3、设置LD_PRELOAD并启动sublime
LD_PRELOAD=./libsublime-imfix.so sublime_text
但是每次都这样启动太麻烦了,
网上有的用sh脚本启动,其实也挺麻烦的
事实上直接修改/usr/bin/sublime的内容即可

4、修改/usr/bin/sublime内容
把LD_PRELOAD的设置添加进去,最终的配置如下:

#!/bin/sh
export SUBLIME_HOME=/opt/sublime_text_3
LD_LIB=$SUBLIME_HOME/libsublime-imfix.so
LD_PRELOAD=$LD_LIB $SUBLIME_HOME/sublime_text $*

俱乐部春纳网页后端小结

ubuntu下开发环境的搭建

php + apache + mysql 安装

sudo apt-get install apache2
sudo apt-get install libapache2-mod-php5 php5
sudo apt-get install mysql-server mysql-client
参考资料:ubuntu下安装Apache+PHP+Mysql

apache的使用

把文件放到/var/www/html目录下,通过localhost访问
一般情况下apache是自动打开的,手动开启关闭重启的命令如下:
apachectl -k start
apachectl -k stop
apachectl -k restart

mysql的使用

登录前

登录:mysql [-h 服务器] -u 用户名 -p然后按照提示输入密码
修改密码:mysqladmin -u 用户名 password "新密码"然后按照提示输入原密码

登录后

用户相关操作、创建数据库(root)

创建用户:insert into mysql.user(Host, User, Password) values('主机', '用户名', password('密码'));
本地访问的主机是localhost, 如果要远程通过phpmyadmin管理数据库,那么主机是%
创建数据库:creat database 数据库名;
用户授权:grant [select, update, insert, delect, all] on 数据库.表 to '用户名'@‘主机名’ identified by "密码";
如果是所有数据库或所有表,用*代替
授权后刷新系统权限表:flush privileges;
删除用户:delect from user where User='用户名' and Host='主机名';;
删除后刷新系统权限表:flush privileges;
删除数据库:drop database 数据库名;
修改用户密码:update mysql.user set password=password('新密码') where User='用户名' and Host='主机名';
修改后刷新系统权限表:flush privileges;

切换数据库:

use 数据库名;

显示数据:

select 列 from 表名;
若显示全部内容,列使用*

查看数据库:

show databases;
数据库中的charset尽量使用utf8,而不是gbk,防止暴露服务器信息
参考资料:GBK字符编码(字符集)缺陷导致web安全漏洞【虽然看不太懂】

phpmyadmin的使用

安装:

从官网下载后解压到/var/www/html,通过localhost访问

配置:

如果本地访问,直接使用就好
如果要连接到远程服务器的mysql,那么打开目录下的libraries/config.defaut.php文件,找到$cfg['Servers'][$i]['host']将值改为相应的服务器

mysql备份

mysqldump命令

mysqldump -h 主机 -u 用户名 -p 密码 数据库名

备份脚本(.sh)

USER="username"
PASSWORD="password"
DATABASE="datebase"
HOST="localhost"
OPTIONS="-h$HOST -u$USER -p$PASSWORD $DATABASE"

DATE=`date '+%Y%m%d-%H%M'`
BACKUP_DIR=/home/zk/datebase/
DUMPFILE=$DATE.sql

cd $BACKUP_DIR
mysqldump $OPTIONS > $DUMPFILE

该脚本将datebase数据库的内容备份到/home/zk/datebase/目录下,文件以备份时间命名

定时备份crontab

参考资料:Linux crontab定时执行任务 命令格式与详细例子
基本格式:分 时 日 月 周 命令
每一行为一个任务,定时执行命令
可以用星号(*)表示任意值
crontab -e:直接编辑时程表(vi)
crontab -r:删除当前时程表
crontab -l:列出当前时程表
crontab FILE:使用FILE文件的内容替代当前时程表

CI框架

安装:

从官网下载后解压到/var/www/html目录下即可
通过localhost/...../index.php/..控制器../..方法../..参数..访问
相关子目录:

application:用户主要目录(主要是views, controllers, models, config)
system:框架的系统目录,一般不用管
user_guide:用户手册,英文版,不需要可以删除

设计模式:MVC(Model - View - Controller)

模型(Model):处理页面和数据库的交互

数据库配置:.../application/config/database.php文件

$db['default']['hostname']:主机名
$db['default']['username']:mysql用户名
$db['default']['password']:mysql密码
$db['default']['database']:所用的数据库

形式
<? php          
    Class 模型名(首字母大写,与文件名相同,单文件名全小写) extends CI_model    
    {        
        public function 方法名()        
        {            
            ...........        
        }    
    }

特殊方法:__construct(),构建函数,当模型被调用时,首先自动调用这个函数
通常要在构建函数中载入数据库$this->load->database();

获取数据库内容
获取整张表

$query=$this->db->get('表名');返回Active Record类并且赋给变量$query
$query->result_array();调用类的result_array()方法,返回一个包含各行内容的字典,可以在视图上通过foreach分别取出每一行
对于每一行,是一个以字段名为索引的字典

获取某一行的内容

$query=$this->db->get_where('表名', 筛选条件);筛选条件是一个{‘字段名’=>值}的数组
$query->row_array();调用类的row_array()方法,返回一个包含匹配行的内容的字典

向数据库写入内容

$this->input-&gt;post('提交内容的name');返回表当内对应name的value
$this->db->insert('表名', 内容)写入数据库,内容是一个字典

视图(View):要展示的静态页面

可以把一个页面写成多个文件,显示时再拼接起来
比如可以创建一个templates文件夹,里面放一些各个网页通用头和尾
必须是php文件

html -> php改写

连接的js和css等文件,还有图片等资源不能放在application目录下
需要在根目录下创建一个文件夹,把这些资源放进去
主要是修改连接的js和csss等文件、图片等资源的文件的地址

  • 可以借助url的辅助函数
    需要在方法中加入$this->load->helper('url');载入辅助函数
    把根目录或者资源的目录写入..../application/config/config.php$config['base_url']
    利用base_url()函数
    如config中的base_url设置为localhost/join/resources
    那么,base_url(‘image/background.jpg’)就会返回字符串loacalhost/join/resources/image/background.jpg

js,css文件内不能写php语句,如果需要使用这个函数,可以把内容直接复制到页面上,而不通过连接
注意:改写的时候要注意不要不小心把原文件上的括号、分号给删掉了

控制器(Controller):包含各种方法

形式
<? php        
    class 控制器名(首字母大写,与文件名相同,但文件名全小写) extends CI_controller    
    {        
        public function 方法名()        
        {            
            ........        
        }    
    }

特殊方法:construct(),构建函数
通常需要加入`parents::
construct();,……并不知道有什么卵用,照搬=。=$this->load->model(‘模型名’);载入模型$this->load->view(‘视图文件名’, $data);`:不是只显示这个视图的内容,而是把这个文件的 内容写到当前方法的页面上,并且把变量$data内的数据传递到这个视图文件上

传递的数据在视图文件上的访问

$data['news']['title']
在视图上通过$news['title]来访问这个变量

调用模型上的方法:

$this->模型名->方法名;

表单数据的传递

controllers要有一个方法来接受表单的数据
form的action应指向这个方法
这个方法用来处理表单传递的数据,并且调用模型的方法与数据库进行交互

提交到服务器

登录:

ssh 用户名@服务器,然后输入密码

服务器与本地文件的传输:

scp命令,用法与cp命令相似
比如上传本地~/home.php到服务器的主目录下:
scp ~/home.php 用户名@服务器:home.php

网站的目录:

一般来说,
对于nginx,放在/usr/share/nginx/html/目录下
对于apache,放在/var/www/html/目录下

nginx的配置挺复杂的,还有待以后进一步的学习

关闭ci框架的debug模式

网站挂上去后最好关闭debug模式(防止程序信息暴露在前台)
..../application/config/database.php中的$db['default']['db_debug']修改为FALSE即可
i

github

其实一开始写后端的时候并没有git= =时间紧啊,没时间给我慢慢玩
不过网站挂上去,在写查重和改写雨停的优化版的时候就玩了玩git

github在Windows上有客户端,直接使用就好

在linux上需要用git

安装git:

sudo apt-get install git

配置git:

本地创建ssh key:

ssh-keygen -t rsa -C "邮箱",会提示确认路径和密码,一路回车默认即可

在github中添加ssh key:

进入Account Settings,选择SSH Key,Add SSH Key,title随便填,key复制粘贴文件~/.ssh/id_rsa.pub的全部内容

设置username和email

每次commit都会记录这两个信息
git config --global user.name "用户名"
git config --global user.email "邮箱"

设置要git的目录

进入要git的目录
初始化:git init
添加远程地址:git remote add origin git@github.com:github用户名/远程仓库名.git

使用(简单的git命令)

git clone <ADDRESS>:克隆代码库,可以是本地代码库,也可以是远程代码库(USERNAME@HOST:PATH)
git add <-i> <FILE> ....:【-i,交互式】添加文件到代码库中
git rm <FILE>:将文件移出代码库
git commit -m <MESSAGE>:提交更改,将修改的文件提交到缓冲区
git push <ORIGIN> <BRANCH>:推送代码库到远程代码库
git pull:从远程同步代码库到本地并合并
git branch:查看当前分支
git branch <BRANCH>:创建分支(主分支为Master)
git branch -d <BRANCH>:删除分支
git checkout <BRANCH>:切换到指定分支
git checkout -- <FILE>:从缓冲区替换本地改动
git log:查看提交的历史记录
git status:显示当前的修改状态
git reset <LOG>:恢复到历史版本
参考资料:
git - 简易指南
【Github教程】史上最全github使用方法:github入门到精通

附:ubuntu下的sublime text

安装:

sudo apt-get install sublime-text

中文支持

参考资料:解决Ubuntu下Sublime Text 2无法输入中文的方法

Markdown支持

参考资料:sublime text 2 下的Markdown写作
但是语法高亮的设置我一直找不到,最后是在Package Control: install Pakage中直接安装MarkdownEditing,但是语法高亮效果不太满意,不知道有没有更好的解决方法
注:语法高亮在markdown preview的设置文件中,而且需要下载主题才能使用。
另外可以在github上下载chrome的markdown preview plus插件,这样就能在浏览器上直接打开md文件啦(2015/04/28)

最后

雨停的前端做的好赞!!!诶,不会美工的小码农真是不能比啊╮(╯▽╰)╭

= =学习CI,用服务器的时候遇到好多逗比的问题去请教蛋蛋和六神

感谢蛋蛋和六神的指导嘿嘿~~~

CI官网的教程写的有点晦涩,不太好懂,这里只是个人在使用中自己的理解,可能会有一些错误,欢迎指出~

不过网站还不会挂咯,找个时间得学学怎么挂网站~~

居然……还我数据?!?!

在学校的时候把windows还原了一遍,导致linux进不去了,orz
回来之后重装linux,结果不小心操作不当,把硬盘格式化了!!
后来发现硬盘又出了点问题,哎烦,查了好多东西,试了好多方法,而且恰巧在看鸟哥的第三章(硬盘分区方面的内容),折腾了三天半终于把这娃整好,真是感动
查的东西好杂,让我写个总结冷静冷静

  • 【补充】:重装linux后无法进入windows的解决方法
    ubuntu和debian直接使用update-grub命令扫一遍即可
    通用的方法是grub-mkconfig -o /boot/grub/grub.cfg

还原windows导致linux无法进入的原因

windows是相当霸道的,在安装时,win会主动覆盖掉MBR,也就直接把linux的引导加载程序给覆盖掉,linux就自然无法进入啦

那么,我们把linux引导加载程序grub修复了就好啦?

咳咳……蛋蛋让我去百度查查怎么修复grub,不过太弱了,看了三种修复的教程都没能看懂,最后似乎还把linux整坏了!!!只好重装linux,可又忘记重新分区,选了个“清除所有程序和数据”…………呵呵,linux比windows还霸道,清除的不只是linux的系统分区,而是全硬盘啊!!把我全硬盘都格式化成ext4,真是吓哭我了……

找回丢失的数据

  1. 硬盘被格式化并不会直接把上面的数据抹去,不被新数据覆盖的区段的数据其实是可以复原的;
  2. 而linux安装的位置在硬盘的头部,我的主要数据是放在DEF盘,并不会被覆盖,修复数据还是有戏的
    重要的数据放在C盘是极不安全的,一旦系统崩溃,还原、重装之后就被新的系统文件覆盖,找都找不回来……所以平常要把桌面、我的文档、收藏夹等数据设置在DEF盘,至少应该把文档所在的文件夹放在DEF盘,再创建快捷方式到桌面上……非常非常重要的数据要做好备份,备份到网络上(并不可靠)或者备份到移动硬盘等其他设备上

所以呢,网上找找PE,通过UltraISO等软件把iso镜像烧进U盘里,插进计算机,开机启动项设置为该U盘,在PE下利用diskgenius等分区工具进行“重建分区表”就可以啦。它会自动搜索电脑上丢失的分区,把还存在数据恢复出来

但是,恢复的数据会有少数被损坏,也就是说不可能完好无损的找回来,所以平时要保护好自己的数据啊?!?!?!!!
而且,找回来的分区必须设置为主分区= =好麻烦!改回逻辑分区的话需要把数据移到其他地方,把这些分区删除后新建扩展分区,在扩展分区下新建逻辑分区,再把数据移回来!
因为一个硬盘只能存在四个主分区或扩展分区(其中扩展分区至多只能有一个)
嗯……我比较懒,所以把原本win上的四个磁盘缩减为三个磁盘,都作为主分区,留下一个作为扩展分区给linux
另外呢,win系统分区必须激活设置为活动分区!!

安装windows

找回数据时,把dell出厂的镜像分区也给找回来了,但是还原程序被格掉,重装后也识别不了出厂镜像= =它那鬼镜像(wim)还分成了两组,每组两个,着实看不懂要怎么用……只好放弃正版win8,上网找了原版的win8.1企业版,在PE下通过winNT安装,再用激活工具破解,从此又过上了盗版的日子
不过镜像我还留着,据说有方法能够调用,但是实在复杂,等我有空好好研究研究

安装windows其实有个小插曲,一开始NT一直识别不了我下载的win8.1的安装程序,折腾半天后无奈的检验了一下SHA1,简直哭,SHA1值并不匹配——也就是下载的时候文件被损坏了!
所以啊,以后安装重要的、安装过程复杂的程序、软件,一定一定要注意先校验一下MD5值或者SHA1值(在官网下载的话一般会提供)
否则如果在安装过程中才发现损坏,会很麻烦的。比如安装系统,装一半的时候才告诉你文件损坏,可是你原来的win早被格式化了,整起来可就麻烦多了(所幸现在系统安装程序在安装前大都保守地检验了一下安装文件的完整性)

internal hard disk drive not found?

这条错误信息着实折腾了我好久,到底是无法读取硬盘呢还是无法调用驱动程序?
大概是后者吧,毕竟我在BIOS中能找到硬盘的信息,在PE下也能正常访问硬盘的数据
硬盘驱动?这个一般操作系统都会提供——一开始我以为只要把系统装上,这个问题就能够解决,然而并没有这么简单!
在PE下安装系统后,需要重启进行最后的设置,但是每次都不能成功进入系统,都卡在这句错误信息上!
怎么办?我尝试了百度上的各种办法,最后终于成功了?!——
在BIOS的boot选项卡下把Secure Boot设置为Disabled,把Boot List Option改为Legacy
竟然这样就把问题解决了?!百思不得其解,只好求教度娘这两个选项的含义

Secure Boot ##参考资料##

若干年前,各大主板厂商推出UEFI取代历史悠久的BIOS,UEFI全称为“统一的可扩展固定接口”。
而Secure Boot是UEFI的一部分,它采用密钥,防止恶意软件侵入操作系统和硬件驱动程序

但是UEFI并不能得到广泛的推广,原因是微软的态度并不积极,他们要求安装Windows8时要关闭Secure Boot,而对预装的Windows8需要打开Secure Boot,所以我在安装windows时就必须把Secure Boot关闭啦!

Boot List Option

硬盘启动模式,包括Legacy和UEFI两种

UEFI如上文介绍,是一种新的BIOS;
Legacy则是传统的BIOS模式

在UEFI下安装的系统以后只能由UEFI模式进入系统;
在Legacy下安装的系统以后只能由Legacy模式进入系统

甚是复杂!!

(2015/08/12补充)以UEFI模式安装windows系统

参考资料:win8/win8.1 UEFI + GPT 安装详细图解

  • 先前windows安装失败的原因
    数据恢复后将分区表格式转换成了MRP格式,而安装uefi系统需要gpt分区表
  • UEFI系统启动速度比传统模式快
  • gpt分区表
    • 支持四个以上的主分区
    • 支持2.2TB以上的硬盘
    • 但是XP不支持该分区表
  • 分区表转换方法
    • 通过PE系统下的diskgenuis工具
    • 直接利用windows安装程序的命令行(需要烧制到光盘或U盘)
      注意:修改分区表后硬盘数据将丢失,要提前做好备份工作
      • 在安装界面下按<S-F10>打开命令行
      • 输入diskpart启动分区工具
      • 输入list disk打印所用硬盘,找到所要修改的硬盘的序号
      • 输入select disk {num}选择num号硬盘
      • 输入clean清空硬盘数据
      • 输入convert gpt修改分区表类型

Linux安装

引导方式有两种——

  1. win引导linux:
    把引导分区设置为挂载/boot的分区
    通过软件easybcd等软件进行引导
  2. linux引导win:(默认)引导程序将被安装到MBR上

最后

电脑修的累死我咯,但是能修好,重新见到windows桌面,真是非常的愉悦,感觉三天半没白忙)
说不定以后找不到工作还能给人修修电脑啊哈哈哈哈哈哈!!!

唯一不满意的是没能把出厂的原版系统找回来,等有空接着研究研究吧!

第一二期作业总结

呃,要用markdown写文章呢差点忘了……
这期作业写了我八九个小时啊,翻阅了好多资料,干脆写份总结算了。下面就按照我做作业的思路、途中遇到的问题及解决方法展开来写吧——

大致设想

首先,模仿百度主页嘛,找张图片,做个输入框,搞个submit的按钮,简单安上导航栏,右上角意思意思搞个“登陆”、“注册”的鬼玩意。

input text的尺寸调整

  • text是没有width、height属性的
  • 宽度其实可以通过字段size调整
  • 高度可以通过style属性中的字体高度font-size来调整
  • 另外还有style中的padding,可以调整输入框的内边距,不然输入的时候字压着边框太丑

    text和submit之间总存在缝隙

    取消缝隙,首先要设置两个元素的margin为0,另外submit默认是有边框的,所以还要设置submit的border为0。但如果将两元素的代码分成两行,则间隙仍不能取消

  • 其实回车也会留下空格的,所以只要把回车去掉,将两个元素挤在一行即可

<input type="text" style="margin:0px"><input type="submit" style="margin:0px;border:0;background-color:blue">

text和submit高低错位

首先考虑用top,bottom属性设置上下留空白解决,但是无论怎么改变值都没有变化
查过资料后知道,设置left,right,top,bottom前需要设置position

position有五种值:
static(默认):不定位
relative:相对于块
absolute:相对于页面
fixed:相对于视窗
inherit:继承父元素的值

栅格系统

我直接在<body></body>中加入

<div class="container">
    <div class="row">
        <div class="col-md-1">...</div>
        <div class="col-md-offset-1">...</div>
    </div>    
</div>

结果什么都没发生!!!
后来才知道,是忘了<link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.css" rel="stylesheet">……呵……呵……简直逗比~

说道栅格系统,顺便截一段资料过来吧:
栅格系统 bootstrap

  • 行列必须包含在.container里
  • 列(.col-xx-xx)必须包含在行(.row)里





















































超小屏幕 手机(<768px) <="" td="">
小屏幕 平板 (>=768px) 中等屏幕 桌面显示器 (>=992px) 大屏幕 大桌面显示器 (>=1200px)
栅格系统行为 总是水平 开始是堆叠在一起的,当大于这些阈值时将变为水平排列C
.container最大宽度 None(自动) 750px 970px 1170px
类前缀 .col-xs- .col-sm- .col-md- .col-lg-
列(column)数 12
最大列宽 自动 ~62px ~81px ~97px
槽(gutter)宽 30px(每列左右均有15px)
可嵌套
偏移(offsets)
列排序

做作业的时候忘记练习表格了……所以手码下来……
哇靠!手码表格真是累死了……


列合并:colspan属性

逗比功能:鼠标悬浮事件

效果:鼠标移到submit上,submit会自动跳到输入框的另外一侧,就是不给你点,还要在输入框上出现(don’t touch me!),但是不要影响搜索!要搜索的话只能点击图片,为了别太坑还得做一个小小的提示框,但不能太大——简直逗比的我……

submit跳动

  • 鼠标悬浮事件Mouseover
  • 在两个分别设置两个submit,让他们不能同时出现,display属性,toggle方法
    起始状态下,左边的dispaly为none
    触发事件后,对两个submit都采用toggle方法

    在输入框中出现don’t touch me

  • text输入框value属性的获取和修改
    • 获取:$(“…”).val()方法即可返回表单的字段
    • 修改:$(“…”).attr(“…”,”…”)方法即可将第一个参数匹配的属性修改为第二个参数的内容
  • 但是这个用起来似乎有点麻烦……在百度上看到另外一种解决办法
    • document.#form.#text.value = document.#text.#text.value + “(Don’t touch me!)”;
    • 要注意的是:这里的#form和#text是form标签和input标签的name属性而不是 id 属性!而且这里不需要打井号,如:document.form.text.value

      点击图片自动跳转到百度搜索

  • 跳转
    • 在百度搜索试着搜索aaa,搜索页得到的网址是http://www.baidu.com/baidu?wd=aaa
    • 那么猜测,直接修改网址末尾的aaa为搜索的内容是否可行?结果是可以的……
    • 那么,我们只需要把搜索内容连接到http://www.baidu.com/baidu?wd=末尾作为图片的超链接即可实现
  • 搜索内容的获取
    • 键盘keyup事件
      与其相对的还有一个keydown事件,down是指按下键盘(包括输入法还未把输出到对话框)触发的时间,up是指松开键盘(包括输入法把内容输出到对话框上)触发的时间,
      显然这里要用的是应该是keyup事件
      另外,使用keyup事件不会在不通过键盘输入来修改内容的情况下触发,也就是说,前边用JQ修改输入框内容并不会触发事件,不影响内容的获取
    • 将搜索内容与http://www.baidu.com/baidu?wd=组成的连接作为图片连接的href属性,使用attr("href","...")方法即可

      警告框

  • 使用bootstrap,自己再对字体、尺寸稍作修改即可
  • 但是千万别忘了
    <link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.css" rel="stylesheet">
  • display:none
  • show()方法

    注意

    使用JQ的格式——
    注意:fuction紧跟后面只有一个右括号
    ready的右括号在最后边,中间的代码即花括号的内容是fuction的内容

    $(document).ready(function(){


    });


    $(document).ready(function(){

    $("...").click(function(){
        $("...").attr("","");
    });
    

    });

  • 选择器在圆括号内,除了document和this之外都要加双引号,类要加’.’,id要加’#’
  • JQ的内容一定要嵌套在$(document).ready(function(){.....});
  • 语句后边别忘了分号

登陆、注册弹框

在百度搜到一种遮罩式弹框的模板

jQuery+css+html实现页面遮罩弹出框
具体代码如下:

CSS部分:

body { 
    font-family:Arial, Helvetica, sans-serif; 
    font-size:12px; 
    margin:0; 
} 
#main { 
    height:1800px; 
    padding-top:90px;     //打开弹窗的按钮
    text-align:center; 
} 
#fullbg { 
    background-color:gray; 
    left:0; 
    opacity:0.5;        //背景设置为灰色
    position:absolute; 
    top:0; 
    z-index:3; 
    filter:alpha(opacity=50); 
    -moz-opacity:0.5; 
    -khtml-opacity:0.5; 
} 
#dialog { 
    background-color:#fff; 
    border:5px solid rgba(0,0,0, 0.4); 
    height:400px; 
    left:50%; 
    margin:-200px 0 0 -200px; 
    padding:1px; 
    position:fixed !important; //弹出的浮动对话框 
    position:absolute; 
    top:50%; 
    width:400px; 
    z-index:5; 
    border-radius:5px; 
    display:none; 
} 
dialog p { 
    margin:0 0 12px; 
    height:24px; 
    line-height:24px; 
    background:#CCCCCC; 
} 
#dialog p.close { 
    text-align:right; 
    padding-right:10px; 
} 
#dialog p.close a { 
    color:#fff; 
    text-decoration:none; 
} 

HTML部分:

<div id="main"><a href="javascript:showBg();">点击这里查看效果</a> 
    <div id="fullbg"></div> 
    <div id="dialog"> 
        <p class="close"><a href="#" onclick="closeBg();">关闭</a>  
        <div>正在加载,请稍后....</div> 
    </div> 
</div>

JQ部分:

<script type="text/javascript">
    //显示灰色 jQuery 遮罩层
    function showBg() {
        var bh = $("body").height();
        var bw = $("body").width();
        $("#fullbg").css({
            height:bh,
            width:bw,
            display:"block"
        });
        $("#dialog").show();
    }
    //关闭灰色 jQuery 遮罩
    function closeBg() {
        $("#fullbg,#dialog").hide();
    }
</script>

只要稍微修改一些数值就可以直接拿来用啦!!

第二个弹框

但是……直接套用模板,把html再复制一份企图创建第二个弹框就出现问题了,两个按钮弹出的是同一个框,咋办咧?
观察一下代码,发现JQ部分的$("#dialog").show();语句是对id为dialog的操作,如果在html里直接复制了第一个弹框,id都为dialog,show只为作用于第一个匹配项………
如果想要第二个弹框,就得让两个弹框的id不同,改为dialog1和dialog2单击后调用两个不一样的函数,也就是简单的复制一下showBg()函数,并且改为showBg1()和showBg2,并在css部分做出相应的修改就可以啦~

模态框

上面这个弹框太low了,其实bootstrap已经给我们提供了一个模态框的组件,方便又好看
但是偶尔会出现一些奇怪的bug…………

给鼠标悬浮在submit上的时候加个音频

嘿嘿,逗比功能就是拿来搞怪的,既然如此干嘛不个它加个笑声的音频呢?

找到一个简短的小黄人笑声的音频

添加一个<audio>标签

controls属性:如果不设置的话,默认不显示播放器;如果设置为controls=”controls”则显示播放器
play()方法:让指定的播放器播放音频
具体代码如下:

html部分:

<audio id="laugh">
    <source src="..." type="audio/mpeg">
</audio>

JQ部分:(插入在submit的mouseover事件下)

laugh.play();
奇怪的是!这里要直接对这个对象使用方法,id不加井号;
$("#laugh").play();却行不通?!

咳咳,总结——

感觉像是玩上瘾了,居然两天花了八九个小时做这个网页,而且这两天还是很多课不是没课的……
又在第三天花了四五个小时写下这个玩意儿……我也是够拼哒= =
虽然很好玩,做出来也很有成就感……但是………我的眼睛啊!!!!!不知道近视又是加深的几度?!

《Java核心技术》Chap5笔记

继承

类、超类、子类

通过extends关键字来实现类的继承

class Manager extends Employee
{
    ...//Add some new methods and new fields
}

此时,ManagerEmployee子类EmployeeManager超类
Manager会继承Employee的所有方法和域

重写类方法、访问超类的私有域

重新定义一个方法,如果与超类的某个方法重名,那么该方法会覆盖掉超类的方法
但是,子类并不能访问超类的私有域
解决方法:
通过超类的方法来访问超类的私有域,
如果子类重写了超类的方法,那么可以通过关键字super来访问超类的方法
如:

class Manager extends Employee
{
    public double getSalary()   //超类中也有一个getSalary方法
    {
        double baseSalary = super.getSalary();  //通过super关键字来访问超类的方法
        return baseSalary + bonus;
    }
}

C++中,访问超类是用::操作符的形式

子类的构造器

子类的构造器第一行必须调用超类的构造器对对象进行构造
如:

public Manager(String n, double s, int year, int month, int day)
{
    super(n, s, year, month, day);   //借助super关键字
    bonus = 0;  //而外的域初始化
}

如果没有显式地调用超类的构造器,那么将自动调用超类默认的(没有参数)的构造器;
如果超类没有默认的构造器,也没有显示地调用超类的构造器,那么java编译器将会报错

在C++的构造函数中,使用初始化列表语法来调用超类的构造函数

对象的引用变量

如果将对象的引用变量声明为某个类的引用变量,那么它除了可以引用这个类的对象之外,还可以引用它的子类的对象
如:
已知ManagerEmployee的子类,
Employee e;既可以引用Employee的对象,也可以引用Manager类的对象

这种一个对象变量可以指示多种实际类型的现象称为多态(polymorphism)
在运行时能自动选择调用哪个方法的现象称为动态绑定(dynamic binding)

继承层次

由一个类派生出来的所有类的集合称为继承层次(inheritance hierarchy)
从某个特定的类到其祖先的路径称为继承链(inheritance chain)

Java不支持多继承

多态

如前所述,某个类的引用变量可以引用该类的子类的实例,称对象变量是多态的
但是,不能通过这个引用变量访问子类的方法和域
如:

Manager boss = new Manager(...);
Employee e = boss;    //Manager是Employee的子类
boss.setBonus(5000);  //setBonus()是Manager类特有的方法,Employee没有该方法    
e.setBonus(5000);     //ERROR!!!! Employee没有这个方法,不能调用;此时可以利用强制类型转换解决  
e.getBonus();         //OK,Employee有这个方法,但是Manager重写了这个方法,由于动态绑定,此处会自动调用Manager的方法   

注意:不能将一个超类的引用赋给子类变量(如:Manager boss = e;

动态绑定、对象方法调用的过程

  1. 编译器查看对象的声明类型和方法名
    编译器会一一列出对象所在类的所有该方法名(包括超类的public方法)的方法(此处还没有检查参数类型)
  2. 编译器查看调用方法时提供的参数类型
    编译器会挑出类型匹配的所有方法(不一定要完全匹配,兼容即可)
    • 方法名和参数类型作为一个方法的签名(唯一证明),而返回类型不是
      因此覆盖方法时应保证返回类型是兼容的
  3. 如果是privatestaticfinal方法
    编译器则可以准确地知道调用哪一个方法,这种调用方式称为静态绑定
  4. 程序运行时采用动态绑定调用方法,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法

如果每次调用方法都进行一次搜索,那么时间开销太大了
因此,虚拟机会预先为每个类创建一个方法表(method table),调用方法时虚拟机直接在该表中查找对应的方法

  • 动态绑定的特性:无需对现存代码进行修改,就可以对程序进行扩展。
    假如为一个类创建子类,那么子类的实例无需对该类进行重新编译即可调用该类的方法

注意:覆盖一个方法时,子类方法的可见性不得低于超类方法的可见性;特别是当超类方法是public时,子类方法必须是public

阻止继承:final类和方法

  • final类:不允许扩展(即不允许利用该类定义子类)的类
    类中的方法会自动设置为final,而域不会
  • final方法:不允许覆盖(重写)的方法
  • 如果类中某个方法很简短、被频繁调用且没有真正进行覆盖
    那么即时编译器会将其进行内联处理以提高程序效率
    如果虚拟机又加载了一个子类,子类对这个方法进行了覆盖,那么编译器会自动取消内联

强制类型转换

  • 与内置类型的强制转换形式一致
  • 只能在继承称此内进行类型转换
  • 子类转换成超类是允许的
    正如超类的引用变量能直接引用子类
  • 超类转换成子类不一定允许
    如果出错,会引发一个异常,如果没有被捕获,那么出现编译错误
    通常需要借助instranceof运算符来判断该对象是否能成为目标子类的实例
    如:(ManagerEmployee的子类)

    if( staff[1] instanceof Manager )
    {
        boss = (Manager) staff[1];
    }
    

注意:尽可能避免强制类型转换和instanceof的使用
在C++中,如果强制转换失败将得到的是null对象,而不会产生异常或是出现编译错误

抽象类

abstract修饰符定义抽象类和抽象方法

  • 抽象方法相当于“占位”的作用,表明这个类存在这个方法,但类的定义中不给出该方法的具体定义,而在其子类中进行具体的定义
  • 包含抽象方法的类必须定义为抽象类,不包含抽象方法的类也可以定义为抽象类
    • 抽象类不允许直接产生实例,但其具体子类可以
    • 可以定义一个抽象类的引用变量,用它来引用其子类的实例

如:

abstract class Person   //包含抽象方法,必须定义为抽象类
{
    private String name;
    public Person(String n)
    {
        name = n;
    }
    public abstract String getDescription();  //抽象方法
    public String getName()
    {
        return name;
    }
}

C++中,在方法定义的尾部加上=0来表示一个纯虚函数(抽象方法);包含抽象方法的类自动作为抽象类

抽象类的作用:
假设StudentEmployeePerson的子类
StudentEmloyee中都定义了getDescription方法
变量Student s = new StudentEmployee e = new Employee都可以调用这个方法
但是Person p = sPerson p = e则不行(因为Person类不具有这个方法)
如果给Person类添加一个getDescription抽象方法,那么变量p就能调用这个方法(即时编译器会自动确定调用哪一个类的方法)

受保护访问

使用protected的实例域对所有子类及同一个包的所有其他类都可见
该实例域可以被子类的方法访问
假设p域为A类的protected域,B类和C类为A类的子类
但B类中的方法只能访问B对象的p域,而不能访问C类的p域
注意:该修饰符应当谨慎使用;一定程度上破坏了oop的数据封装原则
C++中,保护域仅对子类可见,这一点上Java的protected安全性比较低

控制可见性的访问修饰符归纳

  • private:仅对本类可见(常用于实例域)
  • public:对所有类可见(常用于方法)
  • protected:对本包和所有子类可见(常用于特殊的实例域)
  • 默认:本包可见(尽量避免)

Object:所有类的超类

每个类均由Object类扩展而来,但无需显式的书写extends Object
可以创建一个Object类的引用变量来引用任何类型的对象
所有类都拥有一些通用方法,这些方法定义在Object类中
C++中没有所有类的根类,但每个指针都可以转换成`void `指针*

equals方法

该方法不是标准库提供的方法,需要自己编写
检查两个对象是否相等(状态相同)
形式:objectA.equals(objectB),返回布尔值

一般实现机制:

  1. 检查两个对象是否为同一个引用
  2. 检查参数里的对象是否为null
  3. 检查两个对象是否为同一个类
  4. 检查两个对象的实例域是否完全相等

如果在子类中定义equals方法,那么它必须显调用超类的equals方法

class Manager extends Employee
{
    ...
    public boolean equals(Object otherObject)
    {
        //检查是否属于同一个类     
        if( !super.equals(otherObject) ) 
            return false;   
        Manager other = (Manager) otherObject;
        //检查实例域是否完全相等
        return bonus == other.bonus;
    }
}

相等测试与继承

  • Java语言规范要求equals方法具有以下特性
    • 自反性:x.equals(x)应该返回true
    • 对称性:若x.equals(y)为true,则y.equals(x)也应该为true
    • 传递性:若x.equals(y)y.equals(z)为true,则x.equals(z)也应该为true
    • 一致性:若x和y引用的对象不变,那么反复调用x.equals(y)应该返回相同的结果
    • 对于任意非空引用x,x.equals(null)应该返回false
  • 编写equals方法的建议
    • 显式参数命名为otherObject,类型为Object(否则无法覆盖Object.equals()),之后将其转换为一个叫做other的变量
    • 检测thisotherObject是否引用同一个对象
      if(this == otherObject) return true;
    • 检测otherObject是否为null
      if(otherObject == null) return false;
    • 检测thisotherObject是否属于同一个类
      • 如果equals在每个子类中语义不同:
        if(getClass() != otherObject.getClass()) return false;
      • 如果语义统一
        if(!(otherObject instanceof ClassName)) return false;
    • otherObject转换成相应的类的类型变量
      ClassName other = (ClassName) otherObject;
    • ==比较所需的基本类型域,用equals比较所需的对象域
      如果都匹配,返回true,否则返回false
    • 如果在子类中重定义equals,则必须调用suer.equals(other)
  • 对于数组类型的域,可以使用静态的Arrays.equals方法检测两个数组是否相等
    甚至可以用于对象数组,它将自动调用对应类的equals方法
  • 可以使用@Override对覆盖超类的方法进行标记
    当尝试定义一个新的方法(即不小心出现错误)的时候,编译器会报错
    如:@Override public boolean equals(Object otherObject)

hashCode方法

散列码是由对象导出的一个整型值,是没有规律的
一般两个不同的对象的散列码不同

  • Object.hashCode()导出的是对象的存储地址
  • 自定义类可以自己重写hashCode方法
  • String.hashCode()的散列码是由字符串内容计算得到
  • hashCode()equals()的结果应统一
    即当x.equals(y)返回true时,x.hashCode()y.hashCode()的结果应该一致
  • Objects.hashCode(Object a)(注意是Objects不是Object)
    当a为null时返回0,否则返回a.hashCode()
  • 散列码的计算应合理组合实例域的散列码,以便能够均匀地产生散列码
    也可以直接借助Objects.hash来生成散列码
    如利用实例域xyz组合生成散列码:

    public int hashCode()
    {
        return Objects.hash(x, y, z);
    }
    

toString方法

用于返回表示对象值的字符串
Point.toString()返回java.awt.Point[x=10,y=20]
通常toString方法都以类名+域值的形式返回

  • 标准库中大多数类都提供了toString方法
  • 自定义类最好也能提供这个方法,以便后期调试
  • 当一个对象与字符串相加时,系统会自动调用toString方法
    "abcd" + x相当于"abcd" + x.toString()
  • 当用System.out.println(x)打印对象x时,也会自动调用toString方法
    System.out.println(x.toString())
  • Object.toString()返回对象的类名与散列码
    System.out.println(System.out)返回java.io.PrintStream@2f6684
  • 但是,数组直接继承了object类的toString方法
    也就是说
    int[] luckNumbers = {2, 3, 5};
    String s = "" + luckNumbers;
    得到的s将会是I@1a46e30(I表示整型数组)
    • 此时我们可以调用静态方法Arrays.toString()来打印
      String s = "" + Arrays.toString(luckNumbers);
    • 如果要打印多维数组,那么应调用Arrays.deepToString方法
  • 一般的toString方法的定义方式

    public String toString()
    {
        return getClass().getName()    //获取类名   
            + "[name="    + name
            + ",salary="  + salary
            + ",hireDay=" + hireDay
            + "]";
    }
    

泛型数组列表

Java允许定义数组时用变量来确定数组大小
ArrayList类又提供了一个动态数组,称为数组列表
ArrayList类是一个采用类型参数(type parameter)的泛型类(generic class)

  • 数组列表管理着对象引用的一个内部数组
  • 当内部数组占满后如果再往其中添加内容,数组列表会自动创建一个更大的数组,并把数据拷贝到新的数组上

    //创建数组列表时需要指明对象的类
    ArrayList<Employee> staff = new ArrayList<Employee>();    
    //等号右边可以不指明对象类型
    ArrayList<Employee> staff = new ArrayList<>();  
    //可以对数组列表调用“add”方法添加对象
    staff.add(new Employee(...));
    //如果能提前估计对象的熟练,可以在填充数组前,调用“ensureCapacity”方法指定内部数组的大小,当超过这个大小时会自动创建一个更大的数组(这是与普通数组的区别)  
    staff.ensureCapacity(100);   
    //也可以在初始化时直接指定内部数组的大小   
    ArrayList<Employee> staff = new ArrayList<>(100);
    //调用“size”方法返回内部数组的实际大小  
    //与普通数组的“length”方法类似 
    staff.size();
    //当确认数组大小不再发生变化时,可以调用“trimToSize”回收多余空间
    //如果大小固定后再次添加元素,那么将会移动存储块
    staff.trimToSize();
    

在老版本的Java中,程序员采用Vector类实现动态数组,但ArrayList类更加有效
在C++中,利用vector模板来实现动态数组。C++中重载了[]运算符,Java中必须显式调用;C++中向量是值传递的,即a=b会拷贝一个新的给a,Java中ArrayList类是引用的传递,即a和b引用同一个数组列表

访问数组列表元素

staff.set(i, harry);          //类似于数组的staff[i] = harry;
Employee e = staff.get(i);    //类似于数组的e = staff[i];
staff.add(i, e);              //把e插入到i位置,i~(size()-1)的元素后移
Employee e = staff.remove(i); //移除i位置的元素并返回,类似python的pop
staff.toArray(a);             //把数组列表staff的内容填入普通数组a    
for(Employee e : staff)       //可以利用for each遍历数组列表
{
    ...
}

类型化与原始数组列表的兼容性

P186~187,此处并没有看懂!

对象包装器与自动装箱

有时候需要将基本类型转换成对象使用
这些类称为包装器(wrapper)

  • 常见对象包装器类
    • Interger
    • Long
    • Float
    • Double
    • Short
    • Byte
    • …以上六个对象包装器类又派生于Number类
    • Charactrer
    • Void
    • Boolean
  • 对象包装器类是不可变的
  • 对象包装器类是不可扩展的(final类)
  • 常用于构造数组列表
    数组列表是不支持基本类型数据的
    此时可以借助对象包装器类实现
    ArrayList<Interger> list = new ArrayList<>();
  • 为数组列表添加修改元素时会自动装箱(autoboxing)
    如:list.add(3)会自动替换成list.add(Interger.valueOf(3))
  • 表达式中绝大多数的对象包装器能够自动装箱和拆箱
    如:
    int n = list.get(i);等价于int n = list.get(i).intValue();
    Interger n = 3;n++等也会自动装箱和拆箱
  • 但是对于==运算符,对象包装器不会自动拆箱
    如:a、b为某对象包装器的实例
    if(a==b) ...判断的是a和b是否为同一个引用
    if(a.equals(b)) ...才能实现两个值的比较
  • 对象包装器类不能实现方法的传递的参数可变
    因为对象包装器类是不可变的
    可以借助org.omg.CORBA包中定义的持有者类型(holder)
    如:IntHolder、BooleanHolder等
    持有者类型都包含一个公有值域value!!!可以直接访问这个域

常见方法——

//java.lang.Interger,其他对象包装类的方法类似
int intValue()      //以int的形式返回对象的值   
static String toString(int i)   //以String对象的形式返回i的十进制表示    
static String toString(int i, int radix)   //同上,但以radix进制表示 
static int parseInt(String s)   //以十进制将s转换为整型   
static int parseInt(String s, int radix)  //同上,以radix进制
static Interger valueOf(String s)  //以十进制将s转换成Interger类的实例   
static Interger valueOf(String s, int radix)    //同上,以radix进制    
//java.text.NumberFormat    
Number parse(String s)     //将s转换成数值并返回    

参数数量可变的方法

在参数类型之后加上三个句点.即表示可接受任意数量的该类型参数
printf方法的定义
public PrintStream printf(String fmt, Obejct... args){}
剩下的这些参数会依次放入args数组中

枚举类

  • enum关键字定义的枚举类型实际上是由枚举类实现的
  • 构造器值在构造枚举常量时被调用
  • 所有的枚举类型都是Enum类的子类
  • 在枚举类型中可以添加一些构造器、方法、域
    例如:

    public enum Size
    {
        SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
        private String abbreviation;
        private Size(String abbreviation)
        {
            this.abbreviation = abbreviation;
        }
        public String getAbbreviation()
        {
            return abbreviation;
        }
    }
    

常用方法——

//java.lang.Enum<E>
static Enum valueOf(Class enumClass, String name)  //返回指定类、指定名字的枚举常量
static toString()   //枚举常量名
int ordinal()    //返回枚举常量在enum声明中的位置(从0开始)
int compareTo(E other)    //如果枚举常量在other前,返回负值;如果this==other,返回0;如果在other后,返回正值

反射

能够分析类能力的程序称为反射(reflective)

  • 反射机制用来——
    • 在运行中分析类的能力
    • 在运行中查看对象
    • 实现通用的数组操作代码
    • 利用Method对象(类似C++中的函数指针)

反射机制通常用于工具构造,而不是应用程序设计

此节暂时跳过
P192-212

继承设计的技巧

  1. 将公共操作和域放在超类
  2. 不要使用受保护域
    protected机制并不能很好的保护域
    第一,子类集合是无限制的,任何人都能派生一个子类并编写代码来直接访问受保护域,破坏了封装性;
    第二,Java中,同一个包中所有类都可以访问受保护域
    受保护域一般用于指示那些不提供一般用途,而应在子类中重新定义的方法
  3. 只使用继承来实现”is a“关系
    不滥用继承
  4. 除非所有继承的方法都有意义,否则不要使用继承
  5. 在覆盖方法时,不要改变预期的行为
    即不要偏离最初设计的想法
  6. 使用多态,而非类型信息
    if(x...)...else if(x...)...(用来判断x的类型,来执行相应的动作)应放在同一个方法里
  7. 不要过多地使用反射
    反射在编译时很难发现错误

《Java核心技术》Chap4笔记

Chap4对象与类

预定义类

对象与对象变量

构造器是一种特殊的方法,用来构造类的一个实例,
构造器名与类名是一致的,
new Date()就构造出Date类的一个实例,也就是一个对象
Date deadline = new Date();中,deadline是一个对象变量而不是对象,它仅仅起一个引用的作用
因此Date deadline;的deadline是不能被直接使用的
在Java中,对象都是存储在堆中的

GregorianCalendar

Date类用来保存时间点,GregorianCalendar类用来给时间点命名
这使得中国阴历、希伯来阴历、泰国佛历等历法得以实现

new GregorianCalendar()  //构造日历类的一个实例   
new GregorianCalendar(1999, 11, 31)  //还可以给出时间,但是月份是从0记起的,也就是说这里表示的是1999-12-31而不是11月    
new GregorianCalendar(1999, Calendar.DECEMBER, 31)  //直接用常量来表示更为直观   
new GregorianCalendar(1999, Calendar.DECEMBER, 31, 23, 59, 59)  //还可以给出具体的时间点   

更改器方法与访问器方法

更改器方法(mutator method):对实例域作出修改的方法
访问器方法(accessor method):仅访问实例域而不进行修改的方法
GregorianCalendar类中的get方法为访问器方法,setadd方法为更改器方法

GregorianCalendar now = new GregorianCalendar();   //构造实例  
int month = now.get(Calendar.MONTH);  //用get方法访问now实例的月份    
now.set(Calendar.DAY_OF_MONTH, 15);  //用set方法修改now实例的日   
now.set(1999, 11, 31);  //也可以直接设置年月日甚至时分秒  
now.add(Calendar.MONTH, 3);  //用add方法修改now实例的月份使之增加三个月    

GregorianCalendar类的其他方法:

int getFirstDayOfWeek()  //获取当前用户所在地区的一星期中的第一天,如Calendar.SUNDAY   
Date getTime()  //获取当前值所表达的时间点    

日期格式规范的几个方法

/*java.text.DateFormatSymbols*/   
String[] getShortWeekdays()    
String[] getShortMonths()    
String[] getWeekdays()   
String[] getMonths()    
//分别获取当前地区的星期几、月份的缩写和完整名,分别以Calendar的星期和月份常量作为数组索引值     

用户自定义类

主力类(workhorse class)没有main方法
一个程序中只能有一个类有main方法,这个类必须是共有类(public),而且这个类的类名必须与.java文件名相同
编译时,每个类都会单独生成.class文件
运行程序时,只需要指向包含main方法的类名

定义示例

类的定义包含实例域、构造器、方法(访问器方法、更改器方法)

class Employee
{
    //instance fieds实例域
    private String name;
    private double salary;   
    private Date hireDay;    
    /*
     *私有变量,只有自身能够使用,外部无法访问
     *注意:为了保证类的“封装”性,应尽量使实例域私有化,外部只能通过访问器方法来访问
     */
    ...
    //constructor构造器   
    public Employee(String n, double s, int year, int month, int day)  //构造器与类名相同
    {
        name = n;
        salary = s;
        GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
        hireDay = calendar.getTime();
    }
    /*
     *当一个实例被创建时,会自动运行构造器
     *每个类可以有一个以上的构造器    
     *每个构造器可以带任意数量的参数   
     *构造器没有返回值  
     *构造器总是伴随new操作符使用,不能用来对一个实例进行重新设置
     *注意不要在构造器中定义与实例域重名的变量  
     */
    ...
    //methods方法(访问器方法)   
    public String getName()
    {
        return name;
    }
    public double getSalary()
    {
        return salary;
    }
    public Date getHireDay()
    {
        return hireDay.clone();   //返回引用可变对象的一个副本,防止外部直接修改对象的设置,以保证封装性
    }
    ...
    //methods方法(更改器方法)   
    public void raiseSalary(double byPercent)
    {//薪资增长
        double raise = salary * byPercent / 100;
        salary += raise;
    }
    //public表明构造器和方法公有,即除了自身可以调用外,外部也可以调用
}

多个源文件的使用

不同的类可以放在不同在源文件,只要分别编译了就能够运行
还可以每个类单独使用一个源文件(文件名与类名相同)
当编译包含main方法的源文件时,编译器会查找是否存在调用的自定义类对应的.class文件,否则则会查找对应的.java文件并主动进行编译
如果存在.java文件且比对应的.class文件新,那么该类也会被重新编译

隐式参数与显式参数

以上面的raseSalary方法为例

public void raiseSalary(double byPercent)
{//薪资增长
    double raise = salary * byPercent / 100;
    salary += raise;
}

其中的double byPercent为显式参数
而对象本身为隐式参数
如调用number007.raiseSalary(5)相当于

double raise = number007.salary * 5 /100;    
number007.salary += raise;   
//注意到salary相当于number007.salary     
//但是实例域是私有的,实际上在外部不能如此处理    

隐式参数用关键字this来表示,上面的调用也相当于

double raise = this.salary * 5 /100;    
this.salary += raise;   

有的程序员也习惯直接在方法定义中使用this,来显示表明这是类内的实例域

封装的优点

  1. 可以改变内部实现,如对数据做一定处理后通过访问器方法返回出来
  2. 更改器方法可以添加错误检查,而直接访问实例域无法自动进行检查
    注意:尽量不要编写返回引用可变对象的访问器方法,否则如果对该访问器返回的对象使用更改器方法会直接修改类内的私有实例域,从而破坏了封装性;如果需要返回,可以clone这个对象(即生成一个副本)后返回

基于类的访问权限

一个对象不仅可以访问自己的私有域,还可以访问同类对象的私有域
如:

class Employee
{
    ...
    public boolean equals(Employee other)
    {
        return name.equals(other.name);  //这里访问了同类(Employee)的其他对象(other)的私有域(name)
    }
}

私有方法

将方法定义的public修改为private即可使方法私有化而不被外部调用
通常用于编写实现其他公有方法的辅助方法

final实例域

对实例域使用final关键字可以使实例域被构造器初始化后就不可修改
final关键字常用于:基本类型域、不可变类的域
不应对可变类使用final关键字,如:private final Date hiredate;hiredate是一个不可变的引用变量,但他引用的Date对象却是可变的,此处的final会给读者造成混乱

静态域和静态方法

static修饰符

静态域

静态域属于类而不属于对象,故又称为类域
每个对象都共用一个静态域

静态常量

修饰符static final
实例域通常是私有的,但静态常量却是公有的,反正外部也无法改变常量
静态常量是属于类的,无需通过对象,而可以直接通过类来访问
Math.PI

静态方法

静态方法不能向对象实施操作(即不能使用隐式参数this),只能访问类的静态域(而不能访问对象的实例域)
Math.pow()
常用情形:

  1. 不需要访问对象状态,参数都是显示提供的
  2. 不需要访问对象的实例域,只需要类的静态域

通过对象来访问静态域、静态常量、静态方法也不是不可以,只是这样会造成混淆

C、C++、JAVA中的static修饰符
第一种含义(C):变量一直存在,当再次进入该块时依然存在
第二种含义(C):不能被其他文件访问的全局变量和函数
第三种含义(C++、JAVA):属于类且不属于对象的变量和函数

main方法

每个类都可以定义一个共用静态的main方法
其中一个用来执行程序的主体结构
其他用来分别对类进行单独的测试

方法参数

  • Java的方法参数都是按值调用,也就是说调用一个方法,它对外部的变量不会做任何的修改
    但是,当传入的是一个对象引用变量时,修改的是对象的设置,而并没有拷贝一个副本
  • 方法参数的两种类型:基本数据类型、对象引用
    注意:即使是对象引用,其传参方式依旧是值传递,JAVA是不支持地址传递的

C++中引用调用的参数标有&符号

对象构造

重载

  • 类的方法可以名字相同,但参数不同
  • 重载解析(overloading resolution):
    编译器会根据各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配
  • 如果编译器找不到匹配的参数,或者找除多个可能的匹配,就会产生编译错误

默认域初始化

默认情况下,如果没有给域赋初值,会被自动初始化为0falsenull
但这不是一种良好的编程习惯,最好能够显式地初始化域

无参数的构造器

如果编写类时没有给出任何构造器

那么系统会自动提供一个给每个域赋予默认初始化值的无参数构造器
如:

public Employee()
{
    name = "";
    salary = 0;
    hireDay = new Date();
}
如果编写类时给出了构造器

那么至少必须提供一个无参数构造器,否则被视为不合法
提供无参数构造器时可以直接给出一个空的无参数构造器,它将表示采取默认初始化值的无参数构造器
如:

public Employee()
{
}
显式域初始化

如果某些实例域无论在哪个构造器中都赋予相同的初值,那么可以直接在实例域的定义时赋值
而且初值无需是常量
如:

class Employee
{
    private static int nextId;
    private int id = assignId();
    ...
    private static int assignId()
    {
        int r = nextId;
        nextId++;
        return r;
    }
    ...
}

在C++中,不能够直接初始化类的实例域,但是可以通过特殊的“初始化器列表”语法来进行实例域的初始化

参数名

如果参数名与实例域重复,那么实例域名会被覆盖,但是可以通过隐式参数来使用

public Employee(String name, double salary)
{
    this.name = name;
    this.salary = salary;
}

调用另一个构造器

构造器可以通过隐式参数this来调用另一个构造器
如:

public Employee(double s)
{
    //调用Employee(String n, double s)
    this("Employee #" + nextId, s);
    nextId++;
}

此时调用new Employee(123)就相当于调用new Employee("Employee #123", 123)
意义:提高了代码的重用性

在C++中,构造器是不能调用另一个构造器的

初始化块

在类的声明中可以包含多个代码块,当一个对象被构建时,这些代码块会被自动执行
如:

class Employee
{
    private static int nextId;
    private int id;
    private String name;
    private double salary;
    //初始化块    
    {
        id = nextId;
        nextId++;
    }
    ...
}
  • 初始化块放在域定义之后是合法的
    但这样容易引发循环定义,因此
    尽量将初始化块放在域定义之后

静态域的初始化块

static
{
    Random generator = new Random();
    nextId = generator.nextInt(10000);
}

初始化细节

初始化有三种方法:定义时初始化、初始化块、构造器初始化
当构造一个对象时,初始化的顺序是这样的——

  1. 所有数据域被初始化位默认值(0、false、null)
  2. 依次执行所有域初始化语句和初始化块
  3. 执行构造器的主体

对象析构和finalize方法

C++中,通常支持显示的析构器方法,用于回收分配给对象的存储空间
但是JAVA有垃圾回收机制,所以Java不需要也不支持析构器

Java支持名为finalize的方法
它将在垃圾回收期清楚对象之前被调用,用于回收内存以外的资源
但是不要依赖它来回收任何短缺的资源,因为很难知道这个方法被调用的具体时刻

Java允许使用包(package)将类组织起来

  • 包可以使用嵌套层次
  • 所有的标准包都处于java或javax包层次中
  • 从编译器的角度看,嵌套的包之间没有任何关系,每一个都拥有独立的类集合

类的导入

一个类可以使用所属包的所有类 和 其他包的公有类

使用类的时在类名前添加完整的包名

如:

java.util.Date today = new java.util.Date();   
利用import语句导入类

如:

import java.util.Date;
Date today = new Date();   

也可以导入整个包:

import java.util.*;   

但绝对不要导入所有标准包的所有类,如import java.*.*;

如果导入多个包的所有类,而且他们有重复的类名时,编译器会报错:

import java.util.*;   //包含Date类   
import java.sql.*;    //也包含Date类   

此时可以显示地导入特定类,来指明来源的包:

import java.util.*;   
import java.sql.*;    
import java.util.Date;   //告知编译器,导入的是java.util包内的Date类     

如果两个类都要同时使用,那就只能通过完整名来使用类啦:

java.util.Date deadline = new java.util.Date();   
java.sql.Date today = new java.sql.Date(...);    

静态导入

可以导入类的静态方法和静态域
如:

import static java.lang.System.*;  //导入System类的所有静态方法和静态域      
out.println("Goodbye, World!");    //无需再指明System类    

也可以导入特定静态方法或特定静态域
如:

import static java.lang.System.out;  //只导入System类的out方法   

将类放入包中

在源文件开头添加package语句,如

package com.horstmann.corejava;   
public class Empoyee
{
    ...
}
如果不在源文件中添加package语句

那么源文件中的类都会放入一个默认包中(default package),默认包是没有名字的

包中的文件必须放到与包名相匹配的目录中

以主体结构的源文件为基目录
com.horstmann.corejava的源文件应放在com/horstmann/corejava目录下

编译包含主体结构的源文件时,编译器就会自动到相应的目录查找文件并进行编译

当然,也可以单独编译源文件并运行它的类,但参数给出的形式不同

# 通过指定路径来编译
javac com/mycompany/PayrollApp.java
# 通过指定包来运行   
java com.mycompany.PayrollApp

注意:编译器在编译源文件时不会检查目录结构,如果对应包的源文件没有在指定目录,那么编译不会出错,但最终程序却无法运行

包作用域

  • public修饰的部分,可以被任意类使用
  • private修饰的部分,只能被定义它们的类使用
  • 既没有public也没有private修饰的部分,可以被同一包内的任何类使用

用户无法自定义java.的类

类路径

JAR文件

类文件也可以存储在JAR(Java归档)文件中,文件中可以包含多个压缩形式的类文件和子目录,可以节省空间并改善性能
JAR文件使用ZIP格式组织文件和子目录,可以使用ZIP使用程序查看

使类能被多个程序共享的几个要求

  1. 把类都放到一个目录中
    如:/home/user/classdir
  2. 把JAR文件放在一个目录中
    如:/home/user/archives
  3. 设置类路径(class path)

类路径

类路径通常包含基目录、当前目录、jar文件目录

类路径格式
  • UNIX环境中,以冒号:为分隔符
    如:/home/user/classdir:.:/home/user/archives/archive.jar
    其中句点.表示当前目录
    javac编译器总在当前目录中查找文件,但java虚拟机仅在类路径有句点.时才查找当前目录
  • Windows环境中,以分号;为分隔符
    如:c:\classdir;.;c:\archives\archive.jar
  • 运行时库文件会被自动搜索,无需显式地将它们列在类路径中
文件定位顺序
  • 对于虚拟机
    1. 查看jre/libjre/lib/ext目录下的归档文件中所存放的系统类文件
    2. 按照类路径的顺序依次查找各个目录和归档文件
  • 对于编译器
    • 如果引用了一个类但没有指定包
      那么会查询所有import命令导入的包的所有源文件
      如果找不到这个类或者找到多个类则会报错
    • 如果引用了一个类且指定了包
      那么会查询这个包下的所有源文件
      如果找不到这个类或者找到多个类则会报错
设置类路径
为虚拟机添加参数

采用-classpath-cp选项为虚拟机指定类路径
如:java -cp /home/user/classdir:.:/home/user/archives/archive.jar MyProg

设置环境变量CLASSPATH

以bash为例
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar

文档注释

  • /***/注释的内容会自动由javadoc工具生成一个HTML文档
  • 文档从以下特性中抽取信息
    • 公有类与接口
    • 公有的与受保护的构造器方法
    • 公有的和受保护的域
  • 注释内容可以包含HTML标签
    • 但不要使用<h1><hr>,防止和文档格式冲突
    • 如果文档中包含其他文件(如图像)的链接
      那么文件需要放在子目录doc-files中,javadoc将从源拷贝其中的文件到文档目录中
      <img src="doc-files/uml.png" alt="UML diagram">

类注释

类注释放在import语句之后,类定义之前

方法注释

方法注释放在所描述的方法之前
可以使用以下特殊的标记——

  • @param变量描述
    为“参数”部分添加一个条目,该描述可以占多行
  • @return描述
    对“返回”部分添加条目,可以占多行
  • @throws类描述
    表示该方法可能出现的异常

如:

/**  
 * Raises the salary of an employee.  
 * @param byPercent the percentage by which to raise the salary (e.g. 10 means 10%)
 * @return the amount of the raise
 */
public double raiseSalary(double byPercent)
{
    ...
}

域注释

只对公有域(通常指的是静态常量)建立文档
如:

/**
 * The "Hearts" card suit
 */
public static final int HEARTS = 1;

通用注释

可以使用以下标记

  • @author姓名
    产生一个“作者”条目,可以使用多个
  • @version文本
    产生一个“版本”条目,为对当前版本的描述
  • @since文本
    产生一个“始于”条目,对引入特性的版本的描述
  • @deprecated文本
    对类、方法、变量添加一个不再使用的注释,并给出取代的建议
    如:@deprecated Use <code> setVisible(true) </code> instead
  • @see引用
    see also部分添加一个超链接
    • @see com.horstmann.corejava.Employee#raiseSalary(double)
      • 包名和类名可以省略,此时表示当前包或当前类
      • 类名和方法名必须用井号#隔开,因为javadoc无法自动区分类名和方法名
    • @see <a href="www.horstmann.com/corejava.html">The Core Java home page</a>
      可以使用a标签提供一个超链接
    • @see "Core Java 2 volume 2"
      可以添加双引号""使双引号的内容显示到文档上
    • 可以为一个特性添加多个@see标记,但是必须放在一起
  • @link引用
    指向其他类或方法的超链接
    {@link package.class#feature label}

包与概述注释

有两种方法书写包注释

  • 提供一个package.html文件
    <body>标签内的内容都会被提取出来形成文档
  • 提供一个package-info.java文件
    一个javadoc注释紧跟在包语句后
    不该添加其他的代码或注释

注释的抽取

(假设HTML文件放在目录docDirectory下)

  • 切换到包含想要生成文档的源文件目录
  • 调用javadoc命令生成文档
    • 如果是一个包
      javadoc -d docDirectory nameOfPackage
    • 如果有多个包
      javadoc -d docDirectory nameOfPackage1 nameOfPackage2 ...
    • 如果文件在默认包中
      javadoc -d docDirectory *.java

类设计技巧

  • 一定要保证数据私有
    • 防止破坏封装性
    • 访问器方法、更改器方法
    • 实例域私有化
  • 一定要对数据初始化
    • Java系统只会对对象的实例域进行初始化,而不会对局部变量进行初始化
    • 不要依赖系统的初始化,应该显式的初始化所有数据
  • 不要在类中使用过多的基本类型
  • 不是所有的域都需要独立的域访问器和域更改器
    如常量
  • 将职责过多的类进行分解
  • 类名和方法名要能够体现它们的职责
    一般以形容词(动名词)+名词来命名
    访问器以get为前缀,更改器以set为前缀

《Java核心技术》Chap3笔记

Chap3 JAVA的基本程序设计结构

JAVA语法与C++语法相似,此处仅列出与C语法不同的语法点

  • JAVA虚拟机从指定类的main方法开始执行
    • main方法必须是静态的
    • main方法不直接给操作系统返回“退出代码”,若正常退出,java应用程序返回0。如果希望返回其他代码,需要调用System.exit方法
  • JAVA所有函数都属于某个类的方法

注释

JAVA有三种注释方法

  1. //
  2. /**/
  3. /***/:将注释内容自动生成文档

数据类型

JAVA是一种强类型语言

整型

四种整型类型
  • int:4字节,约+-20亿
  • short:2字节,约+-3万
  • long:8字节,约+-9e19
  • byte:1字节,-128~127
整型常数表示:
  • 默认:十进制
  • 后缀L:长整型
  • 前缀0:八进制
  • 前缀0x:十六进制
  • 前缀0b:二进制
  • 常数表示时可以人为的加上下划线_来使其更容易阅读
    如:1_000_000编译器会自动去除下划线

浮点类型

两种浮点类型
  • float:4字节,6~7位有效数字
  • double:8字节,15位有效数字
  • 通常使用double类型,除非需要快速处理单精度类型数据、需要存储大量单精度类型数据
浮点类型常数表示:
  • 默认:double类型
  • 后缀F:float类型
  • 后缀D:double类型
浮点类型的特殊常量
  • Double.POSITIVE_INFINITY:无穷大
  • Double.NEGATIVE_INFINITY:无穷小
  • Double.NaN:“not a number”非数值
  • float类型同理
  • 判断一个特定值是否为数值
    • 不可以使用if(x == Double.NaN)
    • 但可以使用if(Double.isNaN(x))
浮点类型存在舍入误差

这是因为浮点数值是采用二进制系统表示的,
(2.0-1.1)将得到的是0.899999999999999而不是0.9
所以浮点数值不适用于进制出现舍入误差的金融计算当中,此时可以使用BigDecimal

字符类型

16字节的UTF16编码格式
JAVA中不建议使用char类型,最好将需要处理的字符串用抽象数据类型表示

boolean类型

  • 只有false和true两个值
  • 不能和整型相互转换
    if( x=0 ),表达式x=0的值为整型0
    • 在C语言中,该语句符合语法
    • 但在JAVA中,该语句不符合语法,因为条件表达式要求是boolean类型,而boolean类型和整型不能相互转换

变量

声明

  • 可以使用除了“+”、空格等以外的任何unicode字符(包括中文)
  • 变量名长度没有限制
  • 一次可以声明多个变量,但不建议这样做,每行声明一个变量并给出相应的注释能提高程序的可读性
  • 变量声明应尽可能靠近第一次使用的地方,而不一定都在函数开头
  • JAVA不区分“定义”和“声明”
  • 局部变量不会自动初始化为null

常量

  • 定义借助final关键字,如final double pi=3.14
  • 常量只能被赋值一次
  • “const”也是JAVA的保留字,但并不使用

运算符

一般运算

  • 整数除以0会引发异常,浮点数除以0会得到无穷大或NaN
  • 支持+=++A?B:C、位运算符
  • 对两个boolean值进行&|运算,两边的表达式都要经过计算
    但若进行&&||运算,则采取“短路”原则
  • C和C++对于>>运算采取“符号拓展”还是高位补0是不确定的
    但JAVA规范为“符号拓展”,即可用于任意整型
  • JAVA不支持幂运算符^
  • JAVA不支持逗号运算符,但在for语句中可以用for分隔表达式列表

数学函数与常量

包含于数学类Math

  • 幂运算:Math.pow(x,a)
  • 其他:
    Math.sin()
    Math.cos()
    Math.tan()
    Math.atan()
    Math.exp()
    Math.log()
    Math.log10()
    等等
  • 数学常量
    Math.PI
    Math.E
    等等
  • Math类的方法都采用计算机浮点单元中的例程以获得最快的性能
    如果需要确保在所有平台上得到相同的结果,可以使用StrictMath

类型转换

Java类型转换
虚线表示转换过程中会损失精度
运算过程中会自动将操作数往“高级别”的类型转换
“级别”顺序:double,float,long,int,“others”

强类型转换

同C语言

  • 浮点数转换为整型
    • 直接使用(int)等将自动截断小数部分
    • 如果需要进行舍入运算,应借助Math.round()方法
  • 如果尝试将一种类型强制转换为超出范围的另一种类型,其数值将会被截断
  • 一般不要将boolean类型强制转换为其他类型,也不要将其他类型强制转换为boolean类型
    如果非要这么做,可以借助三元运算符b?1:0

优先级和结合性

Java优先级和结合性
与C语言几乎一致,除了少量的特性增减
大致顺序如下:

  1. 初等运算符、方法调用
  2. 一元运算符、new
  3. 算术运算符(先乘除,后加减)
  4. 位“移”运算符
  5. 关系运算符(但==!=优先级较低)、instanceof
  6. 位“与”
  7. 位“异”
  8. 位“或”
  9. 逻辑运算符(除了!,且&&优先级较高)
  10. 条件运算符
  11. 赋值运算符

枚举类型

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
Size s = Size.MEDIUM;   

此时的Size类型变量只能存储这个类型枚举出来的某个值,或是null

字符串

没有内置的字符串类型,但有预定义的String类
双引号扩起来的字符串都是String类的一个实例
java字符串都是Unicode字符序列

子串

String类的substring方法可以提取从较长的字符串中提取出一个子串

String greeting = "Hello";
String s = greeting.substring(0, 3);   

注意:这里提取的是0-2的字符,即大于等于0,小于3

拼接

字符串可以用加号+进行拼接
如果字符串跟一个非字符串数值进行拼接,后者将会转换为一个字符串(任何java对象都可以转换成字符串)

不可变字符串

String类对象是不可变字符串,即字符串内容不可以改变
倘若要改变字符串内容,可以借助子串和拼接
如:将greeting变量的内容从“Hello”改为“Help!”

greeting = greeting.substring(0, 3) + "p!";

优点:编译器可以让字符串共享,而通常情况下,共享的使用频率远高于提取、拼接字符串
共享:想象各种字符串存放在公共的存储池,变量指向存储池中相应的位置;复制字符串变量时,原始字符串和复制的字符串共享相同的字符
但有时侯需要用到构建字符串StringBuilder类

另外,当String类的实例指向另一个字符串(重新赋值)时,原始字符串会被垃圾回收机制自动回收

检测字符串是否相等

使用equals方法检测两个字符串是否相等

s.equals(t);  

该方法返回一个boolean值
如果需要忽略大小写,可以使用equalsIgnoreCase方法

  • C语言中用strcmp函数进行字符串比较
    java中也有对应的compareTo方法,但是用equals方法更为直观
  • C++语言中直接用==来判断两个字符串是否相等,
    但在JAVA中用==判断的是两个字符串是否在同一位置(有可能共享),一般是没有意义的

空串和null串

空串:长度为0(str.length() == 0),内容为空(str.equals("")
null串:目前没有任何对象与该变量关联(str == null

代码点与代码单元

大多数常用的Unicode字符都使用一个代码单元表示,但辅助字符等需要一对代码单元表示
无论是用一个代码单元还是用一对代码单元表示,每个字符就作为一个代码点

  • length()方法返回的是代码单元的数量
  • codePointCount(x, y)方法返回代码单元为x至(y-1)的代码点数量
    int cpCount = greeting.codePointCount(0, greeting.length())可以获取一个字符串的代码点数量
  • charAt(i)方法返回位置为i的代码单元
    如果想获取位置为i的代码单元,可以这么做
    int index = greeting.offsetByCodePoints(0, i);
    int cp = greeting.codePointAt(index);
    注意:codePointAt()方法的参数为代码单元的索引,而不是代码点的

常用字符串方法

char charAt(int index)      
int codePointAt(int index)     
int offsetByCodePoints(int startIndex, int cpCount)  
int compareTo(String other)  
boolean endsWith(String suffix)  
boolean equals(Object other)     
boolean equalsIngoreCase(String other)  
int indexOf(String str [, int fromIndex])  
int indexOf(int cp [, int fromIndex])   //返回(以fromIndex为索引0的)与str字符串或cp代码点匹配的第一个子串的开始位置的索引,若不存在,返回-1  
int lastIndexOf(String str [, int fromIndex])  
int lastIndexOf(int cp [, int fromIndex])   //最后一个子串   
int length()  
int codePointCount(int startIndex, int endIndex)   //startIndex到endIndex-1之间   
String replace(CharSequence oldString, CharSequence newString)   //返回新串,新串中将旧串中的oldString中的字符映射位newString中的对应字符    
boolean startWith(String prefix)   
String substring(int beginIndex [, int endIndex])  //startIndex到endIndex-1之间  
String toLowerCase()   
String toUpperCase()   
String trim()    //删除头部和尾部的空格     

注意:java一般使用的是不可变字符串,因此上述方法都是返回新串,而不是进行原地操作

构建字符串

有时候需要用较短字符串构建长字符串(如按键、文件中的单词等)
采用String类来拼接字符串每次都产生新串,耗时费空间
此时可以使用StringBuilder

使用方法
  1. 构建一个空的字符串构建器
    StringBuilder builder = new StringBuilder();
  2. 不断添加子串或字符
    builder.append(ch);
    builder.append(str);
  3. 将builder下的字符串和字符构建成长字符串并返回
    String completedString = builder.toString();
StringBuilder类的常用方法
StringBuilder()          
int length()          
StringBuilder append(String str)     
StringBuilder append(char c)           
StringBuilder appendCodePoint(int cp)     
void setCharAt(int i, char c)    
StringBuilder insert(int offset, String str)       
StringBuilder insert(int offset, char c)    
StringBuilder delect(int startIndex, int endIndex)     
String toString()   

注意:上述返回StringBuilder的方法,都是返回this

输入输出

输入

相关方法
/*java.util.Scanner*/
Scanner(InputStream in)  //用给定的输入流in创建一个Scanner对象  
String nextLine()  //读取一行   
String next()  //读取一个单词(以空格为分隔符)   
int nextInt()  //读取一个整型  
double nextDouble()  //读取一个浮点数   
boolean hasNext()  //检测是否还有单词可以读取   
boolean hasNextInt()  
boolean hasNextDouble()  

/*java.lang.System*/  
static Console console()  //如果有可能进行交互操作,则通过控制台窗口为用户返回一个Console对象;否则返回null    

/*java.io.Console*/  
static char[] readPassword(String prompt, Object...args)   
static String readLine(String prompt, Object...args)    //显示prompt,并读取用户输入   
常见用法
import java.util.*;  //加载java.util包
Scanner in = new Scanner(System.in);  //以标准输入流创建一个Scanner对象  
System.out.print("What is your name?");  //输出到标准输出流  
String name = in.nextLine();  //从标准输入流读取一行   

格式化输出

Java沿用了C语言库函数中的printf,此处只列出不同之处

转换符

%d%x%o%f%e%g%s%c%%
%b:布尔
%h:散列码
%tx:日期时间,此处x为额外的参数
%n:行分隔符
%a:十六进制浮点数

字符串输出

System.out.print()
System.out.println()输出一行(自动换行)

标志

+空格0-,
(:将负数括在括号内
,:添加分组分隔符,如3,333,333
#:对于f格式,添加小数点;对于x或0格式,添加0x或0
$:给定被格式化的参数索引,如%1$d, %1$x表示以十进制和十六进制打印第一个参数,注意参数索引是从1开始,而不是0
<:沿用上一个参数的值,如%d%<x表示以十进制和十六进制打印同一个参数

格式化后存储字符串(而不直接输出)

String.format()方法

日期时间参数(即%tx中的x)

%tc:完整的日期和时间,如Mon Feb 09 18:05:19 PST 2004
%tF:ISO8601日期,如2004-02-09
%tD:美国格式的日期,如02/09/2004
%tT:24小时时间,如18:09:19
%tr:12小时时间,如06:09:19pm
%tR:24小时时间(没有秒),如18:09
%tY:年份,如:2004
%tB:月份的完整拼写,如February
%tb:月份的缩写,如Feb
%tm:月份的数字,如02
%td:日的数字,如09
%tA:星期几的完整拼写,如Monday
%ta:星期几的缩写,如Mon
%tH:24小时数字,如18
%tI:12小时数字,如06
%tM:分钟的数字,如05
%tS:秒钟的数字,如19
%tL:三位毫秒数字,如047
%tN:九位毫秒数字,如047000000
%tP:上下午标志(大写),如PM
%tp:上下午标志(小写),如pm
%tZ:时区,如PST

其他:%ty%tC%te%tj%tk%tl%tz%ts%tQ

使用方法:
System.out.printf( "%1$s %2$tB %2$te %2$tY", "Due date", new Date() );
System.out.printf( "%s %tB %<te %<tY", "Due date", new Date() );

文件输入输出

文件输出
Scanner in = new Scanner(Paths.get("myfile.txt"));    //用File对象构造一个Scanner对象,注意不要直接用字符串“myfile.txt”构造Scanner对象;如果文件名中存在反斜杠,则需要用两个反斜杠(第一个反斜杠为转义)    

如果用一个不存在的文件构造一个Scanner,则会发生异常

文件输入
PrintWriter out = new PrintWriter("myfile.txt");    //构造一个PrintWriter对象,如果不存在则创建    
out.print(".....");   
out.println("......");
out.printf(".....", ....);    

如果用一个不能被创建的文件名构造PrintWriter,则会发生异常

文件路径

可以用绝对路径,也可以使用相对路径
如果是在控制台调用java,则以控制台的当前路径为基准;
如果是在集成环境调用java,则以IDE设定为基准;
可以通过String dir = System.getProperty("user.dir");来获取当前的路径

流程控制

与C语言基本一致,只有以下少数区别

嵌套的语句块内不能重定义外层语句块的同名变量

public static void main(String[] args)
{
    int n;
    ...
    {
        int k;
        int n;  //Error!!!!!!!!1
    }
}

没有goto语句,但是break语句可以带标签

可以利用带标签的break语句跳出多层循环
但“goto”仍作为Java的保留字

在循环中检测两个浮点数是否相等需要特别小心

for(double x=0; x!=10; x+=0.1) ....有可能永远不会跳出循环
因为浮点数存在舍入误差,x可能从9.99999999999998跳到10.09999999999998

switch语句中的case

  • 允许使用字符串常量
  • 使用枚举常量时,无需列出枚举名

    Size sz = ...;
    switch (sz)
    {
        case SMALL:   //不需要用Size.SMALL    
            ....
            break;
        ....
    }
    

大数值

java.math包中包含了BigInteger类和BigDecimal类,
分别用来支持任意精度的整型和浮点型计算

相关方法

/*java.math.BigInteger*/
BigInteger add(BigInteger other)     
BigInteger subtract(BigInteger other)   
BigInteger multiply(BigInteger other)  
BigInteger divide(BigInteger other)   
BigInteger mod(BigInteger other)     
int compareTo(BigInteger other)     
static BigInteger valueOf(long x)     //将x转换为BigInteger并返回   

/*java.math.BigDecimal*/
BigDecimal divide(BigDecimal other, RoundingMode mode)   //必须给除舍入方式,RoundingMode.HALF_UP即为四舍五入     
static BigDecimal valueOf(long x [, int scale])   //将x(x/10^scale)转换为BigDecimal并返回   
//....基本与BigInteger类似   

使用示例(以BigInteger为例)

BigInteger a = BigInteger.valueOf(100);  
BigInteger b = BigInteger.valueOf(10);
BgiInteger c;
c = a.add(b);       //c = a + b;   
c = a.subtract(b);  //c = a - b;   
c = a.multiply(b);  //c = a * b;   
c = a.divide(b);    //c = a / b;   
c = a.mod(b);       //c = a % b;  
if( a.compareTo(b) ) ;   //if(a - b) ;   

数组

声明

int[] a;  //空的数组,不能直接使用    
int[] a = new int[100];  //构造含有100个元素的数组   
int[] a = new int[n];    //元素个数可以是变量   

数字数组:所有元素初始化为0
boolean数组:所有元素初始化为false
对象数组(如字符串数组):所有元素初始化为null

获取数组长度:a.length,这是数组的一个属性,而不是方法,不需要加括号

for each循环

for( int element : a )      
    .... ;

将可迭代对象(实现Iterable接口的对象)的每个程序暂存于变量element

如果需要打印整个数组,可以这样使用System.out.println( Arrays.toString(a) ),将得到诸如[2,3,5,7,11,13]的输出

数组初始化、匿名数组

int[] smallPrimes = {2, 3, 5, 7, 11, 13};    //注意无需使用new   
smallPrimes = new int[] {17, 19, 23, 29, 31, 37};   //给smallPrimes重新初始化    

Java还允许长度为0的数组,如new elementType[0]
注意:数组长度为0与null不同

数组拷贝

浅复制(共享)
int[] luckyNumbers = smallPrimes;    //两个名字共享一个数组    
深复制

借助Arrays.copyOf(Arrays x, int length)方法,拷贝x数组的length个元素生成新数组并返回

int[] copiedLuckyNumbers = Arrays.copyOf(smallPrimes, smallPrimes.length);   

也可以巧妙地用来增长数组:

luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);  //将数组增长一倍   

命令行参数

public static void main(String[] args)中的args即为命令行参数
与C不同的是,命令行参数不包含文件名
即对于命令java Message -h world,程序获取的第一个参数,即args[0]是-h而不是javaMessage

数组排序

Arrays.sort(a):原地操作,用优化的快速排序法对a数组进行排序
Math.random()方法可以返回一个0-1但不包含1的随机浮点数(乘以n后可以获得0-(n-1)的随机整数)

相关方法

static String toSrting(type[] a)     
static type copyOf(type[] a, int length)    //如果length > a.length,将返回false或0  
static type copyOfRange(type[] a, int start, int end)  //如果end > a.length-1,将返回false或0    
static void sort(type[] a)   
static int binarySearch(type[] a [, int start, int end], type v)  //二分搜索查找v元素的位置,若查找失败,返回负数r; (-r-1)表示为了维持数组的顺序,v应插入的位置    
static void fill(type[] a, type v)  //将数组a的所有元素设置为v    
static boolean equals(type[] a, type[] b)  //当两个数组大小相等,且每个元素对应相等时(此时两个数组未必共享),返回true    

多维数组

double[][] balances;
double[][] balances = new double[10][10];  
int[][] magicSquare = 
    {
        {16, 3, 2, 13},
        {5, 10, 11, 8},
        {9, 6, 7, 12},  
        {4, 15, 14, 1}
    };  
System.out.println( Array.deepToString(a) );  //快速打印整个多维数组    

不规则数组

Java的多维数组实际上是“数组的数组”,可以方便地构造不规则数组

//例如构造一个三角形的不规则数组
int[][] odds = new int[10][];
for(int n = 0; n < 10; n++)
    odds[n] = new int[n+1];